1const TEX_DEFAULT: &str = "mtrl/invisible";
7const ALMOST_EQUAL_DELTA: f64 = 0.000000001;
8use core::fmt;
9use glam::DVec3;
10use std::fmt::{Display, Formatter};
11
12#[derive(Debug, Default, Clone, Copy, PartialEq)]
14pub struct SideGeom(pub [DVec3; 3]);
15impl SideGeom {
16 pub fn normal(self) -> Option<DVec3> {
17 let Self([p0, p1, p2]) = self;
18 ((p0 - p1).cross(p2 - p1)).try_normalize()
19 }
20 pub fn dist(self) -> Option<f64> {
21 let Self([_p0, p1, _p2]) = self;
22 Some(self.normal()?.dot(p1))
23 }
24 pub fn equivalent(self, other: SideGeom) -> bool {
25 let Some(normal) = self.normal() else {
26 return false;
27 };
28 let Some(other_normal) = other.normal() else {
29 return false;
30 };
31 if normal.dot(other_normal) < 1.0 - ALMOST_EQUAL_DELTA {
32 return false;
33 }
34
35 let Some(dist) = self.dist() else {
36 return false;
37 };
38 let Some(other_dist) = other.dist() else {
39 return false;
40 };
41 if (dist - other_dist) > ALMOST_EQUAL_DELTA {
42 return false;
43 }
44 true
45 }
46}
47
48#[derive(Debug, Clone, PartialEq)]
55pub struct SideMtrl {
56 pub texture: String,
57}
58
59impl Default for SideMtrl {
60 fn default() -> Self {
61 Self {
62 texture: String::from(TEX_DEFAULT),
63 }
64 }
65}
66
67#[derive(Debug, Default, Clone, PartialEq)]
69pub struct Side {
70 pub geom: SideGeom,
71 pub mtrl: SideMtrl,
72}
73
74impl Side {
75 pub(crate) fn bake(&self) -> impl Display + use<'_> {
76 struct SideDisplay<'a>(&'a Side);
77 impl Display for SideDisplay<'_> {
78 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
79 write!(
80 f,
81 "( {:.6} {:.6} {:.6} ) ( {:.6} {:.6} {:.6} ) ( {:.6} {:.6} {:.6} ) {} 0 0 0 0.5 0.5 0 0 0",
82 self.0.geom.0[0][0],
83 self.0.geom.0[0][1],
84 self.0.geom.0[0][2],
85 self.0.geom.0[1][0],
86 self.0.geom.0[1][1],
87 self.0.geom.0[1][2],
88 self.0.geom.0[2][0],
89 self.0.geom.0[2][1],
90 self.0.geom.0[2][2],
91 self.0.mtrl.texture
92 )
93 }
94 }
95 SideDisplay(self)
96 }
97}
98
99use chull::ConvexHullWrapper;
100#[derive(Debug, Clone)]
103pub struct Brush {
104 vertices: Vec<DVec3>,
105 sides: Vec<([usize; 3], SideMtrl)>, }
107
108impl Brush {
109 pub fn try_from_vertices(
110 vertices: &[DVec3],
111 max_iter: Option<usize>,
112 ) -> Result<Self, chull::convex::ErrorKind> {
113 let vertices: Vec<Vec<f64>> = vertices
114 .iter()
115 .map(|vertex| vec![vertex.x, vertex.y, vertex.z])
116 .collect();
117
118 let hull = ConvexHullWrapper::try_new(&vertices, max_iter)?;
119
120 Ok(hull.into())
121 }
122
123 pub fn side_vertex_indices(&self) -> (&Vec<DVec3>, &Vec<([usize; 3], SideMtrl)>) {
124 (&self.vertices, &self.sides)
125 }
126
127 pub fn triangles(&self) -> impl Iterator<Item = Side> + use<'_> {
128 self.sides.iter().map(|([idx0, idx1, idx2], mtrl)| Side {
129 geom: SideGeom([
130 self.vertices[*idx0],
131 self.vertices[*idx1],
132 self.vertices[*idx2],
133 ]),
134 mtrl: mtrl.clone(),
135 })
136 }
137
138 pub fn to_sides_unique(&self) -> Vec<Side> {
139 let mut result: Vec<_> = self.triangles().collect();
140
141 let keep: Vec<_> = result
142 .iter()
143 .enumerate()
144 .map(|(i, candidate)| {
145 !result[0..i]
146 .iter()
147 .any(|so_far| SideGeom::equivalent(so_far.geom, candidate.geom))
148 })
149 .collect();
150
151 let mut keep_iter = keep.iter();
152
153 result.retain(|_| *keep_iter.next().unwrap());
155 result
156 }
157
158 pub fn vertices(&self) -> &Vec<DVec3> {
159 &self.vertices
160 }
161}
162
163#[allow(clippy::get_first)]
164impl From<ConvexHullWrapper<f64>> for Brush {
165 fn from(hull: ConvexHullWrapper<f64>) -> Self {
166 let (vertices, side_indices) = hull.vertices_indices();
167
168 let vertices: Vec<DVec3> = vertices
169 .into_iter()
170 .map(|vertex| DVec3 {
171 x: *vertex
172 .get(0)
173 .expect("vertices expected to have three components"),
174 y: *vertex
175 .get(1)
176 .expect("vertices expected to have three components"),
177 z: *vertex
178 .get(2)
179 .expect("vertices expected to have three components"),
180 })
181 .collect();
182
183 #[cfg(debug_assertions)]
185 assert_eq!(side_indices.len() % 3, 0);
186
187 use itertools::Itertools;
188 let sides = side_indices
189 .into_iter()
190 .tuples()
191 .map(|(i0, i1, i2)| (i1, i0, i2)) .map(|(i0, i1, i2)| ([i0, i1, i2], SideMtrl::default()))
193 .collect();
194
195 Self { vertices, sides }
196 }
197}
198
199impl Brush {
200 pub(crate) fn bake(&self) -> impl Display + use<'_> {
201 struct BrushDisp<'a>(&'a Brush);
202 impl Display for BrushDisp<'_> {
203 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
204 writeln!(f, "{{",)?;
205 for side in self.0.to_sides_unique().iter() {
206 writeln!(f, "{}", side.bake())?;
207 }
208 writeln!(f, "}}")?;
209 Ok(())
210 }
211 }
212 BrushDisp(self)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn almost_equals(a: &DVec3, b: &DVec3) -> bool {
221 const EPSILON: f64 = 0.000000001;
222 (a.x - b.x).abs() < EPSILON && (a.y - b.y).abs() < EPSILON && (a.z - b.z).abs() < EPSILON
223 }
224
225 #[test]
226 fn test_brush_cube() {
227 let vertices = vec![
228 DVec3::from([0.0, 0.0, 0.0]),
229 DVec3::from([0.0, 0.0, 1.0]),
230 DVec3::from([0.0, 1.0, 0.0]),
231 DVec3::from([0.0, 1.0, 1.0]),
232 DVec3::from([1.0, 0.0, 0.0]),
233 DVec3::from([1.0, 0.0, 1.0]),
234 DVec3::from([1.0, 1.0, 0.0]),
235 DVec3::from([1.0, 1.0, 1.0]),
236 DVec3::from([0.5, 0.5, 0.5]),
237 ];
238
239 let brush = Brush::try_from_vertices(&vertices, Some(1000)).unwrap();
240
241 let extracted_vertices: &Vec<DVec3> = brush.vertices();
242 let extracted_sides: Vec<Side> = brush.to_sides_unique();
243
244 assert_eq!(extracted_vertices.len(), 8);
245 assert_eq!(extracted_sides.len(), 6);
246 assert!(
247 extracted_vertices
248 .iter()
249 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 0.0, 0.0])))
250 );
251 assert!(
252 extracted_vertices
253 .iter()
254 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 0.0, 1.0])))
255 );
256 assert!(
257 extracted_vertices
258 .iter()
259 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 1.0, 0.0])))
260 );
261 assert!(
262 extracted_vertices
263 .iter()
264 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 1.0, 1.0])))
265 );
266 assert!(
267 extracted_vertices
268 .iter()
269 .any(|vertex| almost_equals(vertex, &DVec3::from([1.0, 0.0, 0.0])))
270 );
271 assert!(
272 extracted_vertices
273 .iter()
274 .any(|vertex| almost_equals(vertex, &DVec3::from([1.0, 0.0, 1.0])))
275 );
276 assert!(
277 extracted_vertices
278 .iter()
279 .any(|vertex| almost_equals(vertex, &DVec3::from([1.0, 1.0, 0.0])))
280 );
281 assert!(
282 extracted_vertices
283 .iter()
284 .any(|vertex| almost_equals(vertex, &DVec3::from([1.0, 1.0, 1.0])))
285 );
286 }
287
288 #[test]
289 fn test_brush_pyramid() {
290 let vertices = vec![
291 DVec3::from([0.0, 0.0, 0.0]),
292 DVec3::from([0.0, 0.0, 1.0]),
293 DVec3::from([0.0, 1.0, 0.0]),
294 DVec3::from([1.0, 0.0, 0.0]),
295 DVec3::from([0.3, 0.3, 0.3]),
296 ];
297
298 let brush = Brush::try_from_vertices(&vertices, Some(1000)).unwrap();
299
300 let extracted_vertices: &Vec<DVec3> = brush.vertices();
301 let extracted_sides: Vec<Side> = brush.to_sides_unique();
302
303 assert_eq!(extracted_vertices.len(), 4);
304 assert_eq!(extracted_sides.len(), 4);
305
306 assert!(
307 extracted_vertices
308 .iter()
309 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 0.0, 0.0])))
310 );
311 assert!(
312 extracted_vertices
313 .iter()
314 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 0.0, 1.0])))
315 );
316 assert!(
317 extracted_vertices
318 .iter()
319 .any(|vertex| almost_equals(vertex, &DVec3::from([0.0, 1.0, 0.0])))
320 );
321 assert!(
322 extracted_vertices
323 .iter()
324 .any(|vertex| almost_equals(vertex, &DVec3::from([1.0, 0.0, 0.0])))
325 );
326 }
327
328 #[test]
329 fn bake_side() {
330 let testvertex1 = DVec3 {
331 x: 1.0,
332 y: 2.0,
333 z: 3.0,
334 };
335
336 let testvertex2 = DVec3 {
337 x: 10.0,
338 y: 20.0,
339 z: 30.0,
340 };
341
342 let testvertex3 = DVec3 {
343 x: 100.0,
344 y: 200.0,
345 z: 300.0,
346 };
347
348 let side = Side {
349 geom: SideGeom([testvertex1, testvertex2, testvertex3]),
350 mtrl: SideMtrl::default(),
351 };
352
353 assert_eq!(
354 format!("{}", side.bake()),
355 "( 1.000000 2.000000 3.000000 ) ( 10.000000 20.000000 30.000000 ) ( 100.000000 200.000000 300.000000 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0"
356 );
357 }
358
359 #[test]
360 fn bake_brush_pyramid() {
361 let vertices = vec![
362 DVec3::from([0.0, 0.0, 0.0]),
363 DVec3::from([0.0, 0.0, 1.0]),
364 DVec3::from([0.0, 1.0, 0.0]),
365 DVec3::from([1.0, 0.0, 0.0]),
366 DVec3::from([0.3, 0.3, 0.3]),
367 ];
368
369 let brush = Brush::try_from_vertices(&vertices, Some(1000)).unwrap();
370
371 let should_eq_str = r"{
372( 0.000000 0.000000 0.000000 ) ( 0.000000 1.000000 0.000000 ) ( 0.000000 0.000000 1.000000 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
373( 0.000000 1.000000 0.000000 ) ( 1.000000 0.000000 0.000000 ) ( 0.000000 0.000000 1.000000 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
374( 1.000000 0.000000 0.000000 ) ( 0.000000 0.000000 0.000000 ) ( 0.000000 0.000000 1.000000 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
375( 0.000000 0.000000 0.000000 ) ( 1.000000 0.000000 0.000000 ) ( 0.000000 1.000000 0.000000 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
376}
377";
378 println!("{}", brush.bake());
379 println!("{}", should_eq_str);
380 assert_eq!(format!("{}", brush.bake()), should_eq_str);
381 }
382}