mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 00:23:14 +00:00
wgpu: length measurement tool (L hotkey, adaptive 1/2/3/4+ point readout)
Ports Bonsai's LengthMeasurement onto WgpuViewportWindow as a new
WgpuLengthMeasurement class. Each LMB appends a world-space pick point
and the readout adapts to the running count:
1 pt → laser-measure: coplanar-patch BFS on the click's surface
projects every patch vertex into the surface's own tangent
basis to get face extents (X/Y/Z bars dashed in world space),
plus ENH coords for the picked point, plus a vertical
raycast for floor/ceiling distance on horizontal surfaces.
2 pts → distance A→B + axis-coloured ΔX/ΔY/ΔZ stair-step + dashed
perpendicular projection when both picks landed on
near-parallel surfaces.
3 pts → angle at middle vertex + triangle area + perimeter.
4+ pts → polygon area via best-fit-plane shoelace (Jacobi-3x3
eigendecomp inline; no Eigen dep) or fan-triangulated
fallback for non-planar loops, plus closed-loop perimeter.
Backspace / Del removes the last point; Esc / L again exits.
Dependencies layered in:
- pickMeshLocalAt now refines the AABB-coarse pickSurfaceAt hit into a
real triangle hit via Möller-Trumbore against the picked instance's
CPU mesh shadow. Without this the BFS seeds with whatever triangle
is closest to the bounding-box corner — producing patches and
extents shaped like the AABB instead of the surface.
- meshLocalToGlobal: applies the instance's placement_transformation
only (no per-model CoordinateOperation in wgpu yet). ENH equals
IFC-world for non-federated loads, which is what the minimal viewer
handles.
- raycast: brute-force world-AABB cull + Möller-Trumbore over the CPU
mesh shadow. Used by the laser-measure ceiling/floor distance.
- ToolMode gains Length; click handler routes plain/Alt LMB through
onLengthPick, Backspace through onLengthBackspace. Marquee-arm is
gated off in Length mode.
Volume HUD now shows "Volume: 0.0000 m³ (0 objects)" the moment V is
pressed, matching how A primes "Area: 0.0000 m²" — gives the user a
visible cue the tool is active before any selection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -326,14 +326,39 @@ public:
|
||||
};
|
||||
bool pickMeshLocalAt(int x, int y, MeshLocalPick& out);
|
||||
|
||||
// Resolve a mesh-local point to a (placement-applied) global frame.
|
||||
// Matches GL ViewportWindow::meshLocalToGlobal's shape so the Length
|
||||
// tool's ENH readout ports unchanged. The wgpu viewer doesn't carry
|
||||
// per-model CoordinateOperation yet, so this currently outputs
|
||||
// placement_transformation · mesh_local (i.e. the IFC's own world
|
||||
// coords pre-georeferencing); ENH and IFC-world coincide for the
|
||||
// non-federated case the minimal viewer handles today.
|
||||
bool meshLocalToGlobal(uint32_t object_id, const float mesh_local[3],
|
||||
double global_out[3]) const;
|
||||
|
||||
// CPU world-space raycast. Brute-force: per-instance world-AABB
|
||||
// reject, then ray-into-mesh-local + Möller-Trumbore against the
|
||||
// CPU mesh shadow. `dir` must be a unit vector — distance is the
|
||||
// ray's t parameter, which equals world distance only at |dir|=1.
|
||||
// Used by the Length tool's 1-point laser-measure overlay to find
|
||||
// the ceiling/floor counterpart of a horizontal-surface click.
|
||||
struct RaycastHit {
|
||||
uint32_t object_id = 0;
|
||||
float distance = 0.0f;
|
||||
float world_pos[3] = {0, 0, 0};
|
||||
float world_normal[3]= {0, 0, 0};
|
||||
};
|
||||
bool raycast(const float origin[3], const float dir[3], RaycastHit& out) const;
|
||||
|
||||
// Measurement tools. Mirrors GL ViewportWindow::ToolMode. Volume is
|
||||
// selection-driven (LMB / marquee). Area is click-to-accumulate:
|
||||
// each LMB picks a triangle and either adds or removes its
|
||||
// coplanar patch via BFS over shared edges; Alt+LMB skips the BFS.
|
||||
// V/A toggle, Esc exits.
|
||||
// V/A/L toggle, Esc exits. Length consumes Backspace too for
|
||||
// remove-last-point semantics.
|
||||
// NoTool (not None) because X11/X.h #define's None as 0L; including
|
||||
// it transitively via Qt's xcb back-end breaks any enum named None.
|
||||
enum class ToolMode { NoTool, Volume, Area };
|
||||
enum class ToolMode { NoTool, Volume, Area, Length };
|
||||
ToolMode toolMode() const { return tool_mode_; }
|
||||
void setToolMode(ToolMode m);
|
||||
|
||||
@@ -539,6 +564,14 @@ private:
|
||||
void onAreaPick(int x_phys, int y_phys, bool alt);
|
||||
void updateAreaHud();
|
||||
|
||||
// Length tool state lives in WgpuLengthMeasurement. Same lifecycle
|
||||
// pattern: lazily constructed on first L press, cleared on tool
|
||||
// exit, click handler routes LMB through onLengthPick + Backspace
|
||||
// through onLengthBackspace.
|
||||
std::unique_ptr<class WgpuLengthMeasurement> length_tool_;
|
||||
void onLengthPick(int x_phys, int y_phys, bool alt);
|
||||
void onLengthBackspace();
|
||||
|
||||
// Pick pass (stage 4). Single-sample R32UInt target + depth, vertex-
|
||||
// pulled from the same visible_draws / instances buffers as the main
|
||||
// pass — pick fragment outputs the instance's object_id. The pick
|
||||
|
||||
Reference in New Issue
Block a user