use axiolid_brep::ExactBRep;
use axiolid_core::{Point2, Point3, Scalar, Vec2};
use axiolid_curve::Curve2;
use axiolid_evaluate::surface::evaluate;
use axiolid_evaluate::{derivative2, evaluate2};
use axiolid_surface::Surface;
use axiolid_topology::{Face, Orientation};
use core::f64::consts::{FRAC_PI_2, TAU};
use crate::exact::ExactMeasureError;
pub(crate) struct Chart {
pub(crate) u_period: Option<Scalar>,
pub(crate) v_period: Option<Scalar>,
pub(crate) poles: Vec<Scalar>,
}
pub(crate) fn chart(surface: &Surface) -> Result<Chart, ExactMeasureError> {
let chart = match surface {
Surface::Plane(_) | Surface::BSpline(_) => Chart {
u_period: None,
v_period: None,
poles: Vec::new(),
},
Surface::Cylinder(_) | Surface::EllipticalCylinder(_) => Chart {
u_period: Some(TAU),
v_period: None,
poles: Vec::new(),
},
Surface::Cone(cone) => {
let slope = cone.semi_angle.tan();
let poles = if slope.is_finite() && slope != 0.0 {
vec![-cone.radius / slope]
} else {
Vec::new()
};
Chart {
u_period: Some(TAU),
v_period: None,
poles,
}
}
Surface::Sphere(_) => Chart {
u_period: Some(TAU),
v_period: None,
poles: vec![-FRAC_PI_2, FRAC_PI_2],
},
Surface::Torus(_) => Chart {
u_period: Some(TAU),
v_period: Some(TAU),
poles: Vec::new(),
},
_ => {
return Err(ExactMeasureError::NonPlanarFace(crate::exact::family(
surface,
)))
}
};
Ok(chart)
}
pub(crate) enum Piece<'a> {
Curve {
curve: &'a Curve2,
start: Scalar,
end: Scalar,
offset: Vec2,
},
Segment { from: Point2, to: Point2 },
}
impl Piece<'_> {
pub(crate) fn span(&self) -> (Scalar, Scalar) {
match self {
Self::Curve { start, end, .. } => (*start, *end),
Self::Segment { .. } => (0.0, 1.0),
}
}
pub(crate) fn smooth_spans(&self) -> Vec<(Scalar, Scalar)> {
let (a, b) = self.span();
let mut cuts = vec![a];
let kinked = match self {
Self::Curve {
curve: Curve2::Implicit(_),
..
} => true,
Self::Curve {
curve: Curve2::Lifted(l),
..
} => matches!(
l.curve.as_ref(),
axiolid_curve::Curve3::ImplicitSection(_) | axiolid_curve::Curve3::PairSection(_)
),
_ => false,
};
if kinked {
let (lo, hi) = (a.min(b), a.max(b));
let mut k = lo.floor() + 1.0;
let mut inner = Vec::new();
while k < hi {
inner.push(k);
k += 1.0;
}
if b < a {
inner.reverse();
}
cuts.extend(inner);
}
cuts.push(b);
cuts.windows(2).map(|w| (w[0], w[1])).collect()
}
pub(crate) fn at(&self, t: Scalar) -> Result<(Point2, Vec2), ExactMeasureError> {
match self {
Self::Curve { curve, offset, .. } => {
let point = evaluate2(curve, t).map_err(|_| crate::exact::EVALUATION)?;
let tangent = derivative2(curve, t).map_err(|_| crate::exact::EVALUATION)?;
Ok((point + *offset, tangent))
}
Self::Segment { from, to } => Ok((*from + (*to - *from) * t, *to - *from)),
}
}
pub(crate) fn end_point(&self) -> Result<Point2, ExactMeasureError> {
Ok(self.at(self.span().1)?.0)
}
}
pub(crate) struct Boundary<'a> {
pub(crate) pieces: Vec<Piece<'a>>,
pub(crate) winding: [i64; 2],
pub(crate) wraps: [bool; 2],
pub(crate) anchor: Point2,
}
pub(crate) fn pole_on_domain_side(
chart: &Chart,
boundary: &Boundary<'_>,
) -> Result<Scalar, ExactMeasureError> {
let upward = match boundary.winding[0] {
1 => true,
-1 => false,
_ => {
return Err(ExactMeasureError::NonPlanarFace(
"face boundary winds around the surface more than once",
))
}
};
chart
.poles
.iter()
.copied()
.filter(|pole| (*pole > boundary.anchor.y) == upward)
.min_by(|a, b| {
(a - boundary.anchor.y)
.abs()
.total_cmp(&(b - boundary.anchor.y).abs())
})
.ok_or(ExactMeasureError::NonPlanarFace(
"face boundary winds around a surface with no pole on the domain side",
))
}
pub(crate) fn assemble<'a>(
brep: &'a ExactBRep,
face: &Face<axiolid_brep::SurfaceId>,
surface: &Surface,
chart: &Chart,
linear: Scalar,
) -> Result<Boundary<'a>, ExactMeasureError> {
let topology = brep.topology();
let mut pieces = Vec::new();
let mut winding = [0_i64; 2];
let mut wraps = [false; 2];
let mut anchor = None;
for bound in &face.bounds {
let wire = topology
.loops()
.get(bound.loop_id.index())
.ok_or(ExactMeasureError::DanglingReference)?;
let reversed = bound.orientation == Orientation::Reversed;
let mut uses = Vec::with_capacity(wire.edges.len());
for (index, use_) in wire.edges.iter().enumerate() {
let pcurve = use_.pcurve.ok_or(ExactMeasureError::DanglingReference)?;
let curve = brep
.curves2()
.get(pcurve.index())
.ok_or(ExactMeasureError::DanglingReference)?;
let interval = brep
.pcurve_interval(bound.loop_id, index)
.ok_or(ExactMeasureError::DanglingReference)?;
let (start, end) = if reversed {
(interval.end, interval.start)
} else {
(interval.start, interval.end)
};
uses.push((curve, start, end));
}
if reversed {
uses.reverse();
}
let Some(&(first_curve, first_start, _)) = uses.first() else {
continue;
};
let loop_start =
evaluate2(first_curve, first_start).map_err(|_| crate::exact::EVALUATION)?;
anchor.get_or_insert(loop_start);
let mut offset = Vec2::ZERO;
let mut cursor = loop_start;
for (curve, start, end) in uses {
let raw = evaluate2(curve, start).map_err(|_| crate::exact::EVALUATION)?;
let (shift, bridge) = join(surface, chart, cursor, raw + offset, linear)?;
offset += shift;
if let Some(segment) = bridge {
pieces.push(segment);
}
let piece = Piece::Curve {
curve,
start,
end,
offset,
};
cursor = piece.end_point()?;
pieces.push(piece);
}
let (shift, bridge) = join(surface, chart, cursor, loop_start, linear)?;
if let Some(segment) = bridge {
pieces.push(segment);
}
let turns = [
periods(shift.x, chart.u_period),
periods(shift.y, chart.v_period),
];
for axis in 0..2 {
winding[axis] += turns[axis];
wraps[axis] |= turns[axis] != 0;
}
}
let anchor = anchor.ok_or(ExactMeasureError::Degenerate)?;
Ok(Boundary {
pieces,
winding,
wraps,
anchor,
})
}
fn periods(shift: Scalar, period: Option<Scalar>) -> i64 {
period.map_or(0, |period| (shift / period).round() as i64)
}
fn join<'a>(
surface: &Surface,
chart: &Chart,
from: Point2,
to: Point2,
linear: Scalar,
) -> Result<(Vec2, Option<Piece<'a>>), ExactMeasureError> {
let there = point(surface, from)?;
let here = point(surface, to)?;
if (there - here).length() > linear {
return Err(ExactMeasureError::NonPlanarFace(
"consecutive pcurves of a face loop do not meet on the surface",
));
}
let on_pole = chart.poles.iter().any(|pole| {
(from.y - pole).abs() <= parameter_slack(*pole)
&& (to.y - pole).abs() <= parameter_slack(*pole)
});
let shift = if on_pole {
Vec2::ZERO
} else {
let wrap = |gap: Scalar, period: Option<Scalar>| {
period.map_or(0.0, |period| (gap / period).round() * period)
};
Vec2::new(
wrap(from.x - to.x, chart.u_period),
wrap(from.y - to.y, chart.v_period),
)
};
let landed = to + shift;
let bridge = (landed != from).then_some(Piece::Segment { from, to: landed });
Ok((shift, bridge))
}
fn parameter_slack(value: Scalar) -> Scalar {
1e-9 * value.abs().max(1.0)
}
pub(crate) fn point(surface: &Surface, at: Point2) -> Result<Point3, ExactMeasureError> {
evaluate(surface, at.x, at.y).map_err(|_| crate::exact::EVALUATION)
}
struct MonoArc {
piece: usize,
t0: Scalar,
t1: Scalar,
a: Point2,
b: Point2,
}
pub(crate) struct Domain<'a> {
boundary: Boundary<'a>,
arcs: Vec<MonoArc>,
transpose: bool,
period: Option<Scalar>,
across: Option<Scalar>,
pole: Option<(Scalar, i64)>,
pub(crate) min: Point2,
pub(crate) max: Point2,
}
fn turning_points(curve: &Curve2, lo: Scalar, hi: Scalar) -> Option<Vec<Scalar>> {
let mut out = Vec::new();
let mut add_trig = |a: Scalar, b: Scalar| {
if a == 0.0 && b == 0.0 {
return;
}
let base = b.atan2(a);
let first = ((lo - base) / core::f64::consts::PI).floor() as i64;
let last = ((hi - base) / core::f64::consts::PI).ceil() as i64;
for k in first..=last {
let t = base + k as Scalar * core::f64::consts::PI;
if t > lo && t < hi {
out.push(t);
}
}
};
match curve {
Curve2::Line(_) => {}
Curve2::Circle(c) => {
add_trig(c.radius * c.frame.x.x, c.radius * c.frame.y.x);
add_trig(c.radius * c.frame.x.y, c.radius * c.frame.y.y);
}
Curve2::Ellipse(e) => {
add_trig(e.semi_axis_x * e.frame.x.x, e.semi_axis_y * e.frame.y.x);
add_trig(e.semi_axis_x * e.frame.x.y, e.semi_axis_y * e.frame.y.y);
}
Curve2::Sinusoid(w) => add_trig(w.cosine, w.sine),
Curve2::Polyline(_) => {
let mut k = lo.floor() + 1.0;
while k < hi {
out.push(k);
k += 1.0;
}
}
Curve2::Implicit(c) => out.extend(c.turning_points(lo, hi)),
Curve2::BSpline(b) => {
out.extend(b.knots.iter().copied().filter(|k| *k > lo && *k < hi));
out.extend(scanned_turns(curve, lo, hi)?);
}
Curve2::Lifted(l) => {
if let axiolid_curve::Curve3::ImplicitSection(_)
| axiolid_curve::Curve3::PairSection(_) = l.curve.as_ref()
{
let mut k = lo.floor() + 1.0;
while k < hi {
out.push(k);
k += 1.0;
}
}
out.extend(scanned_turns(curve, lo, hi)?);
}
_ => return None,
}
out.sort_by(Scalar::total_cmp);
Some(out)
}
fn scanned_turns(curve: &Curve2, lo: Scalar, hi: Scalar) -> Option<Vec<Scalar>> {
let n = 256;
let d = |t: Scalar| derivative2(curve, t).ok();
let mut out = Vec::new();
let mut previous = (lo, d(lo + (hi - lo) * 1e-9)?);
for i in 1..=n {
let t = if i == n {
hi - (hi - lo) * 1e-9
} else {
lo + (hi - lo) * i as Scalar / n as Scalar
};
let now = d(t)?;
for axis in 0..2 {
let (a, b) = (previous.1[axis], now[axis]);
if (a < 0.0) != (b < 0.0) && a != 0.0 && b != 0.0 {
let (mut x0, mut x1) = (previous.0, t);
for _ in 0..80 {
let m = 0.5 * (x0 + x1);
let dm = d(m)?[axis];
if (dm < 0.0) == (a < 0.0) {
x0 = m;
} else {
x1 = m;
}
}
out.push(0.5 * (x0 + x1));
}
}
previous = (t, now);
}
Some(out)
}
fn slack(value: Scalar) -> Scalar {
1e-9 * (1.0 + value.abs())
}
impl<'a> Domain<'a> {
pub(crate) fn new(
brep: &'a ExactBRep,
face: &Face<axiolid_brep::SurfaceId>,
surface: &Surface,
linear: Scalar,
) -> Result<Option<Self>, ExactMeasureError> {
let chart = chart(surface)?;
let boundary = assemble(brep, face, surface, &chart, linear)?;
let transpose = boundary.wraps[1];
if transpose && (boundary.wraps[0] || boundary.winding[1] != 0) {
return Err(ExactMeasureError::NonPlanarFace(
"face boundary winds around the surface in both directions",
));
}
let pole = if !transpose && boundary.winding[0] != 0 {
Some((pole_on_domain_side(&chart, &boundary)?, boundary.winding[0]))
} else {
None
};
let swap = |p: Point2| if transpose { Point2::new(p.y, p.x) } else { p };
let mut arcs = Vec::new();
for (index, piece) in boundary.pieces.iter().enumerate() {
let (start, end) = piece.span();
let (lo, hi) = (start.min(end), start.max(end));
let mut cuts = match piece {
Piece::Curve { curve, .. } => match turning_points(curve, lo, hi) {
Some(cuts) => cuts,
None => return Ok(None),
},
Piece::Segment { .. } => Vec::new(),
};
if start > end {
cuts.reverse();
}
let mut ts = vec![start];
ts.extend(cuts);
ts.push(end);
for pair in ts.windows(2) {
let a = swap(piece.at(pair[0])?.0);
let b = swap(piece.at(pair[1])?.0);
arcs.push(MonoArc {
piece: index,
t0: pair[0],
t1: pair[1],
a,
b,
});
}
}
if arcs.is_empty() {
return Ok(None);
}
let mut vertices: Vec<Point2> = Vec::new();
let mut snap = |p: Point2| -> Point2 {
let near =
|q: &Point2| (q.x - p.x).abs() <= slack(p.x) && (q.y - p.y).abs() <= slack(p.y);
if let Some(q) = vertices.iter().find(|q| near(q)) {
return *q;
}
vertices.push(p);
p
};
for arc in &mut arcs {
arc.a = snap(arc.a);
arc.b = snap(arc.b);
}
let mut min = Point2::splat(Scalar::INFINITY);
let mut max = Point2::splat(Scalar::NEG_INFINITY);
for arc in &arcs {
min = min.min(arc.a.min(arc.b));
max = max.max(arc.a.max(arc.b));
}
if let Some((pole, _)) = pole {
min.y = min.y.min(pole);
max.y = max.y.max(pole);
}
let period = if transpose {
chart.v_period
} else {
chart.u_period
};
let across = if transpose {
chart.u_period
} else {
chart.v_period
};
let (min, max) = if transpose {
(Point2::new(min.y, min.x), Point2::new(max.y, max.x))
} else {
(min, max)
};
Ok(Some(Self {
boundary,
arcs,
transpose,
period,
across,
pole,
min,
max,
}))
}
fn swap(&self, p: Point2) -> Point2 {
if self.transpose {
Point2::new(p.y, p.x)
} else {
p
}
}
fn shifts(&self, lo: Scalar, hi: Scalar) -> Vec<Scalar> {
let Some(period) = self.period else {
return vec![0.0];
};
let (min, max) = self.arcs.iter().fold(
(Scalar::INFINITY, Scalar::NEG_INFINITY),
|(min, max), arc| (min.min(arc.a.x.min(arc.b.x)), max.max(arc.a.x.max(arc.b.x))),
);
let first = ((lo - max) / period).floor() as i64;
let last = ((hi - min) / period).ceil() as i64;
(first..=last).map(|k| k as Scalar * period).collect()
}
pub(crate) fn touches(&self, lo: Point2, hi: Point2) -> Result<bool, ExactMeasureError> {
let (lo, hi) = {
let (a, b) = (self.swap(lo), self.swap(hi));
(a.min(b), a.max(b))
};
for shift in self.shifts(lo.x, hi.x) {
let (lo, hi) = (
Point2::new(lo.x - shift, lo.y),
Point2::new(hi.x - shift, hi.y),
);
for arc in &self.arcs {
if self.arc_touches(arc, lo, hi, 0)? {
return Ok(true);
}
}
}
Ok(false)
}
fn arc_touches(
&self,
arc: &MonoArc,
lo: Point2,
hi: Point2,
depth: u32,
) -> Result<bool, ExactMeasureError> {
let (a_lo, a_hi) = (arc.a.min(arc.b), arc.a.max(arc.b));
let (sx, sy) = (
slack(a_hi.x.abs().max(a_lo.x.abs())),
slack(a_hi.y.abs().max(a_lo.y.abs())),
);
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 {
return Ok(false);
}
let along = |a: Scalar, b: Scalar, edge: Scalar, s: Scalar| {
(a - b).abs() <= s && (a - edge).abs() <= s && (b - edge).abs() <= s
};
if along(arc.a.x, arc.b.x, lo.x, sx)
|| along(arc.a.x, arc.b.x, hi.x, sx)
|| along(arc.a.y, arc.b.y, lo.y, sy)
|| along(arc.a.y, arc.b.y, hi.y, sy)
{
return Ok(false);
}
let inside = |p: Point2| p.x >= lo.x && p.x <= hi.x && p.y >= lo.y && p.y <= hi.y;
if inside(arc.a) || inside(arc.b) || depth >= 40 {
return Ok(true);
}
let tm = 0.5 * (arc.t0 + arc.t1);
let m = self.swap(self.boundary.pieces[arc.piece].at(tm)?.0);
let first = MonoArc {
piece: arc.piece,
t0: arc.t0,
t1: tm,
a: arc.a,
b: m,
};
let second = MonoArc {
piece: arc.piece,
t0: tm,
t1: arc.t1,
a: m,
b: arc.b,
};
Ok(self.arc_touches(&first, lo, hi, depth + 1)?
|| self.arc_touches(&second, lo, hi, depth + 1)?)
}
pub(crate) fn contains(&self, p: Point2) -> Result<Option<bool>, ExactMeasureError> {
let p = match self.across {
Some(period) => {
let q = self.swap(p);
let (lo, hi) = {
let (a, b) = (self.swap(self.min), self.swap(self.max));
(a.y.min(b.y), a.y.max(b.y))
};
let k = ((0.5 * (lo + hi) - q.y) / period).round();
let moved = q.y + k * period;
if moved >= lo - slack(lo) && moved <= hi + slack(hi) {
self.swap(Point2::new(q.x, moved))
} else {
p
}
}
None => p,
};
let beyond = |value: Scalar, lo: Scalar, hi: Scalar| {
value < lo - slack(lo) || value > hi + slack(hi)
};
let (u_wraps, v_wraps) = if self.transpose {
(false, self.period.is_some())
} else {
(self.period.is_some(), false)
};
if (!u_wraps && beyond(p.x, self.min.x, self.max.x))
|| (!v_wraps && beyond(p.y, self.min.y, self.max.y))
{
return Ok(Some(false));
}
let q = self.swap(p);
let mut total: i64 = 0;
for shift in self.shifts(q.x, q.x) {
for arc in &self.arcs {
match self.crossing(arc, q.x - shift, q.y, 0)? {
Some(weight) => total += weight,
None => return Ok(None),
}
}
}
if let Some((pole, winding)) = self.pole {
if (pole - q.y).abs() <= slack(pole) {
return Ok(None);
}
if pole > q.y {
total += winding;
}
}
if self.transpose {
total = -total;
}
Ok(match total {
0 => Some(false),
1 | -1 => Some(true),
_ => None,
})
}
fn crossing(
&self,
arc: &MonoArc,
c: Scalar,
y0: Scalar,
depth: u32,
) -> Result<Option<i64>, ExactMeasureError> {
let (a, b) = (arc.a, arc.b);
let left = |x: Scalar| x <= c;
if left(a.x) == left(b.x) {
return Ok(Some(0));
}
let weight = if b.x < a.x { 1 } else { -1 };
let dy = slack(y0);
if a.y.min(b.y) > y0 + dy {
return Ok(Some(weight));
}
if a.y.max(b.y) < y0 - dy {
return Ok(Some(0));
}
if depth >= 48 {
return Ok(None);
}
let tm = 0.5 * (arc.t0 + arc.t1);
let m = self.swap(self.boundary.pieces[arc.piece].at(tm)?.0);
let half = if left(a.x) != left(m.x) {
MonoArc {
piece: arc.piece,
t0: arc.t0,
t1: tm,
a,
b: m,
}
} else {
MonoArc {
piece: arc.piece,
t0: tm,
t1: arc.t1,
a: m,
b,
}
};
self.crossing(&half, c, y0, depth + 1)
}
}
pub struct FaceDomain<'a> {
domain: Domain<'a>,
}
impl<'a> FaceDomain<'a> {
pub fn new(
brep: &'a ExactBRep,
face: axiolid_topology::FaceId,
tolerance: axiolid_core::Tolerance,
) -> Result<Option<Self>, ExactMeasureError> {
let topology = brep.topology();
let record = topology
.faces()
.get(face.index())
.ok_or(ExactMeasureError::DanglingReference)?;
let surface = record
.surface
.and_then(|id| brep.surfaces().get(id.index()))
.ok_or(ExactMeasureError::MissingSurface)?;
let linear = tolerance.linear().max(1e-12);
Ok(Domain::new(brep, record, surface, linear)?.map(|domain| Self { domain }))
}
pub fn contains(&self, at: Point2) -> Result<Option<bool>, ExactMeasureError> {
self.domain.contains(at)
}
#[must_use]
pub fn bounds(&self) -> (Point2, Point2) {
(self.domain.min, self.domain.max)
}
}