ifcviewer: section-plane cut tool on web — shared gizmo, true-face pick, drag/Del

Full section tool for the web viewport, with the gizmo + interaction shared with
desktop from one codebase.

- True-face surface pick. pickSurfaceAt had always ray-cast the instance AABB (to
  skip a depth readback), so cuts sat in front of the real surface. The pick
  fragment already computes the exact world_pos (it clips sections with it); now
  it OUTPUTS it to a 3rd pick MRT (RGBA32F) that every pick path renders, and
  pickSurfaceAt / pickSurfaceAtAsync read it back (decodeMappedPickPosition;
  ray-AABB kept only as a fallback). The web async pick chains id -> normal ->
  position spontaneous staging maps.
- Web tool: LMB drops a cut at the picked surface (LMB drag still orbits), K
  toggles, Shift+K clears; oriented to the real MRT surface normal. Exports + a
  Section / Clear cuts toolbar pair.
- Shared gizmo: lifted the section-gizmo renderer (SECTION_WGSL + thick-line AA +
  quad+arrow VBO + pack + screen-space hit-test) out of the Qt-coupled
  OverlayRenderer into a Qt-free SectionGizmoRenderer that ViewportCore::render
  draws for BOTH desktop and web (both already render via render()). One identical
  gizmo; OverlayRenderer's now-dead section code removed. Fixed 1 m size (matches
  the desktop constant).
- Interaction (shared): hitTestSectionGizmo (SectionGizmoRenderer::hitTest) +
  beginSectionDrag / updateSectionDrag / endSectionDrag live in ViewportCore.
  Drag a gizmo arrow to slide the plane along its normal; Del/Backspace removes
  the most recent cut. Desktop's ViewportWindow dropped its duplicate hit-test /
  drag math + state and delegates to the core; web wires the same calls.

Tests: sectionPlaneCount add/clear/cap (Catch2, 125); web smoke "click a surface
cuts geometry, clear restores" exercises the shared gizmo + 3-MRT pick (11/11).
Desktop object-pick / marquee unaffected; BonsaiViewer builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-03 17:31:02 +10:00
parent 9ad10c009b
commit da5c0b7991
14 changed files with 1093 additions and 536 deletions
@@ -143,6 +143,25 @@ TEST_CASE("toggleXray flips the active state", "[camera][xray]") {
REQUIRE_FALSE(core.xrayActive());
}
TEST_CASE("section planes: add appends, clear drops all, capped at the max",
"[camera][section]") {
MockHost host; ViewportCore core(&host);
REQUIRE(core.sectionPlaneCount() == 0);
const Eigen::Vector3f pt(1, 2, 3), n(0, 0, 1);
REQUIRE(core.addSectionPlaneAtSurface(pt, n, 1.0f));
REQUIRE(core.addSectionPlaneAtSurface(pt, n, 1.0f));
REQUIRE(core.sectionPlaneCount() == 2);
// Fill to the cap (kMaxSectionPlanes == 6); further adds are rejected.
while (core.sectionPlaneCount() < 6) REQUIRE(core.addSectionPlaneAtSurface(pt, n, 1.0f));
REQUIRE_FALSE(core.addSectionPlaneAtSurface(pt, n, 1.0f));
REQUIRE(core.sectionPlaneCount() == 6);
core.clearSectionPlanes();
REQUIRE(core.sectionPlaneCount() == 0);
}
TEST_CASE("setNavPreset maps names to the shared button bindings", "[camera][nav]") {
MockHost host; ViewportCore core(&host);
using B = ViewportCore::MouseBtn; using M = ViewportCore::NavMod;