Implement closest_point for Segments.
This commit is contained in:
parent
33bed5dd70
commit
0372a8a734
@ -1480,6 +1480,38 @@ impl Segment2d {
|
|||||||
self.reverse();
|
self.reverse();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the point on the [`Segment2d`] that is closest to the specified `point`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn closest_point(&self, point: Vec2) -> Vec2 {
|
||||||
|
// `point`
|
||||||
|
// x
|
||||||
|
// ^|
|
||||||
|
// / |
|
||||||
|
//`offset`/ |
|
||||||
|
// / | `segment_vector`
|
||||||
|
// x----.-------------->x
|
||||||
|
// 0 t 1
|
||||||
|
let segment_vector = self.vertices[1] - self.vertices[0];
|
||||||
|
let offset = point - self.vertices[0];
|
||||||
|
// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.
|
||||||
|
let projection_scaled = segment_vector.dot(offset);
|
||||||
|
|
||||||
|
// `point` is too far "left" in the picture
|
||||||
|
if projection_scaled <= 0.0 {
|
||||||
|
return self.vertices[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let length_squared = segment_vector.length_squared();
|
||||||
|
// `point` is too far "right" in the picture
|
||||||
|
if projection_scaled >= length_squared {
|
||||||
|
return self.vertices[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.
|
||||||
|
let t = projection_scaled / length_squared;
|
||||||
|
self.vertices[0] + t * segment_vector
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[Vec2; 2]> for Segment2d {
|
impl From<[Vec2; 2]> for Segment2d {
|
||||||
@ -2288,6 +2320,52 @@ mod tests {
|
|||||||
assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);
|
assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn segment_closest_point() {
|
||||||
|
assert_eq!(
|
||||||
|
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 0.0))
|
||||||
|
.closest_point(Vec2::new(1.0, 6.0)),
|
||||||
|
Vec2::new(1.0, 0.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
let segments = [
|
||||||
|
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),
|
||||||
|
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)),
|
||||||
|
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)),
|
||||||
|
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(1.0, 5.0 * f32::EPSILON)),
|
||||||
|
];
|
||||||
|
let points = [
|
||||||
|
Vec2::new(0.0, 0.0),
|
||||||
|
Vec2::new(1.0, 0.0),
|
||||||
|
Vec2::new(-1.0, 1.0),
|
||||||
|
Vec2::new(1.0, 1.0),
|
||||||
|
Vec2::new(-1.0, 0.0),
|
||||||
|
Vec2::new(5.0, -1.0),
|
||||||
|
Vec2::new(1.0, f32::EPSILON),
|
||||||
|
];
|
||||||
|
|
||||||
|
for point in points.iter() {
|
||||||
|
for segment in segments.iter() {
|
||||||
|
let closest = segment.closest_point(*point);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.point1()),
|
||||||
|
"Closest point must always at least as close as either vertex."
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.point2()),
|
||||||
|
"Closest point must always at least as close as either vertex."
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.center()),
|
||||||
|
"Closest point must always at least as close as the center."
|
||||||
|
);
|
||||||
|
let closest_to_closest = segment.closest_point(closest);
|
||||||
|
// Closest point must already be on the segment
|
||||||
|
assert_relative_eq!(closest_to_closest, closest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circle_math() {
|
fn circle_math() {
|
||||||
let circle = Circle { radius: 3.0 };
|
let circle = Circle { radius: 3.0 };
|
||||||
|
@ -548,6 +548,38 @@ impl Segment3d {
|
|||||||
self.reverse();
|
self.reverse();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the point on the [`Segment3d`] that is closest to the specified `point`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn closest_point(&self, point: Vec3) -> Vec3 {
|
||||||
|
// `point`
|
||||||
|
// x
|
||||||
|
// ^|
|
||||||
|
// / |
|
||||||
|
//`offset`/ |
|
||||||
|
// / | `segment_vector`
|
||||||
|
// x----.-------------->x
|
||||||
|
// 0 t 1
|
||||||
|
let segment_vector = self.vertices[1] - self.vertices[0];
|
||||||
|
let offset = point - self.vertices[0];
|
||||||
|
// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.
|
||||||
|
let projection_scaled = segment_vector.dot(offset);
|
||||||
|
|
||||||
|
// `point` is too far "left" in the picture
|
||||||
|
if projection_scaled <= 0.0 {
|
||||||
|
return self.vertices[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let length_squared = segment_vector.length_squared();
|
||||||
|
// `point` is too far "right" in the picture
|
||||||
|
if projection_scaled >= length_squared {
|
||||||
|
return self.vertices[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.
|
||||||
|
let t = projection_scaled / length_squared;
|
||||||
|
self.vertices[0] + t * segment_vector
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[Vec3; 2]> for Segment3d {
|
impl From<[Vec3; 2]> for Segment3d {
|
||||||
@ -1532,6 +1564,55 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn segment_closest_point() {
|
||||||
|
assert_eq!(
|
||||||
|
Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(3.0, 0.0, 0.0))
|
||||||
|
.closest_point(Vec3::new(1.0, 6.0, -2.0)),
|
||||||
|
Vec3::new(1.0, 0.0, 0.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
let segments = [
|
||||||
|
Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0)),
|
||||||
|
Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
|
||||||
|
Segment3d::new(Vec3::new(1.0, 0.0, 2.0), Vec3::new(0.0, 1.0, -2.0)),
|
||||||
|
Segment3d::new(
|
||||||
|
Vec3::new(1.0, 0.0, 0.0),
|
||||||
|
Vec3::new(1.0, 5.0 * f32::EPSILON, 0.0),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let points = [
|
||||||
|
Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
Vec3::new(1.0, 0.0, 0.0),
|
||||||
|
Vec3::new(-1.0, 1.0, 2.0),
|
||||||
|
Vec3::new(1.0, 1.0, 1.0),
|
||||||
|
Vec3::new(-1.0, 0.0, 0.0),
|
||||||
|
Vec3::new(5.0, -1.0, 0.5),
|
||||||
|
Vec3::new(1.0, f32::EPSILON, 0.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
for point in points.iter() {
|
||||||
|
for segment in segments.iter() {
|
||||||
|
let closest = segment.closest_point(*point);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.point1()),
|
||||||
|
"Closest point must always at least as close as either vertex."
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.point2()),
|
||||||
|
"Closest point must always at least as close as either vertex."
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
point.distance_squared(closest) <= point.distance_squared(segment.center()),
|
||||||
|
"Closest point must always at least as close as the center."
|
||||||
|
);
|
||||||
|
let closest_to_closest = segment.closest_point(closest);
|
||||||
|
// Closest point must already be on the segment
|
||||||
|
assert_relative_eq!(closest_to_closest, closest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sphere_math() {
|
fn sphere_math() {
|
||||||
let sphere = Sphere { radius: 4.0 };
|
let sphere = Sphere { radius: 4.0 };
|
||||||
|
Loading…
Reference in New Issue
Block a user