mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
ifcviewer: viewport overlay subsystem (highlight tris + HUD text)
New OverlayRenderer module owns every client-supplied overlay primitive drawn after the main pass: tinted, depth-aware highlight triangles via its own GL shader, and top-left HUD text via QPainter on a QOpenGLPaintDevice. Public surface on ViewportWindow is just two forwarders (setHighlightTriangles, setHudText). ViewportWindow's MeshLocalPick now exposes the instance's composed transform so consumers can map mesh-local geometry back to world space without re-querying. AreaMeasurement uses both: its selection key is now (object_id, tri) so per-instance highlighting works for two distinct walls sharing a mesh, and on every pick it rebuilds the world-space tri list and the HUD readout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "OverlayRenderer.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QPainter>
|
||||
#include <QtGlobal>
|
||||
#include <QtOpenGL/QOpenGLPaintDevice>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const char* VERT_SRC = 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* FRAG_SRC = R"(
|
||||
#version 450 core
|
||||
uniform vec4 u_color;
|
||||
out vec4 frag_color;
|
||||
void main() {
|
||||
frag_color = u_color;
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace
|
||||
|
||||
void OverlayRenderer::initialize(QOpenGLFunctions_4_5_Core* gl) {
|
||||
if (gl_) return;
|
||||
gl_ = gl;
|
||||
|
||||
GLuint vs = compile(gl_, GL_VERTEX_SHADER, VERT_SRC);
|
||||
GLuint fs = compile(gl_, GL_FRAGMENT_SHADER, FRAG_SRC);
|
||||
program_ = link(gl_, vs, fs);
|
||||
u_view_proj_ = gl_->glGetUniformLocation(program_, "u_view_proj");
|
||||
u_color_ = gl_->glGetUniformLocation(program_, "u_color");
|
||||
|
||||
gl_->glCreateVertexArrays(1, &vao_);
|
||||
gl_->glCreateBuffers(1, &vbo_);
|
||||
gl_->glEnableVertexArrayAttrib(vao_, 0);
|
||||
gl_->glVertexArrayAttribFormat(vao_, 0, 3, GL_FLOAT, GL_FALSE, 0);
|
||||
gl_->glVertexArrayAttribBinding(vao_, 0, 0);
|
||||
gl_->glVertexArrayVertexBuffer(vao_, 0, vbo_, 0, 3 * sizeof(float));
|
||||
}
|
||||
|
||||
void OverlayRenderer::release() {
|
||||
if (!gl_) return;
|
||||
if (vbo_) gl_->glDeleteBuffers(1, &vbo_);
|
||||
if (vao_) gl_->glDeleteVertexArrays(1, &vao_);
|
||||
if (program_) gl_->glDeleteProgram(program_);
|
||||
program_ = vao_ = vbo_ = 0;
|
||||
vbo_capacity_ = 0;
|
||||
vertex_count_ = 0;
|
||||
gl_ = nullptr;
|
||||
}
|
||||
|
||||
void OverlayRenderer::setHudText(const QString& text) {
|
||||
hud_text_ = text;
|
||||
}
|
||||
|
||||
void OverlayRenderer::setHighlightTriangles(const std::vector<float>& world_xyz,
|
||||
float r, float g, float b, float a) {
|
||||
if (!gl_) return;
|
||||
color_[0] = r; color_[1] = g; color_[2] = b; color_[3] = a;
|
||||
vertex_count_ = GLsizei(world_xyz.size() / 3);
|
||||
if (vertex_count_ == 0) return;
|
||||
|
||||
const size_t bytes = world_xyz.size() * sizeof(float);
|
||||
if (bytes > vbo_capacity_) {
|
||||
// Grow with a little headroom so frequent appends don't realloc.
|
||||
const size_t new_cap = bytes + bytes / 2;
|
||||
gl_->glNamedBufferData(vbo_, GLsizeiptr(new_cap),
|
||||
nullptr, GL_DYNAMIC_DRAW);
|
||||
vbo_capacity_ = new_cap;
|
||||
}
|
||||
gl_->glNamedBufferSubData(vbo_, 0, GLsizeiptr(bytes), world_xyz.data());
|
||||
}
|
||||
|
||||
void OverlayRenderer::render(const float view_proj[16],
|
||||
int pixel_w, int pixel_h, qreal dpr) {
|
||||
if (!gl_) return;
|
||||
|
||||
// GL pass: tinted highlight triangles.
|
||||
if (program_ && vertex_count_ > 0 && color_[3] > 0.0f) {
|
||||
gl_->glUseProgram(program_);
|
||||
gl_->glUniformMatrix4fv(u_view_proj_, 1, GL_FALSE, view_proj);
|
||||
gl_->glUniform4fv(u_color_, 1, color_);
|
||||
|
||||
// Save the GL state we touch and restore at the end so the rest of
|
||||
// the render pass keeps seeing what it expects.
|
||||
GLboolean prev_blend = gl_->glIsEnabled(GL_BLEND);
|
||||
GLboolean prev_cull = gl_->glIsEnabled(GL_CULL_FACE);
|
||||
GLboolean prev_depth_msk = GL_TRUE;
|
||||
gl_->glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_msk);
|
||||
GLint prev_depth_func = GL_LESS;
|
||||
gl_->glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func);
|
||||
GLint prev_blend_src = GL_ONE, prev_blend_dst = GL_ZERO;
|
||||
gl_->glGetIntegerv(GL_BLEND_SRC_ALPHA, &prev_blend_src);
|
||||
gl_->glGetIntegerv(GL_BLEND_DST_ALPHA, &prev_blend_dst);
|
||||
|
||||
gl_->glEnable(GL_BLEND);
|
||||
gl_->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
gl_->glDisable(GL_CULL_FACE); // both sides tinted
|
||||
gl_->glDepthMask(GL_FALSE); // tint, don't occlude
|
||||
gl_->glDepthFunc(GL_LEQUAL); // win the coplanar fight
|
||||
|
||||
gl_->glBindVertexArray(vao_);
|
||||
gl_->glDrawArrays(GL_TRIANGLES, 0, vertex_count_);
|
||||
gl_->glBindVertexArray(0);
|
||||
|
||||
if (!prev_blend) gl_->glDisable(GL_BLEND);
|
||||
gl_->glBlendFunc(prev_blend_src, prev_blend_dst);
|
||||
if (prev_cull) gl_->glEnable(GL_CULL_FACE);
|
||||
gl_->glDepthMask(prev_depth_msk);
|
||||
gl_->glDepthFunc(prev_depth_func);
|
||||
}
|
||||
|
||||
// QPainter pass: HUD text. This rebinds programs/VAOs internally, so
|
||||
// it has to come after every other GL primitive in the overlay.
|
||||
if (!hud_text_.isEmpty() && pixel_w > 0 && pixel_h > 0) {
|
||||
QOpenGLPaintDevice device(QSize(pixel_w, pixel_h));
|
||||
device.setDevicePixelRatio(dpr);
|
||||
QPainter painter(&device);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::TextAntialiasing);
|
||||
|
||||
QFont font("monospace", 11);
|
||||
font.setStyleHint(QFont::TypeWriter);
|
||||
painter.setFont(font);
|
||||
const QFontMetrics fm(font);
|
||||
const int pad_x = 10, pad_y = 6, margin = 12;
|
||||
const int text_w = fm.horizontalAdvance(hud_text_);
|
||||
const int text_h = fm.height();
|
||||
const QRect bg(margin, margin,
|
||||
text_w + 2 * pad_x,
|
||||
text_h + 2 * pad_y);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(0, 0, 0, 160));
|
||||
painter.drawRoundedRect(bg, 4, 4);
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(bg.adjusted(pad_x, pad_y, -pad_x, -pad_y),
|
||||
Qt::AlignLeft | Qt::AlignVCenter,
|
||||
hud_text_);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user