use crate::geom::{Matrix, Point, Vector};
use crate::material::Material;
use crate::sampler::SamplerPoint;
use rand::prelude::*;
pub struct Segment<R: Rng> {
inner: Object<R>,
}
#[derive(Clone)]
pub(crate) enum Object<R: Rng> {
Line {
material: Material<R>,
p0: SamplerPoint<R>,
p1: SamplerPoint<R>,
},
Curve {
material: Material<R>,
p0: SamplerPoint<R>,
p1: SamplerPoint<R>,
p2: SamplerPoint<R>,
},
}
impl<R> Segment<R>
where
R: Rng,
{
pub fn line_from_points<A, B>(start: A, end: B, material: Material<R>) -> Self
where
A: Into<SamplerPoint<R>>,
B: Into<SamplerPoint<R>>,
{
let start = start.into();
let end = end.into();
Self {
inner: Object::Line {
material,
p0: start.into(),
p1: end.into(),
},
}
}
pub fn curve_from_points<A, B, C>(start: A, mid: B, end: C, material: Material<R>) -> Self
where
A: Into<SamplerPoint<R>>,
B: Into<SamplerPoint<R>>,
C: Into<SamplerPoint<R>>,
{
let p0 = start.into();
let p1 = mid.into();
let p2 = end.into();
Self {
inner: Object::Curve {
material,
p0,
p1,
p2,
},
}
}
}
impl<R> Object<R>
where
R: Rng,
{
#[inline(always)]
pub(crate) fn process_material(
&self,
direction: &Vector,
normal: &Vector,
wavelength: f64,
alpha: f64,
rng: &mut R,
) -> Option<Vector> {
let material = match self {
Object::Curve { material, .. } => material,
Object::Line { material, .. } => material,
};
(material)(direction, normal, wavelength, alpha, rng)
}
#[inline(always)]
fn get_line_hit(
s1: Point,
s2: Point,
origin: &Point,
dir: &Vector,
) -> Option<(Point, Vector, f64)> {
let sd = s2 - s1;
let mat_a = Matrix {
a1: sd.x,
b1: -dir.x,
a2: sd.y,
b2: -dir.y,
};
let omega = origin.clone() - s1;
let result = match mat_a.inverse() {
Some(m) => m * omega,
None => {
return None; }
};
if (result.x >= 0.0) && (result.x <= 1.0) && (result.y > 0.0) {
} else {
return None;
};
let alpha = result.x;
let distance = result.y;
let hit = *origin + (*dir * distance);
let norm = Vector { x: -sd.y, y: sd.x };
return Some((hit, norm, alpha));
}
#[inline(always)]
fn get_point_on_bezier(p0: Point, p1: Point, p2: Point, alpha: f64) -> Point {
let beta = 1.0 - alpha;
((p0.v() * beta * beta) + (p1.v() * beta * alpha * 2.0) + (p2.v() * alpha * alpha)).p()
}
#[inline(always)]
fn get_normal_on_bezier(p0: Point, p1: Point, p2: Point, alpha: f64) -> Vector {
let w0 = (p1 - p0) * 2.0;
let w1 = (p2 - p1) * 2.0;
(w0 * (1.0 - alpha) + w1 * alpha).normal()
}
#[inline(always)]
fn process_curve_hit(
p0: Point,
p1: Point,
p2: Point,
origin: &Point,
dir: &Vector,
alpha: f64,
) -> Option<(f64, Point)> {
if 0.0 >= alpha || alpha >= 1.0 {
return None;
}
let hit = Self::get_point_on_bezier(p0, p1, p2, alpha);
let ray_alpha = (hit.x - origin.x) / dir.x;
if ray_alpha < 0.0 {
return None;
} else {
Some((ray_alpha, hit))
}
}
#[inline(always)]
fn get_curve_hit(
p0: Point,
p1: Point,
p2: Point,
origin: &Point,
dir: &Vector,
) -> Option<(Point, Vector, f64)> {
let rotation_matrix = Matrix {
a1: dir.x,
a2: dir.y,
b1: dir.y,
b2: -dir.x,
};
let pa0 = rotation_matrix * (p0 - *origin).p();
let pa1 = rotation_matrix * (p1 - *origin).p();
let pa2 = rotation_matrix * (p2 - *origin).p();
let a = pa0.y;
let b = pa1.y;
let c = pa2.y;
let d = a - 2.0 * b + c;
if d.abs() > 0.0001 {
let m1 = -f64::sqrt(b * b - a * c);
let m2 = b - a;
let v1 = -(m1 + m2) / d;
let v2 = -(-m1 + m2) / d;
let r1 = Self::process_curve_hit(p0, p1, p2, origin, dir, v1);
let r2 = Self::process_curve_hit(p0, p1, p2, origin, dir, v2);
if r1.is_none() && r2.is_none() {
return None;
}
let (d1, hit1) = r1.unwrap_or((f64::MAX, (0.0, 0.0).into()));
let (d2, hit2) = r2.unwrap_or((f64::MAX, (0.0, 0.0).into()));
if d1 < d2 {
let norm = Self::get_normal_on_bezier(p0, p1, p2, v1);
Some((hit1, norm, v1))
} else {
let norm = Self::get_normal_on_bezier(p0, p1, p2, v2);
Some((hit2, norm, v2))
}
} else {
if b != c {
let t = (2.0 * b - c) / (2.0 * b - 2.0 * c);
let (_, hit) = Self::process_curve_hit(p0, p1, p2, origin, dir, t)?;
let norm = Self::get_normal_on_bezier(p0, p1, p2, t);
Some((hit, norm, t))
} else {
None
}
}
}
#[inline(always)]
pub(crate) fn get_hit(
&self,
origin: &Point,
dir: &Vector,
rng: &mut R,
) -> Option<(Point, Vector, f64)> {
match self {
Object::Curve { p0, p1, p2, .. } => {
Self::get_curve_hit(p0.get(rng), p1.get(rng), p2.get(rng), origin, dir)
}
Object::Line { p0, p1, .. } => {
Self::get_line_hit(p0.get(rng), p1.get(rng), origin, dir)
}
}
}
}
impl<R> From<Segment<R>> for Object<R>
where
R: Rng,
{
fn from(value: Segment<R>) -> Self {
value.inner
}
}
#[cfg(test)]
mod tests {
type RandGen = rand_pcg::Pcg64Mcg;
use crate::material::hqz_legacy_default;
use super::Object;
use super::Segment;
use crate::geom::{Point, Vector};
use crate::material::hqz_legacy;
use rand::prelude::*;
#[test]
fn segment_into() {
let s = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), hqz_legacy_default());
let _: Object<RandGen> = s.into();
}
#[test]
fn hit_line_1() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Object::Line {
p0: (0.0, 0.0).into(),
p1: (10.0, 10.0).into(),
material: m,
};
let origin = Point { x: 10.0, y: 0.0 };
let dir = Vector { x: -1.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
let (a, b, _) = a.expect("A was not meant to be `None`");
assert_eq!(a.x, 5.0);
assert_eq!(a.y, 5.0);
assert_eq!(b.x, -10.0);
assert_eq!(b.y, 10.0);
}
#[test]
fn hit_curve_1() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Object::Curve {
p0: (0.0, 0.0).into(),
p1: (5.0, 5.0).into(),
p2: (10.0, 10.0).into(),
material: m,
};
let origin = Point { x: 0.0, y: 5.0 };
let dir = Vector { x: 1.0, y: 0.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
let (a, b, _) = a.expect("A was not meant to be `None`");
assert_eq!(a.x, 5.0);
assert_eq!(a.y, 5.0);
assert_eq!(b.x, -10.0);
assert_eq!(b.y, 10.0);
}
#[test]
fn hit_curve_2() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
let origin = Point { x: 0.0, y: 10.0 };
let dir = Vector { x: 1.0, y: -1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
let (a, b, _) = a.expect("A was not meant to be `None`");
assert_eq!(a.x, 5.0);
assert_eq!(a.y, 5.0);
assert_eq!(b.x, -10.0);
assert_eq!(b.y, 10.0);
}
#[test]
fn hit_curve_horz() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::curve_from_points((0.0, 5.0), (5.0, 5.0), (10.0, 5.0), m).inner;
let origin = Point { x: 5.0, y: 0.0 };
let dir = Vector { x: 0.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
let (a, b, _) = a.expect("A was not meant to be `None`");
assert_eq!(a.x.round(), 5.0);
assert_eq!(a.y.round(), 5.0);
assert_eq!(b.x.round(), 0.0);
assert_eq!(b.y.round(), 10.0);
}
#[test]
fn hit_curve_vert() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::curve_from_points((5.0, 0.0), (5.0, 5.0), (5.0, 10.0), m).inner;
let origin = Point { x: 0.0, y: 5.0 };
let dir = Vector { x: 1.0, y: 0.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
let (a, b, _) = a.expect("A was not meant to be `None`");
assert_eq!(a.x, 5.0);
assert_eq!(a.y, 5.0);
assert_eq!(b.x.round(), -10.0);
assert_eq!(b.y.round(), 0.0);
}
#[test]
fn miss_line_1() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m).inner;
let origin = Point { x: 30.0, y: 0.0 };
let dir = Vector { x: -1.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
assert!(a.is_none());
}
#[test]
fn miss_line_2() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::line_from_points((0.0, 0.0), (10.0, 10.0), m).inner;
let origin = Point { x: 10.0, y: 0.0 };
let dir = Vector { x: 1.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
assert!(a.is_none());
}
#[test]
fn miss_curve_1() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
let origin = Point { x: 30.0, y: 0.0 };
let dir = Vector { x: -1.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
assert!(a.is_none());
}
#[test]
fn miss_curve_2() {
let mut rng = RandGen::from_entropy();
let m = hqz_legacy(0.3, 0.3, 0.3);
let obj = Segment::curve_from_points((0.0, 0.0), (5.0, 5.0), (10.0, 10.0), m).inner;
let origin = Point { x: 10.0, y: 0.0 };
let dir = Vector { x: 1.0, y: 1.0 };
let a = obj.get_hit(&origin, &dir, &mut rng);
assert!(a.is_none());
}
}