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 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 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 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 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; 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(¢roid);
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}