brep_kernel/geometry/analytic_surface/
revolution.rs1use super::*;
2use super::recognition::{full_circle_knots, nearly_equal_points, surface_scale};
3
4pub struct RevolutionStructure {
9 pub frame: RevolutionFrame,
10 pub generatrix: NurbsCurve,
11 pub sweep: f64,
12}
13
14pub(crate) fn circumcenter(a: Vec3, b: Vec3, c: Vec3) -> Option<Vec3> {
15 let ab = b.sub(a);
16 let ac = c.sub(a);
17 let normal = ab.cross(ac);
18 let normal_squared = normal.dot(normal);
19 if normal_squared <= 1e-30 {
20 return None;
21 }
22 let offset = normal
23 .cross(ab)
24 .scale(ac.dot(ac))
25 .add(ac.cross(normal).scale(ab.dot(ab)))
26 .scale(1.0 / (2.0 * normal_squared));
27 Some(a.add(offset))
28}
29
30pub fn revolution_structure(surface: &NurbsSurface) -> Option<RevolutionStructure> {
31 if surface.degree_u != 2 {
32 return None;
33 }
34 let rows = &surface.control_points;
35 if rows.len() < 3 || rows.len() % 2 == 0 {
36 return None;
37 }
38 let spans = (rows.len() - 1) / 2;
39 if spans > 4 {
40 return None;
41 }
42 let expected = full_circle_knots(spans);
43 if surface.knots_u.len() != expected.len()
44 || surface
45 .knots_u
46 .iter()
47 .zip(&expected)
48 .any(|(a, b)| (a - b).abs() > 1e-12)
49 {
50 return None;
51 }
52 let scale = surface_scale(surface);
53 let mut sweep = None;
55 for column in 0..rows[0].len() {
56 let corner = rows[0][column].w;
57 let middle = rows[1][column].w;
58 if corner.abs() <= 1e-12 {
59 continue;
60 }
61 let ratio = middle / corner;
62 if !(ratio > 0.0 && ratio <= 1.0 + 1e-12) {
63 return None;
64 }
65 let arc_angle = 2.0 * ratio.min(1.0).acos();
66 if arc_angle <= 1e-9 {
67 return None;
68 }
69 sweep = Some(arc_angle * spans as f64);
70 break;
71 }
72 let sweep = sweep?;
73 if sweep > std::f64::consts::TAU + 1e-9 {
74 return None;
75 }
76 let mut frame = None;
79 for column in 0..rows[0].len() {
80 let arc_points: Vec<crate::Vec4> = rows.iter().map(|row| row[column]).collect();
81 let Ok(arc) = NurbsCurve::new(2, surface.knots_u.clone(), arc_points) else {
82 return None;
83 };
84 let p0 = arc.evaluate(0.0).ok()?;
85 let p1 = arc.evaluate(0.4).ok()?;
86 let p2 = arc.evaluate(0.8).ok()?;
87 let Some(center) = circumcenter(p0, p1, p2) else {
88 continue;
89 };
90 let radial = p0.sub(center);
91 let radius = radial.length();
92 if radius <= RECOGNITION_TOLERANCE * scale.max(1.0) {
93 continue;
94 }
95 let axis = radial.cross(p1.sub(center)).normalized().ok()?;
96 let x_axis = radial.scale(1.0 / radius);
97 let y_axis = axis.cross(x_axis);
98 frame = Some(RevolutionFrame {
99 origin: center,
100 axis,
101 x_axis,
102 y_axis,
103 });
104 break;
105 }
106 let frame = frame?;
107 let generatrix =
108 NurbsCurve::new(surface.degree_v, surface.knots_v.clone(), rows[0].clone()).ok()?;
109 let rebuilt = make_revolution(frame.origin, frame.axis, &generatrix, sweep).ok()?;
110 if !nearly_equal_points(surface, &rebuilt, scale) {
111 return None;
112 }
113 Some(RevolutionStructure {
114 frame,
115 generatrix,
116 sweep,
117 })
118}
119
120pub(super) fn rotate_curve_about_axis(
121 curve: &NurbsCurve,
122 origin: Vec3,
123 axis: Vec3,
124 angle: f64,
125) -> Option<NurbsCurve> {
126 let (sin, cos) = angle.sin_cos();
127 let controls = curve
128 .control_points
129 .iter()
130 .map(|control| {
131 let point = control.point().ok()?;
132 let d = point.sub(origin);
133 let axial = axis.scale(d.dot(axis));
134 let radial = d.sub(axial);
135 let ortho = axis.cross(radial);
136 let rotated = origin
137 .add(axial)
138 .add(radial.scale(cos))
139 .add(ortho.scale(sin));
140 Some(crate::Vec4::from_point(rotated, control.w))
141 })
142 .collect::<Option<Vec<_>>>()?;
143 NurbsCurve::new(curve.degree, curve.knots.clone(), controls).ok()
144}
145
146pub(super) fn intersect_coaxial_revolutions(
154 first: &NurbsSurface,
155 second: &NurbsSurface,
156 tolerance: f64,
157) -> Option<Vec<NurbsCurve>> {
158 let a = revolution_structure(first)?;
159 let b = revolution_structure(second)?;
160 let scale = surface_scale(first).max(surface_scale(second)).max(1.0);
161 let alignment = a.frame.axis.dot(b.frame.axis);
162 if alignment.abs() < 1.0 - 1e-9 {
163 return None;
164 }
165 let offset = b.frame.origin.sub(a.frame.origin);
166 let perpendicular = offset.sub(a.frame.axis.scale(offset.dot(a.frame.axis)));
167 if perpendicular.length() > 1e-9 * scale {
168 return None;
169 }
170 let b_start = b
172 .frame
173 .x_axis
174 .dot(a.frame.y_axis)
175 .atan2(b.frame.x_axis.dot(a.frame.x_axis));
176 let (b_lo, b_hi) = if alignment > 0.0 {
177 (b_start, b_start + b.sweep)
178 } else {
179 (b_start - b.sweep, b_start)
180 };
181 let tau = std::f64::consts::TAU;
182 let mut intervals = Vec::new();
183 for shift in [-tau, 0.0, tau] {
184 let lo = (b_lo + shift).max(0.0);
185 let hi = (b_hi + shift).min(a.sweep);
186 if hi - lo > 1e-9 {
187 intervals.push((lo, hi));
188 }
189 }
190 if intervals.is_empty() {
191 return Some(Vec::new());
192 }
193 let middle = (intervals[0].0 + intervals[0].1) / 2.0;
194 let generatrix = rotate_curve_about_axis(&a.generatrix, a.frame.origin, a.frame.axis, middle)?;
195 let hits = crate::intersect_curve_surface(&generatrix, second, tolerance).ok()?;
196 if hits.len() > 8 {
197 return None;
200 }
201 let radius_floor = (tolerance * 10.0).max(1e-9 * scale);
202 let mut seen: Vec<(f64, f64)> = Vec::new();
203 let mut curves = Vec::new();
204 for hit in hits {
205 if hit.tangential
215 && std::env::var("BREP_COAXIAL_TANGENT_SKIP").as_deref() != Ok("0")
216 {
217 continue;
218 }
219 let (_, radius, axial) = a.frame.cylindrical(hit.point);
220 if radius <= radius_floor {
221 continue;
222 }
223 if seen
224 .iter()
225 .any(|(r, z)| (r - radius).abs() + (z - axial).abs() <= radius_floor)
226 {
227 continue;
228 }
229 seen.push((radius, axial));
230 for (lo, hi) in &intervals {
231 let center = a.frame.origin.add(a.frame.axis.scale(axial));
232 curves.push(
233 make_arc(center, a.frame.x_axis, a.frame.y_axis, radius, *lo, *hi).ok()?,
234 );
235 }
236 }
237 Some(curves)
238}