The correct way to handle dragging, scaling, and boundary detection of 3D models in Three.js. Addresses issues such as mouse movement not syncing with model movement and only part of the model being visible after scaling.
问题: 使用固定的 panSpeed = 0.01 进行平移计算
// ❌ 错误方式
const panSpeed = 0.01;
newPosition.add(right.multiplyScalar(deltaX * panSpeed));
为什么发生: 相机距离变化时,同样的世界空间距离在屏幕上的像素表现不同。距离近时像素多,距离远时像素少。
解决方案: 根据相机距离和 FOV 动态计算像素→世界空间的映射
// ✅ 正确方式:动态计算
const cameraDistance = camera.position.distanceTo(modelCenter);
const fov = camera.fov * (Math.PI / 180);
const screenHeight = renderer.domElement.clientHeight;
const screenWidth = renderer.domElement.clientWidth;
// 在相机距离处,视口的世界空间高度
const worldHeight = 2 * Math.tan(fov / 2) * cameraDistance;
const worldWidth = worldHeight * (screenWidth / screenHeight);
// 每像素对应的世界空间距离
const pixelToWorldX = worldWidth / screenWidth;
const pixelToWorldY = worldHeight / screenHeight;
// 应用:鼠标移动的像素 × 每像素对应的世界空间距离
newPosition.add(right.multiplyScalar(deltaX * pixelToWorldX));
newPosition.add(up.multiplyScalar(-deltaY * pixelToWorldY));
问题: 使用模型中心点的 NDC 坐标判断是否出界
// ❌ 错误方式:限制中心点位置
const ndc = position.clone().project(camera);
if (ndc.y > 0.2) clampedY = 0.2; // 限制顶部
为什么发生: 模型放大后,中心点在屏幕中心,但身体大部分已超出屏幕。限制中心点 = 限制只能看到身体中间部分。
解决方案: 计算模型在屏幕上的可见区域(像素),只在可见区域过小时才校正
// ✅ 正确方式:基于可见像素
const MIN_VISIBLE_PIXELS = 50;
// 1. 计算模型包围盒并投影到屏幕
const box = new THREE.Box3().setFromObject(vrm.scene);
const corners = [/* 8个顶点 */];
let modelMinX = Infinity, modelMaxX = -Infinity;
let modelMinY = Infinity, modelMaxY = -Infinity;
corners.forEach(corner => {
const projected = corner.clone().project(camera);
const screenX = (projected.x * 0.5 + 0.5) * screenWidth;
const screenY = (-projected.y * 0.5 + 0.5) * screenHeight;
// 更新边界...
});
// 2. 计算可见区域
const visibleWidth = Math.max(0, Math.min(screenWidth, modelMaxX) - Math.max(0, modelMinX));
const visibleHeight = Math.max(0, Math.min(screenHeight, modelMaxY) - Math.max(0, modelMinY));
const visiblePixels = visibleWidth * visibleHeight;
// 3. 只在可见区域太小时校正
if (visiblePixels < MIN_VISIBLE_PIXELS) {
// 将模型拉回可见区域
}
worldHeight = 2 × tan(fov/2) × cameraDistance
pixelToWorld = worldHeight / screenHeight
const ndc = worldPos.clone().project(camera);
const screenX = (ndc.x * 0.5 + 0.5) * screenWidth;
const screenY = (-ndc.y * 0.5 + 0.5) * screenHeight; // Y 轴反向
npx skills add Project-N-E-K-O/3d-camera-interaction下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer