scirs2_spatial/convex_hull/geometry/
calculations_3d.rs

1//! 3D geometric calculations for convex hull operations
2//!
3//! This module provides utility functions for 3D geometric computations
4//! commonly used in convex hull algorithms.
5
6use crate::error::SpatialResult;
7use scirs2_core::ndarray::ArrayView2;
8
9/// Compute the signed volume of a tetrahedron
10///
11/// # Arguments
12///
13/// * `p0` - First vertex [x, y, z]
14/// * `p1` - Second vertex [x, y, z]  
15/// * `p2` - Third vertex [x, y, z]
16/// * `p3` - Fourth vertex [x, y, z]
17///
18/// # Returns
19///
20/// * Signed volume of tetrahedron formed by the four points
21///
22/// # Examples
23///
24/// ```
25/// use scirs2_spatial::convex_hull::geometry::calculations_3d::tetrahedron_volume;
26///
27/// let p0 = [0.0, 0.0, 0.0];
28/// let p1 = [1.0, 0.0, 0.0];
29/// let p2 = [0.0, 1.0, 0.0];
30/// let p3 = [0.0, 0.0, 1.0];
31///
32/// let volume = tetrahedron_volume(p0, p1, p2, p3);
33/// assert!((volume - 1.0/6.0).abs() < 1e-10);
34/// ```
35pub fn tetrahedron_volume(p0: [f64; 3], p1: [f64; 3], p2: [f64; 3], p3: [f64; 3]) -> f64 {
36    let v1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
37    let v2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
38    let v3 = [p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2]];
39
40    // Compute scalar triple product: v1 · (v2 × v3)
41    let cross = [
42        v2[1] * v3[2] - v2[2] * v3[1],
43        v2[2] * v3[0] - v2[0] * v3[2],
44        v2[0] * v3[1] - v2[1] * v3[0],
45    ];
46
47    (v1[0] * cross[0] + v1[1] * cross[1] + v1[2] * cross[2]).abs() / 6.0
48}
49
50/// Compute the area of a 3D triangle
51///
52/// # Arguments
53///
54/// * `p0` - First vertex [x, y, z]
55/// * `p1` - Second vertex [x, y, z]
56/// * `p2` - Third vertex [x, y, z]
57///
58/// # Returns
59///
60/// * Area of the triangle in 3D space
61///
62/// # Examples
63///
64/// ```
65/// use scirs2_spatial::convex_hull::geometry::calculations_3d::triangle_area_3d;
66///
67/// let p0 = [0.0, 0.0, 0.0];
68/// let p1 = [1.0, 0.0, 0.0];
69/// let p2 = [0.0, 1.0, 0.0];
70///
71/// let area = triangle_area_3d(p0, p1, p2);
72/// assert!((area - 0.5).abs() < 1e-10);
73/// ```
74pub fn triangle_area_3d(p0: [f64; 3], p1: [f64; 3], p2: [f64; 3]) -> f64 {
75    let v1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
76    let v2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
77
78    // Compute cross product v1 × v2
79    let cross = [
80        v1[1] * v2[2] - v1[2] * v2[1],
81        v1[2] * v2[0] - v1[0] * v2[2],
82        v1[0] * v2[1] - v1[1] * v2[0],
83    ];
84
85    // Magnitude of cross product gives twice the triangle area
86    let magnitude = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
87    magnitude / 2.0
88}
89
90/// Compute the volume of a 3D polyhedron using triangulation from centroid
91///
92/// # Arguments
93///
94/// * `points` - Input points array
95/// * `simplices` - Triangular faces of the polyhedron
96///
97/// # Returns
98///
99/// * Result containing the polyhedron volume
100///
101/// # Examples
102///
103/// ```
104/// use scirs2_spatial::convex_hull::geometry::calculations_3d::compute_polyhedron_volume;
105/// use scirs2_core::ndarray::array;
106///
107/// // Unit tetrahedron
108/// let points = array![
109///     [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]
110/// ];
111/// let simplices = vec![
112///     vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]
113/// ];
114///
115/// let volume = compute_polyhedron_volume(&points.view(), &simplices).unwrap();
116/// assert!(volume > 0.0);
117/// ```
118pub fn compute_polyhedron_volume(
119    points: &ArrayView2<'_, f64>,
120    simplices: &[Vec<usize>],
121) -> SpatialResult<f64> {
122    if simplices.is_empty() {
123        return Ok(0.0);
124    }
125
126    // Compute the centroid of all vertices
127    let vertex_indices: std::collections::HashSet<usize> =
128        simplices.iter().flat_map(|s| s.iter()).cloned().collect();
129
130    let mut centroid = [0.0, 0.0, 0.0];
131    for &idx in &vertex_indices {
132        centroid[0] += points[[idx, 0]];
133        centroid[1] += points[[idx, 1]];
134        centroid[2] += points[[idx, 2]];
135    }
136    let n = vertex_indices.len() as f64;
137    centroid[0] /= n;
138    centroid[1] /= n;
139    centroid[2] /= n;
140
141    let mut total_volume = 0.0;
142
143    // For each triangular face, form a tetrahedron with the centroid
144    for simplex in simplices {
145        if simplex.len() != 3 {
146            continue; // Skip non-triangular faces
147        }
148
149        let p0 = [
150            points[[simplex[0], 0]],
151            points[[simplex[0], 1]],
152            points[[simplex[0], 2]],
153        ];
154        let p1 = [
155            points[[simplex[1], 0]],
156            points[[simplex[1], 1]],
157            points[[simplex[1], 2]],
158        ];
159        let p2 = [
160            points[[simplex[2], 0]],
161            points[[simplex[2], 1]],
162            points[[simplex[2], 2]],
163        ];
164
165        // Compute the volume of the tetrahedron formed by centroid, p0, p1, p2
166        let tet_volume = tetrahedron_volume(centroid, p0, p1, p2);
167        total_volume += tet_volume.abs();
168    }
169
170    Ok(total_volume)
171}
172
173/// Compute the surface area of a 3D polyhedron
174///
175/// # Arguments
176///
177/// * `points` - Input points array
178/// * `simplices` - Triangular faces of the polyhedron
179///
180/// # Returns
181///
182/// * Result containing the polyhedron surface area
183///
184/// # Examples
185///
186/// ```
187/// use scirs2_spatial::convex_hull::geometry::calculations_3d::compute_polyhedron_surface_area;
188/// use scirs2_core::ndarray::array;
189///
190/// // Unit tetrahedron
191/// let points = array![
192///     [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]
193/// ];
194/// let simplices = vec![
195///     vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]
196/// ];
197///
198/// let surface_area = compute_polyhedron_surface_area(&points.view(), &simplices).unwrap();
199/// assert!(surface_area > 0.0);
200/// ```
201pub fn compute_polyhedron_surface_area(
202    points: &ArrayView2<'_, f64>,
203    simplices: &[Vec<usize>],
204) -> SpatialResult<f64> {
205    if simplices.is_empty() {
206        return Ok(0.0);
207    }
208
209    let mut surface_area = 0.0;
210
211    // For each triangular face, compute its area
212    for simplex in simplices {
213        if simplex.len() != 3 {
214            continue; // Skip non-triangular faces
215        }
216
217        let p0 = [
218            points[[simplex[0], 0]],
219            points[[simplex[0], 1]],
220            points[[simplex[0], 2]],
221        ];
222        let p1 = [
223            points[[simplex[1], 0]],
224            points[[simplex[1], 1]],
225            points[[simplex[1], 2]],
226        ];
227        let p2 = [
228            points[[simplex[2], 0]],
229            points[[simplex[2], 1]],
230            points[[simplex[2], 2]],
231        ];
232
233        let area = triangle_area_3d(p0, p1, p2);
234        surface_area += area;
235    }
236
237    Ok(surface_area)
238}
239
240/// Compute cross product of two 3D vectors
241///
242/// # Arguments
243///
244/// * `v1` - First vector [x, y, z]
245/// * `v2` - Second vector [x, y, z]
246///
247/// # Returns
248///
249/// * Cross product v1 × v2 as [x, y, z]
250///
251/// # Examples
252///
253/// ```
254/// use scirs2_spatial::convex_hull::geometry::calculations_3d::cross_product_3d;
255///
256/// let v1 = [1.0, 0.0, 0.0];
257/// let v2 = [0.0, 1.0, 0.0];
258///
259/// let cross = cross_product_3d(v1, v2);
260/// assert_eq!(cross, [0.0, 0.0, 1.0]);
261/// ```
262pub fn cross_product_3d(v1: [f64; 3], v2: [f64; 3]) -> [f64; 3] {
263    [
264        v1[1] * v2[2] - v1[2] * v2[1],
265        v1[2] * v2[0] - v1[0] * v2[2],
266        v1[0] * v2[1] - v1[1] * v2[0],
267    ]
268}
269
270/// Compute dot product of two 3D vectors
271///
272/// # Arguments
273///
274/// * `v1` - First vector [x, y, z]
275/// * `v2` - Second vector [x, y, z]
276///
277/// # Returns
278///
279/// * Dot product v1 · v2
280///
281/// # Examples
282///
283/// ```
284/// use scirs2_spatial::convex_hull::geometry::calculations_3d::dot_product_3d;
285///
286/// let v1 = [1.0, 2.0, 3.0];
287/// let v2 = [4.0, 5.0, 6.0];
288///
289/// let dot = dot_product_3d(v1, v2);
290/// assert_eq!(dot, 32.0); // 1*4 + 2*5 + 3*6 = 32
291/// ```
292pub fn dot_product_3d(v1: [f64; 3], v2: [f64; 3]) -> f64 {
293    v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
294}
295
296/// Compute the magnitude of a 3D vector
297///
298/// # Arguments
299///
300/// * `v` - Vector [x, y, z]
301///
302/// # Returns
303///
304/// * Magnitude (length) of the vector
305///
306/// # Examples
307///
308/// ```
309/// use scirs2_spatial::convex_hull::geometry::calculations_3d::vector_magnitude_3d;
310///
311/// let v = [3.0, 4.0, 0.0];
312/// let magnitude = vector_magnitude_3d(v);
313/// assert_eq!(magnitude, 5.0);
314/// ```
315pub fn vector_magnitude_3d(v: [f64; 3]) -> f64 {
316    (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
317}
318
319/// Normalize a 3D vector
320///
321/// # Arguments
322///
323/// * `v` - Vector to normalize [x, y, z]
324///
325/// # Returns
326///
327/// * Normalized vector, or zero vector if input has zero magnitude
328///
329/// # Examples
330///
331/// ```
332/// use scirs2_spatial::convex_hull::geometry::calculations_3d::normalize_3d;
333///
334/// let v = [3.0, 4.0, 0.0];
335/// let normalized = normalize_3d(v);
336/// assert_eq!(normalized, [0.6, 0.8, 0.0]);
337/// ```
338pub fn normalize_3d(v: [f64; 3]) -> [f64; 3] {
339    let magnitude = vector_magnitude_3d(v);
340    if magnitude < 1e-12 {
341        [0.0, 0.0, 0.0]
342    } else {
343        [v[0] / magnitude, v[1] / magnitude, v[2] / magnitude]
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use scirs2_core::ndarray::arr2;
351
352    #[test]
353    fn test_tetrahedron_volume() {
354        let p0 = [0.0, 0.0, 0.0];
355        let p1 = [1.0, 0.0, 0.0];
356        let p2 = [0.0, 1.0, 0.0];
357        let p3 = [0.0, 0.0, 1.0];
358
359        let volume = tetrahedron_volume(p0, p1, p2, p3);
360        assert!((volume - 1.0 / 6.0).abs() < 1e-10);
361    }
362
363    #[test]
364    fn test_triangle_area_3d() {
365        let p0 = [0.0, 0.0, 0.0];
366        let p1 = [1.0, 0.0, 0.0];
367        let p2 = [0.0, 1.0, 0.0];
368
369        let area = triangle_area_3d(p0, p1, p2);
370        assert!((area - 0.5).abs() < 1e-10);
371    }
372
373    #[test]
374    fn test_cross_product_3d() {
375        let v1 = [1.0, 0.0, 0.0];
376        let v2 = [0.0, 1.0, 0.0];
377
378        let cross = cross_product_3d(v1, v2);
379        assert_eq!(cross, [0.0, 0.0, 1.0]);
380    }
381
382    #[test]
383    fn test_dot_product_3d() {
384        let v1 = [1.0, 2.0, 3.0];
385        let v2 = [4.0, 5.0, 6.0];
386
387        let dot = dot_product_3d(v1, v2);
388        assert_eq!(dot, 32.0);
389    }
390
391    #[test]
392    fn test_vector_magnitude_3d() {
393        let v = [3.0, 4.0, 0.0];
394        let magnitude = vector_magnitude_3d(v);
395        assert_eq!(magnitude, 5.0);
396    }
397
398    #[test]
399    fn test_normalize_3d() {
400        let v = [3.0, 4.0, 0.0];
401        let normalized = normalize_3d(v);
402        assert!((normalized[0] - 0.6).abs() < 1e-10);
403        assert!((normalized[1] - 0.8).abs() < 1e-10);
404        assert_eq!(normalized[2], 0.0);
405    }
406
407    #[test]
408    fn test_polyhedron_volume() {
409        // Unit tetrahedron
410        let points = arr2(&[
411            [0.0, 0.0, 0.0],
412            [1.0, 0.0, 0.0],
413            [0.0, 1.0, 0.0],
414            [0.0, 0.0, 1.0],
415        ]);
416        let simplices = vec![vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]];
417
418        let volume = compute_polyhedron_volume(&points.view(), &simplices).unwrap();
419        assert!(volume > 0.0);
420        // Unit tetrahedron volume should be 1/6
421        assert!((volume - 1.0 / 6.0).abs() < 0.1); // Allow some numerical error
422    }
423
424    #[test]
425    fn test_polyhedron_surface_area() {
426        // Unit tetrahedron
427        let points = arr2(&[
428            [0.0, 0.0, 0.0],
429            [1.0, 0.0, 0.0],
430            [0.0, 1.0, 0.0],
431            [0.0, 0.0, 1.0],
432        ]);
433        let simplices = vec![vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]];
434
435        let surface_area = compute_polyhedron_surface_area(&points.view(), &simplices).unwrap();
436        assert!(surface_area > 0.0);
437    }
438}