axiolid_construct/
extrude.rs1use axiolid_contracts::{GeomError, GeomResult, Sign};
9use axiolid_core::{Point2, Point3, Scalar, Vec3};
10use axiolid_mesh::TriMesh;
11use axiolid_reference::arithmetic::{
12 expansion_sign, expansion_sum, grow_expansion, scale_expansion,
13};
14use axiolid_reference::expansion::two_product;
15
16pub use crate::extrude_exact::extrude_profile_exact;
17
18pub fn extrude(
26 points: &[Point2],
27 triangles: &[[u32; 3]],
28 loops: &[core::ops::Range<usize>],
29 direction: Vec3,
30 depth: Scalar,
31) -> GeomResult<TriMesh> {
32 if !depth.is_finite() || depth <= 0.0 {
33 return Err(GeomError::InvalidInput(format!(
34 "extrusion depth must be positive and finite, got {depth}"
35 )));
36 }
37 if !direction.is_finite() || direction.length() <= 0.0 {
38 return Err(GeomError::InvalidInput(
39 "extrusion direction must be a finite non-zero vector".to_owned(),
40 ));
41 }
42 let offset = direction.normalize() * depth;
43 if !offset.is_finite() {
44 return Err(GeomError::Degenerate(
45 "extrusion direction could not be normalised".to_owned(),
46 ));
47 }
48
49 let n = points.len();
50 let mut positions = Vec::with_capacity(n * 2);
51 positions.extend(points.iter().map(|p| Point3::new(p.x, p.y, 0.0)));
53 positions.extend(points.iter().map(|p| Point3::new(p.x, p.y, 0.0) + offset));
54
55 let mut indices: Vec<u32> = Vec::with_capacity(triangles.len() * 6 + n * 6);
56 let top = n as u32;
57
58 for t in triangles {
62 indices.extend_from_slice(&[t[0] + top, t[1] + top, t[2] + top]);
63 indices.extend_from_slice(&[t[0], t[2], t[1]]);
64 }
65
66 for range in loops {
68 let len = range.len();
69 if len < 3 {
70 return Err(GeomError::InvalidInput(format!(
71 "extrusion loop needs at least 3 vertices, got {len}"
72 )));
73 }
74 for k in 0..len {
75 let a = (range.start + k) as u32;
76 let b = (range.start + (k + 1) % len) as u32;
77 indices.extend_from_slice(&[a, b, b + top]);
78 indices.extend_from_slice(&[a, b + top, a + top]);
79 }
80 }
81
82 if offset.z < 0.0 {
90 for triangle in indices.chunks_exact_mut(3) {
91 triangle.swap(1, 2);
92 }
93 }
94
95 Ok(TriMesh::new(positions, indices))
96}
97
98pub fn extrude_profile(
104 rings: &crate::profile::Rings,
105 direction: Vec3,
106 depth: Scalar,
107 _tolerance: axiolid_core::Tolerance,
108) -> GeomResult<TriMesh> {
109 let (points, triangles) = crate::profile::triangulate(rings)?;
110 let mut loops = Vec::with_capacity(1 + rings.holes.len());
111 let mut start = 0usize;
112 loops.push(start..rings.outer.len());
113 start += rings.outer.len();
114 for hole in &rings.holes {
115 loops.push(start..start + hole.len());
116 start += hole.len();
117 }
118 extrude(&points, &triangles, &loops, direction, depth)
119}
120
121#[must_use]
137pub fn outward_orientation(mesh: &TriMesh) -> Option<bool> {
138 if mesh.indices.len() < 12 {
139 return None;
141 }
142 let mut total: Vec<f64> = vec![0.0];
143 for corner in mesh.indices.chunks_exact(3) {
144 let a = mesh.positions[corner[0] as usize];
145 let b = mesh.positions[corner[1] as usize];
146 let c = mesh.positions[corner[2] as usize];
147 total = expansion_sum(&total, &triple_product(a, b, c));
148 }
149 match expansion_sign(&total) {
150 Sign::Positive => Some(true),
151 Sign::Negative => Some(false),
152 Sign::Zero => None,
153 _ => None,
155 }
156}
157
158#[must_use]
160fn triple_product(a: Point3, b: Point3, c: Point3) -> Vec<f64> {
161 let term = |p: f64, q: f64, r: f64, s: f64, k: f64| {
162 scale_expansion(&exact_difference_of_products(p, q, r, s), k)
164 };
165 let x = term(b.y, c.z, c.y, b.z, a.x);
166 let y = term(b.z, c.x, c.z, b.x, a.y);
167 let z = term(b.x, c.y, c.x, b.y, a.z);
168 expansion_sum(&expansion_sum(&x, &y), &z)
169}
170
171#[must_use]
173fn exact_difference_of_products(p: f64, q: f64, r: f64, s: f64) -> Vec<f64> {
174 let (pq, pq_err) = two_product(p, q);
175 let (rs, rs_err) = two_product(r, s);
176 let e = grow_expansion(&[pq_err], -rs_err);
177 let e = grow_expansion(&e, pq);
178 grow_expansion(&e, -rs)
179}