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;
40
41pub const MAX_POSITION_PANELS: usize = 1 << 22;
43
44#[derive(Debug, Clone, Copy)]
47enum 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
73fn 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
84fn 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 Some(Self {
166 local,
167 placement,
168 stretch,
169 origin,
170 pieces,
171 step,
172 heading_rounding,
173 at: 0.0,
174 sum: [(0.0, 0.0); 2],
175 remainder: 0.0,
176 })
177 }
178
179 fn march(&mut self, s: Scalar) -> Option<()> {
181 if !(s.is_finite() && (0.0..=self.local.length).contains(&s)) {
182 return None;
183 }
184 if s < self.at {
185 self.at = 0.0;
186 self.sum = [(0.0, 0.0); 2];
187 self.remainder = 0.0;
188 }
189 for &(lo, hi, bend) in &self.pieces {
190 let (a, b) = (lo.max(self.at), hi.min(s));
191 if b <= a {
192 continue;
193 }
194 let n = ((b - a) / self.step).ceil().max(1.0);
195 if n > MAX_POSITION_PANELS as Scalar {
196 return None;
197 }
198 let w = (b - a) / n;
199 for i in 0..n as usize {
200 let theta = self.local.heading_at(a + (i as Scalar + 0.5) * w)?;
201 add(&mut self.sum[0], w * theta.cos());
202 add(&mut self.sum[1], w * theta.sin());
203 }
204 self.remainder += n * w * w * w / 24.0 * bend;
205 }
206 self.at = s;
207 Some(())
208 }
209
210 fn local_point(&self) -> (Point2, Scalar) {
213 let p = Point2::new(self.sum[0].0 + self.sum[0].1, self.sum[1].0 + self.sum[1].1);
214 let error = self.remainder + self.at * (self.heading_rounding + 16.0 * Scalar::EPSILON);
215 (p, error)
216 }
217
218 fn placed_error(&self, local: Scalar) -> Scalar {
220 inflate(
221 self.stretch * local + 8.0 * Scalar::EPSILON * (self.origin + self.stretch * self.at),
222 )
223 }
224
225 pub fn point2(&mut self, s: Scalar) -> Option<(Point2, Scalar)> {
229 let Placement::Plane(frame) = self.placement else {
230 return None;
231 };
232 self.march(s)?;
233 let (p, local) = self.local_point();
234 let out = Point2::new(
235 frame.origin.x + frame.x.x * p.x + frame.y.x * p.y,
236 frame.origin.y + frame.x.y * p.x + frame.y.y * p.y,
237 );
238 Some((out, self.placed_error(local)))
239 }
240
241 pub fn point3(&mut self, s: Scalar) -> Option<(Point3, Scalar)> {
243 let Placement::Space(frame) = self.placement else {
244 return None;
245 };
246 self.march(s)?;
247 let (p, local) = self.local_point();
248 Some((
249 frame.origin + frame.x * p.x + frame.y * p.y,
250 self.placed_error(local),
251 ))
252 }
253
254 pub fn offset_point2(&mut self, s: Scalar, distance: Scalar) -> Option<(Point2, Scalar)> {
261 let Placement::Plane(frame) = self.placement else {
262 return None;
263 };
264 let (p, error) = self.point2(s)?;
265 let theta = self.local.heading_at(s)?;
266 let (c, n) = (theta.cos(), theta.sin());
267 let t = Vec2::new(frame.x.x * c + frame.y.x * n, frame.x.y * c + frame.y.y * n);
268 let size = t.length();
269 let det = (frame.x.x * frame.y.y - frame.x.y * frame.y.x).abs();
270 if !(size.is_finite() && size > 0.0 && det > 0.0 && distance.is_finite()) {
271 return None;
272 }
273 let left = Vec2::new(-t.y, t.x) / size;
274 let condition = self.stretch * self.stretch / det;
275 let turn = condition * (self.heading_rounding + 16.0 * Scalar::EPSILON);
276 Some((
277 p + left * distance,
278 inflate(error + distance.abs() * (turn + 8.0 * Scalar::EPSILON)),
279 ))
280 }
281}