7、三维机械设计、装配与运动仿真组件 - /设计与仿真组件/3d-mechanical-designer
// 三维机械设计、装配与运动仿真通用组件
// 初始化Three.js相关变量
let scene, camera, renderer, controls, transformControls, raycaster, mouse;
let robot = { parts: [], joints: [] };
let selectedObject = null;
let clock = new THREE.Clock();
let isSimulating = false;
let keyframes = [];
let currentTime = 0;
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
initScene();
initEventListeners();
createExampleRobot();
// 初始化完成后隐藏加载动画(如果有)
const loadingOverlay = document.querySelector('.loading-overlay');
if (loadingOverlay) {
loadingOverlay.style.display = 'none';
}
});
// 初始化场景
function initScene() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf8f8f8);
// 添加网格
const gridHelper = new THREE.GridHelper(10, 10, 0xcccccc, 0xe0e0e0);
scene.add(gridHelper);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
// 添加定向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 设置相机
const canvas = document.getElementById('robot-canvas');
const aspect = canvas.clientWidth / canvas.clientHeight;
camera = new THREE.PerspectiveCamera(45, aspect, 0.1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 设置渲染器
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
// 添加轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// 添加变换控制器
transformControls = new THREE.TransformControls(camera, renderer.domElement);
transformControls.addEventListener('dragging-changed', (event) => {
controls.enabled = !event.value;
});
scene.add(transformControls);
// 创建射线投射器,用于选择对象
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// 窗口大小变化时更新渲染器尺寸
window.addEventListener('resize', onWindowResize);
// 开始渲染循环
animate();
}
// 窗口大小变化事件处理器
function onWindowResize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height, false);
}
// 渲染循环
function animate() {
requestAnimationFrame(animate);
// 更新轨道控制器
controls.update();
// 如果正在仿真,执行运动仿真
if (isSimulating) {
updateSimulation();
}
// 更新坐标显示
updateCoordinatesDisplay();
// 更新FPS显示
updateFPS();
// 渲染场景
renderer.render(scene, camera);
}
// 更新坐标显示
function updateCoordinatesDisplay() {
const cameraPosition = camera.position;
document.getElementById('coordinates-display').textContent =
`X: ${cameraPosition.x.toFixed(2)} Y: ${cameraPosition.y.toFixed(2)} Z: ${cameraPosition.z.toFixed(2)}`;
}
// 更新FPS显示
let frameCount = 0;
let prevTime = performance.now();
function updateFPS() {
frameCount++;
const currentTime = performance.now();
if (currentTime - prevTime >= 1000) {
const fps = Math.round((frameCount * 1000) / (currentTime - prevTime));
document.getElementById('fps-display').textContent = `FPS: ${fps}`;
frameCount = 0;
prevTime = currentTime;
}
}
// 添加机器人关节
function addJoint(type, position) {
const geometry = new THREE.SphereGeometry(0.2, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 0xff5722 });
const joint = new THREE.Mesh(geometry, material);
joint.position.copy(position || new THREE.Vector3(0, 0, 0));
joint.userData.isJoint = true;
joint.userData.type = type || 'revolute';
joint.userData.range = 90;
joint.userData.speed = 1;
joint.userData.angle = 0;
scene.add(joint);
robot.joints.push(joint);
updateModelInfo();
return joint;
}
// 添加机器人连杆
function addLink(startJoint, endJoint) {
if (!startJoint || !endJoint) return null;
// 计算连杆位置和方向
const start = startJoint.position.clone();
const end = endJoint.position.clone();
const direction = end.clone().sub(start);
const length = direction.length();
// 创建连杆几何体
const geometry = new THREE.CylinderGeometry(0.1, 0.1, length, 16);
geometry.translate(0, length / 2, 0);
// 创建连杆材质
const material = new THREE.MeshStandardMaterial({ color: 0x2196F3 });
const link = new THREE.Mesh(geometry, material);
// 设置连杆位置和旋转
link.position.copy(start);
link.lookAt(end);
link.rotateX(Math.PI / 2);
// 设置连杆的自定义数据
link.userData.isLink = true;
link.userData.startJoint = startJoint;
link.userData.endJoint = endJoint;
scene.add(link);
robot.parts.push(link);
updateModelInfo();
return link;
}
// 更新模型信息显示
function updateModelInfo() {
document.getElementById('model-info').textContent =
`零件: ${robot.parts.length} | 关节: ${robot.joints.length}`;
}
// 选择对象
function selectObject(object) {
if (selectedObject === object) return;
// 取消先前选择的对象
if (selectedObject) {
if (selectedObject.material && selectedObject.material.emissive) {
selectedObject.material.emissive.setHex(0x000000);
}
}
// 设置新选择的对象
selectedObject = object;
if (selectedObject) {
// 高亮选中的对象
if (selectedObject.material && selectedObject.material.emissive) {
selectedObject.material.emissive.setHex(0x333333);
}
// 应用变换控制器
transformControls.attach(selectedObject);
// 更新属性面板
updatePropertiesPanel();
} else {
transformControls.detach();
document.getElementById('selected-item-name').textContent = '无';
}
}
// 更新属性面板
function updatePropertiesPanel() {
if (!selectedObject) return;
// 更新选中项目名称
let itemName = selectedObject.userData.isJoint ? '关节' :
selectedObject.userData.isLink ? '连杆' : '物体';
document.getElementById('selected-item-name').textContent = itemName;
// 更新位置
document.getElementById('position-x').value = selectedObject.position.x.toFixed(2);
document.getElementById('position-y').value = selectedObject.position.y.toFixed(2);
document.getElementById('position-z').value = selectedObject.position.z.toFixed(2);
// 更新旋转(转换为度数)
const rotation = selectedObject.rotation.toArray().slice(0, 3).map(rad => (rad * 180 / Math.PI).toFixed(0));
document.getElementById('rotation-x').value = rotation[0];
document.getElementById('rotation-y').value = rotation[1];
document.getElementById('rotation-z').value = rotation[2];
// 更新尺寸
document.getElementById('scale').value = selectedObject.scale.x.toFixed(1);
// 更新材质颜色
if (selectedObject.material && selectedObject.material.color) {
const color = '#' + selectedObject.material.color.getHexString();
document.getElementById('material-color').value = color;
}
// 更新关节特有的属性
const jointProperties = document.querySelectorAll('.joint-property');
if (selectedObject.userData.isJoint) {
jointProperties.forEach(el => el.style.display = 'flex');
// 更新关节类型
document.getElementById('joint-type').value = selectedObject.userData.type || 'revolute';
// 更新关节范围
document.getElementById('joint-range').value = selectedObject.userData.range || 90;
document.getElementById('joint-range-value').textContent = `${selectedObject.userData.range || 90}°`;
// 更新关节速度
document.getElementById('joint-speed').value = selectedObject.userData.speed || 1;
} else {
jointProperties.forEach(el => el.style.display = 'none');
}
}
// 更新模型参数
function updateModelParameters() {
if (!selectedObject) return;
// 更新位置
selectedObject.position.set(
parseFloat(document.getElementById('position-x').value),
parseFloat(document.getElementById('position-y').value),
parseFloat(document.getElementById('position-z').value)
);
// 更新旋转(转换为弧度)
selectedObject.rotation.set(
parseFloat(document.getElementById('rotation-x').value) * Math.PI / 180,
parseFloat(document.getElementById('rotation-y').value) * Math.PI / 180,
parseFloat(document.getElementById('rotation-z').value) * Math.PI / 180
);
// 更新尺寸
const scale = parseFloat(document.getElementById('scale').value);
selectedObject.scale.set(scale, scale, scale);
// 更新材质颜色
if (selectedObject.material && selectedObject.material.color) {
const color = document.getElementById('material-color').value;
selectedObject.material.color.set(color);
}
// 对于关节,更新特有属性
if (selectedObject.userData.isJoint) {
selectedObject.userData.type = document.getElementById('joint-type').value;
selectedObject.userData.range = parseInt(document.getElementById('joint-range').value);
selectedObject.userData.speed = parseFloat(document.getElementById('joint-speed').value);
document.getElementById('joint-range-value').textContent = `${selectedObject.userData.range}°`;
}
// 如果是连杆,可能需要更新其几何形状
if (selectedObject.userData.isLink) {
updateLinkGeometry(selectedObject);
}
}
// 更新连杆几何形状
function updateLinkGeometry(link) {
if (