1use axiolid_core::{Point2, Point3, Scalar};
57use axiolid_curve::{Intrinsic2, Intrinsic3};
58
59use super::positions::{add, unit_frame, Placement};
60use super::{frame_stretch2, frame_stretch3, inflate};
61use crate::chain::{curvature_bound, harmonic_rounding};
62
63pub const MAX_TABLE_PANELS: usize = 1 << 22;
65
66const CHECKPOINT_PANELS: usize = 64;
69
70const END_SLACK: Scalar = 1e-9;
73
74#[derive(Debug, Clone, Copy)]
76struct Checkpoint {
77 sum: [(Scalar, Scalar); 2],
78 remainder: Scalar,
79}
80
81#[derive(Debug, Clone, Copy)]
83struct Span {
84 lo: Scalar,
85 hi: Scalar,
86 width: Scalar,
87 panels: usize,
88 base: usize,
90 b4: Scalar,
92}
93
94#[derive(Debug, Clone)]
99pub struct IntrinsicPositionTable {
100 local: Intrinsic2,
102 placement: Placement,
103 stretch: Scalar,
104 origin: Scalar,
105 end: Scalar,
106 spans: Vec<Span>,
107 checkpoints: Vec<Checkpoint>,
108 heading_rounding: Scalar,
109 panels: usize,
110 total_remainder: Scalar,
112}
113
114fn remainder(width: Scalar, b4: Scalar) -> Scalar {
116 width.powi(5) / 4320.0 * b4
117}
118
119impl IntrinsicPositionTable {
120 #[must_use]
126 pub fn new2(curve: &Intrinsic2, error: Scalar, end: Scalar) -> Option<Self> {
127 let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
128 Self::build(
129 local,
130 Placement::Plane(curve.start),
131 frame_stretch2(&curve.start),
132 curve.start.origin.abs().max_element(),
133 error,
134 end,
135 )
136 }
137
138 #[must_use]
142 pub fn new3(curve: &Intrinsic3, error: Scalar, end: Scalar) -> Option<Self> {
143 if !curve.is_planar() {
144 return None;
145 }
146 let local = Intrinsic2::new(unit_frame(), curve.curvature.clone(), curve.length);
147 Self::build(
148 local,
149 Placement::Space(curve.start),
150 frame_stretch3(&curve.start),
151 curve.start.origin.abs().max_element(),
152 error,
153 end,
154 )
155 }
156
157 fn build(
158 local: Intrinsic2,
159 placement: Placement,
160 stretch: Scalar,
161 origin: Scalar,
162 error: Scalar,
163 end: Scalar,
164 ) -> Option<Self> {
165 let length = local.length;
166 if !(error.is_finite() && error > 0.0 && length.is_finite() && length > 0.0) {
167 return None;
168 }
169 if !(end.is_finite() && end > 0.0 && end <= length + END_SLACK * length.max(1.0)) {
170 return None;
171 }
172 let end = end.min(length);
173 let law = &local.curvature;
174 let rate = law.derivative();
175 let second = rate.derivative();
176 let third = second.derivative();
177 let seams: Vec<Scalar> = law
178 .seams_within(end)
179 .into_iter()
180 .filter(|&s| s > 0.0 && s < end)
181 .collect();
182 let mut ends = vec![0.0];
183 ends.extend(seams.iter().copied());
184 ends.push(end);
185 let mut pieces = Vec::with_capacity(ends.len() - 1);
186 let mut worst: Scalar = 0.0;
187 for pair in ends.windows(2) {
188 let (lo, hi) = (pair[0], pair[1]);
189 let k = curvature_bound(law, lo, hi)?;
190 let k1 = curvature_bound(&rate, lo, hi)?;
191 let k2 = curvature_bound(&second, lo, hi)?;
192 let k3 = curvature_bound(&third, lo, hi)?;
193 let b4 = inflate(k3 + 4.0 * k * k2 + 3.0 * k1 * k1 + 6.0 * k * k * k1 + k.powi(4));
194 if !b4.is_finite() {
195 return None;
196 }
197 worst = worst.max(b4);
198 pieces.push((lo, hi, b4));
199 }
200 let budget = 0.5 * error / stretch;
201 let step = if worst > 0.0 {
202 (4320.0 * budget / (end * worst)).powf(0.25).min(end)
203 } else {
204 end
205 };
206 if !(step.is_finite() && step > 0.0) {
207 return None;
208 }
209 let mut spans = Vec::with_capacity(pieces.len());
210 let mut total = 0usize;
211 for (lo, hi, b4) in pieces {
212 let count = ((hi - lo) / step).ceil().max(1.0);
213 if count > MAX_TABLE_PANELS as Scalar || total + count as usize > MAX_TABLE_PANELS {
214 return None;
215 }
216 let panels = count as usize;
217 spans.push(Span {
218 lo,
219 hi,
220 width: (hi - lo) / count,
221 panels,
222 base: total,
223 b4,
224 });
225 total += panels;
226 }
227 let turning = curvature_bound(law, 0.0, end)? * end;
228 let heading_rounding =
229 64.0 * Scalar::EPSILON * (1.0 + turning) * (1.0 + seams.len() as Scalar)
230 + harmonic_rounding(law, end)?;
231 let mut table = Self {
232 local,
233 placement,
234 stretch,
235 origin,
236 end,
237 spans,
238 checkpoints: Vec::with_capacity(total / CHECKPOINT_PANELS + 1),
239 heading_rounding,
240 panels: total,
241 total_remainder: 0.0,
242 };
243 let mut state = Checkpoint {
244 sum: [(0.0, 0.0); 2],
245 remainder: 0.0,
246 };
247 for index in 0..total {
248 if index % CHECKPOINT_PANELS == 0 {
249 table.checkpoints.push(state);
250 }
251 table.add_panel(index, &mut state)?;
252 }
253 table.total_remainder = state.remainder;
254 Some(table)
255 }
256
257 #[must_use]
259 pub fn panels(&self) -> usize {
260 self.panels
261 }
262
263 fn span_of(&self, index: usize) -> &Span {
265 let at = self.spans.partition_point(|s| s.base + s.panels <= index);
266 &self.spans[at.min(self.spans.len() - 1)]
267 }
268
269 fn grid(span: &Span, i: usize) -> Scalar {
271 if i == 0 {
272 span.lo
273 } else {
274 span.lo + i as Scalar * span.width
275 }
276 }
277
278 fn rule(&self, a: Scalar, w: Scalar, sum: &mut [(Scalar, Scalar); 2]) -> Option<()> {
281 let half = 0.5 * w;
282 let offset = half / (3.0 as Scalar).sqrt();
283 for x in [a + half - offset, a + half + offset] {
284 let theta = self.local.heading_at(x)?;
285 add(&mut sum[0], half * theta.cos());
286 add(&mut sum[1], half * theta.sin());
287 }
288 Some(())
289 }
290
291 fn add_panel(&self, index: usize, state: &mut Checkpoint) -> Option<()> {
293 let span = self.span_of(index);
294 let a = Self::grid(span, index - span.base);
295 self.rule(a, span.width, &mut state.sum)?;
296 state.remainder += remainder(span.width, span.b4);
297 Some(())
298 }
299
300 fn local_point(&self, s: Scalar) -> Option<(Point2, Scalar)> {
303 if !(s.is_finite() && (0.0..=self.end).contains(&s)) {
304 return None;
305 }
306 let at = self.spans.partition_point(|span| span.hi < s);
307 let span = self.spans.get(at)?;
308 let i = (((s - span.lo) / span.width).floor().max(0.0) as usize).min(span.panels - 1);
309 let index = span.base + i;
310 let first = index - index % CHECKPOINT_PANELS;
311 let mut state = self.checkpoints[index / CHECKPOINT_PANELS];
312 for panel in first..index {
313 self.add_panel(panel, &mut state)?;
314 }
315 let from = Self::grid(span, i);
316 let rest = (s - from).max(0.0);
317 if rest > 0.0 {
318 self.rule(from, rest, &mut state.sum)?;
319 state.remainder += remainder(rest, span.b4);
320 }
321 let p = Point2::new(
322 state.sum[0].0 + state.sum[0].1,
323 state.sum[1].0 + state.sum[1].1,
324 );
325 let error = state.remainder + s * (self.heading_rounding + 16.0 * Scalar::EPSILON);
326 Some((p, error))
327 }
328
329 fn placed_error(&self, local: Scalar, s: Scalar) -> Scalar {
332 inflate(self.stretch * local + 8.0 * Scalar::EPSILON * (self.origin + self.stretch * s))
333 }
334
335 #[must_use]
340 pub fn point2(&self, s: Scalar) -> Option<(Point2, Scalar)> {
341 let Placement::Plane(frame) = self.placement else {
342 return None;
343 };
344 let (p, local) = self.local_point(s)?;
345 let out = Point2::new(
346 frame.origin.x + frame.x.x * p.x + frame.y.x * p.y,
347 frame.origin.y + frame.x.y * p.x + frame.y.y * p.y,
348 );
349 Some((out, self.placed_error(local, s)))
350 }
351
352 #[must_use]
354 pub fn point3(&self, s: Scalar) -> Option<(Point3, Scalar)> {
355 let Placement::Space(frame) = self.placement else {
356 return None;
357 };
358 let (p, local) = self.local_point(s)?;
359 Some((
360 frame.origin + frame.x * p.x + frame.y * p.y,
361 self.placed_error(local, s),
362 ))
363 }
364
365 #[must_use]
370 pub fn max_error(&self) -> Scalar {
371 let local = inflate(
372 self.total_remainder + self.end * (self.heading_rounding + 16.0 * Scalar::EPSILON),
373 );
374 self.placed_error(local, self.end)
375 }
376
377 #[must_use]
380 pub fn heading_rounding(&self) -> Scalar {
381 self.heading_rounding
382 }
383}