1use alloc::vec;
2use alloc::vec::Vec;
3use he3d_math::scalar::{cos, sin};
4use he3d_math::{Vec2, Vec3};
5
6use crate::Error;
7
8pub const MAX_VERTEX_COUNT: usize = 16_777_216;
11
12#[derive(Clone, Debug, PartialEq)]
20pub struct Mesh {
21 vertices: Vec<Vec3>,
22 uvs: Vec<Vec2>,
23 triangle_normals: Vec<Vec3>,
24}
25
26impl Mesh {
27 pub fn new(vertices: Vec<Vec3>, uvs: Vec<Vec2>) -> Result<Self, Error> {
36 if vertices.is_empty() || vertices.len() > MAX_VERTEX_COUNT {
37 return Err(Error::InvalidSize);
38 }
39 if vertices.len() % 3 != 0 {
40 return Err(Error::InvalidData);
41 }
42 if uvs.len() != vertices.len() {
43 return Err(Error::BufferMismatch);
44 }
45 let mut mesh = Self {
46 vertices,
47 uvs,
48 triangle_normals: Vec::new(),
49 };
50 mesh.recalculate_normals();
51 Ok(mesh)
52 }
53
54 pub fn from_vertices(vertices: Vec<Vec3>) -> Result<Self, Error> {
62 let count = vertices.len();
63 if count == 0 || count > MAX_VERTEX_COUNT {
64 return Err(Error::InvalidSize);
65 }
66 if count % 3 != 0 {
67 return Err(Error::InvalidData);
68 }
69 Self::new(vertices, vec![Vec2::ZERO; count])
70 }
71
72 #[must_use]
75 pub fn vertex_count(&self) -> usize {
76 self.vertices.len()
77 }
78
79 #[must_use]
82 pub fn triangle_count(&self) -> usize {
83 self.vertices.len() / 3
84 }
85
86 #[must_use]
89 pub fn is_valid(&self) -> bool {
90 !self.vertices.is_empty() && self.vertices.len() % 3 == 0
91 }
92
93 #[must_use]
96 pub fn vertices(&self) -> &[Vec3] {
97 &self.vertices
98 }
99
100 #[must_use]
103 pub fn uvs(&self) -> &[Vec2] {
104 &self.uvs
105 }
106
107 #[must_use]
110 pub fn triangle_normals(&self) -> &[Vec3] {
111 &self.triangle_normals
112 }
113
114 pub fn recalculate_normals(&mut self) {
117 let triangle_count = self.vertices.len() / 3;
118 self.triangle_normals.clear();
119 self.triangle_normals.reserve(triangle_count);
120 for tri in self.vertices.chunks_exact(3) {
121 let e1 = tri[1] - tri[0];
122 let e2 = tri[2] - tri[0];
123 self.triangle_normals.push(e1.cross(e2).normalize_or_zero());
124 }
125 }
126
127 pub fn triangle(width: f32, height: f32) -> Result<Self, Error> {
134 if !positive_finite(width) || !positive_finite(height) {
135 return Err(Error::InvalidSize);
136 }
137 let hx = width * 0.5;
138 let hy = height * 0.5;
139 let vertices = vec![
140 Vec3::new(-hx, -hy, 0.0),
141 Vec3::new(0.0, hy, 0.0),
142 Vec3::new(hx, -hy, 0.0),
143 ];
144 let uvs = vec![
145 Vec2::new(0.0, 0.0),
146 Vec2::new(0.5, 1.0),
147 Vec2::new(1.0, 0.0),
148 ];
149 Self::new(vertices, uvs)
150 }
151
152 pub fn plane(width: f32, depth: f32) -> Result<Self, Error> {
159 if !positive_finite(width) || !positive_finite(depth) {
160 return Err(Error::InvalidSize);
161 }
162 let hx = width * 0.5;
163 let hz = depth * 0.5;
164 let vertices = vec![
165 Vec3::new(-hx, 0.0, -hz),
166 Vec3::new(hx, 0.0, hz),
167 Vec3::new(hx, 0.0, -hz),
168 Vec3::new(-hx, 0.0, -hz),
169 Vec3::new(-hx, 0.0, hz),
170 Vec3::new(hx, 0.0, hz),
171 ];
172 let uvs = vec![
173 Vec2::new(0.0, 0.0),
174 Vec2::new(1.0, 1.0),
175 Vec2::new(1.0, 0.0),
176 Vec2::new(0.0, 0.0),
177 Vec2::new(0.0, 1.0),
178 Vec2::new(1.0, 1.0),
179 ];
180 Self::new(vertices, uvs)
181 }
182
183 pub fn cube(width: f32, height: f32, depth: f32) -> Result<Self, Error> {
190 if !positive_finite(width) || !positive_finite(height) || !positive_finite(depth) {
191 return Err(Error::InvalidSize);
192 }
193 let hx = width * 0.5;
194 let hy = height * 0.5;
195 let hz = depth * 0.5;
196 let p = [
197 Vec3::new(-hx, -hy, -hz),
198 Vec3::new(hx, -hy, -hz),
199 Vec3::new(hx, hy, -hz),
200 Vec3::new(-hx, hy, -hz),
201 Vec3::new(-hx, -hy, hz),
202 Vec3::new(hx, -hy, hz),
203 Vec3::new(hx, hy, hz),
204 Vec3::new(-hx, hy, hz),
205 ];
206 let (uv0, uv1, uv2, uv3) = (
207 Vec2::new(0.0, 0.0),
208 Vec2::new(1.0, 0.0),
209 Vec2::new(1.0, 1.0),
210 Vec2::new(0.0, 1.0),
211 );
212
213 let mut vertices = Vec::with_capacity(36);
214 let mut uvs = Vec::with_capacity(36);
215 let mut emit = |a: usize, b: usize, c: usize, uv: [Vec2; 3]| {
216 push_tri(&mut vertices, &mut uvs, p[a], p[b], p[c], uv);
217 };
218 emit(4, 5, 6, [uv0, uv1, uv2]);
219 emit(4, 6, 7, [uv0, uv2, uv3]);
220 emit(1, 0, 3, [uv0, uv1, uv2]);
221 emit(1, 3, 2, [uv0, uv2, uv3]);
222 emit(0, 4, 7, [uv0, uv1, uv2]);
223 emit(0, 7, 3, [uv0, uv2, uv3]);
224 emit(5, 1, 2, [uv0, uv1, uv2]);
225 emit(5, 2, 6, [uv0, uv2, uv3]);
226 emit(3, 7, 6, [uv0, uv1, uv2]);
227 emit(3, 6, 2, [uv0, uv2, uv3]);
228 emit(0, 1, 5, [uv0, uv1, uv2]);
229 emit(0, 5, 4, [uv0, uv2, uv3]);
230
231 Self::new(vertices, uvs)
232 }
233
234 #[expect(clippy::cast_precision_loss)]
250 pub fn sphere(radius: f32, segments: u32, rings: u32) -> Result<Self, Error> {
251 if !positive_finite(radius) {
252 return Err(Error::InvalidSize);
253 }
254 let segments = segments.max(3);
255 let rings = rings.max(2);
256 let total = (segments as usize)
257 .checked_mul(rings as usize)
258 .and_then(|v| v.checked_mul(6))
259 .ok_or(Error::Overflow)?;
260 if total == 0 || total > MAX_VERTEX_COUNT {
261 return Err(Error::InvalidSize);
262 }
263
264 let tau = core::f32::consts::TAU;
265 let pi = core::f32::consts::PI;
266 let mut vertices = Vec::with_capacity(total);
267 let mut uvs = Vec::with_capacity(total);
268
269 for ring in 0..rings {
270 let v0 = ring as f32 / rings as f32;
271 let v1 = (ring + 1) as f32 / rings as f32;
272 let lat0 = (v0 - 0.5) * pi;
273 let lat1 = (v1 - 0.5) * pi;
274 let y0 = sin(lat0) * radius;
275 let y1 = sin(lat1) * radius;
276 let r0 = cos(lat0) * radius;
277 let r1 = cos(lat1) * radius;
278
279 for segment in 0..segments {
280 let u0 = segment as f32 / segments as f32;
281 let u1 = (segment + 1) as f32 / segments as f32;
282 let lon0 = u0 * tau;
283 let lon1 = u1 * tau;
284
285 let p00 = Vec3::new(sin(lon0) * r0, y0, cos(lon0) * r0);
286 let p01 = Vec3::new(sin(lon1) * r0, y0, cos(lon1) * r0);
287 let p10 = Vec3::new(sin(lon0) * r1, y1, cos(lon0) * r1);
288 let p11 = Vec3::new(sin(lon1) * r1, y1, cos(lon1) * r1);
289
290 push_tri(
291 &mut vertices,
292 &mut uvs,
293 p00,
294 p11,
295 p10,
296 [Vec2::new(u0, v0), Vec2::new(u1, v1), Vec2::new(u0, v1)],
297 );
298 push_tri(
299 &mut vertices,
300 &mut uvs,
301 p00,
302 p01,
303 p11,
304 [Vec2::new(u0, v0), Vec2::new(u1, v0), Vec2::new(u1, v1)],
305 );
306 }
307 }
308
309 Self::new(vertices, uvs)
310 }
311}
312
313fn positive_finite(value: f32) -> bool {
314 value.is_finite() && value > 0.0
315}
316
317fn push_tri(
318 vertices: &mut Vec<Vec3>,
319 uvs: &mut Vec<Vec2>,
320 a: Vec3,
321 b: Vec3,
322 c: Vec3,
323 uv: [Vec2; 3],
324) {
325 vertices.extend([a, b, c]);
326 uvs.extend(uv);
327}
328
329#[cfg(test)]
330mod tests {
331 use alloc::vec;
332
333 use super::*;
334
335 #[test]
336 fn triangle_is_valid() {
337 let mesh = Mesh::triangle(1.0, 1.0).unwrap();
338 assert_eq!(mesh.vertex_count(), 3);
339 assert_eq!(mesh.triangle_count(), 1);
340 assert!(mesh.is_valid());
341 }
342
343 #[test]
344 fn cube_has_twelve_triangles() {
345 let mesh = Mesh::cube(1.0, 1.0, 1.0).unwrap();
346 assert_eq!(mesh.vertex_count(), 36);
347 assert_eq!(mesh.triangle_count(), 12);
348 }
349
350 #[test]
351 fn rejects_empty_mesh() {
352 assert_eq!(Mesh::new(Vec::new(), Vec::new()), Err(Error::InvalidSize));
353 }
354
355 #[test]
356 fn from_vertices_empty_is_invalid_size() {
357 assert_eq!(Mesh::from_vertices(Vec::new()), Err(Error::InvalidSize));
358 }
359
360 #[test]
361 fn rejects_non_triangle_vertex_count() {
362 let v = vec![Vec3::ZERO, Vec3::X, Vec3::Y, Vec3::Z];
363 assert_eq!(Mesh::from_vertices(v), Err(Error::InvalidData));
364 }
365
366 #[test]
367 fn rejects_uv_length_mismatch() {
368 let v = vec![Vec3::ZERO, Vec3::X, Vec3::Y];
369 let uv = vec![Vec2::ZERO];
370 assert_eq!(Mesh::new(v, uv), Err(Error::BufferMismatch));
371 }
372
373 #[test]
374 fn rejects_zero_or_negative_primitive_sizes() {
375 assert_eq!(Mesh::triangle(0.0, 1.0), Err(Error::InvalidSize));
376 assert_eq!(Mesh::plane(-1.0, 1.0), Err(Error::InvalidSize));
377 assert_eq!(Mesh::cube(1.0, 1.0, f32::NAN), Err(Error::InvalidSize));
378 assert_eq!(Mesh::sphere(0.0, 8, 4), Err(Error::InvalidSize));
379 }
380
381 #[test]
382 fn sphere_rejects_overflow() {
383 assert!(Mesh::sphere(1.0, u32::MAX, u32::MAX).is_err());
384 }
385
386 #[test]
387 fn sphere_vertex_count_matches_tessellation() {
388 let segments = 16u32;
389 let rings = 8u32;
390 let mesh = Mesh::sphere(0.5, segments, rings).unwrap();
391 assert_eq!(mesh.vertex_count(), (segments * rings * 6) as usize);
392 assert_eq!(mesh.triangle_normals().len(), mesh.triangle_count());
393 }
394}