Skip to main content

box3d_rust/math_functions/
ray_triangle.rs

1//! Scalar ray-triangle and AABB-triangle overlap from simd.c (B3_SIMD_NONE path).
2//!
3//! SPDX-FileCopyrightText: 2026 Erin Catto
4//! SPDX-License-Identifier: MIT
5
6use super::*;
7
8/// True if any component of `a` is strictly less than the corresponding component of `b`.
9fn any_less3(a: Vec3, b: Vec3) -> bool {
10    a.x < b.x || a.y < b.y || a.z < b.z
11}
12
13/// True if any component of `a` is strictly greater than the corresponding component of `b`.
14fn any_greater3(a: Vec3, b: Vec3) -> bool {
15    a.x > b.x || a.y > b.y || a.z > b.z
16}
17
18/// Transpose three column vectors in place (scalar B3_TRANSPOSE3).
19fn transpose3(c1: &mut Vec3, c2: &mut Vec3, c3: &mut Vec3) {
20    let temp1 = c1.y;
21    let temp2 = c1.z;
22    let temp3 = c2.z;
23
24    c1.y = c2.x;
25    c1.z = c3.x;
26    c2.z = c3.y;
27
28    c2.x = temp1;
29    c3.x = temp2;
30    c3.y = temp3;
31}
32
33/// Test overlap between an AABB (center + extent) and a triangle.
34/// (simd.c: b3TestBoundsTriangleOverlap)
35pub fn test_bounds_triangle_overlap(
36    node_center: Vec3,
37    node_extent: Vec3,
38    mut vertex1: Vec3,
39    mut vertex2: Vec3,
40    mut vertex3: Vec3,
41) -> bool {
42    let two = Vec3 {
43        x: 2.0,
44        y: 2.0,
45        z: 2.0,
46    };
47
48    // Setup triangle
49    vertex1 = sub(vertex1, node_center);
50    vertex2 = sub(vertex2, node_center);
51    vertex3 = sub(vertex3, node_center);
52
53    // Face separation
54    let triangle_min = min(vertex1, min(vertex2, vertex3));
55    let triangle_max = max(vertex1, max(vertex2, vertex3));
56
57    let separation1 = sub(triangle_min, node_extent);
58    let separation2 = add(triangle_max, node_extent);
59
60    let face_separation = max(separation1, neg(separation2));
61    if any_greater3(face_separation, VEC3_ZERO) {
62        return false;
63    }
64
65    // SAT: Face separation
66    let edge1 = sub(vertex2, vertex1);
67    let edge2 = sub(vertex3, vertex2);
68    let edge3 = sub(vertex1, vertex3);
69
70    let normal = cross(edge1, edge2);
71
72    let d = dot(normal, vertex1);
73    let e = dot(abs(normal), node_extent);
74    let triangle_separation = Vec3 {
75        x: abs_float(d) - e,
76        y: abs_float(d) - e,
77        z: abs_float(d) - e,
78    };
79    if any_greater3(triangle_separation, VEC3_ZERO) {
80        return false;
81    }
82
83    // SAT: Edge separation
84    let edge_separation1 = sub(
85        sub(
86            abs(cross(edge1, add(vertex1, vertex3))),
87            abs(cross(edge1, edge3)),
88        ),
89        mul(two, modified_cross(abs(edge1), node_extent)),
90    );
91    if any_greater3(edge_separation1, VEC3_ZERO) {
92        return false;
93    }
94
95    let edge_separation2 = sub(
96        sub(
97            abs(cross(edge2, add(vertex1, vertex2))),
98            abs(cross(edge2, edge1)),
99        ),
100        mul(two, modified_cross(abs(edge2), node_extent)),
101    );
102    if any_greater3(edge_separation2, VEC3_ZERO) {
103        return false;
104    }
105
106    let edge_separation3 = sub(
107        sub(
108            abs(cross(edge3, add(vertex2, vertex3))),
109            abs(cross(edge3, edge2)),
110        ),
111        mul(two, modified_cross(abs(edge3), node_extent)),
112    );
113    if any_greater3(edge_separation3, VEC3_ZERO) {
114        return false;
115    }
116
117    true
118}
119
120/// True if all components of `a` are ≤ the corresponding components of `b`.
121fn all_less_eq3(a: Vec3, b: Vec3) -> bool {
122    a.x <= b.x && a.y <= b.y && a.z <= b.z
123}
124
125/// Test overlap between two AABBs given as min/max corners.
126/// (simd.h: b3TestBoundsOverlap)
127pub fn test_bounds_overlap(
128    node_min1: Vec3,
129    node_max1: Vec3,
130    node_min2: Vec3,
131    node_max2: Vec3,
132) -> bool {
133    let separation = max(sub(node_min2, node_max1), sub(node_min1, node_max2));
134    all_less_eq3(separation, VEC3_ZERO)
135}
136
137/// Test a ray for edge separation with an AABB (Gino, p80).
138/// (simd.h: b3TestBoundsRayOverlap)
139pub fn test_bounds_ray_overlap(
140    node_min: Vec3,
141    node_max: Vec3,
142    mut ray_start: Vec3,
143    ray_delta: Vec3,
144) -> bool {
145    let node_center = mul_sv(0.5, add(node_min, node_max));
146    let node_extent = sub(node_max, node_center);
147
148    ray_start = sub(ray_start, node_center);
149
150    let edge_separation = sub(
151        abs(cross(ray_delta, ray_start)),
152        modified_cross(abs(ray_delta), node_extent),
153    );
154    all_less_eq3(edge_separation, VEC3_ZERO)
155}
156
157/// Intersect a ray with a triangle. Returns the hit fraction in (0, 1], or 1.0 on miss.
158/// (simd.c: b3IntersectRayTriangle, scalar path)
159pub fn intersect_ray_triangle(
160    ray_start: Vec3,
161    ray_delta: Vec3,
162    vertex1: Vec3,
163    vertex2: Vec3,
164    vertex3: Vec3,
165) -> f32 {
166    // Test if ray intersects this triangle sharing same calculations for each triangle
167    {
168        let edge1 = sub(vertex3, vertex2);
169        let edge2 = sub(vertex1, vertex3);
170        let edge3 = sub(vertex2, vertex1);
171
172        let mid_point1 = mul_sv(0.5, add(vertex2, vertex3));
173        let mid_point2 = mul_sv(0.5, add(vertex3, vertex1));
174        let mid_point3 = mul_sv(0.5, add(vertex1, vertex2));
175
176        let mut normal1 = cross(edge1, sub(mid_point1, ray_start));
177        let mut normal2 = cross(edge2, sub(mid_point2, ray_start));
178        let mut normal3 = cross(edge3, sub(mid_point3, ray_start));
179        transpose3(&mut normal1, &mut normal2, &mut normal3);
180
181        let ray_delta_x = Vec3 {
182            x: ray_delta.x,
183            y: ray_delta.x,
184            z: ray_delta.x,
185        };
186        let ray_delta_y = Vec3 {
187            x: ray_delta.y,
188            y: ray_delta.y,
189            z: ray_delta.y,
190        };
191        let ray_delta_z = Vec3 {
192            x: ray_delta.z,
193            y: ray_delta.z,
194            z: ray_delta.z,
195        };
196
197        let volumes = add(
198            add(mul(normal1, ray_delta_x), mul(normal2, ray_delta_y)),
199            mul(normal3, ray_delta_z),
200        );
201        if any_less3(volumes, VEC3_ZERO) {
202            return 1.0;
203        }
204    }
205
206    // Compute intersection with triangle plane
207    let edge1 = sub(vertex2, vertex1);
208    let edge2 = sub(vertex3, vertex1);
209    let normal = cross(edge1, edge2);
210
211    let denominator = dot(normal, ray_delta);
212    if denominator >= 0.0 {
213        return 1.0;
214    }
215
216    let mut lambda = dot(normal, sub(vertex1, ray_start)) / denominator;
217    if lambda <= 0.0 {
218        return 1.0;
219    }
220
221    lambda = min_float(lambda, 1.0);
222    lambda
223}