Create util module and move Interval struct to it

This commit is contained in:
Matthew Gordon 2020-02-06 16:49:11 -05:00
parent 8c527d34fc
commit 65b5e3c45d
4 changed files with 516 additions and 506 deletions

View File

@ -8,6 +8,7 @@ pub mod mesh;
pub mod raycasting;
pub mod sampler;
pub mod scene;
pub mod util;
#[cfg(bench)]
mod tests {

View File

@ -1,90 +1,12 @@
use nalgebra::{convert, Point3, RealField};
use nalgebra::{Point3, RealField};
use crate::util::Interval;
use super::{IntersectP, Ray};
use itertools::izip;
#[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> {
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())
{
t_interval_in_bounds = t_interval_in_bounds.intersection(Interval::new(
(bounds.min - ray_origin) / ray_direction,
(bounds.max - ray_origin) / ray_direction,
(bounds.get_min() - ray_origin) / ray_direction,
(bounds.get_max() - ray_origin) / ray_direction,
));
if t_interval_in_bounds.is_empty() {
return false;
@ -175,439 +97,193 @@ impl<T: RealField> IntersectP<T> for BoundingBox<T> {
mod tests {
use super::*;
use itertools::{Itertools, MinMaxResult};
use quickcheck::TestResult;
use quickcheck_macros::quickcheck;
mod interval {
use super::*;
use nalgebra::Vector3;
#[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
}
#[test]
fn from_corners_with_same_point_yields_degenerate_intervals() {
let test_point = Point3::new(0f64, 1f64, 2f64);
let target = BoundingBox::from_corners(test_point, test_point);
assert!(target.bounds.iter().all(|e| e.is_degenerate()));
}
mod bounding_box {
use super::*;
#[test]
fn from_corners_yields_same_result_with_any_oposite_corners() {
let corner_000 = Point3::new(0.0, 0.0, 0.0);
let corner_001 = Point3::new(0.0, 0.0, 1.0);
let corner_010 = Point3::new(0.0, 1.0, 0.0);
let corner_011 = Point3::new(0.0, 1.0, 1.0);
let corner_100 = Point3::new(1.0, 0.0, 0.0);
let corner_101 = Point3::new(1.0, 0.0, 1.0);
let corner_110 = Point3::new(1.0, 1.0, 0.0);
let corner_111 = Point3::new(1.0, 1.0, 1.0);
use nalgebra::Vector3;
#[test]
fn from_corners_with_same_point_yields_degenerate_intervals() {
let test_point = Point3::new(0f64, 1f64, 2f64);
let target = BoundingBox::from_corners(test_point, test_point);
assert!(target.bounds.iter().all(|e| e.is_degenerate()));
}
#[test]
fn from_corners_yields_same_result_with_any_oposite_corners() {
let corner_000 = Point3::new(0.0, 0.0, 0.0);
let corner_001 = Point3::new(0.0, 0.0, 1.0);
let corner_010 = Point3::new(0.0, 1.0, 0.0);
let corner_011 = Point3::new(0.0, 1.0, 1.0);
let corner_100 = Point3::new(1.0, 0.0, 0.0);
let corner_101 = Point3::new(1.0, 0.0, 1.0);
let corner_110 = Point3::new(1.0, 1.0, 0.0);
let corner_111 = Point3::new(1.0, 1.0, 1.0);
let test_inputs: Vec<(Point3<f64>, Point3<f64>)> = vec![
(corner_000, corner_111),
(corner_001, corner_110),
(corner_010, corner_101),
(corner_011, corner_100),
(corner_100, corner_011),
(corner_101, corner_010),
(corner_110, corner_001),
(corner_111, corner_000),
];
for (a, b) in test_inputs {
let target = BoundingBox::from_corners(a, b);
assert!(target
.bounds
.iter()
.all(|bounds| bounds.min == 0.0 && bounds.max == 1.0));
}
}
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
let distance_from_start = (value - interval.min).abs();
let range = interval.max - interval.min;
let multiple_of_range = distance_from_start / range;
return interval.min + multiple_of_range.fract() * range;
}
#[quickcheck]
fn wrap_value_in_interval_produces_values_in_interval(v: f64, a: f64, b: f64) -> bool {
let interval = Interval::new(a, b);
interval.contains_value(wrap_value_in_interval(v, interval))
}
fn wrap_point_into_bounding_box(
point: Point3<f64>,
bounds: &BoundingBox<f64>,
) -> Point3<f64> {
Point3::from(Vector3::from_iterator(
point
.iter()
.zip(bounds.bounds.iter())
.map(|(&value, &interval)| wrap_value_in_interval(value, interval)),
))
}
#[quickcheck]
fn correctly_detects_intersections(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> bool {
let bounds = BoundingBox::from_corners(corner1, corner2);
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
let ray = Ray::new(ray_origin, point_in_bounds - ray_origin);
bounds.intersect(&ray)
}
#[quickcheck]
fn intersect_always_true_when_ray_origin_is_inside_bounds(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> TestResult {
let bounds = BoundingBox::from_corners(corner1, corner2);
let ray_origin = wrap_point_into_bounding_box(ray_origin, &bounds);
let ray = Ray::new(ray_origin, ray_origin - random_point);
TestResult::from_bool(bounds.intersect(&ray))
}
#[quickcheck]
fn no_intersection_when_behind_ray(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> TestResult {
let bounds = BoundingBox::from_corners(corner1, corner2);
if bounds.contains_point(ray_origin) {
return TestResult::discard();
}
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
let ray = Ray::new(ray_origin, ray_origin - point_in_bounds);
TestResult::from_bool(bounds.intersect(&ray))
}
#[test]
fn intersection_detected_when_ray_parallel_to_axis() {
let target = BoundingBox::from_corners(
Point3::new(1.0f64, 2.0, 3.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));
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));
assert!(target.intersect(&y_ray));
let z_ray = Ray::new(Point3::new(2.0, 3.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
assert!(target.intersect(&z_ray));
}
#[test]
fn intersection_missed_when_ray_parallel_to_axis() {
let target = BoundingBox::from_corners(
Point3::new(1.0f64, 2.0, 3.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));
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));
assert!(!target.intersect(&y_ray));
let z_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
assert!(!target.intersect(&z_ray));
}
#[quickcheck]
fn union_with_self_yields_self(a: Point3<f64>, b: Point3<f64>) -> bool {
let test_inputs: Vec<(Point3<f64>, Point3<f64>)> = vec![
(corner_000, corner_111),
(corner_001, corner_110),
(corner_010, corner_101),
(corner_011, corner_100),
(corner_100, corner_011),
(corner_101, corner_010),
(corner_110, corner_001),
(corner_111, corner_000),
];
for (a, b) in test_inputs {
let target = BoundingBox::from_corners(a, b);
let result = target.union(&target);
target
assert!(target
.bounds
.iter()
.zip(result.bounds.iter())
.all(|(a, b)| a.min == b.min && a.max == b.max)
}
#[quickcheck]
fn union_yields_full_ranges(
a: Point3<f64>,
b: Point3<f64>,
c: Point3<f64>,
d: Point3<f64>,
) -> bool {
let target1 = BoundingBox::from_corners(a, b);
let target2 = BoundingBox::from_corners(c, d);
let result = target1.union(&target2);
izip!(
result.bounds.iter(),
target1.bounds.iter(),
target2.bounds.iter()
)
.all(|(r, t1, t2)| {
r.min <= t1.min && r.min <= t2.min && r.max >= t1.max && r.max >= t2.max
})
}
#[quickcheck]
fn empty_box_contains_no_points(p: Point3<f64>) -> bool {
let target = BoundingBox::empty();
!target.contains_point(p)
}
#[quickcheck]
fn from_points_produces_box_that_contains_only_points_bounded_by_inputs_on_all_axes(
p: Point3<f64>,
a: Point3<f64>,
b: Point3<f64>,
c: Point3<f64>,
d: Point3<f64>,
e: Point3<f64>,
) -> bool {
let points = vec![a, b, c, d, e];
let target = BoundingBox::from_points(&points);
let is_in_bounds = points.iter().any(|elem| elem.x >= p.x)
&& points.iter().any(|elem| elem.x <= p.x)
&& points.iter().any(|elem| elem.y >= p.y)
&& points.iter().any(|elem| elem.y <= p.y)
&& points.iter().any(|elem| elem.z >= p.z)
&& points.iter().any(|elem| elem.z <= p.z);
target.contains_point(p) == is_in_bounds
.all(|bounds| bounds.get_min() == 0.0 && bounds.get_max() == 1.0));
}
}
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
let distance_from_start = (value - interval.get_min()).abs();
let range = interval.get_max() - interval.get_min();
let multiple_of_range = distance_from_start / range;
return interval.get_min() + multiple_of_range.fract() * range;
}
#[quickcheck]
fn wrap_value_in_interval_produces_values_in_interval(v: f64, a: f64, b: f64) -> bool {
let interval = Interval::new(a, b);
interval.contains_value(wrap_value_in_interval(v, interval))
}
fn wrap_point_into_bounding_box(point: Point3<f64>, bounds: &BoundingBox<f64>) -> Point3<f64> {
Point3::from(Vector3::from_iterator(
point
.iter()
.zip(bounds.bounds.iter())
.map(|(&value, &interval)| wrap_value_in_interval(value, interval)),
))
}
#[quickcheck]
fn correctly_detects_intersections(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> bool {
let bounds = BoundingBox::from_corners(corner1, corner2);
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
let ray = Ray::new(ray_origin, point_in_bounds - ray_origin);
bounds.intersect(&ray)
}
#[quickcheck]
fn intersect_always_true_when_ray_origin_is_inside_bounds(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> TestResult {
let bounds = BoundingBox::from_corners(corner1, corner2);
let ray_origin = wrap_point_into_bounding_box(ray_origin, &bounds);
let ray = Ray::new(ray_origin, ray_origin - random_point);
TestResult::from_bool(bounds.intersect(&ray))
}
#[quickcheck]
fn no_intersection_when_behind_ray(
ray_origin: Point3<f64>,
corner1: Point3<f64>,
corner2: Point3<f64>,
random_point: Point3<f64>,
) -> TestResult {
let bounds = BoundingBox::from_corners(corner1, corner2);
if bounds.contains_point(ray_origin) {
return TestResult::discard();
}
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
let ray = Ray::new(ray_origin, ray_origin - point_in_bounds);
TestResult::from_bool(bounds.intersect(&ray))
}
#[test]
fn intersection_detected_when_ray_parallel_to_axis() {
let target =
BoundingBox::from_corners(Point3::new(1.0f64, 2.0, 3.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));
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));
assert!(target.intersect(&y_ray));
let z_ray = Ray::new(Point3::new(2.0, 3.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
assert!(target.intersect(&z_ray));
}
#[test]
fn intersection_missed_when_ray_parallel_to_axis() {
let target =
BoundingBox::from_corners(Point3::new(1.0f64, 2.0, 3.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));
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));
assert!(!target.intersect(&y_ray));
let z_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
assert!(!target.intersect(&z_ray));
}
#[quickcheck]
fn union_with_self_yields_self(a: Point3<f64>, b: Point3<f64>) -> bool {
let target = BoundingBox::from_corners(a, b);
let result = target.union(&target);
target
.bounds
.iter()
.zip(result.bounds.iter())
.all(|(a, b)| a.get_min() == b.get_min() && a.get_max() == b.get_max())
}
#[quickcheck]
fn union_yields_full_ranges(
a: Point3<f64>,
b: Point3<f64>,
c: Point3<f64>,
d: Point3<f64>,
) -> bool {
let target1 = BoundingBox::from_corners(a, b);
let target2 = BoundingBox::from_corners(c, d);
let result = target1.union(&target2);
izip!(
result.bounds.iter(),
target1.bounds.iter(),
target2.bounds.iter()
)
.all(|(r, t1, t2)| {
r.get_min() <= t1.get_min()
&& r.get_min() <= t2.get_min()
&& r.get_max() >= t1.get_max()
&& r.get_max() >= t2.get_max()
})
}
#[quickcheck]
fn empty_box_contains_no_points(p: Point3<f64>) -> bool {
let target = BoundingBox::empty();
!target.contains_point(p)
}
#[quickcheck]
fn from_points_produces_box_that_contains_only_points_bounded_by_inputs_on_all_axes(
p: Point3<f64>,
a: Point3<f64>,
b: Point3<f64>,
c: Point3<f64>,
d: Point3<f64>,
e: Point3<f64>,
) -> bool {
let points = vec![a, b, c, d, e];
let target = BoundingBox::from_points(&points);
let is_in_bounds = points.iter().any(|elem| elem.x >= p.x)
&& points.iter().any(|elem| elem.x <= p.x)
&& points.iter().any(|elem| elem.y >= p.y)
&& points.iter().any(|elem| elem.y <= p.y)
&& points.iter().any(|elem| elem.z >= p.z)
&& points.iter().any(|elem| elem.z <= p.z);
target.contains_point(p) == is_in_bounds
}
}

330
src/util/interval.rs Normal file
View File

@ -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
}
}

3
src/util/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod interval;
pub use interval::Interval;