1use axiolid_core::{Frame3, Interval, Point2, Point3, Scalar, Tolerance, Vec2, Vec3};
28use axiolid_curve::{Basis, Carrier, Curve2, Curve3, Field2, ImplicitCurve2, SeriesField2, Trig2};
29use axiolid_evaluate::curve::{evaluate2, evaluate3, locate2, locate3};
30use axiolid_surface::{Plane, Surface};
31
32use crate::exact_curve_intersection::{ExactCurveIntersection, ExactCurveRefusal};
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct CurveCurveHit {
37 pub first: Scalar,
39 pub second: Scalar,
41 pub point: Point3,
43 pub multiplicity: usize,
49}
50
51fn series(u: Basis, v: Basis, coefficients: Vec<Vec<Scalar>>) -> Field2 {
52 Field2::Series(SeriesField2 { u, v, coefficients })
53}
54
55fn fourier(t: &Trig2) -> Vec<Scalar> {
56 vec![t.constant, t.cos, t.sin, t.cos2, t.sin2]
57}
58
59fn plane_field(curve: &Curve2) -> Option<Field2> {
61 let p = Basis::Power;
62 let f = Basis::Fourier;
63 Some(match curve {
64 Curve2::Line(l) => {
65 let n = Vec2::new(-l.direction.y, l.direction.x);
66 if n.length() == 0.0 {
67 return None;
68 }
69 let n = n.normalize();
70 series(p, p, vec![vec![-n.dot(l.origin), n.y], vec![n.x, 0.0]])
71 }
72 Curve2::Circle(c) => conic_field(c.frame, c.radius, c.radius)?,
73 Curve2::Ellipse(e) => conic_field(e.frame, e.semi_axis_x, e.semi_axis_y)?,
74 Curve2::Sinusoid(w) => series(
75 f,
76 p,
77 vec![vec![-w.mean, 1.0], vec![-w.cosine, 0.0], vec![-w.sine, 0.0]],
78 ),
79 Curve2::QuadraticGraph(g) => {
80 let (a, b, c) = (fourier(&g.a), fourier(&g.b), fourier(&g.c));
81 series(f, p, (0..5).map(|i| vec![c[i], b[i], a[i]]).collect())
82 }
83 Curve2::AngleGraph(g) => {
84 let (a, b, c) = (fourier(&g.a), fourier(&g.b), fourier(&g.c));
85 series(f, f, vec![c.iter().map(|x| -x).collect(), a, b])
86 }
87 Curve2::Implicit(c) => c.field.clone(),
88 _ => return None,
89 })
90}
91
92fn conic_field(frame: axiolid_core::Frame2, a: Scalar, b: Scalar) -> Option<Field2> {
94 let (x, y) = (frame.x, frame.y);
95 if (x.length() - 1.0).abs() > 1e-12
96 || (y.length() - 1.0).abs() > 1e-12
97 || x.dot(y).abs() > 1e-12
98 {
99 return None;
100 }
101 let lin = |axis: Vec2| [-axis.dot(frame.origin), axis.x, axis.y];
103 let (lx, ly) = (lin(x), lin(y));
104 let square = |l: [Scalar; 3]| {
106 let mut c = vec![vec![0.0; 3]; 3];
107 c[0][0] = l[0] * l[0];
108 c[1][0] = 2.0 * l[0] * l[1];
109 c[0][1] = 2.0 * l[0] * l[2];
110 c[2][0] = l[1] * l[1];
111 c[1][1] = 2.0 * l[1] * l[2];
112 c[0][2] = l[2] * l[2];
113 c
114 };
115 let (sx, sy) = (square(lx), square(ly));
116 let mut c = vec![vec![0.0; 3]; 3];
117 for i in 0..3 {
118 for j in 0..3 {
119 c[i][j] = b * b * sx[i][j] + a * a * sy[i][j];
120 }
121 }
122 c[0][0] -= a * a * b * b;
123 Some(series(Basis::Power, Basis::Power, c))
124}
125
126fn traced_piece(curve: &Curve2, span: Interval) -> Option<ImplicitCurve2> {
129 if let Curve2::Implicit(c) = curve {
130 let (a, b) = (span.start.min(span.end), span.start.max(span.end));
131 return c.sub(a, b, None);
132 }
133 let field = plane_field(curve)?;
134 let n = 128;
135 let at = |i: usize| {
136 evaluate2(
137 curve,
138 span.start + (span.end - span.start) * i as Scalar / n as Scalar,
139 )
140 .ok()
141 };
142 let samples: Vec<Point2> = (0..=n).map(at).collect::<Option<_>>()?;
143 let (mut lo, mut hi) = (samples[0], samples[0]);
144 for p in &samples {
145 lo = lo.min(*p);
146 hi = hi.max(*p);
147 }
148 let pad = (hi - lo) * 0.1 + Vec2::splat(1e-3 * (1.0 + (hi - lo).length()));
149 let curves = crate::implicit_trace::trace(
150 &field,
151 axiolid_curve::implicit::Cell {
152 lo: lo - pad,
153 hi: hi + pad,
154 },
155 crate::implicit_trace::Periodic { u: false, v: false },
156 )
157 .ok()?;
158 let closed = (samples[0] - samples[n]).length() <= 1e-12 * (1.0 + samples[0].length());
159 crate::implicit_ops::extract_stretch(
160 &curves,
161 (false, false),
162 samples[0],
163 [samples[n / 3], samples[2 * n / 3]],
164 samples[n],
165 closed,
166 )
167}
168
169fn within(t: Scalar, span: Interval) -> bool {
171 let (lo, hi) = (span.start.min(span.end), span.start.max(span.end));
172 let slack = 1e-9 * (1.0 + lo.abs().max(hi.abs()));
173 t >= lo - slack && t <= hi + slack
174}
175
176pub fn section_curve_curve_intersection2(
187 first: &Curve2,
188 first_span: Interval,
189 second: &Curve2,
190 second_span: Interval,
191 tolerance: Tolerance,
192) -> Result<Vec<CurveCurveHit>, ExactCurveRefusal> {
193 let (traced, other, swapped) = match (plane_field(first), plane_field(second)) {
195 (_, Some(f)) => (traced_piece(first, first_span), f, false),
196 (Some(f), None) => (traced_piece(second, second_span), f, true),
197 (None, None) => return Err(ExactCurveRefusal::UnsupportedCurve),
198 };
199 let traced = match traced {
200 Some(t) => t,
201 None if plane_field(if swapped { second } else { first }).is_none() => {
202 return Err(ExactCurveRefusal::UnsupportedCurve)
203 }
204 None => return Err(ExactCurveRefusal::UnsupportedCurve),
205 };
206 let roots =
207 crate::implicit_ops::roots_along(&traced, &other, traced.closure(false, false).is_some())
208 .map_err(|_| ExactCurveRefusal::Undecided)?;
209 let mut hits = Vec::new();
210 for (t, multiplicity) in roots {
211 let Some(p) = traced.point(t) else { continue };
212 let (a, b) = match (locate2(first, p, tolerance), locate2(second, p, tolerance)) {
213 (Ok(a), Ok(b)) => (a, b),
214 _ => continue,
215 };
216 if !within(a, first_span) || !within(b, second_span) {
217 continue;
218 }
219 hits.push(CurveCurveHit {
220 first: a,
221 second: b,
222 point: Point3::new(p.x, p.y, 0.0),
223 multiplicity,
224 });
225 }
226 hits.sort_by(|x, y| x.first.total_cmp(&y.first));
227 hits.dedup_by(|x, y| (x.first - y.first).abs() <= 1e-9 * (1.0 + x.first.abs()));
228 Ok(hits)
229}
230
231fn carriers_of(curve: &Curve3) -> Option<Vec<Surface>> {
234 let plane = |origin: Point3, z: Vec3| -> Surface {
235 let z = z.normalize();
236 let helper = if z.x.abs() < 0.9 { Vec3::X } else { Vec3::Y };
237 let x = helper.cross(z).normalize();
238 Surface::Plane(Plane {
239 frame: Frame3 {
240 origin,
241 x,
242 y: z.cross(x),
243 z,
244 },
245 })
246 };
247 Some(match curve {
248 Curve3::Line(l) => {
249 let d = l.direction.normalize();
250 let helper = if d.x.abs() < 0.9 { Vec3::X } else { Vec3::Y };
251 let n = d.cross(helper).normalize();
252 vec![plane(l.origin, n), plane(l.origin, d.cross(n))]
253 }
254 Curve3::Circle(c) => vec![plane(c.frame.origin, c.frame.x.cross(c.frame.y))],
255 Curve3::Ellipse(e) => vec![plane(e.frame.origin, e.frame.x.cross(e.frame.y))],
256 Curve3::ImplicitSection(s) => vec![surface_of(&s.carrier)?],
257 Curve3::RuledSection(r) => vec![surface_of(&Carrier::Ruled(r.carrier))?],
258 Curve3::TorusSection(t) => vec![surface_of(&Carrier::Torus(t.torus))?],
259 _ => return None,
260 })
261}
262
263pub(crate) fn surface_of(carrier: &Carrier) -> Option<Surface> {
265 use axiolid_surface::{Cone, Cylinder, EllipticalCylinder, Sphere, Torus};
266 Some(match carrier {
267 Carrier::Plane(f) => Surface::Plane(Plane { frame: *f }),
268 Carrier::Ruled(k) => {
269 if k.slope != 0.0 {
270 if k.x_radius != k.y_radius {
271 return None;
272 }
273 Surface::Cone(Cone {
274 frame: k.frame,
275 radius: k.x_radius,
276 semi_angle: k.slope.atan(),
277 })
278 } else if k.x_radius == k.y_radius {
279 Surface::Cylinder(Cylinder {
280 frame: k.frame,
281 radius: k.x_radius,
282 })
283 } else {
284 Surface::EllipticalCylinder(EllipticalCylinder {
285 frame: k.frame,
286 semi_axis_x: k.x_radius,
287 semi_axis_y: k.y_radius,
288 })
289 }
290 }
291 Carrier::Sphere { frame, radius } => Surface::Sphere(Sphere {
292 frame: *frame,
293 radius: *radius,
294 }),
295 Carrier::Torus(t) => Surface::Torus(Torus {
296 frame: t.frame,
297 major_radius: t.major_radius,
298 minor_radius: t.minor_radius,
299 }),
300 Carrier::Spline(b) => Surface::BSpline((**b).clone()),
301 })
302}
303
304pub fn section_curve_curve_intersection3(
314 first: &Curve3,
315 first_span: Interval,
316 second: &Curve3,
317 second_span: Interval,
318 tolerance: Tolerance,
319) -> Result<Vec<CurveCurveHit>, ExactCurveRefusal> {
320 let carriers = carriers_of(second).ok_or(ExactCurveRefusal::UnsupportedCurve)?;
321 let mut hits = Vec::new();
322 for carrier in &carriers {
323 let found = match first {
324 Curve3::Line(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => {
325 crate::exact_curve_intersection::exact_curve_surface_intersection(first, carrier)
326 }
327 _ => {
328 crate::implicit_ops::section_curve_surface_intersection(first, first_span, carrier)
329 }
330 };
331 let points = match found {
332 Ok(ExactCurveIntersection::Points(points)) => points,
333 Ok(_) => continue,
336 Err(e) => return Err(e),
337 };
338 for hit in points {
339 let Ok(b) = locate3(second, hit.point, tolerance) else {
340 continue;
341 };
342 let Ok(on) = evaluate3(second, b) else {
343 continue;
344 };
345 if (on - hit.point).length() > tolerance.linear() {
346 continue;
347 }
348 let a = hit.parameter.approx();
349 if !within(a, first_span) || !within(b, second_span) {
350 continue;
351 }
352 hits.push(CurveCurveHit {
353 first: a,
354 second: b,
355 point: hit.point,
356 multiplicity: hit.multiplicity,
357 });
358 }
359 }
360 hits.sort_by(|x, y| x.first.total_cmp(&y.first));
361 hits.dedup_by(|x, y| (x.first - y.first).abs() <= 1e-9 * (1.0 + x.first.abs()));
362 Ok(hits)
363}