From ea24851a5193368d5f3c9d3bcb90416c5201aab6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 5 Jun 2026 20:56:24 +1000 Subject: [PATCH] ifcviewer: move section_planes_ + xray_alpha_cap_ + updateFrameUniforms into ViewportCore (#84-m) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-frame uniform packing now lives in ViewportCore::updateFrameUniforms, which reads the camera (via the already-migrated buildViewProj), the section_planes_ vector, and the xray_alpha_cap_ scalar — all of which have moved into ViewportCore alongside frame_uniform_buffer_. ViewportWindow keeps reference-aliases on section_planes_ and xray_alpha_cap_ so the section-tool and X-ray toggle (still Qt-input- bound, still living in VW) keep compiling unchanged. The render-path caller in VW::render now does core_.updateFrameUniforms(). Extracted SectionPlane into its own Qt-free header (SectionPlane.h) so ViewportCore doesn't have to include OverlayRenderer.h's QString / QHash. OverlayRenderer.h re-exports it. --- src/ifcviewer/CMakeLists.txt | 1 + src/ifcviewer/OverlayRenderer.h | 12 +-------- src/ifcviewer/SectionPlane.h | 42 ++++++++++++++++++++++++++++++++ src/ifcviewer/ViewportCore.cpp | 38 +++++++++++++++++++++++++++++ src/ifcviewer/ViewportCore.h | 21 ++++++++++++++++ src/ifcviewer/ViewportWindow.cpp | 42 +++----------------------------- src/ifcviewer/ViewportWindow.h | 27 ++++++++++---------- 7 files changed, 121 insertions(+), 62 deletions(-) create mode 100644 src/ifcviewer/SectionPlane.h diff --git a/src/ifcviewer/CMakeLists.txt b/src/ifcviewer/CMakeLists.txt index 9d7bbffd90..ec79fe1740 100644 --- a/src/ifcviewer/CMakeLists.txt +++ b/src/ifcviewer/CMakeLists.txt @@ -157,6 +157,7 @@ set(IFCVIEWER_CORE_HEADERS LodBuilder.h Stopwatch.h ModelGpuData.h + SectionPlane.h SelectionState.h SidecarCache.h StreamingLoader.h diff --git a/src/ifcviewer/OverlayRenderer.h b/src/ifcviewer/OverlayRenderer.h index cb9e94f73f..6963dfa2f3 100644 --- a/src/ifcviewer/OverlayRenderer.h +++ b/src/ifcviewer/OverlayRenderer.h @@ -45,17 +45,7 @@ struct OverlayFrame { int device_pixel_ratio = 1; }; -// One section plane as the visualizer consumes it. The viewport owns the -// authoritative state vector (the section tool mutates it); the overlay -// reads from a non-owning span every frame. Held by value because the -// struct is small and copies happen at most six times per frame. -struct SectionPlane { - Eigen::Vector3f n = Eigen::Vector3f::UnitZ(); // unit normal - float d = 0.0f; // -dot(n, origin) - Eigen::Vector3f origin = Eigen::Vector3f::Zero(); // surface point at the - // moment the plane was added - float visual_radius = 0.0f; -}; +#include "SectionPlane.h" // All viewport overlays in one place: axis indicator (corner + pivot), // section plane gizmos, and the marquee drag rect. Mirrors GL's diff --git a/src/ifcviewer/SectionPlane.h b/src/ifcviewer/SectionPlane.h new file mode 100644 index 0000000000..c37efc9461 --- /dev/null +++ b/src/ifcviewer/SectionPlane.h @@ -0,0 +1,42 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCVIEWER_SECTIONPLANE_H +#define IFCVIEWER_SECTIONPLANE_H + +#include + +// One section plane as the renderer + overlay visualiser consume it. Lives +// in its own Qt-free header so ViewportCore can include it without +// dragging in OverlayRenderer's QString / QHash dependencies. +// +// The viewport owns the authoritative state vector (the section tool +// mutates it; the per-frame uniform packs it for the WGSL shader; the +// overlay reads from a non-owning span every frame). Held by value +// because the struct is small and copies happen at most six times per +// frame (kMaxSectionPlanes). +struct SectionPlane { + Eigen::Vector3f n = Eigen::Vector3f::UnitZ(); // unit normal + float d = 0.0f; // -dot(n, origin) + Eigen::Vector3f origin = Eigen::Vector3f::Zero(); // surface point at the + // moment the plane was added + float visual_radius = 0.0f; +}; + +#endif diff --git a/src/ifcviewer/ViewportCore.cpp b/src/ifcviewer/ViewportCore.cpp index b6f1763e65..6d62f365f0 100644 --- a/src/ifcviewer/ViewportCore.cpp +++ b/src/ifcviewer/ViewportCore.cpp @@ -1121,6 +1121,44 @@ void ViewportCore::uploadSelectionFlagsIfDirty() { selection_.markClean(); } +void ViewportCore::updateFrameUniforms() { + Eigen::Matrix4f view, proj; + buildViewProj(view, proj); + + const Eigen::Matrix4f view_proj = proj * view; + + FrameUniforms u = {}; + std::memcpy(u.view_proj, view_proj.data(), 16 * sizeof(float)); + + // Values match the GL viewport's main fragment shader so a side-by-side + // diff of the two backends only shows what the wgpu pipeline has yet to + // implement (edge silhouette pass, MSAA polish, etc.) — not lighting + // model differences. Key + fill are ~unit-length, ~120° apart. + Eigen::Vector3f L( 0.3f, 0.5f, 0.8f); L.normalize(); + Eigen::Vector3f F(-0.3f, -0.5f, 0.8f); F.normalize(); + u.light_dir[0] = L.x(); u.light_dir[1] = L.y(); u.light_dir[2] = L.z(); u.light_dir[3] = 0; + u.fill_dir [0] = F.x(); u.fill_dir [1] = F.y(); u.fill_dir [2] = F.z(); u.fill_dir [3] = 0; + u.sky_color [0] = 0.55f; u.sky_color [1] = 0.60f; u.sky_color [2] = 0.70f; + u.ground_color[0] = 0.35f; u.ground_color[1] = 0.32f; u.ground_color[2] = 0.28f; + + // Pack active section planes. `is_section_clipped` (WGSL) reads + // u.clip_count and u.clip_planes[0..clip_count) and discards + // fragments on the positive side. + const int n = std::min(int(section_planes_.size()), kMaxSectionPlanes); + u.clip_count = n; + for (int i = 0; i < n; ++i) { + const SectionPlane& p = section_planes_[i]; + u.clip_planes[i][0] = p.n.x(); + u.clip_planes[i][1] = p.n.y(); + u.clip_planes[i][2] = p.n.z(); + u.clip_planes[i][3] = p.d; + } + u.xray_alpha_cap = xray_alpha_cap_; + u._pad_xray[0] = u._pad_xray[1] = u._pad_xray[2] = 0.0f; + + wgpuQueueWriteBuffer(queue_, frame_uniform_buffer_, 0, &u, sizeof(u)); +} + // =========================================================================== // Lifecycle (#84-l): initWgpu + probeAndCreatePool + shutdown // =========================================================================== diff --git a/src/ifcviewer/ViewportCore.h b/src/ifcviewer/ViewportCore.h index 5428c58691..7dd8bf642f 100644 --- a/src/ifcviewer/ViewportCore.h +++ b/src/ifcviewer/ViewportCore.h @@ -47,6 +47,7 @@ #include "InstanceCompose.h" #include "InstancedGeometry.h" #include "ModelGpuData.h" +#include "SectionPlane.h" #include "SelectionState.h" #include "StreamingThread.h" #include "ViewportHost.h" @@ -223,6 +224,12 @@ public: void ensureSelectionFlagsBuffer(); void uploadSelectionFlagsIfDirty(); + // Build the FrameUniforms struct (view-proj + lighting + section + // planes + xray cap) from current camera + section_planes_ + + // xray_alpha_cap_ and upload it via the queue. Called once per + // render() at frame start, before any draw encode. + void updateFrameUniforms(); + // ---- wgpu lifecycle ---------------------------------------------------- // // initWgpu brings up the wgpu instance, gets the platform surface @@ -320,6 +327,20 @@ private: uint32_t selection_flags_capacity_ = 0; // u32 entries std::vector selection_flags_scratch_; + // Active world-space section planes (up to kMaxSectionPlanes); packed + // into the per-frame uniform every render and consumed by the WGSL + // is_section_clipped fragment gate. The section tool in + // ViewportWindow mutates this through addSectionPlaneAtSurface / + // removeSectionPlane (still Qt-bound — they wire into the input + // path). Reading happens here. + std::vector section_planes_; + + // 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). + // Toggled by ViewportWindow::toggleXray; consumed by cull + // (transparent-pass classifier) and updateFrameUniforms. + float xray_alpha_cap_ = 1.0f; + // Selection + per-element visibility state machines. Pure CPU // bookkeeping today (no GPU touch beyond the readback uploaded via // selection_flags_buffer_). Mutated on the main thread between diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 0b107ad114..23eca3d5bd 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -271,6 +271,8 @@ ViewportWindow::ViewportWindow(QWindow* parent) selection_flags_buffer_ (core_.selection_flags_buffer_), selection_flags_capacity_(core_.selection_flags_capacity_), selection_flags_scratch_ (core_.selection_flags_scratch_), + section_planes_ (core_.section_planes_), + xray_alpha_cap_ (core_.xray_alpha_cap_), selection_ (core_.selection_), visibility_ (core_.visibility_) { // wgpu doesn't need a GL context; we just need a real native window @@ -3941,7 +3943,7 @@ void ViewportWindow::render() { WGPUTextureView view = wgpuTextureCreateView(surf_tex.texture, nullptr); - updateFrameUniforms(); + core_.updateFrameUniforms(); // Per-frame cull: extract frustum planes from the same VP we just wrote // into the uniform, then run cullModelCpu on every visible model. The @@ -5817,43 +5819,7 @@ static Eigen::Vector3f orbitEye(const float target[3], float dist, // identically everywhere. // buildViewProj moved to ViewportCore (#84-h). -void ViewportWindow::updateFrameUniforms() { - Eigen::Matrix4f view, proj; - core_.buildViewProj(view, proj); - - const Eigen::Matrix4f view_proj = proj * view; - - FrameUniforms u = {}; - std::memcpy(u.view_proj, view_proj.data(), 16 * sizeof(float)); - - // Values match the GL viewport's main fragment shader so a side-by-side - // diff of the two backends only shows what the wgpu pipeline has yet to - // implement (edge silhouette pass, MSAA polish, etc.) — not lighting - // model differences. Key + fill are ~unit-length, ~120° apart. - Eigen::Vector3f L( 0.3f, 0.5f, 0.8f); L.normalize(); - Eigen::Vector3f F(-0.3f, -0.5f, 0.8f); F.normalize(); - u.light_dir[0] = L.x(); u.light_dir[1] = L.y(); u.light_dir[2] = L.z(); u.light_dir[3] = 0; - u.fill_dir [0] = F.x(); u.fill_dir [1] = F.y(); u.fill_dir [2] = F.z(); u.fill_dir [3] = 0; - u.sky_color [0] = 0.55f; u.sky_color [1] = 0.60f; u.sky_color [2] = 0.70f; - u.ground_color[0] = 0.35f; u.ground_color[1] = 0.32f; u.ground_color[2] = 0.28f; - - // Pack active section planes. `is_section_clipped` (WGSL) reads - // u.clip_count and u.clip_planes[0..clip_count) and discards - // fragments on the positive side. - const int n = std::min(int(section_planes_.size()), kMaxSectionPlanes); - u.clip_count = n; - for (int i = 0; i < n; ++i) { - const SectionPlane& p = section_planes_[i]; - u.clip_planes[i][0] = p.n.x(); - u.clip_planes[i][1] = p.n.y(); - u.clip_planes[i][2] = p.n.z(); - u.clip_planes[i][3] = p.d; - } - u.xray_alpha_cap = xray_alpha_cap_; - u._pad_xray[0] = u._pad_xray[1] = u._pad_xray[2] = 0.0f; - - wgpuQueueWriteBuffer(queue_, frame_uniform_buffer_, 0, &u, sizeof(u)); -} +// updateFrameUniforms moved to ViewportCore (#84-m). // computeSceneAabb moved to ViewportCore (#84-h). diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index efbb3223cb..a431ad4ceb 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -557,7 +557,6 @@ private: // any projection is unreliable. True (cull) when AABB is provably // behind every relevant pyramid cell. bool aabbOccludedByHiz(const float mn[3], const float mx[3]) const; - void updateFrameUniforms(); void flushPendingSidecarQueue(); // computeSceneAabb moved to ViewportCore (#84-h). @@ -777,20 +776,22 @@ private: int pick_w_ = 0; int pick_h_ = 0; - // Section-cutting state. SectionPlane lives in OverlayRenderer.h - // because the visualiser reads it; the viewport owns the authoritative - // vector that the section tool mutates. - std::vector section_planes_; + // Section-cutting state aliases (storage in core_). The section tool + // mutates section_planes_ through addSectionPlaneAtSurface / + // removeSectionPlane; the per-frame uniform packs the same vector + // for the WGSL fragment-side clip gate. + std::vector& section_planes_; bool section_tool_active_ = false; - // X-ray mode. Default 1.0 = no effect (fragment shader clamps - // alpha = min(in.color.a, xray_alpha_cap_), which returns in.color.a - // when the cap is 1). Alt+X drops it to 0.3 to translucent the whole - // scene; pressing again restores 1.0. When < 1.0, the cull - // classifier also routes every instance into the transparent pass - // so the blend stage actually fires (an opaque-pass fragment with - // capped alpha would still overwrite the back buffer). - float xray_alpha_cap_ = 1.0f; + // X-ray cap alias (storage in core_). Default 1.0 = no effect + // (fragment shader clamps alpha = min(in.color.a, xray_alpha_cap_), + // which returns in.color.a when the cap is 1). Alt+X drops it to + // 0.3 to translucent the whole scene; pressing again restores 1.0. + // When < 1.0, the cull classifier (also in core) routes every + // instance into the transparent pass so the blend stage actually + // fires (an opaque-pass fragment with capped alpha would still + // overwrite the back buffer). + float& xray_alpha_cap_; // Marquee box-select. Armed on LMB press (when no other tool consumes // the click), becomes active after the cursor moves past