#include "vde/brep/incremental_mesh.h" #include "vde/brep/modeling.h" #include #include namespace vde::brep { // ═══════════════════════════════════════════════════════════ // Internal // ═══════════════════════════════════════════════════════════ namespace { /// Estimate memory used by a FaceMesh (bytes) size_t estimate_face_mesh_memory(const IncrementalMesher::FaceMesh& fm) { size_t bytes = 0; bytes += fm.vertices.size() * sizeof(core::Point3D); bytes += fm.triangles.size() * sizeof(std::array); return bytes; } } // namespace // ═══════════════════════════════════════════════════════════ // IncrementalMesher implementation // ═══════════════════════════════════════════════════════════ void IncrementalMesher::invalidate_face(int face_id) { dirty_set_.insert(face_id); // Clear cached mesh data for this face (will be rebuilt) auto it = meshes_.find(face_id); if (it != meshes_.end()) { it->second.vertices.clear(); it->second.triangles.clear(); } } int IncrementalMesher::rebuild_dirty(const BrepModel& body, double deflection) { int rebuilt = 0; // Copy dirty set because we'll iterate and modify auto dirty_copy = dirty_set_; for (int face_id : dirty_copy) { // Tessellate just this face auto fm = tessellate_face(body, face_id, deflection); meshes_[face_id] = std::move(fm); dirty_set_.erase(face_id); rebuilt++; } return rebuilt; } int IncrementalMesher::rebuild_all(const BrepModel& body, double deflection) { size_t n_faces = body.num_faces(); // Mark all faces as dirty for (size_t i = 0; i < n_faces; ++i) { invalidate_face(static_cast(i)); } return rebuild_dirty(body, deflection); } const IncrementalMesher::FaceMesh* IncrementalMesher::get_mesh(int face_id) const { auto it = meshes_.find(face_id); if (it != meshes_.end() && dirty_set_.count(face_id) == 0) { const_cast(this)->cache_hits_++; return &it->second; } const_cast(this)->cache_misses_++; return nullptr; } mesh::HalfedgeMesh IncrementalMesher::merged_mesh() const { std::vector clean_meshes; for (const auto& [face_id, fm] : meshes_) { if (dirty_set_.count(face_id) == 0 && !fm.vertices.empty()) { clean_meshes.push_back(&fm); } } return merge_face_meshes(clean_meshes); } void IncrementalMesher::clear_all() { meshes_.clear(); dirty_set_.clear(); cache_hits_ = 0; cache_misses_ = 0; } double IncrementalMesher::hit_rate() const { int total = cache_hits_ + cache_misses_; return total > 0 ? static_cast(cache_hits_) / total : 0.0; } size_t IncrementalMesher::memory_estimate() const { size_t total = 0; for (const auto& [face_id, fm] : meshes_) { total += estimate_face_mesh_memory(fm); } return total; } IncrementalMesher::FaceMesh IncrementalMesher::tessellate_face( const BrepModel& body, int face_id, double deflection) const { FaceMesh result; result.tessellation_deflection = deflection; if (face_id < 0 || face_id >= static_cast(body.num_faces())) { return result; } // Get full mesh tessellation first (this is the heavy operation) auto full_mesh = body.to_mesh(deflection); // For now, extract all triangles into a single face mesh, // since face-level mesh extraction requires face ID tracking in to_mesh(). // In a full implementation, body.to_mesh() would tag each triangle with // its source face ID, allowing per-face extraction. for (size_t fi = 0; fi < full_mesh.num_faces(); ++fi) { auto verts = full_mesh.face_vertices(static_cast(fi)); if (verts.size() >= 3) { // Triangulate polygon into triangle fans for (size_t j = 1; j + 1 < verts.size(); ++j) { // Check if vertices already in result int i0 = -1, i1 = -1, i2 = -1; for (size_t k = 0; k < result.vertices.size(); ++k) { if (full_mesh.vertex(verts[0]) == result.vertices[k]) i0 = static_cast(k); if (full_mesh.vertex(verts[j]) == result.vertices[k]) i1 = static_cast(k); if (full_mesh.vertex(verts[j+1]) == result.vertices[k]) i2 = static_cast(k); } if (i0 < 0) { i0 = static_cast(result.vertices.size()); result.vertices.push_back(full_mesh.vertex(verts[0])); } if (i1 < 0) { i1 = static_cast(result.vertices.size()); result.vertices.push_back(full_mesh.vertex(verts[j])); } if (i2 < 0) { i2 = static_cast(result.vertices.size()); result.vertices.push_back(full_mesh.vertex(verts[j+1])); } result.triangles.push_back({i0, i1, i2}); } } } return result; } mesh::HalfedgeMesh IncrementalMesher::merge_face_meshes( const std::vector& meshes) const { mesh::HalfedgeMesh result; // Collect all vertices and triangles std::vector all_verts; std::vector> all_tris; for (const auto* fm : meshes) { if (!fm) continue; int base_idx = static_cast(all_verts.size()); for (const auto& v : fm->vertices) { all_verts.push_back(v); } for (const auto& tri : fm->triangles) { all_tris.push_back({{ tri[0] + base_idx, tri[1] + base_idx, tri[2] + base_idx }}); } } // Build halfedge mesh from collected data if (!all_verts.empty() && !all_tris.empty()) { result.build_from_triangles(all_verts, all_tris); } return result; } } // namespace vde::brep