1use crate::math::vec;
3
4#[repr(u8)]
7#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
8pub enum BaseTriSphere {
9 #[default]
11 Icosa = 0,
12 Octo = 1,
14 Tetra = 2,
16}
17
18impl BaseTriSphere {
19 pub const fn vertex_degree(self) -> usize {
21 self.lookup::<5, 4, 3>() as usize
22 }
23
24 pub const fn edge_length(self) -> f64 {
27 [1.1071487177940904, std::f64::consts::FRAC_PI_2][self as usize]
28 }
29
30 pub const fn edge_cos_length(self) -> f64 {
33 [C_1, 0.0][self as usize]
34 }
35
36 pub(crate) const fn first_face_inner(self) -> u8 {
38 self.lookup::<0, 20, 28>()
39 }
40
41 pub(crate) const fn last_face_inner(self) -> u8 {
43 self.lookup::<20, 28, 32>()
44 }
45
46 pub(crate) const fn first_vertex_inner(self) -> u8 {
48 self.lookup::<0, 12, 18>()
49 }
50
51 pub(crate) const fn last_vertex_inner(self) -> u8 {
53 self.lookup::<12, 18, 22>()
54 }
55
56 const fn lookup<const ICOSA: u8, const OCTO: u8, const TETRA: u8>(self) -> u8 {
58 ((((TETRA as u32) << 16) | ((OCTO as u32) << 8) | ICOSA as u32) >> (self as u32 * 8)) as u8
59 }
60
61 pub const fn face_at(self, point: [f64; 3]) -> Face {
63 match self {
64 BaseTriSphere::Icosa => {
65 let index = icosa_point_index(point);
66 ICOSA_FACE_AT[index as usize]
67 }
68 BaseTriSphere::Octo => {
69 let index = octo_point_index(point);
70 Face((OCTO_FACE_AT >> (index * 8)) as u8)
71 }
72 BaseTriSphere::Tetra => todo!(),
73 }
74 }
75
76 pub const fn num_vertices(self) -> usize {
78 self.lookup::<12, 6, 4>() as usize
79 }
80
81 pub const fn vertex(self, index: usize) -> Vertex {
83 assert!(index < self.num_vertices(), "index out of bounds");
84 Vertex(self.first_vertex_inner() + index as u8)
85 }
86}
87
88impl crate::Sphere for BaseTriSphere {
89 type Face = Face;
90 type Vertex = Vertex;
91 type HalfEdge = HalfEdge;
92
93 fn num_faces(&self) -> usize {
94 self.lookup::<20, 8, 4>() as usize
95 }
96
97 fn face(&self, index: usize) -> Face {
98 assert!(index < self.num_faces(), "index out of bounds");
99 Face(self.first_face_inner() + index as u8)
100 }
101
102 fn faces(&self) -> impl Iterator<Item = Face> {
103 (self.first_face_inner()..self.last_face_inner()).map(Face)
104 }
105
106 fn face_at(&self, point: [f64; 3]) -> Face {
107 (*self).face_at(point)
108 }
109
110 fn num_vertices(&self) -> usize {
111 (*self).num_vertices()
112 }
113
114 fn vertex(&self, index: usize) -> Vertex {
115 (*self).vertex(index)
116 }
117
118 fn vertices(&self) -> impl Iterator<Item = Vertex> {
119 (self.first_vertex_inner()..self.last_vertex_inner()).map(Vertex)
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
125pub struct Face(pub(crate) u8);
126
127impl Face {
128 pub const fn sphere(self) -> BaseTriSphere {
130 unsafe { std::mem::transmute(self.0.saturating_sub(12) / 8) }
131 }
132
133 pub(crate) fn owns_vertex_1(self) -> bool {
135 OWNERSHIP.owns_vert_1 & (1 << self.0) != 0
136 }
137
138 pub(crate) fn owns_edge_2(self) -> bool {
140 OWNERSHIP.owns_edge_2 & (1 << self.0) != 0
141 }
142
143 pub(crate) fn num_owned_vertices_before(self) -> usize {
146 let start = self.sphere().first_face_inner();
147 let before_mask = (1u32 << self.0) - 1;
148 ((OWNERSHIP.owns_vert_1 & before_mask) >> start).count_ones() as usize
149 }
150
151 pub(crate) fn num_owned_edges_before(self) -> usize {
154 let start = self.sphere().first_face_inner();
155 let index = self.0 - start;
156 let before_mask = (1u32 << self.0) - 1;
157 index as usize + ((OWNERSHIP.owns_edge_2 & before_mask) >> start).count_ones() as usize
158 }
159
160 pub const fn side(self, index: usize) -> HalfEdge {
163 assert!(index < 3, "index out of bounds");
164 HalfEdge((self.0 << 2) | index as u8)
165 }
166
167 pub const fn center(self) -> [f64; 3] {
169 let v_0 = self.side(0).start().pos();
170 let v_1 = self.side(1).start().pos();
171 let v_2 = self.side(2).start().pos();
172 let mul = [0.4194695241216063, 0.5773502691896257][self.sphere() as usize]; vec::mul(vec::add(vec::add(v_0, v_1), v_2), mul)
174 }
175}
176
177#[test]
178fn test_center_face_at() {
179 use crate::Sphere;
180 for sphere in [BaseTriSphere::Icosa, BaseTriSphere::Octo] {
182 for face in sphere.faces() {
183 let center = face.center();
184 assert!((vec::dot(center, center) - 1.0).abs() < 1.0e-12);
185 assert_eq!(sphere.face_at(center), face);
186 }
187 }
188}
189
190impl crate::Face for Face {
191 type Vertex = Vertex;
192 type HalfEdge = HalfEdge;
193
194 fn index(&self) -> usize {
195 (self.0 - self.sphere().first_face_inner()) as usize
196 }
197
198 fn area(&self) -> f64 {
199 let v_0 = self.side(0).start().pos();
201 let v_1 = self.side(1).start().pos();
202 let v_2 = self.side(2).start().pos();
203 crate::util::tri_area([v_0, v_1, v_2])
204 }
205
206 fn num_sides(&self) -> usize {
207 3
208 }
209
210 fn side(&self, index: usize) -> HalfEdge {
211 (*self).side(index)
212 }
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
217pub struct Vertex(u8);
218
219impl Vertex {
220 pub const fn sphere(self) -> BaseTriSphere {
222 unsafe { std::mem::transmute(self.0.saturating_sub(6) / 6) }
223 }
224
225 pub(crate) const fn owner(self) -> Face {
227 OWNERSHIP.vert_owner[self.0 as usize]
228 }
229
230 pub const fn pos(self) -> [f64; 3] {
232 VERTS[self.0 as usize]
233 }
234}
235
236impl crate::Vertex for Vertex {
237 type Face = Face;
238 type HalfEdge = HalfEdge;
239
240 fn index(&self) -> usize {
241 (self.0 - self.sphere().first_vertex_inner()) as usize
242 }
243
244 fn pos(&self) -> [f64; 3] {
245 (*self).pos()
246 }
247
248 fn degree(&self) -> usize {
249 self.sphere().vertex_degree()
250 }
251
252 fn outgoing(&self, index: usize) -> HalfEdge {
253 assert!(index < self.degree(), "index out of bounds");
254 OUTGOING[self.0 as usize][index]
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
260pub struct HalfEdge(u8);
261
262impl HalfEdge {
263 pub const fn sphere(self) -> BaseTriSphere {
265 self.inside().sphere()
266 }
267
268 pub const fn side_index(self) -> usize {
271 (self.0 & 0b11) as usize
272 }
273
274 pub const fn inside(self) -> Face {
276 Face(self.0 >> 2)
277 }
278
279 pub const fn start(self) -> Vertex {
281 Vertex(INDICES[self.inside().0 as usize][self.side_index()])
282 }
283
284 pub const fn complement(self) -> Self {
288 COMPLEMENT[self.inside().0 as usize][self.side_index()]
289 }
290
291 pub const fn prev(self) -> Self {
294 HalfEdge(if self.0 & 0b11 == 0 {
295 self.0 + 2
296 } else {
297 self.0 - 1
298 })
299 }
300
301 pub const fn next(self) -> Self {
304 HalfEdge(if self.0 & 0b11 == 2 {
305 self.0 - 2
306 } else {
307 self.0 + 1
308 })
309 }
310}
311
312impl crate::HalfEdge for HalfEdge {
313 type Face = Face;
314 type Vertex = Vertex;
315
316 fn side_index(&self) -> usize {
317 (*self).side_index()
318 }
319
320 fn length(&self) -> f64 {
321 self.sphere().edge_length()
322 }
323
324 fn angle(&self) -> f64 {
325 let v_a = self.prev().start().pos();
327 let v_b = self.start().pos();
328 let v_c = self.next().start().pos();
329 crate::util::angle(v_a, v_b, v_c)
330 }
331
332 fn inside(&self) -> Face {
333 (*self).inside()
334 }
335
336 fn start(&self) -> Vertex {
337 (*self).start()
338 }
339
340 fn complement(&self) -> Self {
341 (*self).complement()
342 }
343
344 fn prev(&self) -> Self {
345 (*self).prev()
346 }
347
348 fn next(&self) -> Self {
349 (*self).next()
350 }
351}
352
353static OUTGOING: [[HalfEdge; 5]; NUM_VERTS] = const {
355 let mut res: [[HalfEdge; 5]; NUM_VERTS] = [[HalfEdge(u8::MAX); 5]; NUM_VERTS];
356 let mut j = 0;
357 while j < NUM_VERTS {
358 let vert = Vertex(j as u8);
359 let owner = vert.owner();
360
361 let first_outgoing = {
363 let mut k = 0;
364 loop {
365 let edge = owner.side(k);
366 if edge.start().0 == vert.0 {
367 break edge;
368 }
369 k += 1;
370 if k == 3 {
371 panic!("owner does not contain vertex");
372 }
373 }
374 };
375
376 let mut k = 0;
378 let mut outgoing = first_outgoing;
379 loop {
380 res[j][k] = outgoing;
381 k += 1;
382 outgoing = outgoing.prev().complement();
383 assert!(
384 outgoing.start().0 == vert.0,
385 "outgoing edge does not start at vertex"
386 );
387 if outgoing.0 == first_outgoing.0 {
388 break;
389 }
390 }
391 assert!(
392 k == Vertex(j as u8).sphere().vertex_degree(),
393 "degree mismatch"
394 );
395 j += 1;
396 }
397 res
398};
399
400struct OwnershipInfo {
410 vert_owner: [Face; NUM_VERTS],
411 owns_vert_1: u32,
412 owns_edge_2: u32,
413}
414
415static OWNERSHIP: OwnershipInfo = const {
417 let mut vert_owner: [Face; NUM_VERTS] = [Face(u8::MAX); NUM_VERTS];
419 vert_owner[0] = Face(0);
420 vert_owner[12] = Face(20);
421
422 let mut owns_vert_1 = 0;
424 let mut i = 0;
425 while i < NUM_FACES {
426 let v_1 = INDICES[i][1] as usize;
427 if vert_owner[v_1].0 == u8::MAX {
428 vert_owner[v_1] = Face(i as u8);
429 owns_vert_1 |= 1 << i;
430 }
431 i += 1;
432 }
433
434 let mut j = 0;
436 while j < NUM_VERTS {
437 assert!(vert_owner[j].0 != u8::MAX, "vertex does not have an owner");
438 j += 1;
439 }
440
441 let mut edge_has_owner: [[bool; NUM_VERTS]; NUM_VERTS] = [[false; NUM_VERTS]; NUM_VERTS];
443 let mut num_owned_edges = 0;
444 let mut i = 0;
445 while i < NUM_FACES {
446 let v_0 = INDICES[i][0] as usize;
447 let v_1 = INDICES[i][1] as usize;
448 let edge_has_owner = if v_0 < v_1 {
449 &mut edge_has_owner[v_0][v_1]
450 } else {
451 &mut edge_has_owner[v_1][v_0]
452 };
453 assert!(!*edge_has_owner, "edge already has an owner");
454 *edge_has_owner = true;
455 num_owned_edges += 1;
456 i += 1;
457 }
458 let mut owns_edge_2 = 0;
459 let mut i = 0;
460 while i < NUM_FACES {
461 let v_2 = INDICES[i][2] as usize;
462 let v_0 = INDICES[i][0] as usize;
463 let edge_has_owner = if v_0 < v_2 {
464 &mut edge_has_owner[v_0][v_2]
465 } else {
466 &mut edge_has_owner[v_2][v_0]
467 };
468 if !*edge_has_owner {
469 *edge_has_owner = true;
470 num_owned_edges += 1;
471 owns_edge_2 |= 1 << i;
472 }
473 i += 1;
474 }
475
476 assert!(
478 num_owned_edges == NUM_FACES * 3 / 2,
479 "not all edges have an owner"
480 );
481
482 OwnershipInfo {
484 vert_owner,
485 owns_vert_1,
486 owns_edge_2,
487 }
488};
489
490static COMPLEMENT: [[HalfEdge; 3]; NUM_FACES] = const {
492 let mut adjacent: [[u8; NUM_VERTS]; NUM_VERTS] = [[u8::MAX; NUM_VERTS]; NUM_VERTS];
494 let mut i = 0;
495 while i < NUM_FACES {
496 let v_0 = INDICES[i][0] as usize;
497 let v_1 = INDICES[i][1] as usize;
498 let v_2 = INDICES[i][2] as usize;
499 assert!(adjacent[v_0][v_1] == u8::MAX, "duplicate edge detected");
500 assert!(adjacent[v_1][v_2] == u8::MAX, "duplicate edge detected");
501 assert!(adjacent[v_2][v_0] == u8::MAX, "duplicate edge detected");
502 adjacent[v_0][v_1] = (i as u8) << 2;
503 adjacent[v_1][v_2] = ((i as u8) << 2) | 1;
504 adjacent[v_2][v_0] = ((i as u8) << 2) | 2;
505 i += 1;
506 }
507
508 let mut res: [[HalfEdge; 3]; NUM_FACES] = [[HalfEdge(0); 3]; NUM_FACES];
510 i = 0;
511 while i < NUM_FACES {
512 let v_0 = INDICES[i][0] as usize;
513 let v_1 = INDICES[i][1] as usize;
514 let v_2 = INDICES[i][2] as usize;
515 assert!(adjacent[v_1][v_0] != u8::MAX, "hanging edge detected");
516 assert!(adjacent[v_2][v_1] != u8::MAX, "hanging edge detected");
517 assert!(adjacent[v_0][v_2] != u8::MAX, "hanging edge detected");
518 res[i][0] = HalfEdge(adjacent[v_1][v_0]);
519 res[i][1] = HalfEdge(adjacent[v_2][v_1]);
520 res[i][2] = HalfEdge(adjacent[v_0][v_2]);
521 i += 1;
522 }
523 res
524};
525
526const fn icosa_point_index(point: [f64; 3]) -> u8 {
532 let mut res = 0;
536 let mut i = 0;
537 while i < 5 {
538 let dot = vec::dot(point, BaseTriSphere::Icosa.vertex(i).pos());
539 let comp = (dot.is_sign_positive() as u8 + 1) * ((dot.abs() > C_1) as u8);
540 res = res * 3 + comp;
541 i += 1;
542 }
543 res
544}
545
546static ICOSA_FACE_AT: [Face; 243] = const {
552 let mut res = [Face(u8::MAX); 243];
553
554 let sphere = BaseTriSphere::Icosa;
556 let mut i = sphere.first_face_inner();
557 while i < sphere.last_face_inner() {
558 let face = Face(i);
559 let j = icosa_point_index(face.center());
560 assert!(res[j as usize].0 == u8::MAX, "index already assigned");
561 res[j as usize] = face;
562 i += 1;
563 }
564
565 let mut next = res;
568 let mut all_filled = false;
569 while !all_filled {
570 let mut index = 0;
571 all_filled = true;
572 while index < 243 {
573 if res[index].0 == u8::MAX {
574 all_filled = false;
575
576 let mut mul = 1;
578 while mul <= 83 {
579 let comp = (index / mul) % 3;
580 if comp == 0 {
581 if res[index + mul].0 != u8::MAX {
582 next[index] = res[index + mul];
583 break;
584 }
585 if res[index + 2 * mul].0 != u8::MAX {
586 next[index] = res[index + 2 * mul];
587 break;
588 }
589 } else if res[index - comp * mul].0 != u8::MAX {
590 next[index] = res[index - comp * mul];
591 break;
592 }
593 mul *= 3;
594 }
595 }
596 index += 1;
597 }
598 res = next;
599 }
600 res
601};
602
603const fn octo_point_index([x, y, z]: [f64; 3]) -> u8 {
609 (((x >= 0.0) as u8) << 2) | (((y >= 0.0) as u8) << 1) | ((z >= 0.0) as u8)
610}
611
612const OCTO_FACE_AT: u64 = const {
615 let sphere = BaseTriSphere::Octo;
616 let mut i = sphere.first_face_inner();
617 let mut res = 0;
618 while i < sphere.last_face_inner() {
619 let face = Face(i);
620 let j = octo_point_index(face.center());
621 res |= (i as u64) << (j * 8);
622 i += 1;
623 }
624 res
625};
626
627const NUM_VERTS: usize = 12 + 6;
629
630const NUM_FACES: usize = 20 + 8;
632
633static VERTS: [[f64; 3]; NUM_VERTS] = [
635 [0.0, 0.0, 1.0],
637 [C_0, 0.0, C_1],
639 [C_2, C_3, C_1],
640 [-C_4, C_5, C_1],
641 [-C_4, -C_5, C_1],
642 [C_2, -C_3, C_1],
643 [C_4, -C_5, -C_1],
645 [C_4, C_5, -C_1],
646 [-C_2, C_3, -C_1],
647 [-C_0, 0.0, -C_1],
648 [-C_2, -C_3, -C_1],
649 [0.0, 0.0, -1.0],
651 [0.0, 0.0, 1.0],
653 [1.0, 0.0, 0.0],
654 [0.0, 1.0, 0.0],
655 [-1.0, 0.0, 0.0],
656 [0.0, -1.0, 0.0],
657 [0.0, 0.0, -1.0],
658];
659
660static INDICES: [[u8; 3]; NUM_FACES] = [
662 [0, 1, 2],
664 [0, 2, 3],
665 [0, 3, 4],
666 [0, 4, 5],
667 [0, 5, 1],
668 [5, 6, 1],
670 [6, 7, 1],
671 [1, 7, 2],
672 [7, 8, 2],
673 [2, 8, 3],
674 [8, 9, 3],
675 [3, 9, 4],
676 [9, 10, 4],
677 [4, 10, 5],
678 [10, 6, 5],
679 [10, 11, 6],
681 [6, 11, 7],
682 [7, 11, 8],
683 [8, 11, 9],
684 [9, 11, 10],
685 [12, 13, 14],
687 [12, 14, 15],
688 [12, 15, 16],
689 [12, 16, 13],
690 [16, 17, 13],
692 [13, 17, 14],
693 [14, 17, 15],
694 [15, 17, 16],
695];
696
697const C_0: f64 = 0.8944271909999159;
699
700const C_1: f64 = 0.4472135954999579;
702
703const C_2: f64 = 0.276393202250021;
705
706const C_3: f64 = 0.8506508083520399;
708
709const C_4: f64 = 0.7236067977499789;
711
712const C_5: f64 = 0.5257311121191336;