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