Skip to main content

avila_tesselation/
lib.rs

1//! # avila-tesselation
2//!
3//! **Engine de Tesselação - Conversão de Sólidos IFC em Triângulos**
4//!
5//! Converte representações geométricas de alto nível (IFC) em meshes trianguladas:
6//! - Extruded Solids (perfis extrudados)
7//! - BRep (Boundary Representation)
8//! - CSG (Constructive Solid Geometry)
9//! - Swept Solids
10//!
11//! Pipeline: IFC Geometry → Tesselação → Mesh 3D
12
13use avila_vec3d::*;
14use avila_mesh::*;
15use serde::{Deserialize, Serialize};
16
17pub type Result<T> = std::result::Result<T, TesselationError>;
18
19// ============================================================================
20// ERROS
21// ============================================================================
22
23#[derive(Debug, thiserror::Error)]
24pub enum TesselationError {
25    #[error("Invalid geometry: {0}")]
26    InvalidGeometry(String),
27
28    #[error("Unsupported geometry type: {0}")]
29    UnsupportedGeometry(String),
30
31    #[error("Tesselation failed: {0}")]
32    TesselationFailed(String),
33
34    #[error("Vec3d error: {0}")]
35    Vec3dError(#[from] Vec3dError),
36
37    #[error("Mesh error: {0}")]
38    MeshError(#[from] avila_mesh::MeshError),
39}
40
41// ============================================================================
42// GEOMETRIA IFC (representações de alto nível)
43// ============================================================================
44
45/// Tipos de geometria IFC
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum IfcGeometry {
48    /// Sólido extrudado (perfil 2D + direção/distância)
49    ExtrudedAreaSolid {
50        profile: Vec<Vec2>,
51        extrusion_direction: Vec3,
52        depth: f32,
53    },
54
55    /// Caixa (box)
56    Box {
57        center: Vec3,
58        size: Vec3,
59    },
60
61    /// Cilindro
62    Cylinder {
63        base_center: Vec3,
64        radius: f32,
65        height: f32,
66    },
67
68    /// Esfera
69    Sphere {
70        center: Vec3,
71        radius: f32,
72    },
73
74    /// Malha triangulada (já tesselada)
75    TriangulatedMesh {
76        vertices: Vec<Vec3>,
77        indices: Vec<u32>,
78    },
79
80    /// BRep (Boundary Representation) - faces, edges, vertices
81    Brep {
82        faces: Vec<BrepFace>,
83    },
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct BrepFace {
88    pub outer_bound: Vec<Vec3>,
89    pub inner_bounds: Vec<Vec<Vec3>>,
90}
91
92// ============================================================================
93// TESSELATOR PRINCIPAL
94// ============================================================================
95
96pub struct Tesselator {
97    tolerance: f32, // Tolerância para curvas/aproximações
98}
99
100impl Tesselator {
101    pub fn new() -> Self {
102        Self { tolerance: 0.01 }
103    }
104
105    pub fn with_tolerance(tolerance: f32) -> Self {
106        Self { tolerance }
107    }
108
109    /// Converte geometria IFC em mesh
110    pub fn tesselate(&self, geometry: &IfcGeometry) -> Result<Mesh> {
111        match geometry {
112            IfcGeometry::ExtrudedAreaSolid { profile, extrusion_direction, depth } => {
113                self.tesselate_extruded_solid(profile, *extrusion_direction, *depth)
114            }
115            IfcGeometry::Box { center, size } => {
116                Ok(self.tesselate_box(*center, *size))
117            }
118            IfcGeometry::Cylinder { base_center, radius, height } => {
119                Ok(self.tesselate_cylinder(*base_center, *radius, *height))
120            }
121            IfcGeometry::Sphere { center, radius } => {
122                Ok(self.tesselate_sphere(*center, *radius))
123            }
124            IfcGeometry::TriangulatedMesh { vertices, indices } => {
125                self.tesselate_from_triangles(vertices, indices)
126            }
127            IfcGeometry::Brep { faces } => {
128                self.tesselate_brep(faces)
129            }
130        }
131    }
132
133    // ========================================================================
134    // EXTRUDED SOLID
135    // ========================================================================
136
137    fn tesselate_extruded_solid(&self, profile: &[Vec2], direction: Vec3, depth: f32) -> Result<Mesh> {
138        if profile.len() < 3 {
139            return Err(TesselationError::InvalidGeometry(
140                "Profile must have at least 3 points".into()
141            ));
142        }
143
144        let mut mesh = Mesh::new();
145
146        // Normalizar direção
147        let dir = direction.normalize()?;
148
149        // Criar perfil bottom e top
150        let bottom_verts: Vec<Vec3> = profile.iter()
151            .map(|p| Vec3::new(p.x, 0.0, p.y))
152            .collect();
153
154        let top_verts: Vec<Vec3> = bottom_verts.iter()
155            .map(|v| *v + dir * depth)
156            .collect();
157
158        // Adicionar vértices
159        let bottom_indices: Vec<u32> = bottom_verts.iter()
160            .map(|&v| {
161                let normal = -dir; // Normal para baixo
162                mesh.add_vertex(Vertex::new(v).with_normal(normal))
163            })
164            .collect();
165
166        let top_indices: Vec<u32> = top_verts.iter()
167            .map(|&v| {
168                let normal = dir; // Normal para cima
169                mesh.add_vertex(Vertex::new(v).with_normal(normal))
170            })
171            .collect();
172
173        // Triangular bottom face (fan triangulation)
174        for i in 1..bottom_indices.len() - 1 {
175            mesh.add_triangle(bottom_indices[0], bottom_indices[i + 1], bottom_indices[i])?;
176        }
177
178        // Triangular top face
179        for i in 1..top_indices.len() - 1 {
180            mesh.add_triangle(top_indices[0], top_indices[i], top_indices[i + 1])?;
181        }
182
183        // Faces laterais
184        for i in 0..profile.len() {
185            let next = (i + 1) % profile.len();
186
187            let b0 = bottom_verts[i];
188            let b1 = bottom_verts[next];
189            let t0 = top_verts[i];
190            let t1 = top_verts[next];
191
192            // Calcular normal da face lateral
193            let edge1 = t0 - b0;
194            let edge2 = b1 - b0;
195            let normal = edge1.cross(&edge2).normalize().unwrap_or(Vec3::X);
196
197            // Adicionar 4 vértices da face lateral (quad)
198            let v0 = mesh.add_vertex(Vertex::new(b0).with_normal(normal));
199            let v1 = mesh.add_vertex(Vertex::new(b1).with_normal(normal));
200            let v2 = mesh.add_vertex(Vertex::new(t1).with_normal(normal));
201            let v3 = mesh.add_vertex(Vertex::new(t0).with_normal(normal));
202
203            // 2 triângulos
204            mesh.add_triangle(v0, v1, v2)?;
205            mesh.add_triangle(v0, v2, v3)?;
206        }
207
208        Ok(mesh)
209    }
210
211    // ========================================================================
212    // BOX
213    // ========================================================================
214
215    fn tesselate_box(&self, center: Vec3, size: Vec3) -> Mesh {
216        let mut mesh = primitives::cube(1.0);
217
218        // Escalar e transladar
219        let transform = Mat4::translation(center).mul_mat4(&Mat4::scale(size));
220        mesh.transform(&transform);
221
222        mesh
223    }
224
225    // ========================================================================
226    // CYLINDER
227    // ========================================================================
228
229    fn tesselate_cylinder(&self, base_center: Vec3, radius: f32, height: f32) -> Mesh {
230        let mut mesh = Mesh::new();
231
232        let segments = 32;
233
234        // Bottom circle
235        let bottom_center_idx = mesh.add_vertex(
236            Vertex::new(base_center).with_normal(Vec3::new(0.0, -1.0, 0.0))
237        );
238
239        for i in 0..segments {
240            let theta = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
241            let x = radius * theta.cos();
242            let z = radius * theta.sin();
243            let pos = base_center + Vec3::new(x, 0.0, z);
244            mesh.add_vertex(Vertex::new(pos).with_normal(Vec3::new(0.0, -1.0, 0.0)));
245        }
246
247        // Bottom triangles
248        for i in 0..segments {
249            let next = (i + 1) % segments;
250            mesh.add_triangle(bottom_center_idx, (i + 1) as u32, (next + 1) as u32).unwrap();
251        }
252
253        // Top circle
254        let top_center = base_center + Vec3::new(0.0, height, 0.0);
255        let top_center_idx = mesh.add_vertex(
256            Vertex::new(top_center).with_normal(Vec3::new(0.0, 1.0, 0.0))
257        );
258
259        let top_start = mesh.vertices.len() as u32;
260        for i in 0..segments {
261            let theta = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
262            let x = radius * theta.cos();
263            let z = radius * theta.sin();
264            let pos = top_center + Vec3::new(x, 0.0, z);
265            mesh.add_vertex(Vertex::new(pos).with_normal(Vec3::new(0.0, 1.0, 0.0)));
266        }
267
268        // Top triangles
269        for i in 0..segments {
270            let next = (i + 1) % segments;
271            mesh.add_triangle(top_center_idx, (top_start + next), (top_start + i)).unwrap();
272        }
273
274        // Side faces
275        for i in 0..segments {
276            let next = (i + 1) % segments;
277
278            let b0 = (i + 1) as u32;
279            let b1 = (next + 1) as u32;
280            let t0 = top_start + i;
281            let t1 = top_start + next;
282
283            mesh.add_triangle(b0, b1, t1).unwrap();
284            mesh.add_triangle(b0, t1, t0).unwrap();
285        }
286
287        mesh.recalculate_normals_smooth();
288        mesh
289    }
290
291    // ========================================================================
292    // SPHERE
293    // ========================================================================
294
295    fn tesselate_sphere(&self, center: Vec3, radius: f32) -> Mesh {
296        let mut mesh = primitives::sphere(radius, 2);
297        mesh.transform(&Mat4::translation(center));
298        mesh
299    }
300
301    // ========================================================================
302    // TRIANGULATED MESH
303    // ========================================================================
304
305    fn tesselate_from_triangles(&self, vertices: &[Vec3], indices: &[u32]) -> Result<Mesh> {
306        let mut mesh = Mesh::with_capacity(vertices.len(), indices.len());
307
308        for &v in vertices {
309            mesh.add_vertex(Vertex::new(v));
310        }
311
312        for chunk in indices.chunks_exact(3) {
313            mesh.add_triangle(chunk[0], chunk[1], chunk[2])?;
314        }
315
316        mesh.recalculate_normals_smooth();
317        Ok(mesh)
318    }
319
320    // ========================================================================
321    // BREP (simplificado - triangula faces planares)
322    // ========================================================================
323
324    fn tesselate_brep(&self, faces: &[BrepFace]) -> Result<Mesh> {
325        let mut mesh = Mesh::new();
326
327        for face in faces {
328            // Triangular outer bound (ear clipping simplificado)
329            let outer = &face.outer_bound;
330            if outer.len() < 3 {
331                continue;
332            }
333
334            // Fan triangulation (assume face convexa)
335            let base_idx = mesh.add_vertex(Vertex::new(outer[0]));
336            for i in 1..outer.len() - 1 {
337                let v1 = mesh.add_vertex(Vertex::new(outer[i]));
338                let v2 = mesh.add_vertex(Vertex::new(outer[i + 1]));
339                mesh.add_triangle(base_idx, v1, v2)?;
340            }
341
342            // TODO: inner bounds (furos)
343        }
344
345        mesh.recalculate_normals_smooth();
346        Ok(mesh)
347    }
348}
349
350impl Default for Tesselator {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356// ============================================================================
357// TESTES
358// ============================================================================
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn test_tesselate_box() {
366        let tesselator = Tesselator::new();
367        let geometry = IfcGeometry::Box {
368            center: Vec3::ZERO,
369            size: Vec3::ONE,
370        };
371
372        let mesh = tesselator.tesselate(&geometry).unwrap();
373        assert!(mesh.validate().is_ok());
374        assert!(mesh.triangle_count() > 0);
375    }
376
377    #[test]
378    fn test_tesselate_cylinder() {
379        let tesselator = Tesselator::new();
380        let geometry = IfcGeometry::Cylinder {
381            base_center: Vec3::ZERO,
382            radius: 1.0,
383            height: 2.0,
384        };
385
386        let mesh = tesselator.tesselate(&geometry).unwrap();
387        assert!(mesh.validate().is_ok());
388        assert!(mesh.triangle_count() > 0);
389    }
390
391    #[test]
392    fn test_tesselate_extruded_solid() {
393        let tesselator = Tesselator::new();
394
395        // Perfil quadrado
396        let profile = vec![
397            Vec2::new(-1.0, -1.0),
398            Vec2::new(1.0, -1.0),
399            Vec2::new(1.0, 1.0),
400            Vec2::new(-1.0, 1.0),
401        ];
402
403        let geometry = IfcGeometry::ExtrudedAreaSolid {
404            profile,
405            extrusion_direction: Vec3::Y,
406            depth: 3.0,
407        };
408
409        let mesh = tesselator.tesselate(&geometry).unwrap();
410        assert!(mesh.validate().is_ok());
411        assert!(mesh.triangle_count() > 0);
412    }
413}