1use super::*;
2use crate::Vec4;
3
4const HARD_SPANS: usize = 65_536;
5
6#[derive(Clone, Debug, PartialEq)]
8pub struct EdgeChordError {
9 pub edge_id: u64,
10 pub requested: f64,
11 pub reason: EdgeChordFailure,
12}
13
14#[derive(Clone, Debug, PartialEq)]
15pub enum EdgeChordFailure {
16 InvalidTolerance,
17 UnsupportedCurve,
18 EndpointMismatch,
19 DegenerateEdge,
20 SafetyCap,
21 ParameterResolution,
22 Evaluation(String),
23}
24
25impl std::fmt::Display for EdgeChordError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "edge chord tolerance: edge {} requested {:e}: {:?}", self.edge_id, self.requested, self.reason)
28 }
29}
30impl std::error::Error for EdgeChordError {}
31
32#[derive(Clone, Debug, PartialEq)]
35pub enum TessellationError {
36 EdgeChord(EdgeChordError),
37 Geometry(String),
38}
39impl From<String> for TessellationError {
40 fn from(value: String) -> Self { Self::Geometry(value) }
41}
42impl From<EdgeChordError> for TessellationError {
43 fn from(value: EdgeChordError) -> Self { Self::EdgeChord(value) }
44}
45impl std::fmt::Display for TessellationError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self { Self::EdgeChord(e) => e.fmt(f), Self::Geometry(e) => f.write_str(e) }
48 }
49}
50impl std::error::Error for TessellationError {}
51
52pub(super) fn enabled() -> bool {
53 crate::switch!("BREP_TESS_CHORD_BUDGET", true)
54}
55
56fn certificate_spans(curve: &crate::NurbsCurve) -> Option<Vec<Vec<Vec4>>> {
57 let p = curve.degree;
58 let points = &curve.control_points;
59 let knots = &curve.knots;
60 if points.len() < p + 1 || knots.len() != points.len() + p + 1 { return None; }
61 let (first, last) = (*knots.first()?, *knots.last()?);
62 let head = knots.iter().take_while(|&&k| k == first).count().saturating_sub(p + 1);
66 let tail = knots.iter().rev().take_while(|&&k| k == last).count().saturating_sub(p + 1);
67 if head + tail > points.len() - p - 1 { return None; }
68 crate::curve::bezier_decompose_raw(&knots[head..knots.len() - tail],
69 &points[head..points.len() - tail], p,
70 Vec4 { x: 0., y: 0., z: 0., w: 0. },
71 |a, b, alpha| a.scale(alpha).add(b.scale(1. - alpha)))
72 .or_else(|| separated_spans(&knots[head..knots.len() - tail],
73 &points[head..points.len() - tail], p))
74}
75
76#[cold]
80#[inline(never)]
81fn separated_spans(knots: &[f64], points: &[Vec4], p: usize) -> Option<Vec<Vec<Vec4>>> {
82 if p == 0 || points.len() < p + 1 || knots.len() != points.len() + p + 1
83 || knots.iter().any(|k| !k.is_finite()) || knots.windows(2).any(|k| k[1] < k[0]) {
84 return None;
85 }
86 let decompose = |knots: &[f64], points: &[Vec4]| crate::curve::bezier_decompose_raw(
87 knots, points, p, Vec4 { x: 0., y: 0., z: 0., w: 0. },
88 |a, b, alpha| a.scale(alpha).add(b.scale(1. - alpha)));
89 let mut spans = Vec::new();
90 let (mut knot_begin, mut point_begin) = (0, 0);
91 let mut i = p + 1;
92 while i < knots.len() - p - 1 {
93 let mut end = i + 1;
94 while end < knots.len() && knots[end] == knots[i] { end += 1; }
95 let multiplicity = end - i;
96 if multiplicity > p + 1 { return None; }
97 if multiplicity == p + 1 {
98 let point_end = point_begin + end - knot_begin - p - 1;
99 spans.extend(decompose(&knots[knot_begin..end], points.get(point_begin..point_end)?)?);
100 knot_begin = i;
101 point_begin = point_end;
102 }
103 i = end;
104 }
105 if knot_begin == 0 { return None; }
106 spans.extend(decompose(&knots[knot_begin..], points.get(point_begin..)?)?);
107 Some(spans)
108}
109
110fn control_bound<const N: usize>(points: &[Vec4], lo: f64, hi: f64, a: Vec3, b: Vec3, tolerance: f64) -> f64 {
111 let zero = Vec4 { x: 0., y: 0., z: 0., w: 0. };
112 let mut work = [zero; N];
113 let mut net = [zero; N];
114 let count = points.len();
115 work[..count].copy_from_slice(points);
116 net[0] = work[0];
117 for n in (1..count).rev() {
118 for i in 0..n { work[i] = work[i].scale(1. - hi).add(work[i + 1].scale(hi)); }
119 net[count - n] = work[0];
120 }
121 if lo > 0. {
122 let t = lo / hi;
123 work[..count].copy_from_slice(&net[..count]);
124 for n in (1..count).rev() {
125 for i in 0..n { work[i] = work[i].scale(1. - t).add(work[i + 1].scale(t)); }
126 net[n - 1] = work[n - 1];
127 }
128 }
129 tight_bound::<N>(&net[..count], a, b, tolerance, 3)
130}
131
132fn tight_bound<const N: usize>(points: &[Vec4], a: Vec3, b: Vec3, tolerance: f64, depth: usize) -> f64 {
133 let distance = |p: Vec4| deflection_to_chord(Vec3::new(p.x / p.w, p.y / p.w, p.z / p.w), a, b);
134 let mut bound = 0.0f64;
135 for &p in points {
136 let d = distance(p);
137 if !d.is_finite() { return f64::INFINITY; }
138 bound = bound.max(d);
139 }
140 if bound <= tolerance || depth == 0 { return bound; }
141 let zero = Vec4 { x: 0., y: 0., z: 0., w: 0. };
145 let mut work = [zero; N];
146 let mut left = [zero; N];
147 let mut right = [zero; N];
148 let count = points.len();
149 work[..count].copy_from_slice(points);
150 left[0] = work[0];
151 right[count - 1] = work[count - 1];
152 for n in (1..count).rev() {
153 for i in 0..n { work[i] = work[i].scale(0.5).add(work[i + 1].scale(0.5)); }
154 left[count - n] = work[0];
155 right[n - 1] = work[n - 1];
156 }
157 if distance(work[0]) > tolerance { return bound; }
160 tight_bound::<N>(&left[..count], a, b, tolerance, depth - 1)
161 .max(tight_bound::<N>(&right[..count], a, b, tolerance, depth - 1))
162}
163
164pub fn sample_edge_polyline_checked(
171 edge: &EdgeRecord,
172 start: Vec3,
173 end: Vec3,
174 chord_tolerance: f64,
175) -> Result<Vec<(f64, Vec3)>, EdgeChordError> {
176 refine(edge, start, end, chord_tolerance, &[0.0, 1.0], HARD_SPANS)
177}
178
179fn full_line_certified(edge: &EdgeRecord, start: Vec3, end: Vec3, tolerance: f64, seeds: &[f64]) -> bool {
184 let curve = &edge.curve;
185 if curve.degree != 1 || curve.control_points.len() != 2 || curve.knots.len() != 4
186 || !curve.is_validated() || seeds != [0., 1.] { return false; }
187 let k = &curve.knots;
188 if k.iter().any(|v| !v.is_finite()) || k[0] != k[1] || k[2] != k[3]
189 || k[2] - k[0] <= crate::tolerance::FP_FLOOR
190 || !(edge.t1 - edge.t0).is_finite() { return false; }
191 let forward = edge.t0 == k[0] && edge.t1 == k[2];
192 if !forward && !(edge.t0 == k[2] && edge.t1 == k[0]) { return false; }
193 if curve.control_points.iter().any(|p| p.w <= crate::tolerance::FP_FLOOR
194 || ![p.x, p.y, p.z, p.w].iter().all(|v| v.is_finite()))
195 || ![start.x, start.y, start.z, end.x, end.y, end.z].iter().all(|v| v.is_finite()) {
196 return false;
197 }
198 let poles = &curve.control_points;
199 let a = poles[usize::from(!forward)].point().unwrap();
200 let b = poles[usize::from(forward)].point().unwrap();
201 let scale = [a.x.abs(), a.y.abs(), a.z.abs(), b.x.abs(), b.y.abs(), b.z.abs(),
202 start.x.abs(), start.y.abs(), start.z.abs(), end.x.abs(), end.y.abs(), end.z.abs()]
203 .into_iter().fold(0.0f64, f64::max);
204 let roundoff = 128. * f64::EPSILON * scale;
205 if !roundoff.is_finite() || tolerance <= roundoff || tolerance < 16. * f64::MIN_POSITIVE.sqrt() {
206 return false;
207 }
208 let budget = tolerance - roundoff;
209 a.sub(start).length() <= budget && b.sub(end).length() <= budget
210}
211
212pub(super) fn refine(
213 edge: &EdgeRecord,
214 start: Vec3,
215 end: Vec3,
216 tolerance: f64,
217 seeds: &[f64],
218 cap: usize,
219) -> Result<Vec<(f64, Vec3)>, EdgeChordError> {
220 let fail = |reason| EdgeChordError { edge_id: edge.id, requested: tolerance, reason };
221 if !tolerance.is_finite() || tolerance <= 0.0 {
222 return Err(fail(EdgeChordFailure::InvalidTolerance));
223 }
224 if seeds.len().saturating_sub(1) > cap {
225 return Err(fail(EdgeChordFailure::SafetyCap));
226 }
227 if edge.curve.degree > 64 || edge.curve.control_points.len() > HARD_SPANS {
228 return Err(fail(EdgeChordFailure::SafetyCap));
229 }
230 if full_line_certified(edge, start, end, tolerance, seeds) {
231 return Ok(vec![(0., start), (1., end)]);
232 }
233 let spans = certificate_spans(&edge.curve)
234 .ok_or_else(|| fail(EdgeChordFailure::UnsupportedCurve))?;
235 let knots: Vec<_> = edge.curve.knots.windows(2).filter(|p| p[1] > p[0])
236 .map(|p| (p[0], p[1])).collect();
237 if spans.len() != knots.len() || spans.is_empty()
238 || !edge.t0.is_finite() || !edge.t1.is_finite()
239 || !(edge.t1 - edge.t0).is_finite()
240 || spans.iter().flatten().any(|p| p.w <= 0.0 || ![p.x, p.y, p.z, p.w].iter().all(|v| v.is_finite()))
241 || ![start.x, start.y, start.z, end.x, end.y, end.z].iter().all(|v| v.is_finite())
242 {
243 return Err(fail(EdgeChordFailure::UnsupportedCurve));
244 }
245 let parameter = |f: f64| {
246 if f == 0. { edge.t0 } else if f == 1. { edge.t1 }
247 else { edge.t0 + f * (edge.t1 - edge.t0) }
248 };
249 let evaluate = |f: f64| edge.curve.evaluate(parameter(f))
250 .map_err(|e| fail(EdgeChordFailure::Evaluation(e)));
251 let coordinate_scale = spans.iter().flatten()
252 .flat_map(|p| [p.x.abs() / p.w, p.y.abs() / p.w, p.z.abs() / p.w])
253 .chain([start.x.abs(), start.y.abs(), start.z.abs(), end.x.abs(), end.y.abs(), end.z.abs()])
254 .fold(0.0f64, f64::max);
255 let roundoff = 64. * f64::EPSILON * (edge.curve.degree + 1) as f64 * coordinate_scale;
259 if !roundoff.is_finite() || tolerance <= roundoff || tolerance < 16. * f64::MIN_POSITIVE.sqrt() {
260 return Err(fail(EdgeChordFailure::ParameterResolution));
261 }
262 let budget = tolerance - roundoff;
263 let endpoint = |t: f64| {
268 let last = knots.len() - 1;
269 let pole = if !edge.curve.is_validated() { None }
270 else if t == knots[0].0 && knots[0].1 - knots[0].0 > crate::tolerance::FP_FLOOR {
271 spans[0].first()
272 } else if t == knots[last].1 && knots[last].1 - knots[last].0 > crate::tolerance::FP_FLOOR {
273 spans.last().unwrap().last()
274 } else { None };
275 pole.map_or_else(|| edge.curve.evaluate(t), |p| p.point())
276 .map_err(|e| fail(EdgeChordFailure::Evaluation(e)))
277 };
278 if endpoint(edge.t0)?.sub(start).length() > tolerance || endpoint(edge.t1)?.sub(end).length() > tolerance {
279 return Err(fail(EdgeChordFailure::EndpointMismatch));
280 }
281 let mut output = vec![(0.0, start)];
282 let mut pending = Vec::new();
283 let mut b = end;
284 for pair in seeds.windows(2).rev() {
285 let a = if pair[0] == 0.0 { start } else { evaluate(pair[0])? };
286 pending.push((pair[0], pair[1], a, b));
287 b = a;
288 }
289 while let Some((f0, f1, a, b)) = pending.pop() {
290 let domain = (knots[0].0, knots.last().unwrap().1);
293 let t0 = parameter(f0).clamp(domain.0, domain.1);
294 let t1 = parameter(f1).clamp(domain.0, domain.1);
295 let mut bound = 0.0f64;
296 let first = knots.partition_point(|(_, hi)| *hi < t0.min(t1));
297 for ((lo, hi), points) in knots[first..].iter().zip(&spans[first..]) {
298 if *lo > t0.max(t1) { break; }
299 let from = t0.min(t1).max(*lo);
300 let to = t0.max(t1).min(*hi);
301 if to < from { continue; }
302 let u = ((from - lo) / (hi - lo)).clamp(0.0, 1.0);
303 let v = ((to - lo) / (hi - lo)).clamp(0.0, 1.0);
304 let distance = if points.len() <= 4 {
305 control_bound::<4>(points, u, v, a, b, budget)
306 } else {
307 control_bound::<65>(points, u, v, a, b, budget)
308 };
309 if !distance.is_finite() { return Err(fail(EdgeChordFailure::UnsupportedCurve)); }
310 bound = bound.max(distance);
311 }
312 if bound <= budget {
313 output.push((f1, b));
314 continue;
315 }
316 if edge.degenerate { return Err(fail(EdgeChordFailure::DegenerateEdge)); }
317 if output.len() + pending.len() >= cap {
318 return Err(fail(EdgeChordFailure::SafetyCap));
319 }
320 let middle = f0 + (f1 - f0) * 0.5;
321 if middle <= f0 || middle >= f1 || parameter(middle) == t0
322 || parameter(middle) == t1 {
323 return Err(fail(EdgeChordFailure::ParameterResolution));
324 }
325 let m = evaluate(middle)?;
326 pending.push((middle, f1, m, b));
327 pending.push((f0, middle, a, m));
328 }
329 Ok(output)
330}
331
332pub(super) fn refine_shared(edge: &EdgeRecord, start: Vec3, end: Vec3, tolerance: f64, seeds: &[f64])
333 -> Result<Vec<(f64, Vec3)>, EdgeChordError>
334{
335 refine(edge, start, end, tolerance, seeds, HARD_SPANS)
336}