diff --git a/src/bonsaiviewer/ElementRegistry.cpp b/src/bonsaiviewer/ElementRegistry.cpp index ed83bd1504..6305f9d743 100644 --- a/src/bonsaiviewer/ElementRegistry.cpp +++ b/src/bonsaiviewer/ElementRegistry.cpp @@ -89,7 +89,7 @@ std::optional ElementRegistry::findEntity(uint32_t object_id) con } void ElementRegistry::onSidecarElementsReady(uint32_t /*model_id*/, - std::vector elements, + std::vector elements, std::string string_table) { auto string_from_table = [&](uint32_t offset, uint32_t length) -> QString { if (length == 0 || offset + length > string_table.size()) return {}; @@ -101,7 +101,6 @@ void ElementRegistry::onSidecarElementsReady(uint32_t /*model_id*/, info.object_id = packed_element.object_id; info.model_id = packed_element.model_id; info.ifc_id = packed_element.ifc_id; - info.parent_id = packed_element.parent_id; info.guid = string_from_table(packed_element.guid_offset, packed_element.guid_length); info.name = string_from_table(packed_element.name_offset, packed_element.name_length); info.type = string_from_table(packed_element.type_offset, packed_element.type_length); @@ -115,7 +114,6 @@ void ElementRegistry::onStreamedElementsReady(uint32_t /*model_id*/, std::vector info.object_id = element.object_id; info.model_id = element.model_id; info.ifc_id = element.ifc_id; - info.parent_id = element.parent_id; info.guid = QString::fromStdString(element.guid); info.name = QString::fromStdString(element.name); info.type = QString::fromStdString(element.type); diff --git a/src/bonsaiviewer/ElementRegistry.h b/src/bonsaiviewer/ElementRegistry.h index 5f5cc7a76d..173f3a6aa4 100644 --- a/src/bonsaiviewer/ElementRegistry.h +++ b/src/bonsaiviewer/ElementRegistry.h @@ -30,7 +30,7 @@ #include class SceneLoader; -struct PackedElementInfo; +struct ElementTableRecord; struct ElementInfo; namespace bonsaiviewer { @@ -39,7 +39,6 @@ struct BasicElementInfo { uint32_t object_id = 0; uint32_t model_id = 0; int ifc_id = 0; - int parent_id = 0; QString guid; QString name; QString type; @@ -59,7 +58,7 @@ public: private: void onSidecarElementsReady(uint32_t model_id, - std::vector elements, + std::vector elements, std::string string_table); void onStreamedElementsReady(uint32_t model_id, std::vector elements); diff --git a/src/ifcviewer-web/sample.ifcview b/src/ifcviewer-web/sample.ifcview index e91c0a92c8..c6c3550cd8 100644 Binary files a/src/ifcviewer-web/sample.ifcview and b/src/ifcviewer-web/sample.ifcview differ diff --git a/src/ifcviewer/Federation.h b/src/ifcviewer/Federation.h index af124ac50e..e30866aac5 100644 --- a/src/ifcviewer/Federation.h +++ b/src/ifcviewer/Federation.h @@ -131,7 +131,7 @@ ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file); // Build a FederatedFalseOrigin guess so that a model lands near the // federation origin instead of out at its surveyor coordinates. Designed // to work without an open IFC file so it's usable from sidecar-only loads -// (the inputs are all derivable from the resident MeshInfo + InstanceCpu +// (the inputs are all derivable from the resident MeshInfo + InstanceInfo // data + ModelGeoref). // // Position: `first_geometry_point_m` is a point that actually lies on the diff --git a/src/ifcviewer/GeometryStreamer.cpp b/src/ifcviewer/GeometryStreamer.cpp index ff08aad348..ccc76ff210 100644 --- a/src/ifcviewer/GeometryStreamer.cpp +++ b/src/ifcviewer/GeometryStreamer.cpp @@ -144,7 +144,7 @@ std::vector GeometryStreamer::drainElements() { return result; } -// Build a mesh chunk (local coords, 28-byte interleaved vertices) from a +// Build a streamed mesh record (local coords, 28-byte interleaved vertices) from a // TriangulationElement. Per-vertex color is baked from material_ids so that // triangulations with per-face materials still render correctly. // Vertex rebasing: when `offset` is non-zero, every vertex position is @@ -153,13 +153,13 @@ std::vector GeometryStreamer::drainElements() { // compensates by post-multiplying each instance's PlacementTransformation // by T(+offset), which is mathematically the identity overall but moves // the magnitude off the float-precision-sensitive vertex column. -static MeshChunk buildMeshChunk(uint32_t model_id, +static StreamedMesh buildStreamedMesh(uint32_t model_id, uint32_t local_mesh_id, const IfcGeom::TriangulationElement* elem, const Eigen::Vector3d& offset) { - MeshChunk chunk; - chunk.model_id = model_id; - chunk.local_mesh_id = local_mesh_id; + StreamedMesh mesh; + mesh.model_id = model_id; + mesh.local_mesh_id = local_mesh_id; const auto& geom = elem->geometry(); const auto& verts = geom.verts(); @@ -168,7 +168,7 @@ static MeshChunk buildMeshChunk(uint32_t model_id, const auto& materials = geom.materials(); const auto& material_ids = geom.material_ids(); - if (verts.empty() || faces.empty()) return chunk; + if (verts.empty() || faces.empty()) return mesh; const size_t num_verts_src = verts.size() / 3; const size_t num_tris = faces.size() / 3; @@ -184,8 +184,8 @@ static MeshChunk buildMeshChunk(uint32_t model_id, std::unordered_map remap; remap.reserve(num_verts_src); - chunk.vertices.reserve(num_verts_src * INSTANCED_VERTEX_STRIDE_FLOATS); - chunk.indices.reserve(faces.size()); + mesh.vertices.reserve(num_verts_src * INSTANCED_VERTEX_STRIDE_FLOATS); + mesh.indices.reserve(faces.size()); // Track local AABB as we emit vertices. float local_aabb_min[3] = { std::numeric_limits::max(), @@ -201,16 +201,16 @@ static MeshChunk buildMeshChunk(uint32_t model_id, if (it != remap.end()) return it->second; const uint32_t new_idx = static_cast( - chunk.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS); + mesh.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS); // Subtract in double, narrow to float — preserves precision when // verts are far from origin and offset cancels the magnitude. float px = static_cast(verts[orig_idx * 3 + 0] - offset.x()); float py = static_cast(verts[orig_idx * 3 + 1] - offset.y()); float pz = static_cast(verts[orig_idx * 3 + 2] - offset.z()); - chunk.vertices.push_back(px); - chunk.vertices.push_back(py); - chunk.vertices.push_back(pz); + mesh.vertices.push_back(px); + mesh.vertices.push_back(py); + mesh.vertices.push_back(pz); if (px < local_aabb_min[0]) local_aabb_min[0] = px; if (px > local_aabb_max[0]) local_aabb_max[0] = px; if (py < local_aabb_min[1]) local_aabb_min[1] = py; @@ -219,13 +219,13 @@ static MeshChunk buildMeshChunk(uint32_t model_id, if (pz > local_aabb_max[2]) local_aabb_max[2] = pz; if (orig_idx * 3 + 2 < normals.size()) { - chunk.vertices.push_back(static_cast(normals[orig_idx * 3 + 0])); - chunk.vertices.push_back(static_cast(normals[orig_idx * 3 + 1])); - chunk.vertices.push_back(static_cast(normals[orig_idx * 3 + 2])); + mesh.vertices.push_back(static_cast(normals[orig_idx * 3 + 0])); + mesh.vertices.push_back(static_cast(normals[orig_idx * 3 + 1])); + mesh.vertices.push_back(static_cast(normals[orig_idx * 3 + 2])); } else { - chunk.vertices.push_back(0.0f); - chunk.vertices.push_back(1.0f); - chunk.vertices.push_back(0.0f); + mesh.vertices.push_back(0.0f); + mesh.vertices.push_back(1.0f); + mesh.vertices.push_back(0.0f); } MaterialInfo m; @@ -235,7 +235,7 @@ static MeshChunk buildMeshChunk(uint32_t model_id, uint32_t packed = packRGBA8(m); float packed_as_float; std::memcpy(&packed_as_float, &packed, sizeof(float)); - chunk.vertices.push_back(packed_as_float); + mesh.vertices.push_back(packed_as_float); remap.emplace(key, new_idx); return new_idx; @@ -243,19 +243,19 @@ static MeshChunk buildMeshChunk(uint32_t model_id, for (size_t t = 0; t < num_tris; ++t) { const int mat_id = have_per_tri_material ? material_ids[t] : -1; - chunk.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 0]), mat_id)); - chunk.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 1]), mat_id)); - chunk.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 2]), mat_id)); + mesh.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 0]), mat_id)); + mesh.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 1]), mat_id)); + mesh.indices.push_back(emit_vertex(static_cast(faces[t * 3 + 2]), mat_id)); } - if (chunk.vertices.empty()) { + if (mesh.vertices.empty()) { for (int a = 0; a < 3; ++a) local_aabb_min[a] = local_aabb_max[a] = 0.0f; } for (int a = 0; a < 3; ++a) { - chunk.local_aabb_min[a] = local_aabb_min[a]; - chunk.local_aabb_max[a] = local_aabb_max[a]; + mesh.local_aabb_min[a] = local_aabb_min[a]; + mesh.local_aabb_max[a] = local_aabb_max[a]; } - return chunk; + return mesh; } // Port of ifcopenshell.util.representation.get_prioritised_contexts: rank every @@ -562,7 +562,6 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { info.guid = tri_elem->guid(); info.name = tri_elem->name(); info.type = tri_elem->type(); - info.parent_id = tri_elem->parent_id(); { std::lock_guard lock(elements_mutex_); pending_elements_.push_back(std::move(info)); @@ -604,19 +603,19 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { } } - MeshChunk mesh_chunk = - buildMeshChunk(model_id_, local_mesh_id, tri_elem, offset); + StreamedMesh streamed_mesh = + buildStreamedMesh(model_id_, local_mesh_id, tri_elem, offset); MeshAabb mesh_aabb; for (int a = 0; a < 3; ++a) { - mesh_aabb.lmin[a] = mesh_chunk.local_aabb_min[a]; - mesh_aabb.lmax[a] = mesh_chunk.local_aabb_max[a]; + mesh_aabb.lmin[a] = streamed_mesh.local_aabb_min[a]; + mesh_aabb.lmax[a] = streamed_mesh.local_aabb_max[a]; mesh_aabb.offset[a] = offset[a]; } mesh_aabb.has_offset = (offset.squaredNorm() > 0.0); if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1); mesh_aabbs[local_mesh_id] = mesh_aabb; - if (!mesh_chunk.indices.empty()) { - emit meshReady(std::move(mesh_chunk)); + if (!streamed_mesh.indices.empty()) { + emit meshReady(std::move(streamed_mesh)); } } @@ -635,7 +634,7 @@ void GeometryStreamer::run(const std::string& path, int num_threads) { mat_d.block<3, 1>(0, 3) += mat_d.block<3, 3>(0, 0) * mesh_rebase_offset; } - InstanceChunk inst; + StreamedInstance inst; inst.model_id = model_id_; inst.local_mesh_id = local_mesh_id; inst.object_id = object_id; diff --git a/src/ifcviewer/GeometryStreamer.h b/src/ifcviewer/GeometryStreamer.h index ac31035c43..41fe3e1b53 100644 --- a/src/ifcviewer/GeometryStreamer.h +++ b/src/ifcviewer/GeometryStreamer.h @@ -41,7 +41,6 @@ struct ElementInfo { std::string guid; std::string name; std::string type; - int parent_id; }; class GeometryStreamer : public QObject { @@ -70,8 +69,8 @@ public: signals: void progressChanged(int percent); - void meshReady(MeshChunk chunk); - void instanceReady(InstanceChunk chunk); + void meshReady(StreamedMesh mesh); + void instanceReady(StreamedInstance instance_record); void finished(); void cancelled(); void errorOccurred(const QString& message); diff --git a/src/ifcviewer/InstanceCompose.cpp b/src/ifcviewer/InstanceCompose.cpp index bd6ce53af8..9fb4e5b7c8 100644 --- a/src/ifcviewer/InstanceCompose.cpp +++ b/src/ifcviewer/InstanceCompose.cpp @@ -84,7 +84,7 @@ bool findInstanceInModels( if (it == model_data.object_id_to_instance.end()) continue; const uint32_t instance_index = it->second; if (instance_index >= model_data.instances.size()) continue; - const InstanceCpu& instance = model_data.instances[instance_index]; + const InstanceInfo& instance = model_data.instances[instance_index]; out.model_id = model_id; out.mesh_id = instance.mesh_id; std::memcpy(out.placement_transformation, diff --git a/src/ifcviewer/InstanceCompose.h b/src/ifcviewer/InstanceCompose.h index 34d668ee98..4cfc667041 100644 --- a/src/ifcviewer/InstanceCompose.h +++ b/src/ifcviewer/InstanceCompose.h @@ -66,7 +66,7 @@ void composeInstance( // Result of a successful findInstance lookup. The placement_transformation // is double[16] column-major (pre-CoordinateOperation / FederatedFalseOrigin -// / ModelTransformation) — the same convention as InstanceCpu so the +// / ModelTransformation) — the same convention as InstanceInfo so the // measurement / picking tools can re-compose at need. struct InstanceLookup { uint32_t model_id = 0; diff --git a/src/ifcviewer/InstancedGeometry.h b/src/ifcviewer/InstancedGeometry.h index 20854f997b..bf9972816f 100644 --- a/src/ifcviewer/InstancedGeometry.h +++ b/src/ifcviewer/InstancedGeometry.h @@ -39,8 +39,8 @@ static constexpr int INSTANCED_VERTEX_STRIDE_BYTES = 12; // Streamer-side intermediate format: 7 floats per vertex (pos3 + normal3 + -// color-as-float). GeometryStreamer writes this into MeshChunk.vertices; -// ViewportWindow::uploadMeshChunk quantizes it down to STRIDE_BYTES on the +// color-as-float). GeometryStreamer writes this into StreamedMesh.vertices; +// ViewportWindow::uploadStreamedMesh quantizes it down to STRIDE_BYTES on the // way to the VBO. Not the GPU layout — purely a transfer convention. static constexpr int INSTANCED_VERTEX_STRIDE_FLOATS = 7; @@ -109,7 +109,7 @@ static_assert(sizeof(InstanceGpu) == 80, "InstanceGpu must be 80 bytes"); // 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 { +struct InstanceInfo { uint32_t mesh_id = 0; // index into meshes array uint32_t object_id = 0; uint32_t color_override_rgba8 = 0; @@ -120,12 +120,12 @@ struct InstanceCpu { float world_aabb_max[3]{}; }; -// Chunks emitted by the streamer to the viewport (main thread). +// Transfer records emitted by the streamer to the viewport (main thread). // Emitted the first time a representation id is seen. Carries the mesh // geometry in local coords. `local_mesh_id` is the streamer-assigned id // within this model. -struct MeshChunk { +struct StreamedMesh { uint32_t model_id = 0; uint32_t local_mesh_id = 0; std::vector vertices; // 7 floats * N_verts (pos3+norm3+color1_packed) @@ -135,9 +135,9 @@ struct MeshChunk { }; // Emitted for every placement (every triangulation element from the -// iterator). For the first instance of a mesh, the MeshChunk is emitted +// iterator). For the first instance of a mesh, the StreamedMesh is emitted // just before this. -struct InstanceChunk { +struct StreamedInstance { uint32_t model_id = 0; uint32_t local_mesh_id = 0; uint32_t object_id = 0; diff --git a/src/ifcviewer/ModelGpuData.h b/src/ifcviewer/ModelGpuData.h index 8971726151..3f7a44a5cb 100644 --- a/src/ifcviewer/ModelGpuData.h +++ b/src/ifcviewer/ModelGpuData.h @@ -34,7 +34,7 @@ #include "InstancedGeometry.h" #include "BufferPool.h" #include "ChunkPlanner.h" // WGPU_CHUNK_VERTEX_BYTES_LIMIT (shared with bake) -#include "SidecarCache.h" // PackedElementInfo (deferred property metadata) +#include "SidecarCache.h" // ElementTableRecord (element metadata) // Per-model wgpu state. Mirrors the GL backend's ModelGpuData but with // wgpu handles. Stage 2 only allocates and uploads the four core buffers; @@ -305,26 +305,27 @@ struct ModelGpuData { bool streaming_from_web = false; // Web analog of streaming_file_path: which registered JS byte-source // (Module.__ifcvSources[id] = a picked File or a remote URL) this model's - // chunk + deferred reads pull from. Lets several federated models stream + // chunk + element metadata reads pull from. Lets several federated models stream // from different files at once, mirroring the desktop per-model path. int web_source_id = 0; - // v15 deferred property metadata (web, on-demand). The IFC element tree - // (elements + string_table — names/GUIDs/hierarchy, for UI/picking, never + // v15 element metadata (web, on-demand). The IFC element metadata + // (elements + string_table — names/GUIDs, for UI/picking, never // rendering) lives in a separate file block fetched only when a consumer // asks, so first paint doesn't wait on it. Empty until - // loadDeferredMetadataWeb fetches [deferred_meta_offset, +bytes) and parses - // it; deferred_meta_loaded latches so it fetches at most once. - std::vector elements; + // loadElementMetadataWeb fetches [element_metadata_comp_offset, +bytes) and parses + // it; element_metadata_loaded latches so it fetches at most once. + std::vector elements; std::string string_table; - // v16: the deferred block is a single zstd frame at deferred_comp_offset of - // deferred_comp_size bytes, expanding to deferred_raw_size. - uint64_t deferred_comp_offset = 0; - uint64_t deferred_comp_size = 0; - uint64_t deferred_raw_size = 0; - bool deferred_meta_loaded = false; + // v16: the element metadata block is a single zstd frame at + // element_metadata_comp_offset of element_metadata_comp_size bytes, + // expanding to element_metadata_raw_size. + uint64_t element_metadata_comp_offset = 0; + uint64_t element_metadata_comp_size = 0; + uint64_t element_metadata_raw_size = 0; + bool element_metadata_loaded = false; // applyCachedModel rebases instance object_ids by this base to keep them - // globally unique across models; deferred elements carry the sidecar's + // globally unique across models; element metadata records carry the sidecar's // original (local) ids, so they're rebased by the same amount on load. uint32_t object_id_base = 0; @@ -378,10 +379,10 @@ struct ModelGpuData { // CPU side, kept for cull / picking / federation recompose. std::vector meshes; - std::vector instances; + std::vector instances; // Per-mesh "any vertex has alpha < 255?" flag, indexed by mesh_id. - // Populated at uploadMeshChunk / applyStreamedChunk as vertex bytes + // Populated at uploadStreamedMesh / applyStreamedChunk as vertex bytes // become CPU-resident. Used at cull time to classify each instance // into the opaque or transparent draw partition: an instance with // color_override_rgba8==0 (the "use baked vertex color" sentinel) diff --git a/src/ifcviewer/SceneLoader.cpp b/src/ifcviewer/SceneLoader.cpp index fa5beb15e3..6f370c60de 100644 --- a/src/ifcviewer/SceneLoader.cpp +++ b/src/ifcviewer/SceneLoader.cpp @@ -279,7 +279,7 @@ void SceneLoader::applySidecarData(uint32_t mid, StreamingSidecar metadata) { model.has_georef = true; } - std::vector elements = std::move(d.elements); + std::vector elements = std::move(d.elements); std::string stbl = std::move(d.string_table); viewport_->applyCachedModel(mid, std::move(metadata)); @@ -331,24 +331,24 @@ void SceneLoader::onStreamerProgressChanged(int percent) { emit progressChanged(percent); } -void SceneLoader::onStreamerMeshReady(MeshChunk chunk) { - viewport_->uploadMeshChunk(chunk); +void SceneLoader::onStreamerMeshReady(StreamedMesh mesh) { + viewport_->uploadStreamedMesh(mesh); if (loading_model_id_ != 0) { auto it = models_.find(loading_model_id_); if (it != models_.end() && it->second.sidecar_builder) { - it->second.sidecar_builder->onMeshReady(chunk); + it->second.sidecar_builder->onMeshReady(mesh); } } } -void SceneLoader::onStreamerInstanceReady(InstanceChunk chunk) { +void SceneLoader::onStreamerInstanceReady(StreamedInstance instance_record) { if (loading_model_id_ != 0) { auto it = models_.find(loading_model_id_); if (it != models_.end() && it->second.sidecar_builder) { - it->second.sidecar_builder->onInstanceReady(chunk); + it->second.sidecar_builder->onInstanceReady(instance_record); } } - viewport_->uploadInstanceChunk(chunk); + viewport_->uploadStreamedInstance(instance_record); } void SceneLoader::onElementPollTick() { diff --git a/src/ifcviewer/SceneLoader.h b/src/ifcviewer/SceneLoader.h index 1915207ba0..11bb929c4f 100644 --- a/src/ifcviewer/SceneLoader.h +++ b/src/ifcviewer/SceneLoader.h @@ -102,7 +102,7 @@ signals: // packed element set. Consumer is responsible for decoding + tree/ // property-map population. Moved arguments — avoid unnecessary copies. void sidecarElementsReady(uint32_t mid, - std::vector elements, + std::vector elements, std::string string_table); void loadedFromSidecar(uint32_t mid, qint64 elapsed_ms); @@ -128,8 +128,8 @@ signals: private slots: void onStreamerProgressChanged(int percent); - void onStreamerMeshReady(MeshChunk chunk); - void onStreamerInstanceReady(InstanceChunk chunk); + void onStreamerMeshReady(StreamedMesh mesh); + void onStreamerInstanceReady(StreamedInstance instance_record); void onStreamerFinished(); void onStreamerCancelled(); void onStreamerError(const QString& msg); diff --git a/src/ifcviewer/SidecarBuilder.cpp b/src/ifcviewer/SidecarBuilder.cpp index c9b19d32bf..8a5459a46d 100644 --- a/src/ifcviewer/SidecarBuilder.cpp +++ b/src/ifcviewer/SidecarBuilder.cpp @@ -37,14 +37,14 @@ SidecarBuilder::SidecarBuilder(QObject* parent) { } -void SidecarBuilder::onMeshReady(const MeshChunk& chunk) { - if (chunk.vertices.empty() || chunk.indices.empty()) return; +void SidecarBuilder::onMeshReady(const StreamedMesh& mesh) { + if (mesh.vertices.empty() || mesh.indices.empty()) return; // Streamer format: 7 floats/vertex (pos3 + normal3 + color-as-float). - const size_t n_verts = chunk.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS; + const size_t n_verts = mesh.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS; // Recompute a tight local AABB from the actual vertex positions, same - // way ViewportWindow::uploadMeshChunk does so the .ifcview byte layout + // way ViewportWindow::uploadStreamedMesh does so the .ifcview byte layout // matches the live-render path. float bmin[3] = { std::numeric_limits::infinity(), std::numeric_limits::infinity(), @@ -53,7 +53,7 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) { -std::numeric_limits::infinity(), -std::numeric_limits::infinity() }; for (size_t i = 0; i < n_verts; ++i) { - const float* vertex = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS; + const float* vertex = mesh.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS; for (int a = 0; a < 3; ++a) { if (vertex[a] < bmin[a]) bmin[a] = vertex[a]; if (vertex[a] > bmax[a]) bmax[a] = vertex[a]; @@ -68,7 +68,7 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) { const size_t vb_offset = sidecar_data_.vertices.size(); sidecar_data_.vertices.resize(vb_offset + n_verts * INSTANCED_VERTEX_STRIDE_BYTES); for (size_t i = 0; i < n_verts; ++i) { - quantizeVertex(chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS, + quantizeVertex(mesh.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS, bmin, extent_recip, sidecar_data_.vertices.data() + vb_offset + i * INSTANCED_VERTEX_STRIDE_BYTES); @@ -76,13 +76,13 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) { const size_t ib_offset = sidecar_data_.indices.size(); sidecar_data_.indices.insert(sidecar_data_.indices.end(), - chunk.indices.begin(), chunk.indices.end()); + mesh.indices.begin(), mesh.indices.end()); MeshInfo info; info.vbo_byte_offset = static_cast(vb_offset); info.vertex_count = static_cast(n_verts); info.ebo_byte_offset = static_cast(ib_offset * sizeof(uint32_t)); - info.index_count = static_cast(chunk.indices.size()); + info.index_count = static_cast(mesh.indices.size()); for (int a = 0; a < 3; ++a) { info.local_aabb_min[a] = bmin[a]; info.local_aabb_max[a] = bmax[a]; @@ -92,30 +92,30 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) { info.lod1_ebo_byte_offset = 0; info.lod1_index_count = 0; - if (sidecar_data_.meshes.size() <= chunk.local_mesh_id) { - sidecar_data_.meshes.resize(chunk.local_mesh_id + 1); + if (sidecar_data_.meshes.size() <= mesh.local_mesh_id) { + sidecar_data_.meshes.resize(mesh.local_mesh_id + 1); } - sidecar_data_.meshes[chunk.local_mesh_id] = info; + sidecar_data_.meshes[mesh.local_mesh_id] = info; } -void SidecarBuilder::onInstanceReady(const InstanceChunk& chunk) { - InstanceCpu instance; - instance.mesh_id = chunk.local_mesh_id; - instance.object_id = chunk.object_id; - instance.color_override_rgba8 = chunk.color_override_rgba8; - instance.model_id = chunk.model_id; +void SidecarBuilder::onInstanceReady(const StreamedInstance& instance_record) { + InstanceInfo instance; + instance.mesh_id = instance_record.local_mesh_id; + instance.object_id = instance_record.object_id; + instance.color_override_rgba8 = instance_record.color_override_rgba8; + instance.model_id = instance_record.model_id; - // The streamer's chunk.transform is the double-precision + // The streamer's instance 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(instance.placement_transformation, chunk.transform, + std::memcpy(instance.placement_transformation, instance_record.transform, sizeof(instance.placement_transformation)); for (int i = 0; i < 16; ++i) { - instance.transform[i] = static_cast(chunk.transform[i]); + instance.transform[i] = static_cast(instance_record.transform[i]); } - std::memcpy(instance.world_aabb_min, chunk.world_aabb_min, sizeof(instance.world_aabb_min)); - std::memcpy(instance.world_aabb_max, chunk.world_aabb_max, sizeof(instance.world_aabb_max)); + std::memcpy(instance.world_aabb_min, instance_record.world_aabb_min, sizeof(instance.world_aabb_min)); + std::memcpy(instance.world_aabb_max, instance_record.world_aabb_max, sizeof(instance.world_aabb_max)); sidecar_data_.instances.push_back(instance); } @@ -140,11 +140,10 @@ SidecarData SidecarBuilder::finalize(const ModelGeoref& georef, sidecar_data_.map_unit_to_meters = georef.units.map_unit_to_meters; for (const auto& info : elements) { - PackedElementInfo packed; + ElementTableRecord packed; packed.object_id = info.object_id; packed.model_id = info.model_id; packed.ifc_id = info.ifc_id; - packed.parent_id = info.parent_id; packed.guid_offset = static_cast(sidecar_data_.string_table.size()); packed.guid_length = static_cast(info.guid.size()); diff --git a/src/ifcviewer/SidecarBuilder.h b/src/ifcviewer/SidecarBuilder.h index bb12d20f2a..6a38527949 100644 --- a/src/ifcviewer/SidecarBuilder.h +++ b/src/ifcviewer/SidecarBuilder.h @@ -58,8 +58,8 @@ public: // Accumulator interface. Safe to call repeatedly from the same thread the // streamer signals are delivered to. - void onMeshReady(const MeshChunk& chunk); - void onInstanceReady(const InstanceChunk& chunk); + void onMeshReady(const StreamedMesh& mesh); + void onInstanceReady(const StreamedInstance& instance_record); // Finishes assembly using the georef + element batch the host collected // during streaming. Returns the assembled SidecarData by move; the @@ -76,4 +76,3 @@ private: }; #endif // SIDECARBUILDER_H - diff --git a/src/ifcviewer/SidecarCache.cpp b/src/ifcviewer/SidecarCache.cpp index 3ff1a98b24..582329b69f 100644 --- a/src/ifcviewer/SidecarCache.cpp +++ b/src/ifcviewer/SidecarCache.cpp @@ -30,7 +30,7 @@ // MeshInfo[num_meshes] // // uint32_t num_instances -// InstanceCpu[num_instances] (already sorted by mesh_id; v13 layout) +// InstanceInfo[num_instances] (already sorted by mesh_id; v13 layout) // // uint32_t has_coordinate_operation (v11+) // double[16] coordinate_operation_meters (v11+; column-major) @@ -38,7 +38,7 @@ // double map_unit_to_meters (v11+) // // uint32_t num_elements -// PackedElementInfo[num_elements] +// ElementTableRecord[num_elements] // uint32_t string_table_bytes // char[string_table_bytes] @@ -205,24 +205,24 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) { if (!wrU64(std::uint64_t(geom_end - geom_start))) { fclose(f); return false; } if (fseek(f, geom_end, SEEK_SET) != 0) { fclose(f); return false; } - // --- Critical metadata block (zstd): meshes, instances, georef, chunk TOC - std::vector critical_metadata; - appendVec(critical_metadata, data.meshes); - appendVec(critical_metadata, data.instances); - appendBytes(critical_metadata, &data.has_coordinate_operation, 4); - appendBytes(critical_metadata, data.coordinate_operation_meters, sizeof(double) * 16); - appendBytes(critical_metadata, &data.project_length_to_meters, sizeof(double)); - appendBytes(critical_metadata, &data.map_unit_to_meters, sizeof(double)); - appendVec(critical_metadata, chunks); - if (!wrBlock(critical_metadata)) { fclose(f); return false; } + // --- Geometry metadata block (zstd): meshes, instances, georef, chunk TOC + std::vector geometry_metadata; + appendVec(geometry_metadata, data.meshes); + appendVec(geometry_metadata, data.instances); + appendBytes(geometry_metadata, &data.has_coordinate_operation, 4); + appendBytes(geometry_metadata, data.coordinate_operation_meters, sizeof(double) * 16); + appendBytes(geometry_metadata, &data.project_length_to_meters, sizeof(double)); + appendBytes(geometry_metadata, &data.map_unit_to_meters, sizeof(double)); + appendVec(geometry_metadata, chunks); + if (!wrBlock(geometry_metadata)) { fclose(f); return false; } - // --- Deferred metadata block (zstd): element tree + string table --------- - std::vector deferred_metadata; - appendVec(deferred_metadata, data.elements); + // --- Element metadata block (zstd): elements + string table -------------- + std::vector element_metadata; + appendVec(element_metadata, data.elements); std::uint32_t stbl_len = static_cast(data.string_table.size()); - appendBytes(deferred_metadata, &stbl_len, 4); - appendBytes(deferred_metadata, data.string_table.data(), stbl_len); - if (!wrBlock(deferred_metadata)) { fclose(f); return false; } + appendBytes(element_metadata, &stbl_len, 4); + appendBytes(element_metadata, data.string_table.data(), stbl_len); + if (!wrBlock(element_metadata)) { fclose(f); return false; } fclose(f); return true; @@ -286,12 +286,12 @@ std::optional readSidecar(const std::string& ifc_path) { out.assign(std::size_t(raw), 0); return SidecarCompress::decompress(z.data(), z.size(), out.data(), out.size()); }; - std::vector critical_metadata, deferred_metadata; - if (!readBlock(critical_metadata) || !readBlock(deferred_metadata)) return fail(); + std::vector geometry_metadata, element_metadata; + if (!readBlock(geometry_metadata) || !readBlock(element_metadata)) return fail(); fclose(f); SidecarData data; - BufReader cr{ critical_metadata.data(), critical_metadata.size() }; + BufReader cr{ geometry_metadata.data(), geometry_metadata.size() }; if (!cr.takeVec(data.meshes)) return std::nullopt; if (!cr.takeVec(data.instances)) return std::nullopt; if (!cr.take(&data.has_coordinate_operation, 4)) return std::nullopt; @@ -300,7 +300,7 @@ std::optional readSidecar(const std::string& ifc_path) { if (!cr.take(&data.map_unit_to_meters, sizeof(double))) return std::nullopt; if (!cr.takeVec(data.chunks)) return std::nullopt; - BufReader dr{ deferred_metadata.data(), deferred_metadata.size() }; + BufReader dr{ element_metadata.data(), element_metadata.size() }; if (!dr.takeVec(data.elements)) return std::nullopt; std::uint32_t stbl_len = 0; if (!dr.take(&stbl_len, 4)) return std::nullopt; diff --git a/src/ifcviewer/SidecarCache.h b/src/ifcviewer/SidecarCache.h index e1b18e715a..6035bd1733 100644 --- a/src/ifcviewer/SidecarCache.h +++ b/src/ifcviewer/SidecarCache.h @@ -46,7 +46,7 @@ static constexpr uint32_t SIDECAR_MAGIC = 0x49465657; // "IFVW" // same cache serves either source format. Staleness is user-managed // (delete the sidecar to force a rebuild). // v9 = unused `reserved` field dropped from header (16 B -> 12 B). -// v10 = InstanceCpu gains placement_transformation[16] alongside transform[16] +// v10 = InstanceInfo gains placement_transformation[16] alongside transform[16] // — record grew from 104 B to 168 B. placement_transformation is the // raw streamer output; transform is the composed FederatedFalseOrigin · // ModelTransformation · CoordinateOperation · placement_transformation @@ -59,8 +59,8 @@ 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. -// v12 = InstanceCpu::placement_transformation is double[16], and -// InstanceChunk carries the streamer placement as double[16]. This keeps +// v12 = InstanceInfo::placement_transformation is double[16], and +// StreamedInstance 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. // v13 = Map unit scale in cached ModelGeoref is derived from @@ -73,23 +73,25 @@ static constexpr uint32_t SIDECAR_MAGIC = 0x49465657; // "IFVW" // Morton quantisation isn't bit-identical across toolchains (x86 baker vs // wasm loader), so it must be baked in. No back-compat: v13 sidecars are // rejected (regenerate them). -// v15 = The post-index metadata is split into a render-CRITICAL block (meshes, -// instances, georef, chunk TOC) followed by a DEFERRED block (elements + -// string_table — the IFC element tree, used for UI/picking, never for -// rendering), with the critical block's byte length written just after -// the index section. The web loader reads only the critical block before +// v15 = The post-index metadata is split into a geometry metadata block (meshes, +// instances, georef, chunk TOC) followed by an element metadata block (elements + +// string_table — IFC element metadata, used for UI/picking, never for +// rendering), with the geometry block's byte length written just after +// the index section. The web loader reads only the geometry block before // painting, so first geometry no longer waits on the property data; the -// deferred block is fetched lazily (or skipped where unused). Desktop +// element block is fetched lazily (or skipped where unused). Desktop // reads both. No back-compat: regenerate sidecars. // v16 = Geometry + metadata are zstd-COMPRESSED. Each chunk's vertex bytes and // index bytes are stored as two independent zstd frames (so per-chunk // Range streaming still works — you fetch + decompress just one chunk), -// and the critical + deferred metadata blocks are single zstd frames. +// and the geometry + element metadata blocks are single zstd frames. // The chunk TOC records each chunk's compressed blob offsets/sizes plus // the raw (decompressed) sizes. ~3-5x fewer bytes over the wire while // keeping HTTP Range intact (unlike server Content-Encoding). No // back-compat: regenerate sidecars. -static constexpr uint32_t SIDECAR_VERSION = 16; +// v17 = Removes unused element hierarchy metadata. No back-compat: regenerate +// sidecars. +static constexpr uint32_t SIDECAR_VERSION = 17; static constexpr uint32_t SIDECAR_ENDIAN = 0x01020304; // Chunk table-of-contents entry (v16). A chunk is a CONTIGUOUS range of meshes @@ -111,11 +113,10 @@ struct SidecarChunk { // Fixed-size element record. Strings are stored as (offset, length) pairs // into a separate string table. -struct PackedElementInfo { +struct ElementTableRecord { uint32_t object_id; uint32_t model_id; int32_t ifc_id; - int32_t parent_id; uint32_t guid_offset; uint32_t guid_length; uint32_t name_offset; @@ -134,7 +135,7 @@ struct SidecarData { // Mesh dictionary and per-instance data. std::vector meshes; // indexed by local_mesh_id - std::vector instances; // sorted by mesh_id + std::vector instances; // sorted by mesh_id // CoordinateOperation cache (v11+). Mirrors ModelGeoref so a sidecar // load can apply georef without re-parsing the IFC source. @@ -149,8 +150,8 @@ struct SidecarData { double map_unit_to_meters = 1.0; uint32_t has_coordinate_operation = 0; - // Element tree metadata. - std::vector elements; + // Element metadata. + std::vector elements; std::string string_table; // Streaming chunk TOC. Always written on disk (v14); geometry is laid out diff --git a/src/ifcviewer/SidecarLayout.cpp b/src/ifcviewer/SidecarLayout.cpp index a80abc6216..2229ceed0a 100644 --- a/src/ifcviewer/SidecarLayout.cpp +++ b/src/ifcviewer/SidecarLayout.cpp @@ -88,7 +88,7 @@ void reorderSidecarByMorton(SidecarData& sd) { std::vector new_vertices; new_vertices.reserve(sd.vertices.size()); std::vector new_indices; new_indices.reserve(sd.indices.size()); std::vector new_meshes(mesh_count); - std::vector new_instances; new_instances.reserve(sd.instances.size()); + std::vector new_instances; new_instances.reserve(sd.instances.size()); // Pass A: vertices + LOD0 indices + instances, mesh-by-mesh in the new // order, recording the new offsets on each MeshInfo. @@ -112,7 +112,7 @@ void reorderSidecarByMorton(SidecarData& sd) { new_mesh_info.first_instance = std::uint32_t(new_instances.size()); new_mesh_info.instance_count = std::uint32_t(insts_by_mesh[old].size()); for (std::uint32_t instance_index : insts_by_mesh[old]) { - InstanceCpu instance = sd.instances[instance_index]; + InstanceInfo instance = sd.instances[instance_index]; instance.mesh_id = new_mesh_index; new_instances.push_back(instance); } diff --git a/src/ifcviewer/SidecarLayout.h b/src/ifcviewer/SidecarLayout.h index 40d21e6297..be70e57960 100644 --- a/src/ifcviewer/SidecarLayout.h +++ b/src/ifcviewer/SidecarLayout.h @@ -42,7 +42,7 @@ // // Pure transform (no Qt / no wgpu): meshes, vertices, indices (LOD0 + LOD1), // and instances are all rebuilt in the new order with vbo/ebo/lod1 offsets, -// MeshInfo.first_instance, and InstanceCpu.mesh_id remapped consistently. +// MeshInfo.first_instance, and InstanceInfo.mesh_id remapped consistently. // Index values are mesh-local, so they move unchanged. Element/georef/string // data is mesh-independent and untouched. No-op for < 2 meshes. // diff --git a/src/ifcviewer/StreamingLoader.cpp b/src/ifcviewer/StreamingLoader.cpp index dea53f79c3..b93b20db63 100644 --- a/src/ifcviewer/StreamingLoader.cpp +++ b/src/ifcviewer/StreamingLoader.cpp @@ -25,9 +25,9 @@ // uint32 num_indices // uint32[num_indices] index data <-- streaming skips // uint32 num_meshes + MeshInfo[] <-- streaming reads -// uint32 num_instances + InstanceCpu[] <-- streaming reads +// uint32 num_instances + InstanceInfo[] <-- streaming reads // uint32 has_coord_op + double[16] + 2× double <-- streaming reads -// uint32 num_elements + PackedElementInfo[] <-- streaming reads +// uint32 num_elements + ElementTableRecord[] <-- streaming reads // uint32 string_table_bytes + char[] <-- streaming reads // // Streaming reader returns offsets to the two skipped sections so chunks @@ -100,8 +100,8 @@ bool parseSidecarHead(const uint8_t* data, size_t n, uint64_t& out_geom_bytes) { return true; } -bool parseSidecarCritical(const uint8_t* data, size_t n, SidecarData& out) { - // v15 render-critical block: meshes, instances, georef, chunk TOC. +bool parseSidecarGeometryMetadata(const uint8_t* data, size_t n, SidecarData& out) { + // v15 geometry metadata block: meshes, instances, georef, chunk TOC. BufCursor c{data, n}; if (!c.takeVec(out.meshes)) return false; if (!c.takeVec(out.instances)) return false; @@ -113,8 +113,8 @@ bool parseSidecarCritical(const uint8_t* data, size_t n, SidecarData& out) { return true; } -bool parseSidecarDeferred(const uint8_t* data, size_t n, SidecarData& out) { - // v15 deferred block: element tree + string table (UI/picking, not rendered). +bool parseSidecarElementMetadata(const uint8_t* data, size_t n, SidecarData& out) { + // v15+ element metadata block: elements + string table (UI/picking, not rendered). BufCursor c{data, n}; if (!c.takeVec(out.elements)) return false; uint32_t stbl_len = 0; @@ -166,16 +166,18 @@ std::optional readSidecarMetadataOnly(const std::string& ifc_p return SidecarCompress::decompress(z.data(), z.size(), raw.data(), raw.size()); }; - std::vector crit, def; - if (!readBlock(crit)) return fail(); - if (!readBlock(def, &out.deferred_comp_offset, &out.deferred_comp_size, - &out.deferred_raw_size)) return fail(); + std::vector geometry_metadata, element_metadata; + if (!readBlock(geometry_metadata)) return fail(); + if (!readBlock(element_metadata, &out.element_metadata_comp_offset, &out.element_metadata_comp_size, + &out.element_metadata_raw_size)) return fail(); std::fclose(f); - // Desktop reads both blocks up front; the web path reads only critical - // before painting and fetches the deferred block on demand. - if (!parseSidecarCritical(crit.data(), crit.size(), out.meta)) return std::nullopt; - if (!parseSidecarDeferred(def.data(), def.size(), out.meta)) return std::nullopt; + // Desktop reads both blocks up front; the web path reads only geometry + // metadata before painting and fetches the element metadata block on demand. + if (!parseSidecarGeometryMetadata(geometry_metadata.data(), geometry_metadata.size(), out.meta)) + return std::nullopt; + if (!parseSidecarElementMetadata(element_metadata.data(), element_metadata.size(), out.meta)) + return std::nullopt; return out; } diff --git a/src/ifcviewer/StreamingLoader.h b/src/ifcviewer/StreamingLoader.h index 7f1d5c2810..4f6c89c330 100644 --- a/src/ifcviewer/StreamingLoader.h +++ b/src/ifcviewer/StreamingLoader.h @@ -46,7 +46,7 @@ struct StreamingSidecar { // Everything except vertices + indices — same shape as SidecarData but // with empty vertices / indices vectors. The renderer uses meshes / - // instances / georef / chunks immediately (elements/strings deferred). + // instances / georef / chunks immediately (elements/strings are element metadata). SidecarData meta; // v16: the compressed geometry section starts here. Each chunk's two zstd @@ -54,12 +54,13 @@ struct StreamingSidecar { // a per-chunk load fetches [that, +*_comp_size) and decompresses to *_raw_size. uint64_t geometry_section_offset = 0; - // v16 deferred (property) block locator: a single zstd frame at - // deferred_comp_offset of deferred_comp_size bytes → deferred_raw_size. The - // web loader fetches it on demand (elements/strings); desktop reads it up front. - uint64_t deferred_comp_offset = 0; - uint64_t deferred_comp_size = 0; - uint64_t deferred_raw_size = 0; + // v16 element metadata block locator: a single zstd frame at + // element_metadata_comp_offset of element_metadata_comp_size bytes → + // element_metadata_raw_size. The web loader fetches it on demand + // (elements/strings); desktop reads it up front. + uint64_t element_metadata_comp_offset = 0; + uint64_t element_metadata_comp_size = 0; + uint64_t element_metadata_raw_size = 0; // Resolved on-disk path so subsequent chunk reads can re-open / seek. std::string file_path; @@ -104,18 +105,18 @@ inline constexpr std::size_t SIDECAR_HEAD_BYTES = 20; bool parseSidecarHead(const std::uint8_t* data, std::size_t n, std::uint64_t& out_geom_bytes); -// Parse the v15 render-CRITICAL metadata block (mesh dict, instance dict, +// Parse the v15 geometry metadata block (mesh dict, instance dict, // georef, chunk TOC) — everything needed to set up + draw the scene. `data` -// points at the first critical byte; `n` is critical_meta_bytes. Returns false +// points at the first geometry metadata byte; `n` is geometry_metadata_bytes. Returns false // on any bounds overrun, leaving out_meta partially filled. -bool parseSidecarCritical(const std::uint8_t* data, std::size_t n, - SidecarData& out_meta); +bool parseSidecarGeometryMetadata(const std::uint8_t* data, std::size_t n, + SidecarData& out_meta); -// Parse the v15 DEFERRED metadata block (element table + string table — the +// Parse the v15 element metadata block (element table + string table — the // IFC property tree, used for UI/picking, never for rendering). Fetched on -// demand. `data` points at the first deferred byte; `n` is its length. -bool parseSidecarDeferred(const std::uint8_t* data, std::size_t n, - SidecarData& out_meta); +// demand. `data` points at the first element metadata byte; `n` is its length. +bool parseSidecarElementMetadata(const std::uint8_t* data, std::size_t n, + SidecarData& out_meta); // A coalesced read plan: a single contiguous source read whose bytes are // scattered into the destination at the recorded offsets. Merging adjacent diff --git a/src/ifcviewer/VertexQuantization.h b/src/ifcviewer/VertexQuantization.h index 248dca2b35..495f0aedc1 100644 --- a/src/ifcviewer/VertexQuantization.h +++ b/src/ifcviewer/VertexQuantization.h @@ -20,7 +20,7 @@ // Inline helpers that turn streamer-format vertices (7 floats per vertex: // pos3 + normal3 + color-as-float) into the 12 B quantized VBO layout used // by both the viewport's GPU buffers and the .ifcview sidecar. Shared -// between ViewportWindow::uploadMeshChunk and SidecarBuilder so the +// between ViewportWindow::uploadStreamedMesh and SidecarBuilder so the // on-disk format stays identical to what the viewport renders. #ifndef VERTEXQUANTIZATION_H diff --git a/src/ifcviewer/ViewportCore.cpp b/src/ifcviewer/ViewportCore.cpp index 320d82589d..8563a71576 100644 --- a/src/ifcviewer/ViewportCore.cpp +++ b/src/ifcviewer/ViewportCore.cpp @@ -2858,8 +2858,8 @@ void ViewportCore::cullModelCpuUpload(ModelGpuData& m) { } // =========================================================================== -// Sidecar / direct load (#84-q): applyCachedModel + uploadMeshChunk + -// uploadInstanceChunk + finalizeModel +// Sidecar / direct load (#84-q): applyCachedModel + uploadStreamedMesh + +// uploadStreamedInstance + finalizeModel // =========================================================================== #include "ChunkPlanner.h" @@ -3220,14 +3220,14 @@ void ViewportCore::applyCachedModel(std::uint32_t model_id, host_->requestFrame(); } -void ViewportCore::uploadMeshChunk(const MeshChunk& chunk) { - if (chunk.vertices.empty() || chunk.indices.empty()) return; - SidecarData& s = getOrCreateDirectStaging(pending_direct_loads_, chunk.model_id); +void ViewportCore::uploadStreamedMesh(const StreamedMesh& mesh) { + if (mesh.vertices.empty() || mesh.indices.empty()) return; + SidecarData& s = getOrCreateDirectStaging(pending_direct_loads_, mesh.model_id); // Streamer format: 7 floats / vertex (pos3 + normal3 + color-as-float). // Same quantisation as SidecarBuilder::onMeshReady so direct-load and // sidecar-load produce byte-identical GPU buffers. - const std::size_t n_verts = chunk.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS; + const std::size_t n_verts = mesh.vertices.size() / INSTANCED_VERTEX_STRIDE_FLOATS; float bmin[3] = { std::numeric_limits::infinity(), std::numeric_limits::infinity(), @@ -3236,7 +3236,7 @@ void ViewportCore::uploadMeshChunk(const MeshChunk& chunk) { -std::numeric_limits::infinity(), -std::numeric_limits::infinity() }; for (std::size_t i = 0; i < n_verts; ++i) { - const float* vertex = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS; + const float* vertex = mesh.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS; for (int a = 0; a < 3; ++a) { if (vertex[a] < bmin[a]) bmin[a] = vertex[a]; if (vertex[a] > bmax[a]) bmax[a] = vertex[a]; @@ -3251,7 +3251,7 @@ void ViewportCore::uploadMeshChunk(const MeshChunk& chunk) { const std::size_t vb_offset = s.vertices.size(); s.vertices.resize(vb_offset + n_verts * INSTANCED_VERTEX_STRIDE_BYTES); for (std::size_t i = 0; i < n_verts; ++i) { - quantizeVertex(chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS, + quantizeVertex(mesh.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS, bmin, extent_recip, s.vertices.data() + vb_offset + i * INSTANCED_VERTEX_STRIDE_BYTES); @@ -3259,13 +3259,13 @@ void ViewportCore::uploadMeshChunk(const MeshChunk& chunk) { const std::size_t ib_offset = s.indices.size(); s.indices.insert(s.indices.end(), - chunk.indices.begin(), chunk.indices.end()); + mesh.indices.begin(), mesh.indices.end()); MeshInfo info{}; info.vbo_byte_offset = std::uint32_t(vb_offset); info.vertex_count = std::uint32_t(n_verts); info.ebo_byte_offset = std::uint32_t(ib_offset * sizeof(std::uint32_t)); - info.index_count = std::uint32_t(chunk.indices.size()); + info.index_count = std::uint32_t(mesh.indices.size()); for (int a = 0; a < 3; ++a) { info.local_aabb_min[a] = bmin[a]; info.local_aabb_max[a] = bmax[a]; @@ -3275,27 +3275,27 @@ void ViewportCore::uploadMeshChunk(const MeshChunk& chunk) { info.lod1_ebo_byte_offset = 0; info.lod1_index_count = 0; - if (s.meshes.size() <= chunk.local_mesh_id) { - s.meshes.resize(chunk.local_mesh_id + 1); + if (s.meshes.size() <= mesh.local_mesh_id) { + s.meshes.resize(mesh.local_mesh_id + 1); } - s.meshes[chunk.local_mesh_id] = info; + s.meshes[mesh.local_mesh_id] = info; } -void ViewportCore::uploadInstanceChunk(const InstanceChunk& chunk) { - SidecarData& s = getOrCreateDirectStaging(pending_direct_loads_, chunk.model_id); +void ViewportCore::uploadStreamedInstance(const StreamedInstance& instance_record) { + SidecarData& s = getOrCreateDirectStaging(pending_direct_loads_, instance_record.model_id); InstanceInfo instance{}; - instance.mesh_id = chunk.local_mesh_id; - instance.object_id = chunk.object_id; - instance.color_override_rgba8 = chunk.color_override_rgba8; - instance.model_id = chunk.model_id; - std::memcpy(instance.placement_transformation, chunk.transform, + instance.mesh_id = instance_record.local_mesh_id; + instance.object_id = instance_record.object_id; + instance.color_override_rgba8 = instance_record.color_override_rgba8; + instance.model_id = instance_record.model_id; + std::memcpy(instance.placement_transformation, instance_record.transform, sizeof(instance.placement_transformation)); for (int i = 0; i < 16; ++i) { - instance.transform[i] = float(chunk.transform[i]); + instance.transform[i] = float(instance_record.transform[i]); } - std::memcpy(instance.world_aabb_min, chunk.world_aabb_min, sizeof(instance.world_aabb_min)); - std::memcpy(instance.world_aabb_max, chunk.world_aabb_max, sizeof(instance.world_aabb_max)); + std::memcpy(instance.world_aabb_min, instance_record.world_aabb_min, sizeof(instance.world_aabb_min)); + std::memcpy(instance.world_aabb_max, instance_record.world_aabb_max, sizeof(instance.world_aabb_max)); s.instances.push_back(instance); } diff --git a/src/ifcviewer/ViewportCore.h b/src/ifcviewer/ViewportCore.h index 1772c63ac1..8f23a04f9e 100644 --- a/src/ifcviewer/ViewportCore.h +++ b/src/ifcviewer/ViewportCore.h @@ -472,8 +472,8 @@ public: // the viewer one mesh + one instance at a time, then calls // finalizeModel once everything's staged. The staging map lives on // ViewportCore so both halves can share it. - void uploadMeshChunk(const MeshChunk& chunk); - void uploadInstanceChunk(const InstanceChunk& chunk); + void uploadStreamedMesh(const StreamedMesh& mesh); + void uploadStreamedInstance(const StreamedInstance& instance_record); void finalizeModel(std::uint32_t model_id); // ---- Cross-chunk + screenshot capture (#84-v) ------------------------- @@ -1162,8 +1162,8 @@ private: // completes. std::string pending_screenshot_path_; - // Bonsai direct-load staging map. uploadMeshChunk + - // uploadInstanceChunk append into entries keyed by model_id; the + // Bonsai direct-load staging map. uploadStreamedMesh + + // uploadStreamedInstance append into entries keyed by model_id; the // finalizeModel call moves the entry out, hands it to // applyCachedModel, and uploads the chunk slices synchronously. std::unordered_map> diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 7b0c45234a..253dca9e50 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -550,17 +550,19 @@ void ViewportWindow::applyCachedModel(uint32_t model_id, StreamingSidecar metada } // ----------------------------------------------------------------------------- -// Direct-IFC ingestion (mirrors GL ViewportWindow::uploadMeshChunk / -// uploadInstanceChunk / finalizeModel). Streamer pushes chunks; we stage +// Direct-IFC ingestion (mirrors GL ViewportWindow::uploadStreamedMesh / +// uploadStreamedInstance / finalizeModel). Streamer pushes transfer records; we stage // them into a SidecarData-shaped buffer and commit at finalize via the // same chunk planner the sidecar load uses. // ----------------------------------------------------------------------------- // getOrCreateDirectStaging moved to ViewportCore (anon namespace) (#84-q). -void ViewportWindow::uploadMeshChunk(const MeshChunk& chunk) { core_.uploadMeshChunk(chunk); } +void ViewportWindow::uploadStreamedMesh(const StreamedMesh& mesh) { core_.uploadStreamedMesh(mesh); } -void ViewportWindow::uploadInstanceChunk(const InstanceChunk& chunk) { core_.uploadInstanceChunk(chunk); } +void ViewportWindow::uploadStreamedInstance(const StreamedInstance& instance_record) { + core_.uploadStreamedInstance(instance_record); +} void ViewportWindow::finalizeModel(uint32_t model_id) { core_.finalizeModel(model_id); } diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index a8d9b36a78..18b918a04a 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -122,8 +122,8 @@ public: struct StreamingSidecar metadata); // Direct-IFC ingestion (mirrors GL ViewportWindow). The host (typically - // a GeometryStreamer running on a worker) calls uploadMeshChunk + - // uploadInstanceChunk once per representation / placement as the IFC + // a GeometryStreamer running on a worker) calls uploadStreamedMesh + + // uploadStreamedInstance once per representation / placement as the IFC // triangulates; finalizeModel commits when the iterator finishes. // Staged in CPU memory; finalizeModel runs the chunk planner over the // staged data, allocates pool slices, and uploads — same render path @@ -131,8 +131,8 @@ public: // every chunk lands `is_resident=true` immediately. The streamer's // model_id is passed through unchanged; the viewport's globally-unique // object_id rebasing happens at finalize time. - void uploadMeshChunk(const struct MeshChunk& chunk); - void uploadInstanceChunk(const struct InstanceChunk& chunk); + void uploadStreamedMesh(const struct StreamedMesh& mesh); + void uploadStreamedInstance(const struct StreamedInstance& instance_record); void finalizeModel(uint32_t model_id); void removeModel(uint32_t model_id); diff --git a/src/ifcviewer/tests/test_instance_compose.cpp b/src/ifcviewer/tests/test_instance_compose.cpp index 324434454e..f184af1dee 100644 --- a/src/ifcviewer/tests/test_instance_compose.cpp +++ b/src/ifcviewer/tests/test_instance_compose.cpp @@ -283,7 +283,7 @@ namespace { ModelGpuData make_model_with_one_instance(uint32_t object_id, uint32_t mesh_id, double placement_tx) { ModelGpuData m; - InstanceCpu inst{}; + InstanceInfo inst{}; inst.mesh_id = mesh_id; inst.object_id = object_id; // Column-major identity with a tx for verification. diff --git a/src/ifcviewer/tests/test_instanced_geometry.cpp b/src/ifcviewer/tests/test_instanced_geometry.cpp index 267fe2de20..9993709a6a 100644 --- a/src/ifcviewer/tests/test_instanced_geometry.cpp +++ b/src/ifcviewer/tests/test_instanced_geometry.cpp @@ -21,7 +21,7 @@ // vertex quantization used to fill it. // // quantizeVertex / octEncodeNormal (VertexQuantization.h) are the shared -// production helpers: ViewportWindow::uploadMeshChunk and SidecarBuilder both +// production helpers: ViewportWindow::uploadStreamedMesh and SidecarBuilder both // route through them so the rendered VBO and the on-disk .ifcview record are // byte-identical. The tests exercise that real implementation directly: // - runtime size/alignment assertions (defense in depth for the static_asserts) @@ -245,14 +245,14 @@ TEST_CASE("quantizeVertex passes the packed color through unchanged", "[instgeom REQUIRE(std::memcmp(dst + INSTANCED_VERTEX_COLOR_OFFSET, rgba, 4) == 0); } -TEST_CASE("MeshChunk and InstanceChunk default-init to zeroed metadata", "[instgeom]") { - MeshChunk mc; +TEST_CASE("StreamedMesh and StreamedInstance default-init to zeroed metadata", "[instgeom]") { + StreamedMesh mc; REQUIRE(mc.model_id == 0); REQUIRE(mc.local_mesh_id == 0); REQUIRE(mc.vertices.empty()); REQUIRE(mc.indices.empty()); - InstanceChunk ic; + StreamedInstance ic; REQUIRE(ic.model_id == 0); REQUIRE(ic.local_mesh_id == 0); REQUIRE(ic.object_id == 0); diff --git a/src/ifcviewer/tests/test_sidecar_cache.cpp b/src/ifcviewer/tests/test_sidecar_cache.cpp index 09d2c1b2e7..feb8d380dc 100644 --- a/src/ifcviewer/tests/test_sidecar_cache.cpp +++ b/src/ifcviewer/tests/test_sidecar_cache.cpp @@ -83,7 +83,7 @@ SidecarData buildFixture() { sd.instances.resize(5); for (size_t i = 0; i < sd.instances.size(); ++i) { - InstanceCpu& inst = sd.instances[i]; + InstanceInfo& inst = sd.instances[i]; inst.mesh_id = (i < 3) ? 0u : 1u; inst.object_id = uint32_t(100 + i); inst.color_override_rgba8 = uint32_t(0xAA000000u | (i * 0x010203u)); @@ -109,11 +109,10 @@ SidecarData buildFixture() { sd.string_table = std::string("\0Wall\0Slab\0", 11); // includes embedded NULs sd.elements.resize(3); for (size_t i = 0; i < sd.elements.size(); ++i) { - PackedElementInfo& e = sd.elements[i]; + ElementTableRecord& e = sd.elements[i]; e.object_id = uint32_t(100 + i); e.model_id = 1; e.ifc_id = int32_t(1000 + i); - e.parent_id = (i == 0) ? -1 : int32_t(100); e.guid_offset = 0; e.guid_length = 0; e.name_offset = 1; e.name_length = 4; // "Wall" e.type_offset = 6; e.type_length = 4; // "Slab" @@ -136,10 +135,10 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) { if (std::memcmp(&a.meshes[i], &b.meshes[i], sizeof(MeshInfo)) != 0) return false; } for (size_t i = 0; i < a.instances.size(); ++i) { - if (std::memcmp(&a.instances[i], &b.instances[i], sizeof(InstanceCpu)) != 0) return false; + if (std::memcmp(&a.instances[i], &b.instances[i], sizeof(InstanceInfo)) != 0) return false; } for (size_t i = 0; i < a.elements.size(); ++i) { - if (std::memcmp(&a.elements[i], &b.elements[i], sizeof(PackedElementInfo)) != 0) return false; + if (std::memcmp(&a.elements[i], &b.elements[i], sizeof(ElementTableRecord)) != 0) return false; } // v11 georef block. @@ -155,10 +154,12 @@ bool sidecarDataEqual(const SidecarData& a, const SidecarData& b) { } // namespace -TEST_CASE("MeshInfo and InstanceCpu have stable layouts (sidecar wire format)", "[sidecar]") { +TEST_CASE("MeshInfo and InstanceInfo have stable layouts (sidecar wire format)", "[sidecar]") { REQUIRE(sizeof(MeshInfo) == 56); + REQUIRE(sizeof(InstanceInfo) == 232); REQUIRE(sizeof(InstanceGpu) == 80); - REQUIRE(SIDECAR_VERSION == 16); + REQUIRE(sizeof(ElementTableRecord) == 36); + REQUIRE(SIDECAR_VERSION == 17); REQUIRE(sizeof(SidecarChunk) == 56); REQUIRE(SIDECAR_MAGIC == 0x49465657u); } diff --git a/src/ifcviewer/tests/test_sidecar_layout.cpp b/src/ifcviewer/tests/test_sidecar_layout.cpp index dde36947b0..c6729baa5c 100644 --- a/src/ifcviewer/tests/test_sidecar_layout.cpp +++ b/src/ifcviewer/tests/test_sidecar_layout.cpp @@ -81,7 +81,7 @@ SidecarData buildFixture() { for (uint32_t k = 0; k < 3; ++k) { // outer loop = interleave for (int i = 0; i < N; ++i) { if (k >= ninst(i)) continue; - InstanceCpu ic; + InstanceInfo ic; ic.mesh_id = uint32_t(i); // authoritative ic.object_id = obj++; ic.model_id = 1; @@ -114,7 +114,7 @@ struct InstSig { } }; -InstSig sigFor(const SidecarData& sd, const InstanceCpu& inst) { +InstSig sigFor(const SidecarData& sd, const InstanceInfo& inst) { const MeshInfo& m = sd.meshes.at(inst.mesh_id); InstSig s{}; s.verts.assign(sd.vertices.begin() + m.vbo_byte_offset, diff --git a/src/ifcviewer/tests/test_streaming_loader.cpp b/src/ifcviewer/tests/test_streaming_loader.cpp index 4f031aa4ad..60b344682b 100644 --- a/src/ifcviewer/tests/test_streaming_loader.cpp +++ b/src/ifcviewer/tests/test_streaming_loader.cpp @@ -84,7 +84,6 @@ SidecarData buildFixture() { sd.elements[i].object_id = uint32_t(100 + i); sd.elements[i].model_id = 1; sd.elements[i].ifc_id = int32_t(1000 + i); - sd.elements[i].parent_id = (i == 0) ? -1 : int32_t(100); } // v16 stores geometry per-chunk (compressed); a fixture with geometry needs // a chunk TOC covering its meshes (one chunk per mesh here). @@ -113,8 +112,8 @@ TEST_CASE("readSidecarMetadataOnly returns metadata, skips bulk geometry", // Chunk TOC carries compressed blob locators for each chunk. REQUIRE(meta->meta.chunks.size() == sd.chunks.size()); REQUIRE(meta->meta.chunks[0].v_comp_size > 0); - // The deferred (property) block locator is recorded for on-demand fetch. - REQUIRE(meta->deferred_comp_size > 0); + // The element metadata block locator is recorded for on-demand fetch. + REQUIRE(meta->element_metadata_comp_size > 0); // Metadata round-trips. REQUIRE(meta->meta.meshes.size() == sd.meshes.size()); @@ -197,37 +196,37 @@ TEST_CASE("parseSidecarHead validates magic / version, reads geom length", "[str REQUIRE_FALSE(parseSidecarHead(bad, sizeof(bad), got)); } -TEST_CASE("v16 deferred block: fetch via locator, decompress, parse", "[streaming]") { - fs::path dir = makeScratchDir("v16def"); +TEST_CASE("v16 element metadata block: fetch via locator, decompress, parse", "[streaming]") { + fs::path dir = makeScratchDir("v16element"); fs::path ifc = dir / "model.ifc"; SidecarData sd = buildFixture(); REQUIRE(writeSidecar(ifc.string(), sd)); auto meta = readSidecarMetadataOnly(ifc.string()); REQUIRE(meta.has_value()); - REQUIRE(meta->meta.meshes.size() == sd.meshes.size()); // critical + REQUIRE(meta->meta.meshes.size() == sd.meshes.size()); // geometry metadata REQUIRE(meta->meta.chunks.size() == sd.chunks.size()); - REQUIRE(meta->meta.elements.size() == sd.elements.size()); // desktop reads deferred too - REQUIRE(meta->deferred_comp_size > 0); + REQUIRE(meta->meta.elements.size() == sd.elements.size()); // desktop reads element metadata too + REQUIRE(meta->element_metadata_comp_size > 0); - // The on-demand path (web) fetches the compressed deferred frame via the + // The on-demand path (web) fetches the compressed element metadata frame via the // recorded locator and decompresses it — verify that round-trips. FILE* f = std::fopen((dir / "model.ifcview").string().c_str(), "rb"); REQUIRE(f); - std::vector cz(size_t(meta->deferred_comp_size)); - std::fseek(f, long(meta->deferred_comp_offset), SEEK_SET); + std::vector cz(size_t(meta->element_metadata_comp_size)); + std::fseek(f, long(meta->element_metadata_comp_offset), SEEK_SET); REQUIRE(std::fread(cz.data(), 1, cz.size(), f) == cz.size()); std::fclose(f); - std::vector raw(size_t(meta->deferred_raw_size)); + std::vector raw(size_t(meta->element_metadata_raw_size)); REQUIRE(SidecarCompress::decompress(cz.data(), cz.size(), raw.data(), raw.size())); SidecarData d; - REQUIRE(parseSidecarDeferred(raw.data(), raw.size(), d)); + REQUIRE(parseSidecarElementMetadata(raw.data(), raw.size(), d)); REQUIRE(d.elements.size() == sd.elements.size()); REQUIRE(d.string_table == sd.string_table); SidecarData chopped; - REQUIRE_FALSE(parseSidecarDeferred(raw.data(), raw.size() - 1, chopped)); + REQUIRE_FALSE(parseSidecarElementMetadata(raw.data(), raw.size() - 1, chopped)); } TEST_CASE("planSidecarReadRanges coalesces adjacent ranges, keeps far ones split",