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> {
245 let rigid = Rigid::new(transform)?;
246 let source = &self.topology;
247
248 let mut curves3 = Vec::with_capacity(self.curves3.len());
249 let mut curve_reparam = Vec::with_capacity(self.curves3.len());
250 for curve in &self.curves3 {
251 let (curve, reparam) = curve3(curve, &rigid)?;
252 curves3.push(curve);
253 curve_reparam.push(reparam);
254 }
255 let mut surfaces = Vec::with_capacity(self.surfaces.len());
256 let mut axial = Vec::with_capacity(self.surfaces.len());
257 for surface in &self.surfaces {
258 let (surface, reflected) = surface_of(surface, &rigid)?;
259 surfaces.push(surface);
260 axial.push(reflected);
261 }
262
263 let mut pcurve_reflect: Vec<Option<bool>> = vec![None; self.curves2.len()];
267 for face in source.faces() {
268 let Some(surface) = face.surface else {
269 continue;
270 };
271 let reflect = axial.get(surface.index()).copied().unwrap_or(false);
272 for bound in &face.bounds {
273 let Some(wire) = source.loops().get(bound.loop_id.index()) else {
274 continue;
275 };
276 for use_ in &wire.edges {
277 let Some(pcurve) = use_.pcurve else {
278 continue;
279 };
280 let Some(slot) = pcurve_reflect.get_mut(pcurve.index()) else {
281 continue;
282 };
283 match slot {
284 Some(existing) if *existing != reflect => {
285 return Err(TransformError::Unsupported(
286 "a pcurve shared by faces of different families under a reflection",
287 ));
288 }
289 _ => *slot = Some(reflect),
290 }
291 }
292 }
293 }
294 let mut curves2 = Vec::with_capacity(self.curves2.len());
295 let mut pcurve_reparam = Vec::with_capacity(self.curves2.len());
296 for (index, curve) in self.curves2.iter().enumerate() {
297 let (curve, reparam) = curve2(curve, &rigid, pcurve_reflect[index].unwrap_or(false))?;
298 curves2.push(curve);
299 pcurve_reparam.push(reparam);
300 }
301
302 let mut topology = ExactTopology::default();
303 for vertex in source.vertices() {
304 topology.add_vertex(Vertex {
305 position: rigid.point(vertex.position),
306 });
307 }
308 let mut edge_intervals = self.edge_intervals.clone();
309 for (index, edge) in source.edges().iter().enumerate() {
310 let id = topology.add_edge(Edge {
311 start: edge.start,
312 end: edge.end,
313 curve: edge.curve,
314 });
315 let reparam = edge
316 .curve
317 .and_then(|curve| curve_reparam.get(curve.index()).copied())
318 .unwrap_or(Reparam::Same);
319 debug_assert_eq!(source.edge_id_at(index), Some(id));
320 if let Some(interval) = edge_intervals.get_mut(&id) {
321 *interval = reparam.interval(*interval);
322 }
323 }
324 let mut pcurve_intervals = self.pcurve_intervals.clone();
325 for (index, wire) in source.loops().iter().enumerate() {
326 let id = topology.add_loop(Loop {
327 edges: wire.edges.clone(),
328 });
329 debug_assert_eq!(source.loop_id_at(index), Some(id));
330 for (use_index, use_) in wire.edges.iter().enumerate() {
331 let reparam = use_
332 .pcurve
333 .and_then(|curve| pcurve_reparam.get(curve.index()).copied())
334 .unwrap_or(Reparam::Same);
335 if let Some(interval) = pcurve_intervals.get_mut(&(id, use_index)) {
336 *interval = reparam.interval(*interval);
337 }
338 }
339 }
340 for face in source.faces() {
341 let orientation = match (face.orientation, rigid.mirror) {
342 (orientation, false) => orientation,
343 (Orientation::Forward, true) => Orientation::Reversed,
344 (Orientation::Reversed, true) => Orientation::Forward,
345 };
346 topology.add_face(Face {
347 surface: face.surface,
348 bounds: face.bounds.clone(),
349 orientation,
350 });
351 }
352 for shell in source.shells() {
353 topology.add_shell(shell.clone());
354 }
355 for solid in source.solids() {
356 topology.add_solid(solid.clone());
357 }
358
359 Ok(ExactBRep {
360 topology,
361 curves3,
362 curves2,
363 surfaces,
364 edge_intervals,
365 pcurve_intervals,
366 face_names: self.face_names.clone(),
367 edge_names: self.edge_names.clone(),
368 })
369 }
370}
371
372fn surface_of(surface: &Surface, rigid: &Rigid) -> Result<(Surface, bool), TransformError> {
374 let axial = rigid.mirror;
375 Ok(match surface {
376 Surface::Plane(p) => (
377 Surface::Plane(Plane {
378 frame: rigid.planar_frame(&p.frame),
379 }),
380 false,
381 ),
382 Surface::Cylinder(c) => (
383 Surface::Cylinder(Cylinder {
384 frame: rigid.axial_frame(&c.frame),
385 ..*c
386 }),
387 axial,
388 ),
389 Surface::EllipticalCylinder(c) => (
390 Surface::EllipticalCylinder(EllipticalCylinder {
391 frame: rigid.axial_frame(&c.frame),
392 ..*c
393 }),
394 axial,
395 ),
396 Surface::Cone(c) => (
397 Surface::Cone(Cone {
398 frame: rigid.axial_frame(&c.frame),
399 ..*c
400 }),
401 axial,
402 ),
403 Surface::Sphere(s) => (
404 Surface::Sphere(Sphere {
405 frame: rigid.axial_frame(&s.frame),
406 ..*s
407 }),
408 axial,
409 ),
410 Surface::Torus(t) => (
411 Surface::Torus(Torus {
412 frame: rigid.axial_frame(&t.frame),
413 ..*t
414 }),
415 axial,
416 ),
417 Surface::BSpline(b) => (Surface::BSpline(spline_surface(b, rigid)), false),
418 _ => return Err(TransformError::Unsupported("an unknown surface family")),
419 })
420}
421
422fn spline_surface(surface: &BSplineSurface, rigid: &Rigid) -> BSplineSurface {
423 BSplineSurface {
424 control_points: surface
425 .control_points
426 .iter()
427 .map(|row| row.iter().map(|p| rigid.point(*p)).collect())
428 .collect(),
429 ..surface.clone()
430 }
431}
432
433fn carrier(value: &Carrier, rigid: &Rigid) -> Result<Carrier, TransformError> {
434 Ok(match value {
435 Carrier::Plane(frame) => Carrier::Plane(rigid.planar_frame(frame)),
436 Carrier::Ruled(ruled) => {
437 rigid.unsupported_mirrored("a curve on a ruled carrier under a reflection")?;
438 Carrier::Ruled(RuledCarrier {
439 frame: rigid.axial_frame(&ruled.frame),
440 ..*ruled
441 })
442 }
443 Carrier::Sphere { frame, radius } => {
444 rigid.unsupported_mirrored("a curve on a spherical carrier under a reflection")?;
445 Carrier::Sphere {
446 frame: rigid.axial_frame(frame),
447 radius: *radius,
448 }
449 }
450 Carrier::Torus(torus) => {
451 rigid.unsupported_mirrored("a curve on a toroidal carrier under a reflection")?;
452 Carrier::Torus(TorusCarrier {
453 frame: rigid.axial_frame(&torus.frame),
454 ..*torus
455 })
456 }
457 Carrier::Spline(surface) => Carrier::Spline(Box::new(spline_surface(surface, rigid))),
458 })
459}
460
461fn curve3(curve: &Curve3, rigid: &Rigid) -> Result<(Curve3, Reparam), TransformError> {
462 Ok(match curve {
463 Curve3::Line(line) => (
464 Curve3::Line(Line3 {
465 origin: rigid.point(line.origin),
466 direction: rigid.vector(line.direction),
467 }),
468 Reparam::Same,
469 ),
470 Curve3::Circle(circle) => (
471 Curve3::Circle(Circle3 {
472 frame: rigid.planar_frame(&circle.frame),
473 ..*circle
474 }),
475 Reparam::Same,
476 ),
477 Curve3::Ellipse(ellipse) => (
478 Curve3::Ellipse(Ellipse3 {
479 frame: rigid.planar_frame(&ellipse.frame),
480 ..*ellipse
481 }),
482 Reparam::Same,
483 ),
484 Curve3::Polyline(polyline) => (
485 Curve3::Polyline(Polyline {
486 points: polyline.points.iter().map(|p| rigid.point(*p)).collect(),
487 closed: polyline.closed,
488 }),
489 Reparam::Same,
490 ),
491 Curve3::BSpline(spline) => (
492 Curve3::BSpline(BSplineCurve {
493 control_points: spline
494 .control_points
495 .iter()
496 .map(|p| rigid.point(*p))
497 .collect(),
498 ..spline.clone()
499 }),
500 Reparam::Same,
501 ),
502 Curve3::Intrinsic(intrinsic) => {
503 rigid.unsupported_mirrored("an intrinsic space curve under a reflection")?;
506 (
507 Curve3::Intrinsic(Intrinsic3 {
508 start: rigid.planar_frame(&intrinsic.start),
509 ..intrinsic.clone()
510 }),
511 Reparam::Same,
512 )
513 }
514 Curve3::RuledSection(section) => {
515 rigid.unsupported_mirrored("a ruled section under a reflection")?;
516 (
517 Curve3::RuledSection(RuledSection3 {
518 carrier: RuledCarrier {
519 frame: rigid.axial_frame(§ion.carrier.frame),
520 ..section.carrier
521 },
522 graph: section.graph,
523 }),
524 Reparam::Same,
525 )
526 }
527 Curve3::TorusSection(section) => {
528 rigid.unsupported_mirrored("a torus section under a reflection")?;
529 (
530 Curve3::TorusSection(TorusSection3 {
531 torus: TorusCarrier {
532 frame: rigid.axial_frame(§ion.torus.frame),
533 ..section.torus
534 },
535 graph: section.graph,
536 }),
537 Reparam::Same,
538 )
539 }
540 Curve3::ImplicitSection(section) => (
541 Curve3::ImplicitSection(ImplicitSection3 {
542 carrier: carrier(§ion.carrier, rigid)?,
543 curve: section.curve.clone(),
544 }),
545 Reparam::Same,
546 ),
547 Curve3::PairSection(section) => (
548 Curve3::PairSection(PairSection3 {
549 first: carrier(§ion.first, rigid)?,
550 second: carrier(§ion.second, rigid)?,
551 nodes: section
552 .nodes
553 .iter()
554 .map(|node| PairNode {
555 point: rigid.point(node.point),
556 ..*node
557 })
558 .collect(),
559 }),
560 Reparam::Same,
561 ),
562 Curve3::Elevated(_) => {
563 return Err(TransformError::Unsupported(
564 "an elevated alignment curve (plan and height law)",
565 ))
566 }
567 Curve3::Banked(_) => {
569 return Err(TransformError::Unsupported(
570 "a banked alignment curve (centreline and cant law)",
571 ))
572 }
573 _ => return Err(TransformError::Unsupported("an unknown curve family")),
574 })
575}
576
577fn reflect_point(p: Point2) -> Point2 {
579 Point2::new(TAU - p.x, p.y)
580}
581
582fn reflect_vector(v: Vec2) -> Vec2 {
584 Vec2::new(-v.x, v.y)
585}
586
587fn reflect_conic_frame(frame: &Frame2) -> Frame2 {
590 Frame2 {
591 origin: reflect_point(frame.origin),
592 x: reflect_vector(frame.x),
593 y: -reflect_vector(frame.y),
594 }
595}
596
597fn curve2(
601 curve: &Curve2,
602 rigid: &Rigid,
603 reflect: bool,
604) -> Result<(Curve2, Reparam), TransformError> {
605 if let Curve2::Lifted(lifted) = curve {
606 rigid.unsupported_mirrored("a lifted pcurve under a reflection")?;
609 let (space, _) = curve3(&lifted.curve, rigid)?;
610 return Ok((
611 Curve2::Lifted(LiftedCurve2 {
612 curve: Box::new(space),
613 carrier: carrier(&lifted.carrier, rigid)?,
614 ..lifted.clone()
615 }),
616 Reparam::Same,
617 ));
618 }
619 if !reflect {
620 return Ok((curve.clone(), Reparam::Same));
621 }
622 Ok(match curve {
623 Curve2::Line(line) => (
624 Curve2::Line(Line2 {
625 origin: reflect_point(line.origin),
626 direction: reflect_vector(line.direction),
627 }),
628 Reparam::Same,
629 ),
630 Curve2::Circle(circle) => (
631 Curve2::Circle(Circle2 {
632 frame: reflect_conic_frame(&circle.frame),
633 ..*circle
634 }),
635 Reparam::Negated,
636 ),
637 Curve2::Ellipse(ellipse) => (
638 Curve2::Ellipse(Ellipse2 {
639 frame: reflect_conic_frame(&ellipse.frame),
640 ..*ellipse
641 }),
642 Reparam::Negated,
643 ),
644 Curve2::Polyline(polyline) => (
645 Curve2::Polyline(Polyline {
646 points: polyline.points.iter().map(|p| reflect_point(*p)).collect(),
647 closed: polyline.closed,
648 }),
649 Reparam::Same,
650 ),
651 Curve2::BSpline(spline) => (
652 Curve2::BSpline(BSplineCurve {
653 control_points: spline
654 .control_points
655 .iter()
656 .map(|p| reflect_point(*p))
657 .collect(),
658 ..spline.clone()
659 }),
660 Reparam::Same,
661 ),
662 Curve2::Sinusoid(wave) => (
665 Curve2::Sinusoid(Sinusoid2 {
666 sine: -wave.sine,
667 ..*wave
668 }),
669 Reparam::Reflected,
670 ),
671 _ => {
672 return Err(TransformError::Unsupported(
673 "a pcurve family on a curved face under a reflection",
674 ))
675 }
676 })
677}