diff --git a/src/ifcviewer/ViewportCore.cpp b/src/ifcviewer/ViewportCore.cpp index cef5199cc7..4ad8e46a88 100644 --- a/src/ifcviewer/ViewportCore.cpp +++ b/src/ifcviewer/ViewportCore.cpp @@ -5268,3 +5268,52 @@ void ViewportCore::render() { } } } + +// =========================================================================== +// Section planes (#84-y) +// =========================================================================== + +bool ViewportCore::addSectionPlaneAtSurface(const Eigen::Vector3f& point, + const Eigen::Vector3f& normal, + float visual_radius) { + if (int(section_planes_.size()) >= kMaxSectionPlanes) { + Log::warn() << "[wgpu section] cap reached (" << kMaxSectionPlanes + << " planes)"; + return false; + } + Eigen::Vector3f n = normal; + if (n.squaredNorm() < 1e-8f) return false; + n.normalize(); + // Auto-flip the normal so the camera-facing half gets cut away. + const Eigen::Vector3f eye = orbitEye(camera_target_, camera_distance_, + camera_yaw_deg_, camera_pitch_deg_); + const Eigen::Vector3f eye_dir = eye - point; + if (n.dot(eye_dir) < 0.0f) n = -n; + + SectionPlane p; + p.n = n; + p.origin = point; + p.d = -n.dot(point); + p.visual_radius = (visual_radius > 0.0f) ? visual_radius : 1.0f; + section_planes_.push_back(p); + Log::info() + << "[wgpu section] added plane #" << section_planes_.size() - 1 + << " origin=(" << point.x() << "," << point.y() << "," << point.z() << ")" + << " normal=(" << n.x() << "," << n.y() << "," << n.z() << ")"; + host_->requestFrame(); + return true; +} + +void ViewportCore::removeSectionPlane(int index) { + if (index < 0 || index >= int(section_planes_.size())) return; + section_planes_.erase(section_planes_.begin() + index); + Log::info() << "[wgpu section] removed plane " << index; + host_->requestFrame(); +} + +void ViewportCore::clearSectionPlanes() { + if (section_planes_.empty()) return; + section_planes_.clear(); + Log::info() << "[wgpu section] cleared all planes"; + host_->requestFrame(); +} diff --git a/src/ifcviewer/ViewportCore.h b/src/ifcviewer/ViewportCore.h index 3df584bdfb..01755df9e2 100644 --- a/src/ifcviewer/ViewportCore.h +++ b/src/ifcviewer/ViewportCore.h @@ -355,6 +355,23 @@ public: void finalizeScreenshotCapture(WGPUBuffer capture_buffer, std::uint32_t padded_bpr); + // ---- Section planes (#84-y) ------------------------------------------- + // + // Append a section plane at the supplied surface hit, with a normal + // auto-flipped toward the camera so the first click reveals the + // surface the user just clicked. `visual_radius` controls the + // overlay gizmo size; <= 0 falls back to 1 m. Returns false when + // the kMaxSectionPlanes cap is already reached. + bool addSectionPlaneAtSurface(const Eigen::Vector3f& point, + const Eigen::Vector3f& normal, + float visual_radius); + + // Remove a single section plane by index (no-op when out of range). + void removeSectionPlane(int index); + + // Drop every section plane. No-op when none are active. + void clearSectionPlanes(); + // ---- Render loop (#84-x) ---------------------------------------------- // // Encode one frame: acquire the swapchain texture, run cull (parallel diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 4e8264b72a..dd73d27b25 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -969,51 +969,13 @@ void ViewportWindow::toggleSectionTool() { if (isExposed()) requestUpdate(); } -bool ViewportWindow::addSectionPlaneAtSurface(const Eigen::Vector3f& point, - const Eigen::Vector3f& normal, - float visual_radius) { - if (int(section_planes_.size()) >= kMaxSectionPlanes) { - std::fprintf(stderr, "[warn] [wgpu section] cap reached (%d planes)\n", - kMaxSectionPlanes); - return false; - } - Eigen::Vector3f n = normal; - if (n.squaredNorm() < 1e-8f) return false; - n.normalize(); - // Auto-flip the normal so the camera-facing half gets cut away — that - // way the first click always reveals the surface the user just clicked. - const Eigen::Vector3f eye = orbitEye(camera_target_, camera_distance_, - camera_yaw_deg_, camera_pitch_deg_); - const Eigen::Vector3f eye_dir = eye - point; - if (n.dot(eye_dir) < 0.0f) n = -n; - - SectionPlane p; - p.n = n; - p.origin = point; - p.d = -n.dot(point); - p.visual_radius = (visual_radius > 0.0f) ? visual_radius : 1.0f; - section_planes_.push_back(p); - Log::info().noquote().nospace() - << "[wgpu section] added plane #" << section_planes_.size() - 1 - << " origin=(" << point.x() << "," << point.y() << "," << point.z() << ")" - << " normal=(" << n.x() << "," << n.y() << "," << n.z() << ")"; - if (isExposed()) requestUpdate(); - return true; +bool ViewportWindow::addSectionPlaneAtSurface(const Eigen::Vector3f& point, const Eigen::Vector3f& normal, float visual_radius) { + return core_.addSectionPlaneAtSurface(point, normal, visual_radius); } -void ViewportWindow::removeSectionPlane(int index) { - if (index < 0 || index >= int(section_planes_.size())) return; - section_planes_.erase(section_planes_.begin() + index); - Log::info().noquote() << "[wgpu section] removed plane" << index; - if (isExposed()) requestUpdate(); -} +void ViewportWindow::removeSectionPlane(int index) { core_.removeSectionPlane(index); } -void ViewportWindow::clearSectionPlanes() { - if (section_planes_.empty()) return; - section_planes_.clear(); - Log::info() << "[wgpu section] cleared all planes"; - if (isExposed()) requestUpdate(); -} +void ViewportWindow::clearSectionPlanes() { core_.clearSectionPlanes(); } void ViewportWindow::setOverlayLines( const std::vector& groups) {