1use std::cell::Cell;
56use std::f64::consts::TAU;
57use std::fmt;
58
59use axiolid_core::{Frame2, Frame3, Interval, Point2, Scalar, Transform3, Vec2, Vec3};
60use axiolid_curve::{
61 BSplineCurve, BSplineSurface, Carrier, Circle2, Circle3, Curve2, Curve3, Ellipse2, Ellipse3,
62 ImplicitSection3, Intrinsic3, LiftedCurve2, Line2, Line3, PairNode, PairSection3, Polyline,
63 RuledCarrier, RuledSection3, Sinusoid2, TorusCarrier, TorusSection3,
64};
65use axiolid_surface::{Cone, Cylinder, EllipticalCylinder, Plane, Sphere, Surface, Torus};
66use axiolid_topology::{Edge, Face, Loop, Orientation, Vertex};
67
68use crate::{ExactBRep, ExactTopology};
69
70pub const RIGID_TOLERANCE: Scalar = 1e-12;
77
78#[non_exhaustive]
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum TransformError {
82 NonFinite,
84 NotRigid,
87 Unsupported(&'static str),
89}
90
91impl fmt::Display for TransformError {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match self {
94 Self::NonFinite => f.write_str("the transform has a non-finite entry"),
95 Self::NotRigid => f.write_str(
96 "the transform is not rigid: its linear part is not orthonormal \
97 (a scale or shear)",
98 ),
99 Self::Unsupported(what) => write!(f, "{what} cannot be placed exactly"),
100 }
101 }
102}
103
104impl std::error::Error for TransformError {}
105
106#[derive(Debug, Clone)]
108struct Rigid {
109 transform: Transform3,
110 mirror: bool,
111 lossless: Option<Cell<bool>>,
115}
116
117impl Rigid {
118 fn new(transform: &Transform3) -> Result<Self, TransformError> {
119 let m = transform.matrix3;
120 let columns = [m.x_axis, m.y_axis, m.z_axis];
121 if !columns.iter().all(|c| c.is_finite()) || !transform.translation.is_finite() {
122 return Err(TransformError::NonFinite);
123 }
124 for i in 0..3 {
125 for j in i..3 {
126 let want = if i == j { 1.0 } else { 0.0 };
127 if (columns[i].dot(columns[j]) - want).abs() > RIGID_TOLERANCE {
128 return Err(TransformError::NotRigid);
129 }
130 }
131 }
132 Ok(Self {
133 transform: *transform,
134 mirror: m.determinant() < 0.0,
135 lossless: None,
136 })
137 }
138
139 fn point(&self, p: Vec3) -> Vec3 {
140 let mapped = self.transform.transform_point3(p);
141 if let Some(lossless) = &self.lossless {
142 let t = self.transform.translation;
143 let sums = [
144 (p.x, t.x, mapped.x),
145 (p.y, t.y, mapped.y),
146 (p.z, t.z, mapped.z),
147 ];
148 if !sums.iter().all(|&(a, b, sum)| exact_sum(a, b, sum)) {
149 lossless.set(false);
150 }
151 }
152 mapped
153 }
154
155 fn vector(&self, v: Vec3) -> Vec3 {
156 self.transform.transform_vector3(v)
157 }
158
159 fn planar_frame(&self, frame: &Frame3) -> Frame3 {
162 let z = self.vector(frame.z);
163 Frame3 {
164 origin: self.point(frame.origin),
165 x: self.vector(frame.x),
166 y: self.vector(frame.y),
167 z: if self.mirror { -z } else { z },
168 }
169 }
170
171 fn axial_frame(&self, frame: &Frame3) -> Frame3 {
174 let y = self.vector(frame.y);
175 Frame3 {
176 origin: self.point(frame.origin),
177 x: self.vector(frame.x),
178 y: if self.mirror { -y } else { y },
179 z: self.vector(frame.z),
180 }
181 }
182
183 fn unsupported_mirrored(&self, what: &'static str) -> Result<(), TransformError> {
184 if self.mirror {
185 Err(TransformError::Unsupported(what))
186 } else {
187 Ok(())
188 }
189 }
190}
191
192fn exact_sum(a: Scalar, b: Scalar, sum: Scalar) -> bool {
195 if sum != a + b || !sum.is_finite() {
196 return false;
197 }
198 let virtual_b = sum - a;
199 let error = (a - (sum - virtual_b)) + (b - virtual_b);
200 error == 0.0
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205enum Reparam {
206 Same,
208 Negated,
210 Reflected,
213}
214
215impl Reparam {
216 fn interval(self, interval: Interval) -> Interval {
217 match self {
218 Self::Same => interval,
219 Self::Negated => Interval::new(-interval.start, -interval.end),
220 Self::Reflected => Interval::new(TAU - interval.start, TAU - interval.end),
221 }
222 }
223}
224
225impl ExactBRep {
226 pub fn transformed(&self, transform: &Transform3) -> Result<ExactBRep, TransformError> {
274 self.moved(&Rigid::new(transform)?)
275 }
276
277 pub fn translated_exactly(&self, offset: Vec3) -> Result<Option<ExactBRep>, TransformError> {
293 let mut rigid = Rigid::new(&Transform3::from_translation(offset))?;
294 rigid.lossless = Some(Cell::new(true));
295 let moved = self.moved(&rigid)?;
296 Ok(rigid
297 .lossless
298 .is_some_and(|lossless| lossless.get())
299 .then_some(moved))
300 }
301
302 fn moved(&self, rigid: &Rigid) -> Result<ExactBRep, TransformError> {
303 let source = &self.topology;
304
305 let mut curves3 = Vec::with_capacity(self.curves3.len());
306 let mut curve_reparam = Vec::with_capacity(self.curves3.len());
307 for curve in &self.curves3 {
308 let (curve, reparam) = curve3(curve, rigid)?;
309 curves3.push(curve);
310 curve_reparam.push(reparam);
311 }
312 let mut surfaces = Vec::with_capacity(self.surfaces.len());
313 let mut axial = Vec::with_capacity(self.surfaces.len());
314 for surface in &self.surfaces {
315 let (surface, reflected) = surface_of(surface, rigid)?;
316 surfaces.push(surface);
317 axial.push(reflected);
318 }
319
320 let mut pcurve_reflect: Vec<Option<bool>> = vec![None; self.curves2.len()];
324 for face in source.faces() {
325 let Some(surface) = face.surface else {
326 continue;
327 };
328 let reflect = axial.get(surface.index()).copied().unwrap_or(false);
329 for bound in &face.bounds {
330 let Some(wire) = source.loops().get(bound.loop_id.index()) else {
331 continue;
332 };
333 for use_ in &wire.edges {
334 let Some(pcurve) = use_.pcurve else {
335 continue;
336 };
337 let Some(slot) = pcurve_reflect.get_mut(pcurve.index()) else {
338 continue;
339 };
340 match slot {
341 Some(existing) if *existing != reflect => {
342 return Err(TransformError::Unsupported(
343 "a pcurve shared by faces of different families under a reflection",
344 ));
345 }
346 _ => *slot = Some(reflect),
347 }
348 }
349 }
350 }
351 let mut curves2 = Vec::with_capacity(self.curves2.len());
352 let mut pcurve_reparam = Vec::with_capacity(self.curves2.len());
353 for (index, curve) in self.curves2.iter().enumerate() {
354 let (curve, reparam) = curve2(curve, rigid, pcurve_reflect[index].unwrap_or(false))?;
355 curves2.push(curve);
356 pcurve_reparam.push(reparam);
357 }
358
359 let mut topology = ExactTopology::default();
360 for vertex in source.vertices() {
361 topology.add_vertex(Vertex {
362 position: rigid.point(vertex.position),
363 });
364 }
365 let mut edge_intervals = self.edge_intervals.clone();
366 for (index, edge) in source.edges().iter().enumerate() {
367 let id = topology.add_edge(Edge {
368 start: edge.start,
369 end: edge.end,
370 curve: edge.curve,
371 });
372 let reparam = edge
373 .curve
374 .and_then(|curve| curve_reparam.get(curve.index()).copied())
375 .unwrap_or(Reparam::Same);
376 debug_assert_eq!(source.edge_id_at(index), Some(id));
377 if let Some(interval) = edge_intervals.get_mut(&id) {
378 *interval = reparam.interval(*interval);
379 }
380 }
381 let mut pcurve_intervals = self.pcurve_intervals.clone();
382 for (index, wire) in source.loops().iter().enumerate() {
383 let id = topology.add_loop(Loop {
384 edges: wire.edges.clone(),
385 });
386 debug_assert_eq!(source.loop_id_at(index), Some(id));
387 for (use_index, use_) in wire.edges.iter().enumerate() {
388 let reparam = use_
389 .pcurve
390 .and_then(|curve| pcurve_reparam.get(curve.index()).copied())
391 .unwrap_or(Reparam::Same);
392 if let Some(interval) = pcurve_intervals.get_mut(&(id, use_index)) {
393 *interval = reparam.interval(*interval);
394 }
395 }
396 }
397 for face in source.faces() {
398 let orientation = match (face.orientation, rigid.mirror) {
399 (orientation, false) => orientation,
400 (Orientation::Forward, true) => Orientation::Reversed,
401 (Orientation::Reversed, true) => Orientation::Forward,
402 };
403 topology.add_face(Face {
404 surface: face.surface,
405 bounds: face.bounds.clone(),
406 orientation,
407 });
408 }
409 for shell in source.shells() {
410 topology.add_shell(shell.clone());
411 }
412 for solid in source.solids() {
413 topology.add_solid(solid.clone());
414 }
415
416 Ok(ExactBRep {
417 topology,
418 curves3,
419 curves2,
420 surfaces,
421 edge_intervals,
422 pcurve_intervals,
423 face_names: self.face_names.clone(),
424 edge_names: self.edge_names.clone(),
425 })
426 }
427}
428
429fn surface_of(surface: &Surface, rigid: &Rigid) -> Result<(Surface, bool), TransformError> {
431 let axial = rigid.mirror;
432 Ok(match surface {
433 Surface::Plane(p) => (
434 Surface::Plane(Plane {
435 frame: rigid.planar_frame(&p.frame),
436 }),
437 false,
438 ),
439 Surface::Cylinder(c) => (
440 Surface::Cylinder(Cylinder {
441 frame: rigid.axial_frame(&c.frame),
442 ..*c
443 }),
444 axial,
445 ),
446 Surface::EllipticalCylinder(c) => (
447 Surface::EllipticalCylinder(EllipticalCylinder {
448 frame: rigid.axial_frame(&c.frame),
449 ..*c
450 }),
451 axial,
452 ),
453 Surface::Cone(c) => (
454 Surface::Cone(Cone {
455 frame: rigid.axial_frame(&c.frame),
456 ..*c
457 }),
458 axial,
459 ),
460 Surface::Sphere(s) => (
461 Surface::Sphere(Sphere {
462 frame: rigid.axial_frame(&s.frame),
463 ..*s
464 }),
465 axial,
466 ),
467 Surface::Torus(t) => (
468 Surface::Torus(Torus {
469 frame: rigid.axial_frame(&t.frame),
470 ..*t
471 }),
472 axial,
473 ),
474 Surface::BSpline(b) => (Surface::BSpline(spline_surface(b, rigid)), false),
475 _ => return Err(TransformError::Unsupported("an unknown surface family")),
476 })
477}
478
479fn spline_surface(surface: &BSplineSurface, rigid: &Rigid) -> BSplineSurface {
480 BSplineSurface {
481 control_points: surface
482 .control_points
483 .iter()
484 .map(|row| row.iter().map(|p| rigid.point(*p)).collect())
485 .collect(),
486 ..surface.clone()
487 }
488}
489
490fn carrier(value: &Carrier, rigid: &Rigid) -> Result<Carrier, TransformError> {
491 Ok(match value {
492 Carrier::Plane(frame) => Carrier::Plane(rigid.planar_frame(frame)),
493 Carrier::Ruled(ruled) => {
494 rigid.unsupported_mirrored("a curve on a ruled carrier under a reflection")?;
495 Carrier::Ruled(RuledCarrier {
496 frame: rigid.axial_frame(&ruled.frame),
497 ..*ruled
498 })
499 }
500 Carrier::Sphere { frame, radius } => {
501 rigid.unsupported_mirrored("a curve on a spherical carrier under a reflection")?;
502 Carrier::Sphere {
503 frame: rigid.axial_frame(frame),
504 radius: *radius,
505 }
506 }
507 Carrier::Torus(torus) => {
508 rigid.unsupported_mirrored("a curve on a toroidal carrier under a reflection")?;
509 Carrier::Torus(TorusCarrier {
510 frame: rigid.axial_frame(&torus.frame),
511 ..*torus
512 })
513 }
514 Carrier::Spline(surface) => Carrier::Spline(Box::new(spline_surface(surface, rigid))),
515 })
516}
517
518fn curve3(curve: &Curve3, rigid: &Rigid) -> Result<(Curve3, Reparam), TransformError> {
519 Ok(match curve {
520 Curve3::Line(line) => (
521 Curve3::Line(Line3 {
522 origin: rigid.point(line.origin),
523 direction: rigid.vector(line.direction),
524 }),
525 Reparam::Same,
526 ),
527 Curve3::Circle(circle) => (
528 Curve3::Circle(Circle3 {
529 frame: rigid.planar_frame(&circle.frame),
530 ..*circle
531 }),
532 Reparam::Same,
533 ),
534 Curve3::Ellipse(ellipse) => (
535 Curve3::Ellipse(Ellipse3 {
536 frame: rigid.planar_frame(&ellipse.frame),
537 ..*ellipse
538 }),
539 Reparam::Same,
540 ),
541 Curve3::Polyline(polyline) => (
542 Curve3::Polyline(Polyline {
543 points: polyline.points.iter().map(|p| rigid.point(*p)).collect(),
544 closed: polyline.closed,
545 }),
546 Reparam::Same,
547 ),
548 Curve3::BSpline(spline) => (
549 Curve3::BSpline(BSplineCurve {
550 control_points: spline
551 .control_points
552 .iter()
553 .map(|p| rigid.point(*p))
554 .collect(),
555 ..spline.clone()
556 }),
557 Reparam::Same,
558 ),
559 Curve3::Intrinsic(intrinsic) => {
560 rigid.unsupported_mirrored("an intrinsic space curve under a reflection")?;
563 (
564 Curve3::Intrinsic(Intrinsic3 {
565 start: rigid.planar_frame(&intrinsic.start),
566 ..intrinsic.clone()
567 }),
568 Reparam::Same,
569 )
570 }
571 Curve3::RuledSection(section) => {
572 rigid.unsupported_mirrored("a ruled section under a reflection")?;
573 (
574 Curve3::RuledSection(RuledSection3 {
575 carrier: RuledCarrier {
576 frame: rigid.axial_frame(§ion.carrier.frame),
577 ..section.carrier
578 },
579 graph: section.graph,
580 }),
581 Reparam::Same,
582 )
583 }
584 Curve3::TorusSection(section) => {
585 rigid.unsupported_mirrored("a torus section under a reflection")?;
586 (
587 Curve3::TorusSection(TorusSection3 {
588 torus: TorusCarrier {
589 frame: rigid.axial_frame(§ion.torus.frame),
590 ..section.torus
591 },
592 graph: section.graph,
593 }),
594 Reparam::Same,
595 )
596 }
597 Curve3::ImplicitSection(section) => (
598 Curve3::ImplicitSection(ImplicitSection3 {
599 carrier: carrier(§ion.carrier, rigid)?,
600 curve: section.curve.clone(),
601 }),
602 Reparam::Same,
603 ),
604 Curve3::PairSection(section) => (
605 Curve3::PairSection(PairSection3 {
606 first: carrier(§ion.first, rigid)?,
607 second: carrier(§ion.second, rigid)?,
608 nodes: section
609 .nodes
610 .iter()
611 .map(|node| PairNode {
612 point: rigid.point(node.point),
613 ..*node
614 })
615 .collect(),
616 }),
617 Reparam::Same,
618 ),
619 Curve3::Elevated(_) => {
620 return Err(TransformError::Unsupported(
621 "an elevated alignment curve (plan and height law)",
622 ))
623 }
624 Curve3::Banked(_) => {
626 return Err(TransformError::Unsupported(
627 "a banked alignment curve (centreline and cant law)",
628 ))
629 }
630 _ => return Err(TransformError::Unsupported("an unknown curve family")),
631 })
632}
633
634fn reflect_point(p: Point2) -> Point2 {
636 Point2::new(TAU - p.x, p.y)
637}
638
639fn reflect_vector(v: Vec2) -> Vec2 {
641 Vec2::new(-v.x, v.y)
642}
643
644fn reflect_conic_frame(frame: &Frame2) -> Frame2 {
647 Frame2 {
648 origin: reflect_point(frame.origin),
649 x: reflect_vector(frame.x),
650 y: -reflect_vector(frame.y),
651 }
652}
653
654fn curve2(
658 curve: &Curve2,
659 rigid: &Rigid,
660 reflect: bool,
661) -> Result<(Curve2, Reparam), TransformError> {
662 if let Curve2::Lifted(lifted) = curve {
663 rigid.unsupported_mirrored("a lifted pcurve under a reflection")?;
666 let (space, _) = curve3(&lifted.curve, rigid)?;
667 return Ok((
668 Curve2::Lifted(LiftedCurve2 {
669 curve: Box::new(space),
670 carrier: carrier(&lifted.carrier, rigid)?,
671 ..lifted.clone()
672 }),
673 Reparam::Same,
674 ));
675 }
676 if !reflect {
677 return Ok((curve.clone(), Reparam::Same));
678 }
679 Ok(match curve {
680 Curve2::Line(line) => (
681 Curve2::Line(Line2 {
682 origin: reflect_point(line.origin),
683 direction: reflect_vector(line.direction),
684 }),
685 Reparam::Same,
686 ),
687 Curve2::Circle(circle) => (
688 Curve2::Circle(Circle2 {
689 frame: reflect_conic_frame(&circle.frame),
690 ..*circle
691 }),
692 Reparam::Negated,
693 ),
694 Curve2::Ellipse(ellipse) => (
695 Curve2::Ellipse(Ellipse2 {
696 frame: reflect_conic_frame(&ellipse.frame),
697 ..*ellipse
698 }),
699 Reparam::Negated,
700 ),
701 Curve2::Polyline(polyline) => (
702 Curve2::Polyline(Polyline {
703 points: polyline.points.iter().map(|p| reflect_point(*p)).collect(),
704 closed: polyline.closed,
705 }),
706 Reparam::Same,
707 ),
708 Curve2::BSpline(spline) => (
709 Curve2::BSpline(BSplineCurve {
710 control_points: spline
711 .control_points
712 .iter()
713 .map(|p| reflect_point(*p))
714 .collect(),
715 ..spline.clone()
716 }),
717 Reparam::Same,
718 ),
719 Curve2::Sinusoid(wave) => (
722 Curve2::Sinusoid(Sinusoid2 {
723 sine: -wave.sine,
724 ..*wave
725 }),
726 Reparam::Reflected,
727 ),
728 _ => {
729 return Err(TransformError::Unsupported(
730 "a pcurve family on a curved face under a reflection",
731 ))
732 }
733 })
734}