diff --git a/src/ifcviewer-full/Measurement.cpp b/src/ifcviewer-full/Measurement.cpp index 77e62f798e..104689f497 100644 --- a/src/ifcviewer-full/Measurement.cpp +++ b/src/ifcviewer-full/Measurement.cpp @@ -55,7 +55,7 @@ double meshLocalVolume(const ViewportWindow::MeshTriangles& tris) { return std::abs(sum) / 6.0; } -double det3(const float M[16]) { +double det3(const double M[16]) { // Upper-left 3x3 of a column-major 4x4: M[col * 4 + row]. const double m00 = M[0], m10 = M[1], m20 = M[2]; const double m01 = M[4], m11 = M[5], m21 = M[6]; diff --git a/src/ifcviewer/GeometryStreamer.cpp b/src/ifcviewer/GeometryStreamer.cpp index 786a923a8d..20216d2187 100644 --- a/src/ifcviewer/GeometryStreamer.cpp +++ b/src/ifcviewer/GeometryStreamer.cpp @@ -619,8 +619,9 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { // Vertex rebasing cont.: post-multiply the per-instance // PlacementTransformation by T(+offset) so world position is - // preserved. Matrix arithmetic is in double; narrow to float - // at the end. + // preserved. Keep the emitted placement in double so later + // CoordinateOperation / false-origin composition can cancel + // large translations before the final GPU float upload. Eigen::Matrix4d mat_d = tri_elem->transformation().data()->ccomponents(); if (mesh_aabbs[local_mesh_id].has_offset) { @@ -637,11 +638,15 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { inst.object_id = object_id; inst.color_override_rgba8 = 0; for (int i = 0; i < 16; ++i) { - inst.transform[i] = static_cast(mat_d.data()[i]); + inst.transform[i] = mat_d.data()[i]; } const MeshAabb& ma = mesh_aabbs[local_mesh_id]; - worldAabbFromLocal(ma.lmin, ma.lmax, inst.transform, + float mat_f[16]; + for (int i = 0; i < 16; ++i) { + mat_f[i] = static_cast(inst.transform[i]); + } + worldAabbFromLocal(ma.lmin, ma.lmax, mat_f, inst.world_aabb_min, inst.world_aabb_max); emit instanceReady(std::move(inst)); diff --git a/src/ifcviewer/InstancedGeometry.h b/src/ifcviewer/InstancedGeometry.h index 9b2eb02bba..20854f997b 100644 --- a/src/ifcviewer/InstancedGeometry.h +++ b/src/ifcviewer/InstancedGeometry.h @@ -101,17 +101,20 @@ static_assert(sizeof(InstanceGpu) == 80, "InstanceGpu must be 80 bytes"); // // `placement_transformation` is the raw streamer output (the iterator's // transform with vertex-rebasing offset folded in; pre-CoordinateOperation -// / FederatedFalseOrigin / ModelTransformation). `transform` is the -// composed FederatedFalseOrigin · ModelTransformation · CoordinateOperation -// · placement_transformation result — what gets uploaded to the SSBO and -// used to compute world_aabb_*. When ViewportWindow's stage matrices are -// all identity (default), the two are equal. +// / FederatedFalseOrigin / ModelTransformation). Keep it in double precision: +// large IFC placements must not be rounded before the federation false origin +// has a chance to cancel them. `transform` is the composed +// FederatedFalseOrigin · ModelTransformation · CoordinateOperation +// · placement_transformation result — narrowed to float only after composition, +// uploaded to the SSBO, and used to compute world_aabb_*. When ViewportWindow's +// stage matrices are all identity (default), transform is the float rendering +// copy of placement_transformation. struct InstanceCpu { uint32_t mesh_id = 0; // index into meshes array uint32_t object_id = 0; uint32_t color_override_rgba8 = 0; uint32_t model_id = 0; - float placement_transformation[16]{}; + double placement_transformation[16]{}; float transform[16]{}; float world_aabb_min[3]{}; float world_aabb_max[3]{}; @@ -139,7 +142,7 @@ struct InstanceChunk { uint32_t local_mesh_id = 0; uint32_t object_id = 0; uint32_t color_override_rgba8 = 0; - float transform[16]{}; + double transform[16]{}; float world_aabb_min[3]{}; float world_aabb_max[3]{}; }; diff --git a/src/ifcviewer/SceneLoader.cpp b/src/ifcviewer/SceneLoader.cpp index 2185438400..23379a949a 100644 --- a/src/ifcviewer/SceneLoader.cpp +++ b/src/ifcviewer/SceneLoader.cpp @@ -283,10 +283,9 @@ void SceneLoader::applySidecarData(uint32_t mid, SidecarData data) { } if (!data.instances.empty() && !model.has_first_placement) { - using Mat4fCol = Eigen::Matrix; + using Mat4dCol = Eigen::Matrix; model.first_placement = - Eigen::Map(data.instances[0].placement_transformation) - .cast(); + Eigen::Map(data.instances[0].placement_transformation); model.has_first_placement = true; } @@ -356,9 +355,9 @@ void SceneLoader::onStreamerInstanceReady(InstanceChunk chunk) { auto it = models_.find(loading_model_id_); if (it != models_.end()) { if (!it->second.has_first_placement) { - using Mat4fCol = Eigen::Matrix; + using Mat4dCol = Eigen::Matrix; it->second.first_placement = - Eigen::Map(chunk.transform).cast(); + Eigen::Map(chunk.transform); it->second.has_first_placement = true; } if (it->second.sidecar_builder) { diff --git a/src/ifcviewer/SidecarBuilder.cpp b/src/ifcviewer/SidecarBuilder.cpp index 82687fa917..ebafab97bb 100644 --- a/src/ifcviewer/SidecarBuilder.cpp +++ b/src/ifcviewer/SidecarBuilder.cpp @@ -105,16 +105,15 @@ void SidecarBuilder::onInstanceReady(const InstanceChunk& chunk) { inst.color_override_rgba8 = chunk.color_override_rgba8; inst.model_id = chunk.model_id; - // The streamer's chunk.transform is the placement_transformation. With - // identity stage matrices (no FederatedFalseOrigin / ModelTransformation - // / CoordinateOperation applied yet), transform == placement_transformation - // and chunk.world_aabb_* is already the world AABB. ViewportWindow's - // applyCachedModel will recompose against the consumer's stage matrices - // at load time, so the cached transform/world_aabb is just a sensible - // identity-stage baseline. + // The streamer's chunk.transform is the double-precision + // placement_transformation. The cached float transform/world_aabb is only + // an identity-stage baseline; applyCachedModel recomposes from placement + // against the consumer's stage matrices at load time. std::memcpy(inst.placement_transformation, chunk.transform, sizeof(inst.placement_transformation)); - std::memcpy(inst.transform, chunk.transform, sizeof(inst.transform)); + for (int i = 0; i < 16; ++i) { + inst.transform[i] = static_cast(chunk.transform[i]); + } std::memcpy(inst.world_aabb_min, chunk.world_aabb_min, sizeof(inst.world_aabb_min)); std::memcpy(inst.world_aabb_max, chunk.world_aabb_max, sizeof(inst.world_aabb_max)); diff --git a/src/ifcviewer/SidecarCache.cpp b/src/ifcviewer/SidecarCache.cpp index 86951f7395..0b5a346e6a 100644 --- a/src/ifcviewer/SidecarCache.cpp +++ b/src/ifcviewer/SidecarCache.cpp @@ -17,7 +17,7 @@ * * ********************************************************************************/ -// v11 layout (all multi-byte fields native-endian; endianness marker in header). +// v12 layout (all multi-byte fields native-endian; endianness marker in header). // // SidecarHeader (12 bytes) // @@ -30,7 +30,7 @@ // MeshInfo[num_meshes] // // uint32_t num_instances -// InstanceCpu[num_instances] (already sorted by mesh_id; v10 layout) +// InstanceCpu[num_instances] (already sorted by mesh_id; v12 layout) // // uint32_t has_coordinate_operation (v11+) // double[16] coordinate_operation_meters (v11+; column-major) diff --git a/src/ifcviewer/SidecarCache.h b/src/ifcviewer/SidecarCache.h index ac33e200de..78b5ac2e0a 100644 --- a/src/ifcviewer/SidecarCache.h +++ b/src/ifcviewer/SidecarCache.h @@ -59,7 +59,11 @@ static constexpr uint32_t SIDECAR_MAGIC = 0x49465657; // "IFVW" // georef without re-parsing the IFC source. Edits to the IFC's // IfcMapConversion do NOT invalidate the sidecar — delete the // .ifcview manually if you change the source's georef parameters. -static constexpr uint32_t SIDECAR_VERSION = 11; +// v12 = InstanceCpu::placement_transformation is double[16], and +// InstanceChunk carries the streamer placement as double[16]. This keeps +// large IFC placements exact until CoordinateOperation / FederatedFalseOrigin +// composition has reduced them to viewport-local float-sized values. +static constexpr uint32_t SIDECAR_VERSION = 12; static constexpr uint32_t SIDECAR_ENDIAN = 0x01020304; // Fixed-size element record. Strings are stored as (offset, length) pairs diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index d3448006f3..4055d9b10c 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -1106,9 +1106,8 @@ void ViewportWindow::uploadInstanceChunk(const InstanceChunk& chunk) { sizeof(inst.placement_transformation)); // Compose against the model's current stage matrices to fill in // inst.transform + inst.world_aabb_*. When all stages are identity - // (the default until a setter is called), this reduces to - // transform == placement_transformation and the world AABB matches - // the streamer's pre-computed chunk.world_aabb_* exactly. + // (the default until a setter is called), this reduces to a float render + // copy of placement_transformation. composeInstanceFromPlacement(inst, m); m.instances.push_back(inst); m.instance_reflected.push_back(transformIsReflected(inst.transform) ? 1 : 0); @@ -3811,10 +3810,12 @@ void ViewportWindow::handleWheel(QWheelEvent* e) { void ViewportWindow::composeInstanceFromPlacement(InstanceCpu& inst, const ModelGpuData& m) const { - // Read placement_transformation as float-column-major and lift to double. + // Read placement_transformation in double so large IFC placements are + // cancelled by the stage matrices before the final GPU float upload. + using Mat4dCol = Eigen::Matrix; using Mat4fCol = Eigen::Matrix; const Eigen::Matrix4d P = - Eigen::Map(inst.placement_transformation).cast(); + Eigen::Map(inst.placement_transformation); // FederatedFalseOrigin · ModelTransformation · CoordinateOperation · P. const Eigen::Matrix4d composed = @@ -3960,9 +3961,9 @@ void ViewportWindow::printSelectedObjectCoords() { qInfo(" vertex: (no vertex data)"); } - using Mat4f = Eigen::Matrix; + using Mat4d = Eigen::Matrix; const Eigen::Matrix4d Pd = - Eigen::Map(inst.placement_transformation).cast(); + Eigen::Map(inst.placement_transformation); // global = CoordinateOperation · placement_transformation. // (FederatedFalseOrigin and ModelTransformation are user-side // tweaks; "global" here means the IFC's own georeferenced frame.) @@ -4099,9 +4100,9 @@ bool ViewportWindow::meshLocalToGlobal(uint32_t object_id, auto model_it = models_gpu_.find(inst.model_id); if (model_it == models_gpu_.end()) return false; - using Mat4fCol = Eigen::Matrix; + using Mat4dCol = Eigen::Matrix; const Eigen::Matrix4d placement = - Eigen::Map(inst.placement_transformation).cast(); + Eigen::Map(inst.placement_transformation); const Eigen::Vector4d local(mesh_local[0], mesh_local[1], mesh_local[2], 1.0); const Eigen::Vector3d global = (model_it->second.coordinate_operation_meters * placement * local).head<3>(); diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index c05ee1c493..5cb00b7447 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -203,7 +203,7 @@ public: struct InstanceLookup { uint32_t model_id = 0; uint32_t mesh_id = 0; - float placement_transformation[16]{}; + double placement_transformation[16]{}; }; bool findInstance(uint32_t object_id, InstanceLookup& out) const; diff --git a/src/ifcviewer/tests/test_sidecar_cache.cpp b/src/ifcviewer/tests/test_sidecar_cache.cpp index f592a37772..b4a96ff9a0 100644 --- a/src/ifcviewer/tests/test_sidecar_cache.cpp +++ b/src/ifcviewer/tests/test_sidecar_cache.cpp @@ -89,7 +89,7 @@ SidecarData buildFixture() { inst.color_override_rgba8 = uint32_t(0xAA000000u | (i * 0x010203u)); inst.model_id = 1; for (int k = 0; k < 16; ++k) { - inst.placement_transformation[k] = float(i) * 0.25f + float(k); + inst.placement_transformation[k] = double(i) * 0.25 + double(k); inst.transform[k] = float(i) * 0.5f + float(k); } inst.world_aabb_min[0] = float(i); @@ -155,7 +155,7 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) { TEST_CASE("MeshInfo and InstanceCpu have stable layouts (sidecar wire format)", "[sidecar]") { REQUIRE(sizeof(MeshInfo) == 56); REQUIRE(sizeof(InstanceGpu) == 80); - REQUIRE(SIDECAR_VERSION == 11); + REQUIRE(SIDECAR_VERSION == 12); REQUIRE(SIDECAR_MAGIC == 0x49465657u); }