Skip to main content

he3d_core/
mesh.rs

1use alloc::vec;
2use alloc::vec::Vec;
3use he3d_math::scalar::{cos, sin};
4use he3d_math::{Vec2, Vec3};
5
6use crate::Error;
7
8/// Maximum number of vertices a mesh may declare.
9/// 一个网格可声明的最大顶点数。
10pub const MAX_VERTEX_COUNT: usize = 16_777_216;
11
12/// A triangle mesh owning its vertices, UVs, and per-triangle normals.
13/// 一个拥有顶点、UV 和逐三角形法线的三角形网格。
14///
15/// Storage is a flat vertex list where every three consecutive vertices form a triangle (no index buffer).
16/// 存储是一个扁平的顶点列表,每三个连续顶点构成一个三角形(没有索引缓冲)。
17/// `uvs.len() == vertices.len()` and `triangle_normals.len() == vertices.len()
18/// 3` are invariants maintained by [`Mesh::new`]. / `uvs.len() == vertices.len()` 与 `triangle_normals.len() == vertices.len() / 3` 是由 [`Mesh::new`] 维持的不变式。
19#[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   /// Construct a mesh, validating invariants and computing flat normals.
28   /// 构造一个网格,校验不变式并计算平面法线。
29   ///
30   /// # Errors
31   /// 顶点列表为空、超过 [`MAX_VERTEX_COUNT`]、顶点数不是 3 的倍数,或 UV 数量与顶点数不符时返回相应错误。
32   /// Returns an error when the vertex list is empty or exceeds [`MAX_VERTEX_COUNT`],
33   /// when the vertex count is not a multiple of three, or when the UV count
34   /// does not match the vertex count.
35   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   /// Construct a mesh from vertices only, defaulting UVs to zero.
55   /// 仅从顶点构造网格,UV 默认置零。
56   ///
57   /// # Errors
58   /// 顶点列表为空、超过 [`MAX_VERTEX_COUNT`],或顶点数不是 3 的倍数时返回相应错误。
59   /// Returns an error when the vertex list is empty or exceeds [`MAX_VERTEX_COUNT`],
60   /// or when the vertex count is not a multiple of three.
61   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   /// The number of vertices.
73   /// 顶点数量。
74   #[must_use]
75   pub fn vertex_count(&self) -> usize {
76      self.vertices.len()
77   }
78
79   /// The number of triangles.
80   /// 三角形数量。
81   #[must_use]
82   pub fn triangle_count(&self) -> usize {
83      self.vertices.len() / 3
84   }
85
86   /// Whether the mesh has at least one full triangle.
87   /// 网格是否至少包含一个完整三角形。
88   #[must_use]
89   pub fn is_valid(&self) -> bool {
90      !self.vertices.is_empty() && self.vertices.len() % 3 == 0
91   }
92
93   /// The raw vertex list.
94   /// 原始顶点列表。
95   #[must_use]
96   pub fn vertices(&self) -> &[Vec3] {
97      &self.vertices
98   }
99
100   /// The per-vertex UV list.
101   /// 逐顶点 UV 列表。
102   #[must_use]
103   pub fn uvs(&self) -> &[Vec2] {
104      &self.uvs
105   }
106
107   /// The per-triangle flat normals.
108   /// 逐三角形平面法线。
109   #[must_use]
110   pub fn triangle_normals(&self) -> &[Vec3] {
111      &self.triangle_normals
112   }
113
114   /// Recompute flat per-triangle normals from the current vertices.
115   /// 根据当前顶点重新计算逐三角形平面法线。
116   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   /// A single triangle lying in the XY plane.
128   /// 一个位于 XY 平面上的三角形。
129   ///
130   /// # Errors
131   /// `width` 或 `height` 不是正有限数时返回 [`Error::InvalidSize`]。
132   /// Returns [`Error::InvalidSize`] when `width` or `height` is not a positive finite number.
133   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   /// A flat quad on the XZ plane (two triangles).
153   /// XZ 平面上的一个平面四边形(两个三角形)。
154   ///
155   /// # Errors
156   /// `width` 或 `depth` 不是正有限数时返回 [`Error::InvalidSize`]。
157   /// Returns [`Error::InvalidSize`] when `width` or `depth` is not a positive finite number.
158   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   /// An axis-aligned cube (12 triangles, 36 vertices).
184   /// 一个轴对齐的立方体(12 个三角形,36 个顶点)。
185   ///
186   /// # Errors
187   /// 任一尺寸不是正有限数时返回 [`Error::InvalidSize`]。
188   /// Returns [`Error::InvalidSize`] when any dimension is not a positive finite number.
189   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   /// A UV sphere tessellated into `segments` × `rings` quads.
235   /// 一个被细分为 `segments` × `rings` 个四边形的 UV 球体。
236   ///
237   /// Sizing is checked: the total vertex count must fit within [`MAX_VERTEX_COUNT`] without overflowing.
238   /// 尺寸经过校验:顶点总数必须在不溢出的前提下落在 [`MAX_VERTEX_COUNT`] 之内。
239   ///
240   /// # Errors
241   /// `radius` 不是正有限数、总顶点数超过 [`MAX_VERTEX_COUNT`],或计算过程溢出时返回相应错误。
242   /// Returns an error when `radius` is not a positive finite number, when the total
243   /// vertex count exceeds [`MAX_VERTEX_COUNT`], or when the computation overflows.
244   // UV coordinates are built from loop indices bounded by `segments`/`rings`; the
245   // f32 mantissa is far larger than any realistic tessellation count, so the
246   // conversion cannot meaningfully lose precision here.
247   // UV 坐标由受 `segments`/`rings` 约束的循环索引构造;f32 的有效数字远大于任何
248   // 现实的细分数量,因此这里的转换不会产生有意义的精度损失。
249   #[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}