use vec3f::Vec3f;
use range::Rangef;
use std::ops::Add;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Segment3f {
pub start: Vec3f,
pub end: Vec3f
}
impl Segment3f {
pub fn new(start: Vec3f, end: Vec3f) -> Segment3f {
Segment3f {
start: start,
end: end
}
}
pub fn to_vec3f(self) -> Vec3f {
self.end - self.start
}
pub fn length(self) -> f32 {
self.start.distance(self.end)
}
pub fn reversed(self) -> Segment3f {
Segment3f {
start: self.end,
end: self.start
}
}
pub fn distance_to_parametric_delta(self, distance: f32) -> f32 {
distance / self.length()
}
pub fn distance_from_parametric_delta(self, delta: f32) -> f32 {
delta * self.length()
}
pub fn point_from_parametric(self, t: f32) -> Vec3f {
self.start.lerp(self.end, t)
}
pub fn segment_from_parametric_range(self, r: Rangef) -> Segment3f {
Segment3f::new(self.point_from_parametric(r.min),
self.point_from_parametric(r.max))
}
pub fn project_segment_as_range(self, other: Segment3f) -> Rangef {
Rangef::from_sorting(self.closest_point_to_point(other.start).0,
self.closest_point_to_point(other.end).0)
}
pub fn point_distance_squared(self, point: Vec3f) -> f32 {
let ab = self.end - self.start;
let ac = point - self.start;
let bc = point - self.end;
let e = ac.dot(ab);
if e <= 0.0 {
ac.dot(ac)
}
else {
let f = ab.dot(ab);
if e >= f {
bc.dot(bc)
}
else {
ac.dot(ac) - e * e / f
}
}
}
pub fn point_distance(self, point: Vec3f) -> f32 {
self.point_distance_squared(point).sqrt()
}
pub fn closest_point_to_point(self, point: Vec3f) -> (f32, Vec3f) {
let a = self.start;
let b = self.end;
let ab = b - a;
let t = (point - a).dot(ab);
if t <= 0.0f32 {
(0.0f32, a)
} else {
let denom = ab.dot(ab);
if t >= denom {
(1.0f32, b)
} else {
let t = t / denom;
(t, a + ab * t)
}
}
}
}
impl Add<Vec3f> for Segment3f {
type Output = Segment3f;
fn add(self, v: Vec3f) -> Self::Output {
Segment3f::new(self.start + v, self.end + v)
}
}
#[cfg(test)]
mod test {
use super::*;
use range::Rangef;
use vec3f::vec3f;
fn make_seg(a: i32, b: i32) -> Segment3f {
Segment3f::new(vec3f(a, 0, 0), vec3f(b, 0, 0))
}
#[test]
fn test_to_vec3f() {
assert_eq!(Segment3f::new(vec3f(0, 0, 0),
vec3f(2, 3, 4)).to_vec3f(),
vec3f(2, 3, 4));
}
#[test]
fn test_segment_length() {
let s = Segment3f::new(vec3f(0, 0, 0),
vec3f(0, 0, 9));
assert_eq!(s.length(), 9.0);
}
#[test]
fn test_segment_reversed() {
let a = vec3f(-1.5, 0, 0);
let b = vec3f(1.0, 0, 0);
assert_eq!(Segment3f::new(a, b).reversed(),
Segment3f::new(b, a));
}
#[test]
fn test_segment_distance_conversion() {
let s = Segment3f::new(vec3f(0, 1, 0),
vec3f(0, 7, 0));
let inputs = [
(0.0f32, 0.0f32),
(6.0, 1.0),
(12.0, 2.0),
(-3.0, -0.5)];
for &(distance, delta) in inputs.iter() {
assert_eq!(s.distance_to_parametric_delta(distance), delta);
assert_eq!(s.distance_from_parametric_delta(delta), distance);
}
}
#[test]
fn test_point_from_parametric() {
let s = Segment3f::new(vec3f(0, 0, -1),
vec3f(0, 0, 3));
assert_eq!(s.point_from_parametric(0.0), vec3f(0, 0, -1));
assert_eq!(s.point_from_parametric(1.0), vec3f(0, 0, 3));
assert_eq!(s.point_from_parametric(0.5), vec3f(0, 0, 1));
}
#[test]
fn test_segment_from_parametric_range() {
let s = make_seg(0, 4);
assert_eq!(s.segment_from_parametric_range(Rangef::new(0.0, 1.0)),
make_seg(0, 4));
assert_eq!(s.segment_from_parametric_range(Rangef::new(0.25, 0.75)),
make_seg(1, 3));
}
#[test]
fn test_segment_closest_point_to_point() {
let s = Segment3f::new(vec3f(2, 0, 0),
vec3f(3, 0, 0));
assert_eq!(s.closest_point_to_point(vec3f(1, 0, 0)),
(0.0, vec3f(2, 0, 0)));
assert_eq!(s.closest_point_to_point(vec3f(4, 0, 0)),
(1.0, vec3f(3, 0, 0)));
assert_eq!(s.closest_point_to_point(vec3f(2.5, 1, 0)),
(0.5, vec3f(2.5, 0, 0)));
}
#[test]
fn test_project_segment_as_range() {
let s = make_seg(0, 4);
assert_eq!(s.project_segment_as_range(make_seg(0, 4)),
Rangef::new(0.0, 1.0));
assert_eq!(s.project_segment_as_range(make_seg(-1, 5)),
Rangef::new(0.0, 1.0));
assert_eq!(s.project_segment_as_range(make_seg(-1, -1)),
Rangef::new(0.0, 0.0));
assert_eq!(s.project_segment_as_range(make_seg(1, 3)),
Rangef::new(0.25, 0.75));
assert_eq!(s.project_segment_as_range(make_seg(3, 1)),
Rangef::new(0.25, 0.75));
}
}