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:
Dion Moult
2026-04-30 12:13:17 +10:00
parent a06e5ecdf5
commit 309e20009c
2 changed files with 201 additions and 9 deletions
+38 -1
View File
@@ -172,6 +172,30 @@ public:
void setSelectedObjectId(uint32_t id);
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 setBenchmarkFrames(int n);
QString cameraString() const;
@@ -356,9 +380,15 @@ private:
// Per-model GPU data
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_color_tex_ = 0;
GLuint pick_pos_tex_ = 0;
GLuint pick_normal_tex_ = 0;
GLuint pick_depth_rbo_ = 0;
int pick_width_ = 0;
int pick_height_ = 0;
@@ -467,6 +497,13 @@ private:
// Selection
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
int frame_count_ = 0;
float accumulated_time_ = 0.0f;