use euclid::{
approxeq::ApproxEq,
default::{Rect, Size2D, Transform3D},
point2, point3, vec3, Angle,
};
use plane_split::{Intersection, Line, LineProjection, NegativeHemisphereError, Plane, Polygon};
#[ test]
fn line_proj_bounds() {
assert_eq!(
(-5 .0 f64, 4 .0 ),
LineProjection {
markers: [-5 .0 f64, 1 .0 , 4 .0 , 2 .0 ]
}
.get_bounds()
);
assert_eq!(
(1 f64, 4 .0 ),
LineProjection {
markers: [4 f64, 3 .0 , 2 .0 , 1 .0 ]
}
.get_bounds()
);
}
#[ test]
fn valid() {
let poly_a: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 0 .0 , 0 .0 ),
point3(1 .0 , 1 .0 , 1 .0 ),
point3(1 .0 , 1 .0 , 0 .0 ),
point3(0 .0 , 1 .0 , 1 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 1 .0 , 0 .0 ),
offset: -1 .0 ,
},
anchor: 0 ,
};
assert!(!poly_a.is_valid()); // points[0] is outside
let poly_b: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 1 .0 , 0 .0 ),
point3(1 .0 , 1 .0 , 1 .0 ),
point3(1 .0 , 1 .0 , 0 .0 ),
point3(0 .0 , 1 .0 , 1 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 1 .0 , 0 .0 ),
offset: -1 .0 ,
},
anchor: 0 ,
};
assert!(!poly_b.is_valid()); // winding is incorrect
let poly_c: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 0 .0 , 1 .0 ),
point3(1 .0 , 0 .0 , 1 .0 ),
point3(1 .0 , 1 .0 , 1 .0 ),
point3(0 .0 , 1 .0 , 1 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 0 .0 , 1 .0 ),
offset: -1 .0 ,
},
anchor: 0 ,
};
assert!(poly_c.is_valid());
}
#[ test]
fn empty() {
let poly = Polygon::from_points(
[
point3(0 .0 , 0 .0 , 1 .0 ),
point3(0 .0 , 0 .0 , 1 .0 ),
point3(0 .0 , 0 .00000001 , 1 .0 ),
point3(1 .0 , 0 .0 , 0 .0 ),
],
1 usize,
);
assert_eq!(None, poly);
}
fn test_transformed(rect: Rect<f64>, transform: Transform3D<f64>) {
let poly = Polygon::from_transformed_rect(rect, transform, 0 ).unwrap();
assert!(poly.is_valid());
let inv_transform = transform.inverse().unwrap();
let poly2 =
Polygon::from_transformed_rect_with_inverse(rect, &transform, &inv_transform, 0 ).unwrap();
assert_eq!(poly.points, poly2.points);
assert!(poly.plane.offset.approx_eq(&poly2.plane.offset));
assert!(poly.plane.normal.dot(poly2.plane.normal).approx_eq(&1 .0 ));
}
#[ test]
fn from_transformed_rect() {
let rect = Rect::new(point2(10 .0 , 10 .0 ), Size2D::new(20 .0 , 30 .0 ));
let transform = Transform3D::rotation(0 .5 f64.sqrt(), 0 .0 , 0 .5 f64.sqrt(), Angle::radians(5 .0 ))
.pre_translate(vec3(0 .0 , 0 .0 , 10 .0 ));
test_transformed(rect, transform);
}
#[ test]
fn from_transformed_rect_perspective() {
let rect = Rect::new(point2(-10 .0 , -5 .0 ), Size2D::new(20 .0 , 30 .0 ));
let mut transform = Transform3D::perspective(400 .0 ).pre_translate(vec3(0 .0 , 0 .0 , 100 .0 ));
transform.m44 = 0 .7 ; //for fun
test_transformed(rect, transform);
}
#[ test]
fn untransform_point() {
let poly: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 0 .0 , 0 .0 ),
point3(0 .5 , 1 .0 , 0 .0 ),
point3(1 .5 , 1 .0 , 0 .0 ),
point3(1 .0 , 0 .0 , 0 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 1 .0 , 0 .0 ),
offset: 0 .0 ,
},
anchor: 0 ,
};
assert_eq!(poly.untransform_point(poly.points[0 ]), point2(0 .0 , 0 .0 ));
assert_eq!(poly.untransform_point(poly.points[1 ]), point2(1 .0 , 0 .0 ));
assert_eq!(poly.untransform_point(poly.points[2 ]), point2(1 .0 , 1 .0 ));
assert_eq!(poly.untransform_point(poly.points[3 ]), point2(0 .0 , 1 .0 ));
}
#[ test]
fn are_outside() {
let plane = Plane {
normal: vec3(0 .0 , 0 .0 , 1 .0 ),
offset: -1 .0 ,
};
assert!(plane.are_outside(&[point3(0 .0 , 0 .0 , 1 .1 ), point3(1 .0 , 1 .0 , 2 .0 ),]));
assert!(plane.are_outside(&[point3(0 .5 , 0 .5 , 1 .0 ),]));
assert!(!plane.are_outside(&[point3(0 .0 , 0 .0 , 1 .0 ), point3(0 .0 , 0 .0 , -1 .0 ),]));
}
#[ test]
fn intersect() {
let poly_a: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 0 .0 , 1 .0 ),
point3(1 .0 , 0 .0 , 1 .0 ),
point3(1 .0 , 1 .0 , 1 .0 ),
point3(0 .0 , 1 .0 , 1 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 0 .0 , 1 .0 ),
offset: -1 .0 ,
},
anchor: 0 ,
};
assert!(poly_a.is_valid());
let poly_b: Polygon<usize> = Polygon {
points: [
point3(0 .5 , 0 .0 , 2 .0 ),
point3(0 .5 , 1 .0 , 2 .0 ),
point3(0 .5 , 1 .0 , 0 .0 ),
point3(0 .5 , 0 .0 , 0 .0 ),
],
plane: Plane {
normal: vec3(1 .0 , 0 .0 , 0 .0 ),
offset: -0 .5 ,
},
anchor: 0 ,
};
assert!(poly_b.is_valid());
let intersection = match poly_a.intersect(&poly_b) {
Intersection::Inside(result) => result,
_ => panic!("Bad intersection" ),
};
assert!(intersection.is_valid());
// confirm the origin is on both planes
assert!(poly_a
.plane
.signed_distance_to(&intersection.origin)
.approx_eq(&0 .0 ));
assert!(poly_b
.plane
.signed_distance_to(&intersection.origin)
.approx_eq(&0 .0 ));
// confirm the direction is coplanar to both planes
assert!(poly_a.plane.normal.dot(intersection.dir).approx_eq(&0 .0 ));
assert!(poly_b.plane.normal.dot(intersection.dir).approx_eq(&0 .0 ));
let poly_c: Polygon<usize> = Polygon {
points: [
point3(0 .0 , -1 .0 , 2 .0 ),
point3(0 .0 , -1 .0 , 0 .0 ),
point3(0 .0 , 0 .0 , 0 .0 ),
point3(0 .0 , 0 .0 , 2 .0 ),
],
plane: Plane {
normal: vec3(1 .0 , 0 .0 , 0 .0 ),
offset: 0 .0 ,
},
anchor: 0 ,
};
assert!(poly_c.is_valid());
let poly_d: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 0 .0 , 0 .5 ),
point3(1 .0 , 0 .0 , 0 .5 ),
point3(1 .0 , 1 .0 , 0 .5 ),
point3(0 .0 , 1 .0 , 0 .5 ),
],
plane: Plane {
normal: vec3(0 .0 , 0 .0 , 1 .0 ),
offset: -0 .5 ,
},
anchor: 0 ,
};
assert!(poly_d.is_valid());
assert!(poly_a.intersect(&poly_c).is_outside());
assert!(poly_a.intersect(&poly_d).is_outside());
}
fn test_cut(poly_base: &Polygon<usize>, extra_count: u8, line: Line) {
assert!(line.is_valid());
let normal = poly_base.plane.normal.cross(line.dir).normalize();
let mut poly = poly_base.clone();
let (extra1, extra2) = poly.split_with_normal(&line, &normal);
assert!(poly.is_valid() && poly_base.contains(&poly));
assert_eq!(extra_count > 0 , extra1.is_some());
assert_eq!(extra_count > 1 , extra2.is_some());
if let Some(extra) = extra1 {
assert!(extra.is_valid() && poly_base.contains(&extra));
}
if let Some(extra) = extra2 {
assert!(extra.is_valid() && poly_base.contains(&extra));
}
}
#[ test]
fn split() {
let poly: Polygon<usize> = Polygon {
points: [
point3(0 .0 , 1 .0 , 0 .0 ),
point3(1 .0 , 1 .0 , 0 .0 ),
point3(1 .0 , 1 .0 , 1 .0 ),
point3(0 .0 , 1 .0 , 1 .0 ),
],
plane: Plane {
normal: vec3(0 .0 , 1 .0 , 0 .0 ),
offset: -1 .0 ,
},
anchor: 0 ,
};
// non-intersecting line
test_cut(
&poly,
0 ,
Line {
origin: point3(0 .0 , 1 .0 , 0 .5 ),
dir: vec3(0 .0 , 1 .0 , 0 .0 ),
},
);
// simple cut (diff=2)
test_cut(
&poly,
1 ,
Line {
origin: point3(0 .0 , 1 .0 , 0 .5 ),
dir: vec3(1 .0 , 0 .0 , 0 .0 ),
},
);
// complex cut (diff=1, wrapped)
test_cut(
&poly,
2 ,
Line {
origin: point3(0 .0 , 1 .0 , 0 .5 ),
dir: vec3(0 .5 f64.sqrt(), 0 .0 , -0 .5 f64.sqrt()),
},
);
// complex cut (diff=1, non-wrapped)
test_cut(
&poly,
2 ,
Line {
origin: point3(0 .5 , 1 .0 , 0 .0 ),
dir: vec3(0 .5 f64.sqrt(), 0 .0 , 0 .5 f64.sqrt()),
},
);
// complex cut (diff=3)
test_cut(
&poly,
2 ,
Line {
origin: point3(0 .5 , 1 .0 , 0 .0 ),
dir: vec3(-0 .5 f64.sqrt(), 0 .0 , 0 .5 f64.sqrt()),
},
);
// perfect diagonal
test_cut(
&poly,
1 ,
Line {
origin: point3(0 .0 , 1 .0 , 0 .0 ),
dir: vec3(0 .5 f64.sqrt(), 0 .0 , 0 .5 f64.sqrt()),
},
);
}
#[ test]
fn plane_unnormalized() {
let zero_vec = vec3(0 .0000001 , 0 .0 , 0 .0 );
let mut plane: Result<Option<Plane>, _> = Plane::from_unnormalized(zero_vec, 1 .0 );
assert_eq!(plane, Ok(None));
plane = Plane::from_unnormalized(zero_vec, 0 .0 );
assert_eq!(plane, Err(NegativeHemisphereError));
plane = Plane::from_unnormalized(zero_vec, -0 .5 );
assert_eq!(plane, Err(NegativeHemisphereError));
let plane = Plane::from_unnormalized(vec3(-3 .0 , 4 .0 , 0 .0 ), 2 .0 )
.unwrap()
.unwrap();
let expected = Plane {
normal: vec3(-3 .0 / 5 .0 , 4 .0 / 5 .0 , 0 .0 ),
offset: 2 .0 / 5 .0 ,
};
assert!(plane.normal.approx_eq(&expected.normal));
assert!(plane.offset.approx_eq(&expected.offset));
}
Messung V0.5 in Prozent C=91 H=95 G=92
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-18)
¤
*© Formatika GbR, Deutschland