chull_adapt/
quick_convex_hull.rs

1// Copyright (C) 2020-2025 phys-convex-hull authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![cfg(any(
16    target_arch = "wasm32",
17    all(not(feature = "qhull"), feature = "quick_convex_hull")
18))]
19
20use glam_det::Point3;
21use log::warn;
22pub use qchull::Config;
23use qchull::{
24    build_convex_hull, ConvexHull as QuickConvexHull,
25    ConvexHullConstructError as QuickConvexHullConstructError,
26    ConvexHullConstructState as QuickConvexHullConstructState,
27};
28
29use crate::{
30    ConvexHull, ConvexHullConstructError, ConvexHullConstructState, ConvexMesh, HalfPlane,
31};
32
33impl ConvexHull {
34    pub fn new(
35        points: &[Point3],
36        config: &Config,
37    ) -> Result<(ConvexHull, ConvexHullConstructState), ConvexHullConstructError> {
38        let result = build_convex_hull(points, config);
39        match result {
40            Ok((hull, state)) => Ok((hull.into(), state.into())),
41            Err(e) => Err(e.into()),
42        }
43    }
44}
45
46impl From<QuickConvexHullConstructState> for ConvexHullConstructState {
47    fn from(state: QuickConvexHullConstructState) -> Self {
48        match state {
49            QuickConvexHullConstructState::Success => ConvexHullConstructState::Success,
50            QuickConvexHullConstructState::PointCountLessThanFour => {
51                ConvexHullConstructState::PointCountLessThanFour
52            }
53            QuickConvexHullConstructState::VolumeTooSmall => {
54                ConvexHullConstructState::VolumeTooSmall
55            }
56            QuickConvexHullConstructState::PointCountOverflow => {
57                ConvexHullConstructState::PointCountOverflow
58            }
59            QuickConvexHullConstructState::FaceCountLimitHit => {
60                ConvexHullConstructState::FaceCountLimitHit
61            }
62        }
63    }
64}
65
66impl From<QuickConvexHullConstructError> for ConvexHullConstructError {
67    fn from(error: QuickConvexHullConstructError) -> Self {
68        match error {
69            QuickConvexHullConstructError::Degenerate => ConvexHullConstructError::Degenerate,
70        }
71    }
72}
73
74impl From<QuickConvexHull> for crate::ConvexHull {
75    fn from(convex_hull: QuickConvexHull) -> Self {
76        let planes: Vec<_> = convex_hull
77            .bounding_planes
78            .iter()
79            .map(|v| HalfPlane {
80                normal: v.normal,
81                offset: v.offset,
82                center: v.center,
83            })
84            .collect();
85
86        crate::ConvexHull {
87            points: convex_hull.points,
88            bounding_planes: planes,
89            face_indices_start: convex_hull.face_indices_start,
90            face_vertex_indices: convex_hull.face_vertex_indices,
91            center: convex_hull.center,
92            volume: convex_hull.volume,
93            total_area: 1.0,
94            shift_center: convex_hull.shift_center,
95            debug: None,
96        }
97    }
98}
99
100impl From<qchull::ConvexMesh> for crate::ConvexMesh {
101    fn from(v: qchull::ConvexMesh) -> Self {
102        crate::ConvexMesh {
103            vertices: (v.vertices().to_vec()),
104            triangles: v.triangles().to_vec(),
105        }
106    }
107}
108
109impl crate::ConvexMesh {
110    pub fn new(
111        points: &[Point3],
112    ) -> Result<(crate::ConvexMesh, ConvexHullConstructState), ConvexHullConstructError> {
113        let mut config = Config::default();
114        config.set_shift_point_align_aabb_center(false);
115        let (hull, state) = ConvexHull::new(points, &config)?;
116        if state != ConvexHullConstructState::Success {
117            warn!("ConvexMesh state mismatch");
118        }
119        let iter = hull.triangle_index_iter();
120        let triangles = iter
121            .flat_map(|v| [v[0] as u32, v[1] as u32, v[2] as u32])
122            .collect::<Vec<_>>();
123        let vertices = hull.points;
124        Ok((
125            ConvexMesh {
126                vertices,
127                triangles,
128            },
129            state,
130        ))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use glam_det::Point3;
137    use qchull::{
138        ConvexHullConstructError as QuickConvexHullConstructError,
139        ConvexHullConstructState as QuickConvexHullConstructState,
140    };
141    use wasm_bindgen_test::*;
142
143    use crate::{Config, ConvexHullConstructError, ConvexHullConstructState};
144    wasm_bindgen_test_configure!(run_in_browser);
145
146    #[test]
147    fn test_build_convex_face() {
148        let points = vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 0.1)];
149
150        let config = Config::default();
151        let result = crate::ConvexHull::new(&points, &config);
152        let (_hull, state) = result.unwrap();
153        assert_eq!(state, ConvexHullConstructState::PointCountLessThanFour);
154
155        // Test with a cube
156        let points = vec![
157            Point3::new(0.0, 0.0, 0.0),
158            Point3::new(1.0, 0.0, 0.0),
159            Point3::new(0.0, 1.0, 0.0),
160            Point3::new(0.0, 0.0, 1.0),
161            Point3::new(1.0, 1.0, 0.0),
162            Point3::new(1.0, 0.0, 1.0),
163            Point3::new(0.0, 1.0, 1.0),
164            Point3::new(1.0, 1.0, 1.0),
165        ];
166        let config = Config::default();
167        let result = crate::ConvexHull::new(&points, &config);
168        let (_hull, state) = result.unwrap();
169        assert_eq!(state, ConvexHullConstructState::Success);
170    }
171    #[test]
172    fn test_from_quick_convex_hull_construct_state() {
173        // Test all variants of QuickConvexHullConstructState
174        let quick_success = QuickConvexHullConstructState::Success;
175        let success: ConvexHullConstructState = quick_success.into();
176        assert_eq!(success, ConvexHullConstructState::Success);
177
178        let quick_point_count_less = QuickConvexHullConstructState::PointCountLessThanFour;
179        let point_count_less: ConvexHullConstructState = quick_point_count_less.into();
180        assert_eq!(
181            point_count_less,
182            ConvexHullConstructState::PointCountLessThanFour
183        );
184
185        let quick_volume_too_small = QuickConvexHullConstructState::VolumeTooSmall;
186        let volume_too_small: ConvexHullConstructState = quick_volume_too_small.into();
187        assert_eq!(volume_too_small, ConvexHullConstructState::VolumeTooSmall);
188
189        let quick_point_count_overflow = QuickConvexHullConstructState::PointCountOverflow;
190        let point_count_overflow: ConvexHullConstructState = quick_point_count_overflow.into();
191        assert_eq!(
192            point_count_overflow,
193            ConvexHullConstructState::PointCountOverflow
194        );
195
196        let quick_face_count_limit_hit = QuickConvexHullConstructState::FaceCountLimitHit;
197        let face_count_limit_hit: ConvexHullConstructState = quick_face_count_limit_hit.into();
198        assert_eq!(
199            face_count_limit_hit,
200            ConvexHullConstructState::FaceCountLimitHit
201        );
202    }
203
204    #[test]
205    fn test_from_quick_convex_hull_construct_error() {
206        // Test the Degenerate variant
207        let quick_degenerate = QuickConvexHullConstructError::Degenerate;
208        let degenerate: ConvexHullConstructError = quick_degenerate.into();
209        assert_eq!(degenerate, ConvexHullConstructError::Degenerate);
210    }
211}