1use vulkano::buffer::BufferContents;
8use vulkano::pipeline::graphics::vertex_input::Vertex;
9
10fn filled_polygon_2d(boundary: &[[f32; 2]]) -> CpuMesh {
11 let mut min_x = f32::INFINITY;
12 let mut max_x = f32::NEG_INFINITY;
13 let mut min_y = f32::INFINITY;
14 let mut max_y = f32::NEG_INFINITY;
15 for [x, y] in boundary {
16 min_x = min_x.min(*x);
17 max_x = max_x.max(*x);
18 min_y = min_y.min(*y);
19 max_y = max_y.max(*y);
20 }
21 let width = (max_x - min_x).max(1.0e-6);
22 let height = (max_y - min_y).max(1.0e-6);
23
24 let mut vertices = Vec::with_capacity(boundary.len() + 1);
25 vertices.push(CpuVertex {
26 pos: [0.0, 0.0, 0.0],
27 uv: [
28 ((0.0 - min_x) / width).clamp(0.0, 1.0),
29 (1.0 - (0.0 - min_y) / height).clamp(0.0, 1.0),
30 ],
31 normal: [0.0, 0.0, 1.0],
32 });
33 for [x, y] in boundary {
34 vertices.push(CpuVertex {
35 pos: [*x, *y, 0.0],
36 uv: [(*x - min_x) / width, 1.0 - (*y - min_y) / height],
37 normal: [0.0, 0.0, 1.0],
38 });
39 }
40
41 let mut indices = Vec::with_capacity(boundary.len() * 3);
42 for i in 0..boundary.len() {
43 let next = (i + 1) % boundary.len();
44 indices.extend_from_slice(&[0, (i + 1) as u32, (next + 1) as u32]);
45 }
46 CpuMesh::new(vertices, indices)
47}
48
49fn signed_area_2d(points: &[[f32; 2]]) -> f32 {
50 let mut area = 0.0;
51 for i in 0..points.len() {
52 let [x0, y0] = points[i];
53 let [x1, y1] = points[(i + 1) % points.len()];
54 area += x0 * y1 - x1 * y0;
55 }
56 area * 0.5
57}
58
59fn add2(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
60 [a[0] + b[0], a[1] + b[1]]
61}
62
63fn sub2(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
64 [a[0] - b[0], a[1] - b[1]]
65}
66
67fn mul2(v: [f32; 2], s: f32) -> [f32; 2] {
68 [v[0] * s, v[1] * s]
69}
70
71fn len2(v: [f32; 2]) -> f32 {
72 (v[0] * v[0] + v[1] * v[1]).sqrt()
73}
74
75fn normalize2(v: [f32; 2]) -> [f32; 2] {
76 let len = len2(v).max(1.0e-6);
77 [v[0] / len, v[1] / len]
78}
79
80fn quadratic_bezier2(a: [f32; 2], b: [f32; 2], c: [f32; 2], t: f32) -> [f32; 2] {
81 let u = 1.0 - t;
82 add2(add2(mul2(a, u * u), mul2(b, 2.0 * u * t)), mul2(c, t * t))
83}
84
85fn add3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
86 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
87}
88
89fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
90 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
91}
92
93fn mul3(v: [f32; 3], s: f32) -> [f32; 3] {
94 [v[0] * s, v[1] * s, v[2] * s]
95}
96
97fn len3(v: [f32; 3]) -> f32 {
98 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
99}
100
101fn normalize3(v: [f32; 3]) -> [f32; 3] {
102 let len = len3(v).max(1.0e-6);
103 [v[0] / len, v[1] / len, v[2] / len]
104}
105
106fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
107 [
108 a[1] * b[2] - a[2] * b[1],
109 a[2] * b[0] - a[0] * b[2],
110 a[0] * b[1] - a[1] * b[0],
111 ]
112}
113
114fn lerp3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
115 [
116 a[0] + (b[0] - a[0]) * t,
117 a[1] + (b[1] - a[1]) * t,
118 a[2] + (b[2] - a[2]) * t,
119 ]
120}
121
122fn append_rounded_corner_points(
123 boundary: &mut Vec<[f32; 2]>,
124 prev: [f32; 2],
125 curr: [f32; 2],
126 next: [f32; 2],
127 bevel_segments: u32,
128 trim_fraction: f32,
129) {
130 if bevel_segments == 0 {
131 boundary.push(curr);
132 return;
133 }
134
135 let to_prev = sub2(prev, curr);
136 let to_next = sub2(next, curr);
137 let trim = len2(to_prev).min(len2(to_next)) * trim_fraction.clamp(0.0, 0.49);
138 let start = add2(curr, mul2(normalize2(to_prev), trim));
139 let end = add2(curr, mul2(normalize2(to_next), trim));
140
141 for i in 0..=bevel_segments {
142 let t = i as f32 / bevel_segments as f32;
143 boundary.push(quadratic_bezier2(start, curr, end, t));
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum PrimitiveTopology {
149 TriangleList,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum IndexFormat {
154 U16,
155 U32,
156}
157
158#[derive(BufferContents, Vertex, Debug, Clone, Copy, Default)]
164#[repr(C)]
165pub struct CpuVertex {
166 #[format(R32G32B32_SFLOAT)]
167 pub pos: [f32; 3],
168 #[format(R32G32_SFLOAT)]
169 pub uv: [f32; 2],
170 #[format(R32G32B32_SFLOAT)]
171 pub normal: [f32; 3],
172}
173
174#[derive(Debug, Clone)]
182pub struct CpuMesh {
183 pub vertices: Vec<CpuVertex>,
184 pub indices_u32: Vec<u32>,
185
186 pub joints0: Option<Vec<[u16; 4]>>,
192 pub weights0: Option<Vec<[f32; 4]>>,
193 pub primitive_topology: PrimitiveTopology,
194 pub index_format: IndexFormat,
195}
196
197impl CpuMesh {
198 pub fn new(vertices: Vec<CpuVertex>, indices_u32: Vec<u32>) -> Self {
199 Self {
200 vertices,
201 indices_u32,
202 joints0: None,
203 weights0: None,
204 primitive_topology: PrimitiveTopology::TriangleList,
205 index_format: IndexFormat::U32,
206 }
207 }
208
209 pub fn with_skinning(mut self, joints0: Vec<[u16; 4]>, weights0: Vec<[f32; 4]>) -> Self {
210 debug_assert_eq!(joints0.len(), self.vertices.len());
211 debug_assert_eq!(weights0.len(), self.vertices.len());
212 debug_assert_eq!(joints0.len(), weights0.len());
213 self.joints0 = Some(joints0);
214 self.weights0 = Some(weights0);
215 self
216 }
217
218 pub fn index_count(&self) -> u32 {
219 self.indices_u32.len() as u32
220 }
221
222 pub fn vertex_count(&self) -> u32 {
223 self.vertices.len() as u32
224 }
225
226 pub fn approximate_heap_bytes(&self) -> usize {
227 let mut bytes = self.vertices.capacity() * std::mem::size_of::<CpuVertex>();
228 bytes += self.indices_u32.capacity() * std::mem::size_of::<u32>();
229 if let Some(joints0) = &self.joints0 {
230 bytes += joints0.capacity() * std::mem::size_of::<[u16; 4]>();
231 }
232 if let Some(weights0) = &self.weights0 {
233 bytes += weights0.capacity() * std::mem::size_of::<[f32; 4]>();
234 }
235 bytes
236 }
237}
238
239pub struct MeshFactory;
246
247impl MeshFactory {
248 pub fn triangle_2d() -> CpuMesh {
250 let h = 0.866_025_4_f32;
255 let y_top = 2.0 * h / 3.0;
256 let y_bottom = -h / 3.0;
257 let y_span = y_top - y_bottom;
258
259 let vertices = vec![
260 CpuVertex {
261 pos: [-0.5, y_bottom, 0.0],
262 uv: [0.0, 0.0],
264 normal: [0.0, 0.0, 1.0],
265 },
266 CpuVertex {
267 pos: [0.5, y_bottom, 0.0],
268 uv: [1.0, 0.0],
269 normal: [0.0, 0.0, 1.0],
270 },
271 CpuVertex {
272 pos: [0.0, y_top, 0.0],
273 uv: [0.5, (y_top - y_bottom) / y_span],
274 normal: [0.0, 0.0, 1.0],
275 },
276 ];
277
278 CpuMesh::new(vertices, vec![0, 1, 2])
279 }
280
281 pub fn quad_2d() -> CpuMesh {
283 let vertices = vec![
284 CpuVertex {
285 pos: [-0.5, -0.5, 0.0],
286 uv: [0.0, 1.0],
288 normal: [0.0, 0.0, 1.0],
289 },
290 CpuVertex {
291 pos: [0.5, -0.5, 0.0],
292 uv: [1.0, 1.0],
293 normal: [0.0, 0.0, 1.0],
294 },
295 CpuVertex {
296 pos: [0.5, 0.5, 0.0],
297 uv: [1.0, 0.0],
298 normal: [0.0, 0.0, 1.0],
299 },
300 CpuVertex {
301 pos: [-0.5, 0.5, 0.0],
302 uv: [0.0, 0.0],
303 normal: [0.0, 0.0, 1.0],
304 },
305 ];
306
307 CpuMesh::new(vertices, vec![0, 1, 2, 0, 2, 3])
309 }
310
311 pub fn cube() -> CpuMesh {
315 let p = 0.5_f32;
316
317 let mut vertices: Vec<CpuVertex> = Vec::with_capacity(24);
319 let mut indices: Vec<u32> = Vec::with_capacity(36);
320
321 let mut push_face = |n: [f32; 3], a: [f32; 3], b: [f32; 3], c: [f32; 3], d: [f32; 3]| {
322 let base = vertices.len() as u32;
323 vertices.push(CpuVertex {
324 pos: a,
325 uv: [0.0, 0.0],
326 normal: n,
327 });
328 vertices.push(CpuVertex {
329 pos: b,
330 uv: [1.0, 0.0],
331 normal: n,
332 });
333 vertices.push(CpuVertex {
334 pos: c,
335 uv: [1.0, 1.0],
336 normal: n,
337 });
338 vertices.push(CpuVertex {
339 pos: d,
340 uv: [0.0, 1.0],
341 normal: n,
342 });
343 indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
345 };
346
347 push_face(
349 [0.0, 0.0, -1.0],
350 [-p, -p, -p],
351 [p, -p, -p],
352 [p, p, -p],
353 [-p, p, -p],
354 );
355 push_face(
357 [0.0, 0.0, 1.0],
358 [-p, -p, p],
359 [p, -p, p],
360 [p, p, p],
361 [-p, p, p],
362 );
363 push_face(
365 [-1.0, 0.0, 0.0],
366 [-p, -p, -p],
367 [-p, -p, p],
368 [-p, p, p],
369 [-p, p, -p],
370 );
371 push_face(
373 [1.0, 0.0, 0.0],
374 [p, -p, -p],
375 [p, p, -p],
376 [p, p, p],
377 [p, -p, p],
378 );
379 push_face(
381 [0.0, -1.0, 0.0],
382 [-p, -p, -p],
383 [p, -p, -p],
384 [p, -p, p],
385 [-p, -p, p],
386 );
387 push_face(
389 [0.0, 1.0, 0.0],
390 [-p, p, -p],
391 [-p, p, p],
392 [p, p, p],
393 [p, p, -p],
394 );
395
396 CpuMesh::new(vertices, indices)
397 }
398
399 pub fn wireframe_box(thickness: f32) -> CpuMesh {
405 let thickness = thickness.clamp(1.0e-4, 1.0);
406 let edge_center = 0.5 - thickness * 0.5;
407 let cube = Self::cube();
408 let mut vertices = Vec::with_capacity(cube.vertices.len() * 12);
409 let mut indices = Vec::with_capacity(cube.indices_u32.len() * 12);
410
411 let mut append_prism = |center: [f32; 3], size: [f32; 3]| {
412 let base = vertices.len() as u32;
413 vertices.extend(cube.vertices.iter().map(|vertex| CpuVertex {
414 pos: [
415 center[0] + vertex.pos[0] * size[0],
416 center[1] + vertex.pos[1] * size[1],
417 center[2] + vertex.pos[2] * size[2],
418 ],
419 uv: vertex.uv,
420 normal: vertex.normal,
421 }));
422 indices.extend(cube.indices_u32.iter().map(|index| base + index));
423 };
424
425 for a in [-edge_center, edge_center] {
426 for b in [-edge_center, edge_center] {
427 append_prism([0.0, a, b], [1.0, thickness, thickness]);
428 append_prism([a, 0.0, b], [thickness, 1.0, thickness]);
429 append_prism([a, b, 0.0], [thickness, thickness, 1.0]);
430 }
431 }
432
433 CpuMesh::new(vertices, indices)
434 }
435
436 pub fn tetrahedron() -> CpuMesh {
438 let vertices = vec![
441 CpuVertex {
442 pos: [0.0, 0.0, 0.6123724],
443 uv: [0.5, 1.0],
444 normal: [0.0, 0.0, 1.0],
445 },
446 CpuVertex {
447 pos: [-0.5, -0.2886751, -0.2041241],
448 uv: [0.0, 0.0],
449 normal: [-1.0, -1.0, -1.0],
450 },
451 CpuVertex {
452 pos: [0.5, -0.2886751, -0.2041241],
453 uv: [1.0, 0.0],
454 normal: [1.0, -1.0, -1.0],
455 },
456 CpuVertex {
457 pos: [0.0, 0.5773503, -0.2041241],
458 uv: [0.5, 0.5],
459 normal: [0.0, 1.0, -1.0],
460 },
461 ];
462
463 let indices = vec![
467 0, 1, 2, 0, 3, 1, 0, 2, 3, 1, 3, 2, ];
472
473 CpuMesh::new(vertices, indices)
474 }
475
476 pub fn icosahedron(tessellations: u32, sphericalness: f32) -> CpuMesh {
481 let radius = 0.5_f32;
482 let sphericalness = sphericalness.clamp(0.0, 1.0);
483 let phi = (1.0 + 5.0_f32.sqrt()) * 0.5;
484
485 let base_positions = [
486 normalize3([-1.0, phi, 0.0]),
487 normalize3([1.0, phi, 0.0]),
488 normalize3([-1.0, -phi, 0.0]),
489 normalize3([1.0, -phi, 0.0]),
490 normalize3([0.0, -1.0, phi]),
491 normalize3([0.0, 1.0, phi]),
492 normalize3([0.0, -1.0, -phi]),
493 normalize3([0.0, 1.0, -phi]),
494 normalize3([phi, 0.0, -1.0]),
495 normalize3([phi, 0.0, 1.0]),
496 normalize3([-phi, 0.0, -1.0]),
497 normalize3([-phi, 0.0, 1.0]),
498 ]
499 .map(|p| mul3(p, radius));
500
501 let base_faces = [
502 [0, 11, 5],
503 [0, 5, 1],
504 [0, 1, 7],
505 [0, 7, 10],
506 [0, 10, 11],
507 [1, 5, 9],
508 [5, 11, 4],
509 [11, 10, 2],
510 [10, 7, 6],
511 [7, 1, 8],
512 [3, 9, 4],
513 [3, 4, 2],
514 [3, 2, 6],
515 [3, 6, 8],
516 [3, 8, 9],
517 [4, 9, 5],
518 [2, 4, 11],
519 [6, 2, 10],
520 [8, 6, 7],
521 [9, 8, 1],
522 ];
523
524 let mut triangles: Vec<[[f32; 3]; 3]> = base_faces
525 .iter()
526 .map(|face| {
527 [
528 base_positions[face[0]],
529 base_positions[face[1]],
530 base_positions[face[2]],
531 ]
532 })
533 .collect();
534
535 for _ in 0..tessellations {
536 let mut next = Vec::with_capacity(triangles.len() * 4);
537 for [a, b, c] in triangles {
538 let ab = mul3(add3(a, b), 0.5);
539 let bc = mul3(add3(b, c), 0.5);
540 let ca = mul3(add3(c, a), 0.5);
541 next.push([a, ab, ca]);
542 next.push([ab, b, bc]);
543 next.push([ca, bc, c]);
544 next.push([ab, bc, ca]);
545 }
546 triangles = next;
547 }
548
549 let mut vertices = Vec::with_capacity(triangles.len() * 3);
550 let mut indices = Vec::with_capacity(triangles.len() * 3);
551
552 for triangle in triangles {
553 let [planar_a, planar_b, planar_c] = triangle;
554 let final_positions = [planar_a, planar_b, planar_c].map(|planar| {
555 let spherical = mul3(normalize3(planar), radius);
556 lerp3(planar, spherical, sphericalness)
557 });
558
559 let face_normal = normalize3(cross3(
560 sub3(final_positions[1], final_positions[0]),
561 sub3(final_positions[2], final_positions[0]),
562 ));
563
564 for pos in final_positions {
565 let spherical_normal = normalize3(pos);
566 let normal = normalize3(lerp3(face_normal, spherical_normal, sphericalness));
567 let u = 0.5 + normal[2].atan2(normal[0]) / std::f32::consts::TAU;
568 let v = 0.5 - normal[1].asin() / std::f32::consts::PI;
569 vertices.push(CpuVertex {
570 pos,
571 uv: [u, v],
572 normal,
573 });
574 indices.push((vertices.len() - 1) as u32);
575 }
576 }
577
578 CpuMesh::new(vertices, indices)
579 }
580
581 pub fn sphere() -> CpuMesh {
585 let radius = 0.5_f32;
586 let rings: u32 = 16;
587 let segments: u32 = 32;
588
589 let mut vertices: Vec<CpuVertex> = Vec::new();
590 let mut indices: Vec<u32> = Vec::new();
591
592 for r in 0..=rings {
595 let v = r as f32 / rings as f32;
596 let theta = v * std::f32::consts::PI; let (st, ct) = theta.sin_cos();
598
599 for s in 0..=segments {
600 let u = s as f32 / segments as f32;
601 let phi = u * std::f32::consts::TAU; let (sp, cp) = phi.sin_cos();
603
604 let x = cp * st;
605 let y = ct;
606 let z = sp * st;
607
608 vertices.push(CpuVertex {
609 pos: [x * radius, y * radius, z * radius],
610 uv: [u, 1.0 - v],
611 normal: [x, y, z],
612 });
613 }
614 }
615
616 let stride = segments + 1;
618 for r in 0..rings {
619 for s in 0..segments {
620 let i0 = r * stride + s;
621 let i1 = i0 + 1;
622 let i2 = (r + 1) * stride + s;
623 let i3 = i2 + 1;
624
625 indices.extend_from_slice(&[i0, i2, i1]);
627 indices.extend_from_slice(&[i1, i2, i3]);
628 }
629 }
630
631 CpuMesh::new(vertices, indices)
632 }
633
634 pub fn cone(number_of_segments: u32) -> CpuMesh {
642 let segs = number_of_segments.max(3);
643 let radius = 0.5_f32;
644 let z_base = -0.5_f32;
645 let z_tip = 0.5_f32;
646
647 let tip = [0.0_f32, 0.0_f32, z_tip];
648 let base_center = [0.0_f32, 0.0_f32, z_base];
649
650 let mut vertices: Vec<CpuVertex> = Vec::new();
651 let mut indices: Vec<u32> = Vec::new();
652
653 fn vec3_sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
654 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
655 }
656
657 fn vec3_cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
658 [
659 a[1] * b[2] - a[2] * b[1],
660 a[2] * b[0] - a[0] * b[2],
661 a[0] * b[1] - a[1] * b[0],
662 ]
663 }
664
665 fn vec3_len(v: [f32; 3]) -> f32 {
666 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
667 }
668
669 fn vec3_normalize(v: [f32; 3]) -> [f32; 3] {
670 let len = vec3_len(v);
671 if len > 0.0 {
672 [v[0] / len, v[1] / len, v[2] / len]
673 } else {
674 [0.0, 0.0, 1.0]
675 }
676 }
677
678 for i in 0..segs {
680 let a0 = (i as f32) / (segs as f32) * std::f32::consts::TAU;
681 let a1 = ((i + 1) as f32) / (segs as f32) * std::f32::consts::TAU;
682 let (s0, c0) = a0.sin_cos();
683 let (s1, c1) = a1.sin_cos();
684
685 let p0 = [c0 * radius, s0 * radius, z_base];
686 let p1 = [c1 * radius, s1 * radius, z_base];
687
688 let e0 = vec3_sub(p0, tip);
690 let e1 = vec3_sub(p1, tip);
691 let n = vec3_normalize(vec3_cross(e0, e1));
692
693 let base = vertices.len() as u32;
694 vertices.push(CpuVertex {
695 pos: tip,
696 uv: [0.5, 0.0],
697 normal: n,
698 });
699 vertices.push(CpuVertex {
700 pos: p0,
701 uv: [0.0, 1.0],
702 normal: n,
703 });
704 vertices.push(CpuVertex {
705 pos: p1,
706 uv: [1.0, 1.0],
707 normal: n,
708 });
709 indices.extend_from_slice(&[base, base + 1, base + 2]);
710 }
711
712 let n_base = [0.0_f32, 0.0_f32, -1.0_f32];
714 for i in 0..segs {
715 let a0 = (i as f32) / (segs as f32) * std::f32::consts::TAU;
716 let a1 = ((i + 1) as f32) / (segs as f32) * std::f32::consts::TAU;
717 let (s0, c0) = a0.sin_cos();
718 let (s1, c1) = a1.sin_cos();
719
720 let p0 = [c0 * radius, s0 * radius, z_base];
721 let p1 = [c1 * radius, s1 * radius, z_base];
722
723 let base = vertices.len() as u32;
724 vertices.push(CpuVertex {
725 pos: base_center,
726 uv: [0.5, 0.5],
727 normal: n_base,
728 });
729 vertices.push(CpuVertex {
730 pos: p1,
731 uv: [0.5 + p1[0], 0.5 - p1[1]],
732 normal: n_base,
733 });
734 vertices.push(CpuVertex {
735 pos: p0,
736 uv: [0.5 + p0[0], 0.5 - p0[1]],
737 normal: n_base,
738 });
739
740 indices.extend_from_slice(&[base, base + 1, base + 2]);
741 }
742
743 CpuMesh::new(vertices, indices)
744 }
745
746 pub fn circle_2d(inner_radius: f32, outer_radius: f32, number_of_segments: u32) -> CpuMesh {
750 let segs = number_of_segments.max(3);
751 let inner = inner_radius.max(0.0);
752 let outer = outer_radius.max(inner + 1.0e-6);
753 let n = [0.0_f32, 0.0_f32, 1.0_f32];
754
755 let mut vertices: Vec<CpuVertex> = Vec::with_capacity((segs as usize) * 2);
756 let mut indices: Vec<u32> = Vec::with_capacity((segs as usize) * 6);
757
758 for i in 0..segs {
760 let a = (i as f32) / (segs as f32) * std::f32::consts::TAU;
761 let (s, c) = a.sin_cos();
762 let x = c * outer;
763 let y = s * outer;
764 let uv = [0.5 + x / (2.0 * outer), 0.5 - y / (2.0 * outer)];
765 vertices.push(CpuVertex {
766 pos: [x, y, 0.0],
767 uv,
768 normal: n,
769 });
770 }
771 for i in 0..segs {
773 let a = (i as f32) / (segs as f32) * std::f32::consts::TAU;
774 let (s, c) = a.sin_cos();
775 let x = c * inner;
776 let y = s * inner;
777 let uv = [0.5 + x / (2.0 * outer), 0.5 - y / (2.0 * outer)];
778 vertices.push(CpuVertex {
779 pos: [x, y, 0.0],
780 uv,
781 normal: n,
782 });
783 }
784
785 for i in 0..segs {
787 let next = (i + 1) % segs;
788 let outer_i = i;
789 let outer_n = next;
790 let inner_i = segs + i;
791 let inner_n = segs + next;
792
793 indices.extend_from_slice(&[outer_i, outer_n, inner_n]);
795 indices.extend_from_slice(&[outer_i, inner_n, inner_i]);
796 }
797
798 CpuMesh::new(vertices, indices)
799 }
800
801 pub fn partial_annulus_2d(
807 inner_radius: f32,
808 outer_radius: f32,
809 start_angle_radians: f32,
810 sweep_angle_radians: f32,
811 number_of_segments: u32,
812 ) -> CpuMesh {
813 let mut start = start_angle_radians;
814 let mut sweep = sweep_angle_radians;
815 if sweep < 0.0 {
816 start += sweep;
817 sweep = -sweep;
818 }
819
820 if sweep >= std::f32::consts::TAU - 1.0e-6 {
821 return Self::circle_2d(inner_radius, outer_radius, number_of_segments);
822 }
823
824 let segs = number_of_segments.max(1);
825 let inner = inner_radius.max(0.0);
826 let outer = outer_radius.max(inner + 1.0e-6);
827 let n = [0.0_f32, 0.0_f32, 1.0_f32];
828
829 let ring_vertex_count = (segs as usize) + 1;
830 let mut vertices: Vec<CpuVertex> = Vec::with_capacity(ring_vertex_count * 2);
831 let mut indices: Vec<u32> = Vec::with_capacity((segs as usize) * 6);
832
833 for i in 0..=segs {
834 let t = i as f32 / segs as f32;
835 let a = start + sweep * t;
836 let (s, c) = a.sin_cos();
837 let x = c * outer;
838 let y = s * outer;
839 let uv = [0.5 + x / (2.0 * outer), 0.5 - y / (2.0 * outer)];
840 vertices.push(CpuVertex {
841 pos: [x, y, 0.0],
842 uv,
843 normal: n,
844 });
845 }
846
847 for i in 0..=segs {
848 let t = i as f32 / segs as f32;
849 let a = start + sweep * t;
850 let (s, c) = a.sin_cos();
851 let x = c * inner;
852 let y = s * inner;
853 let uv = [0.5 + x / (2.0 * outer), 0.5 - y / (2.0 * outer)];
854 vertices.push(CpuVertex {
855 pos: [x, y, 0.0],
856 uv,
857 normal: n,
858 });
859 }
860
861 for i in 0..segs {
862 let outer_i = i;
863 let outer_n = i + 1;
864 let inner_i = segs + 1 + i;
865 let inner_n = inner_i + 1;
866
867 indices.extend_from_slice(&[outer_i, outer_n, inner_n]);
868 indices.extend_from_slice(&[outer_i, inner_n, inner_i]);
869 }
870
871 CpuMesh::new(vertices, indices)
872 }
873
874 pub fn star(
876 points: u32,
877 inner_radius_fraction: f32,
878 outer_bevel_segments: u32,
879 inner_bevel_segments: u32,
880 ) -> CpuMesh {
881 let point_count = points.max(3);
882 let outer = 0.5_f32;
883 let inner = outer * inner_radius_fraction.clamp(0.01, 1.0);
884 let step = std::f32::consts::PI / point_count as f32;
885
886 let mut star_vertices: Vec<[f32; 2]> = Vec::with_capacity((point_count as usize) * 2);
887 for i in 0..point_count {
888 let outer_angle = i as f32 * 2.0 * step - std::f32::consts::FRAC_PI_2;
889 let inner_angle = outer_angle + step;
890 let (outer_s, outer_c) = outer_angle.sin_cos();
891 let (inner_s, inner_c) = inner_angle.sin_cos();
892 star_vertices.push([outer_c * outer, outer_s * outer]);
893 star_vertices.push([inner_c * inner, inner_s * inner]);
894 }
895
896 let mut boundary: Vec<[f32; 2]> = Vec::new();
897 for i in 0..star_vertices.len() {
898 let prev = star_vertices[(i + star_vertices.len() - 1) % star_vertices.len()];
899 let curr = star_vertices[i];
900 let next = star_vertices[(i + 1) % star_vertices.len()];
901 let bevel_segments = if i % 2 == 0 {
902 outer_bevel_segments
903 } else {
904 inner_bevel_segments
905 };
906 append_rounded_corner_points(&mut boundary, prev, curr, next, bevel_segments, 0.35);
907 }
908
909 if signed_area_2d(&boundary) < 0.0 {
910 boundary.reverse();
911 }
912 filled_polygon_2d(&boundary)
913 }
914
915 pub fn heart(number_of_segments: u32) -> CpuMesh {
917 let segs = number_of_segments.max(12);
918 let mut boundary: Vec<[f32; 2]> = Vec::with_capacity(segs as usize);
919 for i in 0..segs {
920 let t = i as f32 / segs as f32 * std::f32::consts::TAU;
921 let x = 16.0 * t.sin().powi(3);
922 let y =
923 13.0 * t.cos() - 5.0 * (2.0 * t).cos() - 2.0 * (3.0 * t).cos() - (4.0 * t).cos();
924 boundary.push([x, y]);
925 }
926
927 let mut min_x = f32::INFINITY;
928 let mut max_x = f32::NEG_INFINITY;
929 let mut min_y = f32::INFINITY;
930 let mut max_y = f32::NEG_INFINITY;
931 for [x, y] in &boundary {
932 min_x = min_x.min(*x);
933 max_x = max_x.max(*x);
934 min_y = min_y.min(*y);
935 max_y = max_y.max(*y);
936 }
937 let center_x = (min_x + max_x) * 0.5;
938 let center_y = (min_y + max_y) * 0.5;
939 let scale = 1.0 / (max_x - min_x).max(max_y - min_y).max(1.0e-6);
940 for point in &mut boundary {
941 point[0] = (point[0] - center_x) * scale;
942 point[1] = (point[1] - center_y) * scale;
943 }
944
945 if signed_area_2d(&boundary) < 0.0 {
946 boundary.reverse();
947 }
948 filled_polygon_2d(&boundary)
949 }
950}
951
952#[cfg(test)]
953mod tests {
954 use super::MeshFactory;
955
956 fn radius2(point: [f32; 3]) -> f32 {
957 (point[0] * point[0] + point[1] * point[1]).sqrt()
958 }
959
960 fn radius3(point: [f32; 3]) -> f32 {
961 (point[0] * point[0] + point[1] * point[1] + point[2] * point[2]).sqrt()
962 }
963
964 #[test]
965 fn sharp_star_alternates_outer_and_inner_radii() {
966 let mesh = MeshFactory::star(5, 0.4, 0, 0);
967 let boundary = &mesh.vertices[1..];
968
969 assert_eq!(boundary.len(), 10);
970 for (index, vertex) in boundary.iter().enumerate() {
971 let expected = if index % 2 == 0 { 0.5 } else { 0.2 };
972 assert!((radius2(vertex.pos) - expected).abs() < 1.0e-4);
973 }
974 }
975
976 #[test]
977 fn beveled_star_generates_intermediate_radii_near_tips() {
978 let mesh = MeshFactory::star(5, 0.4, 3, 0);
979 let boundary = &mesh.vertices[1..];
980
981 assert!(boundary.iter().any(|vertex| {
982 let r = radius2(vertex.pos);
983 r > 0.2 + 1.0e-4 && r < 0.5 - 1.0e-4
984 }));
985 }
986
987 #[test]
988 fn icosahedron_tessellation_increases_triangle_count_by_four_per_level() {
989 let base = MeshFactory::icosahedron(0, 0.0);
990 let subdivided = MeshFactory::icosahedron(2, 0.0);
991
992 assert_eq!(base.indices_u32.len() / 3, 20);
993 assert_eq!(subdivided.indices_u32.len() / 3, 20 * 4 * 4);
994 }
995
996 #[test]
997 fn icosahedron_sphericalness_one_projects_vertices_to_radius() {
998 let mesh = MeshFactory::icosahedron(1, 1.0);
999
1000 for vertex in &mesh.vertices {
1001 assert!((radius3(vertex.pos) - 0.5).abs() < 1.0e-4);
1002 }
1003 }
1004}