1use ferravex_math::{Color, Vec3, vec3};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct Vertex {
5 pub position: Vec3,
6 pub normal: Vec3,
7 pub color: Color,
8}
9impl Vertex {
10 pub const fn new(position: Vec3, normal: Vec3, color: Color) -> Self {
11 Self {
12 position,
13 normal,
14 color,
15 }
16 }
17}
18
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Bounds {
21 pub min: Vec3,
22 pub max: Vec3,
23}
24impl Bounds {
25 pub fn center(self) -> Vec3 {
26 (self.min + self.max) * 0.5
27 }
28 pub fn half_extents(self) -> Vec3 {
29 (self.max - self.min) * 0.5
30 }
31}
32
33#[derive(Clone, Debug, PartialEq)]
34pub struct Mesh {
35 pub vertices: Vec<Vertex>,
36 pub indices: Vec<u32>,
37}
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum MeshError {
40 Empty,
41 InvalidTriangleCount,
42 IndexOutOfBounds,
43 NonFiniteVertex,
44 TooLarge,
45}
46impl std::fmt::Display for MeshError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 write!(f, "invalid mesh: {self:?}")
49 }
50}
51impl std::error::Error for MeshError {}
52impl Mesh {
53 pub fn validate(&self) -> Result<Bounds, MeshError> {
54 if self.vertices.is_empty() || self.indices.is_empty() {
55 return Err(MeshError::Empty);
56 }
57 if self.vertices.len() > 1_000_000 || self.indices.len() > 6_000_000 {
58 return Err(MeshError::TooLarge);
59 }
60 if !self.indices.len().is_multiple_of(3) {
61 return Err(MeshError::InvalidTriangleCount);
62 }
63 let mut bounds = Bounds {
64 min: Vec3::INFINITY,
65 max: Vec3::NEG_INFINITY,
66 };
67 for v in &self.vertices {
68 if !v.position.is_finite() || !v.normal.is_finite() || !v.color.to_vec().is_finite() {
69 return Err(MeshError::NonFiniteVertex);
70 }
71 bounds.min = bounds.min.min(v.position);
72 bounds.max = bounds.max.max(v.position);
73 }
74 if self
75 .indices
76 .iter()
77 .any(|&i| i as usize >= self.vertices.len())
78 {
79 return Err(MeshError::IndexOutOfBounds);
80 }
81 Ok(bounds)
82 }
83 pub fn triangle_count(&self) -> usize {
84 self.indices.len() / 3
85 }
86 pub fn cuboid(size: Vec3, color: Color) -> Self {
88 assert!(size.is_finite() && size.cmpgt(Vec3::ZERO).all());
89 let half = size * 0.5;
90 let mut mesh = Self {
91 vertices: Vec::with_capacity(24),
92 indices: Vec::with_capacity(36),
93 };
94 for (n, u, v) in [
95 (Vec3::X, Vec3::Y, Vec3::Z),
96 (Vec3::NEG_X, Vec3::Y, Vec3::NEG_Z),
97 (Vec3::Y, Vec3::Z, Vec3::X),
98 (Vec3::NEG_Y, Vec3::Z, Vec3::NEG_X),
99 (Vec3::Z, Vec3::X, Vec3::Y),
100 (Vec3::NEG_Z, Vec3::NEG_X, Vec3::Y),
101 ] {
102 let start = mesh.vertices.len() as u32;
103 for (a, b) in [(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)] {
104 mesh.vertices
105 .push(Vertex::new((n + u * a + v * b) * half, n, color));
106 }
107 mesh.indices
108 .extend([start, start + 1, start + 2, start, start + 2, start + 3]);
109 }
110 mesh
111 }
112 pub fn plane(width: f32, depth: f32, subdivisions: u16, color: Color) -> Self {
114 assert!(width.is_finite() && depth.is_finite() && width > 0.0 && depth > 0.0);
115 let n = usize::from(subdivisions.clamp(1, 128));
116 let mut mesh = Self {
117 vertices: Vec::with_capacity((n + 1) * (n + 1)),
118 indices: Vec::with_capacity(n * n * 6),
119 };
120 for z in 0..=n {
121 for x in 0..=n {
122 mesh.vertices.push(Vertex::new(
123 vec3(
124 (x as f32 / n as f32 - 0.5) * width,
125 0.0,
126 (z as f32 / n as f32 - 0.5) * depth,
127 ),
128 Vec3::Y,
129 color,
130 ));
131 }
132 }
133 for z in 0..n {
134 for x in 0..n {
135 let a = (z * (n + 1) + x) as u32;
136 let b = a + (n + 1) as u32;
137 mesh.indices.extend([a, b, b + 1, a, b + 1, a + 1]);
138 }
139 }
140 mesh
141 }
142 pub fn sphere(radius: f32, rings: u16, sectors: u16, color: Color) -> Self {
144 assert!(radius.is_finite() && radius > 0.0);
145 let rings = rings.clamp(2, 64);
146 let sectors = sectors.clamp(3, 128);
147 let mut mesh = Self {
148 vertices: Vec::new(),
149 indices: Vec::new(),
150 };
151 let point = |ring: u16, sector: u16| {
152 let phi = std::f32::consts::PI * ring as f32 / rings as f32;
153 let theta = std::f32::consts::TAU * sector as f32 / sectors as f32;
154 vec3(phi.sin() * theta.cos(), phi.cos(), phi.sin() * theta.sin()) * radius
155 };
156 for ring in 0..rings {
157 for sector in 0..sectors {
158 let a = point(ring, sector);
159 let b = point(ring + 1, sector);
160 let c = point(ring + 1, sector + 1);
161 let d = point(ring, sector + 1);
162 if ring > 0 {
163 mesh.push_outward_triangle(a, c, d, color);
164 }
165 if ring + 1 < rings {
166 mesh.push_outward_triangle(a, b, c, color);
167 }
168 }
169 }
170 mesh
171 }
172 pub fn cylinder(radius: f32, height: f32, sides: u16, color: Color) -> Self {
174 assert!(radius.is_finite() && height.is_finite() && radius > 0.0 && height > 0.0);
175 let sides = sides.clamp(3, 128);
176 let half = height * 0.5;
177 let mut mesh = Self {
178 vertices: Vec::new(),
179 indices: Vec::new(),
180 };
181 for i in 0..sides {
182 let a = std::f32::consts::TAU * i as f32 / sides as f32;
183 let b = std::f32::consts::TAU * (i + 1) as f32 / sides as f32;
184 let p = vec3(a.cos() * radius, -half, a.sin() * radius);
185 let q = vec3(b.cos() * radius, -half, b.sin() * radius);
186 let r = q + Vec3::Y * height;
187 let s = p + Vec3::Y * height;
188 mesh.push_outward_triangle(p, q, r, color);
189 mesh.push_outward_triangle(p, r, s, color);
190 mesh.push_outward_triangle(vec3(0.0, -half, 0.0), q, p, color);
191 mesh.push_outward_triangle(vec3(0.0, half, 0.0), s, r, color);
192 }
193 mesh
194 }
195 fn push_outward_triangle(&mut self, a: Vec3, mut b: Vec3, mut c: Vec3, color: Color) {
196 if (b - a).cross(c - a).dot(a + b + c) < 0.0 {
197 std::mem::swap(&mut b, &mut c);
198 }
199 let normal = (b - a).cross(c - a).normalize_or_zero();
200 let index = self.vertices.len() as u32;
201 for point in [a, b, c] {
202 self.vertices.push(Vertex::new(point, normal, color));
203 }
204 self.indices.extend([index, index + 1, index + 2]);
205 }
206 pub fn bake_vertex_ao(&mut self, mut occlusion: impl FnMut(Vec3, Vec3) -> f32) {
208 for vertex in &mut self.vertices {
209 let factor = occlusion(vertex.position, vertex.normal).clamp(0.0, 1.0);
210 let factor = if factor.is_finite() { factor } else { 1.0 };
211 vertex.color.r *= factor;
212 vertex.color.g *= factor;
213 vertex.color.b *= factor;
214 }
215 }
216}