use num_traits::AsPrimitive;
pub trait Edge3Trait<T> {
fn length(&self, other: &Self) -> T;
fn squared_length(&self, other: &Self) -> T;
fn nearest_to_point3(&self, p1: &Self, point_pos: &Self) -> (T, T);
}
impl<Real> Edge3Trait<Real> for [Real; 3]
where
Real: num_traits::Float + 'static,
f64: AsPrimitive<Real>,
{
fn length(&self, other: &Self) -> Real {
length(self, other)
}
fn squared_length(&self, other: &Self) -> Real {
squared_length(self, other)
}
fn nearest_to_point3(&self, p1: &Self, point_pos: &Self) -> (Real, Real) {
nearest_to_point3(self, p1, point_pos)
}
}
pub fn length<T>(p0: &[T; 3], p1: &[T; 3]) -> T
where
T: num_traits::Float,
{
let x = p0[0] - p1[0];
let y = p0[1] - p1[1];
let z = p0[2] - p1[2];
(x * x + y * y + z * z).sqrt()
}
pub fn squared_length<T>(p0: &[T; 3], p1: &[T; 3]) -> T
where
T: num_traits::Float,
{
let x = p0[0] - p1[0];
let y = p0[1] - p1[1];
let z = p0[2] - p1[2];
x * x + y * y + z * z
}
pub fn position_from_ratio<T>(p0: &[T; 3], p1: &[T; 3], ratio: T) -> [T; 3]
where
T: num_traits::Float,
{
let one = T::one();
[
(one - ratio) * p0[0] + ratio * p1[0],
(one - ratio) * p0[1] + ratio * p1[1],
(one - ratio) * p0[2] + ratio * p1[2],
]
}
pub fn nearest_to_origin3<T>(p0: &[T; 3], p1: &[T; 3]) -> ([T; 3], T, T)
where
T: num_traits::Float,
{
let origin = &[T::zero(); 3];
let (_dist, t) = nearest_to_point3(p0, p1, origin);
let p = position_from_ratio(p0, p1, t);
let s0 = T::one() - t;
(p, s0, t)
}
pub fn nearest_to_point3<T>(p0: &[T; 3], p1: &[T; 3], point_pos: &[T; 3]) -> (T, T)
where
T: num_traits::Float,
{
use crate::vec3::Vec3;
let zero = T::zero();
let one = T::one();
let half = one / (one + one);
let d = p1.sub(p0);
let t = {
if d.dot(&d) > T::epsilon() {
let ps = std::array::from_fn(|i| p0[i] - point_pos[i]);
let a = d.dot(&d);
let b = d.dot(&ps);
(-b / a).clamp(zero, one)
} else {
half
}
};
let p = crate::vec3::axpy(t, &d, p0);
let dist = length(&p, point_pos);
(dist, t)
}
pub fn wdw_integral_of_inverse_distance_cubic<T>(
q: &[T; 3],
p0: &[T; 3],
p1: &[T; 3],
) -> (T, [T; 3])
where
T: num_traits::Float,
{
use crate::vec3::Vec3;
let one = T::one();
let two = one + one;
let three = two + one;
let len = p1.sub(p0).norm();
let lsinv = one / (len * len);
let d = p0.sub(p1).dot(&q.sub(p0)) * lsinv;
let a = q.sub(p0).squared_norm() * lsinv - d * d;
let f = |x| x / (a * (a + x * x).sqrt());
let v = (f(d + one) - f(d)) * lsinv;
let dd = p0.sub(p1).scale(lsinv);
let da = q.sub(p0).scale(two).scale(lsinv).sub(&dd.scale(two * d));
let dfdx = |x| one / (a + x * x).powf(three / two);
let dfda = |x| -(x * (three * a + two * x * x)) / (two * a * a * (a + x * x).powf(three / two));
let t0 = dd.scale(dfdx(d + one) - dfdx(d));
let t1 = da.scale(dfda(d + one) - dfda(d));
let dv = t0.add(&t1);
(v, dv.scale(lsinv))
}
#[cfg(test)]
mod tests {
use crate::edge3::position_from_ratio;
fn numerical(q: &[f64; 3], p0: &[f64; 3], p1: &[f64; 3], n: usize, p: usize) -> f64 {
use crate::vec3::Vec3;
use num_traits::Pow;
let len = p1.sub(p0).norm();
let mut ret = 0.;
for i_seg in 0..n {
let r0 = i_seg as f64 / n as f64;
let r1 = (i_seg + 1) as f64 / n as f64;
let pr0q = position_from_ratio(p0, p1, r0).sub(q);
let pr1q = position_from_ratio(p0, p1, r1).sub(q);
let dist0 = pr0q.norm();
let dist1 = pr1q.norm();
let v0 = 1. / dist0.pow(p as i32);
let v1 = 1. / dist1.pow(p as i32);
let v = (v0 + v1) * 0.5;
ret += v;
}
ret *= len / (n as f64);
ret
}
#[test]
fn test_wdw_integral_of_inverse_distance_cubic() {
use crate::vec3::Vec3;
use rand::SeedableRng;
let mut reng = rand_chacha::ChaChaRng::seed_from_u64(0u64);
for _i in 0..10000 {
let p0 = crate::vec3::sample_unit_cube(&mut reng);
let p1 = crate::vec3::sample_unit_cube(&mut reng);
let q = crate::vec3::sample_unit_cube(&mut reng);
let len = p0.sub(&p1).norm();
let height = crate::tri3::height(&p0, &p1, &q);
if height < 0.1 {
continue;
}
if len < 0.1 {
continue;
}
if p0.sub(&q).norm() < 0.1 {
continue;
}
if p1.sub(&q).norm() < 0.1 {
continue;
}
let (v0, dv0) = crate::edge3::wdw_integral_of_inverse_distance_cubic(&q, &p0, &p1);
assert!((v0 - numerical(&q, &p0, &p1, 1000, 3)).abs() < 1.0e-4 * v0.abs());
let eps = 1.0e-4_f64;
let qex = [q[0] + eps, q[1], q[2]];
let qey = [q[0], q[1] + eps, q[2]];
let qez = [q[0], q[1], q[2] + eps];
let vx = (numerical(&qex, &p0, &p1, 1000, 3) - v0) / eps;
let vy = (numerical(&qey, &p0, &p1, 1000, 3) - v0) / eps;
let vz = (numerical(&qez, &p0, &p1, 1000, 3) - v0) / eps;
let dv1 = [vx, vy, vz];
assert!(dv0.sub(&dv1).norm() < 0.03 * (dv0.norm() + 1.0));
}
}
}
pub fn nearest_to_edge3<T>(p0: &[T; 3], p1: &[T; 3], q0: &[T; 3], q1: &[T; 3]) -> (T, T, T)
where
T: num_traits::Float,
{
use crate::vec3::Vec3;
let zero = T::zero();
let one = T::one();
let half = one / (one + one);
let vp = p1.sub(p0);
let vq = q1.sub(q0);
assert!(vp.norm() > T::zero());
assert!(vq.norm() > T::zero());
if vp.cross(&vq).norm() < T::epsilon() {
let pq0 = p0.sub(q0);
let uvp = vp.normalize();
let vert = pq0.sub(&uvp.scale(pq0.dot(&uvp)));
let dist = vert.norm(); let lp0 = p0.dot(&uvp);
let lp1 = p1.dot(&uvp);
let lq0 = q0.dot(&uvp);
let lq1 = q1.dot(&uvp);
let (lp_min, lp_max, p_min, p_max, rp_min, rp_max) =
(lp0, lp1, p0, p1, T::zero(), T::one());
assert!(lp_min < lp_max);
let (lq_min, lq_max, q_min, q_max, rq_min, rq_max) = if lq0 < lq1 {
(lq0, lq1, q0, q1, T::zero(), T::one())
} else {
(lq1, lq0, q1, q0, T::one(), T::zero())
};
if lp_max < lq_min {
return (p_max.sub(q_min).norm(), rp_max, rq_min);
}
if lq_max < lp_min {
return (q_max.sub(p_min).norm(), rp_min, rq_max);
}
let lm_min = lp_min.max(lq_min);
let lm_max = lp_max.min(lq_max);
let lm = (lm_min + lm_max) * half;
let ratio_p = (lm - lp0) / (lp1 - lp0);
let ratio_q = (lm - lq0) / (lq1 - lq0);
return (dist, ratio_p, ratio_q);
}
let (rp1, rq1) = {
let t0 = vp.dot(&vp);
let t1 = vq.dot(&vq);
let t2 = vp.dot(&vq);
let t3 = vp.dot(&q0.sub(p0));
let t4 = vq.dot(&q0.sub(p0));
let det = t0 * t1 - t2 * t2;
let invdet = one / det;
let rp1 = (t1 * t3 - t2 * t4) * invdet;
let rq1 = (t2 * t3 - t0 * t4) * invdet;
(rp1, rq1)
};
if zero <= rp1 && rp1 <= one && zero <= rq1 && rq1 <= one {
let pc = p0.add(&vp.scale(rp1));
let qc = q0.add(&vq.scale(rq1));
return (pc.sub(&qc).norm(), rp1, rq1);
}
if (zero <= rp1 && rp1 <= one) && (rq1 <= zero || one <= rq1) {
let rq1 = num_traits::clamp(rq1, zero, one);
let qc = crate::vec3::axpy(rq1, &vq, q0);
let (dist, rp1) = nearest_to_point3(p0, p1, &qc);
return (dist, rp1, rq1);
}
if (zero <= rq1 && rq1 <= one) && (rp1 <= zero || one <= rp1) {
let rp1 = num_traits::clamp(rp1, zero, one);
let pc = crate::vec3::axpy(rp1, &vp, p0);
let (dist, rq1) = nearest_to_point3(q0, q1, &pc);
return (dist, rp1, rq1);
}
let rp1 = num_traits::clamp(rp1, zero, one);
let pc = p0.add(&vp.scale(rp1));
let (_dist, rq1) = nearest_to_point3(q0, q1, &pc);
let qc = q0.add(&q1.sub(q0).scale(rq1));
let (_dist, rp1) = nearest_to_point3(p0, p1, &qc);
let pc = p0.add(&p1.sub(p0).scale(rp1));
let (dist, rq1) = nearest_to_point3(q0, q1, &pc);
(dist, rp1, rq1)
}
#[test]
fn test_nearest_to_edge3() {
use crate::vec3::Vec3;
use crate::vec3::axpy;
use rand::SeedableRng;
let mut reng = rand_chacha::ChaChaRng::seed_from_u64(0u64);
let eps = 1.0e-4;
for _i in 0..10000 {
let p0 = crate::vec3::sample_unit_cube::<_, f64>(&mut reng);
let p1 = crate::vec3::sample_unit_cube::<_, f64>(&mut reng);
let q0 = crate::vec3::sample_unit_cube::<_, f64>(&mut reng);
let q1 = crate::vec3::sample_unit_cube::<_, f64>(&mut reng);
let (dist, rp, rq) = crate::edge3::nearest_to_edge3(&p0, &p1, &q0, &q1);
let vp = p1.sub(&p0);
let pc0 = axpy(f64::clamp(rp - eps, 0.0, 1.0), &vp, &p0);
let pc1 = axpy(rp, &vp, &p0);
let pc2 = axpy(f64::clamp(rp + eps, 0.0, 1.0), &vp, &p0);
let vq = q1.sub(&q0);
let qc0 = axpy(f64::clamp(rq - eps, 0.0, 1.0), &vq, &q0);
let qc1 = axpy(rq, &vq, &q0);
let qc2 = axpy(f64::clamp(rq + eps, 0.0, 1.0), &vq, &q0);
assert!((dist - (pc1.sub(&qc1)).norm()).abs() < 1.0e-5);
assert!(dist <= pc0.sub(&qc0).norm());
assert!(dist <= pc0.sub(&qc1).norm());
assert!(dist <= pc0.sub(&qc2).norm());
assert!(dist <= pc1.sub(&qc0).norm());
assert!(dist <= pc1.sub(&qc2).norm());
assert!(dist <= pc2.sub(&qc0).norm());
assert!(dist <= pc2.sub(&qc1).norm());
assert!(dist <= pc2.sub(&qc2).norm());
}
}
pub fn intersection_edge3_when_coplanar<T>(
p0: &[T; 3],
p1: &[T; 3],
q0: &[T; 3],
q1: &[T; 3],
) -> Option<(T, T, T, T)>
where
T: num_traits::Float + Copy + 'static,
f64: AsPrimitive<T>,
{
use crate::vec3::Vec3;
let n = {
let n0 = p1.sub(p0).cross(&q0.sub(p0));
let n1 = p1.sub(p0).cross(&q1.sub(p0));
if n0.squared_norm() < n1.squared_norm() {
n1
} else {
n0
}
};
let p2 = p0.add(&n);
let rq1 = crate::tet::volume(p0, p1, &p2, q0);
let rq0 = crate::tet::volume(p0, p1, &p2, q1);
let rp1 = crate::tet::volume(q0, q1, &p2, p0);
let rp0 = crate::tet::volume(q0, q1, &p2, p1);
if (rp0 - rp1).abs() <= T::zero() {
return None;
}
if (rq0 - rq1).abs() <= T::zero() {
return None;
}
let t = T::one() / (rp0 - rp1);
let (rp0, rp1) = (rp0 * t, -rp1 * t);
let t = T::one() / (rq0 - rq1);
let (rq0, rq1) = (rq0 * t, -rq1 * t);
Some((rp0, rp1, rq0, rq1))
}