ifcviewer: first-person / fly view — share the desktop fly camera with web

DRY the fly camera into ViewportCore so desktop and web share identical math:
- flyMove(fwd,back,right,left,up,down,boost,dt): WASD in the view plane, QE
  along world +Z, forward = eye→target (no snap after orbiting), Shift = 5x,
  dt clamped to 0.1s. flyLook(dx,dy): turn in place (yaw/pitch, eye pinned,
  0.2 deg/px, pitch ±89.9). flyAdjustSpeed(notches): wheel scales speed x1.25.
  fly_move_speed_ + orbitEye now live in the core (orbitEye's duplicate removed
  from ViewportWindow).
- Desktop ViewportWindow: fpsIntegrate / mouse-look / wheel-speed call the core
  methods; behaviour unchanged, BonsaiViewer builds clean.
- Web main_web: Shift+F (or the Fly toolbar button) enters and pointer-locks the
  canvas; W/A/S/D/Q/E + Shift are held-tracked (keydown/keyup) and integrated
  each RAF frame with wall-clock dt; pointer-lock mouse deltas drive flyLook;
  wheel tunes speed. Exit on Esc OR a canvas click (matches desktop) — a
  pointerlockchange handler catches the browser eating the first Esc to release
  the lock (so a single Esc exits), guarded by fly_locked so a denied lock on
  entry doesn't insta-exit. Fly button reflects/ syncs active state.

Verified: web enter→WASD moves→click/Esc exits, 0 GPU errors; 113/113 desktop
core + 6/6 web smoke; desktop app builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-02 14:04:35 +10:00
parent 0b8c787ac0
commit f9acf8be3a
7 changed files with 217 additions and 113 deletions
+60
View File
@@ -526,6 +526,66 @@ void ViewportCore::dollyBy(float notches) {
host_->requestFrame();
}
void ViewportCore::flyMove(bool fwd, bool back, bool right, bool left,
bool up, bool down, bool boost, float dt_seconds) {
if (dt_seconds <= 0.0f) return;
if (dt_seconds > 0.1f) dt_seconds = 0.1f; // stall clamp
// Forward = orbit eye -> target, kept as the view direction in fly mode too
// so entering fly right after orbiting doesn't snap to a new heading.
const Eigen::Vector3f target(camera_target_[0], camera_target_[1], camera_target_[2]);
const Eigen::Vector3f eye = orbitEye(camera_target_, camera_distance_,
camera_yaw_deg_, camera_pitch_deg_);
Eigen::Vector3f forward = target - eye;
if (forward.norm() < 1e-6f) return;
forward.normalize();
// Looking straight up/down degenerates cross(forward, worldZ); fall back to
// worldY so `right` doesn't go NaN.
const Eigen::Vector3f world_up(0.0f, 0.0f, 1.0f);
const Eigen::Vector3f right_basis =
(std::abs(camera_pitch_deg_) >= 89.0f) ? Eigen::Vector3f(0.0f, 1.0f, 0.0f) : world_up;
Eigen::Vector3f right_vec = forward.cross(right_basis);
right_vec.normalize();
Eigen::Vector3f move(0.0f, 0.0f, 0.0f);
if (fwd) move += forward;
if (back) move -= forward;
if (right) move += right_vec;
if (left) move -= right_vec;
if (up) move += world_up;
if (down) move -= world_up;
if (move.isZero()) return;
move.normalize();
const float speed = fly_move_speed_ * (boost ? 5.0f : 1.0f); // absolute m/s
const Eigen::Vector3f delta = move * (speed * dt_seconds);
camera_target_[0] += delta.x();
camera_target_[1] += delta.y();
camera_target_[2] += delta.z();
host_->requestFrame();
}
void ViewportCore::flyLook(float dx_px, float dy_px) {
// Turn in place: pin the eye, rotate yaw/pitch, then re-derive the orbit
// target so orbitEye(target, dist, new_yaw, new_pitch) == the pinned eye.
const Eigen::Vector3f pinned_eye = orbitEye(camera_target_, camera_distance_,
camera_yaw_deg_, camera_pitch_deg_);
camera_yaw_deg_ -= dx_px * 0.2f;
camera_pitch_deg_ += dy_px * 0.2f;
camera_pitch_deg_ = std::clamp(camera_pitch_deg_, -89.9f, 89.9f);
constexpr float kDeg2Rad = 0.01745329251994329577f;
const float yaw = camera_yaw_deg_ * kDeg2Rad;
const float pit = camera_pitch_deg_ * kDeg2Rad;
const float cp = std::cos(pit), sp = std::sin(pit);
const float cy = std::cos(yaw), sy = std::sin(yaw);
camera_target_[0] = pinned_eye.x() - camera_distance_ * cp * cy;
camera_target_[1] = pinned_eye.y() - camera_distance_ * cp * sy;
camera_target_[2] = pinned_eye.z() - camera_distance_ * sp;
host_->requestFrame();
}
void ViewportCore::flyAdjustSpeed(float notches) {
const float factor = std::pow(1.25f, notches); // up = faster
fly_move_speed_ = std::clamp(fly_move_speed_ * factor, 0.05f, 1000.0f);
}
void ViewportCore::toggleProjection() {
projection_ortho_ = !projection_ortho_;
std::fprintf(stderr, "[info] [wgpu] projection: %s\n",