Skip to main content

ling_graphics/
geometry.rs

1use crate::color::Color;
2use crate::math::Aabb;
3use glam::{Vec2, Vec3};
4use std::collections::HashMap;
5
6// ── Vertex ────────────────────────────────────────────────────────────────────
7
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Vertex {
10    pub position: Vec3,
11    pub normal: Vec3,
12    pub uv: Vec2,
13    pub color: Color,
14    pub tangent: Vec3,
15}
16
17impl Vertex {
18    pub fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
19        Self { position, normal, uv, color: Color::WHITE, tangent: Vec3::X }
20    }
21
22    pub fn with_color(mut self, c: Color) -> Self {
23        self.color = c;
24        self
25    }
26}
27
28// ── Mesh ──────────────────────────────────────────────────────────────────────
29
30#[derive(Debug, Clone)]
31pub struct Mesh {
32    pub vertices: Vec<Vertex>,
33    pub indices: Vec<u32>,
34    pub aabb: Aabb,
35}
36
37impl Mesh {
38    pub fn new(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
39        let positions: Vec<Vec3> = vertices.iter().map(|v| v.position).collect();
40        let aabb = if positions.is_empty() {
41            Aabb::new(Vec3::ZERO, Vec3::ZERO)
42        } else {
43            Aabb::from_points(&positions)
44        };
45        Self { vertices, indices, aabb }
46    }
47
48    pub fn triangle_count(&self) -> usize {
49        self.indices.len() / 3
50    }
51
52    /// Recompute smooth normals from triangle face normals.
53    pub fn compute_normals(&mut self) {
54        let mut normals = vec![Vec3::ZERO; self.vertices.len()];
55        for tri in self.indices.chunks(3) {
56            let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
57            let p0 = self.vertices[i0].position;
58            let p1 = self.vertices[i1].position;
59            let p2 = self.vertices[i2].position;
60            let n = (p1 - p0).cross(p2 - p0);
61            normals[i0] += n;
62            normals[i1] += n;
63            normals[i2] += n;
64        }
65        for (v, n) in self.vertices.iter_mut().zip(normals) {
66            v.normal = n.normalize_or_zero();
67        }
68    }
69
70    /// Compute tangents for normal mapping (requires UVs).
71    pub fn compute_tangents(&mut self) {
72        let mut tangents = vec![Vec3::ZERO; self.vertices.len()];
73        for tri in self.indices.chunks(3) {
74            let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
75            let p0 = self.vertices[i0].position;
76            let p1 = self.vertices[i1].position;
77            let p2 = self.vertices[i2].position;
78            let uv0 = self.vertices[i0].uv;
79            let uv1 = self.vertices[i1].uv;
80            let uv2 = self.vertices[i2].uv;
81            let e1 = p1 - p0;
82            let e2 = p2 - p0;
83            let du1 = uv1.x - uv0.x;
84            let dv1 = uv1.y - uv0.y;
85            let du2 = uv2.x - uv0.x;
86            let dv2 = uv2.y - uv0.y;
87            let denom = du1 * dv2 - du2 * dv1;
88            if denom.abs() < 1e-8 {
89                continue;
90            }
91            let inv = 1.0 / denom;
92            let t = (e1 * dv2 - e2 * dv1) * inv;
93            tangents[i0] += t;
94            tangents[i1] += t;
95            tangents[i2] += t;
96        }
97        for (v, t) in self.vertices.iter_mut().zip(tangents) {
98            v.tangent = t.normalize_or_zero();
99        }
100    }
101}
102
103// ── MeshBuilder ───────────────────────────────────────────────────────────────
104
105pub struct MeshBuilder {
106    vertices: Vec<Vertex>,
107    indices: Vec<u32>,
108}
109
110impl MeshBuilder {
111    pub fn new() -> Self {
112        Self { vertices: Vec::new(), indices: Vec::new() }
113    }
114
115    pub fn add_vertex(&mut self, v: Vertex) -> u32 {
116        let idx = self.vertices.len() as u32;
117        self.vertices.push(v);
118        idx
119    }
120
121    pub fn add_triangle(&mut self, i0: u32, i1: u32, i2: u32) {
122        self.indices.extend_from_slice(&[i0, i1, i2]);
123    }
124
125    pub fn build(self) -> Mesh {
126        Mesh::new(self.vertices, self.indices)
127    }
128}
129
130impl Default for MeshBuilder {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136// ── Procedural primitives ─────────────────────────────────────────────────────
137
138/// Axis-aligned unit cube (−0.5 to +0.5 on each axis), with correct per-face normals.
139pub fn cube(half: f32) -> Mesh {
140    let h = half;
141    let faces: &[(Vec3, Vec3, Vec3)] = &[
142        // normal, tangent_u, tangent_v
143        (Vec3::Z, Vec3::X, Vec3::Y),   // +Z
144        (-Vec3::Z, -Vec3::X, Vec3::Y), // -Z
145        (Vec3::Y, Vec3::X, -Vec3::Z),  // +Y
146        (-Vec3::Y, Vec3::X, Vec3::Z),  // -Y
147        (Vec3::X, -Vec3::Z, Vec3::Y),  // +X
148        (-Vec3::X, Vec3::Z, Vec3::Y),  // -X
149    ];
150    let mut verts = Vec::new();
151    let mut idx = Vec::new();
152    for &(n, tu, tv) in faces {
153        let base = verts.len() as u32;
154        let center = n * h;
155        let corners = [
156            center - tu * h - tv * h,
157            center + tu * h - tv * h,
158            center + tu * h + tv * h,
159            center - tu * h + tv * h,
160        ];
161        let uvs = [
162            Vec2::new(0.0, 0.0),
163            Vec2::new(1.0, 0.0),
164            Vec2::new(1.0, 1.0),
165            Vec2::new(0.0, 1.0),
166        ];
167        for (p, uv) in corners.iter().zip(uvs.iter()) {
168            verts.push(Vertex {
169                position: *p,
170                normal: n,
171                uv: *uv,
172                color: Color::WHITE,
173                tangent: tu,
174            });
175        }
176        idx.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
177    }
178    Mesh::new(verts, idx)
179}
180
181/// UV sphere with given radius, rings, and sectors.
182pub fn sphere(radius: f32, rings: u32, sectors: u32) -> Mesh {
183    let rings = rings.max(2);
184    let sectors = sectors.max(3);
185    let mut verts = Vec::new();
186    let mut idx = Vec::new();
187
188    for r in 0..=rings {
189        let phi = std::f32::consts::PI * r as f32 / rings as f32;
190        let (sin_phi, cos_phi) = phi.sin_cos();
191        for s in 0..=sectors {
192            let theta = 2.0 * std::f32::consts::PI * s as f32 / sectors as f32;
193            let (sin_t, cos_t) = theta.sin_cos();
194            let x = sin_phi * cos_t;
195            let y = cos_phi;
196            let z = sin_phi * sin_t;
197            let p = Vec3::new(x, y, z);
198            let u = s as f32 / sectors as f32;
199            let v = r as f32 / rings as f32;
200            verts.push(Vertex {
201                position: p * radius,
202                normal: p,
203                uv: Vec2::new(u, v),
204                color: Color::WHITE,
205                tangent: Vec3::new(-sin_t, 0.0, cos_t),
206            });
207        }
208    }
209
210    let w = sectors + 1;
211    for r in 0..rings {
212        for s in 0..sectors {
213            let i0 = r * w + s;
214            let i1 = i0 + 1;
215            let i2 = (r + 1) * w + s;
216            let i3 = i2 + 1;
217            idx.extend_from_slice(&[i0, i2, i1, i1, i2, i3]);
218        }
219    }
220    Mesh::new(verts, idx)
221}
222
223/// Icosphere with given radius and subdivision level (0 = raw icosahedron, 20 triangles).
224pub fn icosphere(radius: f32, subdivisions: u32) -> Mesh {
225    let t = (1.0 + 5.0_f32.sqrt()) / 2.0;
226    let base_verts: &[[f32; 3]] = &[
227        [-1.0, t, 0.0],
228        [1.0, t, 0.0],
229        [-1.0, -t, 0.0],
230        [1.0, -t, 0.0],
231        [0.0, -1.0, t],
232        [0.0, 1.0, t],
233        [0.0, -1.0, -t],
234        [0.0, 1.0, -t],
235        [t, 0.0, -1.0],
236        [t, 0.0, 1.0],
237        [-t, 0.0, -1.0],
238        [-t, 0.0, 1.0],
239    ];
240    let base_faces: &[[u32; 3]] = &[
241        [0, 11, 5],
242        [0, 5, 1],
243        [0, 1, 7],
244        [0, 7, 10],
245        [0, 10, 11],
246        [1, 5, 9],
247        [5, 11, 4],
248        [11, 10, 2],
249        [10, 7, 6],
250        [7, 1, 8],
251        [3, 9, 4],
252        [3, 4, 2],
253        [3, 2, 6],
254        [3, 6, 8],
255        [3, 8, 9],
256        [4, 9, 5],
257        [2, 4, 11],
258        [6, 2, 10],
259        [8, 6, 7],
260        [9, 8, 1],
261    ];
262
263    let mut points: Vec<Vec3> = base_verts
264        .iter()
265        .map(|v| Vec3::new(v[0], v[1], v[2]).normalize())
266        .collect();
267    let mut faces: Vec<[u32; 3]> = base_faces.to_vec();
268
269    let mut midpoint_cache: HashMap<u64, u32> = HashMap::new();
270
271    let mut get_midpoint = |a: u32, b: u32, pts: &mut Vec<Vec3>| -> u32 {
272        let key = if a < b {
273            ((a as u64) << 32) | b as u64
274        } else {
275            ((b as u64) << 32) | a as u64
276        };
277        if let Some(&idx) = midpoint_cache.get(&key) {
278            return idx;
279        }
280        let mid = (pts[a as usize] + pts[b as usize]).normalize();
281        let idx = pts.len() as u32;
282        pts.push(mid);
283        midpoint_cache.insert(key, idx);
284        idx
285    };
286
287    for _ in 0..subdivisions {
288        let mut new_faces = Vec::with_capacity(faces.len() * 4);
289        for tri in &faces {
290            let m0 = get_midpoint(tri[0], tri[1], &mut points);
291            let m1 = get_midpoint(tri[1], tri[2], &mut points);
292            let m2 = get_midpoint(tri[2], tri[0], &mut points);
293            new_faces.push([tri[0], m0, m2]);
294            new_faces.push([tri[1], m1, m0]);
295            new_faces.push([tri[2], m2, m1]);
296            new_faces.push([m0, m1, m2]);
297        }
298        faces = new_faces;
299    }
300
301    let verts: Vec<Vertex> = points
302        .iter()
303        .map(|&p| {
304            let u = p.z.atan2(p.x) / (2.0 * std::f32::consts::PI) + 0.5;
305            let v = p.y.asin() / std::f32::consts::PI + 0.5;
306            Vertex {
307                position: p * radius,
308                normal: p,
309                uv: Vec2::new(u, v),
310                color: Color::WHITE,
311                tangent: Vec3::new(-p.z, 0.0, p.x).normalize_or_zero(),
312            }
313        })
314        .collect();
315
316    let indices: Vec<u32> = faces.iter().flat_map(|t| t.iter().cloned()).collect();
317    Mesh::new(verts, indices)
318}
319
320/// Cone: apex at +Y, base at −Y, with given radius, height, and radial segments.
321pub fn cone(radius: f32, height: f32, segments: u32) -> Mesh {
322    let segments = segments.max(3);
323    let mut verts = Vec::new();
324    let mut idx = Vec::new();
325    let apex = Vec3::new(0.0, height * 0.5, 0.0);
326    let apex_idx = verts.len() as u32;
327    verts.push(Vertex {
328        position: apex,
329        normal: Vec3::Y,
330        uv: Vec2::new(0.5, 0.0),
331        color: Color::WHITE,
332        tangent: Vec3::X,
333    });
334    let base_center_idx = verts.len() as u32;
335    verts.push(Vertex {
336        position: Vec3::new(0.0, -height * 0.5, 0.0),
337        normal: -Vec3::Y,
338        uv: Vec2::new(0.5, 0.5),
339        color: Color::WHITE,
340        tangent: Vec3::X,
341    });
342
343    let first_base = verts.len() as u32;
344    for i in 0..=segments {
345        let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
346        let (s, c) = angle.sin_cos();
347        let pos = Vec3::new(c * radius, -height * 0.5, s * radius);
348        let side_n = Vec3::new(c * height, radius, s * height).normalize();
349        let uv = Vec2::new(i as f32 / segments as f32, 1.0);
350        verts.push(Vertex {
351            position: pos,
352            normal: side_n,
353            uv,
354            color: Color::WHITE,
355            tangent: Vec3::new(-s, 0.0, c),
356        });
357    }
358    let first_base_cap = verts.len() as u32;
359    for i in 0..=segments {
360        let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
361        let (s, c) = angle.sin_cos();
362        let pos = Vec3::new(c * radius, -height * 0.5, s * radius);
363        let uv = Vec2::new(c * 0.5 + 0.5, s * 0.5 + 0.5);
364        verts.push(Vertex {
365            position: pos,
366            normal: -Vec3::Y,
367            uv,
368            color: Color::WHITE,
369            tangent: Vec3::X,
370        });
371    }
372    // Sides
373    for i in 0..segments {
374        idx.extend_from_slice(&[apex_idx, first_base + i, first_base + i + 1]);
375    }
376    // Base cap
377    for i in 0..segments {
378        idx.extend_from_slice(&[base_center_idx, first_base_cap + i + 1, first_base_cap + i]);
379    }
380    Mesh::new(verts, idx)
381}
382
383/// Square pyramid: apex at +Y, base at −Y.
384pub fn pyramid(base_half: f32, height: f32) -> Mesh {
385    let h2 = height * 0.5;
386    let b = base_half;
387    let apex = Vec3::new(0.0, h2, 0.0);
388
389    let base_pts = [
390        Vec3::new(-b, -h2, -b),
391        Vec3::new(b, -h2, -b),
392        Vec3::new(b, -h2, b),
393        Vec3::new(-b, -h2, b),
394    ];
395
396    let mut verts = Vec::new();
397    let mut idx = Vec::new();
398
399    // Four triangular faces
400    for i in 0..4 {
401        let a = base_pts[i];
402        let c = base_pts[(i + 1) % 4];
403        let n = (c - a).cross(apex - a).normalize();
404        let base = verts.len() as u32;
405        verts.push(Vertex {
406            position: apex,
407            normal: n,
408            uv: Vec2::new(0.5, 0.0),
409            color: Color::WHITE,
410            tangent: Vec3::X,
411        });
412        verts.push(Vertex {
413            position: a,
414            normal: n,
415            uv: Vec2::new(0.0, 1.0),
416            color: Color::WHITE,
417            tangent: Vec3::X,
418        });
419        verts.push(Vertex {
420            position: c,
421            normal: n,
422            uv: Vec2::new(1.0, 1.0),
423            color: Color::WHITE,
424            tangent: Vec3::X,
425        });
426        idx.extend_from_slice(&[base, base + 1, base + 2]);
427    }
428
429    // Base quad
430    let bn = -Vec3::Y;
431    let base_start = verts.len() as u32;
432    for (i, &p) in base_pts.iter().enumerate() {
433        let u = if i == 1 || i == 2 { 1.0 } else { 0.0 };
434        let v = if i == 2 || i == 3 { 1.0 } else { 0.0 };
435        verts.push(Vertex {
436            position: p,
437            normal: bn,
438            uv: Vec2::new(u, v),
439            color: Color::WHITE,
440            tangent: Vec3::X,
441        });
442    }
443    idx.extend_from_slice(&[
444        base_start,
445        base_start + 2,
446        base_start + 1,
447        base_start,
448        base_start + 3,
449        base_start + 2,
450    ]);
451
452    Mesh::new(verts, idx)
453}
454
455/// Cylinder with top and bottom caps.
456pub fn cylinder(radius: f32, height: f32, segments: u32) -> Mesh {
457    let segments = segments.max(3);
458    let h2 = height * 0.5;
459    let mut verts = Vec::new();
460    let mut idx = Vec::new();
461
462    // Side
463    let side_start = 0u32;
464    for i in 0..=segments {
465        let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
466        let (s, c) = angle.sin_cos();
467        let n = Vec3::new(c, 0.0, s);
468        let u = i as f32 / segments as f32;
469        verts.push(Vertex {
470            position: Vec3::new(c * radius, h2, s * radius),
471            normal: n,
472            uv: Vec2::new(u, 0.0),
473            color: Color::WHITE,
474            tangent: Vec3::new(-s, 0.0, c),
475        });
476        verts.push(Vertex {
477            position: Vec3::new(c * radius, -h2, s * radius),
478            normal: n,
479            uv: Vec2::new(u, 1.0),
480            color: Color::WHITE,
481            tangent: Vec3::new(-s, 0.0, c),
482        });
483    }
484    for i in 0..segments {
485        let b = side_start + i * 2;
486        idx.extend_from_slice(&[b, b + 2, b + 1, b + 1, b + 2, b + 3]);
487    }
488
489    // Caps
490    for (cap_y, cap_n, flip) in [(h2, Vec3::Y, false), (-h2, -Vec3::Y, true)] {
491        let center = verts.len() as u32;
492        verts.push(Vertex {
493            position: Vec3::new(0.0, cap_y, 0.0),
494            normal: cap_n,
495            uv: Vec2::new(0.5, 0.5),
496            color: Color::WHITE,
497            tangent: Vec3::X,
498        });
499        let first = verts.len() as u32;
500        for i in 0..=segments {
501            let angle = 2.0 * std::f32::consts::PI * i as f32 / segments as f32;
502            let (s, c) = angle.sin_cos();
503            verts.push(Vertex {
504                position: Vec3::new(c * radius, cap_y, s * radius),
505                normal: cap_n,
506                uv: Vec2::new(c * 0.5 + 0.5, s * 0.5 + 0.5),
507                color: Color::WHITE,
508                tangent: Vec3::X,
509            });
510        }
511        for i in 0..segments {
512            if flip {
513                idx.extend_from_slice(&[center, first + i, first + i + 1]);
514            } else {
515                idx.extend_from_slice(&[center, first + i + 1, first + i]);
516            }
517        }
518    }
519
520    Mesh::new(verts, idx)
521}
522
523/// Torus centered at origin, with major radius R and tube radius r.
524pub fn torus(major_radius: f32, tube_radius: f32, major_segs: u32, tube_segs: u32) -> Mesh {
525    let major_segs = major_segs.max(3);
526    let tube_segs = tube_segs.max(3);
527    let mut verts = Vec::new();
528    let mut idx = Vec::new();
529
530    for i in 0..=major_segs {
531        let u = 2.0 * std::f32::consts::PI * i as f32 / major_segs as f32;
532        let (su, cu) = u.sin_cos();
533        let center = Vec3::new(cu * major_radius, 0.0, su * major_radius);
534        let radial = Vec3::new(cu, 0.0, su);
535        for j in 0..=tube_segs {
536            let v = 2.0 * std::f32::consts::PI * j as f32 / tube_segs as f32;
537            let (sv, cv) = v.sin_cos();
538            let pos = center + (radial * cv + Vec3::Y * sv) * tube_radius;
539            let n = (radial * cv + Vec3::Y * sv).normalize();
540            verts.push(Vertex {
541                position: pos,
542                normal: n,
543                uv: Vec2::new(i as f32 / major_segs as f32, j as f32 / tube_segs as f32),
544                color: Color::WHITE,
545                tangent: Vec3::new(-su, 0.0, cu),
546            });
547        }
548    }
549
550    let w = tube_segs + 1;
551    for i in 0..major_segs {
552        for j in 0..tube_segs {
553            let i0 = i * w + j;
554            let i1 = i0 + 1;
555            let i2 = (i + 1) * w + j;
556            let i3 = i2 + 1;
557            idx.extend_from_slice(&[i0, i1, i2, i1, i3, i2]);
558        }
559    }
560    Mesh::new(verts, idx)
561}
562
563/// Flat plane in the XZ plane, subdivided into a grid.
564pub fn plane(half_size: f32, subdivisions: u32) -> Mesh {
565    let n = subdivisions.max(1) + 1;
566    let mut verts = Vec::new();
567    let mut idx = Vec::new();
568    let step = half_size * 2.0 / subdivisions.max(1) as f32;
569    for row in 0..n {
570        for col in 0..n {
571            let x = -half_size + col as f32 * step;
572            let z = -half_size + row as f32 * step;
573            let u = col as f32 / (n - 1) as f32;
574            let v = row as f32 / (n - 1) as f32;
575            verts.push(Vertex {
576                position: Vec3::new(x, 0.0, z),
577                normal: Vec3::Y,
578                uv: Vec2::new(u, v),
579                color: Color::WHITE,
580                tangent: Vec3::X,
581            });
582        }
583    }
584    for row in 0..n - 1 {
585        for col in 0..n - 1 {
586            let i0 = row * n + col;
587            let i1 = i0 + 1;
588            let i2 = (row + 1) * n + col;
589            let i3 = i2 + 1;
590            idx.extend_from_slice(&[i0, i2, i1, i1, i2, i3]);
591        }
592    }
593    Mesh::new(verts, idx)
594}