solstice_2d/d3/shapes/
polyhedron_geometry.rs

1use crate::d3::{Point3D, Vertex3D};
2use crate::Geometry;
3
4#[derive(Clone, Debug, PartialOrd, PartialEq)]
5pub struct Polyhedron {
6    pub vertices: Vec<Point3D>,
7    pub indices: Vec<u32>,
8    pub radius: f32,
9    pub detail: u32,
10}
11
12impl Polyhedron {
13    pub fn tetrahedron(radius: f32, detail: u32) -> Self {
14        let vertices = vec![
15            Point3D::new(1., 1., 1.),
16            Point3D::new(-1., -1., 1.),
17            Point3D::new(-1., 1., -1.),
18            Point3D::new(1., -1., -1.),
19        ];
20        let indices = vec![2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1];
21        Self {
22            vertices,
23            indices,
24            radius,
25            detail,
26        }
27    }
28
29    pub fn octahedron(radius: f32, detail: u32) -> Self {
30        let vertices = vec![
31            Point3D::new(1., 0., 0.),
32            Point3D::new(-1., 0., 0.),
33            Point3D::new(0., 1., 0.),
34            Point3D::new(0., -1., 0.),
35            Point3D::new(0., 0., 1.),
36            Point3D::new(0., 0., -1.),
37        ];
38
39        let indices = vec![
40            0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2,
41        ];
42
43        Self {
44            vertices,
45            indices,
46            radius,
47            detail,
48        }
49    }
50
51    pub fn icosahedron(radius: f32, detail: u32) -> Self {
52        let t = (1.0 + 5f32.sqrt()) / 2.0;
53
54        let vertices = vec![
55            Point3D::new(-1., t, 0.),
56            Point3D::new(1., t, 0.),
57            Point3D::new(-1., -t, 0.),
58            Point3D::new(1., -t, 0.),
59            Point3D::new(0., -1., t),
60            Point3D::new(0., 1., t),
61            Point3D::new(0., -1., -t),
62            Point3D::new(0., 1., -t),
63            Point3D::new(t, 0., -1.),
64            Point3D::new(t, 0., 1.),
65            Point3D::new(-t, 0., -1.),
66            Point3D::new(-t, 0., 1.),
67        ];
68
69        let indices = vec![
70            0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7,
71            6, 7, 1, 8, 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, 4, 9, 5, 2, 4, 11, 6, 2, 10,
72            8, 6, 7, 9, 8, 1,
73        ];
74
75        Self {
76            vertices,
77            indices,
78            radius,
79            detail,
80        }
81    }
82
83    pub fn dodecahedron(radius: f32, detail: u32) -> Self {
84        let t = (1. + 5f32.sqrt()) / 2.;
85        let r = 1. / t;
86
87        let vertices = vec![
88            // (±1, ±1, ±1)
89            Point3D::new(-1.0, -1.0, -1.0),
90            Point3D::new(-1.0, -1.0, 1.0),
91            Point3D::new(-1.0, 1.0, -1.0),
92            Point3D::new(-1.0, 1.0, 1.0),
93            Point3D::new(1.0, -1.0, -1.0),
94            Point3D::new(1.0, -1.0, 1.0),
95            Point3D::new(1.0, 1.0, -1.0),
96            Point3D::new(1.0, 1.0, 1.0),
97            // (0, ±1/φ, ±φ)
98            Point3D::new(0.0, -r, -t),
99            Point3D::new(0.0, -r, t),
100            Point3D::new(0.0, r, -t),
101            Point3D::new(0.0, r, t),
102            // (±1/φ, ±φ, 0)
103            Point3D::new(-r, -t, 0.0),
104            Point3D::new(-r, t, 0.0),
105            Point3D::new(r, -t, 0.0),
106            Point3D::new(r, t, 0.0),
107            // (±φ, 0, ±1/φ)
108            Point3D::new(-t, 0.0, -r),
109            Point3D::new(t, 0.0, -r),
110            Point3D::new(-t, 0.0, r),
111            Point3D::new(t, 0.0, r),
112        ];
113
114        let indices = vec![
115            3, 11, 7, 3, 7, 15, 3, 15, 13, 7, 19, 17, 7, 17, 6, 7, 6, 15, 17, 4, 8, 17, 8, 10, 17,
116            10, 6, 8, 0, 16, 8, 16, 2, 8, 2, 10, 0, 12, 1, 0, 1, 18, 0, 18, 16, 6, 10, 2, 6, 2, 13,
117            6, 13, 15, 2, 16, 18, 2, 18, 3, 2, 3, 13, 18, 1, 9, 18, 9, 11, 18, 11, 3, 4, 14, 12, 4,
118            12, 0, 4, 0, 8, 11, 9, 5, 11, 5, 19, 11, 19, 7, 19, 5, 14, 19, 14, 4, 19, 4, 17, 1, 12,
119            14, 1, 14, 5, 1, 5, 9,
120        ];
121
122        Self {
123            vertices,
124            indices,
125            radius,
126            detail,
127        }
128    }
129
130    fn vertex_count(&self) -> usize {
131        self.indices.len() * ((self.detail as usize + 1).pow(2))
132    }
133}
134
135fn subdivide(detail: u32, vertices: &mut Vec<Point3D>, indices: &[u32], v: &[Point3D]) {
136    for i in indices.chunks_exact(3) {
137        let a = v[i[0] as usize];
138        let b = v[i[1] as usize];
139        let c = v[i[2] as usize];
140
141        subdivide_face(a, b, c, detail, vertices);
142    }
143}
144
145fn subdivide_face(a: Point3D, b: Point3D, c: Point3D, detail: u32, vertices: &mut Vec<Point3D>) {
146    let cols = (detail + 1) as usize;
147    let mut v = Vec::<Vec<Point3D>>::with_capacity(cols);
148
149    for col in 0..=cols {
150        let aj = a.lerp(&c, col as f32 / cols as f32);
151        let bj = b.lerp(&c, col as f32 / cols as f32);
152
153        let rows = cols - col;
154        v.push(
155            (0..=rows)
156                .map(|row| {
157                    if row == 0 && col == cols {
158                        aj
159                    } else {
160                        aj.lerp(&bj, row as f32 / rows as f32)
161                    }
162                })
163                .collect(),
164        );
165    }
166
167    for col in 0..cols {
168        for row in 0..(2 * (cols - col) - 1) {
169            let k = row / 2; // a flooring division
170            if row % 2 == 0 {
171                vertices.push(v[col][k + 1]);
172                vertices.push(v[col + 1][k]);
173                vertices.push(v[col][k]);
174            } else {
175                vertices.push(v[col][k + 1]);
176                vertices.push(v[col + 1][k + 1]);
177                vertices.push(v[col + 1][k]);
178            }
179        }
180    }
181}
182
183fn apply_radius(radius: f32, vertices: &mut [Point3D]) {
184    for vertex in vertices.iter_mut() {
185        *vertex = vertex.normalize().multiply_scalar(radius);
186    }
187}
188
189fn azimuth(point: &Point3D) -> f32 {
190    point.z.atan2(-point.x)
191}
192
193fn inclination(point: &Point3D) -> f32 {
194    (-point.y).atan2((point.x * point.x + point.z * point.z).sqrt())
195}
196
197fn correct_uvs(vertices: &mut Vec<Vertex3D>) {
198    for triangle in vertices.chunks_exact_mut(3) {
199        let a = triangle[0].position;
200        let b = triangle[1].position;
201        let c = triangle[2].position;
202
203        let cx = a[0] + b[0] + c[0];
204        let cy = a[1] + b[1] + c[1];
205        let cz = a[2] + b[2] + c[2];
206        let centroid = Point3D::new(cx, cy, cz).divide_scalar(3.);
207        let azi = azimuth(&centroid);
208
209        correct_uv(&mut triangle[0].uv[0], &a, azi);
210        correct_uv(&mut triangle[1].uv[0], &b, azi);
211        correct_uv(&mut triangle[2].uv[0], &c, azi);
212    }
213}
214
215fn correct_uv(uv: &mut f32, vector: &[f32; 3], azi: f32) {
216    if (azi < 0.) && (*uv - 1.).abs() < f32::EPSILON {
217        *uv = *uv - 1.;
218    }
219
220    if (vector[0] == 0.) && (vector[2] == 0.) {
221        *uv = azi / 2. / std::f32::consts::PI + 0.5;
222    }
223}
224
225fn correct_seam(vertices: &mut Vec<Vertex3D>) {
226    for triangle in vertices.chunks_exact_mut(3) {
227        let x0 = triangle[0].uv[0];
228        let x1 = triangle[1].uv[0];
229        let x2 = triangle[2].uv[0];
230
231        let max = x0.max(x1.max(x2));
232        let min = x0.min(x1.min(x2));
233
234        if max > 0.9 && min < 0.1 {
235            if x0 < 0.2 {
236                triangle[0].uv[0] += 1.;
237            }
238            if x1 < 0.2 {
239                triangle[1].uv[0] += 1.;
240            }
241            if x2 < 0.2 {
242                triangle[2].uv[0] += 1.;
243            }
244        }
245    }
246}
247
248impl From<&Polyhedron> for Geometry<'_, Vertex3D> {
249    fn from(p: &Polyhedron) -> Self {
250        let mut vertices = Vec::with_capacity(p.vertex_count());
251
252        subdivide(
253            p.detail,
254            &mut vertices,
255            p.indices.as_slice(),
256            p.vertices.as_slice(),
257        );
258        apply_radius(p.radius, vertices.as_mut_slice());
259
260        let mut vertices = vertices
261            .into_iter()
262            .map(|p| {
263                let u = azimuth(&p) / 2. / std::f32::consts::PI + 0.5;
264                let v = inclination(&p) / std::f32::consts::PI + 0.5;
265                let normal = p.normalize();
266                Vertex3D {
267                    position: [p.x, p.y, p.z],
268                    uv: [u, v],
269                    color: [1., 1., 1., 1.],
270                    normal: [normal.x, normal.y, normal.z],
271                }
272            })
273            .collect::<Vec<_>>();
274
275        correct_uvs(&mut vertices);
276        correct_seam(&mut vertices);
277
278        Self::new::<_, Vec<_>>(vertices, None)
279    }
280}
281
282impl From<Polyhedron> for Geometry<'_, Vertex3D> {
283    fn from(p: Polyhedron) -> Self {
284        (&p).into()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn subdivision_count() {
294        for detail in 0..10 {
295            let shape = Polyhedron::tetrahedron(1., detail);
296
297            assert_eq!(12 * ((detail as usize + 1).pow(2)), shape.vertex_count());
298        }
299    }
300}