1use avila_vec3d::*;
14use avila_mesh::*;
15use serde::{Deserialize, Serialize};
16
17pub type Result<T> = std::result::Result<T, TesselationError>;
18
19#[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#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum IfcGeometry {
48 ExtrudedAreaSolid {
50 profile: Vec<Vec2>,
51 extrusion_direction: Vec3,
52 depth: f32,
53 },
54
55 Box {
57 center: Vec3,
58 size: Vec3,
59 },
60
61 Cylinder {
63 base_center: Vec3,
64 radius: f32,
65 height: f32,
66 },
67
68 Sphere {
70 center: Vec3,
71 radius: f32,
72 },
73
74 TriangulatedMesh {
76 vertices: Vec<Vec3>,
77 indices: Vec<u32>,
78 },
79
80 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
92pub struct Tesselator {
97 tolerance: f32, }
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 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 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 let dir = direction.normalize()?;
148
149 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 let bottom_indices: Vec<u32> = bottom_verts.iter()
160 .map(|&v| {
161 let normal = -dir; 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; mesh.add_vertex(Vertex::new(v).with_normal(normal))
170 })
171 .collect();
172
173 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 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 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 let edge1 = t0 - b0;
194 let edge2 = b1 - b0;
195 let normal = edge1.cross(&edge2).normalize().unwrap_or(Vec3::X);
196
197 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 mesh.add_triangle(v0, v1, v2)?;
205 mesh.add_triangle(v0, v2, v3)?;
206 }
207
208 Ok(mesh)
209 }
210
211 fn tesselate_box(&self, center: Vec3, size: Vec3) -> Mesh {
216 let mut mesh = primitives::cube(1.0);
217
218 let transform = Mat4::translation(center).mul_mat4(&Mat4::scale(size));
220 mesh.transform(&transform);
221
222 mesh
223 }
224
225 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 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 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 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 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 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 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 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 fn tesselate_brep(&self, faces: &[BrepFace]) -> Result<Mesh> {
325 let mut mesh = Mesh::new();
326
327 for face in faces {
328 let outer = &face.outer_bound;
330 if outer.len() < 3 {
331 continue;
332 }
333
334 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 }
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#[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 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}