mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-10 06:00:51 +00:00
ifcviewer: add section-plane clipping plumbing
Adds a clip-plane pipeline used by the upcoming section tool:
- Up to 8 SectionPlane{n, d} entries, AND-combined as
fragment-shader discard against world position. Main and pick
fragment shaders both honour the planes, so cut areas are
neither drawn nor selectable.
- Main vertex shader now passes v_world_pos through.
- Pick FBO grows two attachments (RGB32F world position, RGB16F
world normal) and the pick shader writes both alongside the
object id. pickSurfaceAt() does a single readback of all
three. Existing pickObjectAt() still works unchanged for
callers that just want the id.
- addSectionPlaneAtSurface(point, normal) auto-flips the normal
toward the camera so the first click immediately cuts the
camera-facing half.
No UI yet — that's the next commit (gizmo, drag, K shortcut).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,7 @@ uniform uint u_selected_id;
|
|||||||
|
|
||||||
out vec3 v_normal;
|
out vec3 v_normal;
|
||||||
out vec4 v_color;
|
out vec4 v_color;
|
||||||
|
out vec3 v_world_pos;
|
||||||
flat out uint v_object_id;
|
flat out uint v_object_id;
|
||||||
flat out uint v_selected;
|
flat out uint v_selected;
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ void main() {
|
|||||||
vec3 pos_local = mix(mq.aabb_min.xyz, mq.aabb_max.xyz, a_position_q);
|
vec3 pos_local = mix(mq.aabb_min.xyz, mq.aabb_max.xyz, a_position_q);
|
||||||
|
|
||||||
vec4 world = inst.transform * vec4(pos_local, 1.0);
|
vec4 world = inst.transform * vec4(pos_local, 1.0);
|
||||||
|
v_world_pos = world.xyz;
|
||||||
gl_Position = u_view_projection * world;
|
gl_Position = u_view_projection * world;
|
||||||
|
|
||||||
// Rotate the normal by the upper-3x3 of the transform. BIM placements
|
// Rotate the normal by the upper-3x3 of the transform. BIM placements
|
||||||
@@ -149,14 +151,25 @@ static const char* MAIN_FRAGMENT_SHADER = R"(
|
|||||||
#version 450 core
|
#version 450 core
|
||||||
in vec3 v_normal;
|
in vec3 v_normal;
|
||||||
in vec4 v_color;
|
in vec4 v_color;
|
||||||
|
in vec3 v_world_pos;
|
||||||
flat in uint v_object_id;
|
flat in uint v_object_id;
|
||||||
flat in uint v_selected;
|
flat in uint v_selected;
|
||||||
|
|
||||||
uniform vec3 u_light_dir;
|
uniform vec3 u_light_dir;
|
||||||
|
|
||||||
|
// Section planes — clip the half-space dot(n,p)+d > 0. AND-combined.
|
||||||
|
const int MAX_CLIP_PLANES = 8;
|
||||||
|
uniform int u_clip_count;
|
||||||
|
uniform vec4 u_clip_planes[MAX_CLIP_PLANES];
|
||||||
|
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
for (int i = 0; i < u_clip_count; ++i) {
|
||||||
|
if (dot(u_clip_planes[i].xyz, v_world_pos) + u_clip_planes[i].w > 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
// v_normal already has the reflection flip applied in the vertex
|
// v_normal already has the reflection flip applied in the vertex
|
||||||
// shader. When backface culling is off, open shells let us see the
|
// shader. When backface culling is off, open shells let us see the
|
||||||
// "wrong" side of a face — flip based on gl_FrontFacing so both
|
// "wrong" side of a face — flip based on gl_FrontFacing so both
|
||||||
@@ -177,6 +190,7 @@ static const char* PICK_VERTEX_SHADER = R"(
|
|||||||
#version 450 core
|
#version 450 core
|
||||||
#extension GL_ARB_shader_draw_parameters : require
|
#extension GL_ARB_shader_draw_parameters : require
|
||||||
layout(location = 0) in vec3 a_position_q;
|
layout(location = 0) in vec3 a_position_q;
|
||||||
|
layout(location = 1) in vec2 a_normal_oct;
|
||||||
|
|
||||||
struct InstanceRecord {
|
struct InstanceRecord {
|
||||||
mat4 transform;
|
mat4 transform;
|
||||||
@@ -199,6 +213,15 @@ layout(std430, binding = 2) readonly buffer Meshes {
|
|||||||
uniform mat4 u_view_projection;
|
uniform mat4 u_view_projection;
|
||||||
|
|
||||||
flat out uint v_object_id;
|
flat out uint v_object_id;
|
||||||
|
out vec3 v_world_pos;
|
||||||
|
out vec3 v_world_normal;
|
||||||
|
|
||||||
|
vec3 octDecode(vec2 e) {
|
||||||
|
vec3 n = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y));
|
||||||
|
if (n.z < 0.0) n.xy = (1.0 - abs(n.yx)) * vec2(n.x >= 0.0 ? 1.0 : -1.0,
|
||||||
|
n.y >= 0.0 ? 1.0 : -1.0);
|
||||||
|
return normalize(n);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
uint slot = uint(gl_BaseInstanceARB) + uint(gl_InstanceID);
|
uint slot = uint(gl_BaseInstanceARB) + uint(gl_InstanceID);
|
||||||
@@ -206,7 +229,16 @@ void main() {
|
|||||||
InstanceRecord inst = instances[iid];
|
InstanceRecord inst = instances[iid];
|
||||||
MeshQuant mq = meshes[inst.mesh_id];
|
MeshQuant mq = meshes[inst.mesh_id];
|
||||||
vec3 pos_local = mix(mq.aabb_min.xyz, mq.aabb_max.xyz, a_position_q);
|
vec3 pos_local = mix(mq.aabb_min.xyz, mq.aabb_max.xyz, a_position_q);
|
||||||
gl_Position = u_view_projection * inst.transform * vec4(pos_local, 1.0);
|
vec4 world = inst.transform * vec4(pos_local, 1.0);
|
||||||
|
v_world_pos = world.xyz;
|
||||||
|
gl_Position = u_view_projection * world;
|
||||||
|
|
||||||
|
vec3 n_local = octDecode(a_normal_oct);
|
||||||
|
mat3 rot = mat3(inst.transform);
|
||||||
|
vec3 n = rot * n_local;
|
||||||
|
if (determinant(rot) < 0.0) n = -n;
|
||||||
|
v_world_normal = normalize(n);
|
||||||
|
|
||||||
v_object_id = inst.object_id;
|
v_object_id = inst.object_id;
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
@@ -214,8 +246,27 @@ void main() {
|
|||||||
static const char* PICK_FRAGMENT_SHADER = R"(
|
static const char* PICK_FRAGMENT_SHADER = R"(
|
||||||
#version 450 core
|
#version 450 core
|
||||||
flat in uint v_object_id;
|
flat in uint v_object_id;
|
||||||
out uint frag_id;
|
in vec3 v_world_pos;
|
||||||
void main() { frag_id = v_object_id; }
|
in vec3 v_world_normal;
|
||||||
|
|
||||||
|
const int MAX_CLIP_PLANES = 8;
|
||||||
|
uniform int u_clip_count;
|
||||||
|
uniform vec4 u_clip_planes[MAX_CLIP_PLANES];
|
||||||
|
|
||||||
|
layout(location = 0) out uint frag_id;
|
||||||
|
layout(location = 1) out vec3 frag_pos;
|
||||||
|
layout(location = 2) out vec3 frag_normal;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
for (int i = 0; i < u_clip_count; ++i) {
|
||||||
|
if (dot(u_clip_planes[i].xyz, v_world_pos) + u_clip_planes[i].w > 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frag_id = v_object_id;
|
||||||
|
frag_pos = v_world_pos;
|
||||||
|
frag_normal = normalize(v_world_normal);
|
||||||
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
static const char* AXIS_VERTEX_SHADER = R"(
|
static const char* AXIS_VERTEX_SHADER = R"(
|
||||||
@@ -471,7 +522,9 @@ ViewportWindow::~ViewportWindow() {
|
|||||||
if (axis_program_) gl_->glDeleteProgram(axis_program_);
|
if (axis_program_) gl_->glDeleteProgram(axis_program_);
|
||||||
if (pivot_program_) gl_->glDeleteProgram(pivot_program_);
|
if (pivot_program_) gl_->glDeleteProgram(pivot_program_);
|
||||||
if (pick_fbo_) gl_->glDeleteFramebuffers(1, &pick_fbo_);
|
if (pick_fbo_) gl_->glDeleteFramebuffers(1, &pick_fbo_);
|
||||||
if (pick_color_tex_) gl_->glDeleteTextures(1, &pick_color_tex_);
|
if (pick_color_tex_) gl_->glDeleteTextures(1, &pick_color_tex_);
|
||||||
|
if (pick_pos_tex_) gl_->glDeleteTextures(1, &pick_pos_tex_);
|
||||||
|
if (pick_normal_tex_) gl_->glDeleteTextures(1, &pick_normal_tex_);
|
||||||
if (pick_depth_rbo_) gl_->glDeleteRenderbuffers(1, &pick_depth_rbo_);
|
if (pick_depth_rbo_) gl_->glDeleteRenderbuffers(1, &pick_depth_rbo_);
|
||||||
if (hiz_fbo_) gl_->glDeleteFramebuffers(1, &hiz_fbo_);
|
if (hiz_fbo_) gl_->glDeleteFramebuffers(1, &hiz_fbo_);
|
||||||
if (hiz_depth_tex_) gl_->glDeleteTextures(1, &hiz_depth_tex_);
|
if (hiz_depth_tex_) gl_->glDeleteTextures(1, &hiz_depth_tex_);
|
||||||
@@ -1745,15 +1798,27 @@ uint32_t ViewportWindow::pickObjectAt(int x, int y) {
|
|||||||
int h = height() * devicePixelRatio();
|
int h = height() * devicePixelRatio();
|
||||||
if (pick_width_ != w || pick_height_ != h) {
|
if (pick_width_ != w || pick_height_ != h) {
|
||||||
if (pick_fbo_) gl_->glDeleteFramebuffers(1, &pick_fbo_);
|
if (pick_fbo_) gl_->glDeleteFramebuffers(1, &pick_fbo_);
|
||||||
if (pick_color_tex_) gl_->glDeleteTextures(1, &pick_color_tex_);
|
if (pick_color_tex_) gl_->glDeleteTextures(1, &pick_color_tex_);
|
||||||
if (pick_depth_rbo_) gl_->glDeleteRenderbuffers(1, &pick_depth_rbo_);
|
if (pick_pos_tex_) gl_->glDeleteTextures(1, &pick_pos_tex_);
|
||||||
|
if (pick_normal_tex_) gl_->glDeleteTextures(1, &pick_normal_tex_);
|
||||||
|
if (pick_depth_rbo_) gl_->glDeleteRenderbuffers(1, &pick_depth_rbo_);
|
||||||
gl_->glCreateFramebuffers(1, &pick_fbo_);
|
gl_->glCreateFramebuffers(1, &pick_fbo_);
|
||||||
gl_->glCreateTextures(GL_TEXTURE_2D, 1, &pick_color_tex_);
|
gl_->glCreateTextures(GL_TEXTURE_2D, 1, &pick_color_tex_);
|
||||||
gl_->glTextureStorage2D(pick_color_tex_, 1, GL_R32UI, w, h);
|
gl_->glTextureStorage2D(pick_color_tex_, 1, GL_R32UI, w, h);
|
||||||
gl_->glNamedFramebufferTexture(pick_fbo_, GL_COLOR_ATTACHMENT0, pick_color_tex_, 0);
|
gl_->glNamedFramebufferTexture(pick_fbo_, GL_COLOR_ATTACHMENT0, pick_color_tex_, 0);
|
||||||
|
gl_->glCreateTextures(GL_TEXTURE_2D, 1, &pick_pos_tex_);
|
||||||
|
gl_->glTextureStorage2D(pick_pos_tex_, 1, GL_RGB32F, w, h);
|
||||||
|
gl_->glNamedFramebufferTexture(pick_fbo_, GL_COLOR_ATTACHMENT1, pick_pos_tex_, 0);
|
||||||
|
gl_->glCreateTextures(GL_TEXTURE_2D, 1, &pick_normal_tex_);
|
||||||
|
gl_->glTextureStorage2D(pick_normal_tex_, 1, GL_RGB16F, w, h);
|
||||||
|
gl_->glNamedFramebufferTexture(pick_fbo_, GL_COLOR_ATTACHMENT2, pick_normal_tex_, 0);
|
||||||
gl_->glCreateRenderbuffers(1, &pick_depth_rbo_);
|
gl_->glCreateRenderbuffers(1, &pick_depth_rbo_);
|
||||||
gl_->glNamedRenderbufferStorage(pick_depth_rbo_, GL_DEPTH_COMPONENT24, w, h);
|
gl_->glNamedRenderbufferStorage(pick_depth_rbo_, GL_DEPTH_COMPONENT24, w, h);
|
||||||
gl_->glNamedFramebufferRenderbuffer(pick_fbo_, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, pick_depth_rbo_);
|
gl_->glNamedFramebufferRenderbuffer(pick_fbo_, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, pick_depth_rbo_);
|
||||||
|
static const GLenum draw_bufs[3] = {
|
||||||
|
GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2
|
||||||
|
};
|
||||||
|
gl_->glNamedFramebufferDrawBuffers(pick_fbo_, 3, draw_bufs);
|
||||||
pick_width_ = w;
|
pick_width_ = w;
|
||||||
pick_height_ = h;
|
pick_height_ = h;
|
||||||
}
|
}
|
||||||
@@ -1775,6 +1840,91 @@ uint32_t ViewportWindow::pickObjectAt(int x, int y) {
|
|||||||
return pixel;
|
return pixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ViewportWindow::pickSurfaceAt(int x, int y,
|
||||||
|
uint32_t& object_id_out,
|
||||||
|
QVector3D& world_pos_out,
|
||||||
|
QVector3D& world_normal_out) {
|
||||||
|
const uint32_t id = pickObjectAt(x, y);
|
||||||
|
if (id == 0) return false;
|
||||||
|
|
||||||
|
const int px = x * devicePixelRatio();
|
||||||
|
const int py = (height() - y) * devicePixelRatio();
|
||||||
|
float pos[3] = {0, 0, 0};
|
||||||
|
float normal[3] = {0, 0, 0};
|
||||||
|
gl_->glGetTextureSubImage(pick_pos_tex_, 0, px, py, 0, 1, 1, 1,
|
||||||
|
GL_RGB, GL_FLOAT, sizeof(pos), pos);
|
||||||
|
gl_->glGetTextureSubImage(pick_normal_tex_, 0, px, py, 0, 1, 1, 1,
|
||||||
|
GL_RGB, GL_FLOAT, sizeof(normal), normal);
|
||||||
|
|
||||||
|
QVector3D n(normal[0], normal[1], normal[2]);
|
||||||
|
if (n.lengthSquared() < 1e-8f) {
|
||||||
|
// Pick succeeded for object_id but normal attachment was empty —
|
||||||
|
// unexpected (shader writes it on every covered fragment), so bail
|
||||||
|
// rather than hand the caller a degenerate plane.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
object_id_out = id;
|
||||||
|
world_pos_out = QVector3D(pos[0], pos[1], pos[2]);
|
||||||
|
world_normal_out = n.normalized();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::uploadClipPlaneUniforms(GLuint program) {
|
||||||
|
const GLint u_count = gl_->glGetUniformLocation(program, "u_clip_count");
|
||||||
|
if (u_count < 0) return; // program does not declare clipping uniforms
|
||||||
|
const int n = qMin(int(section_planes_.size()), MaxSectionPlanes);
|
||||||
|
gl_->glUniform1i(u_count, n);
|
||||||
|
if (n == 0) return;
|
||||||
|
float packed[MaxSectionPlanes * 4] = {};
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
packed[i * 4 + 0] = section_planes_[i].n.x();
|
||||||
|
packed[i * 4 + 1] = section_planes_[i].n.y();
|
||||||
|
packed[i * 4 + 2] = section_planes_[i].n.z();
|
||||||
|
packed[i * 4 + 3] = section_planes_[i].d;
|
||||||
|
}
|
||||||
|
const GLint u_planes = gl_->glGetUniformLocation(program, "u_clip_planes");
|
||||||
|
if (u_planes >= 0) {
|
||||||
|
gl_->glUniform4fv(u_planes, n, packed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewportWindow::addSectionPlaneAtSurface(const QVector3D& point,
|
||||||
|
const QVector3D& normal) {
|
||||||
|
if (int(section_planes_.size()) >= MaxSectionPlanes) {
|
||||||
|
qWarning("Section plane cap (%d) reached", MaxSectionPlanes);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QVector3D n = normal;
|
||||||
|
if (n.lengthSquared() < 1e-8f) return false;
|
||||||
|
n.normalize();
|
||||||
|
// Auto-flip so the plane clips the camera-facing half — first click
|
||||||
|
// immediately cuts away what's between the user and the clicked surface.
|
||||||
|
const QVector3D eye_dir = (camera_eye_ - point);
|
||||||
|
if (QVector3D::dotProduct(n, eye_dir) < 0.0f) n = -n;
|
||||||
|
|
||||||
|
SectionPlane p;
|
||||||
|
p.n = n;
|
||||||
|
p.d = -QVector3D::dotProduct(n, point);
|
||||||
|
section_planes_.push_back(p);
|
||||||
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::removeSectionPlane(int index) {
|
||||||
|
if (index < 0 || index >= int(section_planes_.size())) return;
|
||||||
|
section_planes_.erase(section_planes_.begin() + index);
|
||||||
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::clearSectionPlanes() {
|
||||||
|
if (section_planes_.empty()) return;
|
||||||
|
section_planes_.clear();
|
||||||
|
have_cached_cull_ = false;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
void ViewportWindow::cullAndUploadVisible(ModelGpuData& m, const float planes[6][4],
|
void ViewportWindow::cullAndUploadVisible(ModelGpuData& m, const float planes[6][4],
|
||||||
float focal_px, float min_pixel_radius) {
|
float focal_px, float min_pixel_radius) {
|
||||||
cullModelCpu(m, planes, focal_px, min_pixel_radius);
|
cullModelCpu(m, planes, focal_px, min_pixel_radius);
|
||||||
@@ -2112,6 +2262,7 @@ void ViewportWindow::render() {
|
|||||||
gl_->glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp.constData());
|
gl_->glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp.constData());
|
||||||
gl_->glUniform3f(u_light, 0.3f, 0.5f, 0.8f);
|
gl_->glUniform3f(u_light, 0.3f, 0.5f, 0.8f);
|
||||||
gl_->glUniform1ui(u_sel, selected_object_id_);
|
gl_->glUniform1ui(u_sel, selected_object_id_);
|
||||||
|
uploadClipPlaneUniforms(main_program_);
|
||||||
|
|
||||||
visible_triangles_ = 0;
|
visible_triangles_ = 0;
|
||||||
visible_objects_ = 0;
|
visible_objects_ = 0;
|
||||||
@@ -2550,8 +2701,11 @@ void ViewportWindow::render() {
|
|||||||
void ViewportWindow::renderPickPass() {
|
void ViewportWindow::renderPickPass() {
|
||||||
gl_->glBindFramebuffer(GL_FRAMEBUFFER, pick_fbo_);
|
gl_->glBindFramebuffer(GL_FRAMEBUFFER, pick_fbo_);
|
||||||
gl_->glViewport(0, 0, pick_width_, pick_height_);
|
gl_->glViewport(0, 0, pick_width_, pick_height_);
|
||||||
GLuint clear_val = 0;
|
const GLuint zero_id = 0;
|
||||||
gl_->glClearBufferuiv(GL_COLOR, 0, &clear_val);
|
const float zero3[4] = {0, 0, 0, 0};
|
||||||
|
gl_->glClearBufferuiv(GL_COLOR, 0, &zero_id);
|
||||||
|
gl_->glClearBufferfv (GL_COLOR, 1, zero3);
|
||||||
|
gl_->glClearBufferfv (GL_COLOR, 2, zero3);
|
||||||
gl_->glClear(GL_DEPTH_BUFFER_BIT);
|
gl_->glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
QMatrix4x4 vp = proj_matrix_ * view_matrix_;
|
QMatrix4x4 vp = proj_matrix_ * view_matrix_;
|
||||||
@@ -2561,6 +2715,7 @@ void ViewportWindow::renderPickPass() {
|
|||||||
gl_->glUseProgram(pick_program_);
|
gl_->glUseProgram(pick_program_);
|
||||||
GLint u_vp = gl_->glGetUniformLocation(pick_program_, "u_view_projection");
|
GLint u_vp = gl_->glGetUniformLocation(pick_program_, "u_view_projection");
|
||||||
gl_->glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp.constData());
|
gl_->glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp.constData());
|
||||||
|
uploadClipPlaneUniforms(pick_program_);
|
||||||
|
|
||||||
gl_->glFrontFace(GL_CCW);
|
gl_->glFrontFace(GL_CCW);
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,30 @@ public:
|
|||||||
void setSelectedObjectId(uint32_t id);
|
void setSelectedObjectId(uint32_t id);
|
||||||
uint32_t pickObjectAt(int x, int y);
|
uint32_t pickObjectAt(int x, int y);
|
||||||
|
|
||||||
|
// Extended pick: returns the object id, world-space hit point, and
|
||||||
|
// world-space surface normal at (x, y). Renders the same pick pass as
|
||||||
|
// pickObjectAt but reads back from two extra color attachments
|
||||||
|
// (world_pos in RGB32F, world_normal in RGB16F). Returns false if the
|
||||||
|
// click missed all geometry; the out-params are then untouched.
|
||||||
|
bool pickSurfaceAt(int x, int y,
|
||||||
|
uint32_t& object_id_out,
|
||||||
|
QVector3D& world_pos_out,
|
||||||
|
QVector3D& world_normal_out);
|
||||||
|
|
||||||
|
// Section planes — fragment-shader clipping with up to MaxSectionPlanes
|
||||||
|
// active. Each plane clips the half-space dot(n, p) + d > 0. addSection-
|
||||||
|
// PlaneAtSurface auto-flips the normal toward the camera so the first
|
||||||
|
// click immediately cuts away the camera-facing side.
|
||||||
|
static constexpr int MaxSectionPlanes = 8;
|
||||||
|
struct SectionPlane {
|
||||||
|
QVector3D n; // unit world-space normal
|
||||||
|
float d; // -dot(n, p) for some p on the plane
|
||||||
|
};
|
||||||
|
int sectionPlaneCount() const { return int(section_planes_.size()); }
|
||||||
|
bool addSectionPlaneAtSurface(const QVector3D& point, const QVector3D& normal);
|
||||||
|
void removeSectionPlane(int index);
|
||||||
|
void clearSectionPlanes();
|
||||||
|
|
||||||
void setCamera(float tx, float ty, float tz, float dist, float yaw, float pitch);
|
void setCamera(float tx, float ty, float tz, float dist, float yaw, float pitch);
|
||||||
void setBenchmarkFrames(int n);
|
void setBenchmarkFrames(int n);
|
||||||
QString cameraString() const;
|
QString cameraString() const;
|
||||||
@@ -356,9 +380,15 @@ private:
|
|||||||
// Per-model GPU data
|
// Per-model GPU data
|
||||||
std::unordered_map<uint32_t, ModelGpuData> models_gpu_;
|
std::unordered_map<uint32_t, ModelGpuData> models_gpu_;
|
||||||
|
|
||||||
// Pick framebuffer
|
// Pick framebuffer. Three color attachments:
|
||||||
|
// 0: R32UI — object_id
|
||||||
|
// 1: RGB32F — world position at hit
|
||||||
|
// 2: RGB16F — world normal at hit (already flipped for reflections in
|
||||||
|
// the vertex shader)
|
||||||
GLuint pick_fbo_ = 0;
|
GLuint pick_fbo_ = 0;
|
||||||
GLuint pick_color_tex_ = 0;
|
GLuint pick_color_tex_ = 0;
|
||||||
|
GLuint pick_pos_tex_ = 0;
|
||||||
|
GLuint pick_normal_tex_ = 0;
|
||||||
GLuint pick_depth_rbo_ = 0;
|
GLuint pick_depth_rbo_ = 0;
|
||||||
int pick_width_ = 0;
|
int pick_width_ = 0;
|
||||||
int pick_height_ = 0;
|
int pick_height_ = 0;
|
||||||
@@ -467,6 +497,13 @@ private:
|
|||||||
// Selection
|
// Selection
|
||||||
uint32_t selected_object_id_ = 0;
|
uint32_t selected_object_id_ = 0;
|
||||||
|
|
||||||
|
// Active section planes. Uploaded as uniform array each frame to the
|
||||||
|
// main + pick programs; capped at MaxSectionPlanes.
|
||||||
|
std::vector<SectionPlane> section_planes_;
|
||||||
|
// Push the current plane list into a freshly-bound program. No-op if
|
||||||
|
// the program does not declare u_clip_count / u_clip_planes.
|
||||||
|
void uploadClipPlaneUniforms(GLuint program);
|
||||||
|
|
||||||
// FPS smoothing
|
// FPS smoothing
|
||||||
int frame_count_ = 0;
|
int frame_count_ = 0;
|
||||||
float accumulated_time_ = 0.0f;
|
float accumulated_time_ = 0.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user