1use axiolid_core::{Frame2, Frame3, Point2, Point3, Scalar, Vec2};
36use axiolid_curve::{Intrinsic2, Intrinsic3};
37
38use super::{frame_stretch2, frame_stretch3, inflate};
39use crate::chain::{curvature_bound, harmonic_rounding};
40
41pub const MAX_POSITION_PANELS: usize = 1 << 22;
43
44#[derive(Debug, Clone, Copy)]
47pub(super) enum Placement {
48 Plane(Frame2),
49 Space(Frame3),
50}
51
52#[derive(Debug, Clone)]
58pub struct IntrinsicPositions {
59 local: Intrinsic2,
61 placement: Placement,
62 stretch: Scalar,
63 origin: Scalar,
64 pieces: Vec<(Scalar, Scalar, Scalar)>,
66 step: Scalar,
67 heading_rounding: Scalar,
68 at: Scalar,
69 sum: [(Scalar, Scalar); 2],
70 remainder: Scalar,
71}
72
73pub(super) fn add(acc: &mut (Scalar, Scalar), value: Scalar) {
75 let total = acc.0 + value;
76 acc.1 += if acc.0.abs() >= value.abs() {
77 (acc.0 - total) + value
78 } else {
79 (value - total) + acc.0
80 };
81 acc.0 = total;
82}
83
84pub(super) fn unit_frame() -> Frame2 {
85 Frame2 {
86 origin: Point2::ZERO,
87 x: Vec2::X,
88 y: Vec2::Y,
89 }
90}
91
92impl IntrinsicPositions {
93 #[must_use]
98 pub fn new2(curve: &Intrinsic2, error: Scalar) -> Option<Self> {
99 let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
100 Self::build(
101 local,
102 Placement::Plane(curve.start),
103 frame_stretch2(&curve.start),
104 curve.start.origin.abs().max_element(),
105 error,
106 )
107 }
108
109 #[must_use]
113 pub fn new3(curve: &Intrinsic3, error: Scalar) -> Option<Self> {
114 if !curve.is_planar() {
115 return None;
116 }
117 let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
118 Self::build(
119 local,
120 Placement::Space(curve.start),
121 frame_stretch3(&curve.start),
122 curve.start.origin.abs().max_element(),
123 error,
124 )
125 }
126
127 fn build(
128 local: Intrinsic2,
129 placement: Placement,
130 stretch: Scalar,
131 origin: Scalar,
132 error: Scalar,
133 ) -> Option<Self> {
134 let length = local.length;
135 if !(error.is_finite() && error > 0.0 && length.is_finite() && length > 0.0) {
136 return None;
137 }
138 let law = &local.curvature;
139 let rate = law.derivative();
140 let seams = law.seams_within(length);
141 let mut ends = vec![0.0];
142 ends.extend(seams.iter().copied());
143 ends.push(length);
144 let mut pieces = Vec::with_capacity(ends.len() - 1);
145 let mut bend: Scalar = 0.0;
146 for pair in ends.windows(2) {
147 let (lo, hi) = (pair[0], pair[1]);
148 let k = curvature_bound(law, lo, hi)?;
149 let m = inflate(curvature_bound(&rate, lo, hi)? + k * k);
150 bend = bend.max(m);
151 pieces.push((lo, hi, m));
152 }
153 let budget = 0.5 * error / stretch;
154 let step = if bend > 0.0 {
155 (24.0 * budget / (length * bend)).sqrt().min(length)
156 } else {
157 length
158 };
159 if !(step.is_finite() && step > 0.0) || length / step > MAX_POSITION_PANELS as Scalar {
160 return None;
161 }
162 let turning = curvature_bound(law, 0.0, length)? * length;
163 let heading_rounding =
164 64.0 * Scalar::EPSILON * (1.0 + turning) * (1.0 + seams.len() as Scalar)
165 + harmonic_rounding(law, length)?;
166 Some(Self {
167 local,
168 placement,
169 stretch,
170 origin,
171 pieces,
172 step,
173 heading_rounding,
174 at: 0.0,
175 sum: [(0.0, 0.0); 2],
176 remainder: 0.0,
177 })
178 }
179
180 fn march(&mut self, s: Scalar) -> Option<()> {
182 if !(s.is_finite() && (0.0..=self.local.length).contains(&s)) {
183 return None;
184 }
185 if s < self.at {
186 self.at = 0.0;
187 self.sum = [(0.0, 0.0); 2];
188 self.remainder = 0.0;
189 }
190 for &(lo, hi, bend) in &self.pieces {
191 let (a, b) = (lo.max(self.at), hi.min(s));
192 if b <= a {
193 continue;
194 }
195 let n = ((b - a) / self.step).ceil().max(1.0);
196 if n > MAX_POSITION_PANELS as Scalar {
197 return None;
198 }
199 let w = (b - a) / n;
200 for i in 0..n as usize {
201 let theta = self.local.heading_at(a + (i as Scalar + 0.5) * w)?;
202 add(&mut self.sum[0], w * theta.cos());
203 add(&mut self.sum[1], w * theta.sin());
204 }
205 self.remainder += n * w * w * w / 24.0 * bend;
206 }
207 self.at = s;
208 Some(())
209 }
210
211 fn local_point(&self) -> (Point2, Scalar) {
214 let p = Point2::new(self.sum[0].0 + self.sum[0].1, self.sum[1].0 + self.sum[1].1);
215 let error = self.remainder + self.at * (self.heading_rounding + 16.0 * Scalar::EPSILON);
216 (p, error)
217 }
218
219 fn placed_error(&self, local: Scalar) -> Scalar {
221 inflate(
222 self.stretch * local + 8.0 * Scalar::EPSILON * (self.origin + self.stretch * self.at),
223 )
224 }
225
226 pub fn point2(&mut self, s: Scalar) -> Option<(Point2, Scalar)> {
230 let Placement::Plane(frame) = self.placement else {
231 return None;
232 };
233 self.march(s)?;
234 let (p, local) = self.local_point();
235 let out = Point2::new(
236 frame.origin.x + frame.x.x * p.x + frame.y.x * p.y,
237 frame.origin.y + frame.x.y * p.x + frame.y.y * p.y,
238 );
239 Some((out, self.placed_error(local)))
240 }
241
242 pub fn point3(&mut self, s: Scalar) -> Option<(Point3, Scalar)> {
244 let Placement::Space(frame) = self.placement else {
245 return None;
246 };
247 self.march(s)?;
248 let (p, local) = self.local_point();
249 Some((
250 frame.origin + frame.x * p.x + frame.y * p.y,
251 self.placed_error(local),
252 ))
253 }
254
255 pub fn offset_point2(&mut self, s: Scalar, distance: Scalar) -> Option<(Point2, Scalar)> {
262 let Placement::Plane(frame) = self.placement else {
263 return None;
264 };
265 let (p, error) = self.point2(s)?;
266 let theta = self.local.heading_at(s)?;
267 let (c, n) = (theta.cos(), theta.sin());
268 let t = Vec2::new(frame.x.x * c + frame.y.x * n, frame.x.y * c + frame.y.y * n);
269 let size = t.length();
270 let det = (frame.x.x * frame.y.y - frame.x.y * frame.y.x).abs();
271 if !(size.is_finite() && size > 0.0 && det > 0.0 && distance.is_finite()) {
272 return None;
273 }
274 let left = Vec2::new(-t.y, t.x) / size;
275 let condition = self.stretch * self.stretch / det;
276 let turn = condition * (self.heading_rounding + 16.0 * Scalar::EPSILON);
277 Some((
278 p + left * distance,
279 inflate(error + distance.abs() * (turn + 8.0 * Scalar::EPSILON)),
280 ))
281 }
282}