mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +00:00
wgpu backend: orbit/pan/zoom mouse navigation
LMB drag → orbit (yaw/pitch, pitch clamped to ±89.9° to avoid gimbal flip at the poles). MMB drag → pan in the camera's screen-space plane, world-units-per-pixel sized against the view frustum at the pivot depth so panning feels constant regardless of zoom. Wheel → zoom (12% per notch, sign matches "wheel up = closer"). LMB is bound to orbit because selection isn't wired yet; will rebind to selection + nav preset once AppSettings ports over. Pure addition to WgpuViewportWindow — overrides four QWindow event handlers, no changes to render or cull paths. Lets you actually fly around a loaded sidecar without a screenshot loop. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1331,6 +1331,80 @@ void WgpuViewportWindow::captureNextFrameToPng(const QString& path, bool quit_af
|
|||||||
if (isExposed()) requestUpdate();
|
if (isExposed()) requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Mouse navigation — orbit, pan, zoom
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// LMB drag → orbit (yaw/pitch). MMB drag → pan (target moves in the camera's
|
||||||
|
// screen-space plane). Wheel → zoom (camera_distance_ multiplies). Pitch is
|
||||||
|
// clamped just shy of ±90° to avoid the gimbal-flip at the poles.
|
||||||
|
//
|
||||||
|
// No nav-preset awareness yet (Blender/Rhino/Revit bindings come later); we
|
||||||
|
// don't have selection bound, so LMB is free to orbit.
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QWheelEvent>
|
||||||
|
|
||||||
|
void WgpuViewportWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
|
nav_active_button_ = event->button();
|
||||||
|
nav_last_pos_ = event->position().toPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WgpuViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == nav_active_button_) {
|
||||||
|
nav_active_button_ = Qt::NoButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WgpuViewportWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
if (nav_active_button_ == Qt::NoButton) return;
|
||||||
|
|
||||||
|
const QPoint pos = event->position().toPoint();
|
||||||
|
const int dx = pos.x() - nav_last_pos_.x();
|
||||||
|
const int dy = pos.y() - nav_last_pos_.y();
|
||||||
|
nav_last_pos_ = pos;
|
||||||
|
|
||||||
|
if (nav_active_button_ == Qt::LeftButton) {
|
||||||
|
// Orbit. 0.4 deg/px feels right for a 1280-wide window.
|
||||||
|
camera_yaw_deg_ += float(dx) * -0.4f;
|
||||||
|
camera_pitch_deg_ += float(dy) * -0.4f;
|
||||||
|
camera_pitch_deg_ = std::clamp(camera_pitch_deg_, -89.9f, 89.9f);
|
||||||
|
requestUpdate();
|
||||||
|
} else if (nav_active_button_ == Qt::MiddleButton) {
|
||||||
|
// Pan in the camera's screen-space plane. World units per pixel
|
||||||
|
// tracks the view-frustum width at the pivot's depth so panning
|
||||||
|
// feels constant regardless of zoom.
|
||||||
|
const QVector3D target(camera_target_[0], camera_target_[1], camera_target_[2]);
|
||||||
|
const QVector3D eye = orbitEye(camera_target_, camera_distance_,
|
||||||
|
camera_yaw_deg_, camera_pitch_deg_);
|
||||||
|
const QVector3D fwd = (target - eye).normalized();
|
||||||
|
const QVector3D right = QVector3D::crossProduct(fwd, QVector3D(0, 0, 1)).normalized();
|
||||||
|
const QVector3D up = QVector3D::crossProduct(right, fwd).normalized();
|
||||||
|
|
||||||
|
const float half_h_world = camera_distance_
|
||||||
|
* std::tan(qDegreesToRadians(camera_fov_y_deg_) * 0.5f);
|
||||||
|
const float pan_per_pixel = (height() > 0)
|
||||||
|
? (2.0f * half_h_world / float(height()))
|
||||||
|
: 0.0f;
|
||||||
|
|
||||||
|
const QVector3D shift = -right * (float(dx) * pan_per_pixel)
|
||||||
|
+ up * (float(dy) * pan_per_pixel);
|
||||||
|
camera_target_[0] += shift.x();
|
||||||
|
camera_target_[1] += shift.y();
|
||||||
|
camera_target_[2] += shift.z();
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WgpuViewportWindow::wheelEvent(QWheelEvent* event) {
|
||||||
|
// 120 = one notch on a typical mouse. Each notch zooms ~12% in/out;
|
||||||
|
// sign matches conventional "wheel up = zoom in".
|
||||||
|
const float notches = float(event->angleDelta().y()) / 120.0f;
|
||||||
|
const float factor = std::pow(0.9f, notches);
|
||||||
|
camera_distance_ = std::max(0.01f, camera_distance_ * factor);
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
void WgpuViewportWindow::shutdown() {
|
void WgpuViewportWindow::shutdown() {
|
||||||
// Release per-model buffers before the device they were created from.
|
// Release per-model buffers before the device they were created from.
|
||||||
for (auto& [mid, m] : models_gpu_) releaseWgpuModelGpuData(m);
|
for (auto& [mid, m] : models_gpu_) releaseWgpuModelGpuData(m);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
#include <QPoint>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <webgpu/webgpu.h>
|
#include <webgpu/webgpu.h>
|
||||||
@@ -86,6 +87,10 @@ protected:
|
|||||||
void exposeEvent(QExposeEvent* event) override;
|
void exposeEvent(QExposeEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
bool event(QEvent* event) override;
|
bool event(QEvent* event) override;
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent* event) override;
|
||||||
|
void wheelEvent(QWheelEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initWgpu();
|
bool initWgpu();
|
||||||
@@ -164,6 +169,13 @@ private:
|
|||||||
// Pending one-shot screenshot, captured at the end of the next render().
|
// Pending one-shot screenshot, captured at the end of the next render().
|
||||||
QString pending_screenshot_path_;
|
QString pending_screenshot_path_;
|
||||||
bool pending_screenshot_quit_ = false;
|
bool pending_screenshot_quit_ = false;
|
||||||
|
|
||||||
|
// Mouse navigation state. LMB drag orbits, MMB drag pans, wheel zooms.
|
||||||
|
// No Blender/Maya preset selection yet — that arrives with the AppSettings
|
||||||
|
// port (post-task-12). Selection is unbound (no pick pass), so LMB is
|
||||||
|
// safe to dedicate to orbit for now.
|
||||||
|
Qt::MouseButton nav_active_button_ = Qt::NoButton;
|
||||||
|
QPoint nav_last_pos_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WGPUVIEWPORTWINDOW_H
|
#endif // WGPUVIEWPORTWINDOW_H
|
||||||
|
|||||||
Reference in New Issue
Block a user