mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
wgpu: section cutting tool — K hotkey, click-to-add, drag arrow, Esc/Del
Mirrors GL ViewportWindow's section tool end-to-end. Hotkeys K toggle the tool active Shift+K clearSectionPlanes Esc deactivate the tool Del/Bksp remove the most recently added plane (tool-active only) LMB click pickSurfaceAt → addSectionPlaneAtSurface (no modifier) LMB drag on the arrow gizmo: slide the plane along its normal State FrameUniforms grows by clip_count (i32) + clip_planes[6] (vec4). The six-plane cap matches GL's MaxSectionPlanes. WGSL pads via three scalar i32s instead of a vec3<i32> so the array starts at offset 144 to match the tightly-packed C++ struct (240 B) — vec3 would have forced clip_planes to 160 and broken the binding-size match. is_section_clipped(world) in WGSL evaluates all active planes and returns true if any signals "on the positive side". Both main and pick fragments discard with it so cuts are visible AND selection is consistent — you can't pick something the user can't see. Surface pick Pick pass now emits 2 color targets: R32UInt object_id at @location(0) and RGBA16F packed world-space normal at @location(1). Normal is packed × 0.5 + 0.5 so unsigned-ish halfs keep the sign. pickSurfaceAt reads both via 1×1 texel copies (RGBA16F is a color format with no full-mip restriction, unlike Depth32Float). World position comes from ray-AABB intersection against the picked instance's AABB — equally accurate for "drop a plane where I clicked" and dodges the Depth32Float copy-extent rule entirely. The pick normal is decoded into the per-fragment surface normal so the plane lands perpendicular to the actual triangle (not the AABB face). Plane gizmo Identical geometry to GL's renderSectionPlanes: 2×2 m quad outline (white) + 1 m arrow shaft along +n (yellow-orange) + 4 arrow-head diagonals. Drawn inside the main MSAA pass with depth LessEqual + no depth write. Lines are rendered as screen-space-expanded thick quads with fwidth-based AA, same technique the axis indicator uses, so the gizmo reads against busy BIM geometry rather than disappearing as 1-px hairlines. Drag mousePressEvent claims a plain-LMB press if it hits an arrow gizmo (12 logical-px grab radius, distance to the (origin, origin+n) screen-space segment). The drag handler projects the cursor delta onto the screen-space axis and converts to metres via delta·axis / |axis|² — same formula GL uses. Mid-drag camera moves keep working because the projection re-runs every frame against the press-time origin. Closes the click-to-add + drag halves of #30 / #60. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -248,8 +248,39 @@ private:
|
||||
// Synchronous pick: encodes a one-shot R32UInt render of the current
|
||||
// visible_draws against the click pixel, copies the single texel back,
|
||||
// waits, and returns the object_id (0 if nothing was hit). Call from
|
||||
// the main thread between renders.
|
||||
uint32_t pickObjectAt(int x_pixels, int y_pixels);
|
||||
// the main thread between renders. When `normal_out` is non-null, the
|
||||
// pick pass's RGBA16F normal MRT is also sampled at the same pixel
|
||||
// (decoded from ×0.5+0.5 packing) so the section tool can drop
|
||||
// perpendicular cuts.
|
||||
uint32_t pickObjectAt(int x_pixels, int y_pixels,
|
||||
QVector3D* normal_out = nullptr);
|
||||
// Pick + ray-cast — returns the object's id, the world-space point
|
||||
// where the pick-pixel pillar enters that instance's AABB, and a
|
||||
// camera-facing normal. Returns false on a background miss. We do
|
||||
// CPU ray-AABB rather than reading per-pixel depth because WebGPU's
|
||||
// copyTextureToBuffer for Depth32Float requires copying the whole
|
||||
// mip extent — wasteful per click — and ray-vs-AABB lands close
|
||||
// enough to the click for the section tool's "drop a plane here" UX.
|
||||
bool pickSurfaceAt(int x_pixels, int y_pixels,
|
||||
uint32_t& object_id_out,
|
||||
QVector3D& world_pos_out,
|
||||
QVector3D& world_normal_out,
|
||||
float* aabb_radius_out = nullptr);
|
||||
|
||||
// Section-cutting tool. Mirrors the GL ViewportWindow API:
|
||||
// K toggle (sectionToolActive / toggleSectionTool)
|
||||
// Shift+K clearSectionPlanes
|
||||
// click addSectionPlaneAtSurface (when tool active)
|
||||
// Del/Backspace removeSectionPlane (most recent, when tool active)
|
||||
// Esc deactivate tool
|
||||
bool sectionToolActive() const { return section_tool_active_; }
|
||||
void toggleSectionTool();
|
||||
bool addSectionPlaneAtSurface(const QVector3D& point,
|
||||
const QVector3D& normal,
|
||||
float visual_radius = 0.0f);
|
||||
void removeSectionPlane(int index);
|
||||
void clearSectionPlanes();
|
||||
int sectionPlaneCount() const { return int(section_planes_.size()); }
|
||||
void ensureHizTextures(int viewport_w, int viewport_h);
|
||||
void releaseHizResources();
|
||||
// Resolves the just-rendered MSAA depth into the small single-sample
|
||||
@@ -445,12 +476,64 @@ private:
|
||||
WGPURenderPipeline pick_pipeline_ = nullptr;
|
||||
WGPUTexture pick_color_texture_ = nullptr;
|
||||
WGPUTextureView pick_color_view_ = nullptr;
|
||||
// Second pick MRT: RGBA16F packed world-space normal. Sampled by
|
||||
// pickSurfaceAt so section cuts are perpendicular to the actual
|
||||
// picked triangle (rather than the AABB face that contains it).
|
||||
WGPUTexture pick_normal_texture_ = nullptr;
|
||||
WGPUTextureView pick_normal_view_ = nullptr;
|
||||
WGPUBuffer pick_normal_staging_buffer_ = nullptr; // 256 B (one RGBA16F texel padded)
|
||||
WGPUTexture pick_depth_texture_ = nullptr;
|
||||
WGPUTextureView pick_depth_view_ = nullptr;
|
||||
WGPUBuffer pick_staging_buffer_ = nullptr; // 256 B (single texel + bytes-per-row pad)
|
||||
int pick_w_ = 0;
|
||||
int pick_h_ = 0;
|
||||
|
||||
// Section-cutting state. Each plane is (n, d) with normal n in world
|
||||
// space and signed distance d = -dot(n, point_on_plane); a point P is
|
||||
// on the kept side iff dot(n, P) + d <= 0. The vector mirrors GL's
|
||||
// ViewportWindow::section_planes_.
|
||||
struct SectionPlane {
|
||||
QVector3D n; // unit normal
|
||||
float d; // -dot(n, origin)
|
||||
QVector3D origin; // surface point at the moment the plane was added
|
||||
float visual_radius; // half-extent for the gizmo quad (set from the picked instance's AABB diagonal)
|
||||
};
|
||||
std::vector<SectionPlane> section_planes_;
|
||||
bool section_tool_active_ = false;
|
||||
// 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;
|
||||
QPoint section_drag_start_mouse_;
|
||||
QVector3D 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 plane visualisation. Renders one translucent quad per active
|
||||
// plane, sized to the scene AABB so the cut is visible at any zoom.
|
||||
// Built once (unit quad in plane-local space), oriented per-plane in
|
||||
// the vertex shader from u_origin + tangent/bitangent (derived from
|
||||
// the plane normal). Two passes: a back-facing fill (alpha 0.18) for
|
||||
// the "behind geometry" hint plus a front-facing fill (alpha 0.35).
|
||||
WGPUShaderModule section_shader_module_ = nullptr;
|
||||
WGPUBindGroupLayout section_bgl_ = nullptr;
|
||||
WGPUPipelineLayout section_pipeline_layout_ = nullptr;
|
||||
WGPURenderPipeline section_pipeline_ = nullptr;
|
||||
WGPUBuffer section_vertex_buffer_ = nullptr;
|
||||
WGPUBuffer section_uniform_buffer_ = nullptr;
|
||||
WGPUBindGroup section_bind_group_ = nullptr;
|
||||
static constexpr uint32_t kSectionUniformSlotSize = 256;
|
||||
bool buildSectionVisualizer();
|
||||
void encodeSectionPlanes(WGPURenderPassEncoder pass,
|
||||
const QMatrix4x4& view_proj);
|
||||
void releaseSectionVisualizer();
|
||||
|
||||
enum class HizSlotState : uint8_t { Idle, Mapping, Mapped };
|
||||
static constexpr int HIZ_SLOTS = 2;
|
||||
WGPUBuffer hiz_staging_buffers_[HIZ_SLOTS] = { nullptr, nullptr };
|
||||
|
||||
Reference in New Issue
Block a user