Skip to main content

ling_graphics/
geometry.rs

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