1use axiolid_brep::ExactBRep;
10use axiolid_core::{Point2, Point3, Scalar, Vec2};
11use axiolid_curve::Curve2;
12use axiolid_evaluate::surface::evaluate;
13use axiolid_evaluate::{derivative2, evaluate2};
14use axiolid_surface::Surface;
15use axiolid_topology::{Face, Orientation};
16use core::f64::consts::{FRAC_PI_2, TAU};
17
18use crate::exact::ExactMeasureError;
19
20pub(crate) struct Chart {
22 pub(crate) u_period: Option<Scalar>,
23 pub(crate) v_period: Option<Scalar>,
24 pub(crate) poles: Vec<Scalar>,
26}
27
28pub(crate) fn chart(surface: &Surface) -> Result<Chart, ExactMeasureError> {
29 let chart = match surface {
30 Surface::Plane(_) | Surface::BSpline(_) => Chart {
31 u_period: None,
32 v_period: None,
33 poles: Vec::new(),
34 },
35 Surface::Cylinder(_) | Surface::EllipticalCylinder(_) => Chart {
36 u_period: Some(TAU),
37 v_period: None,
38 poles: Vec::new(),
39 },
40 Surface::Cone(cone) => {
41 let slope = cone.semi_angle.tan();
42 let poles = if slope.is_finite() && slope != 0.0 {
43 vec![-cone.radius / slope]
44 } else {
45 Vec::new()
46 };
47 Chart {
48 u_period: Some(TAU),
49 v_period: None,
50 poles,
51 }
52 }
53 Surface::Sphere(_) => Chart {
54 u_period: Some(TAU),
55 v_period: None,
56 poles: vec![-FRAC_PI_2, FRAC_PI_2],
57 },
58 Surface::Torus(_) => Chart {
59 u_period: Some(TAU),
60 v_period: Some(TAU),
61 poles: Vec::new(),
62 },
63 _ => {
64 return Err(ExactMeasureError::NonPlanarFace(crate::exact::family(
65 surface,
66 )))
67 }
68 };
69 Ok(chart)
70}
71
72pub(crate) enum Piece<'a> {
74 Curve {
76 curve: &'a Curve2,
77 start: Scalar,
78 end: Scalar,
79 offset: Vec2,
80 },
81 Segment { from: Point2, to: Point2 },
83}
84
85impl Piece<'_> {
86 pub(crate) fn span(&self) -> (Scalar, Scalar) {
87 match self {
88 Self::Curve { start, end, .. } => (*start, *end),
89 Self::Segment { .. } => (0.0, 1.0),
90 }
91 }
92
93 pub(crate) fn smooth_spans(&self) -> Vec<(Scalar, Scalar)> {
97 let (a, b) = self.span();
98 let mut cuts = vec![a];
99 let kinked = match self {
100 Self::Curve {
101 curve: Curve2::Implicit(_),
102 ..
103 } => true,
104 Self::Curve {
105 curve: Curve2::Lifted(l),
106 ..
107 } => matches!(
108 l.curve.as_ref(),
109 axiolid_curve::Curve3::ImplicitSection(_) | axiolid_curve::Curve3::PairSection(_)
110 ),
111 _ => false,
112 };
113 if kinked {
114 let (lo, hi) = (a.min(b), a.max(b));
115 let mut k = lo.floor() + 1.0;
116 let mut inner = Vec::new();
117 while k < hi {
118 inner.push(k);
119 k += 1.0;
120 }
121 if b < a {
122 inner.reverse();
123 }
124 cuts.extend(inner);
125 }
126 cuts.push(b);
127 cuts.windows(2).map(|w| (w[0], w[1])).collect()
128 }
129
130 pub(crate) fn at(&self, t: Scalar) -> Result<(Point2, Vec2), ExactMeasureError> {
131 match self {
132 Self::Curve { curve, offset, .. } => {
133 let point = evaluate2(curve, t).map_err(|_| crate::exact::EVALUATION)?;
134 let tangent = derivative2(curve, t).map_err(|_| crate::exact::EVALUATION)?;
135 Ok((point + *offset, tangent))
136 }
137 Self::Segment { from, to } => Ok((*from + (*to - *from) * t, *to - *from)),
138 }
139 }
140
141 pub(crate) fn end_point(&self) -> Result<Point2, ExactMeasureError> {
142 Ok(self.at(self.span().1)?.0)
143 }
144}
145
146pub(crate) struct Boundary<'a> {
148 pub(crate) pieces: Vec<Piece<'a>>,
149 pub(crate) winding: [i64; 2],
151 pub(crate) wraps: [bool; 2],
153 pub(crate) anchor: Point2,
155}
156
157pub(crate) fn pole_on_domain_side(
160 chart: &Chart,
161 boundary: &Boundary<'_>,
162) -> Result<Scalar, ExactMeasureError> {
163 let upward = match boundary.winding[0] {
165 1 => true,
166 -1 => false,
167 _ => {
168 return Err(ExactMeasureError::NonPlanarFace(
169 "face boundary winds around the surface more than once",
170 ))
171 }
172 };
173 chart
174 .poles
175 .iter()
176 .copied()
177 .filter(|pole| (*pole > boundary.anchor.y) == upward)
178 .min_by(|a, b| {
179 (a - boundary.anchor.y)
180 .abs()
181 .total_cmp(&(b - boundary.anchor.y).abs())
182 })
183 .ok_or(ExactMeasureError::NonPlanarFace(
184 "face boundary winds around a surface with no pole on the domain side",
185 ))
186}
187
188pub(crate) fn assemble<'a>(
191 brep: &'a ExactBRep,
192 face: &Face<axiolid_brep::SurfaceId>,
193 surface: &Surface,
194 chart: &Chart,
195 linear: Scalar,
196) -> Result<Boundary<'a>, ExactMeasureError> {
197 let topology = brep.topology();
198 let mut pieces = Vec::new();
199 let mut winding = [0_i64; 2];
200 let mut wraps = [false; 2];
201 let mut anchor = None;
202
203 for bound in &face.bounds {
204 let wire = topology
205 .loops()
206 .get(bound.loop_id.index())
207 .ok_or(ExactMeasureError::DanglingReference)?;
208 let reversed = bound.orientation == Orientation::Reversed;
209
210 let mut uses = Vec::with_capacity(wire.edges.len());
213 for (index, use_) in wire.edges.iter().enumerate() {
214 let pcurve = use_.pcurve.ok_or(ExactMeasureError::DanglingReference)?;
215 let curve = brep
216 .curves2()
217 .get(pcurve.index())
218 .ok_or(ExactMeasureError::DanglingReference)?;
219 let interval = brep
220 .pcurve_interval(bound.loop_id, index)
221 .ok_or(ExactMeasureError::DanglingReference)?;
222 let (start, end) = if reversed {
223 (interval.end, interval.start)
224 } else {
225 (interval.start, interval.end)
226 };
227 uses.push((curve, start, end));
228 }
229 if reversed {
230 uses.reverse();
231 }
232 let Some(&(first_curve, first_start, _)) = uses.first() else {
233 continue;
234 };
235
236 let loop_start =
237 evaluate2(first_curve, first_start).map_err(|_| crate::exact::EVALUATION)?;
238 anchor.get_or_insert(loop_start);
239 let mut offset = Vec2::ZERO;
240 let mut cursor = loop_start;
241 for (curve, start, end) in uses {
242 let raw = evaluate2(curve, start).map_err(|_| crate::exact::EVALUATION)?;
243 let (shift, bridge) = join(surface, chart, cursor, raw + offset, linear)?;
244 offset += shift;
245 if let Some(segment) = bridge {
246 pieces.push(segment);
247 }
248 let piece = Piece::Curve {
249 curve,
250 start,
251 end,
252 offset,
253 };
254 cursor = piece.end_point()?;
255 pieces.push(piece);
256 }
257
258 let (shift, bridge) = join(surface, chart, cursor, loop_start, linear)?;
260 if let Some(segment) = bridge {
261 pieces.push(segment);
262 }
263 let turns = [
266 periods(shift.x, chart.u_period),
267 periods(shift.y, chart.v_period),
268 ];
269 for axis in 0..2 {
270 winding[axis] += turns[axis];
271 wraps[axis] |= turns[axis] != 0;
272 }
273 }
274
275 let anchor = anchor.ok_or(ExactMeasureError::Degenerate)?;
276 Ok(Boundary {
277 pieces,
278 winding,
279 wraps,
280 anchor,
281 })
282}
283
284fn periods(shift: Scalar, period: Option<Scalar>) -> i64 {
286 period.map_or(0, |period| (shift / period).round() as i64)
287}
288
289fn join<'a>(
296 surface: &Surface,
297 chart: &Chart,
298 from: Point2,
299 to: Point2,
300 linear: Scalar,
301) -> Result<(Vec2, Option<Piece<'a>>), ExactMeasureError> {
302 let there = point(surface, from)?;
303 let here = point(surface, to)?;
304 if (there - here).length() > linear {
305 return Err(ExactMeasureError::NonPlanarFace(
306 "consecutive pcurves of a face loop do not meet on the surface",
307 ));
308 }
309
310 let on_pole = chart.poles.iter().any(|pole| {
313 (from.y - pole).abs() <= parameter_slack(*pole)
314 && (to.y - pole).abs() <= parameter_slack(*pole)
315 });
316 let shift = if on_pole {
317 Vec2::ZERO
318 } else {
319 let wrap = |gap: Scalar, period: Option<Scalar>| {
320 period.map_or(0.0, |period| (gap / period).round() * period)
321 };
322 Vec2::new(
323 wrap(from.x - to.x, chart.u_period),
324 wrap(from.y - to.y, chart.v_period),
325 )
326 };
327 let landed = to + shift;
328 let bridge = (landed != from).then_some(Piece::Segment { from, to: landed });
329 Ok((shift, bridge))
330}
331
332fn parameter_slack(value: Scalar) -> Scalar {
333 1e-9 * value.abs().max(1.0)
334}
335
336pub(crate) fn point(surface: &Surface, at: Point2) -> Result<Point3, ExactMeasureError> {
337 evaluate(surface, at.x, at.y).map_err(|_| crate::exact::EVALUATION)
338}
339
340struct MonoArc {
346 piece: usize,
347 t0: Scalar,
348 t1: Scalar,
349 a: Point2,
350 b: Point2,
351}
352
353pub(crate) struct Domain<'a> {
358 boundary: Boundary<'a>,
359 arcs: Vec<MonoArc>,
360 transpose: bool,
363 period: Option<Scalar>,
365 across: Option<Scalar>,
370 pole: Option<(Scalar, i64)>,
372 pub(crate) min: Point2,
374 pub(crate) max: Point2,
375}
376
377fn turning_points(curve: &Curve2, lo: Scalar, hi: Scalar) -> Option<Vec<Scalar>> {
379 let mut out = Vec::new();
380 let mut add_trig = |a: Scalar, b: Scalar| {
381 if a == 0.0 && b == 0.0 {
383 return;
384 }
385 let base = b.atan2(a);
386 let first = ((lo - base) / core::f64::consts::PI).floor() as i64;
387 let last = ((hi - base) / core::f64::consts::PI).ceil() as i64;
388 for k in first..=last {
389 let t = base + k as Scalar * core::f64::consts::PI;
390 if t > lo && t < hi {
391 out.push(t);
392 }
393 }
394 };
395 match curve {
396 Curve2::Line(_) => {}
397 Curve2::Circle(c) => {
398 add_trig(c.radius * c.frame.x.x, c.radius * c.frame.y.x);
399 add_trig(c.radius * c.frame.x.y, c.radius * c.frame.y.y);
400 }
401 Curve2::Ellipse(e) => {
402 add_trig(e.semi_axis_x * e.frame.x.x, e.semi_axis_y * e.frame.y.x);
403 add_trig(e.semi_axis_x * e.frame.x.y, e.semi_axis_y * e.frame.y.y);
404 }
405 Curve2::Sinusoid(w) => add_trig(w.cosine, w.sine),
406 Curve2::Polyline(_) => {
407 let mut k = lo.floor() + 1.0;
408 while k < hi {
409 out.push(k);
410 k += 1.0;
411 }
412 }
413 Curve2::Implicit(c) => out.extend(c.turning_points(lo, hi)),
416 Curve2::BSpline(b) => {
419 out.extend(b.knots.iter().copied().filter(|k| *k > lo && *k < hi));
420 out.extend(scanned_turns(curve, lo, hi)?);
421 }
422 Curve2::Lifted(l) => {
426 if let axiolid_curve::Curve3::ImplicitSection(_)
427 | axiolid_curve::Curve3::PairSection(_) = l.curve.as_ref()
428 {
429 let mut k = lo.floor() + 1.0;
430 while k < hi {
431 out.push(k);
432 k += 1.0;
433 }
434 }
435 out.extend(scanned_turns(curve, lo, hi)?);
436 }
437 _ => return None,
438 }
439 out.sort_by(Scalar::total_cmp);
440 Some(out)
441}
442
443fn scanned_turns(curve: &Curve2, lo: Scalar, hi: Scalar) -> Option<Vec<Scalar>> {
447 let n = 256;
448 let d = |t: Scalar| derivative2(curve, t).ok();
449 let mut out = Vec::new();
450 let mut previous = (lo, d(lo + (hi - lo) * 1e-9)?);
451 for i in 1..=n {
452 let t = if i == n {
453 hi - (hi - lo) * 1e-9
454 } else {
455 lo + (hi - lo) * i as Scalar / n as Scalar
456 };
457 let now = d(t)?;
458 for axis in 0..2 {
459 let (a, b) = (previous.1[axis], now[axis]);
460 if (a < 0.0) != (b < 0.0) && a != 0.0 && b != 0.0 {
461 let (mut x0, mut x1) = (previous.0, t);
462 for _ in 0..80 {
463 let m = 0.5 * (x0 + x1);
464 let dm = d(m)?[axis];
465 if (dm < 0.0) == (a < 0.0) {
466 x0 = m;
467 } else {
468 x1 = m;
469 }
470 }
471 out.push(0.5 * (x0 + x1));
472 }
473 }
474 previous = (t, now);
475 }
476 Some(out)
477}
478
479fn slack(value: Scalar) -> Scalar {
480 1e-9 * (1.0 + value.abs())
481}
482
483impl<'a> Domain<'a> {
484 pub(crate) fn new(
488 brep: &'a ExactBRep,
489 face: &Face<axiolid_brep::SurfaceId>,
490 surface: &Surface,
491 linear: Scalar,
492 ) -> Result<Option<Self>, ExactMeasureError> {
493 let chart = chart(surface)?;
494 let boundary = assemble(brep, face, surface, &chart, linear)?;
495 let transpose = boundary.wraps[1];
496 if transpose && (boundary.wraps[0] || boundary.winding[1] != 0) {
497 return Err(ExactMeasureError::NonPlanarFace(
498 "face boundary winds around the surface in both directions",
499 ));
500 }
501 let pole = if !transpose && boundary.winding[0] != 0 {
502 Some((pole_on_domain_side(&chart, &boundary)?, boundary.winding[0]))
503 } else {
504 None
505 };
506 let swap = |p: Point2| if transpose { Point2::new(p.y, p.x) } else { p };
507
508 let mut arcs = Vec::new();
509 for (index, piece) in boundary.pieces.iter().enumerate() {
510 let (start, end) = piece.span();
511 let (lo, hi) = (start.min(end), start.max(end));
512 let mut cuts = match piece {
513 Piece::Curve { curve, .. } => match turning_points(curve, lo, hi) {
514 Some(cuts) => cuts,
515 None => return Ok(None),
516 },
517 Piece::Segment { .. } => Vec::new(),
518 };
519 if start > end {
520 cuts.reverse();
521 }
522 let mut ts = vec![start];
523 ts.extend(cuts);
524 ts.push(end);
525 for pair in ts.windows(2) {
526 let a = swap(piece.at(pair[0])?.0);
527 let b = swap(piece.at(pair[1])?.0);
528 arcs.push(MonoArc {
529 piece: index,
530 t0: pair[0],
531 t1: pair[1],
532 a,
533 b,
534 });
535 }
536 }
537 if arcs.is_empty() {
538 return Ok(None);
539 }
540 let mut vertices: Vec<Point2> = Vec::new();
545 let mut snap = |p: Point2| -> Point2 {
546 let near =
547 |q: &Point2| (q.x - p.x).abs() <= slack(p.x) && (q.y - p.y).abs() <= slack(p.y);
548 if let Some(q) = vertices.iter().find(|q| near(q)) {
549 return *q;
550 }
551 vertices.push(p);
552 p
553 };
554 for arc in &mut arcs {
555 arc.a = snap(arc.a);
556 arc.b = snap(arc.b);
557 }
558
559 let mut min = Point2::splat(Scalar::INFINITY);
560 let mut max = Point2::splat(Scalar::NEG_INFINITY);
561 for arc in &arcs {
562 min = min.min(arc.a.min(arc.b));
563 max = max.max(arc.a.max(arc.b));
564 }
565 if let Some((pole, _)) = pole {
566 min.y = min.y.min(pole);
567 max.y = max.y.max(pole);
568 }
569 let period = if transpose {
570 chart.v_period
571 } else {
572 chart.u_period
573 };
574 let across = if transpose {
575 chart.u_period
576 } else {
577 chart.v_period
578 };
579 let (min, max) = if transpose {
580 (Point2::new(min.y, min.x), Point2::new(max.y, max.x))
581 } else {
582 (min, max)
583 };
584 Ok(Some(Self {
585 boundary,
586 arcs,
587 transpose,
588 period,
589 across,
590 pole,
591 min,
592 max,
593 }))
594 }
595
596 fn swap(&self, p: Point2) -> Point2 {
597 if self.transpose {
598 Point2::new(p.y, p.x)
599 } else {
600 p
601 }
602 }
603
604 fn shifts(&self, lo: Scalar, hi: Scalar) -> Vec<Scalar> {
608 let Some(period) = self.period else {
609 return vec![0.0];
610 };
611 let (min, max) = self.arcs.iter().fold(
612 (Scalar::INFINITY, Scalar::NEG_INFINITY),
613 |(min, max), arc| (min.min(arc.a.x.min(arc.b.x)), max.max(arc.a.x.max(arc.b.x))),
614 );
615 let first = ((lo - max) / period).floor() as i64;
616 let last = ((hi - min) / period).ceil() as i64;
617 (first..=last).map(|k| k as Scalar * period).collect()
618 }
619
620 pub(crate) fn touches(&self, lo: Point2, hi: Point2) -> Result<bool, ExactMeasureError> {
627 let (lo, hi) = {
628 let (a, b) = (self.swap(lo), self.swap(hi));
629 (a.min(b), a.max(b))
630 };
631 for shift in self.shifts(lo.x, hi.x) {
632 let (lo, hi) = (
633 Point2::new(lo.x - shift, lo.y),
634 Point2::new(hi.x - shift, hi.y),
635 );
636 for arc in &self.arcs {
637 if self.arc_touches(arc, lo, hi, 0)? {
638 return Ok(true);
639 }
640 }
641 }
642 Ok(false)
643 }
644
645 fn arc_touches(
646 &self,
647 arc: &MonoArc,
648 lo: Point2,
649 hi: Point2,
650 depth: u32,
651 ) -> Result<bool, ExactMeasureError> {
652 let (a_lo, a_hi) = (arc.a.min(arc.b), arc.a.max(arc.b));
653 let (sx, sy) = (
654 slack(a_hi.x.abs().max(a_lo.x.abs())),
655 slack(a_hi.y.abs().max(a_lo.y.abs())),
656 );
657 if a_lo.x - sx > hi.x || a_hi.x + sx < lo.x || a_lo.y - sy > hi.y || a_hi.y + sy < lo.y {
658 return Ok(false);
659 }
660 let along = |a: Scalar, b: Scalar, edge: Scalar, s: Scalar| {
664 (a - b).abs() <= s && (a - edge).abs() <= s && (b - edge).abs() <= s
665 };
666 if along(arc.a.x, arc.b.x, lo.x, sx)
667 || along(arc.a.x, arc.b.x, hi.x, sx)
668 || along(arc.a.y, arc.b.y, lo.y, sy)
669 || along(arc.a.y, arc.b.y, hi.y, sy)
670 {
671 return Ok(false);
672 }
673 let inside = |p: Point2| p.x >= lo.x && p.x <= hi.x && p.y >= lo.y && p.y <= hi.y;
674 if inside(arc.a) || inside(arc.b) || depth >= 40 {
675 return Ok(true);
676 }
677 let tm = 0.5 * (arc.t0 + arc.t1);
678 let m = self.swap(self.boundary.pieces[arc.piece].at(tm)?.0);
679 let first = MonoArc {
680 piece: arc.piece,
681 t0: arc.t0,
682 t1: tm,
683 a: arc.a,
684 b: m,
685 };
686 let second = MonoArc {
687 piece: arc.piece,
688 t0: tm,
689 t1: arc.t1,
690 a: m,
691 b: arc.b,
692 };
693 Ok(self.arc_touches(&first, lo, hi, depth + 1)?
694 || self.arc_touches(&second, lo, hi, depth + 1)?)
695 }
696
697 pub(crate) fn contains(&self, p: Point2) -> Result<Option<bool>, ExactMeasureError> {
700 let p = match self.across {
703 Some(period) => {
704 let q = self.swap(p);
705 let (lo, hi) = {
706 let (a, b) = (self.swap(self.min), self.swap(self.max));
707 (a.y.min(b.y), a.y.max(b.y))
708 };
709 let k = ((0.5 * (lo + hi) - q.y) / period).round();
710 let moved = q.y + k * period;
711 if moved >= lo - slack(lo) && moved <= hi + slack(hi) {
712 self.swap(Point2::new(q.x, moved))
713 } else {
714 p
715 }
716 }
717 None => p,
718 };
719 let beyond = |value: Scalar, lo: Scalar, hi: Scalar| {
723 value < lo - slack(lo) || value > hi + slack(hi)
724 };
725 let (u_wraps, v_wraps) = if self.transpose {
726 (false, self.period.is_some())
727 } else {
728 (self.period.is_some(), false)
729 };
730 if (!u_wraps && beyond(p.x, self.min.x, self.max.x))
731 || (!v_wraps && beyond(p.y, self.min.y, self.max.y))
732 {
733 return Ok(Some(false));
734 }
735 let q = self.swap(p);
736 let mut total: i64 = 0;
737 for shift in self.shifts(q.x, q.x) {
738 for arc in &self.arcs {
739 match self.crossing(arc, q.x - shift, q.y, 0)? {
740 Some(weight) => total += weight,
741 None => return Ok(None),
742 }
743 }
744 }
745 if let Some((pole, winding)) = self.pole {
746 if (pole - q.y).abs() <= slack(pole) {
747 return Ok(None);
748 }
749 if pole > q.y {
750 total += winding;
751 }
752 }
753 if self.transpose {
756 total = -total;
757 }
758 Ok(match total {
761 0 => Some(false),
762 1 | -1 => Some(true),
763 _ => None,
764 })
765 }
766
767 fn crossing(
770 &self,
771 arc: &MonoArc,
772 c: Scalar,
773 y0: Scalar,
774 depth: u32,
775 ) -> Result<Option<i64>, ExactMeasureError> {
776 let (a, b) = (arc.a, arc.b);
777 let left = |x: Scalar| x <= c;
782 if left(a.x) == left(b.x) {
783 return Ok(Some(0));
784 }
785 let weight = if b.x < a.x { 1 } else { -1 };
786 let dy = slack(y0);
787 if a.y.min(b.y) > y0 + dy {
788 return Ok(Some(weight));
789 }
790 if a.y.max(b.y) < y0 - dy {
791 return Ok(Some(0));
792 }
793 if depth >= 48 {
794 return Ok(None);
795 }
796 let tm = 0.5 * (arc.t0 + arc.t1);
798 let m = self.swap(self.boundary.pieces[arc.piece].at(tm)?.0);
799 let half = if left(a.x) != left(m.x) {
800 MonoArc {
801 piece: arc.piece,
802 t0: arc.t0,
803 t1: tm,
804 a,
805 b: m,
806 }
807 } else {
808 MonoArc {
809 piece: arc.piece,
810 t0: tm,
811 t1: arc.t1,
812 a: m,
813 b,
814 }
815 };
816 self.crossing(&half, c, y0, depth + 1)
817 }
818}
819
820pub struct FaceDomain<'a> {
829 domain: Domain<'a>,
830}
831
832impl<'a> FaceDomain<'a> {
833 pub fn new(
841 brep: &'a ExactBRep,
842 face: axiolid_topology::FaceId,
843 tolerance: axiolid_core::Tolerance,
844 ) -> Result<Option<Self>, ExactMeasureError> {
845 let topology = brep.topology();
846 let record = topology
847 .faces()
848 .get(face.index())
849 .ok_or(ExactMeasureError::DanglingReference)?;
850 let surface = record
851 .surface
852 .and_then(|id| brep.surfaces().get(id.index()))
853 .ok_or(ExactMeasureError::MissingSurface)?;
854 let linear = tolerance.linear().max(1e-12);
855 Ok(Domain::new(brep, record, surface, linear)?.map(|domain| Self { domain }))
856 }
857
858 pub fn contains(&self, at: Point2) -> Result<Option<bool>, ExactMeasureError> {
865 self.domain.contains(at)
866 }
867
868 #[must_use]
870 pub fn bounds(&self) -> (Point2, Point2) {
871 (self.domain.min, self.domain.max)
872 }
873}