geop_core_geometry/nurb_surface/
closest.rs1use geop_core_math::{
5 geop_error::GeopResult,
6 scalars::Scalar,
7 solvers::{linear::solve_symmetric2, minimize::descend},
8 vector::Vector3,
9};
10
11use super::NurbSurface;
12use crate::{
13 nearest::{PointPiece, SurfacePatch, least_distance},
14 search::{MAX_NODES, NEAREST_HANDOFF},
15};
16
17const NEWTON_STEPS: usize = 40;
20
21const HALVINGS: usize = 10;
24
25impl NurbSurface<4> {
26 pub fn closest_point(&self, target: &Vector3) -> GeopResult<(Scalar, Scalar, Vector3)> {
37 let nearest = least_distance(
38 vec![PointPiece(*target)],
39 vec![SurfacePatch(self.clone())],
40 self.size()?.mul(Scalar::from_f64(NEAREST_HANDOFF)),
41 MAX_NODES,
42 )?;
43 let distance = |p: &Vector3| p.sub(target).norm_sq().to_f64();
44 let ((mut u, mut v), mut p) = nearest.b;
45 for ((), (su, sv)) in nearest.candidates {
46 let (ru, rv) = self.refine_closest(target, su, sv)?;
47 let q = self.evaluate(ru, rv)?;
48 if distance(&q) < distance(&p) {
49 (u, v, p) = (ru, rv, q);
50 }
51 }
52 Ok((u, v, p))
53 }
54
55 pub fn refine_closest(
65 &self,
66 target: &Vector3,
67 u: Scalar,
68 v: Scalar,
69 ) -> GeopResult<(Scalar, Scalar)> {
70 let ((u_lo, u_hi), (v_lo, v_hi)) = (self.domain_u(), self.domain_v());
71 let positive = |a: Scalar, b: Scalar, c: Scalar| {
72 a.definitely_greater(Scalar::ZERO)
73 && a.mul(c).sub(b.mul(b)).definitely_greater(Scalar::ZERO)
74 };
75 descend(
76 (u, v),
77 NEWTON_STEPS,
78 HALVINGS,
79 |&(u, v)| -> GeopResult<f64> {
80 Ok(self.evaluate(u, v)?.sub(target).norm_sq().to_f64())
81 },
82 |&(u, v)| -> GeopResult<_> {
83 let r = self.evaluate(u, v)?.sub(target);
84 let (su, sv) = self.derivatives(u, v)?;
85 let [suu, suv, svv] = self.second_derivatives(u, v)?;
86 let (gu, gv) = (r.prod_dot(&su), r.prod_dot(&sv));
87 let (a11, a12, a22) = (su.prod_dot(&su), su.prod_dot(&sv), sv.prod_dot(&sv));
88 let (h11, h12, h22) = (
89 a11.add(r.prod_dot(&suu)),
90 a12.add(r.prod_dot(&suv)),
91 a22.add(r.prod_dot(&svv)),
92 );
93 let downhill = (gu.neg(), gv.neg());
94 let (du, dv) = if positive(h11, h12, h22) {
95 solve_symmetric2((h11, h12, h22), downhill)?
96 } else if positive(a11, a12, a22) {
97 solve_symmetric2((a11, a12, a22), downhill)?
98 } else if a11.definitely_greater(Scalar::ZERO) {
99 (gu.neg().div(a11)?, Scalar::ZERO)
100 } else if a22.definitely_greater(Scalar::ZERO) {
101 (Scalar::ZERO, gv.neg().div(a22)?)
102 } else {
103 return Ok(None);
104 };
105 Ok(Some(move |scale: Scalar| {
106 (
107 u.add(du.mul(scale)).sharpen().clamped(u_lo, u_hi),
108 v.add(dv.mul(scale)).sharpen().clamped(v_lo, v_hi),
109 )
110 }))
111 },
112 )
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use geop_core_math::{
119 scalars::Scalar,
120 vector::{Vector3, Vector4},
121 };
122
123 use crate::nurb_surface::NurbSurface3D;
124
125 fn quarter_cylinder() -> NurbSurface3D {
128 let w = std::f64::consts::FRAC_1_SQRT_2;
129 let h = |x: f64, y: f64, z: f64, w: f64| {
130 Vector4::from_array([x * w, y * w, z * w, w].map(Scalar::from_f64))
131 };
132 let mut points = Vec::new();
133 for (x, y, wt) in [(1.0, 0.0, 1.0), (1.0, 1.0, w), (0.0, 1.0, 1.0)] {
134 for z in [0.0, 2.0] {
135 points.push(h(x, y, z, wt));
136 }
137 }
138 let knots = |k: &[f64]| k.iter().map(|&k| Scalar::from_f64(k)).collect();
139 NurbSurface3D::try_new(
140 2,
141 1,
142 points,
143 knots(&[0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
144 knots(&[0.0, 0.0, 1.0, 1.0]),
145 )
146 .unwrap()
147 }
148
149 #[test]
152 fn nearest_point_of_a_cylinder() {
153 let surface = quarter_cylinder();
154 let target = Vector3::from_array([2.0, 0.5, 0.5].map(Scalar::from_f64));
155 let (_, _, p) = surface.closest_point(&target).unwrap();
156 let r = 4.25f64.sqrt();
157 let expected = Vector3::from_array([2.0 / r, 0.5 / r, 0.5].map(Scalar::from_f64));
158 assert!(p.sub(&expected).norm().to_f64() < 1e-12, "{p:?}");
159 }
160}