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 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.is_finite() || normal_squared <= 1e-28 * ab.dot(ab) * ac.dot(ac) {
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
178 .frame
179 .x_axis
180 .dot(a.frame.y_axis)
181 .atan2(b.frame.x_axis.dot(a.frame.x_axis));
182 let tau = std::f64::consts::TAU;
183 let (b_lo, b_hi) = if alignment > 0.0 {
184 (b_start, b_start + b.sweep)
185 } else {
186 (b_start - b.sweep, b_start)
187 };
188 let b_wrapped = b_lo.rem_euclid(tau);
189 let (b_lo, b_hi) = (b_wrapped, b_hi + (b_wrapped - b_lo));
190 let mut intervals = Vec::new();
191 for shift in [-tau, 0.0, tau] {
192 let lo = (b_lo + shift).max(0.0);
193 let hi = (b_hi + shift).min(a.sweep);
194 if hi - lo > 1e-9 {
195 intervals.push((lo, hi));
196 }
197 }
198 intervals.sort_by(|x, y| x.0.total_cmp(&y.0));
201 let mut merged: Vec<(f64, f64)> = Vec::new();
202 for (lo, hi) in intervals {
203 if let Some(last) = merged.last_mut() {
204 if lo <= last.1 + 1e-9 {
205 last.1 = last.1.max(hi);
206 continue;
207 }
208 }
209 merged.push((lo, hi));
210 }
211 let intervals = merged;
212 if intervals.is_empty() {
213 return Some(Vec::new());
214 }
215 let middle = (intervals[0].0 + intervals[0].1) / 2.0;
216 let generatrix = rotate_curve_about_axis(&a.generatrix, a.frame.origin, a.frame.axis, middle)?;
217 let hits = crate::intersect_curve_surface(&generatrix, second, tolerance).ok()?;
218 if hits.len() > 8 {
219 return None;
222 }
223 let radius_floor = (tolerance * 10.0).max(1e-9 * scale);
224 let mut seen: Vec<(f64, f64)> = Vec::new();
225 let mut curves = Vec::new();
226 for hit in hits {
227 if hit.tangential
236 && std::env::var("BREP_COAXIAL_TANGENT_SKIP").as_deref() != Ok("0")
237 {
238 continue;
239 }
240 let (_, radius, axial) = a.frame.cylindrical(hit.point);
241 if radius <= radius_floor {
242 continue;
243 }
244 if seen
245 .iter()
246 .any(|(r, z)| (r - radius).abs() + (z - axial).abs() <= radius_floor)
247 {
248 continue;
249 }
250 seen.push((radius, axial));
251 for (lo, hi) in &intervals {
252 let center = a.frame.origin.add(a.frame.axis.scale(axial));
253 curves.push(
254 make_arc(center, a.frame.x_axis, a.frame.y_axis, radius, *lo, *hi).ok()?,
255 );
256 }
257 }
258 Some(curves)
259}