mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-31 00:46:36 +00:00
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:
@@ -50,6 +50,7 @@
|
||||
#include "InstanceCompose.h"
|
||||
#include "InstancedGeometry.h"
|
||||
#include "ModelGpuData.h"
|
||||
#include "SectionGizmoRenderer.h"
|
||||
#include "SectionPlane.h"
|
||||
#include "SelectionState.h"
|
||||
#include "SidecarCache.h"
|
||||
@@ -110,7 +111,7 @@ public:
|
||||
// from the mesh-local one. Used by the per-model recompose path
|
||||
// after any of the four federation matrices change. Pure scene
|
||||
// math — no GPU touch.
|
||||
void composeInstanceFromPlacement(InstanceCpu& inst,
|
||||
void composeInstanceFromPlacement(InstanceInfo& inst,
|
||||
const ModelGpuData& m) const;
|
||||
|
||||
// Cross-model object_id lookup. Delegates to
|
||||
@@ -425,16 +426,16 @@ public:
|
||||
// `source_label` is a log/identity tag.
|
||||
void loadSidecarMetadataWeb(int source_id, std::string source_label);
|
||||
|
||||
// On-demand fetch of the v15 deferred property block (element tree + string
|
||||
// On-demand fetch of the v15 element metadata block (elements + string
|
||||
// table) for a web-streamed model — what a UI (object tree / selected-name
|
||||
// / search) needs, fetched only when asked so first paint never waits on
|
||||
// it. Populates ModelGpuData.elements/string_table; fires done(ok). At most
|
||||
// one fetch per model.
|
||||
void loadDeferredMetadataWeb(std::uint32_t model_id,
|
||||
std::function<void(bool)> done = {});
|
||||
void loadElementMetadataWeb(std::uint32_t model_id,
|
||||
std::function<void(bool)> done = {});
|
||||
|
||||
// Demo consumer of the deferred fetch: on pick, ensure the owning model's
|
||||
// property block is loaded (loadDeferredMetadataWeb — fetched once, on
|
||||
// Demo consumer of the element metadata fetch: on pick, ensure the owning model's
|
||||
// property block is loaded (loadElementMetadataWeb — fetched once, on
|
||||
// demand), then log the picked object's IFC GUID. The first pick triggers
|
||||
// the network fetch; later picks reuse the cached element table.
|
||||
void logSelectedObjectGuidWeb(std::uint32_t object_id);
|
||||
@@ -524,6 +525,22 @@ public:
|
||||
// Drop every section plane. No-op when none are active.
|
||||
void clearSectionPlanes();
|
||||
|
||||
// Number of active section planes (0..kMaxSectionPlanes).
|
||||
int sectionPlaneCount() const { return int(section_planes_.size()); }
|
||||
|
||||
// ---- Section gizmo interaction (shared desktop + web) -------------------
|
||||
//
|
||||
// All coords are LOGICAL (CSS) pixels; the core derives the logical viewport
|
||||
// from the host. hitTestSectionGizmo returns the plane index whose gizmo
|
||||
// arrow is under (x,y), or -1. The drag trio slides a plane along its normal:
|
||||
// begin captures the plane origin + press point, update reprojects and moves
|
||||
// it, end finishes.
|
||||
int hitTestSectionGizmo(int x, int y);
|
||||
bool beginSectionDrag(int gizmo_index, int mouse_x, int mouse_y);
|
||||
void updateSectionDrag(int mouse_x, int mouse_y);
|
||||
void endSectionDrag() { section_drag_active_ = false; }
|
||||
bool sectionDragActive() const { return section_drag_active_; }
|
||||
|
||||
// ---- Render loop (#84-x) ----------------------------------------------
|
||||
//
|
||||
// Encode one frame: acquire the swapchain texture, run cull (parallel
|
||||
@@ -646,6 +663,27 @@ public:
|
||||
int w, int h,
|
||||
std::uint64_t needed_bytes);
|
||||
|
||||
// CPU half of pickSurfaceAt: cast the pixel's world ray against every
|
||||
// instance carrying `object_id`, returning the closest hit's world pos,
|
||||
// normal (mrt_normal if non-degenerate, else the AABB-face normal), and the
|
||||
// instance bounding-sphere radius. Shared by the sync pickSurfaceAt and the
|
||||
// async pickSurfaceAtAsync. False if no instance is hit.
|
||||
bool raycastSurfaceForObject(std::uint32_t object_id, int x_pixels, int y_pixels,
|
||||
const Eigen::Vector3f& mrt_normal,
|
||||
Eigen::Vector3f& world_pos_out,
|
||||
Eigen::Vector3f& world_normal_out,
|
||||
float& aabb_radius_out);
|
||||
// Decode the RGBA16F pick-normal from the (already-mapped) normal staging
|
||||
// buffer into a unit world normal; unmaps. False if degenerate. Shared by
|
||||
// the sync pickObjectAt and the async pickSurfaceAtAsync.
|
||||
bool decodeMappedPickNormal(Eigen::Vector3f& out);
|
||||
// Logical (CSS-px) viewport size from the host framebuffer / DPR, for the
|
||||
// section-gizmo hit-test + drag (which work in logical pixels).
|
||||
void sectionLogicalViewport(int& w, int& h) const;
|
||||
// Decode the RGBA32F exact world position from the (already-mapped) position
|
||||
// staging buffer; unmaps. False if the texel was a miss (w == 0).
|
||||
bool decodeMappedPickPosition(Eigen::Vector3f& out);
|
||||
|
||||
// Tear down every pick-owned wgpu resource (pipeline + MRTs +
|
||||
// staging buffers). Called from shutdown() before device_ dies.
|
||||
void releasePickResources();
|
||||
@@ -715,8 +753,24 @@ public:
|
||||
Eigen::Vector3f& world_normal_out,
|
||||
float* aabb_radius_out = nullptr);
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
// Async surface pick for web (drives the section tool). Reuses the async
|
||||
// object pick (no new GPU readback), then runs the same CPU ray-AABB cast as
|
||||
// pickSurfaceAt. On web the normal is the AABB-face normal (the precise MRT
|
||||
// normal would need a second async map — a later refinement).
|
||||
struct SurfaceHit {
|
||||
bool found = false;
|
||||
std::uint32_t object_id = 0;
|
||||
Eigen::Vector3f world_pos = Eigen::Vector3f::Zero();
|
||||
Eigen::Vector3f world_normal = Eigen::Vector3f::UnitZ();
|
||||
float aabb_radius = 0.0f;
|
||||
};
|
||||
void pickSurfaceAtAsync(int x_pixels, int y_pixels,
|
||||
std::function<void(SurfaceHit)> cb);
|
||||
#endif
|
||||
|
||||
// Per-pick result for the Area / Length / Volume tools. The
|
||||
// composed_transform mirrors InstanceCpu::transform so callers can
|
||||
// composed_transform mirrors InstanceInfo::transform so callers can
|
||||
// round-trip from mesh-local back to world without re-deriving it.
|
||||
struct MeshLocalPick {
|
||||
std::uint32_t object_id = 0;
|
||||
@@ -845,6 +899,10 @@ private:
|
||||
WGPUPipelineLayout pipeline_layout_ = nullptr;
|
||||
WGPURenderPipeline main_pipeline_ = nullptr;
|
||||
WGPURenderPipeline main_pipeline_transparent_ = nullptr;
|
||||
// Section-plane gizmo, shared by desktop + web (both render via render()).
|
||||
// Lifted out of the Qt-coupled OverlayRenderer so one identical gizmo draws
|
||||
// everywhere; the desktop's OverlayRenderer no longer draws it.
|
||||
SectionGizmoRenderer section_gizmo_;
|
||||
|
||||
// HiZ occlusion-cull pipeline group. Downsamples MSAA depth into a
|
||||
// mip pyramid; consumed by next-frame cull.
|
||||
@@ -934,10 +992,13 @@ private:
|
||||
WGPUTextureView pick_color_view_ = nullptr;
|
||||
WGPUTexture pick_normal_texture_ = nullptr;
|
||||
WGPUTextureView pick_normal_view_ = nullptr;
|
||||
WGPUTexture pick_position_texture_ = nullptr; // RGBA32F exact world pos
|
||||
WGPUTextureView pick_position_view_ = nullptr;
|
||||
WGPUTexture pick_depth_texture_ = nullptr;
|
||||
WGPUTextureView pick_depth_view_ = nullptr;
|
||||
WGPUBuffer pick_staging_buffer_ = nullptr;
|
||||
WGPUBuffer pick_normal_staging_buffer_ = nullptr;
|
||||
WGPUBuffer pick_position_staging_buffer_ = nullptr;
|
||||
int pick_w_ = 0;
|
||||
int pick_h_ = 0;
|
||||
WGPUBuffer box_pick_staging_buffer_ = nullptr;
|
||||
@@ -955,6 +1016,14 @@ private:
|
||||
int box_pick_async_h_ = 0;
|
||||
std::uint64_t box_pick_async_padded_bpr_ = 0;
|
||||
std::uint64_t box_pick_async_bytes_ = 0;
|
||||
// Async surface pick (section tool): chained id→normal staging maps. Reuses
|
||||
// pick_async_in_flight_ (same staging buffers as the single object pick).
|
||||
std::function<void(SurfaceHit)> surface_async_cb_;
|
||||
int surface_async_x_ = 0;
|
||||
int surface_async_y_ = 0;
|
||||
std::uint32_t surface_async_id_ = 0;
|
||||
Eigen::Vector3f surface_async_normal_ = Eigen::Vector3f::Zero();
|
||||
void finishSurfaceAsync(SurfaceHit hit);
|
||||
#endif
|
||||
|
||||
// ---- Frame uniforms + selection bind ----------------------------------
|
||||
@@ -978,6 +1047,13 @@ private:
|
||||
// removeSectionPlane (still Qt-bound — they wire into the input
|
||||
// path). Reading happens here.
|
||||
std::vector<SectionPlane> section_planes_;
|
||||
// Section-gizmo drag state (shared): which plane, its press-time origin, and
|
||||
// the press point (logical px) so update can slide it along the normal.
|
||||
bool section_drag_active_ = false;
|
||||
int section_drag_index_ = -1;
|
||||
Eigen::Vector3f section_drag_start_origin_ = Eigen::Vector3f::Zero();
|
||||
int section_drag_start_mx_ = 0;
|
||||
int section_drag_start_my_ = 0;
|
||||
|
||||
// X-ray mode alpha clamp: when < 1.0 every instance routes through
|
||||
// the transparent pass with fragment.a clamped to min(in.color.a, cap).
|
||||
|
||||
Reference in New Issue
Block a user