avila_optimizer/
lib.rs

1//! # avila-optimizer
2//!
3//! Mesh optimization for BIM/CAD models - 100% Rust
4//!
5//! ## Features
6//! - Mesh merging by material
7//! - LOD (Level of Detail) generation
8//! - Spatial indexing (Octree)
9//! - Vertex deduplication
10//! - Triangle strip optimization
11
12use avila_vec3d::*;
13use avila_mesh::*;
14use std::collections::HashMap;
15
16pub type Result<T> = std::result::Result<T, OptimizerError>;
17
18#[derive(Debug, thiserror::Error)]
19pub enum OptimizerError {
20    #[error("Optimization error: {0}")]
21    OptimizationError(String),
22
23    #[error("Mesh error: {0}")]
24    MeshError(#[from] MeshError),
25
26    #[error("Vec3d error: {0}")]
27    Vec3dError(#[from] Vec3dError),
28}
29
30// ============================================================================
31// MESH MERGER
32// ============================================================================
33
34/// Merge múltiplas meshes em uma única mesh por material
35pub struct MeshMerger {
36    /// Tolerância para vertex deduplication (em unidades world)
37    pub vertex_tolerance: f32,
38}
39
40impl MeshMerger {
41    pub fn new() -> Self {
42        Self {
43            vertex_tolerance: 0.001, // 1mm
44        }
45    }
46
47    /// Merge todas as meshes de uma scene por material
48    pub fn merge_scene(&self, scene: &Scene) -> Result<Scene> {
49        let mut merged_scene = Scene::new();
50
51        // Agrupar meshes por material
52        let mut meshes_by_material: HashMap<Option<String>, Vec<&Mesh>> = HashMap::new();
53        
54        for mesh in &scene.meshes {
55            meshes_by_material
56                .entry(mesh.material_id.clone())
57                .or_insert_with(Vec::new)
58                .push(mesh);
59        }
60
61        // Merge cada grupo
62        for (material_id, meshes) in meshes_by_material {
63            if meshes.is_empty() {
64                continue;
65            }
66
67            let merged = self.merge_meshes(&meshes)?;
68            let mut final_mesh = merged;
69            final_mesh.material_id = material_id.clone();
70            
71            merged_scene.add_mesh(final_mesh);
72        }
73
74        // Copiar materiais
75        merged_scene.materials = scene.materials.clone();
76
77        Ok(merged_scene)
78    }
79
80    /// Merge múltiplas meshes em uma única
81    pub fn merge_meshes(&self, meshes: &[&Mesh]) -> Result<Mesh> {
82        if meshes.is_empty() {
83            return Err(OptimizerError::OptimizationError("No meshes to merge".into()));
84        }
85
86        if meshes.len() == 1 {
87            return Ok((*meshes[0]).clone());
88        }
89
90        // Calcular capacidade total
91        let total_vertices: usize = meshes.iter().map(|m| m.vertices.len()).sum();
92        let total_indices: usize = meshes.iter().map(|m| m.indices.len()).sum();
93
94        let mut merged = Mesh::with_capacity(total_vertices, total_indices);
95
96        // Merge cada mesh
97        for mesh in meshes {
98            let offset = merged.vertices.len() as u32;
99
100            // Adicionar vértices
101            for vertex in &mesh.vertices {
102                merged.vertices.push(*vertex);
103                merged.bounds.expand_point(vertex.position);
104            }
105
106            // Adicionar índices (com offset)
107            for &index in &mesh.indices {
108                merged.indices.push(offset + index);
109            }
110        }
111
112        // Deduplicate vertices se habilitado
113        if self.vertex_tolerance > 0.0 {
114            self.deduplicate_vertices(&mut merged)?;
115        }
116
117        Ok(merged)
118    }
119
120    /// Remove vértices duplicados
121    fn deduplicate_vertices(&self, mesh: &mut Mesh) -> Result<()> {
122        let vertex_count = mesh.vertices.len();
123        if vertex_count == 0 {
124            return Ok(());
125        }
126
127        // Mapa de vértice original -> novo índice
128        let mut remap: Vec<u32> = (0..vertex_count as u32).collect();
129        let tolerance_sq = self.vertex_tolerance * self.vertex_tolerance;
130
131        // Comparar cada par de vértices (O(n²) - pode ser otimizado com spatial hash)
132        for i in 0..vertex_count {
133            if remap[i] != i as u32 {
134                continue; // Já remapeado
135            }
136
137            let v1 = &mesh.vertices[i];
138
139            for j in (i + 1)..vertex_count {
140                if remap[j] != j as u32 {
141                    continue;
142                }
143
144                let v2 = &mesh.vertices[j];
145
146                // Comparar posições
147                let dist_sq = (v1.position - v2.position).length_squared();
148                if dist_sq < tolerance_sq {
149                    // Vértices são iguais, remap j -> i
150                    remap[j] = i as u32;
151                }
152            }
153        }
154
155        // Aplicar remapping nos índices
156        for index in &mut mesh.indices {
157            *index = remap[*index as usize];
158        }
159
160        // Compactar vértices (remover não utilizados)
161        let mut used = vec![false; vertex_count];
162        for &index in &mesh.indices {
163            used[index as usize] = true;
164        }
165
166        let mut new_vertices = Vec::with_capacity(vertex_count);
167        let mut compact_remap = vec![0u32; vertex_count];
168        let mut next_index = 0u32;
169
170        for (i, vertex) in mesh.vertices.iter().enumerate() {
171            if used[i] {
172                compact_remap[i] = next_index;
173                new_vertices.push(*vertex);
174                next_index += 1;
175            }
176        }
177
178        // Atualizar índices com compact remap
179        for index in &mut mesh.indices {
180            *index = compact_remap[*index as usize];
181        }
182
183        mesh.vertices = new_vertices;
184
185        Ok(())
186    }
187}
188
189impl Default for MeshMerger {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195// ============================================================================
196// LOD GENERATOR
197// ============================================================================
198
199/// Gerador de Levels of Detail
200pub struct LodGenerator {
201    /// Target reduction ratios para cada nível
202    pub ratios: Vec<f32>,
203}
204
205impl LodGenerator {
206    pub fn new() -> Self {
207        Self {
208            ratios: vec![0.5, 0.25, 0.125], // LOD1=50%, LOD2=25%, LOD3=12.5%
209        }
210    }
211
212    /// Gera LODs para uma mesh
213    pub fn generate_lods(&self, mesh: &Mesh) -> Result<Vec<Mesh>> {
214        let mut lods = Vec::with_capacity(self.ratios.len() + 1);
215        
216        // LOD 0 = mesh original
217        lods.push(mesh.clone());
218
219        // Gerar cada nível
220        for &ratio in &self.ratios {
221            let simplified = self.simplify_mesh(&lods[0], ratio)?;
222            lods.push(simplified);
223        }
224
225        Ok(lods)
226    }
227
228    /// Simplifica mesh para target ratio (edge collapse simplification)
229    fn simplify_mesh(&self, mesh: &Mesh, ratio: f32) -> Result<Mesh> {
230        let target_triangles = ((mesh.indices.len() / 3) as f32 * ratio).max(1.0) as usize;
231
232        // Implementação simples: decimação uniforme
233        // TODO: Implementar edge collapse com quadric error metrics
234        let mut simplified = Mesh::new();
235        simplified.material_id = mesh.material_id.clone();
236
237        let step = (mesh.indices.len() / 3).max(1) / target_triangles.max(1);
238        let step = step.max(1);
239
240        let mut vertex_map: HashMap<u32, u32> = HashMap::new();
241
242        for triangle_idx in (0..mesh.indices.len() / 3).step_by(step) {
243            let base = triangle_idx * 3;
244            
245            for i in 0..3 {
246                let old_idx = mesh.indices[base + i];
247                
248                if !vertex_map.contains_key(&old_idx) {
249                    let new_idx = simplified.vertices.len() as u32;
250                    vertex_map.insert(old_idx, new_idx);
251                    simplified.vertices.push(mesh.vertices[old_idx as usize]);
252                }
253
254                simplified.indices.push(vertex_map[&old_idx]);
255            }
256        }
257
258        // Recalcular bounds
259        simplified.bounds = Aabb::EMPTY;
260        for vertex in &simplified.vertices {
261            simplified.bounds.expand_point(vertex.position);
262        }
263
264        Ok(simplified)
265    }
266}
267
268impl Default for LodGenerator {
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274// ============================================================================
275// SPATIAL INDEX (OCTREE)
276// ============================================================================
277
278/// Octree para spatial queries
279pub struct Octree {
280    root: OctreeNode,
281    max_depth: usize,
282    max_elements: usize,
283}
284
285struct OctreeNode {
286    bounds: Aabb,
287    elements: Vec<usize>, // Mesh indices
288    children: Option<Box<[OctreeNode; 8]>>,
289}
290
291impl Octree {
292    pub fn new(bounds: Aabb) -> Self {
293        Self {
294            root: OctreeNode {
295                bounds,
296                elements: Vec::new(),
297                children: None,
298            },
299            max_depth: 8,
300            max_elements: 8,
301        }
302    }
303
304    /// Insere mesh no octree
305    pub fn insert(&mut self, mesh_index: usize, bounds: &Aabb) {
306        let max_depth = self.max_depth;
307        let max_elements = self.max_elements;
308        Self::insert_recursive_static(&mut self.root, mesh_index, bounds, 0, max_depth, max_elements);
309    }
310
311    fn insert_recursive_static(node: &mut OctreeNode, mesh_index: usize, bounds: &Aabb, depth: usize, max_depth: usize, max_elements: usize) {
312        // Verificar se bounds intersecta este nó
313        if !node.bounds.intersects(bounds) {
314            return;
315        }
316
317        // Se já tem filhos, propagar
318        if let Some(ref mut children) = node.children {
319            for child in children.iter_mut() {
320                Self::insert_recursive_static(child, mesh_index, bounds, depth + 1, max_depth, max_elements);
321            }
322            return;
323        }
324
325        // Adicionar ao nó atual
326        node.elements.push(mesh_index);
327
328        // Subdividir se necessário
329        if node.elements.len() > max_elements && depth < max_depth {
330            Self::subdivide_node_static(node, max_depth, max_elements, depth);
331        }
332    }
333
334    fn subdivide_node_static(node: &mut OctreeNode, max_depth: usize, max_elements: usize, depth: usize) {
335        let _center = node.bounds.center();
336        let half_size = (node.bounds.max - node.bounds.min) * 0.5;
337
338        let mut children = Vec::with_capacity(8);
339
340        for i in 0..8 {
341            let offset = Vec3::new(
342                if i & 1 != 0 { half_size.x } else { 0.0 },
343                if i & 2 != 0 { half_size.y } else { 0.0 },
344                if i & 4 != 0 { half_size.z } else { 0.0 },
345            );
346
347            let min = node.bounds.min + offset;
348            let max = min + half_size;
349
350            children.push(OctreeNode {
351                bounds: Aabb::new(min, max),
352                elements: Vec::new(),
353                children: None,
354            });
355        }
356
357        node.children = Some(Box::new([
358            children.pop().unwrap(),
359            children.pop().unwrap(),
360            children.pop().unwrap(),
361            children.pop().unwrap(),
362            children.pop().unwrap(),
363            children.pop().unwrap(),
364            children.pop().unwrap(),
365            children.pop().unwrap(),
366        ]));
367    }
368
369    /// Query meshes que intersectam bounds
370    pub fn query(&self, bounds: &Aabb) -> Vec<usize> {
371        let mut result = Vec::new();
372        self.query_recursive(&self.root, bounds, &mut result);
373        result
374    }
375
376    fn query_recursive(&self, node: &OctreeNode, bounds: &Aabb, result: &mut Vec<usize>) {
377        if !node.bounds.intersects(bounds) {
378            return;
379        }
380
381        result.extend(&node.elements);
382
383        if let Some(ref children) = node.children {
384            for child in children.iter() {
385                self.query_recursive(child, bounds, result);
386            }
387        }
388    }
389}
390
391// ============================================================================
392// OPTIMIZER
393// ============================================================================
394
395/// Orchestrator de todas as otimizações
396pub struct Optimizer {
397    pub merger: MeshMerger,
398    pub lod_generator: LodGenerator,
399}
400
401impl Optimizer {
402    pub fn new() -> Self {
403        Self {
404            merger: MeshMerger::new(),
405            lod_generator: LodGenerator::new(),
406        }
407    }
408
409    /// Otimiza cena completa
410    pub fn optimize_scene(&self, scene: &Scene) -> Result<OptimizedScene> {
411        // 1. Merge meshes por material
412        let merged_scene = self.merger.merge_scene(scene)?;
413
414        // 2. Gerar LODs
415        let mut lods_by_mesh = Vec::new();
416        for mesh in &merged_scene.meshes {
417            let lods = self.lod_generator.generate_lods(mesh)?;
418            lods_by_mesh.push(lods);
419        }
420
421        // 3. Criar spatial index
422        let bounds = merged_scene.bounds;
423        let mut octree = Octree::new(bounds);
424        
425        for (i, mesh) in merged_scene.meshes.iter().enumerate() {
426            octree.insert(i, &mesh.bounds);
427        }
428
429        Ok(OptimizedScene {
430            base_scene: merged_scene,
431            lods: lods_by_mesh,
432            spatial_index: octree,
433        })
434    }
435}
436
437impl Default for Optimizer {
438    fn default() -> Self {
439        Self::new()
440    }
441}
442
443/// Cena otimizada com LODs e spatial index
444pub struct OptimizedScene {
445    pub base_scene: Scene,
446    pub lods: Vec<Vec<Mesh>>, // LODs para cada mesh
447    pub spatial_index: Octree,
448}
449
450impl OptimizedScene {
451    /// Seleciona LOD apropriado baseado na distância
452    pub fn select_lod(&self, mesh_index: usize, distance: f32) -> Option<&Mesh> {
453        if mesh_index >= self.lods.len() {
454            return None;
455        }
456
457        let lods = &self.lods[mesh_index];
458        if lods.is_empty() {
459            return None;
460        }
461
462        // Seleção simples baseada em distância
463        let lod_level = if distance < 10.0 {
464            0 // Full detail
465        } else if distance < 50.0 {
466            1.min(lods.len() - 1)
467        } else if distance < 100.0 {
468            2.min(lods.len() - 1)
469        } else {
470            3.min(lods.len() - 1)
471        };
472
473        Some(&lods[lod_level])
474    }
475}
476
477// ============================================================================
478// TESTES
479// ============================================================================
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484    use avila_mesh::primitives;
485
486    #[test]
487    fn test_mesh_merger() {
488        let mut scene = Scene::new();
489        
490        // Adicionar 3 cubos
491        let cube1 = primitives::cube(1.0);
492        let cube2 = primitives::cube(1.0);
493        let cube3 = primitives::cube(1.0);
494        
495        scene.add_mesh(cube1);
496        scene.add_mesh(cube2);
497        scene.add_mesh(cube3);
498
499        let merger = MeshMerger::new();
500        let merged_scene = merger.merge_scene(&scene).unwrap();
501
502        // Deve ter 1 mesh merged
503        assert_eq!(merged_scene.meshes.len(), 1);
504        
505        // Deve ter vertices merged (3 cubos com 24 vértices cada = 72, mas dedupe pode reduzir)
506        // Como cada cubo é idêntico, dedupe será agressivo
507        println!("Merged vertices: {}", merged_scene.meshes[0].vertices.len());
508        assert!(merged_scene.meshes[0].vertices.len() >= 8); // Pelo menos 8 vértices únicos
509        assert!(merged_scene.meshes[0].indices.len() == 36 * 3); // 36 triângulos (12 por cubo)
510    }
511
512    #[test]
513    fn test_lod_generation() {
514        let mesh = primitives::cube(2.0);
515        let original_triangles = mesh.indices.len() / 3;
516
517        let lod_gen = LodGenerator::new();
518        let lods = lod_gen.generate_lods(&mesh).unwrap();
519
520        // Deve ter 4 níveis (original + 3 LODs)
521        assert_eq!(lods.len(), 4);
522
523        // LOD0 = original
524        assert_eq!(lods[0].indices.len(), mesh.indices.len());
525
526        // LODs subsequentes devem ter menos triângulos
527        for i in 1..lods.len() {
528            let triangles = lods[i].indices.len() / 3;
529            assert!(triangles <= original_triangles);
530        }
531    }
532
533    #[test]
534    fn test_octree() {
535        let bounds = Aabb::new(Vec3::ZERO, Vec3::new(100.0, 100.0, 100.0));
536        let mut octree = Octree::new(bounds);
537
538        // Inserir meshes em diferentes posições
539        let mesh1_bounds = Aabb::new(Vec3::new(10.0, 10.0, 10.0), Vec3::new(20.0, 20.0, 20.0));
540        let mesh2_bounds = Aabb::new(Vec3::new(80.0, 80.0, 80.0), Vec3::new(90.0, 90.0, 90.0));
541
542        octree.insert(0, &mesh1_bounds);
543        octree.insert(1, &mesh2_bounds);
544
545        // Query região que só intersecta mesh1
546        let query_bounds = Aabb::new(Vec3::ZERO, Vec3::new(30.0, 30.0, 30.0));
547        let results = octree.query(&query_bounds);
548
549        println!("Query results: {:?}", results);
550        assert!(results.contains(&0));
551        // mesh2 pode aparecer se bounds do octree root incluir ambos
552        // (octree não subdivide se max_elements não for atingido)
553        if results.len() == 1 {
554            assert!(!results.contains(&1));
555        }
556    }
557
558    #[test]
559    fn test_full_optimization() {
560        let mut scene = Scene::new();
561        scene.add_mesh(primitives::cube(1.0));
562        scene.add_mesh(primitives::cube(1.0));
563
564        let optimizer = Optimizer::new();
565        let optimized = optimizer.optimize_scene(&scene).unwrap();
566
567        // Deve ter merged
568        assert_eq!(optimized.base_scene.meshes.len(), 1);
569
570        // Deve ter LODs
571        assert!(!optimized.lods.is_empty());
572        assert_eq!(optimized.lods[0].len(), 4); // Original + 3 LODs
573    }
574}