/******************************************************************************** * * * 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 "SidecarLayout.h" #include "ChunkPlanner.h" #include "InstancedGeometry.h" #include #include void reorderSidecarByMorton(SidecarData& sd) { const std::size_t mesh_count = sd.meshes.size(); if (mesh_count < 2) return; // Per-mesh centroid + instance count, exactly as the loader computes them // before chunk planning (average of instance world-AABB centres). std::vector mesh_centroid_x(mesh_count, 0.0f), mesh_centroid_y(mesh_count, 0.0f), mesh_centroid_z(mesh_count, 0.0f); std::vector mesh_instance_count(mesh_count, 0); for (const auto& inst : sd.instances) { if (inst.mesh_id >= mesh_count) continue; mesh_centroid_x[inst.mesh_id] += 0.5f * (inst.world_aabb_min[0] + inst.world_aabb_max[0]); mesh_centroid_y[inst.mesh_id] += 0.5f * (inst.world_aabb_min[1] + inst.world_aabb_max[1]); mesh_centroid_z[inst.mesh_id] += 0.5f * (inst.world_aabb_min[2] + inst.world_aabb_max[2]); ++mesh_instance_count[inst.mesh_id]; } for (std::size_t i = 0; i < mesh_count; ++i) { if (mesh_instance_count[i] > 0) { const float inv = 1.0f / float(mesh_instance_count[i]); mesh_centroid_x[i] *= inv; mesh_centroid_y[i] *= inv; mesh_centroid_z[i] *= inv; } } // order[new_id] = old mesh id, in the loader's Morton order. const std::vector order = ChunkPlanner::sortMeshIdsByMorton( mesh_count, mesh_centroid_x, mesh_centroid_y, mesh_centroid_z, mesh_instance_count); // Greedy-pack the sorted order into chunks (the same plan the loader used // to derive). Each chunk is a CONSECUTIVE run of `order`, so once we lay // meshes out in `order` the chunk is a contiguous mesh range — recorded in // the TOC as {first_mesh, mesh_count}. std::vector mesh_vertex_count(mesh_count, 0); for (std::size_t i = 0; i < mesh_count; ++i) mesh_vertex_count[i] = sd.meshes[i].vertex_count; const std::vector> packed = ChunkPlanner::greedyPackChunks( order, mesh_vertex_count, INSTANCED_VERTEX_STRIDE_BYTES, WGPU_CHUNK_VERTEX_BYTES_LIMIT); sd.chunks.clear(); sd.chunks.reserve(packed.size()); { std::uint32_t first = 0; for (const auto& chunk : packed) { sd.chunks.push_back({first, std::uint32_t(chunk.size())}); first += std::uint32_t(chunk.size()); } } // Bucket instances by their (authoritative) mesh_id. We must NOT rely on // MeshInfo.first_instance: the baker leaves it 0 for every mesh and stores // instances ungrouped, so first_instance describes nothing. Grouping here // by mesh_id both reorders instances correctly AND fixes first_instance. std::vector> insts_by_mesh(mesh_count); for (std::uint32_t instance_index = 0; instance_index < sd.instances.size(); ++instance_index) { const std::uint32_t mesh_id = sd.instances[instance_index].mesh_id; if (mesh_id < mesh_count) insts_by_mesh[mesh_id].push_back(instance_index); } 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()); // Pass A: vertices + LOD0 indices + instances, mesh-by-mesh in the new // order, recording the new offsets on each MeshInfo. for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) { const std::uint32_t old = order[new_mesh_index]; const MeshInfo& old_mesh_info = sd.meshes[old]; MeshInfo new_mesh_info = old_mesh_info; // carries AABB; offsets/instance fields overwritten below new_mesh_info.vbo_byte_offset = std::uint32_t(new_vertices.size()); const std::size_t vbytes = std::size_t(old_mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES; new_vertices.insert(new_vertices.end(), sd.vertices.begin() + old_mesh_info.vbo_byte_offset, sd.vertices.begin() + old_mesh_info.vbo_byte_offset + vbytes); new_mesh_info.ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t)); const std::size_t i0 = old_mesh_info.ebo_byte_offset / sizeof(std::uint32_t); new_indices.insert(new_indices.end(), sd.indices.begin() + i0, sd.indices.begin() + i0 + old_mesh_info.index_count); 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]) { InstanceInfo instance = sd.instances[instance_index]; instance.mesh_id = new_mesh_index; new_instances.push_back(instance); } new_meshes[new_mesh_index] = new_mesh_info; } // Pass B: LOD1 indices appended after all LOD0 (same global layout as the // baker), in the new order, so a chunk's LOD1 slice is contiguous too. for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) { const MeshInfo& old_mesh_info = sd.meshes[order[new_mesh_index]]; MeshInfo& new_mesh_info = new_meshes[new_mesh_index]; if (old_mesh_info.lod1_index_count == 0) { new_mesh_info.lod1_ebo_byte_offset = 0; continue; } new_mesh_info.lod1_ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t)); const std::size_t l0 = old_mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t); new_indices.insert(new_indices.end(), sd.indices.begin() + l0, sd.indices.begin() + l0 + old_mesh_info.lod1_index_count); } sd.vertices = std::move(new_vertices); sd.indices = std::move(new_indices); sd.meshes = std::move(new_meshes); sd.instances = std::move(new_instances); }