/********************************************************************************
* *
* 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