From 2981500b3bc949986cc51f6b4c9b499953fca011 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 1 Jun 2026 17:38:33 +1000 Subject: [PATCH] Route bonsai through wgpu; delete the GL backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bonsai now drives the wgpu viewport for both sidecar and direct-IFC loads. The GL viewer and its supporting state classes are gone. SceneLoader rewire: - Takes WgpuViewportWindow* instead of ViewportWindow*. - Sidecar path reads metadata only (readSidecarMetadataOnly) and hands the StreamingSidecar off to the new applyCachedModel. Field accesses inside applySidecarData go through .meta. - Direct-IFC path uses the wgpu A-path (upload{Mesh,Instance}Chunk + finalizeModel). The applyLodExtension call is dropped — wgpu has no live LOD1 splice; LOD1 still lands in the on-disk sidecar for the next open. Bonsai migration: - ViewportWindow → WgpuViewportWindow across MainWindow, Measurement, SessionState, and every modules/*/{Commands,Panel,View}.{h,cpp} — 116 sites total. Same s/OverlayRenderer::/WgpuOverlayRenderer::/ rename, 12 sites. - Includes flipped from ../ifcviewer/ViewportWindow.h to ../ifcviewer-wgpu/WgpuViewportWindow.h. OverlayRenderer.h include dropped (transitively reached via the viewport header). - BonsaiViewer links IfcViewerWgpu in addition to IfcViewer for the duration of the migration; the GL-side IfcViewer also publicly links IfcViewerWgpu so SceneLoader can resolve WgpuViewportWindow. GL backend deletion: - src/ifcviewer/ViewportWindow.{cpp,h}, BvhAccel.*, OverlayRenderer.*, Selection.*, Visibility.* all gone. - src/ifcviewer-minimal/ removed entirely (MinimalWindow drove the GL viewport). - src/ifcviewer/tests: test_bvh_accel, test_selection, test_visibility removed. The first has no replacement (wgpu doesn't use a per-instance BVH); the latter two are ported separately. test_lod_builder, test_sidecar_cache, test_instanced_geometry, test_federation remain (backend-agnostic). - IfcViewer's CMakeLists drops OpenGL, Qt::OpenGL, Qt::Widgets — none of the surviving translation units reach for them. Build flag plumbing: - BUILD_BONSAIVIEWER now auto-enables BUILD_BONSAIVIEWER_WGPU since SceneLoader requires the wgpu lib for its WgpuViewportWindow* arg. - The wgpu subprojects add_subdirectory ahead of the GL one so IfcViewerWgpu exists when IfcViewer's link evaluates. - src/ifcviewer-minimal subdir reference removed from cmake/CMakeLists. Co-Authored-By: Claude Opus 4.7 --- cmake/CMakeLists.txt | 30 +- src/bonsaiviewer/CMakeLists.txt | 4 + src/bonsaiviewer/MainWindow.cpp | 8 +- src/bonsaiviewer/Measurement.cpp | 66 +- src/bonsaiviewer/Measurement.h | 30 +- src/bonsaiviewer/SessionState.cpp | 2 +- src/bonsaiviewer/SessionState.h | 4 +- src/bonsaiviewer/modules/models/Commands.cpp | 4 +- src/bonsaiviewer/modules/models/Commands.h | 4 +- src/bonsaiviewer/modules/models/Panel.cpp | 2 +- src/bonsaiviewer/modules/models/Panel.h | 6 +- src/bonsaiviewer/modules/project/Commands.cpp | 24 +- src/bonsaiviewer/modules/project/Commands.h | 12 +- .../modules/viewport/Commands.cpp | 28 +- src/bonsaiviewer/modules/viewport/Commands.h | 28 +- src/bonsaiviewer/modules/viewport/Panel.cpp | 4 +- src/bonsaiviewer/modules/viewport/Panel.h | 6 +- src/bonsaiviewer/modules/viewport/View.cpp | 40 +- src/bonsaiviewer/modules/viewport/View.h | 6 +- src/ifcviewer-minimal/CMakeLists.txt | 37 - src/ifcviewer-minimal/MinimalWindow.cpp | 161 - src/ifcviewer-minimal/MinimalWindow.h | 65 - src/ifcviewer-minimal/main.cpp | 70 - src/ifcviewer/BvhAccel.cpp | 145 - src/ifcviewer/BvhAccel.h | 70 - src/ifcviewer/CMakeLists.txt | 10 +- src/ifcviewer/OverlayRenderer.cpp | 725 --- src/ifcviewer/OverlayRenderer.h | 197 - src/ifcviewer/SceneLoader.cpp | 59 +- src/ifcviewer/SceneLoader.h | 11 +- src/ifcviewer/Selection.cpp | 184 - src/ifcviewer/Selection.h | 126 - src/ifcviewer/ViewportWindow.cpp | 4393 ----------------- src/ifcviewer/ViewportWindow.h | 834 ---- src/ifcviewer/Visibility.cpp | 93 - src/ifcviewer/Visibility.h | 89 - src/ifcviewer/tests/CMakeLists.txt | 45 +- src/ifcviewer/tests/test_bvh_accel.cpp | 184 - src/ifcviewer/tests/test_selection.cpp | 230 - src/ifcviewer/tests/test_visibility.cpp | 180 - 40 files changed, 208 insertions(+), 8008 deletions(-) delete mode 100644 src/ifcviewer-minimal/CMakeLists.txt delete mode 100644 src/ifcviewer-minimal/MinimalWindow.cpp delete mode 100644 src/ifcviewer-minimal/MinimalWindow.h delete mode 100644 src/ifcviewer-minimal/main.cpp delete mode 100644 src/ifcviewer/BvhAccel.cpp delete mode 100644 src/ifcviewer/BvhAccel.h delete mode 100644 src/ifcviewer/OverlayRenderer.cpp delete mode 100644 src/ifcviewer/OverlayRenderer.h delete mode 100644 src/ifcviewer/Selection.cpp delete mode 100644 src/ifcviewer/Selection.h delete mode 100644 src/ifcviewer/ViewportWindow.cpp delete mode 100644 src/ifcviewer/ViewportWindow.h delete mode 100644 src/ifcviewer/Visibility.cpp delete mode 100644 src/ifcviewer/Visibility.h delete mode 100644 src/ifcviewer/tests/test_bvh_accel.cpp delete mode 100644 src/ifcviewer/tests/test_selection.cpp delete mode 100644 src/ifcviewer/tests/test_visibility.cpp diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index b12d648ccf..c30b3fd95c 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -71,9 +71,18 @@ option(BUILD_EXAMPLES "Build example applications." ON) option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON) option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF) option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6 +option(BUILD_IFCMODEL_UI "Build minimal Qt IFC model UI prototype" OFF) option(BUILD_BONSAIVIEWER "Build Bonsai Viewer" OFF) # Requires Qt6 + OpenGL 4.5 option(BUILD_BONSAIVIEWER_TESTS "Build unit tests for Bonsai Viewer core (fetches Catch2 v3)" OFF) option(BUILD_BONSAIVIEWER_WGPU "Build the experimental wgpu backend (fetches wgpu-native binary release)" OFF) +# IfcViewer (the GL static lib) now links against IfcViewerWgpu because +# SceneLoader drives the wgpu viewport. Auto-enable the wgpu subproject +# whenever BUILD_BONSAIVIEWER is on so the link target exists. +if(BUILD_BONSAIVIEWER AND NOT BUILD_BONSAIVIEWER_WGPU) + message(STATUS "BUILD_BONSAIVIEWER implies BUILD_BONSAIVIEWER_WGPU " + "(SceneLoader uses WgpuViewportWindow); auto-enabling.") + set(BUILD_BONSAIVIEWER_WGPU ON) +endif() option(BUILD_PACKAGE "" OFF) option( @@ -645,6 +654,10 @@ if(BUILD_QTVIEWER) add_subdirectory(../src/qtviewer qtviewer) endif() +if(BUILD_IFCMODEL_UI) + add_subdirectory(../src/ifcmodel-ui ifcmodel-ui) +endif() + if(BUILD_IFCGEOM) # install(FILES ${IFCGEOM_H_FILES} # DESTINATION ${INCLUDEDIR}/ifcgeom @@ -686,17 +699,20 @@ if(BUILD_BONSAIVIEWER) enable_testing() endif() + # Wgpu subprojects added first because IfcViewer's SceneLoader now + # links against IfcViewerWgpu; the target must exist when IfcViewer's + # CMakeLists runs. + if(BUILD_BONSAIVIEWER_WGPU) + add_subdirectory(../src/ifcviewer-wgpu ifcviewer-wgpu) + add_subdirectory(../src/ifcviewer-wgpu-minimal ifcviewer-wgpu-minimal) + endif() add_subdirectory(../src/ifcviewer ifcviewer) - add_subdirectory(../src/ifcviewer-minimal ifcviewer-minimal) add_subdirectory(../src/bonsaiviewer bonsaiviewer) endif() -# The wgpu backend is gated independently of BUILD_BONSAIVIEWER: it shares -# Qt but does not depend on the GL viewer's static lib, so a developer can -# build it on its own to iterate on the port without compiling IfcGeom etc. -# However most stages of the port will reference Federation/SceneLoader -# headers from src/ifcviewer, so in practice it ships alongside. -if(BUILD_BONSAIVIEWER_WGPU) +# Standalone wgpu build (no GL viewer / no bonsai) still supported for +# fast iteration on the port. +if(BUILD_BONSAIVIEWER_WGPU AND NOT BUILD_BONSAIVIEWER) add_subdirectory(../src/ifcviewer-wgpu ifcviewer-wgpu) add_subdirectory(../src/ifcviewer-wgpu-minimal ifcviewer-wgpu-minimal) add_subdirectory(../src/wgpu-mem-probe wgpu-mem-probe) diff --git a/src/bonsaiviewer/CMakeLists.txt b/src/bonsaiviewer/CMakeLists.txt index 8ec1b648c5..9e79dffa6b 100644 --- a/src/bonsaiviewer/CMakeLists.txt +++ b/src/bonsaiviewer/CMakeLists.txt @@ -121,7 +121,11 @@ set_target_properties(BonsaiViewer PROPERTIES ) target_link_libraries(BonsaiViewer PRIVATE + # IfcViewer still ships SceneLoader / Federation / GeometryStreamer / + # SidecarBuilder until the GL ViewportWindow is deleted. IfcViewerWgpu + # ships the live render path. Both link cleanly side-by-side. IfcViewer + IfcViewerWgpu Qt${QT_VERSION}::Core Qt${QT_VERSION}::CorePrivate Qt${QT_VERSION}::Gui diff --git a/src/bonsaiviewer/MainWindow.cpp b/src/bonsaiviewer/MainWindow.cpp index 5cfb1d0d45..8c5458ff2b 100644 --- a/src/bonsaiviewer/MainWindow.cpp +++ b/src/bonsaiviewer/MainWindow.cpp @@ -22,7 +22,7 @@ #include "../ifcviewer/AppSettings.h" #include "../ifcviewer/Federation.h" -#include "../ifcviewer/ViewportWindow.h" +#include "../ifcviewer-wgpu/WgpuViewportWindow.h" #include "SessionState.h" #include "components/Buttons.h" #include "components/Panel.h" @@ -529,8 +529,8 @@ void MainWindow::setupLoader() { QMessageBox::warning(this, "Bonsai Viewer", message); }); - connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this, - [this](const ViewportWindow::FrameStats& s) { + connect(viewport_widget_->viewport(), &WgpuViewportWindow::frameStatsUpdated, this, + [this](const WgpuViewportWindow::FrameStats& s) { if (!status_perf_label_->isVisible()) return; status_perf_label_->setText( QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws") @@ -542,7 +542,7 @@ void MainWindow::setupLoader() { .arg(s.total_triangles) .arg(s.gl_draw_calls)); }); - connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked, + connect(viewport_widget_->viewport(), &WgpuViewportWindow::objectPicked, this, [this](uint32_t object_id) { session_state_->setSelectedObjectId(object_id); session_state_->notifySelectionChanged(); diff --git a/src/bonsaiviewer/Measurement.cpp b/src/bonsaiviewer/Measurement.cpp index 104689f497..8cf495b2e7 100644 --- a/src/bonsaiviewer/Measurement.cpp +++ b/src/bonsaiviewer/Measurement.cpp @@ -20,7 +20,7 @@ #include "Measurement.h" -#include "ViewportWindow.h" +#include "WgpuViewportWindow.h" #include #include @@ -35,7 +35,7 @@ namespace { -double meshLocalVolume(const ViewportWindow::MeshTriangles& tris) { +double meshLocalVolume(const WgpuViewportWindow::MeshTriangles& tris) { // Signed tetrahedra from the origin: V = sum( a · (b × c) ) / 6. // Absolute value at the end so winding convention doesn't matter. double sum = 0.0; @@ -67,7 +67,7 @@ double det3(const double M[16]) { } // namespace -double volumeOfObjects(ViewportWindow& vp, +double volumeOfObjects(WgpuViewportWindow& vp, const std::vector& object_ids) { if (object_ids.empty()) return 0.0; @@ -77,14 +77,14 @@ double volumeOfObjects(ViewportWindow& vp, std::unordered_map> by_mesh; by_mesh.reserve(object_ids.size()); for (uint32_t oid : object_ids) { - ViewportWindow::InstanceLookup lk; + WgpuViewportWindow::InstanceLookup lk; if (!vp.findInstance(oid, lk)) continue; const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id; by_mesh[key].push_back(std::abs(det3(lk.placement_transformation))); } double total = 0.0; - ViewportWindow::MeshTriangles tris; + WgpuViewportWindow::MeshTriangles tris; for (const auto& [key, dets] : by_mesh) { const uint32_t model_id = uint32_t(key >> 32); const uint32_t mesh_id = uint32_t(key & 0xffffffffu); @@ -96,7 +96,7 @@ double volumeOfObjects(ViewportWindow& vp, } std::vector> -volumesPerObject(ViewportWindow& vp, +volumesPerObject(WgpuViewportWindow& vp, const std::vector& object_ids) { std::vector> out; if (object_ids.empty()) return out; @@ -108,9 +108,9 @@ volumesPerObject(ViewportWindow& vp, std::unordered_map mesh_vol_local; mesh_vol_local.reserve(object_ids.size()); - ViewportWindow::MeshTriangles tris; + WgpuViewportWindow::MeshTriangles tris; for (uint32_t oid : object_ids) { - ViewportWindow::InstanceLookup lk; + WgpuViewportWindow::InstanceLookup lk; if (!vp.findInstance(oid, lk)) continue; const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id; @@ -238,7 +238,7 @@ constexpr double kCoplanarDot = 0.9999; // ~0.81° tolerance AreaMeasurement::AreaMeasurement() = default; -void AreaMeasurement::clear(ViewportWindow& vp) { +void AreaMeasurement::clear(WgpuViewportWindow& vp) { mesh_cache_.clear(); selected_.clear(); total_area_m2_ = 0.0; @@ -246,7 +246,7 @@ void AreaMeasurement::clear(ViewportWindow& vp) { vp.setOverlayLabels({}); } -void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) { +void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) { // Push every selected triangle's three world-space vertices to the // overlay. Mesh-local positions × per-instance composed transform. std::vector world_xyz; @@ -287,7 +287,7 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) { by_object[object_id].push_back(&sel); } - std::vector labels; + std::vector labels; for (const auto& [obj_id, sels] : by_object) { if (sels.empty()) continue; // All tris belonging to one object share its mesh + transform. @@ -350,7 +350,7 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) { // Centroid → world via the instance's composed transform. const float* M = any.composed_transform; - OverlayRenderer::Label lbl; + WgpuOverlayRenderer::Label lbl; lbl.world_pos[0] = float(M[0]*cx + M[4]*cy + M[8]*cz + M[12]); lbl.world_pos[1] = float(M[1]*cx + M[5]*cy + M[9]*cz + M[13]); lbl.world_pos[2] = float(M[2]*cx + M[6]*cy + M[10]*cz + M[14]); @@ -361,14 +361,14 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) { vp.setOverlayLabels(labels); } -AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp, +AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& vp, uint32_t model_id, uint32_t mesh_id) { const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id); auto it = mesh_cache_.find(key); if (it != mesh_cache_.end()) return &it->second; - ViewportWindow::MeshTriangles tris; + WgpuViewportWindow::MeshTriangles tris; if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr; MeshCache c; @@ -397,8 +397,8 @@ AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp, return &mesh_cache_.emplace(key, std::move(c)).first->second; } -void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) { - ViewportWindow::MeshLocalPick pick; +void AreaMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool alt) { + WgpuViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x, y, pick)) return; MeshCache* cache = meshCache(vp, pick.model_id, pick.mesh_id); @@ -607,10 +607,10 @@ constexpr float DOT_HALO = 1.0f; constexpr float DASH_PERIOD = 9.0f; // px constexpr float DASH_ON_RATIO = 0.55f; // 5 on, 4 off -OverlayRenderer::LineGroup makeGroup(std::vector xyz, +WgpuOverlayRenderer::LineGroup makeGroup(std::vector xyz, float r, float g, float b, bool dashed = false) { - OverlayRenderer::LineGroup gp; + WgpuOverlayRenderer::LineGroup gp; gp.world_xyz = std::move(xyz); gp.color[0] = r; gp.color[1] = g; gp.color[2] = b; gp.color[3] = 1.0f; gp.stroke_color[0] = 0.0f; gp.stroke_color[1] = 0.0f; @@ -635,10 +635,10 @@ void pushSeg(std::vector& xyz, xyz.insert(xyz.end(), b.begin(), b.end()); } -OverlayRenderer::Label makeLabel(const std::array& a, +WgpuOverlayRenderer::Label makeLabel(const std::array& a, const std::array& b, const QString& text) { - OverlayRenderer::Label lbl; + WgpuOverlayRenderer::Label lbl; lbl.world_pos[0] = 0.5f * (a[0] + b[0]); lbl.world_pos[1] = 0.5f * (a[1] + b[1]); lbl.world_pos[2] = 0.5f * (a[2] + b[2]); @@ -646,7 +646,7 @@ OverlayRenderer::Label makeLabel(const std::array& a, return lbl; } -void pushDots(ViewportWindow& vp, const std::vector& xyz) { +void pushDots(WgpuViewportWindow& vp, const std::vector& xyz) { vp.setOverlayPoints(xyz, /*inner*/ 1.0f, 1.0f, 1.0f, 1.0f, /*size*/ DOT_SIZE, @@ -656,7 +656,7 @@ void pushDots(ViewportWindow& vp, const std::vector& xyz) { } // namespace -void LengthMeasurement::clear(ViewportWindow& vp) { +void LengthMeasurement::clear(WgpuViewportWindow& vp) { points_.clear(); normals_.clear(); vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0); @@ -665,8 +665,8 @@ void LengthMeasurement::clear(ViewportWindow& vp) { vp.setHudText(QString()); } -void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) { - ViewportWindow::MeshLocalPick pick; +void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt*/) { + WgpuViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x, y, pick)) return; points_.push_back({pick.world_pos[0], pick.world_pos[1], pick.world_pos[2]}); normals_.push_back({pick.world_normal[0], pick.world_normal[1], pick.world_normal[2]}); @@ -676,14 +676,14 @@ void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) { rebuildOverlay(vp); } -void LengthMeasurement::removeLastPoint(ViewportWindow& vp) { +void LengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) { if (points_.empty()) return; points_.pop_back(); if (!normals_.empty()) normals_.pop_back(); rebuildOverlay(vp); } -void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { +void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { if (points_.size() == 1 && normals_.size() == 1) { rebuildLaserOverlay(vp); return; @@ -694,8 +694,8 @@ void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { for (const auto& p : points_) pushDot(pts_xyz, p); pushDots(vp, pts_xyz); - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; const size_t n = points_.size(); if (n == 2) { @@ -823,7 +823,7 @@ const char* dominantAxisLabel(const float v[3]) { } // namespace -void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { +void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { const auto& wp = first_pick_.world_pos; // float[3] world click const auto& n = first_pick_.world_normal; // float[3] world normal @@ -855,8 +855,8 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { n[0]*t1[1] - n[1]*t1[0], }; - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; QStringList hud_lines; hud_lines << QStringLiteral("Laser measure (click another point for distance)"); double enh[3] = {0.0, 0.0, 0.0}; @@ -873,7 +873,7 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { // co-normal neighbours, then project each patch vertex into the // (t1, t2) basis to get the bounding extent of the face. Stops // exactly at the face edge (no overshoot into adjacent geometry). - ViewportWindow::MeshTriangles tris; + WgpuViewportWindow::MeshTriangles tris; bool have_extent = false; double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0; if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) { @@ -1008,7 +1008,7 @@ void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { wp[1] + NUDGE * n[1], wp[2] + NUDGE * n[2], }; - ViewportWindow::RaycastHit hit; + WgpuViewportWindow::RaycastHit hit; if (vp.raycast(ro, n, hit)) { const double dist = double(hit.distance) + double(NUDGE); const std::array a = {wp[0], wp[1], wp[2]}; diff --git a/src/bonsaiviewer/Measurement.h b/src/bonsaiviewer/Measurement.h index 83f2dbfba5..e535a5b3c6 100644 --- a/src/bonsaiviewer/Measurement.h +++ b/src/bonsaiviewer/Measurement.h @@ -22,7 +22,7 @@ #define IFCINTERFACE_MEASUREMENT_H #include -#include "ViewportWindow.h" +#include "WgpuViewportWindow.h" #include #include #include @@ -36,7 +36,7 @@ // taken as the absolute value of the signed-tetrahedra sum, so winding // convention does not matter. Returns 0.0 for empty input or when nothing // resolves. Recomputes from scratch on every call — no cache. -double volumeOfObjects(ViewportWindow& vp, +double volumeOfObjects(WgpuViewportWindow& vp, const std::vector& object_ids); // Per-object volumes (m³). Same algorithm as volumeOfObjects but @@ -45,11 +45,11 @@ double volumeOfObjects(ViewportWindow& vp, // Used by MainWindow's volume readout to drive both the total HUD and // the per-object overlay labels. std::vector> -volumesPerObject(ViewportWindow& vp, +volumesPerObject(WgpuViewportWindow& vp, const std::vector& object_ids); // Click-to-accumulate area measurement. Each pick resolves the screen -// click to a (instance, triangle) using ViewportWindow's primitives, +// click to a (instance, triangle) using WgpuViewportWindow's primitives, // expands it into the connected coplanar patch (BFS over shared edges, // dot(normal, seed_normal) > 0.9999), then either adds or removes that // patch from the running set depending on whether the seed triangle was @@ -58,7 +58,7 @@ volumesPerObject(ViewportWindow& vp, // separate patches and their areas are summed. // // On every pick the world-space triangles of the running set are pushed -// to ViewportWindow::setHighlightTriangles for in-viewport shading. +// to WgpuViewportWindow::setHighlightTriangles for in-viewport shading. // State is cleared on construction, on clear(), and is expected to be // reset by the host (e.g. when the viewport's area tool toggles off). class AreaMeasurement { @@ -68,11 +68,11 @@ public: // Main entry point: handle one click in area-tool mode. alt = true // suppresses BFS expansion. Logs the per-click delta and running total // via qInfo. Misses are silent. - void onPick(ViewportWindow& vp, int x, int y, bool alt); + void onPick(WgpuViewportWindow& vp, int x, int y, bool alt); // Wipe all accumulated triangles, per-mesh adjacency caches, and the // viewport overlay. - void clear(ViewportWindow& vp); + void clear(WgpuViewportWindow& vp); double totalArea() const { return total_area_m2_; } size_t triangleCount() const { return selected_.size(); } @@ -89,7 +89,7 @@ private: // edge_key (min<<32 | max) → list of triangle indices touching it. std::unordered_map> edges; }; - MeshCache* meshCache(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id); + MeshCache* meshCache(WgpuViewportWindow& vp, uint32_t model_id, uint32_t mesh_id); // Per-selected-triangle record. The composed transform is captured at // pick time so the overlay rebuild doesn't have to re-query the @@ -109,7 +109,7 @@ private: return (uint64_t(object_id) << 32) | uint64_t(tri); } - void rebuildHighlight(ViewportWindow& vp); + void rebuildHighlight(WgpuViewportWindow& vp); std::unordered_map mesh_cache_; std::unordered_map selected_; @@ -138,15 +138,15 @@ class LengthMeasurement { public: LengthMeasurement(); - void onPick(ViewportWindow& vp, int x, int y, bool alt); - void removeLastPoint(ViewportWindow& vp); - void clear(ViewportWindow& vp); + void onPick(WgpuViewportWindow& vp, int x, int y, bool alt); + void removeLastPoint(WgpuViewportWindow& vp); + void clear(WgpuViewportWindow& vp); size_t pointCount() const { return points_.size(); } private: - void rebuildOverlay(ViewportWindow& vp); - void rebuildLaserOverlay(ViewportWindow& vp); + void rebuildOverlay(WgpuViewportWindow& vp); + void rebuildLaserOverlay(WgpuViewportWindow& vp); QString formatReadout() const; std::vector> points_; @@ -156,7 +156,7 @@ private: // updated afterwards. Used by the 1-pt laser BFS to re-locate the // mesh-local position of points_[0] without re-picking. Stays valid // while points_[0] does (pop_back never touches the first element). - ViewportWindow::MeshLocalPick first_pick_{}; + WgpuViewportWindow::MeshLocalPick first_pick_{}; }; #endif // IFCINTERFACE_MEASUREMENT_H diff --git a/src/bonsaiviewer/SessionState.cpp b/src/bonsaiviewer/SessionState.cpp index ea3568da51..56699f59af 100644 --- a/src/bonsaiviewer/SessionState.cpp +++ b/src/bonsaiviewer/SessionState.cpp @@ -35,7 +35,7 @@ SessionState::SessionState(QObject* parent) { } -void SessionState::createLoader(ViewportWindow* viewport) { +void SessionState::createLoader(WgpuViewportWindow* viewport) { Q_ASSERT(!loader_); loader_ = new SceneLoader(viewport, this); loader_->setShouldReadSidecar(true); diff --git a/src/bonsaiviewer/SessionState.h b/src/bonsaiviewer/SessionState.h index 902a933ccb..1e48ef451a 100644 --- a/src/bonsaiviewer/SessionState.h +++ b/src/bonsaiviewer/SessionState.h @@ -28,7 +28,7 @@ class Federation; class SceneLoader; -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer { @@ -44,7 +44,7 @@ public: // Owned by SessionState once it can be tied to a viewport. Wires // loader → element registry signals internally. Call exactly once. - void createLoader(ViewportWindow* viewport); + void createLoader(WgpuViewportWindow* viewport); Federation* federation() const { return federation_; } SceneLoader* loader() const { return loader_; } diff --git a/src/bonsaiviewer/modules/models/Commands.cpp b/src/bonsaiviewer/modules/models/Commands.cpp index ad2a409577..517c8942dc 100644 --- a/src/bonsaiviewer/modules/models/Commands.cpp +++ b/src/bonsaiviewer/modules/models/Commands.cpp @@ -31,7 +31,7 @@ #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" #include "../../../ifcviewer/SidecarBuilder.h" -#include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" #include "../../../ifcgeom/Serializer.h" #include "../../../serializers/document_serializer_plugin.h" @@ -148,7 +148,7 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) { s.setStatusMessage("Models", "Group removed"); } -void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) { +void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id) { const Federation::Model* model = s.federation()->findById(fed_id); const QString label = model ? model->display_name : fed_id; const auto choice = QMessageBox::question( diff --git a/src/bonsaiviewer/modules/models/Commands.h b/src/bonsaiviewer/modules/models/Commands.h index 4bd8a00deb..aeb7f78b4e 100644 --- a/src/bonsaiviewer/modules/models/Commands.h +++ b/src/bonsaiviewer/modules/models/Commands.h @@ -28,7 +28,7 @@ #include class QWidget; -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::models::commands { @@ -41,7 +41,7 @@ void renameGroup(SessionState& s, QWidget& host, const QString& group_id); void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id); void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id); void removeGroup(SessionState& s, QWidget& host, const QString& group_id); -void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id); +void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id); void addModel(SessionState& s, QWidget& host); // Connector picker → pull_models_interactive → addCloudModel + load. // Reachable from AddModelDialog's CloudModel button; the underlying call diff --git a/src/bonsaiviewer/modules/models/Panel.cpp b/src/bonsaiviewer/modules/models/Panel.cpp index ebda8d2a93..6f1ad67b59 100644 --- a/src/bonsaiviewer/modules/models/Panel.cpp +++ b/src/bonsaiviewer/modules/models/Panel.cpp @@ -220,7 +220,7 @@ private: } // namespace ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state, - ViewportWindow* viewport, + WgpuViewportWindow* viewport, QWidget* parent) : components::Panel("Models", nullptr, parent, true) , session_state_(session_state) diff --git a/src/bonsaiviewer/modules/models/Panel.h b/src/bonsaiviewer/modules/models/Panel.h index dadea2ec98..5ff3675113 100644 --- a/src/bonsaiviewer/modules/models/Panel.h +++ b/src/bonsaiviewer/modules/models/Panel.h @@ -26,7 +26,7 @@ #include "../../components/Panel.h" class QTreeView; -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::models { @@ -41,7 +41,7 @@ class ModelsPanel : public components::Panel { Q_OBJECT public: explicit ModelsPanel(bonsaiviewer::SessionState* session_state, - ViewportWindow* viewport, + WgpuViewportWindow* viewport, QWidget* parent = nullptr); // Owned externally (the View constructs and owns the model). The panel @@ -52,7 +52,7 @@ private: void applyColumnLayout(); bonsaiviewer::SessionState* session_state_ = nullptr; - ViewportWindow* viewport_ = nullptr; + WgpuViewportWindow* viewport_ = nullptr; QTreeView* tree_ = nullptr; FederationItemModel* model_ = nullptr; }; diff --git a/src/bonsaiviewer/modules/project/Commands.cpp b/src/bonsaiviewer/modules/project/Commands.cpp index f0e626c7f2..972f9a9303 100644 --- a/src/bonsaiviewer/modules/project/Commands.cpp +++ b/src/bonsaiviewer/modules/project/Commands.cpp @@ -30,7 +30,7 @@ #include "../models/Commands.h" #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" -#include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" #include #include @@ -53,7 +53,7 @@ namespace { // Pure helper — clears the loaded scene without emitting any signals. The // caller (newProject / openProject) emits projectReset / projectOpened once // the whole flow finishes. -void clearScene(SessionState& s, ViewportWindow& vp) { +void clearScene(SessionState& s, WgpuViewportWindow& vp) { vp.setSelectedObjectId(0); s.setSelectedObjectId(0); for (uint32_t mid : s.modelIds()) { @@ -88,7 +88,7 @@ bool confirmDiscardIfDirty(SessionState& s, QWidget& host) { // - if no scene entry exists yet (initial open), queue a load. // Per spec, connector errors are not surfaced to the user; the connector // has already shown its own UI. -void resolveCloudModels(SessionState& s, ViewportWindow& vp) { +void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) { auto* fed = s.federation(); QHash connector_to_fed_ids; for (const auto& m : fed->models()) { @@ -99,7 +99,7 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) { auto* registry = s.connectorRegistry(); QPointer sguard(&s); - QPointer vguard(&vp); + QPointer vguard(&vp); for (auto it = connector_to_fed_ids.constBegin(); it != connector_to_fed_ids.constEnd(); ++it) { @@ -203,7 +203,7 @@ bool isIfcfedUnchanged(const QString& current_path, const QString& candidate_pat return a.readAll() == b.readAll(); } -bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) { +bool openProjectAt(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -268,7 +268,7 @@ bool saveProjectTo(SessionState& s, QWidget& host, const QString& path) { } // namespace -bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) { +bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -285,7 +285,7 @@ bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) { return true; } -bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) { +bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { QFileDialog file_dialog(&host, "Open Project"); file_dialog.setFileMode(QFileDialog::ExistingFile); file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)"); @@ -297,12 +297,12 @@ bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) { return openProjectAt(s, host, vp, path); } -bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) { +bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) { if (path.isEmpty()) return false; return openProjectAt(s, host, vp, path); } -bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { +bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -341,7 +341,7 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { QPointer sguard(&s); QPointer hguard(&host); - QPointer vguard(&vp); + QPointer vguard(&vp); proc->call("pull_ifcfed_interactive", QJsonValue(), [sguard, hguard, vguard, connector_id](const QJsonValue& result) { @@ -371,7 +371,7 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { return true; } -bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { +bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { auto* fed = s.federation(); // Per spec, sync has two independent phases — refreshing the .ifcfed @@ -429,7 +429,7 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { QPointer sguard(&s); QPointer hguard(&host); - QPointer vguard(&vp); + QPointer vguard(&vp); const QString current_path = fed->filePath(); proc->call("pull_ifcfed", fed->manifest(), diff --git a/src/bonsaiviewer/modules/project/Commands.h b/src/bonsaiviewer/modules/project/Commands.h index 12dccdc057..ebc3d2c9f3 100644 --- a/src/bonsaiviewer/modules/project/Commands.h +++ b/src/bonsaiviewer/modules/project/Commands.h @@ -24,7 +24,7 @@ #include class QWidget; -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::project::commands { @@ -32,21 +32,21 @@ namespace bonsaiviewer::modules::project::commands { // User-facing commands. Each owns its own dialogs and confirmations; each // emits exactly one notify() at the end (projectReset / projectOpened / // projectSaved) so views refresh once per command. -bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp); -bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp); +bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); +bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); // Open a specific .ifcfed by path, bypassing the file dialog. Used by the // "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as // openProject; returns false if the load failed or was cancelled. -bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path); +bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path); // Pick a connector, then call pull_ifcfed_interactive and open the resulting // .ifcfed as a fresh project. Non-local models in the loaded federation are // resolved asynchronously via pull_models. -bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp); +bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); // pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads // the .ifcfed from the same cloud target it came from (typically without // user interaction), then opens it like a fresh project — discarding any // local edits after the usual dirty-check prompt. -bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp); +bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); bool saveProject(SessionState& s, QWidget& host); bool saveProjectAs(SessionState& s, QWidget& host); // Push the current federation to the cloud target named in its manifest diff --git a/src/bonsaiviewer/modules/viewport/Commands.cpp b/src/bonsaiviewer/modules/viewport/Commands.cpp index fd8fa30dd5..7464acd7db 100644 --- a/src/bonsaiviewer/modules/viewport/Commands.cpp +++ b/src/bonsaiviewer/modules/viewport/Commands.cpp @@ -22,11 +22,11 @@ #include "../../SessionState.h" #include "../../../ifcviewer/Federation.h" -#include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" namespace bonsaiviewer::modules::viewport::commands { -void setHome(SessionState& session, ViewportWindow& vp) { +void setHome(SessionState& session, WgpuViewportWindow& vp) { auto camera = vp.cameraState(); Federation::HomeView home_view; home_view.target = camera.target; @@ -37,7 +37,7 @@ void setHome(SessionState& session, ViewportWindow& vp) { session.setStatusMessage("Camera", "Home view updated"); } -void goHome(SessionState& session, ViewportWindow& vp) { +void goHome(SessionState& session, WgpuViewportWindow& vp) { Federation* federation = session.federation(); if (!federation->hasHomeView()) { session.setStatusMessage("Camera", "No home view set for this project"); @@ -50,52 +50,52 @@ void goHome(SessionState& session, ViewportWindow& vp) { session.setStatusMessage("Camera", "Home view restored"); } -void viewSelected(ViewportWindow& vp) { +void viewSelected(WgpuViewportWindow& vp) { vp.focusOnSelectedObject(); } -void fly(SessionState& session, ViewportWindow& vp) { +void fly(SessionState& session, WgpuViewportWindow& vp) { vp.requestActivate(); vp.enterFpsMode(); session.setStatusMessage("Mode", "Fly mode active"); } -void toggleSection(SessionState& session, ViewportWindow& vp) { +void toggleSection(SessionState& session, WgpuViewportWindow& vp) { vp.toggleSectionTool(); session.setStatusMessage("Section", vp.sectionToolActive() ? "Section tool active" : "Section tool off"); } -void clearSection(SessionState& session, ViewportWindow& vp) { +void clearSection(SessionState& session, WgpuViewportWindow& vp) { vp.clearSectionPlanes(); session.setStatusMessage("Section", "Section planes cleared"); } -void toggleDistance(ViewportWindow& vp) { +void toggleDistance(WgpuViewportWindow& vp) { vp.toggleLengthTool(); } -void toggleArea(ViewportWindow& vp) { +void toggleArea(WgpuViewportWindow& vp) { vp.toggleAreaTool(); } -void toggleVolume(ViewportWindow& vp) { +void toggleVolume(WgpuViewportWindow& vp) { vp.toggleVolumeTool(); } -void hideSelected(ViewportWindow& vp) { +void hideSelected(WgpuViewportWindow& vp) { vp.hideSelectedElements(); } -void isolateSelected(ViewportWindow& vp) { +void isolateSelected(WgpuViewportWindow& vp) { vp.isolateSelectedElements(); } -void showAll(ViewportWindow& vp) { +void showAll(WgpuViewportWindow& vp) { vp.showAllElements(); } -void invertVisibility(ViewportWindow& vp) { +void invertVisibility(WgpuViewportWindow& vp) { vp.invertElementVisibility(); } diff --git a/src/bonsaiviewer/modules/viewport/Commands.h b/src/bonsaiviewer/modules/viewport/Commands.h index 0233873faa..8f8e25f048 100644 --- a/src/bonsaiviewer/modules/viewport/Commands.h +++ b/src/bonsaiviewer/modules/viewport/Commands.h @@ -21,27 +21,27 @@ #ifndef IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H #define IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::viewport::commands { -void setHome(SessionState& session, ViewportWindow& vp); -void goHome(SessionState& session, ViewportWindow& vp); -void viewSelected(ViewportWindow& vp); +void setHome(SessionState& session, WgpuViewportWindow& vp); +void goHome(SessionState& session, WgpuViewportWindow& vp); +void viewSelected(WgpuViewportWindow& vp); -void fly(SessionState& session, ViewportWindow& vp); -void toggleSection(SessionState& session, ViewportWindow& vp); -void clearSection(SessionState& session, ViewportWindow& vp); +void fly(SessionState& session, WgpuViewportWindow& vp); +void toggleSection(SessionState& session, WgpuViewportWindow& vp); +void clearSection(SessionState& session, WgpuViewportWindow& vp); -void toggleDistance(ViewportWindow& vp); -void toggleArea(ViewportWindow& vp); -void toggleVolume(ViewportWindow& vp); +void toggleDistance(WgpuViewportWindow& vp); +void toggleArea(WgpuViewportWindow& vp); +void toggleVolume(WgpuViewportWindow& vp); -void hideSelected(ViewportWindow& vp); -void isolateSelected(ViewportWindow& vp); -void showAll(ViewportWindow& vp); -void invertVisibility(ViewportWindow& vp); +void hideSelected(WgpuViewportWindow& vp); +void isolateSelected(WgpuViewportWindow& vp); +void showAll(WgpuViewportWindow& vp); +void invertVisibility(WgpuViewportWindow& vp); } // namespace bonsaiviewer::modules::viewport::commands diff --git a/src/bonsaiviewer/modules/viewport/Panel.cpp b/src/bonsaiviewer/modules/viewport/Panel.cpp index ca6c694028..1aa5a978ff 100644 --- a/src/bonsaiviewer/modules/viewport/Panel.cpp +++ b/src/bonsaiviewer/modules/viewport/Panel.cpp @@ -20,7 +20,7 @@ #include "Panel.h" -#include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" #include #include @@ -47,7 +47,7 @@ ViewportPanel::ViewportPanel(QWidget* parent) frame_layout->setContentsMargins(0, 0, 0, 0); frame_layout->setSpacing(0); - viewport_ = new ViewportWindow(); + viewport_ = new WgpuViewportWindow(); viewport_container_ = QWidget::createWindowContainer(viewport_, frame); viewport_container_->setMinimumSize(400, 300); viewport_container_->setFocusPolicy(Qt::StrongFocus); diff --git a/src/bonsaiviewer/modules/viewport/Panel.h b/src/bonsaiviewer/modules/viewport/Panel.h index 6d6db34080..c2bb1d97bb 100644 --- a/src/bonsaiviewer/modules/viewport/Panel.h +++ b/src/bonsaiviewer/modules/viewport/Panel.h @@ -23,7 +23,7 @@ #include -class ViewportWindow; +class WgpuViewportWindow; namespace bonsaiviewer::modules::viewport { @@ -33,10 +33,10 @@ class ViewportPanel : public QWidget { public: explicit ViewportPanel(QWidget* parent = nullptr); - ViewportWindow* viewport() const { return viewport_; } + WgpuViewportWindow* viewport() const { return viewport_; } private: - ViewportWindow* viewport_ = nullptr; + WgpuViewportWindow* viewport_ = nullptr; QWidget* viewport_container_ = nullptr; }; diff --git a/src/bonsaiviewer/modules/viewport/View.cpp b/src/bonsaiviewer/modules/viewport/View.cpp index 06538e70b8..f17a880d71 100644 --- a/src/bonsaiviewer/modules/viewport/View.cpp +++ b/src/bonsaiviewer/modules/viewport/View.cpp @@ -24,8 +24,8 @@ #include "../../SessionState.h" #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" -#include "../../../ifcviewer/ViewportWindow.h" -#include "../../../ifcviewer/OverlayRenderer.h" +#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../../../ifcviewer-wgpu/WgpuOverlayRenderer.h" #include "../../Measurement.h" #include @@ -36,7 +36,7 @@ namespace bonsaiviewer::modules::viewport { ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, - ViewportWindow* viewport, + WgpuViewportWindow* viewport, QObject* parent) : QObject(parent) , session_state_(session_state) @@ -59,54 +59,54 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t) { refresh(); }); // Measurement tools — input-driven, share the View's lifetime. - connect(viewport_, &ViewportWindow::surfacePickedInTool, this, + connect(viewport_, &WgpuViewportWindow::surfacePickedInTool, this, [this](int x, int y, int modifiers) { const bool alt = (modifiers & Qt::AltModifier) != 0; switch (viewport_->toolMode()) { - case ViewportWindow::ToolMode::Area: + case WgpuViewportWindow::ToolMode::Area: area_measurement_->onPick(*viewport_, x, y, alt); viewport_->setHudText(QString("Area: %1 m² (%2 tris)") .arg(area_measurement_->totalArea(), 0, 'f', 4) .arg(area_measurement_->triangleCount())); break; - case ViewportWindow::ToolMode::Length: + case WgpuViewportWindow::ToolMode::Length: length_measurement_->onPick(*viewport_, x, y, alt); break; - case ViewportWindow::ToolMode::None: - case ViewportWindow::ToolMode::Volume: + case WgpuViewportWindow::ToolMode::NoTool: + case WgpuViewportWindow::ToolMode::Volume: break; } }); - connect(viewport_, &ViewportWindow::toolModeChanged, this, - [this](ViewportWindow::ToolMode mode) { + connect(viewport_, &WgpuViewportWindow::toolModeChanged, this, + [this](WgpuViewportWindow::ToolMode mode) { area_measurement_->clear(*viewport_); length_measurement_->clear(*viewport_); switch (mode) { - case ViewportWindow::ToolMode::None: + case WgpuViewportWindow::ToolMode::NoTool: viewport_->setHudText(QString()); viewport_->setOverlayLabels({}); session_state_->setStatusMessage("Measure", "Measurement tool off"); break; - case ViewportWindow::ToolMode::Length: + case WgpuViewportWindow::ToolMode::Length: viewport_->setHudText("Length tool: click first point"); session_state_->setStatusMessage("Measure", "Length tool: LMB add point, Backspace remove last, Esc exits"); break; - case ViewportWindow::ToolMode::Area: + case WgpuViewportWindow::ToolMode::Area: viewport_->setHudText("Area: 0.0000 m² (0 tris)"); session_state_->setStatusMessage("Measure", "Area tool: LMB add, Alt+LMB single tri, click again to remove, Esc exits"); break; - case ViewportWindow::ToolMode::Volume: + case WgpuViewportWindow::ToolMode::Volume: session_state_->setStatusMessage("Measure", "Volume tool: click / box-select objects, Esc exits"); updateVolumeReadout(); break; } }); - connect(viewport_, &ViewportWindow::toolBackspacePressed, this, [this]() { - if (viewport_->toolMode() == ViewportWindow::ToolMode::Length) { + connect(viewport_, &WgpuViewportWindow::toolBackspacePressed, this, [this]() { + if (viewport_->toolMode() == WgpuViewportWindow::ToolMode::Length) { length_measurement_->removeLastPoint(*viewport_); } }); - connect(viewport_, &ViewportWindow::objectPicked, this, [this](uint32_t) { + connect(viewport_, &WgpuViewportWindow::objectPicked, this, [this](uint32_t) { updateVolumeReadout(); }); @@ -194,7 +194,7 @@ void ViewportView::maybeGuessFederatedFalseOrigin(uint32_t mid) { } void ViewportView::updateVolumeReadout() { - if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return; + if (viewport_->toolMode() != WgpuViewportWindow::ToolMode::Volume) return; const auto& sel = viewport_->selection().selectionIds(); if (sel.empty()) { @@ -207,13 +207,13 @@ void ViewportView::updateVolumeReadout() { const auto per_obj = volumesPerObject(*viewport_, ids); double total = 0.0; - std::vector labels; + std::vector labels; labels.reserve(per_obj.size()); for (const auto& [oid, v] : per_obj) { total += v; QVector3D mn, mx; if (!viewport_->computeObjectAabb(oid, mn, mx)) continue; - OverlayRenderer::Label lbl; + WgpuOverlayRenderer::Label lbl; const QVector3D c = (mn + mx) * 0.5f; lbl.world_pos[0] = c.x(); lbl.world_pos[1] = c.y(); diff --git a/src/bonsaiviewer/modules/viewport/View.h b/src/bonsaiviewer/modules/viewport/View.h index ecd7773ba7..09b42452d3 100644 --- a/src/bonsaiviewer/modules/viewport/View.h +++ b/src/bonsaiviewer/modules/viewport/View.h @@ -25,7 +25,7 @@ #include namespace bonsaiviewer { class SessionState; } -class ViewportWindow; +class WgpuViewportWindow; class AreaMeasurement; class LengthMeasurement; @@ -44,7 +44,7 @@ class ViewportView : public QObject { public: explicit ViewportView(bonsaiviewer::SessionState* session_state, - ViewportWindow* viewport, + WgpuViewportWindow* viewport, QObject* parent = nullptr); ~ViewportView() override; @@ -57,7 +57,7 @@ private: void updateVolumeReadout(); bonsaiviewer::SessionState* session_state_ = nullptr; - ViewportWindow* viewport_ = nullptr; + WgpuViewportWindow* viewport_ = nullptr; std::unique_ptr area_measurement_; std::unique_ptr length_measurement_; }; diff --git a/src/ifcviewer-minimal/CMakeLists.txt b/src/ifcviewer-minimal/CMakeLists.txt deleted file mode 100644 index 6efcebb965..0000000000 --- a/src/ifcviewer-minimal/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -################################################################################ -# # -# 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 . # -# # -################################################################################ - -message("Running CMakeLists.txt in /src/ifcviewer-minimal") - -file(GLOB IFCVIEWER_MINIMAL_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -file(GLOB IFCVIEWER_MINIMAL_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -set(IFCVIEWER_MINIMAL_FILES ${IFCVIEWER_MINIMAL_CPP_FILES} ${IFCVIEWER_MINIMAL_H_FILES}) - -add_executable(IfcViewerMinimal ${IFCVIEWER_MINIMAL_FILES}) - -set_target_properties(IfcViewerMinimal PROPERTIES - AUTOMOC ON - WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON -) - -target_link_libraries(IfcViewerMinimal PRIVATE IfcViewer) - -install(TARGETS IfcViewerMinimal EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) -ifcopenshell_deploy_qt_runtime(IfcViewerMinimal) diff --git a/src/ifcviewer-minimal/MinimalWindow.cpp b/src/ifcviewer-minimal/MinimalWindow.cpp deleted file mode 100644 index e74b3f651e..0000000000 --- a/src/ifcviewer-minimal/MinimalWindow.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************************** - * * - * 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 "MinimalWindow.h" -#include "AppSettings.h" - -#include -#include - -MinimalWindow::MinimalWindow(QWidget* parent) - : QMainWindow(parent) -{ - viewport_ = new ViewportWindow(); - viewport_container_ = QWidget::createWindowContainer(viewport_, this); - viewport_container_->setMinimumSize(400, 300); - viewport_container_->setFocusPolicy(Qt::StrongFocus); - setCentralWidget(viewport_container_); - - status_label_ = new QLabel("Ready"); - stats_label_ = new QLabel(); - stats_label_->setVisible(AppSettings::instance().showStats()); - statusBar()->addWidget(status_label_, 1); - statusBar()->addPermanentWidget(stats_label_); - - loader_ = new SceneLoader(viewport_, this); - loader_->setShouldReadSidecar(true); - loader_->setShouldWriteSidecar(true); - connect(loader_, &SceneLoader::loadStarted, - this, &MinimalWindow::onLoadStarted); - connect(loader_, &SceneLoader::loadedFromSidecar, - this, &MinimalWindow::onLoadedFromSidecar); - connect(loader_, &SceneLoader::loadedFromStream, - this, &MinimalWindow::onLoadedFromStream); - connect(loader_, &SceneLoader::loadCancelled, - this, &MinimalWindow::onLoadCancelled); - connect(loader_, &SceneLoader::loadError, - this, &MinimalWindow::onLoadError); - connect(loader_, &SceneLoader::allLoadsFinished, - this, &MinimalWindow::onAllLoadsFinished); - - connect(viewport_, &ViewportWindow::frameStatsUpdated, this, - [this](const ViewportWindow::FrameStats& s) { - if (!stats_label_->isVisible()) return; - stats_label_->setText( - QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 gl_draws (%8 sub)") - .arg(s.fps, 0, 'f', 1) - .arg(s.frame_time_ms, 0, 'f', 1) - .arg(s.visible_objects) - .arg(s.total_objects) - .arg(s.visible_triangles) - .arg(s.total_triangles) - .arg(s.gl_draw_calls) - .arg(s.indirect_sub_draws)); - }); - - connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { - stats_label_->setVisible(show); - if (!show) stats_label_->clear(); - }); - - setWindowTitle("IfcViewerMinimal"); - resize(1200, 800); -} - -void MinimalWindow::addFiles(const QStringList& paths) { - loader_->addFiles(paths); -} - -static QString formatElapsed(qint64 ms) { - return (ms >= 1000) - ? QString::number(ms / 1000.0, 'f', 2) + " s" - : QString::number(ms) + " ms"; -} - -void MinimalWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { - status_label_->setText("Loading: " + display_name); -} - -void MinimalWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { - status_label_->setText(QString("%1 loaded from cache in %2") - .arg(loader_->displayName(mid)) - .arg(formatElapsed(elapsed_ms))); -} - -void MinimalWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { - status_label_->setText(QString("%1 streamed in %2") - .arg(loader_->displayName(mid)) - .arg(formatElapsed(elapsed_ms))); -} - -void MinimalWindow::onLoadCancelled(uint32_t mid) { - status_label_->setText(QString("%1 load cancelled") - .arg(loader_->displayName(mid))); -} - -void MinimalWindow::onLoadError(uint32_t /*mid*/, QString message) { - qWarning("IfcViewerMinimal error: %s", qPrintable(message)); - status_label_->setText("Error: " + message); -} - -void MinimalWindow::onAllLoadsFinished() { - status_label_->setText(QString("Loaded %1 model(s)").arg(loader_->modelCount())); - applyPendingBenchmark(); -} - -void MinimalWindow::setPendingCamera(const QString& params) { - pending_camera_ = params; -} - -void MinimalWindow::setPendingBenchmark(int frames) { - pending_benchmark_ = frames; -} - -void MinimalWindow::setPendingScreenshot(const QString& path) { - pending_screenshot_ = path; -} - -void MinimalWindow::applyPendingBenchmark() { - if (pending_camera_.isEmpty() && pending_benchmark_ <= 0 - && pending_screenshot_.isEmpty()) return; - - if (!pending_camera_.isEmpty()) { - QStringList parts = pending_camera_.split(','); - if (parts.size() == 6) { - viewport_->setCamera( - parts[0].toFloat(), parts[1].toFloat(), parts[2].toFloat(), - parts[3].toFloat(), parts[4].toFloat(), parts[5].toFloat()); - qDebug("Camera set: %s", qPrintable(pending_camera_)); - } else { - qWarning("--camera expects 6 comma-separated values: tx,ty,tz,dist,yaw,pitch"); - } - pending_camera_.clear(); - } - - if (pending_benchmark_ > 0) { - qDebug("Starting benchmark: %d frames", pending_benchmark_); - viewport_->setBenchmarkFrames(pending_benchmark_); - pending_benchmark_ = 0; - } - - if (!pending_screenshot_.isEmpty()) { - viewport_->captureNextFrameToPng(pending_screenshot_, /*quit_after=*/true); - pending_screenshot_.clear(); - } -} diff --git a/src/ifcviewer-minimal/MinimalWindow.h b/src/ifcviewer-minimal/MinimalWindow.h deleted file mode 100644 index 279a2cdd1f..0000000000 --- a/src/ifcviewer-minimal/MinimalWindow.h +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -#ifndef MINIMALWINDOW_H -#define MINIMALWINDOW_H - -#include -#include - -#include "ViewportWindow.h" -#include "SceneLoader.h" - -class MinimalWindow : public QMainWindow { - Q_OBJECT -public: - explicit MinimalWindow(QWidget* parent = nullptr); - ~MinimalWindow() = default; - - void addFiles(const QStringList& paths); - void setPendingCamera(const QString& params); - void setPendingBenchmark(int frames); - // One-shot framebuffer capture queued for the next render. Forwarded - // to ViewportWindow::captureNextFrameToPng; the viewport handles the - // glReadPixels + PNG save + optional QCoreApplication::quit. - void setPendingScreenshot(const QString& path); - -private slots: - void onLoadStarted(uint32_t mid, QString display_name); - void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms); - void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms); - void onLoadCancelled(uint32_t mid); - void onLoadError(uint32_t mid, QString message); - void onAllLoadsFinished(); - -private: - void applyPendingBenchmark(); - - ViewportWindow* viewport_ = nullptr; - SceneLoader* loader_ = nullptr; - QWidget* viewport_container_ = nullptr; - QLabel* status_label_ = nullptr; - QLabel* stats_label_ = nullptr; - - QString pending_camera_; - int pending_benchmark_ = 0; - QString pending_screenshot_; -}; - -#endif // MINIMALWINDOW_H diff --git a/src/ifcviewer-minimal/main.cpp b/src/ifcviewer-minimal/main.cpp deleted file mode 100644 index 46fa62e1ca..0000000000 --- a/src/ifcviewer-minimal/main.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************** - * * - * 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 -#include -#include - -#include "MinimalWindow.h" - -int main(int argc, char* argv[]) { - QApplication app(argc, argv); - app.setApplicationName("IfcViewerMinimal"); - app.setOrganizationName("IfcOpenShell"); - - QSurfaceFormat fmt; - fmt.setVersion(4, 5); - fmt.setProfile(QSurfaceFormat::CoreProfile); - fmt.setDepthBufferSize(24); - fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); - fmt.setSamples(4); - QSurfaceFormat::setDefaultFormat(fmt); - - QCommandLineParser parser; - parser.setApplicationDescription("IfcOpenShell minimal IFC viewer — benchmarking and debugging"); - parser.addHelpOption(); - parser.addPositionalArgument("files", "IFC file(s) to open", "[files...]"); - parser.addOption({{"c", "camera"}, - "Set camera: tx,ty,tz,dist,yaw,pitch", "params"}); - parser.addOption({{"b", "benchmark"}, - "Run N frames then print stats and exit", "frames"}); - parser.addOption({{"s", "screenshot"}, - "Render one frame after load, save to PATH as PNG, exit", "path"}); - parser.process(app); - - MinimalWindow window; - window.show(); - - auto args = parser.positionalArguments(); - if (!args.isEmpty()) { - window.addFiles(args); - } - - if (parser.isSet("camera")) { - window.setPendingCamera(parser.value("camera")); - } - if (parser.isSet("benchmark")) { - window.setPendingBenchmark(parser.value("benchmark").toInt()); - } - if (parser.isSet("screenshot")) { - window.setPendingScreenshot(parser.value("screenshot")); - } - - return app.exec(); -} diff --git a/src/ifcviewer/BvhAccel.cpp b/src/ifcviewer/BvhAccel.cpp deleted file mode 100644 index 4b115bfa4c..0000000000 --- a/src/ifcviewer/BvhAccel.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************** - * * - * 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 "BvhAccel.h" - -#include -#include -#include -#include - -namespace { - -struct Centroid { - float x, y, z; -}; - -Centroid computeCentroid(const BvhItem& it) { - return { - (it.aabb_min[0] + it.aabb_max[0]) * 0.5f, - (it.aabb_min[1] + it.aabb_max[1]) * 0.5f, - (it.aabb_min[2] + it.aabb_max[2]) * 0.5f - }; -} - -void computeAABB(const std::vector& items, - const uint32_t* indices, uint32_t count, - float out_min[3], float out_max[3]) { - out_min[0] = out_min[1] = out_min[2] = std::numeric_limits::max(); - out_max[0] = out_max[1] = out_max[2] = -std::numeric_limits::max(); - for (uint32_t i = 0; i < count; ++i) { - const auto& it = items[indices[i]]; - for (int a = 0; a < 3; ++a) { - if (it.aabb_min[a] < out_min[a]) out_min[a] = it.aabb_min[a]; - if (it.aabb_max[a] > out_max[a]) out_max[a] = it.aabb_max[a]; - } - } -} - -void buildRecursive(ModelBvh& mbvh, - const std::vector& items, - uint32_t start, uint32_t count) { - uint32_t node_idx = static_cast(mbvh.nodes.size()); - mbvh.nodes.emplace_back(); - BvhNode& node = mbvh.nodes[node_idx]; - - computeAABB(items, &mbvh.item_indices[start], count, - node.aabb_min, node.aabb_max); - - if (count <= BVH_MAX_LEAF_SIZE) { - node.right_or_first = start; - node.count = static_cast(count); - node.axis = 0; - return; - } - - float extent[3] = { - node.aabb_max[0] - node.aabb_min[0], - node.aabb_max[1] - node.aabb_min[1], - node.aabb_max[2] - node.aabb_min[2] - }; - int axis = 0; - if (extent[1] > extent[axis]) axis = 1; - if (extent[2] > extent[axis]) axis = 2; - - uint32_t mid = count / 2; - std::nth_element( - mbvh.item_indices.begin() + start, - mbvh.item_indices.begin() + start + mid, - mbvh.item_indices.begin() + start + count, - [&](uint32_t a, uint32_t b) { - Centroid ca = computeCentroid(items[a]); - Centroid cb = computeCentroid(items[b]); - return (&ca.x)[axis] < (&cb.x)[axis]; - }); - - node.count = 0; - node.axis = static_cast(axis); - - buildRecursive(mbvh, items, start, mid); - - uint32_t right_child_idx = static_cast(mbvh.nodes.size()); - buildRecursive(mbvh, items, start + mid, count - mid); - - mbvh.nodes[node_idx].right_or_first = right_child_idx; -} - -ModelBvh buildModelBvh(const std::vector& items, - const std::vector& model_item_indices, - uint32_t model_id) { - ModelBvh mbvh; - mbvh.model_id = model_id; - mbvh.item_indices = model_item_indices; - - uint32_t count = static_cast(model_item_indices.size()); - if (count == 0) return mbvh; - - mbvh.nodes.reserve(count * 2); - buildRecursive(mbvh, items, 0, count); - - assert(!mbvh.nodes.empty()); - return mbvh; -} - -} // anonymous namespace - -ModelBvh buildModelBvhOne(const std::vector& items, uint32_t model_id) { - std::vector idxs(items.size()); - for (uint32_t i = 0; i < items.size(); ++i) idxs[i] = i; - return buildModelBvh(items, idxs, model_id); -} - -std::shared_ptr buildBvhSet(const std::vector& items) { - auto bvh_set = std::make_shared(); - - std::unordered_map> model_items; - for (uint32_t i = 0; i < static_cast(items.size()); ++i) { - model_items[items[i].model_id].push_back(i); - } - - for (auto& [model_id, idxs] : model_items) { - if (idxs.size() < BVH_MIN_OBJECTS) continue; - - ModelBvh mbvh = buildModelBvh(items, idxs, model_id); - bvh_set->bvh_model_ids.insert(model_id); - bvh_set->models[model_id] = std::move(mbvh); - } - - return bvh_set; -} diff --git a/src/ifcviewer/BvhAccel.h b/src/ifcviewer/BvhAccel.h deleted file mode 100644 index 7281dff511..0000000000 --- a/src/ifcviewer/BvhAccel.h +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -#ifndef BVHACCEL_H -#define BVHACCEL_H - -#include -#include -#include -#include -#include - -// Generic BVH item — anything with a world AABB and a model_id. -// For the instanced renderer each item represents one InstanceCpu. -struct BvhItem { - float aabb_min[3]; - float aabb_max[3]; - uint32_t model_id; -}; - -static constexpr uint32_t BVH_MAX_LEAF_SIZE = 8; -static constexpr uint32_t BVH_MIN_OBJECTS = 32; - -struct BvhNode { - float aabb_min[3]; - float aabb_max[3]; - uint32_t right_or_first; // interior: right child index (left is always this_index+1); leaf: first item index - uint16_t count; // 0 = interior; >0 = leaf with this many items - uint16_t axis; // split axis (0/1/2) for interior; unused for leaf -}; -static_assert(sizeof(BvhNode) == 32, "BvhNode must be 32 bytes for cache alignment and sidecar format"); - -struct ModelBvh { - uint32_t model_id = 0; - std::vector nodes; - std::vector item_indices; // indices into the model's InstanceCpu array -}; - -struct BvhSet { - std::unordered_map models; - std::unordered_set bvh_model_ids; -}; - -// Build BVH trees for all models in the given item snapshot. -// Items are expected to already be grouped/filtered by caller if needed. -// item_indices in the result reference positions within the full `items` -// vector — callers providing a single model's items will see 0..N-1. -std::shared_ptr buildBvhSet(const std::vector& items); - -// Build a single-model BVH over `items`. model_id is stored on the result -// for identification; item_indices will be 0..items.size()-1. -ModelBvh buildModelBvhOne(const std::vector& items, uint32_t model_id); - -#endif // BVHACCEL_H diff --git a/src/ifcviewer/CMakeLists.txt b/src/ifcviewer/CMakeLists.txt index e3d847efe0..5d4f086edb 100644 --- a/src/ifcviewer/CMakeLists.txt +++ b/src/ifcviewer/CMakeLists.txt @@ -23,9 +23,7 @@ set(QT_VERSION 6 CACHE STRING "Qt version") # IfcViewerLib always needs OpenGL in addition to Core/Gui/Widgets. We don't # use the CACHE'd QT_COMPONENTS here because it may have been set by another # target (e.g. qtviewer) without the OpenGL component. -find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets OpenGL REQUIRED PATHS ${QT_DIR}) - -find_package(OpenGL REQUIRED) +find_package(Qt${QT_VERSION} COMPONENTS Core Gui REQUIRED PATHS ${QT_DIR}) file(GLOB IFCVIEWER_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) @@ -59,10 +57,10 @@ target_link_libraries(IfcViewer PUBLIC ${CGAL_LIBRARIES} Qt${QT_VERSION}::Core Qt${QT_VERSION}::Gui - Qt${QT_VERSION}::Widgets - Qt${QT_VERSION}::OpenGL - OpenGL::GL ${MESH_OPTIMIZER_LIB} + # SceneLoader drives WgpuViewportWindow; the wgpu lib also provides + # the include path for WgpuViewportWindow.h that SceneLoader.h pulls in. + IfcViewerWgpu ) if(UNIX AND NOT APPLE) diff --git a/src/ifcviewer/OverlayRenderer.cpp b/src/ifcviewer/OverlayRenderer.cpp deleted file mode 100644 index 5f33629c6b..0000000000 --- a/src/ifcviewer/OverlayRenderer.cpp +++ /dev/null @@ -1,725 +0,0 @@ -/******************************************************************************** - * * - * 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 -#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; -} - -QFont overlayTextFont(int point_size) { - QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); - font.setPointSize(point_size); - font.setStyleHint(QFont::TypeWriter); - return font; -} - -// ---- 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