1use crate::curve::interior_knot_count;
2use crate::{project_point_to_surface, NurbsCurve, NurbsSurface, Vec3};
3use serde::Serialize;
4
5const EPSILON: f64 = crate::tolerance::FP_FLOOR;
6
7use crate::spatial::Aabb as Bounds;
8
9
10fn surface_bounds(surface: &NurbsSurface) -> Result<Bounds, String> {
15 let mut bounds = Bounds::from_points([]);
16 for point in surface.control_points.iter().flatten() {
17 let point = point.point()?;
18 bounds.minimum.x = bounds.minimum.x.min(point.x);
19 bounds.minimum.y = bounds.minimum.y.min(point.y);
20 bounds.minimum.z = bounds.minimum.z.min(point.z);
21 bounds.maximum.x = bounds.maximum.x.max(point.x);
22 bounds.maximum.y = bounds.maximum.y.max(point.y);
23 bounds.maximum.z = bounds.maximum.z.max(point.z);
24 }
25 Ok(bounds)
26}
27
28
29fn span_bounds(surface: &NurbsSurface) -> Result<Vec<Bounds>, String> {
40 let (p, q) = (surface.degree_u, surface.degree_v);
41 let rows = surface.control_points.len();
42 let columns = surface.control_points.first().map_or(0, Vec::len);
43 let spans = |knots: &[f64], degree: usize, count: usize| -> Vec<usize> {
44 (degree..count).filter(|&i| i + 1 < knots.len() && knots[i] < knots[i + 1]).collect()
45 };
46 let (spans_u, spans_v) = (spans(&surface.knots_u, p, rows), spans(&surface.knots_v, q, columns));
47 if spans_u.is_empty() || spans_v.is_empty() {
49 return Ok(vec![surface_bounds(surface)?]);
50 }
51 let mut boxes = Vec::with_capacity(spans_u.len() * spans_v.len());
52 for &i in &spans_u {
53 for &j in &spans_v {
54 let mut bounds = Bounds::from_points([]);
55 for row in &surface.control_points[i - p..=i] {
56 for control in &row[j - q..=j] {
57 let point = control.point()?;
58 bounds.minimum.x = bounds.minimum.x.min(point.x);
59 bounds.minimum.y = bounds.minimum.y.min(point.y);
60 bounds.minimum.z = bounds.minimum.z.min(point.z);
61 bounds.maximum.x = bounds.maximum.x.max(point.x);
62 bounds.maximum.y = bounds.maximum.y.max(point.y);
63 bounds.maximum.z = bounds.maximum.z.max(point.z);
64 }
65 }
66 boxes.push(bounds);
67 }
68 }
69 Ok(boxes)
70}
71
72fn fit_parameter(value: f64, minimum: f64, maximum: f64, closed: bool) -> f64 {
73 crate::projection::fit_parameter(value, minimum, maximum, closed)
74}
75
76fn solve_three(matrix: [[f64; 3]; 3], rhs: Vec3) -> Option<[f64; 3]> {
77 let a = matrix;
78 let determinant = a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
79 - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
80 + a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
81 let scale: f64 = a.iter().flatten().map(|value| value.abs()).sum();
82 if determinant.abs() <= 1e-14 * scale.max(1.0).powi(3) {
83 return None;
84 }
85 let x = (rhs.x * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
86 - a[0][1] * (rhs.y * a[2][2] - a[1][2] * rhs.z)
87 + a[0][2] * (rhs.y * a[2][1] - a[1][1] * rhs.z))
88 / determinant;
89 let y = (a[0][0] * (rhs.y * a[2][2] - a[1][2] * rhs.z)
90 - rhs.x * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
91 + a[0][2] * (a[1][0] * rhs.z - rhs.y * a[2][0]))
92 / determinant;
93 let z = (a[0][0] * (a[1][1] * rhs.z - rhs.y * a[2][1])
94 - a[0][1] * (a[1][0] * rhs.z - rhs.y * a[2][0])
95 + rhs.x * (a[1][0] * a[2][1] - a[1][1] * a[2][0]))
96 / determinant;
97 Some([x, y, z])
98}
99
100fn newton_curve_surface(
101 curve: &NurbsCurve,
102 surface: &NurbsSurface,
103 seed: f64,
104) -> Result<[f64; 3], String> {
105 let [t0, t1] = curve.domain()?;
106 let [u0, u1] = surface.domain_u()?;
107 let [v0, v1] = surface.domain_v()?;
108 let (closed_u, closed_v) = surface.closed_directions()?;
109 let mut t = seed;
110 let projection = project_point_to_surface(surface, curve.evaluate(t)?)?;
111 let mut u = projection.u;
112 let mut v = projection.v;
113 for _ in 0..60 {
114 let (curve_point, tangent) = curve.deriv1(t)?;
115 let (surface_point, du, dv) = surface.deriv1(u, v)?;
116 let residual = curve_point.sub(surface_point);
117 if residual.length() <= EPSILON {
118 return Ok([t, u, v]);
119 }
120 let Some([mut step_t, mut step_u, mut step_v]) = solve_three(
121 [
122 [tangent.x, -du.x, -dv.x],
123 [tangent.y, -du.y, -dv.y],
124 [tangent.z, -du.z, -dv.z],
125 ],
126 residual.scale(-1.0),
127 ) else {
128 if tangent.length_squared() <= EPSILON {
129 return Ok([t, u, v]);
130 }
131 let next_t = (t - residual.dot(tangent) / tangent.length_squared()).clamp(t0, t1);
132 if (next_t - t).abs() <= 1e-15 * (t1 - t0) {
133 return Ok([t, u, v]);
134 }
135 t = next_t;
136 let projection =
140 crate::projection::project_point_to_surface_from_seed(surface, curve.evaluate(t)?, [u, v])?;
141 u = projection.u;
142 v = projection.v;
143 continue;
144 };
145 step_t = step_t.clamp(-(t1 - t0) / 4.0, (t1 - t0) / 4.0);
146 step_u = step_u.clamp(-(u1 - u0) / 4.0, (u1 - u0) / 4.0);
147 step_v = step_v.clamp(-(v1 - v0) / 4.0, (v1 - v0) / 4.0);
148 let next_t = (t + step_t).clamp(t0, t1);
149 let next_u = fit_parameter(u + step_u, u0, u1, closed_u);
150 let next_v = fit_parameter(v + step_v, v0, v1, closed_v);
151 let stalled = (next_t - t).abs() <= 1e-15 * (t1 - t0)
152 && (next_u - u).abs() <= 1e-15 * (u1 - u0)
153 && (next_v - v).abs() <= 1e-15 * (v1 - v0);
154 t = next_t;
155 u = next_u;
156 v = next_v;
157 if stalled {
158 return Ok([t, u, v]);
159 }
160 }
161 Ok([t, u, v])
162}
163
164#[derive(Clone, Copy, Debug, Serialize)]
165pub struct CurveSurfaceIntersection {
166 pub t: f64,
167 pub u: f64,
168 pub v: f64,
169 pub point: Vec3,
170 pub gap: f64,
171 pub tangential: bool,
172}
173
174fn duplicate_cap_enabled() -> bool {
178 switch!("BREP_CS_DUPLICATE_CAP", true)
179}
180
181pub fn intersect_curve_surface(
182 curve: &NurbsCurve,
183 surface: &NurbsSurface,
184 tolerance: f64,
185) -> Result<Vec<CurveSurfaceIntersection>, String> {
186 let _lap = crate::projection_profile::Lap::enter(crate::projection_profile::LANE_INTERSECT_CURVE_SURFACE, surface);
187 let seeds = sampled_seeds(curve, surface, tolerance)?;
188 intersect_curve_surface_from_seeds(curve, surface, tolerance, seeds)
189}
190
191fn sampled_seeds(curve: &NurbsCurve, surface: &NurbsSurface, tolerance: f64) -> Result<Vec<f64>, String> {
194 let [t0, t1] = curve.domain()?;
195 let surface_box = surface_bounds(surface)?.expanded(tolerance * 10.0);
196 let sample_count =
197 32usize.max((interior_knot_count(&curve.knots, curve.degree) + 1) * (curve.degree + 1) * 4);
198 let mut samples = Vec::with_capacity(sample_count + 1);
199 for index in 0..=sample_count {
200 let t = t0 + (t1 - t0) * index as f64 / sample_count as f64;
201 samples.push((t, curve.evaluate(t)?));
202 }
203 let span_boxes = span_bounds(surface)?;
204 let mut seeds = Vec::new();
205 for pair in samples.windows(2) {
206 let segment_box = Bounds::from_points([pair[0].1, pair[1].1]).expanded(tolerance * 10.0);
207 if segment_box.intersects_exact(surface_box)
208 && span_boxes.iter().any(|span_box| segment_box.intersects_exact(span_box.expanded(tolerance * 10.0)))
209 {
210 seeds.push((pair[0].0 + pair[1].0) * 0.5);
211 }
212 }
213 Ok(seeds)
214}
215
216pub(crate) fn intersect_curve_surface_from_seeds(
222 curve: &NurbsCurve,
223 surface: &NurbsSurface,
224 tolerance: f64,
225 seeds: Vec<f64>,
226) -> Result<Vec<CurveSurfaceIntersection>, String> {
227 let [t0, t1] = curve.domain()?;
228 let mut results: Vec<CurveSurfaceIntersection> = Vec::new();
229 for seed in seeds {
230 let [t, u, v] = newton_curve_surface(curve, surface, seed)?;
231 let on_curve = curve.evaluate(t)?;
232 let on_surface = surface.evaluate(u, v)?;
233 let gap = on_curve.sub(on_surface).length();
234 if gap > tolerance {
235 continue;
236 }
237 let tangent = curve.deriv1(t)?.1;
238 let uncapped = ((tolerance / tangent.length().max(EPSILON)) * 10.0).max((t1 - t0) * 1e-9);
261 let capped = uncapped.min((t1 - t0) * 1e-6);
262 let parameter_tolerance = if duplicate_cap_enabled() { capped } else { uncapped };
263 if let Some(result) = results.iter().find(|result| {
264 (result.t - t).abs() <= parameter_tolerance
265 || result.point.sub(on_curve).length() <= tolerance * 10.0
266 }) {
267 let chord = result.point.sub(on_curve).length();
274 let gap = (result.t - t).abs();
275 if chord > tolerance * 10.0
276 && gap > capped
277 && std::env::var("BREP_DEBUG_CS_DUPLICATE").is_ok()
278 {
279 eprintln!(
280 "cs-duplicate: dropped t={t:.12} against t={:.12} gap={gap:.6e} ptol={parameter_tolerance:.6e} \
281 chord={chord:.6e} |tangent|={:.6e} domain=[{t0:.6},{t1:.6}] tolerance={tolerance:.3e}",
282 result.t,
283 tangent.length(),
284 );
285 }
286 continue;
287 }
288 let normal = surface.normal(u, v).ok();
289 let tangential = normal
290 .and_then(|normal| {
291 tangent
292 .normalized()
293 .ok()
294 .map(|unit| normal.dot(unit).abs() <= 1e-3)
295 })
296 .unwrap_or(false);
297 results.push(CurveSurfaceIntersection {
298 t,
299 u,
300 v,
301 point: on_curve.add(on_surface).scale(0.5),
302 gap,
303 tangential,
304 });
305 }
306 results.sort_by(|a, b| a.t.total_cmp(&b.t));
307 Ok(results)
308}
309