1use crate::curve::interior_knots;
2use crate::{NurbsCurve, NurbsSurface, Vec3};
3use serde::Serialize;
4
5const EPSILON: f64 = 1e-12;
6const LINEAR_TOLERANCE: f64 = 1e-7;
7const MAX_NEWTON_ITERATIONS: usize = 50;
8
9#[derive(Clone, Copy, Debug, Serialize)]
10pub struct CurveProjection {
11 pub u: f64,
12 pub point: Vec3,
13 pub distance: f64,
14}
15
16#[derive(Clone, Copy, Debug, Serialize)]
17pub struct SurfaceProjection {
18 pub u: f64,
19 pub v: f64,
20 pub point: Vec3,
21 pub distance: f64,
22}
23
24fn fit_parameter(value: f64, minimum: f64, maximum: f64, closed: bool) -> f64 {
25 if closed {
26 let period = maximum - minimum;
27 (value - minimum).rem_euclid(period) + minimum
28 } else {
29 value.clamp(minimum, maximum)
30 }
31}
32
33pub fn project_point_to_curve(curve: &NurbsCurve, point: Vec3) -> Result<CurveProjection, String> {
34 let [start, end] = curve.domain()?;
35 let mut breaks = vec![start];
36 breaks.extend(interior_knots(&curve.knots, curve.degree));
37 breaks.push(end);
38 let samples_per_span = 4usize.max(curve.degree + 2);
39 let mut best_u = start;
40 let mut best_distance_squared = f64::INFINITY;
41 for pair in breaks.windows(2) {
42 for index in 0..=samples_per_span {
43 let parameter = pair[0] + (pair[1] - pair[0]) * index as f64 / samples_per_span as f64;
44 let distance_squared = curve.evaluate(parameter)?.sub(point).length_squared();
45 if distance_squared < best_distance_squared {
46 best_distance_squared = distance_squared;
47 best_u = parameter;
48 }
49 }
50 }
51
52 let mut parameter = best_u;
53 for _ in 0..MAX_NEWTON_ITERATIONS {
54 let derivatives = curve.derivatives_small(parameter, 2)?;
55 let residual = derivatives[0].sub(point);
56 let f = derivatives[1].dot(residual);
57 let derivative = derivatives[2].dot(residual) + derivatives[1].length_squared();
58 let distance_squared = residual.length_squared();
59 if distance_squared < best_distance_squared {
60 best_distance_squared = distance_squared;
61 best_u = parameter;
62 }
63 let residual_length = distance_squared.sqrt();
64 let tangent_length = derivatives[1].length();
65 if residual_length <= LINEAR_TOLERANCE
66 || f.abs() <= EPSILON + 1e-10 * tangent_length * residual_length
67 || derivative.abs() <= EPSILON
68 {
69 break;
70 }
71 let mut step = -f / derivative;
72 let maximum_step = (end - start) / 4.0;
73 if step.abs() > maximum_step {
74 step = step.signum() * maximum_step;
75 }
76 let next = (parameter + step).clamp(start, end);
77 if (next - parameter).abs() <= 1e-15 * (end - start) {
78 parameter = next;
79 break;
80 }
81 parameter = next;
82 }
83 for candidate in [start, end, parameter] {
84 let distance_squared = curve.evaluate(candidate)?.sub(point).length_squared();
85 if distance_squared < best_distance_squared {
86 best_distance_squared = distance_squared;
87 best_u = candidate;
88 }
89 }
90 let projected = curve.evaluate(best_u)?;
91 Ok(CurveProjection {
92 u: best_u,
93 point: projected,
94 distance: projected.sub(point).length(),
95 })
96}
97
98fn project_linear_revolution(
99 surface: &NurbsSurface,
100 point: Vec3,
101) -> Result<Option<SurfaceProjection>, String> {
102 let rows = &surface.control_points;
103 if surface.degree_u != 2
104 || surface.degree_v != 1
105 || rows.len() < 5
106 || rows[0].len() != 2
107 || !surface.closed_directions()?.0
108 {
109 return Ok(None);
110 }
111 let base = rows[0][0].point()?.add(rows[4][0].point()?).scale(0.5);
112 let top = rows[0][1].point()?.add(rows[4][1].point()?).scale(0.5);
113 let axis_vector = top.sub(base);
114 let height = axis_vector.length();
115 if height <= EPSILON {
116 return Ok(None);
117 }
118 let axis = axis_vector.normalized()?;
119 let [v0, v1] = surface.domain_v()?;
120 let axial = point.sub(base).dot(axis).clamp(0.0, height);
121 let v = v0 + (v1 - v0) * axial / height;
122 let circle = surface.iso_curve_v(v)?;
123 let projection = project_point_to_curve(&circle, point)?;
124 let [u0, u1] = surface.domain_u()?;
125 let u = projection.u.clamp(u0, u1);
126 let projected = surface.evaluate(u, v)?;
127 Ok(Some(SurfaceProjection {
128 u,
129 v,
130 point: projected,
131 distance: projected.sub(point).length(),
132 }))
133}
134
135#[derive(Clone, Copy)]
136struct NewtonResult {
137 u: f64,
138 v: f64,
139 distance_squared: f64,
140 converged: bool,
141}
142
143fn surface_newton(
144 surface: &NurbsSurface,
145 point: Vec3,
146 seed_u: f64,
147 seed_v: f64,
148 domains: [f64; 4],
149 closed_u: bool,
150 closed_v: bool,
151) -> Result<NewtonResult, String> {
152 let [u0, u1, v0, v1] = domains;
153 let mut u = seed_u;
154 let mut v = seed_v;
155 let mut converged = false;
156 let mut best = NewtonResult {
157 u,
158 v,
159 distance_squared: surface.evaluate(u, v)?.sub(point).length_squared(),
160 converged,
161 };
162 for _ in 0..MAX_NEWTON_ITERATIONS {
163 let derivatives = surface.derivatives_small(u, v, 2)?;
164 let residual = derivatives[0][0].sub(point);
165 let distance_squared = residual.length_squared();
166 if distance_squared < best.distance_squared {
167 best.u = u;
168 best.v = v;
169 best.distance_squared = distance_squared;
170 }
171 let f = derivatives[1][0].dot(residual);
172 let g = derivatives[0][1].dot(residual);
173 let residual_length = distance_squared.sqrt();
174 if residual_length <= LINEAR_TOLERANCE {
175 converged = true;
176 break;
177 }
178 let tangent_u_length = derivatives[1][0].length();
179 let tangent_v_length = derivatives[0][1].length();
180 let cosine_u = if tangent_u_length * residual_length <= EPSILON {
181 0.0
182 } else {
183 f.abs() / (tangent_u_length * residual_length)
184 };
185 let cosine_v = if tangent_v_length * residual_length <= EPSILON {
186 0.0
187 } else {
188 g.abs() / (tangent_v_length * residual_length)
189 };
190 if cosine_u <= 1e-10 && cosine_v <= 1e-10 {
191 converged = true;
192 break;
193 }
194 let j00 = derivatives[2][0].dot(residual) + derivatives[1][0].length_squared();
195 let j01 = derivatives[1][1].dot(residual) + derivatives[1][0].dot(derivatives[0][1]);
196 let j11 = derivatives[0][2].dot(residual) + derivatives[0][1].length_squared();
197 let determinant = j00 * j11 - j01 * j01;
198 if determinant.abs() <= EPSILON {
199 break;
200 }
201 let mut du = (-f * j11 + g * j01) / determinant;
202 let mut dv = (-g * j00 + f * j01) / determinant;
203 du = du.clamp(-(u1 - u0) / 4.0, (u1 - u0) / 4.0);
204 dv = dv.clamp(-(v1 - v0) / 4.0, (v1 - v0) / 4.0);
205 let next_u = fit_parameter(u + du, u0, u1, closed_u);
206 let next_v = fit_parameter(v + dv, v0, v1, closed_v);
207 let stalled =
208 (next_u - u).abs() <= 1e-15 * (u1 - u0) && (next_v - v).abs() <= 1e-15 * (v1 - v0);
209 u = next_u;
210 v = next_v;
211 if stalled {
212 break;
213 }
214 }
215 let final_distance_squared = surface.evaluate(u, v)?.sub(point).length_squared();
216 if final_distance_squared < best.distance_squared {
217 best.u = u;
218 best.v = v;
219 best.distance_squared = final_distance_squared;
220 }
221 best.converged = converged;
222 Ok(best)
223}
224
225pub fn project_point_to_surface(
226 surface: &NurbsSurface,
227 point: Vec3,
228) -> Result<SurfaceProjection, String> {
229 if let Some(analytic) = surface.analytic() {
233 if let Some(projection) = analytic.project(surface, point) {
234 return Ok(projection);
235 }
236 }
237 project_point_to_surface_general(surface, point)
238}
239
240pub fn project_point_to_surface_seeded(
250 surface: &NurbsSurface,
251 point: Vec3,
252 seed_u: f64,
253 seed_v: f64,
254) -> Result<SurfaceProjection, String> {
255 let [u0, u1] = surface.domain_u()?;
256 let [v0, v1] = surface.domain_v()?;
257 let (closed_u, closed_v) = surface.closed_directions()?;
258 let result = surface_newton(
259 surface,
260 point,
261 seed_u,
262 seed_v,
263 [u0, u1, v0, v1],
264 closed_u,
265 closed_v,
266 )?;
267 let projected = surface.evaluate(result.u, result.v)?;
268 Ok(SurfaceProjection {
269 u: result.u,
270 v: result.v,
271 point: projected,
272 distance: projected.sub(point).length(),
273 })
274}
275
276fn projection_seed_grid(surface: &NurbsSurface) -> Result<&[(f64, f64, Vec3)], String> {
281 if let Some(grid) = surface.projection_grid.get() {
282 return Ok(grid);
283 }
284 let [u0, u1] = surface.domain_u()?;
285 let [v0, v1] = surface.domain_v()?;
286 let (closed_u, closed_v) = surface.closed_directions()?;
287 let mut breaks_u = vec![u0];
288 breaks_u.extend(interior_knots(&surface.knots_u, surface.degree_u));
289 breaks_u.push(u1);
290 let mut breaks_v = vec![v0];
291 breaks_v.extend(interior_knots(&surface.knots_v, surface.degree_v));
292 breaks_v.push(v1);
293 let samples_u = if closed_u {
294 8usize.max(surface.degree_u * 4)
295 } else {
296 3usize.max(surface.degree_u + 1)
297 };
298 let samples_v = if closed_v {
299 8usize.max(surface.degree_v * 4)
300 } else {
301 3usize.max(surface.degree_v + 1)
302 };
303 let mut grid = Vec::new();
304 for u_pair in breaks_u.windows(2) {
305 for v_pair in breaks_v.windows(2) {
306 for i in 0..=samples_u {
307 for j in 0..=samples_v {
308 let u = u_pair[0] + (u_pair[1] - u_pair[0]) * i as f64 / samples_u as f64;
309 let v = v_pair[0] + (v_pair[1] - v_pair[0]) * j as f64 / samples_v as f64;
310 grid.push((u, v, surface.evaluate(u, v)?));
311 }
312 }
313 }
314 }
315 Ok(surface.projection_grid.get_or_init(|| grid))
316}
317
318fn projection_dense_grid(surface: &NurbsSurface) -> Result<&[(f64, f64, Vec3)], String> {
325 if let Some(grid) = surface.projection_dense_grid.get() {
326 return Ok(grid);
327 }
328 let [u0, u1] = surface.domain_u()?;
329 let [v0, v1] = surface.domain_v()?;
330 let mut breaks_u = vec![u0];
331 breaks_u.extend(interior_knots(&surface.knots_u, surface.degree_u));
332 breaks_u.push(u1);
333 let mut breaks_v = vec![v0];
334 breaks_v.extend(interior_knots(&surface.knots_v, surface.degree_v));
335 breaks_v.push(v1);
336 let count_u = 32usize.max((breaks_u.len() - 1) * 8);
337 let count_v = 16usize.max((breaks_v.len() - 1) * 8);
338 let mut grid = Vec::with_capacity((count_u + 1) * (count_v + 1));
339 for i in 0..=count_u {
340 for j in 0..=count_v {
341 let u = u0 + (u1 - u0) * i as f64 / count_u as f64;
342 let v = v0 + (v1 - v0) * j as f64 / count_v as f64;
343 grid.push((u, v, surface.evaluate(u, v)?));
344 }
345 }
346 Ok(surface.projection_dense_grid.get_or_init(|| grid))
347}
348
349const RING_EXPONENTS: usize = 22;
353const RING_INDICES: usize = 33; #[inline]
356fn ring_slot(side: usize, exponent_index: usize, index: usize) -> usize {
357 (side * RING_EXPONENTS + exponent_index) * RING_INDICES + index
358}
359
360fn projection_ring_grid(surface: &NurbsSurface) -> Result<&[Vec3], String> {
367 if let Some(grid) = surface.projection_ring_grid.get() {
368 return Ok(grid);
369 }
370 let [u0, u1] = surface.domain_u()?;
371 let [v0, v1] = surface.domain_v()?;
372 let mut grid = vec![Vec3::default(); 4 * RING_EXPONENTS * RING_INDICES];
373 for exponent in 1..=RING_EXPONENTS as i32 {
374 let offset = 1.0 / 2f64.powi(exponent);
375 let e = exponent as usize - 1;
376 let v_lo = v0 + (v1 - v0) * offset;
377 let v_hi = v1 - (v1 - v0) * offset;
378 let u_lo = u0 + (u1 - u0) * offset;
379 let u_hi = u1 - (u1 - u0) * offset;
380 for index in 0..=32 {
381 let u = u0 + (u1 - u0) * index as f64 / 32.0;
382 grid[ring_slot(0, e, index as usize)] = surface.evaluate(u, v_lo)?;
383 grid[ring_slot(1, e, index as usize)] = surface.evaluate(u, v_hi)?;
384 }
385 for index in 0..=32 {
386 let v = v0 + (v1 - v0) * index as f64 / 32.0;
387 grid[ring_slot(2, e, index as usize)] = surface.evaluate(u_lo, v)?;
388 grid[ring_slot(3, e, index as usize)] = surface.evaluate(u_hi, v)?;
389 }
390 }
391 Ok(surface.projection_ring_grid.get_or_init(|| grid))
392}
393
394pub(crate) fn project_point_to_surface_general(
395 surface: &NurbsSurface,
396 point: Vec3,
397) -> Result<SurfaceProjection, String> {
398 let [u0, u1] = surface.domain_u()?;
399 let [v0, v1] = surface.domain_v()?;
400 let (closed_u, closed_v) = surface.closed_directions()?;
401 let mut best = NewtonResult {
402 u: u0,
403 v: v0,
404 distance_squared: f64::INFINITY,
405 converged: false,
406 };
407 for &(u, v, sample) in projection_seed_grid(surface)? {
408 let distance_squared = sample.sub(point).length_squared();
409 if distance_squared < best.distance_squared {
410 best.u = u;
411 best.v = v;
412 best.distance_squared = distance_squared;
413 }
414 }
415 if let Some(revolution) = project_linear_revolution(surface, point)? {
416 if revolution.distance * revolution.distance < best.distance_squared {
417 best.u = revolution.u;
418 best.v = revolution.v;
419 best.distance_squared = revolution.distance * revolution.distance;
420 }
421 }
422 let first_newton = surface_newton(
423 surface,
424 point,
425 best.u,
426 best.v,
427 [u0, u1, v0, v1],
428 closed_u,
429 closed_v,
430 )?;
431 if first_newton.distance_squared < best.distance_squared {
432 best = first_newton;
433 }
434 let (_, su, sv) = surface.deriv1(best.u, best.v)?;
435 let degenerate = su.cross(sv).length() <= 1e-5 * (1.0 + su.length() + sv.length());
436 if degenerate || !first_newton.converged {
437 let mut grid = best;
438 for &(u, v, sample) in projection_dense_grid(surface)? {
439 let distance_squared = sample.sub(point).length_squared();
440 if distance_squared < grid.distance_squared {
441 grid.u = u;
442 grid.v = v;
443 grid.distance_squared = distance_squared;
444 }
445 }
446 if grid.distance_squared < best.distance_squared {
447 best = grid;
448 }
449 let polished = surface_newton(
450 surface,
451 point,
452 grid.u,
453 grid.v,
454 [u0, u1, v0, v1],
455 closed_u,
456 closed_v,
457 )?;
458 if polished.distance_squared < best.distance_squared {
459 best = polished;
460 }
461
462 let (_, su, sv) = surface.deriv1(best.u, best.v)?;
463 if su.cross(sv).length() <= 1e-5 * (1.0 + su.length() + sv.length()) {
464 let ring_grid = projection_ring_grid(surface)?;
465 let mut ring = best;
466 let scan = |side: usize,
470 exponent_index: usize,
471 along_u: bool,
472 fixed: f64,
473 candidate: &mut NewtonResult| {
474 for index in 0..=32 {
475 let moving = if along_u {
476 u0 + (u1 - u0) * index as f64 / 32.0
477 } else {
478 v0 + (v1 - v0) * index as f64 / 32.0
479 };
480 let (u, v) = if along_u {
481 (moving, fixed)
482 } else {
483 (fixed, moving)
484 };
485 let distance_squared = ring_grid
486 [ring_slot(side, exponent_index, index as usize)]
487 .sub(point)
488 .length_squared();
489 if distance_squared < candidate.distance_squared {
490 candidate.u = u;
491 candidate.v = v;
492 candidate.distance_squared = distance_squared;
493 }
494 }
495 };
496 for exponent in 1..=22 {
497 let offset = 1.0 / 2f64.powi(exponent);
498 let e = exponent as usize - 1;
499 if (best.v - v0).abs() <= (v1 - v0) * 0.02 {
500 scan(0, e, true, v0 + (v1 - v0) * offset, &mut ring);
501 }
502 if (best.v - v1).abs() <= (v1 - v0) * 0.02 {
503 scan(1, e, true, v1 - (v1 - v0) * offset, &mut ring);
504 }
505 if (best.u - u0).abs() <= (u1 - u0) * 0.02 {
506 scan(2, e, false, u0 + (u1 - u0) * offset, &mut ring);
507 }
508 if (best.u - u1).abs() <= (u1 - u0) * 0.02 {
509 scan(3, e, false, u1 - (u1 - u0) * offset, &mut ring);
510 }
511 }
512 if ring.distance_squared < best.distance_squared {
513 best = ring;
514 }
515 let polished = surface_newton(
516 surface,
517 point,
518 ring.u,
519 ring.v,
520 [u0, u1, v0, v1],
521 closed_u,
522 closed_v,
523 )?;
524 if polished.distance_squared < best.distance_squared {
525 best = polished;
526 }
527 }
528 }
529 let projected = surface.evaluate(best.u, best.v)?;
530 Ok(SurfaceProjection {
531 u: best.u,
532 v: best.v,
533 point: projected,
534 distance: projected.sub(point).length(),
535 })
536}
537
538