1use crate::curve::interior_knot_count;
2use crate::{project_point_to_surface, NurbsCurve, NurbsSurface, Vec3};
3use serde::Serialize;
4
5const EPSILON: f64 = 1e-12;
6
7#[derive(Clone, Copy)]
8struct Bounds {
9 minimum: Vec3,
10 maximum: Vec3,
11}
12
13impl Bounds {
14 fn from_points(points: impl IntoIterator<Item = Vec3>) -> Self {
15 let mut bounds = Self {
16 minimum: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
17 maximum: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
18 };
19 for point in points {
20 bounds.minimum.x = bounds.minimum.x.min(point.x);
21 bounds.minimum.y = bounds.minimum.y.min(point.y);
22 bounds.minimum.z = bounds.minimum.z.min(point.z);
23 bounds.maximum.x = bounds.maximum.x.max(point.x);
24 bounds.maximum.y = bounds.maximum.y.max(point.y);
25 bounds.maximum.z = bounds.maximum.z.max(point.z);
26 }
27 bounds
28 }
29
30 fn expanded(self, amount: f64) -> Self {
31 let delta = Vec3::new(amount, amount, amount);
32 Self {
33 minimum: self.minimum.sub(delta),
34 maximum: self.maximum.add(delta),
35 }
36 }
37
38 fn intersects(self, other: Self) -> bool {
39 self.minimum.x <= other.maximum.x
40 && self.maximum.x >= other.minimum.x
41 && self.minimum.y <= other.maximum.y
42 && self.maximum.y >= other.minimum.y
43 && self.minimum.z <= other.maximum.z
44 && self.maximum.z >= other.minimum.z
45 }
46}
47
48fn surface_bounds(surface: &NurbsSurface) -> Result<Bounds, String> {
53 let mut bounds = Bounds::from_points([]);
54 for point in surface.control_points.iter().flatten() {
55 let point = point.point()?;
56 bounds.minimum.x = bounds.minimum.x.min(point.x);
57 bounds.minimum.y = bounds.minimum.y.min(point.y);
58 bounds.minimum.z = bounds.minimum.z.min(point.z);
59 bounds.maximum.x = bounds.maximum.x.max(point.x);
60 bounds.maximum.y = bounds.maximum.y.max(point.y);
61 bounds.maximum.z = bounds.maximum.z.max(point.z);
62 }
63 Ok(bounds)
64}
65
66
67fn span_bounds(surface: &NurbsSurface) -> Result<Vec<Bounds>, String> {
78 let (p, q) = (surface.degree_u, surface.degree_v);
79 let rows = surface.control_points.len();
80 let columns = surface.control_points.first().map_or(0, Vec::len);
81 let spans = |knots: &[f64], degree: usize, count: usize| -> Vec<usize> {
82 (degree..count).filter(|&i| i + 1 < knots.len() && knots[i] < knots[i + 1]).collect()
83 };
84 let (spans_u, spans_v) = (spans(&surface.knots_u, p, rows), spans(&surface.knots_v, q, columns));
85 if spans_u.is_empty() || spans_v.is_empty() {
87 return Ok(vec![surface_bounds(surface)?]);
88 }
89 let mut boxes = Vec::with_capacity(spans_u.len() * spans_v.len());
90 for &i in &spans_u {
91 for &j in &spans_v {
92 let mut bounds = Bounds::from_points([]);
93 for row in &surface.control_points[i - p..=i] {
94 for control in &row[j - q..=j] {
95 let point = control.point()?;
96 bounds.minimum.x = bounds.minimum.x.min(point.x);
97 bounds.minimum.y = bounds.minimum.y.min(point.y);
98 bounds.minimum.z = bounds.minimum.z.min(point.z);
99 bounds.maximum.x = bounds.maximum.x.max(point.x);
100 bounds.maximum.y = bounds.maximum.y.max(point.y);
101 bounds.maximum.z = bounds.maximum.z.max(point.z);
102 }
103 }
104 boxes.push(bounds);
105 }
106 }
107 Ok(boxes)
108}
109
110fn fit_parameter(value: f64, minimum: f64, maximum: f64, closed: bool) -> f64 {
111 if closed {
112 (value - minimum).rem_euclid(maximum - minimum) + minimum
113 } else {
114 value.clamp(minimum, maximum)
115 }
116}
117
118fn solve_three(matrix: [[f64; 3]; 3], rhs: Vec3) -> Option<[f64; 3]> {
119 let a = matrix;
120 let determinant = a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
121 - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
122 + a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
123 let scale: f64 = a.iter().flatten().map(|value| value.abs()).sum();
124 if determinant.abs() <= 1e-14 * scale.max(1.0).powi(3) {
125 return None;
126 }
127 let x = (rhs.x * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
128 - a[0][1] * (rhs.y * a[2][2] - a[1][2] * rhs.z)
129 + a[0][2] * (rhs.y * a[2][1] - a[1][1] * rhs.z))
130 / determinant;
131 let y = (a[0][0] * (rhs.y * a[2][2] - a[1][2] * rhs.z)
132 - rhs.x * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
133 + a[0][2] * (a[1][0] * rhs.z - rhs.y * a[2][0]))
134 / determinant;
135 let z = (a[0][0] * (a[1][1] * rhs.z - rhs.y * a[2][1])
136 - a[0][1] * (a[1][0] * rhs.z - rhs.y * a[2][0])
137 + rhs.x * (a[1][0] * a[2][1] - a[1][1] * a[2][0]))
138 / determinant;
139 Some([x, y, z])
140}
141
142fn newton_curve_surface(
143 curve: &NurbsCurve,
144 surface: &NurbsSurface,
145 seed: f64,
146) -> Result<[f64; 3], String> {
147 let [t0, t1] = curve.domain()?;
148 let [u0, u1] = surface.domain_u()?;
149 let [v0, v1] = surface.domain_v()?;
150 let (closed_u, closed_v) = surface.closed_directions()?;
151 let mut t = seed;
152 let projection = project_point_to_surface(surface, curve.evaluate(t)?)?;
153 let mut u = projection.u;
154 let mut v = projection.v;
155 for _ in 0..60 {
156 let (curve_point, tangent) = curve.deriv1(t)?;
157 let (surface_point, du, dv) = surface.deriv1(u, v)?;
158 let residual = curve_point.sub(surface_point);
159 if residual.length() <= EPSILON {
160 return Ok([t, u, v]);
161 }
162 let Some([mut step_t, mut step_u, mut step_v]) = solve_three(
163 [
164 [tangent.x, -du.x, -dv.x],
165 [tangent.y, -du.y, -dv.y],
166 [tangent.z, -du.z, -dv.z],
167 ],
168 residual.scale(-1.0),
169 ) else {
170 if tangent.length_squared() <= EPSILON {
171 return Ok([t, u, v]);
172 }
173 let next_t = (t - residual.dot(tangent) / tangent.length_squared()).clamp(t0, t1);
174 if (next_t - t).abs() <= 1e-15 * (t1 - t0) {
175 return Ok([t, u, v]);
176 }
177 t = next_t;
178 let projection =
182 crate::projection::project_point_to_surface_from_seed(surface, curve.evaluate(t)?, [u, v])?;
183 u = projection.u;
184 v = projection.v;
185 continue;
186 };
187 step_t = step_t.clamp(-(t1 - t0) / 4.0, (t1 - t0) / 4.0);
188 step_u = step_u.clamp(-(u1 - u0) / 4.0, (u1 - u0) / 4.0);
189 step_v = step_v.clamp(-(v1 - v0) / 4.0, (v1 - v0) / 4.0);
190 let next_t = (t + step_t).clamp(t0, t1);
191 let next_u = fit_parameter(u + step_u, u0, u1, closed_u);
192 let next_v = fit_parameter(v + step_v, v0, v1, closed_v);
193 let stalled = (next_t - t).abs() <= 1e-15 * (t1 - t0)
194 && (next_u - u).abs() <= 1e-15 * (u1 - u0)
195 && (next_v - v).abs() <= 1e-15 * (v1 - v0);
196 t = next_t;
197 u = next_u;
198 v = next_v;
199 if stalled {
200 return Ok([t, u, v]);
201 }
202 }
203 Ok([t, u, v])
204}
205
206#[derive(Clone, Copy, Debug, Serialize)]
207pub struct CurveSurfaceIntersection {
208 pub t: f64,
209 pub u: f64,
210 pub v: f64,
211 pub point: Vec3,
212 pub gap: f64,
213 pub tangential: bool,
214}
215
216fn duplicate_cap_enabled() -> bool {
220 static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
221 *ENABLED.get_or_init(|| std::env::var("BREP_CS_DUPLICATE_CAP").as_deref() != Ok("0"))
222}
223
224pub fn intersect_curve_surface(
225 curve: &NurbsCurve,
226 surface: &NurbsSurface,
227 tolerance: f64,
228) -> Result<Vec<CurveSurfaceIntersection>, String> {
229 let [t0, t1] = curve.domain()?;
230 let surface_box = surface_bounds(surface)?.expanded(tolerance * 10.0);
231 let sample_count =
232 32usize.max((interior_knot_count(&curve.knots, curve.degree) + 1) * (curve.degree + 1) * 4);
233 let mut samples = Vec::with_capacity(sample_count + 1);
234 for index in 0..=sample_count {
235 let t = t0 + (t1 - t0) * index as f64 / sample_count as f64;
236 samples.push((t, curve.evaluate(t)?));
237 }
238 let span_boxes = span_bounds(surface)?;
239 let mut seeds = Vec::new();
240 for pair in samples.windows(2) {
241 let segment_box = Bounds::from_points([pair[0].1, pair[1].1]).expanded(tolerance * 10.0);
242 if segment_box.intersects(surface_box)
243 && span_boxes.iter().any(|span_box| segment_box.intersects(span_box.expanded(tolerance * 10.0)))
244 {
245 seeds.push((pair[0].0 + pair[1].0) * 0.5);
246 }
247 }
248 let mut results: Vec<CurveSurfaceIntersection> = Vec::new();
249 for seed in seeds {
250 let [t, u, v] = newton_curve_surface(curve, surface, seed)?;
251 let on_curve = curve.evaluate(t)?;
252 let on_surface = surface.evaluate(u, v)?;
253 let gap = on_curve.sub(on_surface).length();
254 if gap > tolerance {
255 continue;
256 }
257 let tangent = curve.deriv1(t)?.1;
258 let uncapped = ((tolerance / tangent.length().max(EPSILON)) * 10.0).max((t1 - t0) * 1e-9);
281 let capped = uncapped.min((t1 - t0) * 1e-6);
282 let parameter_tolerance = if duplicate_cap_enabled() { capped } else { uncapped };
283 if let Some(result) = results.iter().find(|result| {
284 (result.t - t).abs() <= parameter_tolerance
285 || result.point.sub(on_curve).length() <= tolerance * 10.0
286 }) {
287 let chord = result.point.sub(on_curve).length();
294 let gap = (result.t - t).abs();
295 if chord > tolerance * 10.0
296 && gap > capped
297 && std::env::var("BREP_DEBUG_CS_DUPLICATE").is_ok()
298 {
299 eprintln!(
300 "cs-duplicate: dropped t={t:.12} against t={:.12} gap={gap:.6e} ptol={parameter_tolerance:.6e} \
301 chord={chord:.6e} |tangent|={:.6e} domain=[{t0:.6},{t1:.6}] tolerance={tolerance:.3e}",
302 result.t,
303 tangent.length(),
304 );
305 }
306 continue;
307 }
308 let normal = surface.normal(u, v).ok();
309 let tangential = normal
310 .and_then(|normal| {
311 tangent
312 .normalized()
313 .ok()
314 .map(|unit| normal.dot(unit).abs() <= 1e-3)
315 })
316 .unwrap_or(false);
317 results.push(CurveSurfaceIntersection {
318 t,
319 u,
320 v,
321 point: on_curve.add(on_surface).scale(0.5),
322 gap,
323 tangential,
324 });
325 }
326 results.sort_by(|a, b| a.t.total_cmp(&b.t));
327 Ok(results)
328}
329