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, Vec3},
10 traits::{
11 Alignable, Empty, FillColor, Interpolatable, Opacity, RotateTransform, ScaleTransform,
12 ShiftTransform,
13 },
14};
15
16mod sphere;
17mod surface;
18
19pub use sphere::*;
20pub use surface::*;
21
22#[derive(Debug, Clone, PartialEq)]
28pub struct MeshItem {
29 pub points: PointVec<Vec3>,
31 pub triangle_indices: Vec<u32>,
33 pub transform: Mat4,
35 pub vertex_colors: PointVec<Rgba>,
37 pub vertex_normals: PointVec<Vec3>,
40}
41
42impl MeshItem {
43 pub fn from_vertices(points: Vec<Vec3>) -> Self {
45 let len = points.len();
46 Self {
47 points: points.into(),
48 triangle_indices: Vec::new(),
49 transform: Mat4::IDENTITY,
50 vertex_colors: vec![Rgba::default(); len].into(),
51 vertex_normals: vec![Vec3::ZERO; len].into(),
52 }
53 }
54
55 pub fn from_indexed_vertices(points: Vec<Vec3>, triangle_indices: Vec<u32>) -> Self {
57 let len = points.len();
58 Self {
59 points: points.into(),
60 triangle_indices,
61 transform: Mat4::IDENTITY,
62 vertex_colors: vec![Rgba::default(); len].into(),
63 vertex_normals: vec![Vec3::ZERO; len].into(),
64 }
65 }
66
67 pub fn with_transform(mut self, transform: Mat4) -> Self {
69 self.transform = transform;
70 self
71 }
72
73 pub fn with_color(mut self, color: AlphaColor<Srgb>) -> Self {
75 let rgba: Rgba = color.into();
76 self.vertex_colors = vec![rgba; self.points.len()].into();
77 self
78 }
79}
80
81impl From<MeshItem> for ranim_core::core_item::mesh_item::MeshItem {
82 fn from(value: MeshItem) -> Self {
83 Self {
84 points: value.points.iter().copied().collect(),
85 triangle_indices: value.triangle_indices,
86 transform: value.transform,
87 vertex_colors: value.vertex_colors.iter().copied().collect(),
88 vertex_normals: value.vertex_normals.iter().copied().collect(),
89 }
90 }
91}
92
93impl Extract for MeshItem {
94 type Target = CoreItem;
95 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
96 buf.push(CoreItem::MeshItem(self.clone().into()));
97 }
98}
99
100impl Alignable for MeshItem {
101 fn is_aligned(&self, other: &Self) -> bool {
102 self.points.is_aligned(&other.points)
103 && self.vertex_colors.is_aligned(&other.vertex_colors)
104 && self.vertex_normals.is_aligned(&other.vertex_normals)
105 }
106
107 fn align_with(&mut self, other: &mut Self) {
108 self.points.align_with(&mut other.points);
109 self.vertex_colors.align_with(&mut other.vertex_colors);
110 self.vertex_normals.align_with(&mut other.vertex_normals);
111 }
112}
113
114impl Interpolatable for MeshItem {
115 fn lerp(&self, target: &Self, t: f64) -> Self {
116 Self {
117 points: self.points.lerp(&target.points, t),
118 triangle_indices: if t < 0.5 {
119 self.triangle_indices.clone()
120 } else {
121 target.triangle_indices.clone()
122 },
123 transform: self.transform.lerp(&target.transform, t),
124 vertex_colors: self.vertex_colors.lerp(&target.vertex_colors, t),
125 vertex_normals: self.vertex_normals.lerp(&target.vertex_normals, t),
126 }
127 }
128}
129
130impl FillColor for MeshItem {
131 fn fill_color(&self) -> AlphaColor<Srgb> {
132 let Rgba(rgba) = self.vertex_colors.first().cloned().unwrap_or_default();
133 AlphaColor::new([rgba.x, rgba.y, rgba.z, rgba.w])
134 }
135
136 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
137 let rgba: Rgba = color.into();
138 self.vertex_colors.iter_mut().for_each(|c| *c = rgba);
139 self
140 }
141
142 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
143 self.vertex_colors.set_opacity(opacity);
144 self
145 }
146}
147
148impl Opacity for MeshItem {
149 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
150 self.vertex_colors.set_opacity(opacity);
151 self
152 }
153}
154
155impl Aabb for MeshItem {
156 fn aabb(&self) -> [DVec3; 2] {
157 if self.points.is_empty() {
158 return [DVec3::ZERO, DVec3::ZERO];
159 }
160
161 let transform = self.transform.as_dmat4();
163
164 let transformed_points: Vec<DVec3> = self
167 .points
168 .iter()
169 .map(|&p| transform.transform_point3(p.as_dvec3()))
170 .collect();
171
172 let mut min = transformed_points[0];
173 let mut max = transformed_points[0];
174
175 for &p in &transformed_points[1..] {
176 min = min.min(p);
177 max = max.max(p);
178 }
179
180 [min, max]
181 }
182}
183
184impl ShiftTransform for MeshItem {
185 fn shift(&mut self, offset: DVec3) -> &mut Self {
186 let translation = Mat4::from_translation(offset.as_vec3());
188 self.transform = translation * self.transform;
189 self
190 }
191}
192
193impl RotateTransform for MeshItem {
194 fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
195 let rotation = Mat4::from_axis_angle(axis.as_vec3().normalize(), angle as f32);
197 self.transform = rotation * self.transform;
198 self
199 }
200}
201
202impl ScaleTransform for MeshItem {
203 fn scale(&mut self, scale: DVec3) -> &mut Self {
204 let scale_mat = Mat4::from_scale(scale.as_vec3());
206 self.transform = scale_mat * self.transform;
207 self
208 }
209}
210
211impl Empty for MeshItem {
212 fn empty() -> Self {
213 Self {
214 points: Vec::new().into(),
215 triangle_indices: Vec::new(),
216 transform: Mat4::IDENTITY,
217 vertex_colors: Vec::new().into(),
218 vertex_normals: Vec::new().into(),
219 }
220 }
221}
222
223pub fn compute_smooth_normals(points: &[DVec3], triangle_indices: &[u32]) -> Vec<DVec3> {
228 let mut normals = vec![DVec3::ZERO; points.len()];
229
230 for tri in triangle_indices.chunks_exact(3) {
231 let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
232 let (p0, p1, p2) = (points[i0], points[i1], points[i2]);
233
234 let e01 = p1 - p0;
235 let e02 = p2 - p0;
236 let face_normal = e01.cross(e02);
237
238 if face_normal.length_squared() < 1e-20 {
240 continue;
241 }
242
243 let e10 = p0 - p1;
245 let e12 = p2 - p1;
246 let e20 = p0 - p2;
247 let e21 = p1 - p2;
248
249 let angle0 = angle_between(e01, e02);
250 let angle1 = angle_between(e10, e12);
251 let angle2 = angle_between(e20, e21);
252
253 normals[i0] += face_normal * angle0;
254 normals[i1] += face_normal * angle1;
255 normals[i2] += face_normal * angle2;
256 }
257
258 for n in &mut normals {
259 let len = n.length();
260 if len > 1e-10 {
261 *n /= len;
262 }
263 }
264
265 normals
266}
267
268fn angle_between(a: DVec3, b: DVec3) -> f64 {
270 let denom = a.length() * b.length();
271 if denom < 1e-20 {
272 return 0.0;
273 }
274 (a.dot(b) / denom).clamp(-1.0, 1.0).acos()
275}
276
277pub fn generate_grid_indices(nu: u32, nv: u32) -> Vec<u32> {
284 let mut indices = Vec::with_capacity(6 * (nu as usize - 1) * (nv as usize - 1));
285 for i in 0..nu - 1 {
286 for j in 0..nv - 1 {
287 let tl = i * nv + j;
288 let tr = i * nv + j + 1;
289 let bl = (i + 1) * nv + j;
290 let br = (i + 1) * nv + j + 1;
291 indices.push(tl);
293 indices.push(bl);
294 indices.push(tr);
295 indices.push(tr);
297 indices.push(bl);
298 indices.push(br);
299 }
300 }
301 indices
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use ranim_core::{
308 anchor::Aabb,
309 color::palette::css,
310 glam::{Mat4, Vec3},
311 traits::{Alignable, Empty, RotateTransform, ScaleTransform, ShiftTransform},
312 };
313
314 #[test]
315 fn test_generate_grid_indices_2x2() {
316 let indices = generate_grid_indices(2, 2);
318 assert_eq!(indices.len(), 6);
319 assert_eq!(indices, vec![0, 2, 1, 1, 2, 3]);
321 }
322
323 #[test]
324 fn test_generate_grid_indices_3x3() {
325 let indices = generate_grid_indices(3, 3);
327 assert_eq!(indices.len(), 24);
328 }
329
330 #[test]
331 fn test_generate_grid_indices_count() {
332 let nu = 10;
333 let nv = 5;
334 let indices = generate_grid_indices(nu, nv);
335 assert_eq!(indices.len(), 6 * (nu as usize - 1) * (nv as usize - 1));
336 }
337
338 #[test]
339 fn test_mesh_item_alignable() {
340 let mut mesh1 = MeshItem::from_indexed_vertices(
341 vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)],
342 vec![0, 1, 2],
343 );
344
345 let mut mesh2 = MeshItem::from_indexed_vertices(
346 vec![
347 Vec3::new(0.0, 0.0, 0.0),
348 Vec3::new(1.0, 0.0, 0.0),
349 Vec3::new(0.0, 1.0, 0.0),
350 Vec3::new(1.0, 1.0, 0.0),
351 ],
352 vec![0, 1, 2, 1, 3, 2],
353 );
354
355 assert!(!mesh1.is_aligned(&mesh2));
357
358 mesh1.align_with(&mut mesh2);
360
361 assert!(mesh1.is_aligned(&mesh2));
363
364 assert_eq!(mesh1.points.len(), 4);
366 assert_eq!(mesh2.points.len(), 4);
367 assert_eq!(mesh1.vertex_colors.len(), 4);
368 assert_eq!(mesh2.vertex_colors.len(), 4);
369 assert_eq!(mesh1.vertex_normals.len(), 4);
370 assert_eq!(mesh2.vertex_normals.len(), 4);
371
372 assert_eq!(mesh1.points[2], Vec3::new(1.0, 0.0, 0.0));
374 assert_eq!(mesh1.points[3], Vec3::new(1.0, 0.0, 0.0));
375
376 assert_eq!(mesh2.points[0], Vec3::new(0.0, 0.0, 0.0));
378 assert_eq!(mesh2.points[3], Vec3::new(1.0, 1.0, 0.0));
379 }
380
381 #[test]
382 fn test_mesh_item_interpolate() {
383 use ranim_core::traits::Interpolatable;
384
385 let mut mesh1 = MeshItem::from_indexed_vertices(
386 vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)],
387 vec![0, 1, 2],
388 )
389 .with_color(css::RED.with_alpha(1.0));
390
391 let mut mesh2 = MeshItem::from_indexed_vertices(
392 vec![Vec3::new(2.0, 0.0, 0.0), Vec3::new(3.0, 0.0, 0.0)],
393 vec![0, 1, 3],
394 )
395 .with_color(css::GREEN.with_alpha(1.0))
396 .with_transform(Mat4::from_translation(Vec3::new(1.0, 0.0, 0.0)));
397
398 mesh1.align_with(&mut mesh2);
400
401 let interpolated = mesh1.lerp(&mesh2, 0.5);
403
404 assert_eq!(interpolated.points[0], Vec3::new(1.0, 0.0, 0.0));
406 assert_eq!(interpolated.points[1], Vec3::new(2.0, 0.0, 0.0));
407
408 assert_eq!(interpolated.triangle_indices, vec![0, 1, 3]);
410
411 assert_eq!(
413 interpolated.transform,
414 Mat4::from_translation(Vec3::new(0.5, 0.0, 0.0))
415 );
416 }
417
418 #[test]
419 fn test_mesh_item_aabb() {
420 use ranim_core::glam::dvec3;
421
422 let mesh = MeshItem::from_indexed_vertices(
423 vec![
424 Vec3::new(-1.0, -1.0, -1.0),
425 Vec3::new(1.0, -1.0, -1.0),
426 Vec3::new(1.0, 1.0, -1.0),
427 Vec3::new(-1.0, 1.0, 1.0),
428 ],
429 vec![0, 1, 2],
430 );
431
432 let [min, max] = mesh.aabb();
433 assert_eq!(min, dvec3(-1.0, -1.0, -1.0));
434 assert_eq!(max, dvec3(1.0, 1.0, 1.0));
435 }
436
437 #[test]
438 fn test_mesh_item_shift() {
439 use ranim_core::glam::dvec3;
440
441 let mut mesh = MeshItem::from_indexed_vertices(
442 vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)],
443 vec![0, 1],
444 );
445
446 mesh.shift(dvec3(1.0, 2.0, 3.0));
447
448 let [min, _max] = mesh.aabb();
450 assert!((min.x - 1.0).abs() < 1e-5);
451 assert!((min.y - 2.0).abs() < 1e-5);
452 assert!((min.z - 3.0).abs() < 1e-5);
453 }
454
455 #[test]
456 fn test_mesh_item_scale() {
457 use ranim_core::glam::dvec3;
458
459 let mut mesh = MeshItem::from_indexed_vertices(
460 vec![Vec3::new(1.0, 1.0, 1.0), Vec3::new(2.0, 2.0, 2.0)],
461 vec![0, 1],
462 );
463
464 mesh.scale(dvec3(2.0, 2.0, 2.0));
465
466 let [min, max] = mesh.aabb();
468 assert!((min.x - 2.0).abs() < 1e-5);
469 assert!((max.x - 4.0).abs() < 1e-5);
470 }
471
472 #[test]
473 fn test_mesh_item_rotate() {
474 use ranim_core::glam::dvec3;
475 use std::f64::consts::PI;
476
477 let mut mesh = MeshItem::from_indexed_vertices(vec![Vec3::new(1.0, 0.0, 0.0)], vec![]);
478
479 mesh.rotate_on_axis(dvec3(0.0, 0.0, 1.0), PI / 2.0);
481
482 let [min, _max] = mesh.aabb();
483 assert!(min.x.abs() < 1e-5);
485 assert!((min.y - 1.0).abs() < 1e-5);
486 }
487
488 #[test]
489 fn test_mesh_item_empty() {
490 let mesh = MeshItem::empty();
491 assert_eq!(mesh.points.len(), 0);
492 assert_eq!(mesh.triangle_indices.len(), 0);
493 assert_eq!(mesh.vertex_colors.len(), 0);
494 assert_eq!(mesh.vertex_normals.len(), 0);
495 }
496}