/******************************************************************************** * * * 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 . * * * ********************************************************************************/ #include "OverlayRenderer.h" #include #include #include #include #include namespace { GLuint compile(QOpenGLFunctions_4_5_Core* gl, GLenum type, const char* src) { GLuint s = gl->glCreateShader(type); gl->glShaderSource(s, 1, &src, nullptr); gl->glCompileShader(s); GLint ok = 0; gl->glGetShaderiv(s, GL_COMPILE_STATUS, &ok); if (!ok) { char log[2048]; gl->glGetShaderInfoLog(s, sizeof(log), nullptr, log); qWarning("OverlayRenderer shader compile error: %s", log); } return s; } GLuint link(QOpenGLFunctions_4_5_Core* gl, GLuint vs, GLuint fs) { GLuint p = gl->glCreateProgram(); gl->glAttachShader(p, vs); gl->glAttachShader(p, fs); gl->glLinkProgram(p); GLint ok = 0; gl->glGetProgramiv(p, GL_LINK_STATUS, &ok); if (!ok) { char log[2048]; gl->glGetProgramInfoLog(p, sizeof(log), nullptr, log); qWarning("OverlayRenderer program link error: %s", log); } gl->glDeleteShader(vs); gl->glDeleteShader(fs); return p; } // ---- Triangle program (flat color) ---- const char* TRI_VS = R"( #version 450 core layout(location = 0) in vec3 in_pos; uniform mat4 u_view_proj; void main() { gl_Position = u_view_proj * vec4(in_pos, 1.0); } )"; const char* TRI_FS = R"( #version 450 core uniform vec4 u_color; out vec4 frag_color; void main() { frag_color = u_color; } )"; // ---- Point sprite program (outlined disc via gl_PointCoord) ---- const char* POINT_VS = R"( #version 450 core layout(location = 0) in vec3 in_pos; uniform mat4 u_view_proj; uniform float u_point_size; void main() { gl_Position = u_view_proj * vec4(in_pos, 1.0); gl_PointSize = u_point_size; } )"; // inner_radius_norm is the inner-disc radius as a fraction of the // half-sprite (so 1.0 = no stroke, smaller = thicker stroke). The // fragment shader reads gl_PointCoord (range [0,1] across the sprite), // computes the distance from the centre normalised against the half- // sprite, picks inner vs stroke with a sharp `step()` (no soft band), // then anti-aliases the *outer* edge only. const char* POINT_FS = R"( #version 450 core uniform vec4 u_inner_color; uniform vec4 u_stroke_color; uniform float u_inner_radius_norm; out vec4 frag_color; void main() { vec2 c = gl_PointCoord - 0.5; float d = length(c) * 2.0; // 0 at centre, 1 at sprite edge if (d > 1.0) discard; float t_inner = step(u_inner_radius_norm, d); vec4 col = mix(u_inner_color, u_stroke_color, t_inner); float aa = fwidth(d); float outer_alpha = smoothstep(1.0, 1.0 - aa, d); frag_color = vec4(col.rgb, col.a * outer_alpha); } )"; // ---- Line program (screen-space-expanded quads with outline) ---- // // Per-vertex layout: (in_a, in_b, in_side, in_along), 8 floats total. // The vertex shader projects both endpoints to screen pixels, computes // the screen-space perpendicular, and offsets *this* corner accordingly. // Output v_dist_px is the signed perpendicular distance from the line // axis at this corner; linear interpolation across the quad gives the // per-fragment distance the FS uses to discard / pick inner vs stroke. const char* LINE_VS = R"( #version 450 core layout(location = 0) in vec3 in_a; layout(location = 1) in vec3 in_b; layout(location = 2) in float in_side; // -1 or +1 layout(location = 3) in float in_along; // 0 (at a) or 1 (at b) uniform mat4 u_view_proj; uniform vec2 u_screen_size; // physical pixels uniform float u_half_width; // inner half-width (px) uniform float u_stroke_extra; // halo per side (px) out float v_dist_px; out float v_along_px; // distance from segment start (px) void main() { vec4 clip_a = u_view_proj * vec4(in_a, 1.0); vec4 clip_b = u_view_proj * vec4(in_b, 1.0); // Project to screen pixels. vec2 screen_a = (clip_a.xy / clip_a.w) * 0.5 * u_screen_size; vec2 screen_b = (clip_b.xy / clip_b.w) * 0.5 * u_screen_size; vec2 delta = screen_b - screen_a; float len = length(delta); vec2 dir = (len > 1e-6) ? (delta / len) : vec2(1.0, 0.0); vec2 perp = vec2(-dir.y, dir.x); // Offset this corner perpendicular to the line. vec4 clip_self = mix(clip_a, clip_b, in_along); vec2 screen_self = (clip_self.xy / clip_self.w) * 0.5 * u_screen_size; float total_half = u_half_width + u_stroke_extra; screen_self += perp * in_side * total_half; // Back to NDC, then to clip space (multiply by w to undo the w-divide // GL is about to apply). Depth is preserved from the picked endpoint. vec2 ndc_out = screen_self / (u_screen_size * 0.5); gl_Position = vec4(ndc_out * clip_self.w, clip_self.z, clip_self.w); v_dist_px = in_side * total_half; v_along_px = in_along * len; } )"; // ---- Screen-space rect program (label + HUD backgrounds) ---- // // Skip QPainter::fillRect entirely — on QOpenGLPaintDevice it's // unreliable across drivers. Backgrounds are drawn as raw GL quads // using NDC-space coordinates; QPainter only renders the text on top. const char* RECT_VS = R"( #version 450 core layout(location = 0) in vec2 in_ndc; void main() { gl_Position = vec4(in_ndc, 0.0, 1.0); } )"; const char* RECT_FS = R"( #version 450 core uniform vec4 u_color; out vec4 frag_color; void main() { frag_color = u_color; } )"; const char* LINE_FS = R"( #version 450 core in float v_dist_px; in float v_along_px; uniform vec4 u_inner_color; uniform vec4 u_stroke_color; uniform float u_half_width; uniform float u_stroke_extra; uniform float u_dash_period; // 0 = solid uniform float u_dash_on_ratio; out vec4 frag_color; void main() { if (u_dash_period > 0.0) { float t = mod(v_along_px, u_dash_period); if (t > u_dash_period * u_dash_on_ratio) discard; } float ad = abs(v_dist_px); float total = u_half_width + u_stroke_extra; if (ad > total) discard; // Sharp inner-to-stroke transition; AA only the outer halo edge so // the line reads crisp instead of mushy. float t_stroke = step(u_half_width, ad); vec4 col = mix(u_inner_color, u_stroke_color, t_stroke); float outer_a = smoothstep(total, total - 1.0, ad); frag_color = vec4(col.rgb, col.a * outer_a); } )"; void uploadFloats(QOpenGLFunctions_4_5_Core* gl, GLuint vbo, size_t& capacity_bytes, const std::vector& data) { const size_t bytes = data.size() * sizeof(float); if (bytes == 0) return; if (bytes > capacity_bytes) { const size_t new_cap = bytes + bytes / 2; gl->glBindBuffer(GL_ARRAY_BUFFER, vbo); gl->glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(new_cap), nullptr, GL_DYNAMIC_DRAW); capacity_bytes = new_cap; } gl->glBindBuffer(GL_ARRAY_BUFFER, vbo); gl->glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(bytes), data.data()); gl->glBindBuffer(GL_ARRAY_BUFFER, 0); } void uploadFloatBytes(QOpenGLFunctions_4_5_Core* gl, GLuint vbo, size_t& capacity_bytes, const float* data, size_t float_count) { const size_t bytes = float_count * sizeof(float); if (bytes == 0) return; if (bytes > capacity_bytes) { const size_t new_cap = bytes + bytes / 2; gl->glBindBuffer(GL_ARRAY_BUFFER, vbo); gl->glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(new_cap), nullptr, GL_DYNAMIC_DRAW); capacity_bytes = new_cap; } gl->glBindBuffer(GL_ARRAY_BUFFER, vbo); gl->glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(bytes), data); gl->glBindBuffer(GL_ARRAY_BUFFER, 0); } // CPU expansion of N segments (3 floats * 2 verts per segment, packed) into // 6 vertices per segment, each carrying (a, b, side, along) = 8 floats. void expandLineSegments(const std::vector& endpoints, std::vector& out) { out.clear(); if (endpoints.size() < 6) return; const size_t n_segs = endpoints.size() / 6; out.reserve(n_segs * 6 * 8); static const float CORNERS[6][2] = { {-1.0f, 0.0f}, {+1.0f, 0.0f}, {-1.0f, 1.0f}, {-1.0f, 1.0f}, {+1.0f, 0.0f}, {+1.0f, 1.0f}, }; for (size_t s = 0; s < n_segs; ++s) { const float* a = &endpoints[s * 6 + 0]; const float* b = &endpoints[s * 6 + 3]; for (int c = 0; c < 6; ++c) { out.push_back(a[0]); out.push_back(a[1]); out.push_back(a[2]); out.push_back(b[0]); out.push_back(b[1]); out.push_back(b[2]); out.push_back(CORNERS[c][0]); out.push_back(CORNERS[c][1]); } } } } // namespace void OverlayRenderer::initialize(QOpenGLFunctions_4_5_Core* gl) { if (gl_) return; gl_ = gl; // Triangle program. { GLuint vs = compile(gl_, GL_VERTEX_SHADER, TRI_VS); GLuint fs = compile(gl_, GL_FRAGMENT_SHADER, TRI_FS); program_tri_ = link(gl_, vs, fs); u_tri_view_proj_ = gl_->glGetUniformLocation(program_tri_, "u_view_proj"); u_tri_color_ = gl_->glGetUniformLocation(program_tri_, "u_color"); } // Point program. { GLuint vs = compile(gl_, GL_VERTEX_SHADER, POINT_VS); GLuint fs = compile(gl_, GL_FRAGMENT_SHADER, POINT_FS); program_pt_ = link(gl_, vs, fs); u_pt_view_proj_ = gl_->glGetUniformLocation(program_pt_, "u_view_proj"); u_pt_point_size_ = gl_->glGetUniformLocation(program_pt_, "u_point_size"); u_pt_inner_color_ = gl_->glGetUniformLocation(program_pt_, "u_inner_color"); u_pt_stroke_color_ = gl_->glGetUniformLocation(program_pt_, "u_stroke_color"); u_pt_inner_radius_ = gl_->glGetUniformLocation(program_pt_, "u_inner_radius_norm"); } // Line program. { GLuint vs = compile(gl_, GL_VERTEX_SHADER, LINE_VS); GLuint fs = compile(gl_, GL_FRAGMENT_SHADER, LINE_FS); program_ln_ = link(gl_, vs, fs); u_ln_view_proj_ = gl_->glGetUniformLocation(program_ln_, "u_view_proj"); u_ln_screen_size_ = gl_->glGetUniformLocation(program_ln_, "u_screen_size"); u_ln_half_width_ = gl_->glGetUniformLocation(program_ln_, "u_half_width"); u_ln_stroke_extra_ = gl_->glGetUniformLocation(program_ln_, "u_stroke_extra"); u_ln_inner_color_ = gl_->glGetUniformLocation(program_ln_, "u_inner_color"); u_ln_stroke_color_ = gl_->glGetUniformLocation(program_ln_, "u_stroke_color"); u_ln_dash_period_ = gl_->glGetUniformLocation(program_ln_, "u_dash_period"); u_ln_dash_on_ratio_ = gl_->glGetUniformLocation(program_ln_, "u_dash_on_ratio"); } // Screen-space rect program. { GLuint vs = compile(gl_, GL_VERTEX_SHADER, RECT_VS); GLuint fs = compile(gl_, GL_FRAGMENT_SHADER, RECT_FS); program_rect_ = link(gl_, vs, fs); u_rect_color_ = gl_->glGetUniformLocation(program_rect_, "u_color"); } // Triangle VAO/VBO: one vec3 attribute. gl_->glCreateVertexArrays(1, &triangles_.vao); gl_->glCreateBuffers(1, &triangles_.vbo); gl_->glEnableVertexArrayAttrib(triangles_.vao, 0); gl_->glVertexArrayAttribFormat(triangles_.vao, 0, 3, GL_FLOAT, GL_FALSE, 0); gl_->glVertexArrayAttribBinding(triangles_.vao, 0, 0); gl_->glVertexArrayVertexBuffer(triangles_.vao, 0, triangles_.vbo, 0, 3 * sizeof(float)); // Point VAO/VBO: one vec3 attribute. gl_->glCreateVertexArrays(1, &points_.vao); gl_->glCreateBuffers(1, &points_.vbo); gl_->glEnableVertexArrayAttrib(points_.vao, 0); gl_->glVertexArrayAttribFormat(points_.vao, 0, 3, GL_FLOAT, GL_FALSE, 0); gl_->glVertexArrayAttribBinding(points_.vao, 0, 0); gl_->glVertexArrayVertexBuffer(points_.vao, 0, points_.vbo, 0, 3 * sizeof(float)); // Line VAO/VBO: 8 floats per vertex (a:vec3, b:vec3, side, along). // Shared across every group; line_draws_ records the (first, count) // slice for each. gl_->glCreateVertexArrays(1, &vao_lines_); gl_->glCreateBuffers(1, &vbo_lines_); const GLsizei stride = 8 * sizeof(float); gl_->glEnableVertexArrayAttrib(vao_lines_, 0); gl_->glVertexArrayAttribFormat(vao_lines_, 0, 3, GL_FLOAT, GL_FALSE, 0); gl_->glVertexArrayAttribBinding(vao_lines_, 0, 0); gl_->glEnableVertexArrayAttrib(vao_lines_, 1); gl_->glVertexArrayAttribFormat(vao_lines_, 1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float)); gl_->glVertexArrayAttribBinding(vao_lines_, 1, 0); gl_->glEnableVertexArrayAttrib(vao_lines_, 2); gl_->glVertexArrayAttribFormat(vao_lines_, 2, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(float)); gl_->glVertexArrayAttribBinding(vao_lines_, 2, 0); gl_->glEnableVertexArrayAttrib(vao_lines_, 3); gl_->glVertexArrayAttribFormat(vao_lines_, 3, 1, GL_FLOAT, GL_FALSE, 7 * sizeof(float)); gl_->glVertexArrayAttribBinding(vao_lines_, 3, 0); gl_->glVertexArrayVertexBuffer(vao_lines_, 0, vbo_lines_, 0, stride); // Screen-rect VAO/VBO: 2 floats per vertex (vec2 NDC). gl_->glCreateVertexArrays(1, &vao_rect_); gl_->glCreateBuffers(1, &vbo_rect_); gl_->glEnableVertexArrayAttrib(vao_rect_, 0); gl_->glVertexArrayAttribFormat(vao_rect_, 0, 2, GL_FLOAT, GL_FALSE, 0); gl_->glVertexArrayAttribBinding(vao_rect_, 0, 0); gl_->glVertexArrayVertexBuffer(vao_rect_, 0, vbo_rect_, 0, 2 * sizeof(float)); } void OverlayRenderer::release() { if (!gl_) return; if (triangles_.vbo) gl_->glDeleteBuffers(1, &triangles_.vbo); if (triangles_.vao) gl_->glDeleteVertexArrays(1, &triangles_.vao); if (points_.vbo) gl_->glDeleteBuffers(1, &points_.vbo); if (points_.vao) gl_->glDeleteVertexArrays(1, &points_.vao); if (vbo_lines_) gl_->glDeleteBuffers(1, &vbo_lines_); if (vao_lines_) gl_->glDeleteVertexArrays(1, &vao_lines_); if (vbo_rect_) gl_->glDeleteBuffers(1, &vbo_rect_); if (vao_rect_) gl_->glDeleteVertexArrays(1, &vao_rect_); if (program_tri_) gl_->glDeleteProgram(program_tri_); if (program_pt_) gl_->glDeleteProgram(program_pt_); if (program_ln_) gl_->glDeleteProgram(program_ln_); if (program_rect_) gl_->glDeleteProgram(program_rect_); triangles_ = {}; points_ = {}; line_draws_.clear(); vao_lines_ = vbo_lines_ = 0; vbo_lines_capacity_ = 0; vao_rect_ = vbo_rect_ = 0; vbo_rect_capacity_ = 0; program_tri_ = program_pt_ = program_ln_ = program_rect_ = 0; gl_ = nullptr; } void OverlayRenderer::setHudText(const QString& text) { hud_text_ = text; } void OverlayRenderer::setOverlayLabels(const std::vector