mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Preserve precise viewer placements
Keep placement transformations in double precision through streaming, sidecar caching, and viewport recomposition so large coordinates can be cancelled before the final GPU float upload. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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<float>(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<float>(inst.transform[i]);
|
||||
}
|
||||
worldAabbFromLocal(ma.lmin, ma.lmax, mat_f,
|
||||
inst.world_aabb_min, inst.world_aabb_max);
|
||||
|
||||
emit instanceReady(std::move(inst));
|
||||
|
||||
@@ -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]{};
|
||||
};
|
||||
|
||||
@@ -283,10 +283,9 @@ void SceneLoader::applySidecarData(uint32_t mid, SidecarData data) {
|
||||
}
|
||||
|
||||
if (!data.instances.empty() && !model.has_first_placement) {
|
||||
using Mat4fCol = Eigen::Matrix<float, 4, 4, Eigen::ColMajor>;
|
||||
using Mat4dCol = Eigen::Matrix<double, 4, 4, Eigen::ColMajor>;
|
||||
model.first_placement =
|
||||
Eigen::Map<const Mat4fCol>(data.instances[0].placement_transformation)
|
||||
.cast<double>();
|
||||
Eigen::Map<const Mat4dCol>(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<float, 4, 4, Eigen::ColMajor>;
|
||||
using Mat4dCol = Eigen::Matrix<double, 4, 4, Eigen::ColMajor>;
|
||||
it->second.first_placement =
|
||||
Eigen::Map<const Mat4fCol>(chunk.transform).cast<double>();
|
||||
Eigen::Map<const Mat4dCol>(chunk.transform);
|
||||
it->second.has_first_placement = true;
|
||||
}
|
||||
if (it->second.sidecar_builder) {
|
||||
|
||||
@@ -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<float>(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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<double, 4, 4, Eigen::ColMajor>;
|
||||
using Mat4fCol = Eigen::Matrix<float, 4, 4, Eigen::ColMajor>;
|
||||
const Eigen::Matrix4d P =
|
||||
Eigen::Map<const Mat4fCol>(inst.placement_transformation).cast<double>();
|
||||
Eigen::Map<const Mat4dCol>(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<float, 4, 4, Eigen::ColMajor>;
|
||||
using Mat4d = Eigen::Matrix<double, 4, 4, Eigen::ColMajor>;
|
||||
const Eigen::Matrix4d Pd =
|
||||
Eigen::Map<const Mat4f>(inst.placement_transformation).cast<double>();
|
||||
Eigen::Map<const Mat4d>(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<float, 4, 4, Eigen::ColMajor>;
|
||||
using Mat4dCol = Eigen::Matrix<double, 4, 4, Eigen::ColMajor>;
|
||||
const Eigen::Matrix4d placement =
|
||||
Eigen::Map<const Mat4fCol>(inst.placement_transformation).cast<double>();
|
||||
Eigen::Map<const Mat4dCol>(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>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user