1#[cfg(feature = "render")]
4use ranim_render::extract::{Extract, ExtractContext};
5
6use ranim_core::{
7 anchor::Aabb,
8 color::{AlphaColor, Srgb},
9 components::{PointVec, rgba::Rgba},
10 glam::DVec3,
11 traits::{Alignable, ApplyTransform, Empty, FillColor, Interpolatable, Opacity},
12};
13
14#[cfg(feature = "render")]
16pub mod render;
17
18mod sphere;
19mod surface;
20
21#[cfg(feature = "gltf")]
23pub mod gltf;
24
25pub use sphere::*;
26pub use surface::*;
27
28#[cfg(feature = "render")]
34pub struct MeshItemPlugin;
35
36#[cfg(feature = "render")]
37impl bevy_app::Plugin for MeshItemPlugin {
38 fn build(&self, app: &mut bevy_app::App) {
39 render::install(app);
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
54pub struct MeshItem {
55 pub points: PointVec<DVec3>,
57 pub triangle_indices: Vec<u32>,
59 pub vertex_colors: PointVec<Rgba>,
61 pub vertex_normals: PointVec<DVec3>,
64}
65
66impl MeshItem {
67 pub fn from_vertices(points: Vec<DVec3>) -> Self {
69 let len = points.len();
70 Self {
71 points: points.into(),
72 triangle_indices: Vec::new(),
73 vertex_colors: vec![Rgba::default(); len].into(),
74 vertex_normals: vec![DVec3::ZERO; len].into(),
75 }
76 }
77
78 pub fn from_indexed_vertices(points: Vec<DVec3>, triangle_indices: Vec<u32>) -> Self {
80 let len = points.len();
81 Self {
82 points: points.into(),
83 triangle_indices,
84 vertex_colors: vec![Rgba::default(); len].into(),
85 vertex_normals: vec![DVec3::ZERO; len].into(),
86 }
87 }
88
89 pub fn with_color(mut self, color: AlphaColor<Srgb>) -> Self {
91 let rgba: Rgba = color.into();
92 self.vertex_colors = vec![rgba; self.points.len()].into();
93 self
94 }
95}
96
97#[cfg(feature = "render")]
98impl From<MeshItem> for render::RenderMeshItem {
99 fn from(value: MeshItem) -> Self {
100 Self {
101 points: value.points.iter().map(|p| p.as_vec3()).collect(),
102 triangle_indices: value.triangle_indices,
103 transform: ranim_core::glam::Mat4::IDENTITY,
104 vertex_colors: value.vertex_colors.iter().copied().collect(),
105 vertex_normals: value.vertex_normals.iter().map(|n| n.as_vec3()).collect(),
106 }
107 }
108}
109
110#[cfg(feature = "render")]
111impl Extract for MeshItem {
112 fn extract(&self, ctx: &mut ExtractContext<'_>) {
113 let mut value = render::RenderMeshItem::from(self.clone());
114 value.transform =
115 ranim_core::glam::DMat4::from(ctx.transform()).as_mat4() * value.transform;
116 let mut entity = ctx.entity::<render::RenderMeshItem>();
117 if entity.get::<render::RenderMeshItem>() != Some(&value) {
118 entity.insert(value);
119 }
120 }
121}
122
123impl<G: Into<ranim_core::glam::DAffine3>> ApplyTransform<G> for MeshItem {
124 fn apply(&mut self, transform: G) -> &mut Self {
125 let transform = transform.into();
126 self.points.apply(transform);
127 if transform.matrix3.determinant().abs() > 1e-12 {
128 let normal_matrix = transform.matrix3.inverse().transpose();
129 self.vertex_normals.iter_mut().for_each(|normal| {
130 if let Some(unit) = (normal_matrix * *normal).try_normalize() {
131 *normal = unit;
132 }
133 });
134 }
135 self
136 }
137}
138
139impl Alignable for MeshItem {
140 fn is_aligned(&self, other: &Self) -> bool {
141 self.points.is_aligned(&other.points)
142 && self.vertex_colors.is_aligned(&other.vertex_colors)
143 && self.vertex_normals.is_aligned(&other.vertex_normals)
144 }
145
146 fn align_with(&mut self, other: &mut Self) {
147 self.points.align_with(&mut other.points);
148 self.vertex_colors.align_with(&mut other.vertex_colors);
149 self.vertex_normals.align_with(&mut other.vertex_normals);
150 }
151}
152
153impl Interpolatable for MeshItem {
154 fn lerp(&self, target: &Self, t: f64) -> Self {
155 Self {
156 points: self.points.lerp(&target.points, t),
157 triangle_indices: if t < 0.5 {
158 self.triangle_indices.clone()
159 } else {
160 target.triangle_indices.clone()
161 },
162 vertex_colors: self.vertex_colors.lerp(&target.vertex_colors, t),
163 vertex_normals: self.vertex_normals.lerp(&target.vertex_normals, t),
164 }
165 }
166}
167
168impl FillColor for MeshItem {
169 fn fill_color(&self) -> AlphaColor<Srgb> {
170 let Rgba(rgba) = self.vertex_colors.first().cloned().unwrap_or_default();
171 AlphaColor::new([rgba.x, rgba.y, rgba.z, rgba.w])
172 }
173
174 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
175 let rgba: Rgba = color.into();
176 self.vertex_colors.iter_mut().for_each(|c| *c = rgba);
177 self
178 }
179
180 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
181 self.vertex_colors.set_opacity(opacity);
182 self
183 }
184}
185
186impl Opacity for MeshItem {
187 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
188 self.vertex_colors.set_opacity(opacity);
189 self
190 }
191}
192
193impl Aabb for MeshItem {
194 fn aabb(&self) -> [DVec3; 2] {
195 if self.points.is_empty() {
196 return [DVec3::ZERO, DVec3::ZERO];
197 }
198
199 let mut min = self.points[0];
201 let mut max = self.points[0];
202
203 for &p in &self.points[1..] {
204 min = min.min(p);
205 max = max.max(p);
206 }
207
208 [min, max]
209 }
210}
211
212impl Empty for MeshItem {
213 fn empty() -> Self {
214 Self {
215 points: Vec::new().into(),
216 triangle_indices: Vec::new(),
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.as_chunks::<3>().0 {
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::DVec3,
311 traits::{Alignable, Empty},
312 };
313
314 #[test]
315 fn generate_grid_indices_follows_quad_layout() {
316 let indices = generate_grid_indices(2, 2);
319 assert_eq!(indices, vec![0, 2, 1, 1, 2, 3]);
320
321 let nu = 10;
323 let nv = 5;
324 assert_eq!(
325 generate_grid_indices(nu, nv).len(),
326 6 * (nu as usize - 1) * (nv as usize - 1)
327 );
328 }
329
330 #[test]
331 fn test_mesh_item_alignable() {
332 let mut short = MeshItem::from_indexed_vertices(vec![DVec3::ZERO, DVec3::X], vec![0, 1, 2]);
333 let mut long = MeshItem::from_indexed_vertices(
334 vec![DVec3::ZERO, DVec3::X, DVec3::Y, DVec3::X + DVec3::Y],
335 vec![0, 1, 2, 1, 3, 2],
336 );
337 let original = long.points.clone();
338 assert!(!short.is_aligned(&long));
339 short.align_with(&mut long);
340 assert!(short.is_aligned(&long));
341 assert_eq!(
342 short.points.as_slice(),
343 &[DVec3::ZERO, DVec3::X, DVec3::X, DVec3::X]
344 );
345 assert_eq!(short.vertex_colors.len(), 4);
346 assert_eq!(short.vertex_normals.len(), 4);
347 assert_eq!(long.points, original);
348 }
349
350 #[test]
351 fn test_mesh_item_interpolate() {
352 use ranim_core::traits::Interpolatable;
353
354 let mut mesh1 = MeshItem::from_indexed_vertices(
355 vec![DVec3::new(0.0, 0.0, 0.0), DVec3::new(1.0, 0.0, 0.0)],
356 vec![0, 1, 2],
357 )
358 .with_color(css::RED.with_alpha(1.0));
359
360 let mut mesh2 = MeshItem::from_indexed_vertices(
361 vec![DVec3::new(2.0, 0.0, 0.0), DVec3::new(3.0, 0.0, 0.0)],
362 vec![0, 1, 3],
363 )
364 .with_color(css::GREEN.with_alpha(1.0));
365
366 mesh1.align_with(&mut mesh2);
368
369 let interpolated = mesh1.lerp(&mesh2, 0.5);
371
372 assert_eq!(interpolated.points[0], DVec3::new(1.0, 0.0, 0.0));
374 assert_eq!(interpolated.points[1], DVec3::new(2.0, 0.0, 0.0));
375
376 assert_eq!(interpolated.triangle_indices, vec![0, 1, 3]);
378 }
379
380 #[test]
381 fn mesh_item_empty_and_bounds() {
382 let mesh = MeshItem::empty();
383 assert_eq!(mesh.points.len(), 0);
384 assert_eq!(mesh.triangle_indices.len(), 0);
385 assert_eq!(mesh.vertex_colors.len(), 0);
386 assert_eq!(mesh.vertex_normals.len(), 0);
387
388 let mesh = MeshItem::from_indexed_vertices(
389 vec![
390 DVec3::new(-1.0, -1.0, -1.0),
391 DVec3::new(1.0, -1.0, -1.0),
392 DVec3::new(1.0, 1.0, -1.0),
393 DVec3::new(-1.0, 1.0, 1.0),
394 ],
395 vec![0, 1, 2],
396 );
397 let [min, max] = mesh.aabb();
398 assert_eq!(min, DVec3::splat(-1.0));
399 assert_eq!(max, DVec3::splat(1.0));
400 }
401}