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
+4 -15
View File
@@ -396,7 +396,7 @@ public:
// A point that actually lies on the model's first instance — the
// first instance's mesh AABB centre transformed by that instance's
// placement, in metres, pre-CoordinateOperation. Lookup only — the
// viewport already keeps the CPU-side MeshInfo + InstanceCpu around
// viewport already keeps the CPU-side MeshInfo + InstanceInfo around
// for picking / measurement; the federation false-origin guess
// (ViewportView::guessFederatedFalseOriginFromFirstModel) consumes
// this lazily on modelGeometryReady. Returns false when the model
@@ -737,20 +737,9 @@ private:
Qt::KeyboardModifiers box_select_press_mods_ = Qt::NoModifier;
static constexpr int kBoxSelectThresholdPx = 5;
// box_pick_staging_buffer_/_capacity_ moved to ViewportCore (#84-t).
// Drag-to-move state for the arrow gizmo. While `section_drag_active_`
// is true, mouseMoveEvent calls updateSectionDrag instead of letting
// the press fall through to the orbit/pan handlers.
bool section_drag_active_ = false;
int section_drag_index_ = -1;
Eigen::Vector2i section_drag_start_mouse_;
Eigen::Vector3f section_drag_start_origin_;
// Mirrors GL ViewportWindow::hitTestSectionGizmo: returns the index of
// the plane whose arrow gizmo is within grab_px of (x, y), or -1.
int hitTestSectionGizmo(int x, int y) const;
// Mirrors GL ViewportWindow::updateSectionDrag: projects the cursor
// delta onto the plane's normal in screen space and slides the plane
// along that direction.
void updateSectionDrag(int x, int y);
// Section-gizmo drag state + hit-test/drag math moved to ViewportCore
// (shared with web; the mouse handlers call core_.begin/update/endSectionDrag
// and core_.hitTestSectionGizmo).
// HiZ slot + pyramid aliases (storage in core_, #84-r). VW's render
// loop reads hiz_valid_ / hiz_vp_ to gate the HizOccludedFn, and