wgpu: volume measurement tool (V hotkey, selection-driven HUD + per-object labels)

Ports Bonsai's volumeOfObjects + volumesPerObject onto WgpuViewportWindow.
Mesh-local volumes are precomputed at applyCachedModel via signed-
tetrahedra-from-origin (dequantising positions from the 12 B/vertex GPU
layout); per-instance volume is just the cached local × |det(placement)|.
No GPU readback — measurement is O(K) in the selection size.

Streaming path computes volumes per-chunk as they arrive — fills any
mesh whose chunk just delivered, then re-runs updateVolumeReadout if
the user is staring at a Volume readout while the geometry pages in.

UX matches GL: V toggles, Esc exits, selection-driven (LMB pick / marquee
/ Shift/Ctrl set ops all funnel into updateVolumeReadout). HUD shows
total + count; one overlay label per object at its AABB centre, capped
at 200 to keep the label-texture cache bounded on large marquees.

Side fixes layered on the label overlay:
- O(1) AABB lookup via object_id_to_instance instead of linear-scanning
  every model's instance list per selected object.
- Label texture cache evicts entries not touched this frame, so churning
  through "X.XXXX m³" strings doesn't pin GPU memory.
- DrawRec stores the WGPUBindGroup handle by value rather than a
  LabelTexture* pointer into the QHash — getOrCreateLabelTexture can
  rehash the table and invalidate every captured pointer, which crashed
  large marquee selections with BindGroup-no-longer-alive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-31 20:57:44 +10:00
parent 3ac7a79b7a
commit 0774398d4e
4 changed files with 359 additions and 9 deletions
+13
View File
@@ -26,6 +26,7 @@
#include <cstdint>
#include <limits>
#include <string>
#include <unordered_map>
#include <vector>
#include "InstancedGeometry.h"
@@ -280,6 +281,18 @@ struct WgpuModelGpuData {
std::vector<MeshInfo> meshes;
std::vector<InstanceCpu> instances;
// Local-frame volume (m³) of every mesh, indexed by mesh_id. Computed
// once at applyCachedModel via signed-tetrahedra-from-origin on the
// raw vertex+index data; reused by the Volume measurement tool to
// avoid re-reading the GPU buffers per click. Empty in streaming mode
// until the chunk holding the mesh has been delivered.
std::vector<double> mesh_local_volumes;
// object_id (globally rebased) → instance index in `instances`.
// Populated alongside the instance vector so the Volume tool can do
// O(1) instance lookup instead of linear-scanning every model.
std::unordered_map<uint32_t, uint32_t> object_id_to_instance;
// Spatial chunk-cull replaced the per-model BVH walk — chunks are
// already a one-level spatial partition of the instances, so a
// single frustum test per chunk gives the same wholesale-reject