| 23 | } // namespace |
| 24 | |
| 25 | ViewerStateIO ApplyViewerControls(const ViewerControls& c, const ViewerStateIO& in, float deltaT, |
| 26 | float defaultAnimSpeed) |
| 27 | { |
| 28 | ViewerStateIO out = in; |
| 29 | out.didReloadTextures = false; |
| 30 | out.didExitViewer = false; |
| 31 | |
| 32 | // Translate (LMB drag) — slide along camera-aligned axes. +X drag |
| 33 | // moves +Aside, +Y drag moves -Up (screen Y grows down, world up |
| 34 | // grows up; flip the sign so dragging down moves the object down |
| 35 | // on screen). |
| 36 | if (c.translateX != 0.0f || c.translateY != 0.0f) |
| 37 | { |
| 38 | out.objectPos += c.translateX * kTranslateSpeed * out.cameraAside; |
| 39 | out.objectPos += -c.translateY * kTranslateSpeed * out.cameraUp; |
| 40 | } |
| 41 | |
| 42 | // Rotate (RMB drag) — build head/dive in object-local frame from |
| 43 | // the current Direction(). |
| 44 | if (c.rotateX != 0.0f || c.rotateY != 0.0f) |
| 45 | { |
| 46 | // Project current Direction() to spherical (head, dive). |
| 47 | Vector3 dir = out.objectOrient.Direction(); |
| 48 | float head = -atan2f(dir.X(), dir.Z()) + c.rotateX * kRotateSpeed; |
| 49 | float xz = sqrtf(dir.X() * dir.X() + dir.Z() * dir.Z()); |
| 50 | float dive = -atan2f(dir.Y(), xz) - c.rotateY * kRotateSpeed; |
| 51 | dive = clamp_(dive, -kPitchClamp, +kPitchClamp); |
| 52 | out.objectOrient = Matrix3(MRotationY, head) * Matrix3(MRotationX, dive); |
| 53 | } |
| 54 | |
| 55 | // Zoom (wheel) — exponential dolly toward the look-at target |
| 56 | // (objectPos). Each tick multiplies camera-to-target distance by |
| 57 | // kZoomPerTick, so visual zoom rate is constant regardless of how |
| 58 | // far we already are. |
| 59 | if (c.zoom != 0.0f) |
| 60 | { |
| 61 | Vector3 toCam = out.cameraPos - out.objectPos; |
| 62 | float curDist = toCam.Size(); |
| 63 | if (curDist > 1e-4f) |
| 64 | { |
| 65 | Vector3 toCamDir = toCam * (1.0f / curDist); |
| 66 | float ratio = powf(kZoomPerTick, c.zoom); |
| 67 | float newDist = clamp_(curDist * ratio, kZoomMinDist, kZoomMaxDist); |
| 68 | out.cameraPos = out.objectPos + toCamDir * newDist; |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // Animation transport. |
| 73 | if (c.animScrub != 0.0f) |
| 74 | { |
| 75 | float p = out.animPhase + c.animScrub * deltaT; |
| 76 | // fastFmod-equivalent for the [0, 1) wrap. |
| 77 | p = p - floorf(p); |
| 78 | out.animPhase = p; |
| 79 | } |
| 80 | if (c.playPauseAnim) |
| 81 | { |
| 82 | out.animSpeed = (out.animSpeed > 0.001f) ? 0.0f : defaultAnimSpeed; |