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}