1use std::f64::consts::TAU;
56use std::fmt;
57
58use axiolid_core::{Frame2, Frame3, Interval, Point2, Scalar, Transform3, Vec2, Vec3};
59use axiolid_curve::{
60 BSplineCurve, BSplineSurface, Carrier, Circle2, Circle3, Curve2, Curve3, Ellipse2, Ellipse3,
61 ImplicitSection3, Intrinsic3, LiftedCurve2, Line2, Line3, PairNode, PairSection3, Polyline,
62 RuledCarrier, RuledSection3, Sinusoid2, TorusCarrier, TorusSection3,
63};
64use axiolid_surface::{Cone, Cylinder, EllipticalCylinder, Plane, Sphere, Surface, Torus};
65use axiolid_topology::{Edge, Face, Loop, Orientation, Vertex};
66
67use crate::{ExactBRep, ExactTopology};
68
69pub const RIGID_TOLERANCE: Scalar = 1e-12;
76
77#[non_exhaustive]
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum TransformError {
81 NonFinite,
83 NotRigid,
86 Unsupported(&'static str),
88}
89
90impl fmt::Display for TransformError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::NonFinite => f.write_str("the transform has a non-finite entry"),
94 Self::NotRigid => f.write_str(
95 "the transform is not rigid: its linear part is not orthonormal \
96 (a scale or shear)",
97 ),
98 Self::Unsupported(what) => write!(f, "{what} cannot be placed exactly"),
99 }
100 }
101}
102
103impl std::error::Error for TransformError {}
104
105#[derive(Debug, Clone, Copy)]
107struct Rigid {
108 transform: Transform3,
109 mirror: bool,
110}
111
112impl Rigid {
113 fn new(transform: &Transform3) -> Result<Self, TransformError> {
114 let m = transform.matrix3;
115 let columns = [m.x_axis, m.y_axis, m.z_axis];
116 if !columns.iter().all(|c| c.is_finite()) || !transform.translation.is_finite() {
117 return Err(TransformError::NonFinite);
118 }
119 for i in 0..3 {
120 for j in i..3 {
121 let want = if i == j { 1.0 } else { 0.0 };
122 if (columns[i].dot(columns[j]) - want).abs() > RIGID_TOLERANCE {
123 return Err(TransformError::NotRigid);
124 }
125 }
126 }
127 Ok(Self {
128 transform: *transform,
129 mirror: m.determinant() < 0.0,
130 })
131 }
132
133 fn point(&self, p: Vec3) -> Vec3 {
134 self.transform.transform_point3(p)
135 }
136
137 fn vector(&self, v: Vec3) -> Vec3 {
138 self.transform.transform_vector3(v)
139 }
140
141 fn planar_frame(&self, frame: &Frame3) -> Frame3 {
144 let z = self.vector(frame.z);
145 Frame3 {
146 origin: self.point(frame.origin),
147 x: self.vector(frame.x),
148 y: self.vector(frame.y),
149 z: if self.mirror { -z } else { z },
150 }
151 }
152
153 fn axial_frame(&self, frame: &Frame3) -> Frame3 {
156 let y = self.vector(frame.y);
157 Frame3 {
158 origin: self.point(frame.origin),
159 x: self.vector(frame.x),
160 y: if self.mirror { -y } else { y },
161 z: self.vector(frame.z),
162 }
163 }
164
165 fn unsupported_mirrored(&self, what: &'static str) -> Result<(), TransformError> {
166 if self.mirror {
167 Err(TransformError::Unsupported(what))
168 } else {
169 Ok(())
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176enum Reparam {
177 Same,
179 Negated,
181 Reflected,
184}
185
186impl Reparam {
187 fn interval(self, interval: Interval) -> Interval {
188 match self {
189 Self::Same => interval,
190 Self::Negated => Interval::new(-interval.start, -interval.end),
191 Self::Reflected => Interval::new(TAU - interval.start, TAU - interval.end),
192 }
193 }
194}
195
196impl ExactBRep {
197 pub fn transformed(&self, transform: &Transform3) -> Result<ExactBRep, TransformError> {
244 let rigid = Rigid::new(transform)?;
245 let source = &self.topology;
246
247 let mut curves3 = Vec::with_capacity(self.curves3.len());
248 let mut curve_reparam = Vec::with_capacity(self.curves3.len());
249 for curve in &self.curves3 {
250 let (curve, reparam) = curve3(curve, &rigid)?;
251 curves3.push(curve);
252 curve_reparam.push(reparam);
253 }
254 let mut surfaces = Vec::with_capacity(self.surfaces.len());
255 let mut axial = Vec::with_capacity(self.surfaces.len());
256 for surface in &self.surfaces {
257 let (surface, reflected) = surface_of(surface, &rigid)?;
258 surfaces.push(surface);
259 axial.push(reflected);
260 }
261
262 let mut pcurve_reflect: Vec<Option<bool>> = vec![None; self.curves2.len()];
266 for face in source.faces() {
267 let Some(surface) = face.surface else {
268 continue;
269 };
270 let reflect = axial.get(surface.index()).copied().unwrap_or(false);
271 for bound in &face.bounds {
272 let Some(wire) = source.loops().get(bound.loop_id.index()) else {
273 continue;
274 };
275 for use_ in &wire.edges {
276 let Some(pcurve) = use_.pcurve else {
277 continue;
278 };
279 let Some(slot) = pcurve_reflect.get_mut(pcurve.index()) else {
280 continue;
281 };
282 match slot {
283 Some(existing) if *existing != reflect => {
284 return Err(TransformError::Unsupported(
285 "a pcurve shared by faces of different families under a reflection",
286 ));
287 }
288 _ => *slot = Some(reflect),
289 }
290 }
291 }
292 }
293 let mut curves2 = Vec::with_capacity(self.curves2.len());
294 let mut pcurve_reparam = Vec::with_capacity(self.curves2.len());
295 for (index, curve) in self.curves2.iter().enumerate() {
296 let (curve, reparam) = curve2(curve, &rigid, pcurve_reflect[index].unwrap_or(false))?;
297 curves2.push(curve);
298 pcurve_reparam.push(reparam);
299 }
300
301 let mut topology = ExactTopology::default();
302 for vertex in source.vertices() {
303 topology.add_vertex(Vertex {
304 position: rigid.point(vertex.position),
305 });
306 }
307 let mut edge_intervals = self.edge_intervals.clone();
308 for (index, edge) in source.edges().iter().enumerate() {
309 let id = topology.add_edge(Edge {
310 start: edge.start,
311 end: edge.end,
312 curve: edge.curve,
313 });
314 let reparam = edge
315 .curve
316 .and_then(|curve| curve_reparam.get(curve.index()).copied())
317 .unwrap_or(Reparam::Same);
318 debug_assert_eq!(source.edge_id_at(index), Some(id));
319 if let Some(interval) = edge_intervals.get_mut(&id) {
320 *interval = reparam.interval(*interval);
321 }
322 }
323 let mut pcurve_intervals = self.pcurve_intervals.clone();
324 for (index, wire) in source.loops().iter().enumerate() {
325 let id = topology.add_loop(Loop {
326 edges: wire.edges.clone(),
327 });
328 debug_assert_eq!(source.loop_id_at(index), Some(id));
329 for (use_index, use_) in wire.edges.iter().enumerate() {
330 let reparam = use_
331 .pcurve
332 .and_then(|curve| pcurve_reparam.get(curve.index()).copied())
333 .unwrap_or(Reparam::Same);
334 if let Some(interval) = pcurve_intervals.get_mut(&(id, use_index)) {
335 *interval = reparam.interval(*interval);
336 }
337 }
338 }
339 for face in source.faces() {
340 let orientation = match (face.orientation, rigid.mirror) {
341 (orientation, false) => orientation,
342 (Orientation::Forward, true) => Orientation::Reversed,
343 (Orientation::Reversed, true) => Orientation::Forward,
344 };
345 topology.add_face(Face {
346 surface: face.surface,
347 bounds: face.bounds.clone(),
348 orientation,
349 });
350 }
351 for shell in source.shells() {
352 topology.add_shell(shell.clone());
353 }
354 for solid in source.solids() {
355 topology.add_solid(solid.clone());
356 }
357
358 Ok(ExactBRep {
359 topology,
360 curves3,
361 curves2,
362 surfaces,
363 edge_intervals,
364 pcurve_intervals,
365 face_names: self.face_names.clone(),
366 edge_names: self.edge_names.clone(),
367 })
368 }
369}
370
371fn surface_of(surface: &Surface, rigid: &Rigid) -> Result<(Surface, bool), TransformError> {
373 let axial = rigid.mirror;
374 Ok(match surface {
375 Surface::Plane(p) => (
376 Surface::Plane(Plane {
377 frame: rigid.planar_frame(&p.frame),
378 }),
379 false,
380 ),
381 Surface::Cylinder(c) => (
382 Surface::Cylinder(Cylinder {
383 frame: rigid.axial_frame(&c.frame),
384 ..*c
385 }),
386 axial,
387 ),
388 Surface::EllipticalCylinder(c) => (
389 Surface::EllipticalCylinder(EllipticalCylinder {
390 frame: rigid.axial_frame(&c.frame),
391 ..*c
392 }),
393 axial,
394 ),
395 Surface::Cone(c) => (
396 Surface::Cone(Cone {
397 frame: rigid.axial_frame(&c.frame),
398 ..*c
399 }),
400 axial,
401 ),
402 Surface::Sphere(s) => (
403 Surface::Sphere(Sphere {
404 frame: rigid.axial_frame(&s.frame),
405 ..*s
406 }),
407 axial,
408 ),
409 Surface::Torus(t) => (
410 Surface::Torus(Torus {
411 frame: rigid.axial_frame(&t.frame),
412 ..*t
413 }),
414 axial,
415 ),
416 Surface::BSpline(b) => (Surface::BSpline(spline_surface(b, rigid)), false),
417 _ => return Err(TransformError::Unsupported("an unknown surface family")),
418 })
419}
420
421fn spline_surface(surface: &BSplineSurface, rigid: &Rigid) -> BSplineSurface {
422 BSplineSurface {
423 control_points: surface
424 .control_points
425 .iter()
426 .map(|row| row.iter().map(|p| rigid.point(*p)).collect())
427 .collect(),
428 ..surface.clone()
429 }
430}
431
432fn carrier(value: &Carrier, rigid: &Rigid) -> Result<Carrier, TransformError> {
433 Ok(match value {
434 Carrier::Plane(frame) => Carrier::Plane(rigid.planar_frame(frame)),
435 Carrier::Ruled(ruled) => {
436 rigid.unsupported_mirrored("a curve on a ruled carrier under a reflection")?;
437 Carrier::Ruled(RuledCarrier {
438 frame: rigid.axial_frame(&ruled.frame),
439 ..*ruled
440 })
441 }
442 Carrier::Sphere { frame, radius } => {
443 rigid.unsupported_mirrored("a curve on a spherical carrier under a reflection")?;
444 Carrier::Sphere {
445 frame: rigid.axial_frame(frame),
446 radius: *radius,
447 }
448 }
449 Carrier::Torus(torus) => {
450 rigid.unsupported_mirrored("a curve on a toroidal carrier under a reflection")?;
451 Carrier::Torus(TorusCarrier {
452 frame: rigid.axial_frame(&torus.frame),
453 ..*torus
454 })
455 }
456 Carrier::Spline(surface) => Carrier::Spline(Box::new(spline_surface(surface, rigid))),
457 })
458}
459
460fn curve3(curve: &Curve3, rigid: &Rigid) -> Result<(Curve3, Reparam), TransformError> {
461 Ok(match curve {
462 Curve3::Line(line) => (
463 Curve3::Line(Line3 {
464 origin: rigid.point(line.origin),
465 direction: rigid.vector(line.direction),
466 }),
467 Reparam::Same,
468 ),
469 Curve3::Circle(circle) => (
470 Curve3::Circle(Circle3 {
471 frame: rigid.planar_frame(&circle.frame),
472 ..*circle
473 }),
474 Reparam::Same,
475 ),
476 Curve3::Ellipse(ellipse) => (
477 Curve3::Ellipse(Ellipse3 {
478 frame: rigid.planar_frame(&ellipse.frame),
479 ..*ellipse
480 }),
481 Reparam::Same,
482 ),
483 Curve3::Polyline(polyline) => (
484 Curve3::Polyline(Polyline {
485 points: polyline.points.iter().map(|p| rigid.point(*p)).collect(),
486 closed: polyline.closed,
487 }),
488 Reparam::Same,
489 ),
490 Curve3::BSpline(spline) => (
491 Curve3::BSpline(BSplineCurve {
492 control_points: spline
493 .control_points
494 .iter()
495 .map(|p| rigid.point(*p))
496 .collect(),
497 ..spline.clone()
498 }),
499 Reparam::Same,
500 ),
501 Curve3::Intrinsic(intrinsic) => {
502 rigid.unsupported_mirrored("an intrinsic space curve under a reflection")?;
505 (
506 Curve3::Intrinsic(Intrinsic3 {
507 start: rigid.planar_frame(&intrinsic.start),
508 ..intrinsic.clone()
509 }),
510 Reparam::Same,
511 )
512 }
513 Curve3::RuledSection(section) => {
514 rigid.unsupported_mirrored("a ruled section under a reflection")?;
515 (
516 Curve3::RuledSection(RuledSection3 {
517 carrier: RuledCarrier {
518 frame: rigid.axial_frame(§ion.carrier.frame),
519 ..section.carrier
520 },
521 graph: section.graph,
522 }),
523 Reparam::Same,
524 )
525 }
526 Curve3::TorusSection(section) => {
527 rigid.unsupported_mirrored("a torus section under a reflection")?;
528 (
529 Curve3::TorusSection(TorusSection3 {
530 torus: TorusCarrier {
531 frame: rigid.axial_frame(§ion.torus.frame),
532 ..section.torus
533 },
534 graph: section.graph,
535 }),
536 Reparam::Same,
537 )
538 }
539 Curve3::ImplicitSection(section) => (
540 Curve3::ImplicitSection(ImplicitSection3 {
541 carrier: carrier(§ion.carrier, rigid)?,
542 curve: section.curve.clone(),
543 }),
544 Reparam::Same,
545 ),
546 Curve3::PairSection(section) => (
547 Curve3::PairSection(PairSection3 {
548 first: carrier(§ion.first, rigid)?,
549 second: carrier(§ion.second, rigid)?,
550 nodes: section
551 .nodes
552 .iter()
553 .map(|node| PairNode {
554 point: rigid.point(node.point),
555 ..*node
556 })
557 .collect(),
558 }),
559 Reparam::Same,
560 ),
561 Curve3::Elevated(_) => {
562 return Err(TransformError::Unsupported(
563 "an elevated alignment curve (plan and height law)",
564 ))
565 }
566 _ => return Err(TransformError::Unsupported("an unknown curve family")),
567 })
568}
569
570fn reflect_point(p: Point2) -> Point2 {
572 Point2::new(TAU - p.x, p.y)
573}
574
575fn reflect_vector(v: Vec2) -> Vec2 {
577 Vec2::new(-v.x, v.y)
578}
579
580fn reflect_conic_frame(frame: &Frame2) -> Frame2 {
583 Frame2 {
584 origin: reflect_point(frame.origin),
585 x: reflect_vector(frame.x),
586 y: -reflect_vector(frame.y),
587 }
588}
589
590fn curve2(
594 curve: &Curve2,
595 rigid: &Rigid,
596 reflect: bool,
597) -> Result<(Curve2, Reparam), TransformError> {
598 if let Curve2::Lifted(lifted) = curve {
599 rigid.unsupported_mirrored("a lifted pcurve under a reflection")?;
602 let (space, _) = curve3(&lifted.curve, rigid)?;
603 return Ok((
604 Curve2::Lifted(LiftedCurve2 {
605 curve: Box::new(space),
606 carrier: carrier(&lifted.carrier, rigid)?,
607 ..lifted.clone()
608 }),
609 Reparam::Same,
610 ));
611 }
612 if !reflect {
613 return Ok((curve.clone(), Reparam::Same));
614 }
615 Ok(match curve {
616 Curve2::Line(line) => (
617 Curve2::Line(Line2 {
618 origin: reflect_point(line.origin),
619 direction: reflect_vector(line.direction),
620 }),
621 Reparam::Same,
622 ),
623 Curve2::Circle(circle) => (
624 Curve2::Circle(Circle2 {
625 frame: reflect_conic_frame(&circle.frame),
626 ..*circle
627 }),
628 Reparam::Negated,
629 ),
630 Curve2::Ellipse(ellipse) => (
631 Curve2::Ellipse(Ellipse2 {
632 frame: reflect_conic_frame(&ellipse.frame),
633 ..*ellipse
634 }),
635 Reparam::Negated,
636 ),
637 Curve2::Polyline(polyline) => (
638 Curve2::Polyline(Polyline {
639 points: polyline.points.iter().map(|p| reflect_point(*p)).collect(),
640 closed: polyline.closed,
641 }),
642 Reparam::Same,
643 ),
644 Curve2::BSpline(spline) => (
645 Curve2::BSpline(BSplineCurve {
646 control_points: spline
647 .control_points
648 .iter()
649 .map(|p| reflect_point(*p))
650 .collect(),
651 ..spline.clone()
652 }),
653 Reparam::Same,
654 ),
655 Curve2::Sinusoid(wave) => (
658 Curve2::Sinusoid(Sinusoid2 {
659 sine: -wave.sine,
660 ..*wave
661 }),
662 Reparam::Reflected,
663 ),
664 _ => {
665 return Err(TransformError::Unsupported(
666 "a pcurve family on a curved face under a reflection",
667 ))
668 }
669 })
670}