Create util module and move Interval struct to it
This commit is contained in:
parent
8c527d34fc
commit
65b5e3c45d
|
|
@ -8,6 +8,7 @@ pub mod mesh;
|
||||||
pub mod raycasting;
|
pub mod raycasting;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,12 @@
|
||||||
use nalgebra::{convert, Point3, RealField};
|
use nalgebra::{Point3, RealField};
|
||||||
|
|
||||||
|
use crate::util::Interval;
|
||||||
|
|
||||||
use super::{IntersectP, Ray};
|
use super::{IntersectP, Ray};
|
||||||
|
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Interval<T: RealField> {
|
|
||||||
min: T,
|
|
||||||
max: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: RealField> Interval<T> {
|
|
||||||
pub fn new(a: T, b: T) -> Self {
|
|
||||||
if a > b {
|
|
||||||
Interval { min: b, max: a }
|
|
||||||
} else {
|
|
||||||
Interval { min: a, max: b }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Interval {
|
|
||||||
min: convert(std::f64::INFINITY),
|
|
||||||
max: convert(std::f64::NEG_INFINITY),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn infinite() -> Self {
|
|
||||||
Interval {
|
|
||||||
min: convert(std::f64::NEG_INFINITY),
|
|
||||||
max: convert(std::f64::INFINITY),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn degenerate(value: T) -> Self {
|
|
||||||
Interval {
|
|
||||||
min: value,
|
|
||||||
max: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_degenerate(self) -> bool {
|
|
||||||
self.min == self.max
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(self) -> bool {
|
|
||||||
self.min > self.max
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_value(&self, value: T) -> bool {
|
|
||||||
value >= self.min && value <= self.max
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intersection(self, b: Self) -> Self {
|
|
||||||
Interval {
|
|
||||||
min: self.min.max(b.min),
|
|
||||||
max: self.max.min(b.max),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union(self, b: Self) -> Self {
|
|
||||||
if self.is_empty() {
|
|
||||||
b
|
|
||||||
} else if b.is_empty() {
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
Interval {
|
|
||||||
min: self.min.min(b.min),
|
|
||||||
max: self.max.max(b.max),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand_to_value(self, v: T) -> Self {
|
|
||||||
if self.is_empty() {
|
|
||||||
Interval::degenerate(v)
|
|
||||||
} else {
|
|
||||||
Interval {
|
|
||||||
min: self.min.min(v),
|
|
||||||
max: self.max.max(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BoundingBox<T: RealField> {
|
pub struct BoundingBox<T: RealField> {
|
||||||
bounds: [Interval<T>; 3],
|
bounds: [Interval<T>; 3],
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +82,8 @@ impl<T: RealField> IntersectP<T> for BoundingBox<T> {
|
||||||
izip!(ray.origin.iter(), ray.direction.iter(), self.bounds.iter())
|
izip!(ray.origin.iter(), ray.direction.iter(), self.bounds.iter())
|
||||||
{
|
{
|
||||||
t_interval_in_bounds = t_interval_in_bounds.intersection(Interval::new(
|
t_interval_in_bounds = t_interval_in_bounds.intersection(Interval::new(
|
||||||
(bounds.min - ray_origin) / ray_direction,
|
(bounds.get_min() - ray_origin) / ray_direction,
|
||||||
(bounds.max - ray_origin) / ray_direction,
|
(bounds.get_max() - ray_origin) / ray_direction,
|
||||||
));
|
));
|
||||||
if t_interval_in_bounds.is_empty() {
|
if t_interval_in_bounds.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -175,250 +97,9 @@ impl<T: RealField> IntersectP<T> for BoundingBox<T> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use itertools::{Itertools, MinMaxResult};
|
|
||||||
|
|
||||||
use quickcheck::TestResult;
|
use quickcheck::TestResult;
|
||||||
use quickcheck_macros::quickcheck;
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
mod interval {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn never_constructed_empty() {
|
|
||||||
let target1 = Interval::new(5f64, 10f64);
|
|
||||||
assert!(!target1.is_empty());
|
|
||||||
let target2 = Interval::new(10f64, 5f64);
|
|
||||||
assert!(!target2.is_empty());
|
|
||||||
let target1 = Interval::new(5f64, -10f64);
|
|
||||||
assert!(!target1.is_empty());
|
|
||||||
let target2 = Interval::new(10f64, -5f64);
|
|
||||||
assert!(!target2.is_empty());
|
|
||||||
let target1 = Interval::new(-5f64, 10f64);
|
|
||||||
assert!(!target1.is_empty());
|
|
||||||
let target2 = Interval::new(-10f64, 5f64);
|
|
||||||
assert!(!target2.is_empty());
|
|
||||||
let target1 = Interval::new(-5f64, -10f64);
|
|
||||||
assert!(!target1.is_empty());
|
|
||||||
let target2 = Interval::new(-10f64, -5f64);
|
|
||||||
assert!(!target2.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_is_empty() {
|
|
||||||
let target: Interval<f64> = Interval::empty();
|
|
||||||
assert!(target.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_when_min_greater_than_max() {
|
|
||||||
let target = Interval {
|
|
||||||
min: 10f64,
|
|
||||||
max: 5f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_when_min_greater_than_max_with_negative_values() {
|
|
||||||
let target = Interval {
|
|
||||||
min: -5f64,
|
|
||||||
max: -10f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_when_min_greater_than_max_with_mixed_signs() {
|
|
||||||
let target = Interval {
|
|
||||||
min: 5f64,
|
|
||||||
max: -10f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_is_not_degenerate() {
|
|
||||||
let target = Interval {
|
|
||||||
min: 10f64,
|
|
||||||
max: 5f64,
|
|
||||||
};
|
|
||||||
assert!(!target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_is_not_degenerate_with_negative_values() {
|
|
||||||
let target = Interval {
|
|
||||||
min: -5f64,
|
|
||||||
max: -10f64,
|
|
||||||
};
|
|
||||||
assert!(!target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_is_not_degenerate_with_mixed_signs() {
|
|
||||||
let target = Interval {
|
|
||||||
min: -5f64,
|
|
||||||
max: 10f64,
|
|
||||||
};
|
|
||||||
assert!(!target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn no_value_is_in_interval_returned_by_emtpy(value: f64) -> bool {
|
|
||||||
!Interval::empty().contains_value(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn identical_min_max_yields_degenerate() {
|
|
||||||
let target = Interval {
|
|
||||||
min: 5f64,
|
|
||||||
max: 5f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn degenerate_is_degenerate() {
|
|
||||||
let target = Interval::degenerate(5f64);
|
|
||||||
assert!(target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn degenerate_is_not_empty() {
|
|
||||||
let target = Interval {
|
|
||||||
min: 5f64,
|
|
||||||
max: 5f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn degenerate_is_degenerate_with_negative_value() {
|
|
||||||
let target = Interval {
|
|
||||||
min: -5f64,
|
|
||||||
max: -5f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn degenerate_is_not_empty_with_negative_value() {
|
|
||||||
let target = Interval {
|
|
||||||
min: -5f64,
|
|
||||||
max: -5f64,
|
|
||||||
};
|
|
||||||
assert!(target.is_degenerate());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn degenerate_contains_expected_value() {
|
|
||||||
let target = Interval::degenerate(5f64);
|
|
||||||
assert!(target.contains_value(5.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn degenerate_does_not_contain_any_values_othter_than_expected_value(value: f64) -> bool {
|
|
||||||
let target_value = if value == 5f64 { 5.5 } else { 5f64 };
|
|
||||||
let target = Interval::degenerate(target_value);
|
|
||||||
!target.contains_value(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn intersection_with_infinite_is_self() {
|
|
||||||
let target = Interval::new(5f32, 10f32);
|
|
||||||
let result = target.intersection(Interval::infinite());
|
|
||||||
assert!(target.min == result.min);
|
|
||||||
assert!(target.max == result.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn union_with_self_yields_self(a: f64, b: f64) -> bool {
|
|
||||||
let target = Interval::new(a, b);
|
|
||||||
let result = target.union(target);
|
|
||||||
result.min == target.min && result.max == target.max
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
fn union_yields_min_and_max(a: f64, b: f64, c: f64, d: f64) -> bool {
|
|
||||||
let values = vec![a, b, c, d];
|
|
||||||
if let MinMaxResult::MinMax(&min, &max) =
|
|
||||||
values.iter().minmax_by(|a, b| a.partial_cmp(b).unwrap())
|
|
||||||
{
|
|
||||||
let target1 = Interval::new(a, b);
|
|
||||||
let target2 = Interval::new(c, d);
|
|
||||||
let result = target1.union(target2);
|
|
||||||
result.min == min && result.max == max
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn union_with_empty_interval_is_correct() {
|
|
||||||
let empty = Interval {
|
|
||||||
min: 1f64,
|
|
||||||
max: -1f64,
|
|
||||||
};
|
|
||||||
let not_empty = Interval {
|
|
||||||
min: 5f64,
|
|
||||||
max: 10f64,
|
|
||||||
};
|
|
||||||
let union1 = not_empty.union(empty);
|
|
||||||
assert!(union1.min == 5.0);
|
|
||||||
assert!(union1.max == 10.0);
|
|
||||||
let union2 = empty.union(not_empty);
|
|
||||||
assert!(union2.min == 5.0);
|
|
||||||
assert!(union2.max == 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn union_with_empty_interval_is_correct_when_empty_interval_produced_by_intersection() {
|
|
||||||
let empty = Interval {
|
|
||||||
min: 1f64,
|
|
||||||
max: -1f64,
|
|
||||||
};
|
|
||||||
let not_empty = Interval {
|
|
||||||
min: 5f64,
|
|
||||||
max: 10f64,
|
|
||||||
};
|
|
||||||
let union1 = not_empty.union(empty);
|
|
||||||
assert!(union1.min == 5.0);
|
|
||||||
assert!(union1.max == 10.0);
|
|
||||||
let union2 = empty.union(not_empty);
|
|
||||||
assert!(union2.min == 5.0);
|
|
||||||
assert!(union2.max == 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
pub fn expand_to_value_creates_interval_that_includes_value(
|
|
||||||
min: f64,
|
|
||||||
max: f64,
|
|
||||||
value: f64,
|
|
||||||
) -> bool {
|
|
||||||
// Don't check if min <= max, we want to test empty intervals too
|
|
||||||
let interval1 = Interval { min, max };
|
|
||||||
let interval2 = interval1.expand_to_value(value);
|
|
||||||
interval2.contains_value(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[quickcheck]
|
|
||||||
pub fn expand_to_value_creates_interval_that_includes_original_interval(
|
|
||||||
b: f64,
|
|
||||||
a: f64,
|
|
||||||
value: f64,
|
|
||||||
) -> bool {
|
|
||||||
let interval1 = Interval::new(a, b);
|
|
||||||
let interval2 = interval1.expand_to_value(value);
|
|
||||||
let interval3 = interval2.intersection(interval1);
|
|
||||||
// If interval2 contains interval1, that the intersection of the two will
|
|
||||||
// be equal to interval1
|
|
||||||
interval1.min == interval3.min && interval1.max == interval3.max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod bounding_box {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -454,15 +135,15 @@ mod tests {
|
||||||
assert!(target
|
assert!(target
|
||||||
.bounds
|
.bounds
|
||||||
.iter()
|
.iter()
|
||||||
.all(|bounds| bounds.min == 0.0 && bounds.max == 1.0));
|
.all(|bounds| bounds.get_min() == 0.0 && bounds.get_max() == 1.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
|
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
|
||||||
let distance_from_start = (value - interval.min).abs();
|
let distance_from_start = (value - interval.get_min()).abs();
|
||||||
let range = interval.max - interval.min;
|
let range = interval.get_max() - interval.get_min();
|
||||||
let multiple_of_range = distance_from_start / range;
|
let multiple_of_range = distance_from_start / range;
|
||||||
return interval.min + multiple_of_range.fract() * range;
|
return interval.get_min() + multiple_of_range.fract() * range;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
|
|
@ -471,10 +152,7 @@ mod tests {
|
||||||
interval.contains_value(wrap_value_in_interval(v, interval))
|
interval.contains_value(wrap_value_in_interval(v, interval))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_point_into_bounding_box(
|
fn wrap_point_into_bounding_box(point: Point3<f64>, bounds: &BoundingBox<f64>) -> Point3<f64> {
|
||||||
point: Point3<f64>,
|
|
||||||
bounds: &BoundingBox<f64>,
|
|
||||||
) -> Point3<f64> {
|
|
||||||
Point3::from(Vector3::from_iterator(
|
Point3::from(Vector3::from_iterator(
|
||||||
point
|
point
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -527,10 +205,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn intersection_detected_when_ray_parallel_to_axis() {
|
fn intersection_detected_when_ray_parallel_to_axis() {
|
||||||
let target = BoundingBox::from_corners(
|
let target =
|
||||||
Point3::new(1.0f64, 2.0, 3.0),
|
BoundingBox::from_corners(Point3::new(1.0f64, 2.0, 3.0), Point3::new(4.0, 5.0, 6.0));
|
||||||
Point3::new(4.0, 5.0, 6.0),
|
|
||||||
);
|
|
||||||
let x_ray = Ray::new(Point3::new(0.0, 3.0, 4.0), Vector3::new(1.0, 0.0, 0.0));
|
let x_ray = Ray::new(Point3::new(0.0, 3.0, 4.0), Vector3::new(1.0, 0.0, 0.0));
|
||||||
assert!(target.intersect(&x_ray));
|
assert!(target.intersect(&x_ray));
|
||||||
let y_ray = Ray::new(Point3::new(2.0, 0.0, 4.0), Vector3::new(0.0, 1.0, 0.0));
|
let y_ray = Ray::new(Point3::new(2.0, 0.0, 4.0), Vector3::new(0.0, 1.0, 0.0));
|
||||||
|
|
@ -541,10 +217,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn intersection_missed_when_ray_parallel_to_axis() {
|
fn intersection_missed_when_ray_parallel_to_axis() {
|
||||||
let target = BoundingBox::from_corners(
|
let target =
|
||||||
Point3::new(1.0f64, 2.0, 3.0),
|
BoundingBox::from_corners(Point3::new(1.0f64, 2.0, 3.0), Point3::new(4.0, 5.0, 6.0));
|
||||||
Point3::new(4.0, 5.0, 6.0),
|
|
||||||
);
|
|
||||||
let x_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0));
|
let x_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0));
|
||||||
assert!(!target.intersect(&x_ray));
|
assert!(!target.intersect(&x_ray));
|
||||||
let y_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
|
let y_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
|
||||||
|
|
@ -561,7 +235,7 @@ mod tests {
|
||||||
.bounds
|
.bounds
|
||||||
.iter()
|
.iter()
|
||||||
.zip(result.bounds.iter())
|
.zip(result.bounds.iter())
|
||||||
.all(|(a, b)| a.min == b.min && a.max == b.max)
|
.all(|(a, b)| a.get_min() == b.get_min() && a.get_max() == b.get_max())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
|
|
@ -580,7 +254,10 @@ mod tests {
|
||||||
target2.bounds.iter()
|
target2.bounds.iter()
|
||||||
)
|
)
|
||||||
.all(|(r, t1, t2)| {
|
.all(|(r, t1, t2)| {
|
||||||
r.min <= t1.min && r.min <= t2.min && r.max >= t1.max && r.max >= t2.max
|
r.get_min() <= t1.get_min()
|
||||||
|
&& r.get_min() <= t2.get_min()
|
||||||
|
&& r.get_max() >= t1.get_max()
|
||||||
|
&& r.get_max() >= t2.get_max()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -609,5 +286,4 @@ mod tests {
|
||||||
&& points.iter().any(|elem| elem.z <= p.z);
|
&& points.iter().any(|elem| elem.z <= p.z);
|
||||||
target.contains_point(p) == is_in_bounds
|
target.contains_point(p) == is_in_bounds
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,330 @@
|
||||||
|
use nalgebra::{convert, RealField};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Interval<T: RealField> {
|
||||||
|
min: T,
|
||||||
|
max: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: RealField> Interval<T> {
|
||||||
|
pub fn new(a: T, b: T) -> Self {
|
||||||
|
if a > b {
|
||||||
|
Interval { min: b, max: a }
|
||||||
|
} else {
|
||||||
|
Interval { min: a, max: b }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Interval {
|
||||||
|
min: convert(std::f64::INFINITY),
|
||||||
|
max: convert(std::f64::NEG_INFINITY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn infinite() -> Self {
|
||||||
|
Interval {
|
||||||
|
min: convert(std::f64::NEG_INFINITY),
|
||||||
|
max: convert(std::f64::INFINITY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn degenerate(value: T) -> Self {
|
||||||
|
Interval {
|
||||||
|
min: value,
|
||||||
|
max: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_min(&self) -> T {
|
||||||
|
return self.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_max(&self) -> T {
|
||||||
|
return self.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_degenerate(self) -> bool {
|
||||||
|
self.min == self.max
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(self) -> bool {
|
||||||
|
self.min > self.max
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_value(&self, value: T) -> bool {
|
||||||
|
value >= self.min && value <= self.max
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersection(self, b: Self) -> Self {
|
||||||
|
Interval {
|
||||||
|
min: self.min.max(b.min),
|
||||||
|
max: self.max.min(b.max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(self, b: Self) -> Self {
|
||||||
|
if self.is_empty() {
|
||||||
|
b
|
||||||
|
} else if b.is_empty() {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
Interval {
|
||||||
|
min: self.min.min(b.min),
|
||||||
|
max: self.max.max(b.max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_to_value(self, v: T) -> Self {
|
||||||
|
if self.is_empty() {
|
||||||
|
Interval::degenerate(v)
|
||||||
|
} else {
|
||||||
|
Interval {
|
||||||
|
min: self.min.min(v),
|
||||||
|
max: self.max.max(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use itertools::{Itertools, MinMaxResult};
|
||||||
|
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn never_constructed_empty() {
|
||||||
|
let target1 = Interval::new(5f64, 10f64);
|
||||||
|
assert!(!target1.is_empty());
|
||||||
|
let target2 = Interval::new(10f64, 5f64);
|
||||||
|
assert!(!target2.is_empty());
|
||||||
|
let target1 = Interval::new(5f64, -10f64);
|
||||||
|
assert!(!target1.is_empty());
|
||||||
|
let target2 = Interval::new(10f64, -5f64);
|
||||||
|
assert!(!target2.is_empty());
|
||||||
|
let target1 = Interval::new(-5f64, 10f64);
|
||||||
|
assert!(!target1.is_empty());
|
||||||
|
let target2 = Interval::new(-10f64, 5f64);
|
||||||
|
assert!(!target2.is_empty());
|
||||||
|
let target1 = Interval::new(-5f64, -10f64);
|
||||||
|
assert!(!target1.is_empty());
|
||||||
|
let target2 = Interval::new(-10f64, -5f64);
|
||||||
|
assert!(!target2.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_empty() {
|
||||||
|
let target: Interval<f64> = Interval::empty();
|
||||||
|
assert!(target.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_when_min_greater_than_max() {
|
||||||
|
let target = Interval {
|
||||||
|
min: 10f64,
|
||||||
|
max: 5f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_when_min_greater_than_max_with_negative_values() {
|
||||||
|
let target = Interval {
|
||||||
|
min: -5f64,
|
||||||
|
max: -10f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_when_min_greater_than_max_with_mixed_signs() {
|
||||||
|
let target = Interval {
|
||||||
|
min: 5f64,
|
||||||
|
max: -10f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_not_degenerate() {
|
||||||
|
let target = Interval {
|
||||||
|
min: 10f64,
|
||||||
|
max: 5f64,
|
||||||
|
};
|
||||||
|
assert!(!target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_not_degenerate_with_negative_values() {
|
||||||
|
let target = Interval {
|
||||||
|
min: -5f64,
|
||||||
|
max: -10f64,
|
||||||
|
};
|
||||||
|
assert!(!target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_not_degenerate_with_mixed_signs() {
|
||||||
|
let target = Interval {
|
||||||
|
min: -5f64,
|
||||||
|
max: 10f64,
|
||||||
|
};
|
||||||
|
assert!(!target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn no_value_is_in_interval_returned_by_emtpy(value: f64) -> bool {
|
||||||
|
!Interval::empty().contains_value(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identical_min_max_yields_degenerate() {
|
||||||
|
let target = Interval {
|
||||||
|
min: 5f64,
|
||||||
|
max: 5f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_is_degenerate() {
|
||||||
|
let target = Interval::degenerate(5f64);
|
||||||
|
assert!(target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_is_not_empty() {
|
||||||
|
let target = Interval {
|
||||||
|
min: 5f64,
|
||||||
|
max: 5f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_is_degenerate_with_negative_value() {
|
||||||
|
let target = Interval {
|
||||||
|
min: -5f64,
|
||||||
|
max: -5f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_is_not_empty_with_negative_value() {
|
||||||
|
let target = Interval {
|
||||||
|
min: -5f64,
|
||||||
|
max: -5f64,
|
||||||
|
};
|
||||||
|
assert!(target.is_degenerate());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_contains_expected_value() {
|
||||||
|
let target = Interval::degenerate(5f64);
|
||||||
|
assert!(target.contains_value(5.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn degenerate_does_not_contain_any_values_othter_than_expected_value(value: f64) -> bool {
|
||||||
|
let target_value = if value == 5f64 { 5.5 } else { 5f64 };
|
||||||
|
let target = Interval::degenerate(target_value);
|
||||||
|
!target.contains_value(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersection_with_infinite_is_self() {
|
||||||
|
let target = Interval::new(5f32, 10f32);
|
||||||
|
let result = target.intersection(Interval::infinite());
|
||||||
|
assert!(target.min == result.min);
|
||||||
|
assert!(target.max == result.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn union_with_self_yields_self(a: f64, b: f64) -> bool {
|
||||||
|
let target = Interval::new(a, b);
|
||||||
|
let result = target.union(target);
|
||||||
|
result.min == target.min && result.max == target.max
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn union_yields_min_and_max(a: f64, b: f64, c: f64, d: f64) -> bool {
|
||||||
|
let values = vec![a, b, c, d];
|
||||||
|
if let MinMaxResult::MinMax(&min, &max) =
|
||||||
|
values.iter().minmax_by(|a, b| a.partial_cmp(b).unwrap())
|
||||||
|
{
|
||||||
|
let target1 = Interval::new(a, b);
|
||||||
|
let target2 = Interval::new(c, d);
|
||||||
|
let result = target1.union(target2);
|
||||||
|
result.min == min && result.max == max
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn union_with_empty_interval_is_correct() {
|
||||||
|
let empty = Interval {
|
||||||
|
min: 1f64,
|
||||||
|
max: -1f64,
|
||||||
|
};
|
||||||
|
let not_empty = Interval {
|
||||||
|
min: 5f64,
|
||||||
|
max: 10f64,
|
||||||
|
};
|
||||||
|
let union1 = not_empty.union(empty);
|
||||||
|
assert!(union1.min == 5.0);
|
||||||
|
assert!(union1.max == 10.0);
|
||||||
|
let union2 = empty.union(not_empty);
|
||||||
|
assert!(union2.min == 5.0);
|
||||||
|
assert!(union2.max == 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn union_with_empty_interval_is_correct_when_empty_interval_produced_by_intersection() {
|
||||||
|
let empty = Interval {
|
||||||
|
min: 1f64,
|
||||||
|
max: -1f64,
|
||||||
|
};
|
||||||
|
let not_empty = Interval {
|
||||||
|
min: 5f64,
|
||||||
|
max: 10f64,
|
||||||
|
};
|
||||||
|
let union1 = not_empty.union(empty);
|
||||||
|
assert!(union1.min == 5.0);
|
||||||
|
assert!(union1.max == 10.0);
|
||||||
|
let union2 = empty.union(not_empty);
|
||||||
|
assert!(union2.min == 5.0);
|
||||||
|
assert!(union2.max == 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
pub fn expand_to_value_creates_interval_that_includes_value(
|
||||||
|
min: f64,
|
||||||
|
max: f64,
|
||||||
|
value: f64,
|
||||||
|
) -> bool {
|
||||||
|
// Don't check if min <= max, we want to test empty intervals too
|
||||||
|
let interval1 = Interval { min, max };
|
||||||
|
let interval2 = interval1.expand_to_value(value);
|
||||||
|
interval2.contains_value(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
pub fn expand_to_value_creates_interval_that_includes_original_interval(
|
||||||
|
b: f64,
|
||||||
|
a: f64,
|
||||||
|
value: f64,
|
||||||
|
) -> bool {
|
||||||
|
let interval1 = Interval::new(a, b);
|
||||||
|
let interval2 = interval1.expand_to_value(value);
|
||||||
|
let interval3 = interval2.intersection(interval1);
|
||||||
|
// If interval2 contains interval1, that the intersection of the two will
|
||||||
|
// be equal to interval1
|
||||||
|
interval1.min == interval3.min && interval1.max == interval3.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod interval;
|
||||||
|
|
||||||
|
pub use interval::Interval;
|
||||||
Loading…
Reference in New Issue