如何用Three.js的TrackballControls编程实现CAD式缩放、旋转、平移按钮触发操作?
Hey there, let's build this out step by step. I've worked on similar CAD-style view controls with TrackballControls before, so I'll walk you through setting up both native mouse interactions and button-triggered actions that fit perfectly for CAD workflows.
1. 基础环境搭建
First, let's set up the core Three.js components, add TrackballControls, and include a test CAD model (we'll use a cube for simplicity):
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>CAD View Controls</title> <style> body { margin: 0; } canvas { display: block; } .control-buttons { position: fixed; top: 15px; left: 15px; z-index: 100; } button { padding: 10px 16px; margin: 0 6px; border: none; border-radius: 4px; background: #2194ce; color: white; cursor: pointer; font-size: 14px; } button:hover { background: #1a7cb8; } </style> </head> <body> <div class="control-buttons"> <button id="zoomBtn">缩放视图</button> <button id="rotateBtn">旋转视图</button> <button id="panBtn">平移视图</button> </div> <script type="module"> import * as THREE from 'three'; import { TrackballControls } from 'three/addons/controls/TrackballControls.js'; // 初始化场景、相机、渲染器 const scene = new THREE.Scene(); scene.background = new THREE.Color(0xf5f5f5); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(6, 6, 6); camera.lookAt(scene.position); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加测试CAD模型 const geometry = new THREE.BoxGeometry(3, 3, 3); const material = new THREE.MeshStandardMaterial({ color: 0x1e88e5, roughness: 0.3, metalness: 0.2 }); const cadModel = new THREE.Mesh(geometry, material); scene.add(cadModel); // 添加光源(让CAD模型细节更清晰) const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9); directionalLight.position.set(8, 12, 10); scene.add(directionalLight);
2. 配置TrackballControls
TrackballControls is perfect for CAD-style free-form navigation. Let's enable all required features and tweak settings for smooth interactions:
// 初始化并配置TrackballControls const controls = new TrackballControls(camera, renderer.domElement); controls.enableZoom = true; // 开启缩放功能 controls.enableRotate = true; // 开启旋转功能 controls.enablePan = true; // 开启平移功能 controls.dampingFactor = 0.06; // 操作阻尼,让视图过渡更自然 controls.minDistance = 2; // 最小缩放距离(防止镜头贴太近) controls.maxDistance = 25; // 最大缩放距离(防止镜头拉太远) controls.update(); // 首次更新控件状态
3. 实现按钮触发的操作逻辑
Now we'll add event listeners to the buttons to manually control the camera and keep the controls state in sync with button actions:
3.1 缩放按钮(点击切换放大/缩小)
// 缩放按钮逻辑 const zoomBtn = document.getElementById('zoomBtn'); let zoomDirection = 1; // 1 = 放大,-1 = 缩小 const zoomStep = 0.6; // 每次缩放的步长 zoomBtn.addEventListener('click', () => { const currentDistance = camera.position.distanceTo(scene.position); // 确保新距离在允许范围内 const newDistance = Math.max(controls.minDistance, Math.min(controls.maxDistance, currentDistance - zoomStep * zoomDirection)); // 按比例调整相机位置,保持朝向模型中心 camera.position.lerp(scene.position, (currentDistance - newDistance) / currentDistance); camera.lookAt(scene.position); controls.update(); // 更新控件状态 zoomDirection *= -1; // 下次点击切换缩放方向 });
3.2 旋转按钮(绕Y轴旋转固定角度)
// 旋转按钮逻辑 const rotateBtn = document.getElementById('rotateBtn'); const rotateAngle = Math.PI / 8; // 每次旋转22.5度(可按需调整) rotateBtn.addEventListener('click', () => { // 绕Y轴旋转相机位置 camera.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), rotateAngle); camera.lookAt(scene.position); controls.update(); });
3.3 平移按钮(沿相机水平方向平移)
// 平移按钮逻辑 const panBtn = document.getElementById('panBtn'); let panDirection = 1; // 1 = 向右,-1 = 向左 const panStep = 0.6; // 每次平移的步长 panBtn.addEventListener('click', () => { // 获取相机的右方向向量 const right = new THREE.Vector3(); camera.getWorldDirection(right).cross(camera.up).normalize(); // 平移相机和控件目标点(保持跟踪一致性) camera.position.add(right.multiplyScalar(panStep * panDirection)); controls.target.add(right.multiplyScalar(panStep * panDirection)); controls.update(); panDirection *= -1; // 下次点击切换平移方向 });
4. 动画循环与窗口适配
Finally, add the animation loop to keep the controls and view updated, plus handle window resizing:
// 动画循环 function animate() { requestAnimationFrame(animate); controls.update(); // 必须持续更新控件 renderer.render(scene, camera); } animate(); // 窗口大小适配 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); controls.handleResize(); }); </script> </body> </html>
额外优化建议
- If you want continuous action on hold (e.g., hold the zoom button to keep scaling), use
setIntervalandclearIntervallike this:let zoomInterval; zoomBtn.addEventListener('mousedown', () => { zoomInterval = setInterval(() => { const currentDistance = camera.position.distanceTo(scene.position); const newDistance = Math.max(controls.minDistance, Math.min(controls.maxDistance, currentDistance - 0.15 * zoomDirection)); camera.position.lerp(scene.position, (currentDistance - newDistance) / currentDistance); camera.lookAt(scene.position); controls.update(); }, 16); // ~60fps }); zoomBtn.addEventListener('mouseup', () => clearInterval(zoomInterval)); zoomBtn.addEventListener('mouseleave', () => clearInterval(zoomInterval)); - Adjust rotation axes (e.g., rotate around X-axis) or pan directions (vertical) to match your CAD workflow needs.
内容的提问来源于stack exchange,提问作者sathish94us

