1use crate::geom::{Matrix, Point, Vector};
17use crate::material::Material;
18use crate::sampler::SamplerPoint;
19
20use rand::prelude::*;
21
22pub struct Segment<R: Rng> {
53 inner: Object<R>,
54}
55
56#[derive(Clone)]
60pub(crate) enum Object<R: Rng> {
61 Line {
63 material: Material<R>,
65 p0: SamplerPoint<R>,
67 p1: SamplerPoint<R>,
69 },
70 Curve {
72 material: Material<R>,
74 p0: SamplerPoint<R>,
76 p1: SamplerPoint<R>,
78 p2: SamplerPoint<R>,
80 },
81}
82
83impl<R> Segment<R>
84where
85 R: Rng,
86{
87 pub fn line_from_points<A, B>(start: A, end: B, material: Material<R>) -> Self
89 where
90 A: Into<SamplerPoint<R>>,
91 B: Into<SamplerPoint<R>>,
92 {
93 let start = start.into();
94 let end = end.into();
95 Self {
96 inner: Object::Line {
97 material,
98 p0: start.into(),
99 p1: end.into(),
100 },
101 }
102 }
103
104 pub fn curve_from_points<A, B, C>(start: A, mid: B, end: C, material: Material<R>) -> Self
109 where
110 A: Into<SamplerPoint<R>>,
111 B: Into<SamplerPoint<R>>,
112 C: Into<SamplerPoint<R>>,
113 {
114 let p0 = start.into();
115 let p1 = mid.into();
116 let p2 = end.into();
117 Self {
118 inner: Object::Curve {
119 material,
120 p0,
121 p1,
122 p2,
123 },
124 }
125 }
126}
127
128impl<R> Object<R>
129where
130 R: Rng,
131{
132 #[inline(always)]
136 pub(crate) fn process_material(
137 &self,
138 direction: &Vector,
139 normal: &Vector,
140 wavelength: f64,
141 alpha: f64,
142 rng: &mut R,
143 ) -> Option<Vector> {
144 let material = match self {
145 Object::Curve { material, .. } => material,
146 Object::Line { material, .. } => material,
147 };
148
149 (material)(direction, normal, wavelength, alpha, rng)
150 }
151
152 #[inline(always)]
153 fn get_line_hit(
154 s1: Point,
155 s2: Point,
156 origin: &Point,
157 dir: &Vector,
158 ) -> Option<(Point, Vector, f64)> {
159 let sd = s2 - s1;
160 let mat_a = Matrix {
161 a1: sd.x,
162 b1: -dir.x,
163 a2: sd.y,
164 b2: -dir.y,
165 };
166
167 let omega = origin.clone() - s1;
168
169 let result = match mat_a.inverse() {
170 Some(m) => m * omega,
171 None => {
172 return None; }
174 };
175 if (result.x >= 0.0) && (result.x <= 1.0) && (result.y > 0.0) {
176 } else {
177 return None;
178 };
179
180 let alpha = result.x;
181 let distance = result.y;
182
183 let hit = *origin + (*dir * distance);
184 let norm = Vector { x: -sd.y, y: sd.x };
185
186 return Some((hit, norm, alpha));
187 }
188
189 #[inline(always)]
190 fn get_point_on_bezier(p0: Point, p1: Point, p2: Point, alpha: f64) -> Point {
191 let beta = 1.0 - alpha;
192 ((p0.v() * beta * beta) + (p1.v() * beta * alpha * 2.0) + (p2.v() * alpha * alpha)).p()
193 }
194
195 #[inline(always)]
196 fn get_normal_on_bezier(p0: Point, p1: Point, p2: Point, alpha: f64) -> Vector {
197 let w0 = (p1 - p0) * 2.0;
198 let w1 = (p2 - p1) * 2.0;
199 (w0 * (1.0 - alpha) + w1 * alpha).normal()
200 }
201
202 #[inline(always)]
203 fn process_curve_hit(
204 p0: Point,
205 p1: Point,
206 p2: Point,
207 origin: &Point,
208 dir: &Vector,
209 alpha: f64,
210 ) -> Option<(f64, Point)> {
211 if 0.0 >= alpha || alpha >= 1.0 {
212 return None;
213 }
214 let hit = Self::get_point_on_bezier(p0, p1, p2, alpha);
215 let ray_alpha = (hit.x - origin.x) / dir.x;
217 if ray_alpha < 0.0 {
218 return None;
219 } else {
220 Some((ray_alpha, hit))
221 }
222 }
223
224 #[inline(always)]
225 fn get_curve_hit(
226 p0: Point,
227 p1: Point,
228 p2: Point,
229 origin: &Point,
230 dir: &Vector,
231 ) -> Option<(Point, Vector, f64)> {
232 let rotation_matrix = Matrix {
234 a1: dir.x,
235 a2: dir.y,
236 b1: dir.y,
237 b2: -dir.x,
238 };
239
240 let pa0 = rotation_matrix * (p0 - *origin).p();
241 let pa1 = rotation_matrix * (p1 - *origin).p();
242 let pa2 = rotation_matrix * (p2 - *origin).p();
243 let a = pa0.y;
244 let b = pa1.y;
245 let c = pa2.y;
246 let d = a - 2.0 * b + c;
247
248 if d.abs() > 0.0001 {
249 let m1 = -f64::sqrt(b * b - a * c);
250 let m2 = b - a;
251 let v1 = -(m1 + m2) / d;
252 let v2 = -(-m1 + m2) / d;
253 let r1 = Self::process_curve_hit(p0, p1, p2, origin, dir, v1);
254 let r2 = Self::process_curve_hit(p0, p1, p2, origin, dir, v2);
255 if r1.is_none() && r2.is_none() {
256 return None;
257 }
258 let (d1, hit1) = r1.unwrap_or((f64::MAX, (0.0, 0.0).into()));
259 let (d2, hit2) = r2.unwrap_or((f64::MAX, (0.0, 0.0).into()));
260 if d1 < d2 {
261 let norm = Self::get_normal_on_bezier(p0, p1, p2, v1);
262 Some((hit1, norm, v1))
263 } else {
264 let norm = Self::get_normal_on_bezier(p0, p1, p2, v2);
265 Some((hit2, norm, v2))
266 }
267 } else {
268 if b != c {
269 let t = (2.0 * b - c) / (2.0 * b - 2.0 * c);
270 let (_, hit) = Self::process_curve_hit(p0, p1, p2, origin, dir, t)?;
271 let norm = Self::get_normal_on_bezier(p0, p1, p2, t);
272 Some((hit, norm, t))
273 } else {
274 None
275 }
276 }
277 }
278
279 #[inline(always)]
290 pub(crate) fn get_hit(
291 &self,
292 origin: &Point,
293 dir: &Vector,
294 rng: &mut R,
295 ) -> Option<(Point, Vector, f64)> {
296 match self {
297 Object::Curve { p0, p1, p2, .. } => {
298 Self::get_curve_hit(p0.get(rng), p1.get(rng), p2.get(rng), origin, dir)
299 }
300 Object::Line { p0, p1, .. } => {
301 Self::get_line_hit(p0.get(rng), p1.get(rng), origin, dir)
302 }
303 }
304 }
305}
306
307impl<R> From<Segment<R>> for Object<R>
308where
309 R: Rng,
310{
311 fn from(value: Segment<R>) -> Self {
312 value.inner
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 type RandGen = rand_pcg::Pcg64Mcg;
319
320 use crate::material::hqz_legacy_default;
321
322 use super::Object;
323 use super::Segment;
324
325 use crate::geom::{Point, Vector};
326 use crate::material::hqz_legacy;
327
328 use rand::prelude::*;
329
330 #[test]
331 fn segment_into() {
332 let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), hqz_legacy_default());
333 let _: Object<RandGen> = s.into();
334 }
336
337 #[test]
338 fn hit_line_1() {
341 let mut rng = RandGen::from_entropy();
342
343 let m = hqz_legacy(0.3, 0.3, 0.3);
344
345 let obj = Object::Line {
346 p0: (0.0, 0.0).into(),
347 p1: (10.0, 10.0).into(),
348 material: m,
349 };
350
351 let origin = Point { x: 10.0, y: 0.0 };
352 let dir = Vector { x: -1.0, y: 1.0 };
353
354 let a = obj.get_hit(&origin, &dir, &mut rng);
355
356 let (a, b, _) = a.expect("A was not meant to be `None`");
357
358 assert_eq!(a.x, 5.0);
360 assert_eq!(a.y, 5.0);
361
362 assert_eq!(b.x, -10.0);
364 assert_eq!(b.y, 10.0);
365 }
367
368 #[test]
369 fn hit_curve_1() {
372 let mut rng = RandGen::from_entropy();
373
374 let m = hqz_legacy(0.3, 0.3, 0.3);
375
376 let obj = Object::Curve {
377 p0: (0.0, 0.0).into(),
378 p1: (5.0, 5.0).into(),
379 p2: (10.0, 10.0).into(),
380 material: m,
381 };
382
383 let origin = Point { x: 0.0, y: 5.0 };
384 let dir = Vector { x: 1.0, y: 0.0 };
385
386 let a = obj.get_hit(&origin, &dir, &mut rng);
387
388 let (a, b, _) = a.expect("A was not meant to be `None`");
389
390 assert_eq!(a.x, 5.0);
392 assert_eq!(a.y, 5.0);
393
394 assert_eq!(b.x, -10.0);
396 assert_eq!(b.y, 10.0);
397 }
399
400 #[test]
401 fn hit_curve_2() {
404 let mut rng = RandGen::from_entropy();
405
406 let m = hqz_legacy(0.3, 0.3, 0.3);
407
408 let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
409
410 let origin = Point { x: 0.0, y: 10.0 };
411 let dir = Vector { x: 1.0, y: -1.0 };
412
413 let a = obj.get_hit(&origin, &dir, &mut rng);
414
415 let (a, b, _) = a.expect("A was not meant to be `None`");
416
417 assert_eq!(a.x, 5.0);
419 assert_eq!(a.y, 5.0);
420
421 assert_eq!(b.x, -10.0);
423 assert_eq!(b.y, 10.0);
424 }
426
427 #[test]
428 fn hit_curve_horz() {
431 let mut rng = RandGen::from_entropy();
432
433 let m = hqz_legacy(0.3, 0.3, 0.3);
434
435 let obj = Segment::curve_from_points((0.0, 5.0), (5.0, 5.0), (10.0, 5.0), m).inner;
436
437 let origin = Point { x: 5.0, y: 0.0 };
438 let dir = Vector { x: 0.0, y: 1.0 };
439
440 let a = obj.get_hit(&origin, &dir, &mut rng);
441
442 let (a, b, _) = a.expect("A was not meant to be `None`");
443
444 assert_eq!(a.x.round(), 5.0);
446 assert_eq!(a.y.round(), 5.0);
447
448 assert_eq!(b.x.round(), 0.0);
450 assert_eq!(b.y.round(), 10.0);
451 }
453
454 #[test]
455 fn hit_curve_vert() {
458 let mut rng = RandGen::from_entropy();
459
460 let m = hqz_legacy(0.3, 0.3, 0.3);
461
462 let obj = Segment::curve_from_points((5.0, 0.0), (5.0, 5.0), (5.0, 10.0), m).inner;
463
464 let origin = Point { x: 0.0, y: 5.0 };
465 let dir = Vector { x: 1.0, y: 0.0 };
466
467 let a = obj.get_hit(&origin, &dir, &mut rng);
468
469 let (a, b, _) = a.expect("A was not meant to be `None`");
470
471 assert_eq!(a.x, 5.0);
473 assert_eq!(a.y, 5.0);
474
475 assert_eq!(b.x.round(), -10.0);
477 assert_eq!(b.y.round(), 0.0);
478 }
480
481 #[test]
482 fn miss_line_1() {
486 let mut rng = RandGen::from_entropy();
487
488 let m = hqz_legacy(0.3, 0.3, 0.3);
489
490 let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m).inner;
491
492 let origin = Point { x: 30.0, y: 0.0 };
493 let dir = Vector { x: -1.0, y: 1.0 };
494
495 let a = obj.get_hit(&origin, &dir, &mut rng);
496
497 assert!(a.is_none());
498 }
499
500 #[test]
501 fn miss_line_2() {
505 let mut rng = RandGen::from_entropy();
506
507 let m = hqz_legacy(0.3, 0.3, 0.3);
508
509 let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m).inner;
510
511 let origin = Point { x: 10.0, y: 0.0 };
512 let dir = Vector { x: 1.0, y: 1.0 };
513
514 let a = obj.get_hit(&origin, &dir, &mut rng);
515
516 assert!(a.is_none());
517 }
518
519 #[test]
520 fn miss_curve_1() {
524 let mut rng = RandGen::from_entropy();
525
526 let m = hqz_legacy(0.3, 0.3, 0.3);
527
528 let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
529
530 let origin = Point { x: 30.0, y: 0.0 };
531 let dir = Vector { x: -1.0, y: 1.0 };
532
533 let a = obj.get_hit(&origin, &dir, &mut rng);
534
535 assert!(a.is_none());
536 }
537
538 #[test]
539 fn miss_curve_2() {
543 let mut rng = RandGen::from_entropy();
544
545 let m = hqz_legacy(0.3, 0.3, 0.3);
546
547 let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
548
549 let origin = Point { x: 10.0, y: 0.0 };
550 let dir = Vector { x: 1.0, y: 1.0 };
551
552 let a = obj.get_hit(&origin, &dir, &mut rng);
553
554 assert!(a.is_none());
555 }
556}