ifcviewer-full: 1-pt laser, 2-pt XYZ + perpendicular, sharper visuals

Length tool's 1-pt laser is now hybrid:
  - On any surface, a coplanar BFS finds the connected face patch
    around the click and projects its vertices into the surface
    tangent basis to get an exact bounding-box extent.  Stops at
    the face edge by construction — no overshoot into adjacent
    geometry like the previous tangent-raycast did.
  - On near-horizontal surfaces (|n.z| > 0.85, i.e. floors and
    ceilings) it additionally fires one raycast in +n to the
    opposing surface — so a single floor click reports X extent +
    Y extent + ceiling height.
  - Bars are labelled by their dominant world axis (X/Y/Z) instead
    of "vertical/horizontal", which reads cleanly on either kind
    of surface.

The 2-pt readout now draws the world-space XYZ stair-step (red ΔX,
green ΔY, blue ΔZ) with each leg labelled, and a dashed
perpendicular line whenever the two picks landed on near-parallel
surfaces — useful for measuring across walls.

To support multiple line styles per frame, OverlayRenderer's
setOverlayLines takes std::vector<LineGroup> instead of a single
inline style; each group has its own color/halo/width and an
optional dash period.  The line shader gained v_along_px +
u_dash_period uniforms (screen-space dashes), and both line and
point shaders now use a sharp step() for the inner→stroke
transition with AA only on the outer halo edge — much crisper than
the previous soft band.  Default visual style trimmed: 1.5px lines
(0.5px halo), 6px dots (1px halo), opaque black halo.

Also adds ViewportWindow::raycast(origin, dir, RaycastHit&) — CPU
ray traversal of each model's per-instance BVH followed by
Möller-Trumbore against the candidate meshes' triangles (lazily
read back, cached per call).  Used by the floor/ceiling laser path
today and reusable for any future raycast-based feature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-08 08:50:02 +10:00
parent acfcf6e14e
commit d8f37b2376
6 changed files with 702 additions and 137 deletions
+22 -9
View File
@@ -230,6 +230,24 @@ public:
};
bool pickMeshLocalAt(int x, int y, MeshLocalPick& out);
// CPU raycast against the per-model BVHs. Walks the BVH for each
// finalised model, transforms the world ray into mesh-local space
// for each candidate instance, reads back its triangles, and runs
// Möller-Trumbore against them. Returns the closest hit overall.
//
// `dir` MUST be a unit vector — distance is reported as the t value
// along the ray, which equals world distance only when |dir|=1.
// Stalls the GL pipeline once per unique (model, mesh) candidate
// because triangle data is read back lazily; budget ~1ms for
// typical BIM scenes.
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);
// Measurement tool modes. While any tool is active, LMB clicks emit
// surfacePickedInTool with the click coordinates (instead of swapping
// object selection); the app interprets them per-tool. Esc exits the
@@ -250,15 +268,10 @@ public:
void setHighlightTriangles(const std::vector<float>& world_xyz,
float r, float g, float b, float a);
// Replace the overlay-line list (3 floats per vertex, 2 verts per
// segment, world space). When stroke_a > 0 each segment is rendered
// with a wider (line_width + 2*stroke_extra) halo behind the inner
// line_width — proper outlined lines via screen-space-quad shader.
void setOverlayLines(const std::vector<float>& world_xyz,
float r, float g, float b, float a,
float line_width,
float stroke_r, float stroke_g, float stroke_b, float stroke_a,
float stroke_extra);
// Replace the overlay line groups. Each group has its own segments
// + style (color/halo/width/dash) — see OverlayRenderer::LineGroup.
// Empty disables every line.
void setOverlayLines(const std::vector<OverlayRenderer::LineGroup>& groups);
// Replace the overlay-point list (3 floats per point, world space).
// pixel_size is the inner-disc diameter in physical pixels; when