1use super::*;
2use super::recognition::{generatrix_is_meridional_half_ray, surface_scale};
3use super::revolution::{intersect_coaxial_revolutions, rotate_curve_about_axis};
4
5struct PlaneData {
7 origin: Vec3,
8 normal: Vec3,
9}
10
11fn plane_data(analytic: &AnalyticSurface) -> Option<PlaneData> {
12 match analytic {
13 AnalyticSurface::Plane {
14 origin,
15 u_dir,
16 v_dir,
17 ..
18 } => Some(PlaneData {
19 origin: *origin,
20 normal: u_dir.cross(*v_dir).normalized().ok()?,
21 }),
22 _ => None,
23 }
24}
25
26fn map_rational_curve(
31 curve: &NurbsCurve,
32 map: impl Fn(crate::Vec4) -> crate::Vec4,
33) -> Option<NurbsCurve> {
34 let mut controls = Vec::with_capacity(curve.control_points.len());
35 let mut sign = 0.0f64;
36 for point in &curve.control_points {
37 let image = map(*point);
38 if image.w == 0.0 || !image.w.is_finite() {
39 return None;
40 }
41 if sign == 0.0 {
42 sign = image.w.signum();
43 } else if image.w.signum() != sign {
44 return None;
45 }
46 controls.push(image);
47 }
48 if sign < 0.0 {
49 for point in &mut controls {
50 point.x = -point.x;
51 point.y = -point.y;
52 point.z = -point.z;
53 point.w = -point.w;
54 }
55 }
56 NurbsCurve::new(curve.degree, curve.knots.clone(), controls).ok()
57}
58
59fn frame_circle(frame: &RevolutionFrame, rho: f64, z: f64) -> Option<NurbsCurve> {
62 make_arc(
63 frame.origin.add(frame.axis.scale(z)),
64 frame.x_axis,
65 frame.y_axis,
66 rho,
67 0.0,
68 std::f64::consts::TAU,
69 )
70 .ok()
71}
72
73pub fn intersect_analytic_pair(
77 first: &NurbsSurface,
78 second: &NurbsSurface,
79 tolerance: f64,
80) -> Option<Vec<NurbsCurve>> {
81 if let Some(curves) = intersect_recognized_pair(first, second, tolerance) {
82 return Some(curves);
83 }
84 if let Some(curves) = intersect_coaxial_revolutions(first, second, tolerance) {
85 return Some(curves);
86 }
87 if let Some(curves) = intersect_axial_plane_revolution(first, second, tolerance) {
88 return Some(curves);
89 }
90 intersect_axial_plane_revolution(second, first, tolerance)
91}
92
93fn intersect_axial_plane_revolution(
99 plane_surface: &NurbsSurface,
100 revolved: &NurbsSurface,
101 _tolerance: f64,
102) -> Option<Vec<NurbsCurve>> {
103 let plane = match plane_surface.analytic()? {
104 AnalyticSurface::Plane {
105 origin,
106 u_dir,
107 v_dir,
108 ..
109 } => (*origin, u_dir.cross(*v_dir).normalized().ok()?),
110 _ => return None,
111 };
112 let structure = revolution_structure(revolved)?;
113 if !generatrix_is_meridional_half_ray(&structure.generatrix, &structure.frame) {
114 return None;
115 }
116 let scale = surface_scale(plane_surface)
117 .max(surface_scale(revolved))
118 .max(1.0);
119 let (plane_origin, plane_normal) = plane;
120 if structure.frame.axis.dot(plane_normal).abs() > 1e-9 {
121 return None;
122 }
123 if structure
124 .frame
125 .origin
126 .sub(plane_origin)
127 .dot(plane_normal)
128 .abs()
129 > 1e-9 * scale
130 {
131 return None;
132 }
133 let radial = structure.frame.axis.cross(plane_normal).normalized().ok()?;
134 let tau = std::f64::consts::TAU;
135 let mut curves = Vec::new();
136 for direction in [radial, radial.scale(-1.0)] {
137 let mut angle = direction
138 .dot(structure.frame.y_axis)
139 .atan2(direction.dot(structure.frame.x_axis));
140 if angle < -1e-9 {
141 angle += tau;
142 }
143 if angle <= structure.sweep + 1e-9 {
144 curves.push(rotate_curve_about_axis(
145 &structure.generatrix,
146 structure.frame.origin,
147 structure.frame.axis,
148 angle.clamp(0.0, structure.sweep),
149 )?);
150 }
151 }
152 Some(curves)
153}
154
155fn intersect_recognized_pair(
156 first: &NurbsSurface,
157 second: &NurbsSurface,
158 tolerance: f64,
159) -> Option<Vec<NurbsCurve>> {
160 let a = first.analytic()?;
161 let b = second.analytic()?;
162 if !matches!(b, AnalyticSurface::Plane { .. }) {
163 if let Some(plane) = plane_data(a) {
164 return intersect_plane_quadric(&plane, b, tolerance);
165 }
166 }
167 if !matches!(a, AnalyticSurface::Plane { .. }) {
168 if let Some(plane) = plane_data(b) {
169 return intersect_plane_quadric(&plane, a, tolerance);
170 }
171 }
172 if let (Some((center_a, radius_a)), Some((center_b, radius_b))) =
175 (a.sphere_geometry(), b.sphere_geometry())
176 {
177 return intersect_sphere_sphere(center_a, radius_a, center_b, radius_b, tolerance);
178 }
179 None
186}
187
188fn ruled_revolution_data(quadric: &AnalyticSurface) -> Option<(RevolutionFrame, f64, f64, f64)> {
195 match quadric {
196 AnalyticSurface::RuledRevolution {
197 frame,
198 rho0,
199 rho1,
200 height,
201 } => Some((frame.clone(), *rho0, *rho1, *height)),
202 AnalyticSurface::Revolution {
203 frame, generatrix, ..
204 } => {
205 let controls = &generatrix.control_points;
206 if generatrix.degree != 1
207 || controls.len() != 2
208 || (controls[0].w - 1.0).abs() > RECOGNITION_TOLERANCE
209 || (controls[1].w - 1.0).abs() > RECOGNITION_TOLERANCE
210 {
211 return None;
212 }
213 let (_, rho0, z0) = frame.cylindrical(controls[0].point().ok()?);
214 let (_, rho1, z1) = frame.cylindrical(controls[1].point().ok()?);
215 let height = z1 - z0;
216 if height.abs() <= 1e-12 * (1.0 + rho0.abs().max(rho1.abs())) {
217 return None;
218 }
219 let origin = frame.origin.add(frame.axis.scale(z0));
220 Some((
221 RevolutionFrame {
222 origin,
223 ..frame.clone()
224 },
225 rho0,
226 rho1,
227 height,
228 ))
229 }
230 _ => None,
231 }
232}
233
234fn degenerate_apex_frustum(rho0: f64, rho1: f64, height: f64) -> bool {
255 if std::env::var("BREP_CONE_APEX_GUARD").as_deref() == Ok("0") {
256 return false;
257 }
258 let delta = rho1 - rho0;
259 delta * delta <= 64.0 * f64::EPSILON * height.abs() * rho0.abs().max(rho1.abs())
260}
261
262fn intersect_plane_quadric(
263 plane: &PlaneData,
264 quadric: &AnalyticSurface,
265 tolerance: f64,
266) -> Option<Vec<NurbsCurve>> {
267 match quadric {
268 AnalyticSurface::RuledRevolution { .. } | AnalyticSurface::Revolution { .. }
269 if ruled_revolution_data(quadric).is_some() =>
270 {
271 let (frame, rho0, rho1, height) = ruled_revolution_data(quadric)?;
272 let (frame, rho0, rho1, height) = (&frame, &rho0, &rho1, &height);
273 let alignment = plane.normal.dot(frame.axis);
274 if alignment.abs() >= 1.0 - 1e-12 {
275 let z = plane.origin.sub(frame.origin).dot(frame.axis);
277 let t = z / height;
278 if !(-1e-9..=1.0 + 1e-9).contains(&t) {
279 return Some(Vec::new());
280 }
281 let rho = rho0 + (rho1 - rho0) * t.clamp(0.0, 1.0);
282 if rho <= tolerance {
283 return Some(Vec::new());
284 }
285 return Some(vec![frame_circle(frame, rho, z)?]);
286 }
287 if (rho1 - rho0).abs() <= 1e-12 || degenerate_apex_frustum(*rho0, *rho1, *height) {
288 if alignment.abs() <= 1e-12 {
290 return cylinder_parallel_plane_lines(plane, frame, *rho0, *height, tolerance);
293 }
294 let circle = frame_circle(frame, *rho0, 0.0)?;
297 let denominator = alignment;
298 let origin_dot = plane.origin.dot(plane.normal);
299 let normal = plane.normal;
300 let axis = frame.axis;
301 let ellipse = map_rational_curve(&circle, |p| {
302 let t = (origin_dot * p.w - Vec3::new(p.x, p.y, p.z).dot(normal)) / denominator;
303 crate::Vec4 {
304 x: p.x + axis.x * t,
305 y: p.y + axis.y * t,
306 z: p.z + axis.z * t,
307 w: p.w,
308 }
309 })?;
310 if !section_within_band(&ellipse, frame, *height, tolerance) {
311 return Some(Vec::new());
312 }
313 return Some(vec![ellipse]);
314 }
315 let apex_t = rho0 / (rho0 - rho1);
319 let apex = frame.origin.add(frame.axis.scale(height * apex_t));
320 let reference_rho = if rho0.abs() > rho1.abs() {
321 *rho0
322 } else {
323 *rho1
324 };
325 let reference_z = if rho0.abs() > rho1.abs() {
326 0.0
327 } else {
328 *height
329 };
330 let circle = frame_circle(frame, reference_rho, reference_z)?;
331 let k = plane.origin.sub(apex).dot(plane.normal);
332 if k.abs() <= tolerance {
333 return None;
335 }
336 let normal = plane.normal;
337 let conic = map_rational_curve(&circle, |p| {
338 let relative =
339 Vec3::new(p.x - p.w * apex.x, p.y - p.w * apex.y, p.z - p.w * apex.z);
340 let w_new = relative.dot(normal);
341 let scaled = relative.scale(k);
342 crate::Vec4 {
343 x: apex.x * w_new + scaled.x,
344 y: apex.y * w_new + scaled.y,
345 z: apex.z * w_new + scaled.z,
346 w: w_new,
347 }
348 })?;
349 if !section_within_band(&conic, frame, *height, tolerance) {
350 return Some(Vec::new());
351 }
352 Some(vec![conic])
353 }
354 AnalyticSurface::Sphere { frame, radius } => {
355 plane_sphere_section(plane, frame.origin, *radius, tolerance)
356 }
357 AnalyticSurface::Torus {
358 frame,
359 major_radius,
360 minor_radius,
361 } => plane_torus_section(plane, frame, *major_radius, *minor_radius, tolerance),
362 AnalyticSurface::Plane { .. } => None,
363 AnalyticSurface::RuledRevolution { .. } => None,
364 AnalyticSurface::Revolution { .. } => {
370 if let Some((center, radius)) = quadric.sphere_geometry() {
371 return plane_sphere_section(plane, center, radius, tolerance);
372 }
373 if let Some((frame, major_radius, minor_radius)) = quadric.torus_geometry() {
374 return plane_torus_section(plane, &frame, major_radius, minor_radius, tolerance);
375 }
376 None
377 }
378 }
379}
380
381fn plane_sphere_section(
384 plane: &PlaneData,
385 center: Vec3,
386 radius: f64,
387 tolerance: f64,
388) -> Option<Vec<NurbsCurve>> {
389 let distance = center.sub(plane.origin).dot(plane.normal);
390 if distance.abs() >= radius - tolerance {
391 return Some(Vec::new());
392 }
393 let circle_center = center.sub(plane.normal.scale(distance));
394 let circle_radius = (radius * radius - distance * distance).sqrt();
395 let x_axis = plane.normal.perpendicular().ok()?;
396 let y_axis = plane.normal.cross(x_axis);
397 Some(vec![make_arc(
398 circle_center,
399 x_axis,
400 y_axis,
401 circle_radius,
402 0.0,
403 std::f64::consts::TAU,
404 )
405 .ok()?])
406}
407
408fn plane_torus_section(
413 plane: &PlaneData,
414 frame: &RevolutionFrame,
415 major_radius: f64,
416 minor_radius: f64,
417 tolerance: f64,
418) -> Option<Vec<NurbsCurve>> {
419 let alignment = plane.normal.dot(frame.axis);
420 if alignment.abs() < 1.0 - 1e-12 {
421 return None;
422 }
423 let z = plane.origin.sub(frame.origin).dot(frame.axis);
424 if z.abs() >= minor_radius - tolerance {
425 return Some(Vec::new());
426 }
427 let offset = (minor_radius * minor_radius - z * z).sqrt();
428 let mut circles = Vec::new();
429 for rho in [major_radius - offset, major_radius + offset] {
430 if rho > tolerance {
431 circles.push(frame_circle(frame, rho, z)?);
432 }
433 }
434 Some(circles)
435}
436
437fn section_within_band(
454 curve: &NurbsCurve,
455 frame: &RevolutionFrame,
456 height: f64,
457 tolerance: f64,
458) -> bool {
459 let (band_low, band_high) = if height >= 0.0 {
460 (0.0, height)
461 } else {
462 (height, 0.0)
463 };
464 let margin = tolerance.max(1e-9) + 1e-9;
465 let mut min_z = f64::INFINITY;
466 let mut max_z = f64::NEG_INFINITY;
467 for p in &curve.control_points {
468 let z = Vec3::new(p.x / p.w, p.y / p.w, p.z / p.w)
469 .sub(frame.origin)
470 .dot(frame.axis);
471 min_z = min_z.min(z);
472 max_z = max_z.max(z);
473 }
474 max_z >= band_low - margin && min_z <= band_high + margin
476}
477
478fn cylinder_parallel_plane_lines(
479 plane: &PlaneData,
480 frame: &RevolutionFrame,
481 radius: f64,
482 height: f64,
483 tolerance: f64,
484) -> Option<Vec<NurbsCurve>> {
485 let axis_to_plane = plane.origin.sub(frame.origin).dot(plane.normal);
487 if axis_to_plane.abs() >= radius - tolerance {
488 return Some(Vec::new());
489 }
490 let chord_half = (radius * radius - axis_to_plane * axis_to_plane).sqrt();
492 let chord_direction = frame.axis.cross(plane.normal).normalized().ok()?;
493 let foot = frame.origin.add(plane.normal.scale(axis_to_plane));
494 let mut lines = Vec::new();
495 for sign in [-1.0, 1.0] {
496 let base = foot.add(chord_direction.scale(sign * chord_half));
497 let top = base.add(frame.axis.scale(height));
498 lines.push(crate::make_line(base, top).ok()?);
499 }
500 Some(lines)
501}
502
503fn intersect_sphere_sphere(
504 center_a: Vec3,
505 radius_a: f64,
506 center_b: Vec3,
507 radius_b: f64,
508 tolerance: f64,
509) -> Option<Vec<NurbsCurve>> {
510 let offset = center_b.sub(center_a);
511 let distance = offset.length();
512 if distance <= tolerance {
513 return if (radius_a - radius_b).abs() <= tolerance {
516 None
517 } else {
518 Some(Vec::new())
519 };
520 }
521 if distance >= radius_a + radius_b - tolerance
522 || distance <= (radius_a - radius_b).abs() + tolerance
523 {
524 return Some(Vec::new());
525 }
526 let normal = offset.scale(1.0 / distance);
527 let along =
528 (distance * distance + radius_a * radius_a - radius_b * radius_b) / (2.0 * distance);
529 let circle_radius_squared = radius_a * radius_a - along * along;
530 if circle_radius_squared <= tolerance * tolerance {
531 return Some(Vec::new());
532 }
533 let center = center_a.add(normal.scale(along));
534 let x_axis = normal.perpendicular().ok()?;
535 let y_axis = normal.cross(x_axis);
536 Some(vec![make_arc(
537 center,
538 x_axis,
539 y_axis,
540 circle_radius_squared.sqrt(),
541 0.0,
542 std::f64::consts::TAU,
543 )
544 .ok()?])
545}