1use axiolid_brep::{ExactBRep, ExactBRepBuilder};
26use axiolid_contracts::{GeomError, GeomResult, Operation};
27use axiolid_core::{Frame3, Interval, Point2, Point3, Tolerance, Vec2, Vec3};
28use axiolid_curve::{Curve2, Line2};
29use axiolid_surface::{Plane, Surface};
30use axiolid_topology::{EdgeId, EdgeUse, Face, FaceBound, Orientation, VertexId};
31
32use crate::extrude_exact::{add_line_edge, add_loop, finish_closed, reserve};
33use crate::BACKEND_ID;
34
35pub const WARPED_WALL: &str =
37 "an exact loft wall whose four corners are not coplanar: the ruled face between the two \
38 section edges is a bilinear patch";
39
40fn unsupported(input: &'static str) -> GeomError {
41 GeomError::UnsupportedInput {
42 backend: BACKEND_ID,
43 operation: Operation::Sweep,
44 input,
45 }
46}
47
48fn place(frame: &Frame3, p: Point2) -> Point3 {
49 frame.origin + frame.x * p.x + frame.y * p.y
50}
51
52pub fn loft_polygon_stations_exact(
68 sections: &[(Frame3, Vec<Vec<Point2>>)],
69 tolerance: Tolerance,
70) -> GeomResult<ExactBRep> {
71 if sections.len() < 2 {
72 return Err(GeomError::InvalidInput(format!(
73 "an exact loft needs at least two sections, got {}",
74 sections.len()
75 )));
76 }
77 let shape: Vec<usize> = sections[0].1.iter().map(Vec::len).collect();
78 if shape.is_empty() || shape.iter().any(|&n| n < 3) {
79 return Err(GeomError::Degenerate(
80 "an exact loft section needs rings of at least three vertices".into(),
81 ));
82 }
83 if sections
84 .iter()
85 .any(|(_, rings)| rings.iter().map(Vec::len).ne(shape.iter().copied()))
86 {
87 return Err(GeomError::InvalidInput(
88 "exact loft sections must share their ring structure".into(),
89 ));
90 }
91 let count = sections.len();
92 let per: usize = shape.iter().sum();
93 let walls = (count - 1) * per;
94 let mut builder = ExactBRepBuilder::default();
95 reserve(
96 &mut builder,
97 count * per,
98 count * per + walls,
99 2 * shape.len() + walls,
100 2 + walls,
101 count * per + walls,
102 2 * per + 4 * walls,
103 2 + walls,
104 )?;
105 let points: Vec<Vec<Vec<Point3>>> = sections
106 .iter()
107 .map(|(frame, rings)| {
108 rings
109 .iter()
110 .map(|ring| ring.iter().map(|p| place(frame, *p)).collect())
111 .collect()
112 })
113 .collect();
114 let vertices: Vec<Vec<Vec<VertexId>>> = points
115 .iter()
116 .map(|rings| {
117 rings
118 .iter()
119 .map(|ring| {
120 ring.iter()
121 .map(|&position| {
122 builder
123 .topology_mut()
124 .add_vertex(axiolid_topology::Vertex { position })
125 })
126 .collect()
127 })
128 .collect()
129 })
130 .collect();
131 let edge = |builder: &mut ExactBRepBuilder,
132 (from, to): (VertexId, VertexId),
133 (a, b): (Point3, Point3)|
134 -> GeomResult<EdgeId> {
135 if (b - a).length() == 0.0 {
136 return Err(GeomError::Degenerate(
137 "an exact loft has a collapsed edge".into(),
138 ));
139 }
140 Ok(add_line_edge(builder, from, to, a, b - a))
141 };
142 let mut rims: Vec<Vec<Vec<EdgeId>>> = Vec::with_capacity(count);
144 for j in 0..count {
145 let mut rings = Vec::with_capacity(shape.len());
146 for (r, &n) in shape.iter().enumerate() {
147 let mut ring = Vec::with_capacity(n);
148 for k in 0..n {
149 let m = (k + 1) % n;
150 ring.push(edge(
151 &mut builder,
152 (vertices[j][r][k], vertices[j][r][m]),
153 (points[j][r][k], points[j][r][m]),
154 )?);
155 }
156 rings.push(ring);
157 }
158 rims.push(rings);
159 }
160 let mut rails: Vec<Vec<Vec<EdgeId>>> = Vec::with_capacity(count - 1);
161 for j in 0..count - 1 {
162 let mut rings = Vec::with_capacity(shape.len());
163 for (r, &n) in shape.iter().enumerate() {
164 let mut ring = Vec::with_capacity(n);
165 for k in 0..n {
166 ring.push(edge(
167 &mut builder,
168 (vertices[j][r][k], vertices[j + 1][r][k]),
169 (points[j][r][k], points[j + 1][r][k]),
170 )?);
171 }
172 rings.push(ring);
173 }
174 rails.push(rings);
175 }
176 let mut faces = Vec::with_capacity(2 + walls);
177 for (j, orientation) in [
179 (0, Orientation::Reversed),
180 (count - 1, Orientation::Forward),
181 ] {
182 let (frame, rings) = §ions[j];
183 let surface = builder.add_surface(Surface::Plane(Plane { frame: *frame }));
184 let mut bounds = Vec::with_capacity(shape.len());
185 for (r, ring) in rings.iter().enumerate() {
186 let n = ring.len();
187 let mut uses = Vec::with_capacity(n);
188 for k in 0..n {
189 let (a, b) = (ring[k], ring[(k + 1) % n]);
190 let pcurve = builder.add_curve2(Curve2::Line(Line2 {
191 origin: Vec2::new(a.x, a.y),
192 direction: b - a,
193 }));
194 uses.push(EdgeUse {
195 edge: rims[j][r][k],
196 orientation: Orientation::Forward,
197 pcurve: Some(pcurve),
198 });
199 }
200 let loop_id = add_loop(&mut builder, uses, vec![Interval::UNIT; n]);
201 bounds.push(FaceBound {
202 loop_id,
203 orientation: Orientation::Forward,
204 outer: r == 0,
205 });
206 }
207 faces.push(builder.topology_mut().add_face(Face {
208 surface: Some(surface),
209 bounds,
210 orientation,
211 }));
212 }
213 let linear = tolerance.linear();
215 for j in 0..count - 1 {
216 for (r, &n) in shape.iter().enumerate() {
217 for k in 0..n {
218 let m = (k + 1) % n;
219 let (a, b) = (points[j][r][k], points[j][r][m]);
220 let (c, d) = (points[j + 1][r][k], points[j + 1][r][m]);
221 let corners = [a, b, d, c];
223 let mut normal = Vec3::ZERO;
224 for i in 0..4 {
225 let (p, q) = (corners[i], corners[(i + 1) % 4]);
226 normal += p.cross(q);
227 }
228 let length = normal.length();
229 let x = (b - a).normalize_or_zero();
230 if !(length.is_finite() && length > 0.0) || x == Vec3::ZERO {
231 return Err(GeomError::Degenerate(
232 "an exact loft wall has no plane".into(),
233 ));
234 }
235 let z = normal / length;
236 let centre = 0.25 * (a + b + c + d);
237 if corners.iter().any(|p| (*p - centre).dot(z).abs() > linear) {
238 return Err(unsupported(WARPED_WALL));
239 }
240 let x = (x - z * x.dot(z)).normalize();
243 let y = z.cross(x);
244 let frame = Frame3 {
245 origin: centre,
246 x,
247 y,
248 z,
249 };
250 let surface = builder.add_surface(Surface::Plane(Plane { frame }));
251 let uv = |p: Point3| Vec2::new((p - centre).dot(x), (p - centre).dot(y));
252 let line = |from: Point3, to: Point3| {
253 Curve2::Line(Line2 {
254 origin: uv(from),
255 direction: uv(to) - uv(from),
256 })
257 };
258 let sides = [
259 (rims[j][r][k], Orientation::Forward, line(a, b)),
260 (rails[j][r][m], Orientation::Forward, line(b, d)),
261 (rims[j + 1][r][k], Orientation::Reversed, line(c, d)),
262 (rails[j][r][k], Orientation::Reversed, line(a, c)),
263 ];
264 let mut uses = Vec::with_capacity(4);
265 let mut intervals = Vec::with_capacity(4);
266 for (edge, orientation, pcurve) in sides {
267 let pcurve = builder.add_curve2(pcurve);
268 uses.push(EdgeUse {
269 edge,
270 orientation,
271 pcurve: Some(pcurve),
272 });
273 intervals.push(match orientation {
274 Orientation::Forward => Interval::UNIT,
275 Orientation::Reversed => Interval::new(1.0, 0.0),
276 });
277 }
278 let loop_id = add_loop(&mut builder, uses, intervals);
279 faces.push(builder.topology_mut().add_face(Face {
280 surface: Some(surface),
281 bounds: vec![FaceBound {
282 loop_id,
283 orientation: Orientation::Forward,
284 outer: true,
285 }],
286 orientation: Orientation::Forward,
287 }));
288 }
289 }
290 }
291 finish_closed(builder, faces)
292}