del_geo/
tri3.rs

1//! methods for 3d triangle
2
3use num_traits::{AsPrimitive, Zero};
4
5/// clamp barycentric coordinates inside a triangle
6pub fn clamp<T>(r0: T, r1: T, r2: T) -> (T, T, T)
7where
8    T: num_traits::Float,
9{
10    // vertex projection
11    if r0 <= T::zero() && r1 <= T::zero() {
12        return (T::zero(), T::zero(), T::one());
13    }
14    if r1 <= T::zero() && r2 <= T::zero() {
15        return (T::one(), T::zero(), T::zero());
16    }
17    if r2 <= T::zero() && r0 <= T::zero() {
18        return (T::zero(), T::one(), T::zero());
19    }
20    // edge projection
21    if r0 <= T::zero() {
22        return (T::zero(), r1 / (r1 + r2), r2 / (r1 + r2));
23    }
24    if r1 <= T::zero() {
25        return (r0 / (r0 + r2), T::zero(), r2 / (r0 + r2));
26    }
27    if r2 <= T::zero() {
28        return (r0 / (r0 + r1), r1 / (r0 + r1), T::zero());
29    }
30    (r0, r1, r2)
31}
32
33// ----------------------
34
35/// normal vector of a 3D triangle (coordinates given by stack-allocated arrays)
36pub fn normal_<T>(v1: &[T; 3], v2: &[T; 3], v3: &[T; 3]) -> [T; 3]
37where
38    T: std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + std::ops::Sub + Copy,
39{
40    [
41        (v2[1] - v1[1]) * (v3[2] - v1[2]) - (v2[2] - v1[2]) * (v3[1] - v1[1]),
42        (v2[2] - v1[2]) * (v3[0] - v1[0]) - (v2[0] - v1[0]) * (v3[2] - v1[2]),
43        (v2[0] - v1[0]) * (v3[1] - v1[1]) - (v2[1] - v1[1]) * (v3[0] - v1[0]),
44    ]
45}
46
47/// area of a 3D triangle (coordinates given by stack-allocated arrays)
48pub fn area_<T>(v1: &[T; 3], v2: &[T; 3], v3: &[T; 3]) -> T
49where
50    T: num_traits::Float,
51{
52    let na = normal_(v1, v2, v3);
53    let half = T::one() / (T::one() + T::one());
54    crate::vec3::squared_norm_(&na).sqrt() * half
55}
56
57pub fn unit_normal_area_<T>(v1: &[T; 3], v2: &[T; 3], v3: &[T; 3]) -> ([T; 3], T)
58where
59    T: num_traits::Float,
60{
61    use crate::vec3;
62    let n = normal_(v1, v2, v3);
63    let half = T::one() / (T::one() + T::one());
64    let a = vec3::norm_(&n) * half;
65    let invlen: T = half / a;
66    ([n[0] * invlen, n[1] * invlen, n[2] * invlen], a)
67}
68
69/// compute cotangents of the three angles of a triangle
70pub fn cot_<T>(p0: &[T; 3], p1: &[T; 3], p2: &[T; 3]) -> [T; 3]
71where
72    T: num_traits::Float,
73{
74    use crate::vec3;
75    assert!(p0.len() == 3 && p1.len() == 3 && p2.len() == 3);
76    let v0 = [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]];
77    let v1 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
78    let v2 = [p0[0] - p1[0], p0[1] - p1[1], p0[2] - p1[2]];
79    let half = T::one() / (T::one() + T::one());
80    let onefourth = T::one() / (T::one() + T::one() + T::one() + T::one());
81    let area = {
82        let na = [
83            v1[1] * v2[2] - v2[1] * v1[2],
84            v1[2] * v2[0] - v2[2] * v1[0],
85            v1[0] * v2[1] - v2[0] * v1[1],
86        ];
87        vec3::squared_norm_(&na).sqrt() * half
88    };
89    let tmp: T = onefourth / area;
90    let l0 = vec3::squared_norm_(&v0);
91    let l1 = vec3::squared_norm_(&v1);
92    let l2 = vec3::squared_norm_(&v2);
93    [
94        (l1 + l2 - l0) * tmp, // cot0 = cos0/sin0 = {(l1*l1+l2*l2-l0*l0)/(2*l1*l2)} / {2*area/(l1*l2)}
95        (l2 + l0 - l1) * tmp, // cot1 = cos1/sin1 = {(l2*l2+l0*l0-l1*l1)/(2*l2*l0)} / {2*area/(l2*l0)}
96        (l0 + l1 - l2) * tmp, // cot2 = cos2/sin2 = {(l0*l0+l1*l1-l2*l2)/(2*l0*l1)} / {2*area/(l0*l1)}
97    ]
98}
99
100pub fn emat_cotangent_laplacian<T>(p0: &[T; 3], p1: &[T; 3], p2: &[T; 3]) -> [[[T; 1]; 3]; 3]
101where
102    T: num_traits::Float,
103{
104    let cots = cot_(p0, p1, p2);
105    let half = T::one() / (T::one() + T::one());
106    [
107        [
108            [(cots[1] + cots[2]) * half],
109            [-cots[2] * half],
110            [-cots[1] * half],
111        ],
112        [
113            [-cots[2] * half],
114            [(cots[2] + cots[0]) * half],
115            [-cots[0] * half],
116        ],
117        [
118            [-cots[1] * half],
119            [-cots[0] * half],
120            [(cots[0] + cots[1]) * half],
121        ],
122    ]
123}
124
125pub fn emat_graph_laplacian<T>(l: T) -> [[[T; 1]; 3]; 3]
126where
127    T: num_traits::Float,
128{
129    let vo = -T::one() * l;
130    let vd = (T::one() + T::one()) * l;
131    [[[vd], [vo], [vo]], [[vo], [vd], [vo]], [[vo], [vo], [vd]]]
132}
133
134/// Möller–Trumbore ray-triangle intersection algorithm
135///
136/// <https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm>
137/// <https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection>
138pub fn ray_triangle_intersection_<T>(
139    ray_org: &[T; 3],
140    ray_dir: &[T; 3],
141    p0: &[T; 3],
142    p1: &[T; 3],
143    p2: &[T; 3],
144) -> Option<T>
145where
146    T: num_traits::Float + Copy + 'static,
147    f64: AsPrimitive<T>,
148{
149    use crate::vec3;
150    let eps: T = T::epsilon();
151    let edge1 = vec3::sub_(p1, p0);
152    let edge2 = vec3::sub_(p2, p0);
153    let pvec = vec3::cross_(ray_dir, &edge2);
154    let det = vec3::dot_(&edge1, &pvec);
155    if det > -eps && det < eps {
156        return None;
157    }
158    let invdet = T::one() / det;
159    let tvec = vec3::sub_(ray_org, p0);
160    let u = invdet * vec3::dot_(&tvec, &pvec);
161    if u < T::zero() || u > T::one() {
162        return None;
163    }
164    let qvec = vec3::cross_(&tvec, &edge1);
165    let v = invdet * vec3::dot_(ray_dir, &qvec);
166    if v < T::zero() || u + v > T::one() {
167        return None;
168    }
169    // At this stage we can compute t to find out where the intersection point is on the line.
170    let t = invdet * vec3::dot_(&edge2, &qvec);
171    Some(t)
172}
173
174// above: w/o nalgebra
175/* --------------------------------------------------------------------------------------- */
176// below: w/ nalgebra
177
178pub struct RayTriangleIntersectionData {
179    inv_det: f32,
180    n_dot_c: f32,
181    n_dot_dir: f32,
182    r_dot_e2: f32,
183    r_dot_e1: f32,
184    c: nalgebra::Vector3<f32>,
185    n: nalgebra::Vector3<f32>,
186    r: nalgebra::Vector3<f32>,
187    e1: nalgebra::Vector3<f32>,
188    e2: nalgebra::Vector3<f32>,
189    dir: nalgebra::Vector3<f32>,
190}
191
192/// ray triangle intersection.
193/// * `dir` - any nonzero vector (not necessary to be a unit vector)
194/// t, u, v: org + t * dir = (1 - u - v) * p0 + u * p1 + v * p2
195pub fn ray_triangle_intersection(
196    org: &nalgebra::Vector3<f32>,
197    dir: &nalgebra::Vector3<f32>,
198    p0: &nalgebra::Vector3<f32>,
199    p1: &nalgebra::Vector3<f32>,
200    p2: &nalgebra::Vector3<f32>,
201) -> Option<(f32, f32, f32, RayTriangleIntersectionData)> {
202    let e1 = p0 - p1;
203    let e2 = p2 - p0;
204    let n = e1.cross(&e2);
205    let n_dot_dir = n.dot(dir);
206    if n_dot_dir.is_zero() {
207        return None;
208    }
209    let inv_det = 1f32 / n_dot_dir;
210    let c = p0 - org;
211    let n_dot_c = n.dot(&c);
212    let t_ = inv_det * n_dot_c;
213    if t_ < 0f32 {
214        return None;
215    }
216    let r = dir.cross(&c);
217    //
218    let r_dot_e2 = r.dot(&e2);
219    let u_ = inv_det * r_dot_e2;
220    if u_ < 0f32 {
221        return None;
222    }
223    //
224    let r_dot_e1 = r.dot(&e1);
225    let v_ = inv_det * r_dot_e1;
226    if v_ < 0f32 {
227        return None;
228    }
229
230    if u_ + v_ >= 1f32 {
231        return None;
232    }
233    Some((
234        t_,
235        u_,
236        v_,
237        RayTriangleIntersectionData {
238            inv_det,
239            n_dot_c,
240            n_dot_dir,
241            r_dot_e2,
242            r_dot_e1,
243            c,
244            n,
245            r,
246            e1,
247            e2,
248            dir: *dir,
249        },
250    ))
251}
252
253pub fn dw_ray_triangle_intersection_(
254    d_t: f32,
255    d_u: f32,
256    d_v: f32,
257    data: &RayTriangleIntersectionData,
258) -> (
259    nalgebra::Vector3<f32>,
260    nalgebra::Vector3<f32>,
261    nalgebra::Vector3<f32>,
262) {
263    let d_n_dot_c = d_t * data.inv_det;
264    let d_r_dot_e2 = d_u * data.inv_det;
265    let d_r_dot_e1 = d_v * data.inv_det;
266    let d_inv_det = d_t * data.n_dot_c + d_u * data.r_dot_e2 + d_v * data.r_dot_e1;
267
268    let mut d_n = d_n_dot_c * data.c;
269    let mut d_c = d_n_dot_c * data.n;
270    let mut d_e2 = d_r_dot_e2 * data.r;
271    let mut d_e1 = d_r_dot_e1 * data.r;
272    let d_r = d_r_dot_e2 * data.e2 + d_r_dot_e1 * data.e1;
273
274    let d_n_dot_dir = -d_inv_det / data.n_dot_dir / data.n_dot_dir;
275    d_n += d_n_dot_dir * data.dir;
276    d_c += d_r.cross(&data.dir);
277    d_e2 += d_n.cross(&data.e1);
278    d_e1 += data.e2.cross(&d_n);
279
280    let d_p0 = d_e1 - d_e2 + d_c;
281    let d_p1 = -d_e1;
282    let d_p2 = d_e2;
283    (d_p0, d_p1, d_p2)
284}
285
286#[test]
287fn test_ray_triangle_intersection() {
288    let p0 = [
289        nalgebra::Vector3::<f32>::new(-1f32, -0.5f32, 0.5f32),
290        nalgebra::Vector3::<f32>::new(1f32, -0.5f32, 0.5f32),
291        nalgebra::Vector3::<f32>::new(0f32, 1f32, -0.5f32),
292    ];
293
294    let origin = nalgebra::Vector3::<f32>::new(1f32, 1f32, 1f32);
295    let target = nalgebra::Vector3::<f32>::new(0f32, 0f32, 0f32);
296    let dir: nalgebra::Vector3<f32> = target - origin;
297
298    let Some((t0, u0, v0, data)) = ray_triangle_intersection(&origin, &dir, &p0[0], &p0[1], &p0[2])
299    else {
300        panic!()
301    };
302
303    let ha: nalgebra::Vector3<f32> = (1f32 - u0 - v0) * p0[0] + u0 * p0[1] + v0 * p0[2];
304    assert!(crate::tet::volume(&p0[0], &p0[1], &p0[2], &ha).abs() < 1.0e-8);
305    let hb: nalgebra::Vector3<f32> = origin + t0 * dir;
306    assert!((ha - hb).norm() < 1.0e-6);
307
308    // d_t, d_u, d_u are back-propagated from the loss
309    let d_t = 1f32;
310    let d_u = 1f32;
311    let d_v = 1f32;
312
313    let dp = dw_ray_triangle_intersection_(d_t, d_u, d_v, &data);
314    let dp = [dp.0, dp.1, dp.2];
315    // dbg!(&d_p0, &d_p1, &d_p2);
316
317    let eps = 1.0e-3;
318    for i_node in 0..3 {
319        for i_dim in 0..3 {
320            let p1 = {
321                let mut p1 = p0.clone();
322                p1[i_node][i_dim] += eps;
323                p1
324            };
325            let Some((t1, u1, v1, _data)) =
326                ray_triangle_intersection(&origin, &dir, &p1[0], &p1[1], &p1[2])
327            else {
328                panic!()
329            };
330            let dloss = ((t1 - t0) * d_t + (u1 - u0) * d_u + (v1 - v0) * d_v) / eps;
331            let dloss_analytic = dp[i_node][i_dim];
332            assert!(
333                (dloss - dloss_analytic).abs() < 6.0e-4,
334                "{} {} {}",
335                dloss_analytic,
336                dloss,
337                dloss - dloss_analytic
338            );
339        }
340    }
341}
342
343/// height of triangle vertex `p2` against the edge connecting `p0` and `p1`
344pub fn height<T>(
345    p0: &nalgebra::Vector3<T>,
346    p1: &nalgebra::Vector3<T>,
347    p2: &nalgebra::Vector3<T>,
348) -> T
349where
350    T: nalgebra::RealField + 'static + Copy + num_traits::Float,
351    f64: AsPrimitive<T>,
352{
353    let a = area(p2, p0, p1);
354    a * 2.0.as_() / (p0 - p1).norm()
355}
356
357/// normal of a triangle
358pub fn normal<T>(
359    p0: &nalgebra::Vector3<T>,
360    p1: &nalgebra::Vector3<T>,
361    p2: &nalgebra::Vector3<T>,
362) -> nalgebra::Vector3<T>
363where
364    T: nalgebra::RealField,
365{
366    (p1 - p0).cross(&(p2 - p0))
367}
368
369pub fn dw_normal<T>(
370    p0: &nalgebra::Vector3<T>,
371    p1: &nalgebra::Vector3<T>,
372    p2: &nalgebra::Vector3<T>,
373) -> [nalgebra::Matrix3<T>; 3]
374where
375    T: nalgebra::RealField + Copy,
376{
377    [
378        crate::mat3::skew(&(p2 - p1)),
379        crate::mat3::skew(&(p0 - p2)),
380        crate::mat3::skew(&(p1 - p0)),
381    ]
382}
383
384#[test]
385fn test_normal() {
386    type V3 = nalgebra::Vector3<f64>;
387    {
388        let p0 = [
389            V3::new(0.0, 0.0, 0.0),
390            V3::new(1.0, 0.0, 0.0),
391            V3::new(0.0, 1.0, 0.0),
392        ];
393        let pn = normal(&p0[0], &p0[1], &p0[2]);
394        assert!((pn - V3::new(0., 0., 1.)).norm() < 1.0e-10);
395    }
396    {
397        let p0 = V3::new(0.1, 0.5, 0.4);
398        let p1 = V3::new(1.2, 0.3, 0.2);
399        let p2 = V3::new(0.3, 1.4, 0.1);
400        let pn = normal(&p0, &p1, &p2);
401        assert!((p0 - p1).dot(&pn) < 1.0e-10);
402        assert!((p1 - p2).dot(&pn) < 1.0e-10);
403        assert!((p2 - p0).dot(&pn) < 1.0e-10);
404    }
405    {
406        let p0 = [
407            V3::new(0.1, 0.4, 0.2),
408            V3::new(1.2, 0.3, 0.7),
409            V3::new(0.3, 1.5, 0.3),
410        ];
411        let cc0 = normal(&p0[0], &p0[1], &p0[2]);
412        let dcc = dw_normal(&p0[0], &p0[1], &p0[2]);
413        let eps = 1.0e-5;
414        for i_node in 0..3 {
415            for i_dim in 0..3 {
416                let p1 = {
417                    let mut p1 = p0;
418                    p1[i_node][i_dim] += eps;
419                    p1
420                };
421                let cc1 = normal(&p1[0], &p1[1], &p1[2]);
422                let dcc_num = (cc1 - cc0) / eps;
423                let mut b = V3::zeros();
424                b[i_dim] = 1.0;
425                let dcc_ana = dcc[i_node] * b;
426                let diff = (dcc_num - dcc_ana).norm();
427                assert!(diff < 1.0e-4);
428                println!(
429                    "normal {}, {} --> {:?}, {:?}, {:?}",
430                    i_node, i_dim, dcc_num, dcc_ana, diff
431                );
432            }
433        }
434    }
435}
436
437/// unit normal of a triangle
438pub fn unit_normal<T>(
439    p0: &nalgebra::Vector3<T>,
440    p1: &nalgebra::Vector3<T>,
441    p2: &nalgebra::Vector3<T>,
442) -> nalgebra::Vector3<T>
443where
444    T: nalgebra::RealField,
445{
446    let n = (p1 - p0).cross(&(p2 - p0));
447    n.normalize()
448}
449
450pub fn area<T>(p0: &nalgebra::Vector3<T>, p1: &nalgebra::Vector3<T>, p2: &nalgebra::Vector3<T>) -> T
451where
452    T: nalgebra::RealField + 'static + Copy,
453    f64: AsPrimitive<T>,
454{
455    (p1 - p0).cross(&(p2 - p0)).norm() * 0.5_f64.as_()
456}
457
458pub fn nearest_to_point3<T>(
459    q0: &nalgebra::Vector3<T>,
460    q1: &nalgebra::Vector3<T>,
461    q2: &nalgebra::Vector3<T>,
462    ps: &nalgebra::Vector3<T>,
463) -> (nalgebra::Vector3<T>, T, T)
464where
465    T: nalgebra::RealField + Copy + 'static,
466    f64: AsPrimitive<T>,
467{
468    let n012 = unit_normal(q0, q1, q2);
469    let pe = ps + n012;
470    let v012 = crate::tet::volume(ps, q0, q1, q2);
471    if v012.abs() > T::zero() {
472        let v0 = crate::tet::volume(ps, q1, q2, &pe);
473        let v1 = crate::tet::volume(ps, q2, q0, &pe);
474        let v2 = crate::tet::volume(ps, q0, q1, &pe);
475        assert!((v0 + v1 + v2).abs() > T::zero());
476        let inv_v012 = T::one() / (v0 + v1 + v2);
477        let r0 = v0 * inv_v012;
478        let r1 = v1 * inv_v012;
479        let r2 = T::one() - r0 - r1;
480        if r0 >= T::zero() && r1 >= T::zero() && r2 >= T::zero() {
481            let nearp = q0.scale(r0) + q1.scale(r1) + q2.scale(r2);
482            return (nearp, r0, r1);
483        }
484    }
485    let r12 = crate::edge3::nearest_to_point3(q1, q2, ps);
486    let r20 = crate::edge3::nearest_to_point3(q2, q0, ps);
487    let r01 = crate::edge3::nearest_to_point3(q0, q1, ps);
488    let d12 = r12.0;
489    let d20 = r20.0;
490    let d01 = r01.0;
491    let r12 = q1 + (q2 - q1).scale(r12.1);
492    let r20 = q2 + (q0 - q2).scale(r20.1);
493    let r01 = q0 + (q1 - q0).scale(r01.1);
494    if d12 < d20 {
495        if d12 < d01 {
496            // 12 is the smallest
497            let r0 = T::zero();
498            let r1 = (r12 - q2).norm() / (q1 - q2).norm();
499            return (r12, r0, r1);
500        }
501    } else if d20 < d01 {
502        // d20 is the smallest
503        let r0 = (r20 - q2).norm() / (q0 - q2).norm();
504        let r1 = T::zero();
505        return (r20, r0, r1);
506    }
507    let r0 = (r01 - q1).norm() / (q0 - q1).norm();
508    let r1 = T::one() - r0;
509    (r01, r0, r1)
510}
511
512#[test]
513fn test_triangle_point_nearest() {
514    let eps = 1.0 - 5f64;
515    for _ in 0..10000 {
516        let q0 = crate::vec3::sample_unit_cube::<f64>();
517        let q1 = crate::vec3::sample_unit_cube::<f64>();
518        let q2 = crate::vec3::sample_unit_cube::<f64>();
519        let ps = crate::vec3::sample_unit_cube::<f64>();
520        let (qs, r0, r1) = nearest_to_point3(&q0, &q1, &q2, &ps);
521        let dist0 = (qs - ps).norm();
522        use crate::tri3::clamp;
523        let (r0a, r1a, r2a) = clamp(r0 + eps, r1 + eps, 1. - r0 - eps - r1 - eps);
524        let qa = q0.scale(r0a) + q1.scale(r1a) + q2.scale(r2a);
525        assert!((qa - ps).norm() >= dist0);
526        let (r0b, r1b, r2b) = clamp(r0 + eps, r1 - eps, 1. - r0 - eps - r1 + eps);
527        let qb = q0.scale(r0b) + q1.scale(r1b) + q2.scale(r2b);
528        assert!((qb - ps).norm() >= dist0);
529        let (r0c, r1c, r2c) = clamp(r0 - eps, r1 + eps, 1. - r0 + eps - r1 - eps);
530        let qc = q0.scale(r0c) + q1.scale(r1c) + q2.scale(r2c);
531        assert!((qc - ps).norm() >= dist0);
532        let (r0d, r1d, r2d) = clamp(r0 - eps, r1 - eps, 1. - r0 + eps - r1 + eps);
533        let qd = q0.scale(r0d) + q1.scale(r1d) + q2.scale(r2d);
534        assert!((qd - ps).norm() >= dist0);
535    }
536}
537
538fn wdw_inverse_distance_cubic_integrated_over_wedge(
539    x: nalgebra::Vector3<f64>,
540    b: f64,
541) -> (f64, nalgebra::Vector3<f64>) {
542    let l = x.norm();
543    let c = 1. / (b * 0.5).tan();
544    let a = (l - x.x) * c - x.y;
545    let t = {
546        let t = (x.z.abs() / a).atan();
547        if t > 0. {
548            t
549        } else {
550            t + core::f64::consts::PI
551        }
552    };
553    let signz = if x.z < 0. { -1. } else { 1. };
554    let w = t * 2. / x.z.abs();
555    let d = 1.0 / (x.z * x.z + a * a);
556    let dwdx = 2. * (1. - x.x / l) * c * d;
557    let dwdy = 2. * (1. - x.y * c / l) * d;
558    let dwdz = -t / (x.z * x.z) + a * d / x.z.abs() - x.z.abs() * c * d / l;
559    (
560        w,
561        nalgebra::Vector3::<f64>::new(dwdx, dwdy, dwdz * signz * 2.),
562    )
563}
564
565pub fn wdw_integral_of_inverse_distance_cubic(
566    p0: &nalgebra::Vector3<f64>,
567    p1: &nalgebra::Vector3<f64>,
568    p2: &nalgebra::Vector3<f64>,
569    q: &nalgebra::Vector3<f64>,
570) -> (f64, nalgebra::Vector3<f64>) {
571    let vz = unit_normal(p0, p1, p2);
572    let z = (q - p0).dot(&vz);
573    let u10 = (p0 - p1).normalize();
574    let u21 = (p1 - p2).normalize();
575    let u02 = (p2 - p0).normalize();
576    //
577    let vy0 = vz.cross(&u02);
578    let beta0 = u02.dot(&u10).acos();
579    let q0 = nalgebra::Vector3::<f64>::new((q - p0).dot(&u02), (q - p0).dot(&vy0), z);
580    let (w0, dw0) = wdw_inverse_distance_cubic_integrated_over_wedge(q0, beta0);
581    let dw0dq = u02.scale(dw0.x) + vy0.scale(dw0.y) + vz.scale(dw0.z);
582    //
583    let vy1 = vz.cross(&u10);
584    let beta1 = u10.dot(&u21).acos();
585    let q1 = nalgebra::Vector3::<f64>::new((q - p1).dot(&u10), (q - p1).dot(&vy1), z);
586    let (w1, dw1) = wdw_inverse_distance_cubic_integrated_over_wedge(q1, beta1);
587    let dw1dq = u10.scale(dw1.x) + vy1.scale(dw1.y) + vz.scale(dw1.z);
588    //
589    let vy2 = vz.cross(&u21);
590    let beta2 = u21.dot(&u02).acos();
591    let q2 = nalgebra::Vector3::<f64>::new((q - p2).dot(&u21), (q - p2).dot(&vy2), z);
592    let (w2, dw2) = wdw_inverse_distance_cubic_integrated_over_wedge(q2, beta2);
593    let dw2dq = u21.scale(dw2.x) + vy2.scale(dw2.y) + vz.scale(dw2.z);
594    //
595    let w = core::f64::consts::PI * 2_f64 / z.abs() - w0 - w1 - w2;
596    let signz = if z < 0. { -1. } else { 1. };
597    let dw = -vz.scale(signz * core::f64::consts::PI * 2_f64 / (z * z)) - dw0dq - dw1dq - dw2dq;
598    (w, dw)
599}
600
601pub fn numerical_integration<F>(
602    p0: &nalgebra::Vector3<f64>,
603    p1: &nalgebra::Vector3<f64>,
604    p2: &nalgebra::Vector3<f64>,
605    integrand: F,
606    n: usize,
607) -> f64
608where
609    F: Fn(f64, f64) -> f64,
610{
611    let area = area(p0, p1, p2);
612    let jacobian = area / (n * n) as f64;
613    let mut val_num = 0_f64;
614    for i in 0..n {
615        for j in 0..i * 2 + 1 {
616            let j0 = j / 2;
617            let (u, v) = match j % 2 {
618                0 => {
619                    let v = (n - i) + (n - i - 1) * 2;
620                    let u = j0 * 2 + j0 + 1;
621                    (u, v)
622                }
623                1 => {
624                    let v = (n - i) * 2 + (n - i - 1);
625                    let u = (j0 + 1) * 2 + j0;
626                    (u, v)
627                }
628                _ => {
629                    panic!();
630                }
631            };
632            let (u, v) = (u as f64 / (n * 3) as f64, v as f64 / (n * 3) as f64);
633            let dist = integrand(u, v);
634            val_num += jacobian * dist;
635        }
636    }
637    val_num
638}
639
640pub fn is_intersection_tri3_sat(
641    p0: &nalgebra::Vector3<f32>,
642    p1: &nalgebra::Vector3<f32>,
643    p2: &nalgebra::Vector3<f32>,
644    q0: &nalgebra::Vector3<f32>,
645    q1: &nalgebra::Vector3<f32>,
646    q2: &nalgebra::Vector3<f32>,
647) -> bool {
648    let ps = nalgebra::Matrix3::<f32>::from_columns(&[*p0, *p1, *p2]);
649    let qs = nalgebra::Matrix3::<f32>::from_columns(&[*q0, *q1, *q2]);
650    let sep = |dir: nalgebra::Vector3<f32>| {
651        let prj0 = dir.transpose() * ps;
652        let prj1 = dir.transpose() * qs;
653        prj0.min() > prj1.max() || prj0.max() < prj1.min()
654    };
655    if sep((p1 - p0).cross(&(p2 - p0))) {
656        return false;
657    }
658    if sep((q1 - q0).cross(&(q2 - q0))) {
659        return false;
660    }
661    if sep((p0 - p1).cross(&(q0 - q1))) {
662        return false;
663    }
664    if sep((p0 - p1).cross(&(q1 - q2))) {
665        return false;
666    }
667    if sep((p0 - p1).cross(&(q2 - q0))) {
668        return false;
669    }
670    if sep((p1 - p2).cross(&(q0 - q1))) {
671        return false;
672    }
673    if sep((p1 - p2).cross(&(q1 - q2))) {
674        return false;
675    }
676    if sep((p1 - p2).cross(&(q2 - q0))) {
677        return false;
678    }
679    if sep((p2 - p0).cross(&(q0 - q1))) {
680        return false;
681    }
682    if sep((p2 - p0).cross(&(q1 - q2))) {
683        return false;
684    }
685    if sep((p2 - p0).cross(&(q2 - q0))) {
686        return false;
687    }
688    true
689}
690
691/// if the triangle share a point, set the point as `p0` and `q0`
692pub fn is_intersection_tri3<T>(
693    p0: &nalgebra::Vector3<T>,
694    p1: &nalgebra::Vector3<T>,
695    p2: &nalgebra::Vector3<T>,
696    q0: &nalgebra::Vector3<T>,
697    q1: &nalgebra::Vector3<T>,
698    q2: &nalgebra::Vector3<T>,
699) -> Option<(nalgebra::Vector3<T>, nalgebra::Vector3<T>)>
700where
701    T: nalgebra::RealField + Copy,
702{
703    let sec = |p0: &nalgebra::Vector3<T>, p1: &nalgebra::Vector3<T>, dp0: T, dp1: T| {
704        let r1 = dp0 / (dp0 - dp1);
705        let r0 = T::one() - r1;
706        p0.scale(r0) + p1.scale(r1)
707    };
708    let sgn = |v: T| {
709        if v == T::zero() {
710            1
711        } else if v < T::zero() {
712            0
713        } else {
714            2
715        }
716    };
717    let nq = normal(q0, q1, q2);
718    // heights of (p0,p1,p2) against the plane span by (q0,q1,q2)
719    let np = normal(p0, p1, p2);
720    // dbg!(p0,p1,p2,q0,q1,q2);
721    let vz = np.cross(&nq);
722    //
723    let (ps, pe) = {
724        let dp0 = (p0 - q0).dot(&nq);
725        let dp1 = (p1 - q0).dot(&nq);
726        let dp2 = (p2 - q0).dot(&nq);
727        let (sp0, sp1, sp2) = (sgn(dp0), sgn(dp1), sgn(dp2));
728        if sp0 == 0 && sp1 == 0 && sp2 == 0 {
729            return None;
730        } // all negative side
731        if sp0 == 2 && sp1 == 2 && sp2 == 2 {
732            return None;
733        } // all positive side
734        if sp0 + sp1 + sp2 == 1 || sp0 + sp1 + sp2 == 5 {
735            return None;
736        } // sharing point but not intersecting
737        if sp0 == 1 && sp1 == 1 && sp2 == 1 {
738            return None;
739        } // degenerate case inside same plane
740          // intersection of the lines connecting (p0,p1),(p1,p2),(p2,p0) and the plane span by (q0,q1,q2)
741        let mut ap = Vec::<nalgebra::Vector3<T>>::with_capacity(2);
742        if (sp0 == 0 && sp1 == 2) || (sp0 == 2 && sp1 == 0) {
743            ap.push(sec(p0, p1, dp0, dp1));
744        }
745        if (sp1 == 0 && sp2 == 2) || (sp1 == 2 && sp2 == 0) {
746            ap.push(sec(p1, p2, dp1, dp2));
747        }
748        if (sp2 == 0 && sp0 == 2) || (sp2 == 2 && sp0 == 0) {
749            ap.push(sec(p2, p0, dp2, dp0));
750        }
751        if sp0 == 1 {
752            ap.push(*p0);
753        }
754        if sp1 == 1 {
755            ap.push(*p1);
756        }
757        if sp2 == 1 {
758            ap.push(*p2);
759        }
760        assert_eq!(ap.len(), 2);
761        (ap[0], ap[1])
762    };
763    let zps = ps.dot(&vz);
764    let zpe = pe.dot(&vz);
765    let (ps, pe, zps, zpe) = if zps > zpe {
766        (pe, ps, zpe, zps)
767    } else {
768        (ps, pe, zps, zpe)
769    };
770    assert!(zps <= zpe);
771    //
772    let (qs, qe) = {
773        // intersection line between triangle p and plane span by (q0,q1,q2)
774        let dq0 = (q0 - p0).dot(&np); // projection of q0
775        let dq1 = (q1 - p0).dot(&np); // projection of q1
776        let dq2 = (q2 - p0).dot(&np); // projection of q2
777        let (sq0, sq1, sq2) = (sgn(dq0), sgn(dq1), sgn(dq2));
778        if sq0 == 0 && sq1 == 0 && sq2 == 0 {
779            return None;
780        }
781        if sq0 == 2 && sq1 == 2 && sq2 == 2 {
782            return None;
783        }
784        if sq0 + sq1 + sq2 == 1 || sq0 + sq1 + sq2 == 5 {
785            return None;
786        } // sharing point
787        if sq0 == 1 && sq1 == 1 && sq2 == 1 {
788            return None;
789        }
790        // intersection of the lines connecting (q0,q1),(q1,q2),(q2,q0) and the plane span by (p0,p1,p2)
791        let mut aq = Vec::<nalgebra::Vector3<T>>::with_capacity(2);
792        if (sq0 == 0 && sq1 == 2) || (sq0 == 2 && sq1 == 0) {
793            aq.push(sec(q0, q1, dq0, dq1));
794        }
795        if (sq1 == 0 && sq2 == 2) || (sq1 == 2 && sq2 == 0) {
796            aq.push(sec(q1, q2, dq1, dq2));
797        }
798        if (sq2 == 0 && sq0 == 2) || (sq2 == 2 && sq0 == 0) {
799            aq.push(sec(q2, q0, dq2, dq0));
800        }
801        if sq0 == 1 {
802            aq.push(*q0);
803        }
804        if sq1 == 1 {
805            aq.push(*q1);
806        }
807        if sq2 == 1 {
808            aq.push(*q2);
809        }
810        assert_eq!(aq.len(), 2);
811        (aq[0], aq[1])
812    };
813    let zqs = qs.dot(&vz);
814    let zqe = qe.dot(&vz);
815    let (qs, qe, zqs, zqe) = if zqs > zqe {
816        (qe, qs, zqe, zqs)
817    } else {
818        (qs, qe, zqs, zqe)
819    };
820    assert!(zqs <= zqe);
821    //
822    if zps >= zqe || zqs >= zpe {
823        return None;
824    } // no overlap or overlap at point
825    let s = if zps < zqs { qs } else { ps };
826    let e = if zpe < zqe { pe } else { qe };
827    Some((s, e))
828}
829
830#[test]
831fn test_triangle_intersection() {
832    for _ in 0..10000 {
833        let p0 = crate::vec3::sample_unit_cube();
834        let p1 = crate::vec3::sample_unit_cube();
835        let p2 = crate::vec3::sample_unit_cube();
836        if area(&p0, &p1, &p2) < 1.0e-2 {
837            continue;
838        }
839        let q0 = crate::vec3::sample_unit_cube();
840        let q1 = crate::vec3::sample_unit_cube();
841        let q2 = crate::vec3::sample_unit_cube();
842        if area(&q0, &q1, &q2) < 1.0e-2 {
843            continue;
844        }
845        let res = is_intersection_tri3(&p0, &p1, &p2, &q0, &q1, &q2);
846        {
847            let res_sat = is_intersection_tri3_sat(&p0, &p1, &p2, &q0, &q1, &q2);
848            assert_eq!(res_sat, res.is_some());
849        }
850        if let Some((r0, r1)) = res {
851            assert!(crate::tet::height(&p0, &p1, &p2, &r0).abs() < 2.0e-6);
852            assert!(crate::tet::height(&p0, &p1, &p2, &r1).abs() < 2.0e-6);
853            assert!(crate::tet::height(&q0, &q1, &q2, &r0).abs() < 2.0e-6);
854            assert!(crate::tet::height(&q0, &q1, &q2, &r1).abs() < 2.0e-6);
855            let bp0 = barycentric(&p0, &p1, &p2, &r0);
856            let bp1 = barycentric(&p0, &p1, &p2, &r1);
857            let bq0 = barycentric(&q0, &q1, &q2, &r0);
858            let bq1 = barycentric(&q0, &q1, &q2, &r1);
859            assert!(bp0.min() > -2.0e-5);
860            assert!(bp1.min() > -2.0e-5);
861            assert!(bq0.min() > -2.0e-5);
862            assert!(bq1.min() > -2.0e-5);
863            assert!(bp0.min().min(bq0.min()) < 3.0e-5);
864            assert!(bp1.min().min(bq1.min()) < 3.0e-5);
865        }
866    }
867}
868
869/// q must be coplanar to the triangle (p0,p1,p2)
870pub fn barycentric<T>(
871    p0: &nalgebra::Vector3<T>,
872    p1: &nalgebra::Vector3<T>,
873    p2: &nalgebra::Vector3<T>,
874    q: &nalgebra::Vector3<T>,
875) -> nalgebra::Vector3<T>
876where
877    T: nalgebra::RealField + Copy,
878    f64: AsPrimitive<T>,
879{
880    let n = (p1 - p0).cross(&(p2 - p0));
881    let s = q + n;
882    let r0 = crate::tet::volume(p1, p2, q, &s);
883    let r1 = crate::tet::volume(p2, p0, q, &s);
884    let r2 = crate::tet::volume(p0, p1, q, &s);
885    let tmp = T::one() / (r0 + r1 + r2);
886    nalgebra::Vector3::<T>::new(r0 * tmp, r1 * tmp, r2 * tmp)
887}
888
889// -----------------------------------
890
891#[cfg(test)]
892mod tests {
893    use rand::Rng;
894
895    use crate::tri3::{
896        area, barycentric, numerical_integration, wdw_integral_of_inverse_distance_cubic,
897    };
898
899    #[test]
900    fn test_w_inverse_distance_cubic_integrated_over_wedge() {
901        for _ in 0..1000 {
902            let x = crate::vec3::sample_unit_cube::<f64>() - nalgebra::Vector3::new(0.5, 0.5, 0.5);
903            if x.z.abs() < 0.1 {
904                continue;
905            }
906            let b = core::f64::consts::PI * 0.5; // 90 degree
907            let n_r = 200;
908            let n_t = 100;
909            let rad = 20.;
910            //
911            let mut val_nmr = 0.;
912            for i_r in 0..n_r {
913                for i_t in 0..n_t {
914                    let r = (i_r as f64 + 0.5) * rad / n_r as f64;
915                    let t = (i_t as f64 + 0.5) * b / n_t as f64;
916                    let pos = nalgebra::Vector3::<f64>::new(r * t.cos(), r * t.sin(), 0.);
917                    let area = rad / n_r as f64 * r * b / n_t as f64;
918                    val_nmr += area * (pos - x).norm().powi(-3);
919                }
920            }
921            use crate::tri3::wdw_inverse_distance_cubic_integrated_over_wedge;
922            let (v_anl, _) = wdw_inverse_distance_cubic_integrated_over_wedge(x, b);
923            assert!((v_anl - val_nmr).abs() < v_anl * 0.1);
924        }
925    }
926
927    #[test]
928    fn test_dw_inverse_distance_cubic_integrated_over_wedge() {
929        use crate::tri3::wdw_inverse_distance_cubic_integrated_over_wedge;
930        for _ in 0..100000 {
931            let x0 = crate::vec3::sample_unit_cube::<f64>() - nalgebra::Vector3::new(0.5, 0.5, 0.5);
932            if x0.z.abs() < 0.2 {
933                continue;
934            }
935            let b = core::f64::consts::PI * (rand::thread_rng().gen::<f64>() * 0.8 + 0.1); // 90 degree
936            let eps = 1.0e-4_f64;
937            let (w0, dw) = wdw_inverse_distance_cubic_integrated_over_wedge(x0, b);
938            let x1x = x0 + nalgebra::Vector3::<f64>::new(eps, 0., 0.);
939            let (w1x, _) = wdw_inverse_distance_cubic_integrated_over_wedge(x1x, b);
940            let x1y = x0 + nalgebra::Vector3::<f64>::new(0., eps, 0.);
941            let (w1y, _) = wdw_inverse_distance_cubic_integrated_over_wedge(x1y, b);
942            let x1z = x0 + nalgebra::Vector3::<f64>::new(0., 0., eps);
943            let (w1z, _) = wdw_inverse_distance_cubic_integrated_over_wedge(x1z, b);
944            assert!(((w1x - w0) / eps - dw.x).abs() < 2.0e-2 * (dw.x.abs() + 1.0e-1));
945            assert!(((w1y - w0) / eps - dw.y).abs() < 3.0e-2 * (dw.y.abs() + 5.0e-1));
946            assert!(((w1z - w0) / eps - dw.z).abs() < 2.0e-2 * (dw.z.abs() + 1.0e-1));
947        }
948    }
949
950    #[test]
951    fn test_w_inverse_distance_cubic_integrated() {
952        for _ in 0..1000 {
953            let p0 = crate::vec3::sample_unit_cube::<f64>();
954            let p1 = crate::vec3::sample_unit_cube::<f64>();
955            let p2 = crate::vec3::sample_unit_cube::<f64>();
956            let q = crate::vec3::sample_unit_cube::<f64>();
957            {
958                let area = area(&p0, &p1, &p2);
959                let h0 = crate::tri3::height(&p1, &p2, &p0);
960                let h1 = crate::tri3::height(&p2, &p0, &p1);
961                let h2 = crate::tri3::height(&p0, &p1, &p2);
962                // dbg!(area, h0, h1, h2);
963                if area < 0.1 || h0 < 0.1 || h1 < 0.1 || h2 < 0.1 {
964                    continue;
965                }
966                let height = crate::tet::height(&p0, &p1, &p2, &q);
967                if height.abs() < 0.1 {
968                    continue;
969                }
970            }
971            if q.z.abs() < 0.2 {
972                continue;
973            }
974            let (val_anly, _) = wdw_integral_of_inverse_distance_cubic(&p0, &p1, &p2, &q);
975            let integrand = |u: f64, v: f64| {
976                let p = (1. - u - v) * p0 + u * p1 + v * p2;
977                let dist = (p - q).norm();
978                1. / (dist * dist * dist)
979            };
980            let val_num = numerical_integration(&p0, &p1, &p2, integrand, 100);
981            assert!((val_num - val_anly).abs() < 3.0e-3 * (val_anly + 0.01));
982        }
983    }
984
985    #[test]
986    fn test_wdw_integral_of_inverse_distance_cubic() {
987        use crate::tri3::wdw_integral_of_inverse_distance_cubic;
988        for _ in 0..10000 {
989            let p0 = crate::vec3::sample_unit_cube::<f64>();
990            let p1 = crate::vec3::sample_unit_cube::<f64>();
991            let p2 = crate::vec3::sample_unit_cube::<f64>();
992            let q0 = crate::vec3::sample_unit_cube::<f64>();
993            {
994                let area = area(&p0, &p1, &p2);
995                let h0 = crate::tri3::height(&p1, &p2, &p0);
996                let h1 = crate::tri3::height(&p2, &p0, &p1);
997                let h2 = crate::tri3::height(&p0, &p1, &p2);
998                if area < 0.1 || h0 < 0.1 || h1 < 0.1 || h2 < 0.1 {
999                    continue;
1000                }
1001                let h = crate::tet::height(&p0, &p1, &p2, &q0);
1002                if h.abs() < 0.1 {
1003                    continue;
1004                }
1005                let b = barycentric(&p0, &p1, &p2, &q0);
1006                if b.abs().min() < 1.0e-2 {
1007                    continue;
1008                }
1009            }
1010            // dbg!(p0-q0,p1-q0,p2-q0);
1011            let (w0, dw) = wdw_integral_of_inverse_distance_cubic(&p0, &p1, &p2, &q0);
1012            let eps = 1.0e-4_f64;
1013            //
1014            let q1x = q0 + nalgebra::Vector3::<f64>::new(eps, 0., 0.);
1015            let (w1x, _) = wdw_integral_of_inverse_distance_cubic(&p0, &p1, &p2, &q1x);
1016            // dbg!((w1x-w0)/eps, dw.x);
1017            assert!(((w1x - w0) / eps - dw.x).abs() < 7.0e-2 * (dw.x.abs() + 1.));
1018            //
1019            let q1y = q0 + nalgebra::Vector3::<f64>::new(0., eps, 0.);
1020            let (w1y, _) = wdw_integral_of_inverse_distance_cubic(&p0, &p1, &p2, &q1y);
1021            // dbg!((w1y-w0)/eps, dw.y);
1022            assert!(((w1y - w0) / eps - dw.y).abs() < 7.0e-2 * (dw.y.abs() + 1.));
1023            //
1024            let q1z = q0 + nalgebra::Vector3::<f64>::new(0., 0., eps);
1025            let (w1z, _) = wdw_integral_of_inverse_distance_cubic(&p0, &p1, &p2, &q1z);
1026            // dbg!((w1z-w0)/eps, dw.z);
1027            assert!(((w1z - w0) / eps - dw.z).abs() < 7.0e-2 * (dw.z.abs() + 1.));
1028        }
1029    }
1030}