1use ogeom_core::{Arena, Key, OgeomResult, Tolerance, Tolerances, ogeom_bail};
27use ogeom_geom::{Curve, PlanarCurve, SurfaceGeometry};
28use ogeom_math::Point;
29use smallvec::SmallVec;
30
31use crate::location::Location;
32use crate::tessellation::Triangulation;
33
34pub type CurveId = Key<Curve>;
36pub type PCurveId = Key<PlanarCurve>;
38pub type SurfaceId = Key<SurfaceGeometry>;
40
41pub type TriangulationId = Key<Triangulation>;
43
44#[derive(Debug, Clone, Default)]
46pub struct GeometryStore {
47 curves: Arena<Curve>,
48 pcurves: Arena<PlanarCurve>,
49 surfaces: Arena<SurfaceGeometry>,
50 triangulations: Arena<Triangulation>,
51}
52
53impl GeometryStore {
54 #[must_use]
56 pub const fn new() -> Self {
57 Self {
58 curves: Arena::new(),
59 pcurves: Arena::new(),
60 surfaces: Arena::new(),
61 triangulations: Arena::new(),
62 }
63 }
64
65 pub fn add_curve(&mut self, curve: Curve) -> CurveId {
67 self.curves.insert(curve)
68 }
69
70 pub fn add_pcurve(&mut self, curve: PlanarCurve) -> PCurveId {
72 self.pcurves.insert(curve)
73 }
74
75 pub fn add_surface(&mut self, surface: SurfaceGeometry) -> SurfaceId {
77 self.surfaces.insert(surface)
78 }
79
80 pub fn add_triangulation(&mut self, mesh: Triangulation) -> TriangulationId {
82 self.triangulations.insert(mesh)
83 }
84
85 #[must_use]
87 pub fn curve(&self, id: CurveId) -> Option<&Curve> {
88 self.curves.get(id)
89 }
90
91 #[must_use]
93 pub fn pcurve(&self, id: PCurveId) -> Option<&PlanarCurve> {
94 self.pcurves.get(id)
95 }
96
97 pub fn pcurve_mut(&mut self, id: PCurveId) -> Option<&mut PlanarCurve> {
100 self.pcurves.get_mut(id)
101 }
102
103 #[must_use]
105 pub fn surface(&self, id: SurfaceId) -> Option<&SurfaceGeometry> {
106 self.surfaces.get(id)
107 }
108
109 pub fn surface_mut(&mut self, id: SurfaceId) -> Option<&mut SurfaceGeometry> {
113 self.surfaces.get_mut(id)
114 }
115
116 #[must_use]
118 pub fn triangulation(&self, id: TriangulationId) -> Option<&Triangulation> {
119 self.triangulations.get(id)
120 }
121
122 #[must_use]
124 pub fn counts(&self) -> (usize, usize, usize) {
125 (self.curves.len(), self.pcurves.len(), self.surfaces.len())
126 }
127
128 pub(crate) const fn scopes(&self) -> GeometryScopes {
133 GeometryScopes {
134 curves: self.curves.scope(),
135 pcurves: self.pcurves.scope(),
136 surfaces: self.surfaces.scope(),
137 triangulations: self.triangulations.scope(),
138 }
139 }
140
141 pub(crate) fn is_dense(&self) -> bool {
146 self.curves.is_dense()
147 && self.pcurves.is_dense()
148 && self.surfaces.is_dense()
149 && self.triangulations.is_dense()
150 }
151
152 pub(crate) fn append(&mut self, other: Self) -> GeometryOffsets {
158 let offsets = GeometryOffsets {
159 curves: arena_len(&self.curves),
160 pcurves: arena_len(&self.pcurves),
161 surfaces: arena_len(&self.surfaces),
162 triangulations: arena_len(&self.triangulations),
163 };
164 for curve in other.curves.into_values() {
165 self.curves.insert(curve);
166 }
167 for pcurve in other.pcurves.into_values() {
168 self.pcurves.insert(pcurve);
169 }
170 for surface in other.surfaces.into_values() {
171 self.surfaces.insert(surface);
172 }
173 for mesh in other.triangulations.into_values() {
174 self.triangulations.insert(mesh);
175 }
176 offsets
177 }
178
179 #[must_use]
184 pub fn holds(&self, repr: &EdgeRepr) -> bool {
185 match repr {
186 EdgeRepr::Curve3d { curve, .. } => self.curve(*curve).is_some(),
187 EdgeRepr::PCurve { curve, surface, .. } => {
188 self.pcurve(*curve).is_some() && self.surface(*surface).is_some()
189 }
190 EdgeRepr::Seam {
191 forward,
192 reversed,
193 surface,
194 ..
195 } => {
196 self.pcurve(*forward).is_some()
197 && self.pcurve(*reversed).is_some()
198 && self.surface(*surface).is_some()
199 }
200 EdgeRepr::Polyline { .. } => true,
202 EdgeRepr::PolygonOnTriangulation { triangulation, .. } => {
203 self.triangulation(*triangulation).is_some()
204 }
205 }
206 }
207
208 pub fn curves(&self) -> impl Iterator<Item = (CurveId, &Curve)> {
210 self.curves.iter()
211 }
212
213 pub fn pcurves(&self) -> impl Iterator<Item = (PCurveId, &PlanarCurve)> {
215 self.pcurves.iter()
216 }
217
218 pub fn surfaces(&self) -> impl Iterator<Item = (SurfaceId, &SurfaceGeometry)> {
220 self.surfaces.iter()
221 }
222
223 pub fn triangulations(&self) -> impl Iterator<Item = (TriangulationId, &Triangulation)> {
225 self.triangulations.iter()
226 }
227
228 #[must_use]
230 pub fn triangulation_count(&self) -> usize {
231 self.triangulations.len()
232 }
233}
234
235#[derive(Debug, Clone, Copy)]
237pub(crate) struct GeometryScopes {
238 pub curves: u32,
239 pub pcurves: u32,
240 pub surfaces: u32,
241 pub triangulations: u32,
242}
243
244#[derive(Debug, Clone, Copy)]
247pub(crate) struct GeometryOffsets {
248 pub curves: u32,
249 pub pcurves: u32,
250 pub surfaces: u32,
251 pub triangulations: u32,
252}
253
254#[allow(clippy::expect_used, reason = "documented panic; see # Panics")]
261pub(crate) fn arena_len<T>(arena: &ogeom_core::Arena<T>) -> u32 {
262 u32::try_from(arena.len()).expect("arena exceeded u32::MAX slots")
263}
264
265pub(crate) fn key_is_unbound<T>(key: ogeom_core::Key<T>) -> bool {
268 key.scope() == ogeom_core::UNSCOPED && key.generation() == 0
269}
270
271#[allow(clippy::expect_used, reason = "documented panic; see # Panics")]
281pub(crate) fn shifted_key<T>(key: ogeom_core::Key<T>, offset: u32) -> ogeom_core::Key<T> {
282 ogeom_core::Key::from_parts(
283 key.index()
284 .checked_add(offset)
285 .expect("arena exceeded u32::MAX slots"),
286 key.generation(),
287 )
288}
289
290#[derive(Debug, Clone, PartialEq)]
295#[non_exhaustive]
296pub enum EdgeRepr {
297 Curve3d {
299 curve: CurveId,
301 location: Location,
303 range: (f64, f64),
305 },
306 PCurve {
308 curve: PCurveId,
310 surface: SurfaceId,
312 location: Location,
314 range: (f64, f64),
316 },
317 Seam {
324 forward: PCurveId,
326 reversed: PCurveId,
328 surface: SurfaceId,
330 location: Location,
332 range: (f64, f64),
334 },
335 PolygonOnTriangulation {
343 triangulation: TriangulationId,
345 indices: Vec<u32>,
347 location: Location,
349 },
350 Polyline {
356 points: Vec<Point>,
358 parameters: Vec<f64>,
366 location: Location,
368 deflection: f64,
370 },
371}
372
373impl EdgeRepr {
374 pub(crate) fn rebind(&mut self, geometry: &GeometryScopes, datums: u32) {
379 match self {
380 Self::Curve3d {
381 curve, location, ..
382 } => {
383 *curve = curve.with_scope(geometry.curves);
384 *location = location.with_datum_scope(datums);
385 }
386 Self::PCurve {
387 curve,
388 surface,
389 location,
390 ..
391 } => {
392 *curve = curve.with_scope(geometry.pcurves);
393 *surface = surface.with_scope(geometry.surfaces);
394 *location = location.with_datum_scope(datums);
395 }
396 Self::Seam {
397 forward,
398 reversed,
399 surface,
400 location,
401 ..
402 } => {
403 *forward = forward.with_scope(geometry.pcurves);
404 *reversed = reversed.with_scope(geometry.pcurves);
405 *surface = surface.with_scope(geometry.surfaces);
406 *location = location.with_datum_scope(datums);
407 }
408 Self::Polyline { location, .. } => {
409 *location = location.with_datum_scope(datums);
410 }
411 Self::PolygonOnTriangulation {
412 triangulation,
413 location,
414 ..
415 } => {
416 *triangulation = triangulation.with_scope(geometry.triangulations);
417 *location = location.with_datum_scope(datums);
418 }
419 }
420 }
421
422 pub(crate) fn shift(&mut self, geometry: &GeometryOffsets, datums: u32) {
428 match self {
429 Self::Curve3d {
430 curve, location, ..
431 } => {
432 *curve = shifted_key(*curve, geometry.curves);
433 *location = location.with_datum_offset(datums);
434 }
435 Self::PCurve {
436 curve,
437 surface,
438 location,
439 ..
440 } => {
441 *curve = shifted_key(*curve, geometry.pcurves);
442 *surface = shifted_key(*surface, geometry.surfaces);
443 *location = location.with_datum_offset(datums);
444 }
445 Self::Seam {
446 forward,
447 reversed,
448 surface,
449 location,
450 ..
451 } => {
452 *forward = shifted_key(*forward, geometry.pcurves);
453 *reversed = shifted_key(*reversed, geometry.pcurves);
454 *surface = shifted_key(*surface, geometry.surfaces);
455 *location = location.with_datum_offset(datums);
456 }
457 Self::Polyline { location, .. } => {
458 *location = location.with_datum_offset(datums);
459 }
460 Self::PolygonOnTriangulation {
461 triangulation,
462 location,
463 ..
464 } => {
465 *triangulation = shifted_key(*triangulation, geometry.triangulations);
466 *location = location.with_datum_offset(datums);
467 }
468 }
469 }
470
471 pub(crate) fn is_unbound(&self) -> bool {
474 let local_location = |location: &Location| {
475 location
476 .chain()
477 .iter()
478 .all(|&(datum, _)| key_is_unbound(datum))
479 };
480 match self {
481 Self::Curve3d {
482 curve, location, ..
483 } => key_is_unbound(*curve) && local_location(location),
484 Self::PCurve {
485 curve,
486 surface,
487 location,
488 ..
489 } => key_is_unbound(*curve) && key_is_unbound(*surface) && local_location(location),
490 Self::Seam {
491 forward,
492 reversed,
493 surface,
494 location,
495 ..
496 } => {
497 key_is_unbound(*forward)
498 && key_is_unbound(*reversed)
499 && key_is_unbound(*surface)
500 && local_location(location)
501 }
502 Self::Polyline { location, .. } => local_location(location),
503 Self::PolygonOnTriangulation {
504 triangulation,
505 location,
506 ..
507 } => key_is_unbound(*triangulation) && local_location(location),
508 }
509 }
510
511 #[must_use]
513 pub const fn surface(&self) -> Option<SurfaceId> {
514 match self {
515 Self::PCurve { surface, .. } | Self::Seam { surface, .. } => Some(*surface),
516 Self::Curve3d { .. } | Self::Polyline { .. } | Self::PolygonOnTriangulation { .. } => {
517 None
518 }
519 }
520 }
521
522 #[must_use]
524 pub const fn location(&self) -> Option<&Location> {
525 match self {
526 Self::Curve3d { location, .. }
527 | Self::PCurve { location, .. }
528 | Self::Seam { location, .. }
529 | Self::Polyline { location, .. }
530 | Self::PolygonOnTriangulation { location, .. } => Some(location),
531 }
532 }
533
534 #[must_use]
536 pub const fn range(&self) -> Option<(f64, f64)> {
537 match self {
538 Self::Curve3d { range, .. } | Self::PCurve { range, .. } | Self::Seam { range, .. } => {
539 Some(*range)
540 }
541 Self::Polyline { .. } | Self::PolygonOnTriangulation { .. } => None,
542 }
543 }
544
545 #[must_use]
547 pub const fn is_curve3d(&self) -> bool {
548 matches!(self, Self::Curve3d { .. })
549 }
550
551 #[must_use]
553 pub const fn is_parametric(&self) -> bool {
554 matches!(self, Self::PCurve { .. } | Self::Seam { .. })
555 }
556}
557
558#[derive(Debug, Clone, PartialEq)]
560pub struct VertexData {
561 pub point: Point,
563 pub tolerance: Tolerance,
565}
566
567impl VertexData {
568 #[must_use]
570 pub fn new(point: Point) -> Self {
571 Self {
572 point,
573 tolerance: Tolerance::MIN,
574 }
575 }
576
577 pub fn with_tolerance(point: Point, tolerance: f64) -> OgeomResult<Self> {
584 if !point.is_finite() {
585 ogeom_bail!(Construction, "vertex position is not finite");
586 }
587 Ok(Self {
588 point,
589 tolerance: Tolerance::new(tolerance)?,
590 })
591 }
592
593 pub fn widen(&mut self, to: Tolerance) {
595 self.tolerance = self.tolerance.widen(to);
596 }
597}
598
599#[derive(Debug, Clone, PartialEq)]
602pub struct EdgeData {
603 pub tolerance: Tolerance,
605 pub representations: SmallVec<[EdgeRepr; 3]>,
607 same_parameter: bool,
611 pub degenerate: bool,
617}
618
619impl EdgeData {
620 #[must_use]
622 pub fn new() -> Self {
623 Self {
624 tolerance: Tolerance::MIN,
625 representations: SmallVec::new(),
626 same_parameter: true,
627 degenerate: false,
628 }
629 }
630
631 #[must_use]
633 pub fn on_curve(curve: CurveId, location: Location, range: (f64, f64)) -> Self {
634 let mut edge = Self::new();
635 edge.representations.push(EdgeRepr::Curve3d {
636 curve,
637 location,
638 range,
639 });
640 edge
641 }
642
643 pub fn add(&mut self, repr: EdgeRepr) {
649 self.representations.push(repr);
650 self.same_parameter = false;
651 }
652
653 #[must_use]
664 pub const fn same_parameter(&self) -> bool {
665 self.same_parameter
666 }
667
668 pub const fn assert_same_parameter(&mut self, agrees: bool) {
676 self.same_parameter = agrees;
677 }
678
679 #[must_use]
681 pub fn curve3d(&self) -> Option<&EdgeRepr> {
682 self.representations.iter().find(|r| r.is_curve3d())
683 }
684
685 #[must_use]
687 pub fn pcurve_on(&self, surface: SurfaceId) -> Option<&EdgeRepr> {
688 self.representations
689 .iter()
690 .find(|r| r.surface() == Some(surface))
691 }
692
693 #[must_use]
705 pub fn pcurve_for(&self, surface: SurfaceId, location: &Location) -> Option<&EdgeRepr> {
706 self.representations
707 .iter()
708 .find(|r| r.surface() == Some(surface) && r.location() == Some(location))
709 .or_else(|| {
710 self.representations
714 .iter()
715 .filter(|r| {
716 r.surface() == Some(surface)
717 && r.location()
718 .is_some_and(|l| !l.is_identity() && location.ends_with(l))
719 })
720 .max_by_key(|r| r.location().map_or(0, Location::depth))
721 })
722 .or_else(|| {
723 self.representations.iter().find(|r| {
724 r.surface() == Some(surface) && r.location().is_some_and(Location::is_identity)
725 })
726 })
727 }
728
729 #[must_use]
731 pub fn parametric_surfaces(&self) -> Vec<SurfaceId> {
732 self.representations
733 .iter()
734 .filter_map(EdgeRepr::surface)
735 .collect()
736 }
737
738 pub fn widen(&mut self, to: Tolerance) {
740 self.tolerance = self.tolerance.widen(to);
741 }
742}
743
744impl Default for EdgeData {
745 fn default() -> Self {
746 Self::new()
747 }
748}
749
750#[derive(Debug, Clone, PartialEq)]
752pub struct FaceData {
753 pub surface: SurfaceId,
755 pub location: Location,
757 pub tolerance: Tolerance,
759 pub natural_restriction: bool,
766 pub triangulation: Option<TriangulationId>,
772}
773
774impl FaceData {
775 #[must_use]
777 pub fn new(surface: SurfaceId, location: Location) -> Self {
778 Self {
779 surface,
780 location,
781 tolerance: Tolerance::MIN,
782 natural_restriction: false,
783 triangulation: None,
784 }
785 }
786
787 #[must_use]
789 pub fn natural(surface: SurfaceId, location: Location) -> Self {
790 Self {
791 natural_restriction: true,
792 ..Self::new(surface, location)
793 }
794 }
795
796 pub fn widen(&mut self, to: Tolerance) {
798 self.tolerance = self.tolerance.widen(to);
799 }
800}
801
802#[derive(Debug, Clone, PartialEq)]
808pub enum NodeData {
809 Vertex(VertexData),
811 Edge(Box<EdgeData>),
813 Face(Box<FaceData>),
815 Container,
818}
819
820impl NodeData {
821 #[must_use]
826 pub fn tolerance(&self) -> Option<Tolerance> {
827 match self {
828 Self::Vertex(v) => Some(v.tolerance),
829 Self::Edge(e) => Some(e.tolerance),
830 Self::Face(f) => Some(f.tolerance),
831 Self::Container => None,
832 }
833 }
834
835 pub fn widen(&mut self, to: Tolerance) {
837 match self {
838 Self::Vertex(v) => v.widen(to),
839 Self::Edge(e) => e.widen(to),
840 Self::Face(f) => f.widen(to),
841 Self::Container => {}
842 }
843 }
844
845 #[must_use]
847 pub const fn as_vertex(&self) -> Option<&VertexData> {
848 match self {
849 Self::Vertex(v) => Some(v),
850 _ => None,
851 }
852 }
853
854 #[must_use]
856 pub const fn as_edge(&self) -> Option<&EdgeData> {
857 match self {
858 Self::Edge(e) => Some(e),
859 _ => None,
860 }
861 }
862
863 #[must_use]
865 pub const fn as_face(&self) -> Option<&FaceData> {
866 match self {
867 Self::Face(f) => Some(f),
868 _ => None,
869 }
870 }
871}
872
873pub fn check_containment(bounding: Tolerance, bounded: Tolerance) -> OgeomResult<()> {
886 ogeom_core::check_containment(bounding, bounded)
887}
888
889#[must_use]
895pub fn enforce_containment(bounding: Tolerance, bounded: Tolerance) -> Tolerance {
896 bounding.widen(bounded)
897}
898
899pub fn check_range(range: (f64, f64), tol: Tolerances) -> OgeomResult<()> {
906 let (a, b) = range;
907 if !a.is_finite() || !b.is_finite() || b <= a + tol.parametric() {
908 ogeom_bail!(
909 Construction,
910 "parameter range [{a}, {b}] is empty or non-finite"
911 );
912 }
913 Ok(())
914}
915
916#[cfg(test)]
917#[allow(clippy::unwrap_used)]
918mod tests {
919 use super::*;
920 use ogeom_geom::{CircleCurve, LineCurve, PlaneSurface};
921 use ogeom_math::{Circle, Direction, Frame, Plane};
922
923 const T: Tolerances = Tolerances::millimetres();
924
925 fn store() -> (GeometryStore, CurveId, SurfaceId, PCurveId) {
926 let mut s = GeometryStore::new();
927 let curve = s.add_curve(
928 LineCurve::segment(Point::ORIGIN, Point::new(10.0, 0.0, 0.0), T)
929 .unwrap()
930 .into(),
931 );
932 let surface = s.add_surface(PlaneSurface::new(Plane::new(Frame::WORLD)).into());
933 let pcurve = s.add_pcurve(
934 ogeom_geom::Line2d::segment(
935 ogeom_math::Point2::ORIGIN,
936 ogeom_math::Point2::new(10.0, 0.0),
937 T,
938 )
939 .unwrap()
940 .into(),
941 );
942 (s, curve, surface, pcurve)
943 }
944
945 #[test]
946 fn the_geometry_store_hands_back_what_it_was_given() {
947 let (s, curve, surface, pcurve) = store();
948 assert!(s.curve(curve).is_some());
949 assert!(s.surface(surface).is_some());
950 assert!(s.pcurve(pcurve).is_some());
951 assert_eq!(s.counts(), (1, 1, 1));
952
953 let mut other = GeometryStore::new();
959 let mut beyond = curve;
960 for _ in 0..5 {
961 beyond = other.add_curve(
962 LineCurve::segment(Point::ORIGIN, Point::new(1.0, 0.0, 0.0), T)
963 .unwrap()
964 .into(),
965 );
966 }
967 assert!(
968 s.curve(beyond).is_none(),
969 "a handle past the end does not resolve"
970 );
971 }
972
973 #[test]
974 fn tolerances_start_at_the_minimum_and_only_widen() {
975 let mut v = VertexData::new(Point::ORIGIN);
976 assert_eq!(v.tolerance, Tolerance::MIN);
977
978 let wide = Tolerance::new(1e-3).unwrap();
979 v.widen(wide);
980 assert_eq!(v.tolerance, wide);
981
982 v.widen(Tolerance::new(1e-9).unwrap());
986 assert_eq!(v.tolerance, wide);
987 }
988
989 #[test]
990 fn degenerate_vertex_data_is_refused() {
991 assert!(VertexData::with_tolerance(Point::ORIGIN, -1.0).is_err());
992 assert!(VertexData::with_tolerance(Point::ORIGIN, f64::NAN).is_err());
993 assert!(VertexData::with_tolerance(Point::new(f64::INFINITY, 0.0, 0.0), 1e-6).is_err());
994 assert!(VertexData::with_tolerance(Point::ORIGIN, 1e-3).is_ok());
995 }
996
997 #[test]
998 fn the_containment_rule_holds_downward_and_is_repaired_upward() {
999 let fine = Tolerance::new(1e-6).unwrap();
1000 let coarse = Tolerance::new(1e-3).unwrap();
1001
1002 assert!(
1003 check_containment(coarse, fine).is_ok(),
1004 "vertex coarser than edge"
1005 );
1006 assert!(
1007 check_containment(fine, coarse).is_err(),
1008 "and not the other way"
1009 );
1010
1011 let repaired = enforce_containment(fine, coarse);
1013 assert_eq!(repaired, coarse);
1014 assert!(check_containment(repaired, coarse).is_ok());
1015 }
1016
1017 #[test]
1018 fn an_edge_holds_several_representations_at_once() {
1019 let (mut s, curve, surface, pcurve) = store();
1023 let other_surface =
1024 s.add_surface(PlaneSurface::new(Plane::through(Point::ORIGIN, Direction::Y)).into());
1025 let other_pcurve = s.add_pcurve(
1026 ogeom_geom::Line2d::segment(
1027 ogeom_math::Point2::ORIGIN,
1028 ogeom_math::Point2::new(0.0, 10.0),
1029 T,
1030 )
1031 .unwrap()
1032 .into(),
1033 );
1034
1035 let mut edge = EdgeData::on_curve(curve, Location::identity(), (0.0, 10.0));
1036 edge.add(EdgeRepr::PCurve {
1037 curve: pcurve,
1038 surface,
1039 location: Location::identity(),
1040 range: (0.0, 10.0),
1041 });
1042 edge.add(EdgeRepr::PCurve {
1043 curve: other_pcurve,
1044 surface: other_surface,
1045 location: Location::identity(),
1046 range: (0.0, 10.0),
1047 });
1048
1049 assert_eq!(edge.representations.len(), 3);
1050 assert!(edge.curve3d().is_some());
1051 assert!(edge.pcurve_on(surface).is_some());
1052 assert!(edge.pcurve_on(other_surface).is_some());
1053 assert_eq!(edge.parametric_surfaces().len(), 2);
1054 }
1055
1056 #[test]
1057 fn adding_a_representation_withdraws_the_same_parameter_claim() {
1058 let (_, curve, surface, pcurve) = store();
1063 let mut edge = EdgeData::on_curve(curve, Location::identity(), (0.0, 10.0));
1064 assert!(
1065 edge.same_parameter(),
1066 "a lone curve trivially agrees with itself"
1067 );
1068
1069 edge.add(EdgeRepr::PCurve {
1070 curve: pcurve,
1071 surface,
1072 location: Location::identity(),
1073 range: (0.0, 10.0),
1074 });
1075 assert!(
1076 !edge.same_parameter(),
1077 "the new representation is unverified"
1078 );
1079
1080 edge.assert_same_parameter(true);
1081 assert!(edge.same_parameter());
1082 }
1083
1084 #[test]
1085 fn a_seam_carries_two_pcurves_because_one_cannot_express_both_sides() {
1086 let mut s = GeometryStore::new();
1090 let cylinder = s.add_surface(
1091 ogeom_geom::CylinderSurface::new(
1092 ogeom_math::Cylinder::new(Frame::WORLD, 2.0, T).unwrap(),
1093 (0.0, 5.0),
1094 )
1095 .unwrap()
1096 .into(),
1097 );
1098 let at_zero = s.add_pcurve(
1099 ogeom_geom::Line2d::segment(
1100 ogeom_math::Point2::ORIGIN,
1101 ogeom_math::Point2::new(0.0, 5.0),
1102 T,
1103 )
1104 .unwrap()
1105 .into(),
1106 );
1107 let at_tau = s.add_pcurve(
1108 ogeom_geom::Line2d::segment(
1109 ogeom_math::Point2::new(core::f64::consts::TAU, 0.0),
1110 ogeom_math::Point2::new(core::f64::consts::TAU, 5.0),
1111 T,
1112 )
1113 .unwrap()
1114 .into(),
1115 );
1116
1117 let mut edge = EdgeData::new();
1118 edge.add(EdgeRepr::Seam {
1119 forward: at_zero,
1120 reversed: at_tau,
1121 surface: cylinder,
1122 location: Location::identity(),
1123 range: (0.0, 5.0),
1124 });
1125
1126 let seam = &edge.representations[0];
1127 assert!(seam.is_parametric());
1128 assert_eq!(seam.surface(), Some(cylinder));
1129 assert_eq!(seam.range(), Some((0.0, 5.0)));
1130 assert!(matches!(seam, EdgeRepr::Seam { .. }));
1131 }
1132
1133 #[test]
1134 fn a_polyline_representation_records_what_it_was_built_to() {
1135 let repr = EdgeRepr::Polyline {
1139 points: vec![Point::ORIGIN, Point::new(1.0, 0.0, 0.0)],
1140 parameters: vec![0.0, 1.0],
1141 location: Location::identity(),
1142 deflection: 1e-3,
1143 };
1144 assert!(!repr.is_curve3d() && !repr.is_parametric());
1145 assert_eq!(repr.surface(), None);
1146 assert_eq!(repr.range(), None, "a polyline has no parameter range");
1147 }
1148
1149 #[test]
1150 fn a_degenerate_edge_is_marked_rather_than_dropped() {
1151 let mut edge = EdgeData::new();
1154 edge.degenerate = true;
1155 assert!(edge.degenerate);
1156 assert!(edge.representations.is_empty());
1157 }
1158
1159 #[test]
1160 fn a_natural_face_covers_its_whole_surface() {
1161 let (_, _, surface, _) = store();
1162 let trimmed = FaceData::new(surface, Location::identity());
1163 let whole = FaceData::natural(surface, Location::identity());
1164 assert!(!trimmed.natural_restriction);
1165 assert!(whole.natural_restriction);
1166 assert_eq!(whole.tolerance, Tolerance::MIN);
1167 }
1168
1169 #[test]
1170 fn node_data_exposes_only_what_it_holds() {
1171 let vertex = NodeData::Vertex(VertexData::new(Point::ORIGIN));
1172 let edge = NodeData::Edge(Box::default());
1173 let container = NodeData::Container;
1174
1175 assert!(vertex.as_vertex().is_some());
1176 assert!(vertex.as_edge().is_none());
1177 assert!(edge.as_edge().is_some());
1178 assert!(edge.as_face().is_none());
1179
1180 assert_eq!(container.tolerance(), None);
1184 assert_eq!(vertex.tolerance(), Some(Tolerance::MIN));
1185 }
1186
1187 #[test]
1188 fn widening_a_container_is_a_no_op_rather_than_an_error() {
1189 let mut container = NodeData::Container;
1193 container.widen(Tolerance::new(1e-3).unwrap());
1194 assert_eq!(container.tolerance(), None);
1195 }
1196
1197 #[test]
1198 fn empty_parameter_ranges_are_refused() {
1199 assert!(check_range((0.0, 1.0), T).is_ok());
1200 assert!(check_range((1.0, 0.0), T).is_err());
1201 assert!(check_range((1.0, 1.0), T).is_err());
1202 assert!(check_range((0.0, f64::NAN), T).is_err());
1203 }
1204
1205 #[test]
1206 fn a_circular_edge_can_carry_its_own_curve() {
1207 let mut s = GeometryStore::new();
1208 let circle =
1209 s.add_curve(CircleCurve::new(Circle::new(Frame::WORLD, 3.0, T).unwrap()).into());
1210 let edge = EdgeData::on_curve(circle, Location::identity(), (0.0, core::f64::consts::TAU));
1211 assert!(edge.curve3d().is_some());
1212 assert_eq!(
1213 edge.curve3d().unwrap().range(),
1214 Some((0.0, core::f64::consts::TAU))
1215 );
1216 assert!(edge.parametric_surfaces().is_empty());
1217 }
1218}