1use crate::frame::Frame3;
4use crate::nurbs::decompose::surface_to_bezier_patches;
5use crate::nurbs::surface::NurbsSurface;
6use crate::vec::{Point3, Vec3};
7
8const MAX_DEPTH: u32 = 48;
10
11const MAX_VISITS: usize = 20_000;
13
14const LEAF: f64 = 1e-3;
17
18const GRAZE_COS: f64 = 1e-9;
20
21const SEPARATION: f64 = 4.0;
24
25#[derive(Debug, Clone)]
32pub struct RaySurface {
33 surface: NurbsSurface,
34 patches: Vec<Patch>,
35 scale: f64,
36}
37
38#[derive(Debug, Clone)]
39struct Patch {
40 rows: usize,
41 cols: usize,
42 pts: Vec<[f64; 4]>,
45 u: (f64, f64),
46 v: (f64, f64),
47}
48
49#[derive(Debug, Clone, Copy)]
51pub struct RayHit {
52 pub u: f64,
54 pub v: f64,
56 pub t: f64,
58 pub point: Point3,
60}
61
62#[derive(Debug, Clone, Default)]
65pub struct RayHits {
66 pub hits: Vec<RayHit>,
68 pub grazed: bool,
71}
72
73impl Patch {
74 fn point(&self, i: usize, j: usize) -> Point3 {
75 let [x, y, z, w] = self.pts[i * self.cols + j];
76 Point3::new(x / w, y / w, z / w)
77 }
78
79 fn split(&self, along_u: bool) -> (Self, Self) {
82 let (n, m) = if along_u {
83 (self.rows, self.cols)
84 } else {
85 (self.cols, self.rows)
86 };
87 let at = |k: usize, l: usize| {
88 if along_u {
89 k * self.cols + l
90 } else {
91 l * self.cols + k
92 }
93 };
94 let mut low = self.pts.clone();
95 let mut high = self.pts.clone();
96 let mut work = vec![[0.0; 4]; n];
97 for l in 0..m {
98 for (k, slot) in work.iter_mut().enumerate() {
99 *slot = self.pts[at(k, l)];
100 }
101 low[at(0, l)] = work[0];
102 high[at(n - 1, l)] = work[n - 1];
103 for r in 1..n {
104 for k in 0..n - r {
105 for c in 0..4 {
106 work[k][c] = 0.5 * (work[k][c] + work[k + 1][c]);
107 }
108 }
109 low[at(r, l)] = work[0];
110 high[at(n - 1 - r, l)] = work[n - 1 - r];
111 }
112 }
113 let (mut a, mut b) = (self.clone(), self.clone());
114 a.pts = low;
115 b.pts = high;
116 if along_u {
117 let mid = f64::midpoint(self.u.0, self.u.1);
118 (a.u, b.u) = ((self.u.0, mid), (mid, self.u.1));
119 } else {
120 let mid = f64::midpoint(self.v.0, self.v.1);
121 (a.v, b.v) = ((self.v.0, mid), (mid, self.v.1));
122 }
123 (a, b)
124 }
125
126 fn spans(&self) -> (f64, f64) {
130 let mut along_u = 0.0_f64;
131 for j in 0..self.cols {
132 let length: f64 = (1..self.rows)
133 .map(|i| (self.point(i, j) - self.point(i - 1, j)).length())
134 .sum();
135 along_u = along_u.max(length);
136 }
137 let mut along_v = 0.0_f64;
138 for i in 0..self.rows {
139 let length: f64 = (1..self.cols)
140 .map(|j| (self.point(i, j) - self.point(i, j - 1)).length())
141 .sum();
142 along_v = along_v.max(length);
143 }
144 (along_u, along_v)
145 }
146}
147
148impl RaySurface {
149 #[must_use]
153 pub fn new(surface: &NurbsSurface) -> Option<Self> {
154 let clamped = |knots: &[f64], degree: usize| {
157 let n = knots.len();
158 n > 2 * degree + 1
159 && knots[..=degree]
160 .iter()
161 .all(|&k| (k - knots[0]).abs() <= 0.0)
162 && knots[n - degree - 1..]
163 .iter()
164 .all(|&k| (k - knots[n - 1]).abs() <= 0.0)
165 };
166 if surface
167 .weights()
168 .iter()
169 .flatten()
170 .any(|&w| !w.is_finite() || w <= 0.0)
171 || !clamped(surface.knots_u(), surface.degree_u())
172 || !clamped(surface.knots_v(), surface.degree_v())
173 {
174 return None;
175 }
176 let patches = surface_to_bezier_patches(surface)
177 .ok()?
178 .into_iter()
179 .map(|p| {
180 let cps = p.surface.control_points();
181 let ws = p.surface.weights();
182 let rows = cps.len();
183 let cols = cps.first().map_or(0, Vec::len);
184 let pts = cps
185 .iter()
186 .zip(ws)
187 .flat_map(|(row, wrow)| {
188 row.iter()
189 .zip(wrow)
190 .map(|(c, &w)| [c.x() * w, c.y() * w, c.z() * w, w])
191 })
192 .collect();
193 Patch {
194 rows,
195 cols,
196 pts,
197 u: p.u_range,
198 v: p.v_range,
199 }
200 })
201 .filter(|p| p.rows > 0 && p.cols > 0)
202 .collect();
203 let b = surface.aabb();
204 let scale = (b.max - b.min).length().max(1e-9);
205 Some(Self {
206 surface: surface.clone(),
207 patches,
208 scale,
209 })
210 }
211
212 #[must_use]
214 pub const fn surface(&self) -> &NurbsSurface {
215 &self.surface
216 }
217
218 #[must_use]
223 pub fn hits(&self, origin: Point3, dir: Vec3, reach: f64) -> RayHits {
224 let mut out = RayHits::default();
225 let Ok(frame) = Frame3::from_normal(origin, dir) else {
226 out.grazed = true;
227 return out;
228 };
229 let leaf = LEAF * self.scale;
230 let mut stack: Vec<(Patch, u32)> = self.patches.iter().map(|p| (p.clone(), 0)).collect();
231 let mut visits = 0_usize;
232 while let Some((patch, depth)) = stack.pop() {
233 visits += 1;
234 if visits > MAX_VISITS {
235 out.grazed = true;
236 return out;
237 }
238 let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
239 for i in 0..patch.rows {
240 for j in 0..patch.cols {
241 let d = patch.point(i, j) - origin;
242 for (k, x) in [frame.x.dot(d), frame.y.dot(d), frame.z.dot(d)]
243 .into_iter()
244 .enumerate()
245 {
246 lo[k] = lo[k].min(x);
247 hi[k] = hi[k].max(x);
248 }
249 }
250 }
251 if lo[0] > reach || hi[0] < -reach || lo[1] > reach || hi[1] < -reach || hi[2] < -reach
252 {
253 continue;
254 }
255 let (along_u, along_v) = patch.spans();
256 if along_u.max(along_v) <= leaf || depth >= MAX_DEPTH {
257 if self.solve(&patch, origin, frame.z, reach, &mut out) {
260 continue;
261 }
262 if depth >= MAX_DEPTH {
263 out.grazed = true;
264 continue;
265 }
266 }
267 let (a, b) = patch.split(along_u >= along_v);
268 stack.push((a, depth + 1));
269 stack.push((b, depth + 1));
270 }
271 out
272 }
273
274 fn second_crossing_near(&self, u: f64, v: f64, dir: Vec3) -> bool {
280 let d = self.surface.derivatives(u, v, 2);
281 let (su, sv) = (d[1][0], d[0][1]);
282 let Ok(n) = su.cross(sv).normalize() else {
283 return true;
284 };
285 let slant = n.dot(dir);
286 if slant.abs() <= GRAZE_COS {
287 return true;
288 }
289 let w = dir - n * slant;
290 let (guu, guv, gvv) = (su.dot(su), su.dot(sv), sv.dot(sv));
291 let det = guu.mul_add(gvv, -guv * guv);
292 if det <= 0.0 || w.length() <= GRAZE_COS {
293 return false;
294 }
295 let (wu, wv) = (su.dot(w), sv.dot(w));
296 let a = gvv.mul_add(wu, -guv * wv) / det;
297 let b = guu.mul_add(wv, -guv * wu) / det;
298 let bend = n.dot(d[2][0] * (a * a) + d[1][1] * (2.0 * a * b) + d[0][2] * (b * b));
299 2.0 * slant.abs() <= SEPARATION * LEAF * self.scale * bend.abs()
300 }
301
302 fn solve(
307 &self,
308 leaf: &Patch,
309 origin: Point3,
310 dir: Vec3,
311 reach: f64,
312 out: &mut RayHits,
313 ) -> bool {
314 let (mut u, mut v) = (
315 f64::midpoint(leaf.u.0, leaf.u.1),
316 f64::midpoint(leaf.v.0, leaf.v.1),
317 );
318 let (du, dv) = (self.surface.domain_u(), self.surface.domain_v());
319 let mut t = dir.dot(self.surface.evaluate(u, v) - origin);
320 for _ in 0..32 {
321 let d = self.surface.derivatives(u, v, 1);
322 let (s, su, sv) = (d[0][0], d[1][0], d[0][1]);
323 let f = s - Vec3::new(origin.x(), origin.y(), origin.z()) - dir * t;
324 let normal = su.cross(sv);
325 let det = -normal.dot(dir);
326 if f.length() <= 1e-12 * self.scale {
327 let slack_u = 1e-9 * (du.1 - du.0);
328 let slack_v = 1e-9 * (dv.1 - dv.0);
329 if u < leaf.u.0 - slack_u
330 || u > leaf.u.1 + slack_u
331 || v < leaf.v.0 - slack_v
332 || v > leaf.v.1 + slack_v
333 || t < -reach
334 {
335 return true;
336 }
337 out.grazed |= self.second_crossing_near(u, v, dir);
338 let point = origin + dir * t;
339 if !out.hits.iter().any(|h| (h.point - point).length() <= reach) {
340 out.hits.push(RayHit { u, v, t, point });
341 }
342 return true;
343 }
344 if det.abs() <= 1e-300 {
345 break;
346 }
347 let neg_dir = -dir;
349 let rhs = -f;
350 let x_u = rhs.cross(sv).dot(neg_dir) / det;
351 let x_v = su.cross(rhs).dot(neg_dir) / det;
352 let x_t = normal.dot(rhs) / det;
353 u = (u + x_u).clamp(du.0, du.1);
354 v = (v + x_v).clamp(dv.0, dv.1);
355 t += x_t;
356 }
357 false
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 #![allow(clippy::unwrap_used)]
364
365 use super::*;
366
367 fn lid() -> NurbsSurface {
370 let rows = (0..6)
371 .map(|i| {
372 (0..3)
373 .map(|j| {
374 let z = if (i, j) == (1, 1) { 10.0 } else { 0.0 };
375 Point3::new(f64::from(i) / 5.0, f64::from(j) / 2.0, z)
376 })
377 .collect()
378 })
379 .collect();
380 NurbsSurface::new(
381 5,
382 2,
383 [vec![0.0; 6], vec![1.0; 6]].concat(),
384 vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
385 rows,
386 vec![vec![1.0; 3]; 6],
387 )
388 .unwrap()
389 }
390
391 #[test]
392 fn a_ray_up_through_the_lids_peak_meets_it_there() {
393 let lid = RaySurface::new(&lid()).unwrap();
394 let up = Vec3::new(0.0, 0.0, 1.0);
395 let at_peak = lid.hits(Point3::new(0.2, 0.5, -1.0), up, 1e-9);
396 assert!(!at_peak.grazed);
397 assert_eq!(at_peak.hits.len(), 1);
398 assert!((at_peak.hits[0].point.z() - 2.048).abs() < 1e-12);
399 assert!((at_peak.hits[0].t - 3.048).abs() < 1e-12);
400 assert!((at_peak.hits[0].u - 0.2).abs() < 1e-9 && (at_peak.hits[0].v - 0.5).abs() < 1e-9);
401
402 let beside = lid.hits(Point3::new(1.5, 0.5, -1.0), up, 1e-9);
403 assert!(beside.hits.is_empty() && !beside.grazed);
404 let behind = lid.hits(Point3::new(0.2, 0.5, 3.0), up, 1e-9);
405 assert!(behind.hits.is_empty());
406 }
407
408 #[test]
411 fn a_level_ray_crosses_the_bulge_twice() {
412 let lid = RaySurface::new(&lid()).unwrap();
413 let across = lid.hits(Point3::new(-1.0, 0.5, 1.5), Vec3::new(1.0, 0.0, 0.0), 1e-9);
414 assert!(!across.grazed);
415 assert_eq!(across.hits.len(), 2, "{:?}", across.hits);
416 for h in &across.hits {
417 let u = h.point.x();
418 assert!((25.0 * u * (1.0 - u).powi(4) - 1.5).abs() < 1e-9, "{h:?}");
419 }
420 }
421
422 #[test]
423 fn a_ray_touching_the_peak_grazes() {
424 let lid = RaySurface::new(&lid()).unwrap();
425 let level = lid.hits(
426 Point3::new(-1.0, 0.5, 2.048),
427 Vec3::new(1.0, 0.0, 0.0),
428 1e-9,
429 );
430 assert!(level.grazed, "{level:?}");
431 }
432
433 #[test]
438 fn a_level_ray_crosses_a_tall_spike() {
439 let rows = (0..3)
440 .map(|i| {
441 (0..3)
442 .map(|j| {
443 let z = if (i, j) == (1, 1) { 4000.0 } else { 0.0 };
444 Point3::new(f64::from(i) / 2.0, f64::from(j) / 2.0, z)
445 })
446 .collect()
447 })
448 .collect();
449 let knots = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
450 let spike =
451 NurbsSurface::new(2, 2, knots.clone(), knots, rows, vec![vec![1.0; 3]; 3]).unwrap();
452 let ray = RaySurface::new(&spike).unwrap();
453 let across = ray.hits(
454 Point3::new(-1.0, 0.5, 500.0),
455 Vec3::new(1.0, 0.0, 0.0),
456 1e-9,
457 );
458 assert!(!across.grazed);
459 let mut xs: Vec<f64> = across.hits.iter().map(|h| h.point.x()).collect();
460 xs.sort_by(f64::total_cmp);
461 let root = std::f64::consts::FRAC_1_SQRT_2;
462 assert_eq!(xs.len(), 2, "{xs:?}");
463 assert!((xs[0] - (1.0 - root) / 2.0).abs() < 1e-9, "{xs:?}");
464 assert!((xs[1] - f64::midpoint(1.0, root)).abs() < 1e-9, "{xs:?}");
465 }
466
467 #[test]
473 fn a_ray_level_with_a_saddles_middle_crosses_it() {
474 let p = Point3::new;
475 let rows = vec![
476 vec![p(0.0, 0.0, 0.25), p(0.0, 1.0, -0.25)],
477 vec![p(1.0, 0.0, -0.25), p(1.0, 1.0, 0.25)],
478 vec![p(2000.0, 0.0, 0.0), p(2000.0, 1.0, 0.0)],
479 ];
480 let s = NurbsSurface::new(
481 1,
482 1,
483 vec![0.0, 0.0, 1.0, 2.0, 2.0],
484 vec![0.0, 0.0, 1.0, 1.0],
485 rows,
486 vec![vec![1.0; 2]; 3],
487 )
488 .unwrap();
489 let ray = RaySurface::new(&s).unwrap();
490 let dir = Vec3::new(1.0, 1.0, 0.0).normalize().unwrap();
491 let found = ray.hits(p(0.5, 0.5, 0.04), dir, 1e-9);
492 assert_eq!(found.hits.len(), 1, "{:?}", found.grazed);
493 assert!((found.hits[0].point - p(0.7, 0.7, 0.04)).length() < 1e-9);
494 }
495
496 #[test]
499 fn an_unclamped_surface_is_not_ready() {
500 let rows = (0..3)
501 .map(|i| {
502 (0..2)
503 .map(|j| Point3::new(0.5 * f64::from(i), f64::from(j), 0.0))
504 .collect()
505 })
506 .collect();
507 let s = NurbsSurface::new(
508 2,
509 1,
510 vec![-2.0, -1.0, 0.0, 1.0, 2.0, 3.0],
511 vec![0.0, 0.0, 1.0, 1.0],
512 rows,
513 vec![vec![1.0; 2]; 3],
514 )
515 .unwrap();
516 assert!(RaySurface::new(&s).is_none());
517 }
518
519 #[test]
520 fn a_surface_with_a_negative_weight_is_not_ready() {
521 let mut weights = vec![vec![1.0; 3]; 6];
522 weights[2][1] = -1.0;
523 let s = lid();
524 let s = NurbsSurface::new(
525 5,
526 2,
527 s.knots_u().to_vec(),
528 s.knots_v().to_vec(),
529 s.control_points().to_vec(),
530 weights,
531 )
532 .unwrap();
533 assert!(RaySurface::new(&s).is_none());
534 }
535}