跳转至

Controls模块架构

Controls Module 位于 UI 事件和相机状态之间。它将事件处理、数学运算和状态管理分离开来,以便每一层都能保持可测试性和可预测性。

分层流程

DOM 事件 → input.ts → CameraController / FPSController → orbit.ts 数学运算 → PerspectiveCamera
  • 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)

更新步骤

  1. 基向量构建 (Basis construction)calculateOrbitBasis(cam.positionV, center, orbitUp) 返回 forward, right, yawAxisforward 是归一化的 (center - position)yawAxis 是重新投影到与 forward 正交的平面上的轨道上向量,然后通过 right = forward × yawAxis 重新正交化。
  2. 对数缩放 (Log zoom)applyDistanceScaling 使用 dist' = exp(log(dist₀) + scroll × dt × 10 × speed) 并在 [MIN_DISTANCE, MAX_DISTANCE] 之间进行钳制。
  3. 平移 (Panning)applyPanning(center, shift, right, yawAxis, dt, speed, distance) 根据距离和速度按比例偏移 center,因此无论缩放级别如何,平移感觉都是一致的。
  4. 旋转 (Rotation)applyRotation(forward, right, yawAxis, yaw, pitch, roll) 应用绕 yawAxis 的偏航,绕 right 的俯仰,以及可选的翻滚(Alt 拖动)。当 forward 在 5° 以内接近 ±yawAxis 时,极点保护会限制俯仰角。
  5. 位置/方向 (Position/orientation)cam.positionV = center - forward * distancecam.rotationQ = lookAtW2C(forward, orbitUpRef) 从新基构建世界→相机四元数。
  6. 衰减 (Decay)applyDecay(rotation, shift, scroll, dt) 将分量乘以 pow(0.8, dt * 60)(60 fps 基准)并将微小值归零。
  7. 轨道上向量维护 (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(右键)。控制器稍后会将这些增量乘以 sensitivitydeltaTime

滚轮

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 场景还是自定义工具驱动,都能提供可预测的行为。