/******************************************************************************** * * * 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 WGPUOVERLAYRENDERER_H #define WGPUOVERLAYRENDERER_H #include #include #include #include #include #include // Per-frame snapshot of viewport state that every overlay needs. Built once // at the top of render() and passed by const-ref to each encodeX() call so // the overlay renderer never reaches back into the viewport. struct OverlayFrame { Eigen::Matrix4f view_proj = Eigen::Matrix4f::Identity(); Eigen::Vector3f camera_target = Eigen::Vector3f::Zero(); float camera_distance = 5.0f; float camera_yaw_deg = 0.0f; float camera_pitch_deg = 0.0f; float camera_fov_y_deg = 45.0f; int viewport_w_px = 0; int viewport_h_px = 0; 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; }; // All viewport overlays in one place: axis indicator (corner + pivot), // section plane gizmos, and the marquee drag rect. Mirrors GL's // OverlayRenderer split so ViewportWindow.cpp doesn't have to // carry ~1.5k lines of pipeline plumbing. // // Lifecycle: init() once after the device is up, destroy() before the // device dies. Pipelines are immutable after init; only per-frame // uniforms get re-written. // // Threading: all calls are main-thread only — they touch the wgpu queue. class OverlayRenderer { public: OverlayRenderer() = default; ~OverlayRenderer(); OverlayRenderer(const OverlayRenderer&) = delete; OverlayRenderer& operator=(const OverlayRenderer&) = delete; bool init(WGPUInstance instance, WGPUDevice device, WGPUQueue queue, WGPUTextureFormat surface_format, int sample_count); void destroy(); // ---- Inside the main MSAA pass, after geometry ---- // Both share depth with the scene so they're correctly occluded. // Orbit pivot indicator. `visible` is the viewport's UI gate (orbit // drag / wheel-zoom afterglow). When false this is a cheap no-op. void encodePivot(WGPURenderPassEncoder pass, const OverlayFrame& f, bool visible); // Per-plane wireframe gizmo (2 × 2 m quad outline + arrow shaft + // arrow head). Drawn at each plane's origin in its local basis; // colour comes from Bonsai's decorator_color_error. void encodeSectionGizmos(WGPURenderPassEncoder pass, const OverlayFrame& f, const std::vector& planes); // Replace the highlight-triangle list. `world_xyz` is 3 floats per // vertex, 3 vertices per triangle, in world space (post-composed- // transform). Empty disables the overlay. Color is RGBA in [0, 1] — // alpha < 1 gives the translucent patch shading the Area tool uses. // Drawn inside the main MSAA pass so depth-test hides patches // behind closer geometry; depth-write stays off so later overlays // can still draw over the highlight. void setHighlightTriangles(const std::vector& world_xyz, float r, float g, float b, float a); void encodeHighlightTriangles(WGPURenderPassEncoder pass, const OverlayFrame& f); // One stylistic group of world-space line segments. Mirrors GL // OverlayRenderer::LineGroup so callers can target either backend // with one struct. struct LineGroup { std::vector world_xyz; // 6 floats per segment (a, b) float color[4] = {1, 1, 1, 1}; // inner color float stroke_color[4] = {0, 0, 0, 1}; // halo (alpha 0 = no stroke) float line_width = 1.5f; // inner full-width (px) float stroke_extra = 0.5f; // halo per side (px) float dash_period_px = 0.0f; // 0 = solid float dash_on_ratio = 0.6f; // [0..1], only when period > 0 }; // Replace the overlay-line set. Each call CPU-expands every segment // into six vertices (two triangles), uploads the concatenated // expanded buffer once, and writes one uniform slot per group; the // next encodeOverlayLines() draws them in order. Empty `groups` // clears the set so subsequent encodes are no-ops. void setOverlayLines(const std::vector& groups); // Encode the most-recently set line groups. One draw per group with // a dynamic uniform offset; the shader handles stroke + dash from // per-group uniforms. Drawn inside the main MSAA pass so the lines // are depth-tested against geometry. void encodeOverlayLines(WGPURenderPassEncoder pass, const OverlayFrame& f); // Replace the overlay-point set. World-space positions are CPU- // expanded into screen-space quads at encode time. `pixel_size` is // the inner-disc diameter (px); when `stroke_a > 0` each quad picks // up a `stroke_extra`-pixel halo per side. Empty `world_xyz` clears // the set. Mirrors GL OverlayRenderer::setOverlayPoints. void setOverlayPoints(const std::vector& world_xyz, float r, float g, float b, float a, float pixel_size, float stroke_r, float stroke_g, float stroke_b, float stroke_a, float stroke_extra); // Encode the most-recently set point list. Single draw covering all // points; the shader does the sprite-distance pick + AA. Drawn // inside the main MSAA pass so depth-test correctly hides points // behind closer geometry. void encodeOverlayPoints(WGPURenderPassEncoder pass, const OverlayFrame& f); // World-anchored text label. Mirrors GL OverlayRenderer::Label so // measure-tool readouts can target either backend. struct Label { float world_pos[3]; QString text; }; void setOverlayLabels(const std::vector