use axiolid_core::{Frame2, Frame3, Point2, Point3, Scalar, Vec2};
use axiolid_curve::{Intrinsic2, Intrinsic3};
use super::{frame_stretch2, frame_stretch3, inflate};
use crate::chain::curvature_bound;
pub const MAX_POSITION_PANELS: usize = 1 << 22;
#[derive(Debug, Clone, Copy)]
enum Placement {
Plane(Frame2),
Space(Frame3),
}
#[derive(Debug, Clone)]
pub struct IntrinsicPositions {
local: Intrinsic2,
placement: Placement,
stretch: Scalar,
origin: Scalar,
pieces: Vec<(Scalar, Scalar, Scalar)>,
step: Scalar,
heading_rounding: Scalar,
at: Scalar,
sum: [(Scalar, Scalar); 2],
remainder: Scalar,
}
fn add(acc: &mut (Scalar, Scalar), value: Scalar) {
let total = acc.0 + value;
acc.1 += if acc.0.abs() >= value.abs() {
(acc.0 - total) + value
} else {
(value - total) + acc.0
};
acc.0 = total;
}
fn unit_frame() -> Frame2 {
Frame2 {
origin: Point2::ZERO,
x: Vec2::X,
y: Vec2::Y,
}
}
impl IntrinsicPositions {
#[must_use]
pub fn new2(curve: &Intrinsic2, error: Scalar) -> Option<Self> {
let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
Self::build(
local,
Placement::Plane(curve.start),
frame_stretch2(&curve.start),
curve.start.origin.abs().max_element(),
error,
)
}
#[must_use]
pub fn new3(curve: &Intrinsic3, error: Scalar) -> Option<Self> {
if !curve.is_planar() {
return None;
}
let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
Self::build(
local,
Placement::Space(curve.start),
frame_stretch3(&curve.start),
curve.start.origin.abs().max_element(),
error,
)
}
fn build(
local: Intrinsic2,
placement: Placement,
stretch: Scalar,
origin: Scalar,
error: Scalar,
) -> Option<Self> {
let length = local.length;
if !(error.is_finite() && error > 0.0 && length.is_finite() && length > 0.0) {
return None;
}
let law = &local.curvature;
let rate = law.derivative();
let seams = law.seams_within(length);
let mut ends = vec![0.0];
ends.extend(seams.iter().copied());
ends.push(length);
let mut pieces = Vec::with_capacity(ends.len() - 1);
let mut bend: Scalar = 0.0;
for pair in ends.windows(2) {
let (lo, hi) = (pair[0], pair[1]);
let k = curvature_bound(law, lo, hi)?;
let m = inflate(curvature_bound(&rate, lo, hi)? + k * k);
bend = bend.max(m);
pieces.push((lo, hi, m));
}
let budget = 0.5 * error / stretch;
let step = if bend > 0.0 {
(24.0 * budget / (length * bend)).sqrt().min(length)
} else {
length
};
if !(step.is_finite() && step > 0.0) || length / step > MAX_POSITION_PANELS as Scalar {
return None;
}
let turning = curvature_bound(law, 0.0, length)? * length;
let heading_rounding =
64.0 * Scalar::EPSILON * (1.0 + turning) * (1.0 + seams.len() as Scalar);
Some(Self {
local,
placement,
stretch,
origin,
pieces,
step,
heading_rounding,
at: 0.0,
sum: [(0.0, 0.0); 2],
remainder: 0.0,
})
}
fn march(&mut self, s: Scalar) -> Option<()> {
if !(s.is_finite() && (0.0..=self.local.length).contains(&s)) {
return None;
}
if s < self.at {
self.at = 0.0;
self.sum = [(0.0, 0.0); 2];
self.remainder = 0.0;
}
for &(lo, hi, bend) in &self.pieces {
let (a, b) = (lo.max(self.at), hi.min(s));
if b <= a {
continue;
}
let n = ((b - a) / self.step).ceil().max(1.0);
if n > MAX_POSITION_PANELS as Scalar {
return None;
}
let w = (b - a) / n;
for i in 0..n as usize {
let theta = self.local.heading_at(a + (i as Scalar + 0.5) * w)?;
add(&mut self.sum[0], w * theta.cos());
add(&mut self.sum[1], w * theta.sin());
}
self.remainder += n * w * w * w / 24.0 * bend;
}
self.at = s;
Some(())
}
fn local_point(&self) -> (Point2, Scalar) {
let p = Point2::new(self.sum[0].0 + self.sum[0].1, self.sum[1].0 + self.sum[1].1);
let error = self.remainder + self.at * (self.heading_rounding + 16.0 * Scalar::EPSILON);
(p, error)
}
fn placed_error(&self, local: Scalar) -> Scalar {
inflate(
self.stretch * local + 8.0 * Scalar::EPSILON * (self.origin + self.stretch * self.at),
)
}
pub fn point2(&mut self, s: Scalar) -> Option<(Point2, Scalar)> {
let Placement::Plane(frame) = self.placement else {
return None;
};
self.march(s)?;
let (p, local) = self.local_point();
let out = Point2::new(
frame.origin.x + frame.x.x * p.x + frame.y.x * p.y,
frame.origin.y + frame.x.y * p.x + frame.y.y * p.y,
);
Some((out, self.placed_error(local)))
}
pub fn point3(&mut self, s: Scalar) -> Option<(Point3, Scalar)> {
let Placement::Space(frame) = self.placement else {
return None;
};
self.march(s)?;
let (p, local) = self.local_point();
Some((
frame.origin + frame.x * p.x + frame.y * p.y,
self.placed_error(local),
))
}
pub fn offset_point2(&mut self, s: Scalar, distance: Scalar) -> Option<(Point2, Scalar)> {
let Placement::Plane(frame) = self.placement else {
return None;
};
let (p, error) = self.point2(s)?;
let theta = self.local.heading_at(s)?;
let (c, n) = (theta.cos(), theta.sin());
let t = Vec2::new(frame.x.x * c + frame.y.x * n, frame.x.y * c + frame.y.y * n);
let size = t.length();
let det = (frame.x.x * frame.y.y - frame.x.y * frame.y.x).abs();
if !(size.is_finite() && size > 0.0 && det > 0.0 && distance.is_finite()) {
return None;
}
let left = Vec2::new(-t.y, t.x) / size;
let condition = self.stretch * self.stretch / det;
let turn = condition * (self.heading_rounding + 16.0 * Scalar::EPSILON);
Some((
p + left * distance,
inflate(error + distance.abs() * (turn + 8.0 * Scalar::EPSILON)),
))
}
}