use glam_det::{Dot, DotClamp, Point3, UnitVec3, Vec3};
pub struct ClosestPointToTriangleResult {
#[allow(dead_code)]
pub region: Region,
pub closest_point: Vec3,
}
pub enum Region {
Region0,
Region1,
Region2,
Region3,
Region4,
Region5,
Region6,
}
pub fn closest_point_to_triangle(
edge0: Vec3,
edge1: Vec3,
diff: Vec3,
a: Vec3,
) -> ClosestPointToTriangleResult {
let a00 = edge0.dot(edge0);
let a01 = edge0.dot(edge1);
let a11 = edge1.dot(edge1);
let b0 = diff.dot(edge0);
let b1 = diff.dot(edge1);
let det = (a00 * a11 - a01 * a01).max(0f32);
let mut s = a01 * b1 - a11 * b0;
let mut t = a01 * b0 - a00 * b1;
let zero = 0f32;
let one = 1f32;
let two = 2f32;
let region: Region;
if s + t <= det {
if s < zero {
if t < zero {
region = Region::Region4;
if b0 < zero {
t = zero;
if -b0 >= a00 {
s = one;
} else {
s = -b0 / a00;
}
} else {
s = zero;
if b1 >= zero {
t = zero;
} else if -b1 >= a11 {
t = one;
} else {
t = -b1 / a11;
}
}
} else {
region = Region::Region3;
s = zero;
if b1 >= zero {
t = zero;
} else if -b1 >= a11 {
t = one;
} else {
t = -b1 / a11;
}
}
} else if t < zero {
region = Region::Region5;
t = zero;
if b0 >= zero {
s = zero;
} else if -b0 >= a00 {
s = one;
} else {
s = -b0 / a00;
}
} else {
region = Region::Region0;
s /= det;
t /= det;
}
} else {
let tmp0;
let tmp1;
let numer;
let denom;
if s < zero {
region = Region::Region2;
tmp0 = a01 + b0;
tmp1 = a11 + b1;
if tmp1 > tmp0 {
numer = tmp1 - tmp0;
denom = a00 - two * a01 + a11;
if numer >= denom {
s = one;
t = zero;
} else {
s = numer / denom;
t = one - s;
}
} else {
s = zero;
if tmp1 <= zero {
t = one;
} else if b1 >= zero {
t = zero;
} else {
t = -b1 / a11;
}
}
} else if t < zero {
region = Region::Region6;
tmp0 = a01 + b1;
tmp1 = a00 + b0;
if tmp1 > tmp0 {
numer = tmp1 - tmp0;
denom = a00 - two * a01 + a11;
if numer >= denom {
t = one;
s = zero;
} else {
t = numer / denom;
s = one - t;
}
} else {
t = zero;
if tmp1 <= zero {
s = one;
} else if b0 >= zero {
s = zero;
} else {
s = -b0 / a00;
}
}
} else {
region = Region::Region1;
numer = a11 + b1 - a01 - b0;
if numer <= zero {
s = zero;
t = one;
} else {
denom = a00 - two * a01 + a11;
if numer >= denom {
s = one;
t = zero;
} else {
s = numer / denom;
t = one - s;
}
}
}
}
ClosestPointToTriangleResult {
region,
closest_point: a + s * edge0 + t * edge1,
}
}
pub fn distance_squared_between_two_segments(
offset_a_to_b: Vec3,
direction_a: UnitVec3,
length_a: f32,
direction_b: UnitVec3,
length_b: f32,
) -> f32 {
let dir_a_dot_offset = direction_a.dot(offset_a_to_b);
let dir_b_dot_offset = direction_b.dot(offset_a_to_b);
let dir_a_dot_b = direction_a.dot_clamp(direction_b);
let mut term0 = (dir_a_dot_offset - dir_b_dot_offset * dir_a_dot_b)
/ (f32::max(f32::EPSILON, 1.0 - dir_a_dot_b * dir_a_dot_b));
let mut term1 = term0 * dir_a_dot_b - dir_b_dot_offset;
term0 = {
let term_bound_0 = f32::clamp(dir_a_dot_offset, 0.0, length_a);
let term_bound_1 = f32::clamp(dir_a_dot_offset + length_b * dir_a_dot_b, 0.0, length_a);
let term_min = term_bound_0.min(term_bound_1);
let term_max = term_bound_1.max(term_bound_0);
f32::clamp(term0, term_min, term_max)
};
term1 = {
let term_bound_0 = f32::clamp(-dir_b_dot_offset, 0.0, length_b);
let term_bound_1 = f32::clamp(-dir_b_dot_offset + length_a * dir_a_dot_b, 0.0, length_b);
let term_min = term_bound_0.min(term_bound_1);
let term_max = term_bound_1.max(term_bound_0);
f32::clamp(term1, term_min, term_max)
};
(direction_a * term0).distance_squared(offset_a_to_b + direction_b * term1)
}
pub fn distance_squared_point_to_segment(point: Point3, a: Point3, b: Point3, len: f32) -> f32 {
let ap = point - a;
let ab = b - a;
let ac_len = ap.dot(ab) / len;
let r = ac_len / len;
if r <= 0.0 {
ap.length_squared()
} else if r >= 1.0 {
(point - b).length_squared()
} else {
ap.length_squared() - ac_len * ac_len
}
}
#[cfg(test)]
mod tests {
use approx_det::assert_relative_eq;
use wasm_bindgen_test::*;
use super::*;
const EPS: f32 = 1e-9f32;
#[test]
#[wasm_bindgen_test]
fn test_distance_between_two_segments() {
let _ = env_logger::builder().is_test(true).try_init();
assert_relative_eq!(
1.0,
distance_squared_between_two_segments(
Vec3::new(1.0, 0.0, 0.0),
UnitVec3::new_normalized(0.0, 1.0, 0.0),
1.0,
UnitVec3::new_normalized(0.0, 1.0, 0.0),
1.0
),
epsilon = EPS
);
assert_relative_eq!(
1.0,
distance_squared_between_two_segments(
Vec3::new(1.0, 0.0, 0.0),
UnitVec3::new_normalized(-1.0, 1.0, 0.0),
1.0,
UnitVec3::new_normalized(1.0, 1.0, 0.0),
1.0
),
epsilon = EPS
);
}
#[test]
#[wasm_bindgen_test]
fn test_point_to_segment() {
let _ = env_logger::builder().is_test(true).try_init();
assert_relative_eq!(
1.0,
distance_squared_point_to_segment(
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
assert_relative_eq!(
1.0,
distance_squared_point_to_segment(
Point3::new(3.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
assert_relative_eq!(
0.0,
distance_squared_point_to_segment(
Point3::new(1.5, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
assert_relative_eq!(
2.0,
distance_squared_point_to_segment(
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
assert_relative_eq!(
2.0,
distance_squared_point_to_segment(
Point3::new(3.0, 1.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
assert_relative_eq!(
1.0,
distance_squared_point_to_segment(
Point3::new(1.5, 1.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
1.0,
)
);
}
}