Controls模块架构
Controls Module 位于 UI 事件和相机状态之间。它将事件处理、数学运算和状态管理分离开来,以便每一层都能保持可测试性和可预测性。
分层流程
- input.ts – 纯函数 (
processKeyboardInput,processMouseInput,processScrollInput) 将原始事件映射为“意图”向量 (amount,rotation,shift,scroll)。 - controller.ts / fps-controller.ts – 每一帧持有状态,累积输入,调用轨道数学辅助函数,并最终修改
PerspectiveCamera.positionV/rotationQ。 - orbit.ts – 数学原语,用于基向量计算、对数缩放、平移、带极点保护的旋转、
lookAtW2C、衰减等。
轨道控制器管线
状态
center // 轨道焦点 (vec3)
rotation // 累积的 偏航/俯仰/翻滚 (yaw/pitch/roll) (vec3)
shift // 累积的平移 (vec2)
scroll // 对数缩放标量 (number)
orbitUp // 帧之间传递的稳定上向量 (vec3)
更新步骤
- 基向量构建 (Basis construction) –
calculateOrbitBasis(cam.positionV, center, orbitUp)返回forward,right,yawAxis。forward是归一化的(center - position)。yawAxis是重新投影到与forward正交的平面上的轨道上向量,然后通过right = forward × yawAxis重新正交化。 - 对数缩放 (Log zoom) –
applyDistanceScaling使用dist' = exp(log(dist₀) + scroll × dt × 10 × speed)并在[MIN_DISTANCE, MAX_DISTANCE]之间进行钳制。 - 平移 (Panning) –
applyPanning(center, shift, right, yawAxis, dt, speed, distance)根据距离和速度按比例偏移center,因此无论缩放级别如何,平移感觉都是一致的。 - 旋转 (Rotation) –
applyRotation(forward, right, yawAxis, yaw, pitch, roll)应用绕yawAxis的偏航,绕right的俯仰,以及可选的翻滚(Alt 拖动)。当forward在 5° 以内接近±yawAxis时,极点保护会限制俯仰角。 - 位置/方向 (Position/orientation) –
cam.positionV = center - forward * distance。cam.rotationQ = lookAtW2C(forward, orbitUpRef)从新基构建世界→相机四元数。 - 衰减 (Decay) –
applyDecay(rotation, shift, scroll, dt)将分量乘以pow(0.8, dt * 60)(60 fps 基准)并将微小值归零。 - 轨道上向量维护 (Orbit-up maintenance) – 清理后的
yawAxis成为下一帧的orbitUp,因此即使经过多次旋转,相机也能保持稳定的参考上向量。
输入辅助函数
键盘
processKeyboardInput(code, pressed, amount, rotation, sensitivity) 更新 amount (KeyWASD, Space, ShiftLeft) 和 rotation[2] (KeyQ/E 翻滚)。如果按键被处理,则返回 true。
鼠标
processMouseInput(dx, dy, leftPressed, rightPressed, rotation, shift) 将增量添加到 rotation(左键)或 shift(右键)。控制器稍后会将这些增量乘以 sensitivity 和 deltaTime。
滚轮
processScrollInput(delta) 简单地缩放滚轮增量(默认 ×3),以便控制器可以累积平滑的对数缩放步长。
轨道数学内部机制 (orbit.ts)
lookAtW2C(forward, up)通过将 +Z 对齐到forward,然后绕forward扭转直到 +Y 与期望的上向量对齐,从而构建世界→相机四元数。它通过回退到另一个轴来处理退化情况(forward ≈ up)。projectOntoPlaneNormed(v, n, fallback)将v投影到法线为n的平面上,并使用 epsilon 安全回退对结果进行归一化。- 极点保护比较预期的俯仰结果与
yawAxis之间的点积。如果新向量将超过极点保护 (cos(5°)),则俯仰会被按比例缩小,以使相机永远不会触及奇点。 - 常量 (
WORLD_UP,MIN_POLE_ANGLE,MIN_DISTANCE等) 被导出以供自定义控制器重用。
FPS 控制器概览
- 维护
yaw/pitch角度以及用于旋转和平移的惯性,以及键盘/鼠标状态映射。 - 当按下左键时,鼠标增量更新
yaw/pitch;否则旋转惯性呈指数衰减 (rotateInertia)。 - 键盘 WASDQE/PageUp/PageDown 在相机空间产生移动向量,该向量通过反转的相机四元数转换为世界空间。
flyMode在全 6DoF(Y 轴自由)和仅地面(移动限制在 XZ 平面)之间切换。 - 滚轮输入提供额外的前进/后退移动。
- 速度倍增器:Caps Lock (×10), Shift (×50), Ctrl (×0.2)。
集成蓝图
DOM 事件 (UIController)
├─ keydown/up → controller.processKeyboard(code, pressed)
├─ mousedown/up → 设置 leftMousePressed/rightMousePressed
├─ mousemove → controller.processMouse(dx, dy)
└─ wheel → controller.processScroll(delta)
每帧:
controller.update(perspectiveCamera, deltaTime)
renderer 上传新的 相机 view/proj 矩阵
控制器暴露 userInput,以便宿主应用程序知道是否发生了任何新的交互(用于暂停自动旋转等)。
可扩展性
- 通过遵守
IController并重用input.ts/orbit.ts辅助函数来实现另一种控制方案。 - 触摸/手势支持可以通过将触摸事件转换为相同的
rotation,shift, 和scroll累加器来分层实现。 - 通过插值相机状态或基于用户模式(例如,轨道 ↔ FPS)切换,在控制器之间进行混合。
通过隔离输入解析、数学应用和相机变更,Controls Module 无论是由默认 UI、Three.js 场景还是自定义工具驱动,都能提供可预测的行为。