1use ranim_core::{
4 Extract,
5 anchor::Aabb,
6 color::{AlphaColor, Srgb},
7 components::{PointVec, rgba::Rgba},
8 core_item::CoreItem,
9 glam::{DVec3, Mat4},
10 traits::{Alignable, ApplyTransform, Empty, FillColor, Interpolatable, Opacity},
11};
12
13mod sphere;
14mod surface;
15
16#[cfg(feature = "gltf")]
18pub mod gltf;
19
20pub use sphere::*;
21pub use surface::*;
22
23#[derive(Debug, Clone, PartialEq)]
34pub struct MeshItem {
35 pub points: PointVec<DVec3>,
37 pub triangle_indices: Vec<u32>,
39 pub vertex_colors: PointVec<Rgba>,
41 pub vertex_normals: PointVec<DVec3>,
44}
45
46impl MeshItem {
47 pub fn from_vertices(points: Vec<DVec3>) -> Self {
49 let len = points.len();
50 Self {
51 points: points.into(),
52 triangle_indices: Vec::new(),
53 vertex_colors: vec![Rgba::default(); len].into(),
54 vertex_normals: vec![DVec3::ZERO; len].into(),
55 }
56 }
57
58 pub fn from_indexed_vertices(points: Vec<DVec3>, triangle_indices: Vec<u32>) -> Self {
60 let len = points.len();
61 Self {
62 points: points.into(),
63 triangle_indices,
64 vertex_colors: vec![Rgba::default(); len].into(),
65 vertex_normals: vec![DVec3::ZERO; len].into(),
66 }
67 }
68
69 pub fn with_color(mut self, color: AlphaColor<Srgb>) -> Self {
71 let rgba: Rgba = color.into();
72 self.vertex_colors = vec![rgba; self.points.len()].into();
73 self
74 }
75}
76
77impl From<MeshItem> for ranim_core::core_item::mesh_item::MeshItem {
78 fn from(value: MeshItem) -> Self {
79 Self {
80 points: value.points.iter().map(|p| p.as_vec3()).collect(),
81 triangle_indices: value.triangle_indices,
82 transform: Mat4::IDENTITY,
83 vertex_colors: value.vertex_colors.iter().copied().collect(),
84 vertex_normals: value.vertex_normals.iter().map(|n| n.as_vec3()).collect(),
85 }
86 }
87}
88
89impl Extract for MeshItem {
90 type Target = CoreItem;
91 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
92 buf.push(CoreItem::MeshItem(self.clone().into()));
93 }
94}
95
96impl<G: Into<ranim_core::glam::DAffine3>> ApplyTransform<G> for MeshItem {
97 fn apply(&mut self, transform: G) -> &mut Self {
98 let transform = transform.into();
99 self.points.apply(transform);
100 if transform.matrix3.determinant().abs() > 1e-12 {
101 let normal_matrix = transform.matrix3.inverse().transpose();
102 self.vertex_normals.iter_mut().for_each(|normal| {
103 if let Some(unit) = (normal_matrix * *normal).try_normalize() {
104 *normal = unit;
105 }
106 });
107 }
108 self
109 }
110}
111
112impl Alignable for MeshItem {
113 fn is_aligned(&self, other: &Self) -> bool {
114 self.points.is_aligned(&other.points)
115 && self.vertex_colors.is_aligned(&other.vertex_colors)
116 && self.vertex_normals.is_aligned(&other.vertex_normals)
117 }
118
119 fn align_with(&mut self, other: &mut Self) {
120 self.points.align_with(&mut other.points);
121 self.vertex_colors.align_with(&mut other.vertex_colors);
122 self.vertex_normals.align_with(&mut other.vertex_normals);
123 }
124}
125
126impl Interpolatable for MeshItem {
127 fn lerp(&self, target: &Self, t: f64) -> Self {
128 Self {
129 points: self.points.lerp(&target.points, t),
130 triangle_indices: if t < 0.5 {
131 self.triangle_indices.clone()
132 } else {
133 target.triangle_indices.clone()
134 },
135 vertex_colors: self.vertex_colors.lerp(&target.vertex_colors, t),
136 vertex_normals: self.vertex_normals.lerp(&target.vertex_normals, t),
137 }
138 }
139}
140
141impl FillColor for MeshItem {
142 fn fill_color(&self) -> AlphaColor<Srgb> {
143 let Rgba(rgba) = self.vertex_colors.first().cloned().unwrap_or_default();
144 AlphaColor::new([rgba.x, rgba.y, rgba.z, rgba.w])
145 }
146
147 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
148 let rgba: Rgba = color.into();
149 self.vertex_colors.iter_mut().for_each(|c| *c = rgba);
150 self
151 }
152
153 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
154 self.vertex_colors.set_opacity(opacity);
155 self
156 }
157}
158
159impl Opacity for MeshItem {
160 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
161 self.vertex_colors.set_opacity(opacity);
162 self
163 }
164}
165
166impl Aabb for MeshItem {
167 fn aabb(&self) -> [DVec3; 2] {
168 if self.points.is_empty() {
169 return [DVec3::ZERO, DVec3::ZERO];
170 }
171
172 let mut min = self.points[0];
174 let mut max = self.points[0];
175
176 for &p in &self.points[1..] {
177 min = min.min(p);
178 max = max.max(p);
179 }
180
181 [min, max]
182 }
183}
184
185impl Empty for MeshItem {
186 fn empty() -> Self {
187 Self {
188 points: Vec::new().into(),
189 triangle_indices: Vec::new(),
190 vertex_colors: Vec::new().into(),
191 vertex_normals: Vec::new().into(),
192 }
193 }
194}
195
196pub fn compute_smooth_normals(points: &[DVec3], triangle_indices: &[u32]) -> Vec<DVec3> {
201 let mut normals = vec![DVec3::ZERO; points.len()];
202
203 for tri in triangle_indices.as_chunks::<3>().0 {
204 let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
205 let (p0, p1, p2) = (points[i0], points[i1], points[i2]);
206
207 let e01 = p1 - p0;
208 let e02 = p2 - p0;
209 let face_normal = e01.cross(e02);
210
211 if face_normal.length_squared() < 1e-20 {
213 continue;
214 }
215
216 let e10 = p0 - p1;
218 let e12 = p2 - p1;
219 let e20 = p0 - p2;
220 let e21 = p1 - p2;
221
222 let angle0 = angle_between(e01, e02);
223 let angle1 = angle_between(e10, e12);
224 let angle2 = angle_between(e20, e21);
225
226 normals[i0] += face_normal * angle0;
227 normals[i1] += face_normal * angle1;
228 normals[i2] += face_normal * angle2;
229 }
230
231 for n in &mut normals {
232 let len = n.length();
233 if len > 1e-10 {
234 *n /= len;
235 }
236 }
237
238 normals
239}
240
241fn angle_between(a: DVec3, b: DVec3) -> f64 {
243 let denom = a.length() * b.length();
244 if denom < 1e-20 {
245 return 0.0;
246 }
247 (a.dot(b) / denom).clamp(-1.0, 1.0).acos()
248}
249
250pub fn generate_grid_indices(nu: u32, nv: u32) -> Vec<u32> {
257 let mut indices = Vec::with_capacity(6 * (nu as usize - 1) * (nv as usize - 1));
258 for i in 0..nu - 1 {
259 for j in 0..nv - 1 {
260 let tl = i * nv + j;
261 let tr = i * nv + j + 1;
262 let bl = (i + 1) * nv + j;
263 let br = (i + 1) * nv + j + 1;
264 indices.push(tl);
266 indices.push(bl);
267 indices.push(tr);
268 indices.push(tr);
270 indices.push(bl);
271 indices.push(br);
272 }
273 }
274 indices
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use ranim_core::{
281 anchor::Aabb,
282 color::palette::css,
283 glam::DVec3,
284 traits::{Alignable, Empty},
285 };
286
287 #[test]
288 fn generate_grid_indices_follows_quad_layout() {
289 let indices = generate_grid_indices(2, 2);
292 assert_eq!(indices, vec![0, 2, 1, 1, 2, 3]);
293
294 let nu = 10;
296 let nv = 5;
297 assert_eq!(
298 generate_grid_indices(nu, nv).len(),
299 6 * (nu as usize - 1) * (nv as usize - 1)
300 );
301 }
302
303 #[test]
304 fn test_mesh_item_alignable() {
305 let mut mesh1 = MeshItem::from_indexed_vertices(
306 vec![DVec3::new(0.0, 0.0, 0.0), DVec3::new(1.0, 0.0, 0.0)],
307 vec![0, 1, 2],
308 );
309
310 let mut mesh2 = MeshItem::from_indexed_vertices(
311 vec![
312 DVec3::new(0.0, 0.0, 0.0),
313 DVec3::new(1.0, 0.0, 0.0),
314 DVec3::new(0.0, 1.0, 0.0),
315 DVec3::new(1.0, 1.0, 0.0),
316 ],
317 vec![0, 1, 2, 1, 3, 2],
318 );
319
320 assert!(!mesh1.is_aligned(&mesh2));
322
323 mesh1.align_with(&mut mesh2);
325
326 assert!(mesh1.is_aligned(&mesh2));
328
329 assert_eq!(mesh1.points.len(), 4);
331 assert_eq!(mesh2.points.len(), 4);
332 assert_eq!(mesh1.vertex_colors.len(), 4);
333 assert_eq!(mesh2.vertex_colors.len(), 4);
334 assert_eq!(mesh1.vertex_normals.len(), 4);
335 assert_eq!(mesh2.vertex_normals.len(), 4);
336
337 assert_eq!(mesh1.points[2], DVec3::new(1.0, 0.0, 0.0));
339 assert_eq!(mesh1.points[3], DVec3::new(1.0, 0.0, 0.0));
340
341 assert_eq!(mesh2.points[0], DVec3::new(0.0, 0.0, 0.0));
343 assert_eq!(mesh2.points[3], DVec3::new(1.0, 1.0, 0.0));
344 }
345
346 #[test]
347 fn test_mesh_item_interpolate() {
348 use ranim_core::traits::Interpolatable;
349
350 let mut mesh1 = MeshItem::from_indexed_vertices(
351 vec![DVec3::new(0.0, 0.0, 0.0), DVec3::new(1.0, 0.0, 0.0)],
352 vec![0, 1, 2],
353 )
354 .with_color(css::RED.with_alpha(1.0));
355
356 let mut mesh2 = MeshItem::from_indexed_vertices(
357 vec![DVec3::new(2.0, 0.0, 0.0), DVec3::new(3.0, 0.0, 0.0)],
358 vec![0, 1, 3],
359 )
360 .with_color(css::GREEN.with_alpha(1.0));
361
362 mesh1.align_with(&mut mesh2);
364
365 let interpolated = mesh1.lerp(&mesh2, 0.5);
367
368 assert_eq!(interpolated.points[0], DVec3::new(1.0, 0.0, 0.0));
370 assert_eq!(interpolated.points[1], DVec3::new(2.0, 0.0, 0.0));
371
372 assert_eq!(interpolated.triangle_indices, vec![0, 1, 3]);
374 }
375
376 #[test]
377 fn mesh_item_empty_and_bounds() {
378 let mesh = MeshItem::empty();
379 assert_eq!(mesh.points.len(), 0);
380 assert_eq!(mesh.triangle_indices.len(), 0);
381 assert_eq!(mesh.vertex_colors.len(), 0);
382 assert_eq!(mesh.vertex_normals.len(), 0);
383
384 let mesh = MeshItem::from_indexed_vertices(
385 vec![
386 DVec3::new(-1.0, -1.0, -1.0),
387 DVec3::new(1.0, -1.0, -1.0),
388 DVec3::new(1.0, 1.0, -1.0),
389 DVec3::new(-1.0, 1.0, 1.0),
390 ],
391 vec![0, 1, 2],
392 );
393 let [min, max] = mesh.aabb();
394 assert_eq!(min, DVec3::splat(-1.0));
395 assert_eq!(max, DVec3::splat(1.0));
396 }
397}