1use crate::assets::Assets;
19use crate::components::*;
20use crate::mesh::MeshData;
21use crate::noise::{Noise, NoiseKind};
22use crate::procedural::signature;
23use crate::reflect::Field;
24use glam::{Mat4, Quat, Vec3};
25use kvas_ecs::World;
26use serde::{Deserialize, Serialize};
27use std::sync::{Arc, RwLock};
28
29crate::choice_enum!(
30 ModAxis { X, Y, Z } default Y
32);
33
34impl ModAxis {
35 fn index(self) -> usize {
36 self as usize
37 }
38}
39
40#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
42#[serde(tag = "type")]
43pub enum Modifier {
44 Array {
46 enabled: bool,
48 count: i32,
50 offset: Vec3,
52 rotation: Vec3,
54 scale: f32,
56 },
57 Mirror {
59 enabled: bool,
61 x: bool,
63 y: bool,
65 z: bool,
67 },
68 Subdivide {
70 enabled: bool,
72 levels: i32,
74 smooth: f32,
76 },
77 Displace {
79 enabled: bool,
81 strength: f32,
83 scale: f32,
85 seed: i32,
87 },
88 Twist {
90 enabled: bool,
92 angle: f32,
94 axis: ModAxis,
96 },
97 Bend {
99 enabled: bool,
101 angle: f32,
103 axis: ModAxis,
105 },
106 Taper {
108 enabled: bool,
110 amount: f32,
112 axis: ModAxis,
114 },
115 Solidify {
117 enabled: bool,
119 thickness: f32,
121 },
122 Wave {
124 enabled: bool,
126 amplitude: f32,
128 wavelength: f32,
130 axis: ModAxis,
132 phase: f32,
134 },
135 Cast {
137 enabled: bool,
139 factor: f32,
141 },
142 Shear {
144 enabled: bool,
146 amount: f32,
148 axis: ModAxis,
150 },
151 Smooth {
153 enabled: bool,
155 iterations: i32,
157 factor: f32,
159 },
160 Jitter {
162 enabled: bool,
164 amount: f32,
166 seed: i32,
168 },
169 RadialArray {
171 enabled: bool,
173 count: i32,
175 radius: f32,
177 axis: ModAxis,
179 face_outward: bool,
181 },
182}
183
184impl Modifier {
185 pub fn array(count: i32, offset: Vec3) -> Self {
187 Modifier::Array { enabled: true, count, offset, rotation: Vec3::ZERO, scale: 1.0 }
188 }
189
190 pub fn named(name: &str) -> Option<Self> {
192 Some(match name {
193 "Array" => Self::array(3, Vec3::new(1.2, 0.0, 0.0)),
194 "Mirror" => Modifier::Mirror { enabled: true, x: true, y: false, z: false },
195 "Subdivide" => Modifier::Subdivide { enabled: true, levels: 1, smooth: 0.0 },
196 "Displace" => Modifier::Displace { enabled: true, strength: 0.15, scale: 2.0, seed: 1 },
197 "Twist" => Modifier::Twist { enabled: true, angle: 90.0, axis: ModAxis::Y },
198 "Bend" => Modifier::Bend { enabled: true, angle: 60.0, axis: ModAxis::Y },
199 "Taper" => Modifier::Taper { enabled: true, amount: -0.6, axis: ModAxis::Y },
200 "Solidify" => Modifier::Solidify { enabled: true, thickness: 0.1 },
201 "Wave" => Modifier::Wave { enabled: true, amplitude: 0.2, wavelength: 2.0, axis: ModAxis::X, phase: 0.0 },
202 "Cast" => Modifier::Cast { enabled: true, factor: 0.6 },
203 "Shear" => Modifier::Shear { enabled: true, amount: 0.4, axis: ModAxis::Y },
204 "Smooth" => Modifier::Smooth { enabled: true, iterations: 2, factor: 0.5 },
205 "Jitter" => Modifier::Jitter { enabled: true, amount: 0.05, seed: 1 },
206 "RadialArray" => Modifier::RadialArray { enabled: true, count: 8, radius: 3.0, axis: ModAxis::Y, face_outward: true },
207 _ => return None,
208 })
209 }
210
211 pub const NAMES: &'static [&'static str] = &["Array", "RadialArray", "Mirror", "Subdivide", "Smooth", "Displace", "Jitter", "Wave", "Twist", "Bend", "Taper", "Shear", "Cast", "Solidify"];
213
214 pub fn name(&self) -> &'static str {
216 match self {
217 Modifier::Array { .. } => "Array",
218 Modifier::Mirror { .. } => "Mirror",
219 Modifier::Subdivide { .. } => "Subdivide",
220 Modifier::Displace { .. } => "Displace",
221 Modifier::Twist { .. } => "Twist",
222 Modifier::Bend { .. } => "Bend",
223 Modifier::Taper { .. } => "Taper",
224 Modifier::Solidify { .. } => "Solidify",
225 Modifier::Wave { .. } => "Wave",
226 Modifier::Cast { .. } => "Cast",
227 Modifier::Shear { .. } => "Shear",
228 Modifier::Smooth { .. } => "Smooth",
229 Modifier::Jitter { .. } => "Jitter",
230 Modifier::RadialArray { .. } => "RadialArray",
231 }
232 }
233
234 pub fn enabled(&self) -> bool {
236 match self {
237 Modifier::Array { enabled, .. }
238 | Modifier::Mirror { enabled, .. }
239 | Modifier::Subdivide { enabled, .. }
240 | Modifier::Displace { enabled, .. }
241 | Modifier::Twist { enabled, .. }
242 | Modifier::Bend { enabled, .. }
243 | Modifier::Taper { enabled, .. }
244 | Modifier::Solidify { enabled, .. }
245 | Modifier::Wave { enabled, .. }
246 | Modifier::Cast { enabled, .. }
247 | Modifier::Shear { enabled, .. }
248 | Modifier::Smooth { enabled, .. }
249 | Modifier::Jitter { enabled, .. }
250 | Modifier::RadialArray { enabled, .. } => *enabled,
251 }
252 }
253
254 pub fn fields(&mut self) -> Vec<Field<'_>> {
256 match self {
257 Modifier::Array { enabled, count, offset, rotation, scale } => {
258 vec![Field::bool("enabled", enabled), Field::i32("count", count), Field::vec3("offset", offset), Field::vec3("rotation", rotation), Field::f32("scale", scale)]
259 }
260 Modifier::Mirror { enabled, x, y, z } => vec![Field::bool("enabled", enabled), Field::bool("x", x), Field::bool("y", y), Field::bool("z", z)],
261 Modifier::Subdivide { enabled, levels, smooth } => vec![Field::bool("enabled", enabled), Field::i32("levels", levels), Field::f32("smooth", smooth)],
262 Modifier::Displace { enabled, strength, scale, seed } => {
263 vec![Field::bool("enabled", enabled), Field::f32("strength", strength), Field::f32("scale", scale), Field::i32("seed", seed)]
264 }
265 Modifier::Twist { enabled, angle, axis } | Modifier::Bend { enabled, angle, axis } => {
266 vec![Field::bool("enabled", enabled), Field::f32("angle", angle), Field::choice("axis", axis)]
267 }
268 Modifier::Taper { enabled, amount, axis } => vec![Field::bool("enabled", enabled), Field::f32("amount", amount), Field::choice("axis", axis)],
269 Modifier::Solidify { enabled, thickness } => vec![Field::bool("enabled", enabled), Field::f32("thickness", thickness)],
270 Modifier::Wave { enabled, amplitude, wavelength, axis, phase } => {
271 vec![Field::bool("enabled", enabled), Field::f32("amplitude", amplitude), Field::f32("wavelength", wavelength), Field::choice("axis", axis), Field::f32("phase", phase)]
272 }
273 Modifier::Cast { enabled, factor } => vec![Field::bool("enabled", enabled), Field::f32("factor", factor)],
274 Modifier::Shear { enabled, amount, axis } => vec![Field::bool("enabled", enabled), Field::f32("amount", amount), Field::choice("axis", axis)],
275 Modifier::Smooth { enabled, iterations, factor } => vec![Field::bool("enabled", enabled), Field::i32("iterations", iterations), Field::f32("factor", factor)],
276 Modifier::Jitter { enabled, amount, seed } => vec![Field::bool("enabled", enabled), Field::f32("amount", amount), Field::i32("seed", seed)],
277 Modifier::RadialArray { enabled, count, radius, axis, face_outward } => {
278 vec![Field::bool("enabled", enabled), Field::i32("count", count), Field::f32("radius", radius), Field::choice("axis", axis), Field::bool("face_outward", face_outward)]
279 }
280 }
281 }
282}
283
284#[derive(Clone, Default, Serialize, Deserialize)]
286#[serde(default)]
287pub struct MeshModifiers(
288 pub Vec<Modifier>,
290);
291
292impl Component for MeshModifiers {}
293
294#[derive(Clone, Default)]
296pub struct ModifierState {
297 sig: u64,
298}
299
300static ASSETS: RwLock<Option<Assets>> = RwLock::new(None);
301
302pub fn set_assets(assets: Assets) {
304 if let Ok(mut slot) = ASSETS.write() {
305 *slot = Some(assets);
306 }
307}
308
309pub fn assets() -> Option<Assets> {
311 ASSETS.read().ok()?.clone()
312}
313
314fn source_mesh(key: &str) -> Option<MeshData> {
315 let assets = ASSETS.read().ok()?.clone().unwrap_or(Assets::Dir(std::path::PathBuf::new()));
316 MeshData::load(&assets, key).ok()
317}
318
319fn axes(axis: ModAxis) -> (usize, usize, usize) {
320 let a = axis.index();
321 (a, (a + 1) % 3, (a + 2) % 3)
322}
323
324fn extent(mesh: &MeshData, axis: usize) -> (f32, f32) {
325 mesh.vertices.iter().fold((f32::MAX, f32::MIN), |(lo, hi), v| (lo.min(v.pos[axis]), hi.max(v.pos[axis])))
326}
327
328fn renormalize(mesh: &mut MeshData) {
329 let _ = mesh.recalculate_normals();
330}
331
332fn subdivide(mesh: &mut MeshData, levels: i32, smooth: f32) {
333 let radius = mesh.vertices.iter().map(|v| Vec3::from(v.pos).length()).fold(0.0, f32::max);
334 for _ in 0..levels.clamp(1, 4) {
335 if mesh.indices.len() > 400_000 {
336 break;
337 }
338 let mut out = MeshData { albedo: mesh.albedo.clone(), base_color: mesh.base_color, dependencies: mesh.dependencies.clone(), ..Default::default() };
339 out.parts = mesh.parts.iter().map(|p| crate::mesh::MeshPart { first_index: p.first_index * 4, index_count: p.index_count * 4, albedo: p.albedo.clone(), normal: p.normal.clone(), orm: p.orm.clone() }).collect();
340 for tri in mesh.indices.chunks_exact(3) {
341 let v = [mesh.vertices[tri[0] as usize], mesh.vertices[tri[1] as usize], mesh.vertices[tri[2] as usize]];
342 let mid = |a: crate::mesh::Vertex, b: crate::mesh::Vertex| {
343 let p = (Vec3::from(a.pos) + Vec3::from(b.pos)) * 0.5;
344 let p = if smooth > 0.0 && radius > 1e-4 && p.length() > 1e-4 { p.lerp(p.normalize() * radius, smooth.clamp(0.0, 1.0)) } else { p };
345 let n = (Vec3::from(a.normal) + Vec3::from(b.normal)).normalize_or_zero();
346 crate::mesh::Vertex { pos: p.into(), normal: n.into(), uv: [(a.uv[0] + b.uv[0]) * 0.5, (a.uv[1] + b.uv[1]) * 0.5] }
347 };
348 let (m01, m12, m20) = (mid(v[0], v[1]), mid(v[1], v[2]), mid(v[2], v[0]));
349 let base = out.vertices.len() as u32;
350 out.vertices.extend([v[0], m01, m20, m01, v[1], m12, m20, m12, v[2], m01, m12, m20]);
351 out.indices.extend([base, base + 1, base + 2, base + 3, base + 4, base + 5, base + 6, base + 7, base + 8, base + 9, base + 10, base + 11]);
352 }
353 *mesh = out;
354 }
355}
356
357fn apply(mesh: &mut MeshData, modifier: &Modifier) {
358 match modifier {
359 Modifier::Array { count, offset, rotation, scale, .. } => {
360 let count = (*count).clamp(1, 64) as usize;
361 let original = mesh.clone();
362 let mut matrix = Mat4::IDENTITY;
363 let step = Mat4::from_scale_rotation_translation(Vec3::splat(scale.max(0.01)), Quat::from_euler(glam::EulerRot::YXZ, rotation.y.to_radians(), rotation.x.to_radians(), rotation.z.to_radians()), *offset);
364 for _ in 1..count {
365 if mesh.vertices.len() + original.vertices.len() > 600_000 {
366 break;
367 }
368 matrix *= step;
369 if let Ok(copy) = original.transformed(matrix) {
370 let _ = mesh.append(©);
371 }
372 }
373 }
374 Modifier::Mirror { x, y, z, .. } => {
375 for (on, scale) in [(*x, Vec3::new(-1.0, 1.0, 1.0)), (*y, Vec3::new(1.0, -1.0, 1.0)), (*z, Vec3::new(1.0, 1.0, -1.0))] {
376 if on && mesh.vertices.len() < 300_000 {
377 if let Ok(copy) = mesh.transformed(Mat4::from_scale(scale)) {
378 let _ = mesh.append(©);
379 }
380 }
381 }
382 }
383 Modifier::Subdivide { levels, smooth, .. } => {
384 subdivide(mesh, *levels, *smooth);
385 }
386 Modifier::Displace { strength, scale, seed, .. } => {
387 let noise = Noise::new(NoiseKind::Perlin).with_seed(*seed as u32).with_octaves(3);
388 for v in &mut mesh.vertices {
389 let p = Vec3::from(v.pos);
390 let d = noise.sample3(p.x * scale, p.y * scale, p.z * scale) * strength;
391 v.pos = (p + Vec3::from(v.normal) * d).into();
392 }
393 renormalize(mesh);
394 }
395 Modifier::Twist { angle, axis, .. } => {
396 let (a, b, c) = axes(*axis);
397 let (lo, hi) = extent(mesh, a);
398 let span = (hi - lo).max(1e-4);
399 for v in &mut mesh.vertices {
400 let t = (v.pos[a] - lo) / span - 0.5;
401 let (s, co) = (angle.to_radians() * t).sin_cos();
402 let (pb, pc) = (v.pos[b], v.pos[c]);
403 v.pos[b] = pb * co - pc * s;
404 v.pos[c] = pb * s + pc * co;
405 }
406 renormalize(mesh);
407 }
408 Modifier::Bend { angle, axis, .. } => {
409 let (a, b, _) = axes(*axis);
410 let (lo, hi) = extent(mesh, a);
411 let span = (hi - lo).max(1e-4);
412 let k = angle.to_radians() / span;
413 if k.abs() > 1e-4 {
414 let r = 1.0 / k;
415 let mid = (lo + hi) * 0.5;
416 for v in &mut mesh.vertices {
417 let along = v.pos[a] - mid;
418 let theta = k * along;
419 let off = v.pos[b];
420 v.pos[a] = (r - off) * theta.sin() + mid;
421 v.pos[b] = r - (r - off) * theta.cos();
422 }
423 renormalize(mesh);
424 }
425 }
426 Modifier::Taper { amount, axis, .. } => {
427 let (a, b, c) = axes(*axis);
428 let (lo, hi) = extent(mesh, a);
429 let span = (hi - lo).max(1e-4);
430 for v in &mut mesh.vertices {
431 let t = (v.pos[a] - lo) / span;
432 let f = (1.0 + amount * t).max(0.01);
433 v.pos[b] *= f;
434 v.pos[c] *= f;
435 }
436 renormalize(mesh);
437 }
438 Modifier::Wave { amplitude, wavelength, axis, phase, .. } => {
439 let (a, b, _) = axes(*axis);
440 let up = (a + 1) % 3;
441 let _ = b;
442 let k = std::f32::consts::TAU / wavelength.max(0.05);
443 for v in &mut mesh.vertices {
444 v.pos[up] += ((v.pos[a] * k) + phase * std::f32::consts::TAU).sin() * amplitude;
445 }
446 renormalize(mesh);
447 }
448 Modifier::Cast { factor, .. } => {
449 let radius = mesh.vertices.iter().map(|v| Vec3::from(v.pos).length()).sum::<f32>() / mesh.vertices.len().max(1) as f32;
450 for v in &mut mesh.vertices {
451 let p = Vec3::from(v.pos);
452 if p.length() > 1e-5 {
453 v.pos = p.lerp(p.normalize() * radius, *factor).into();
454 }
455 }
456 renormalize(mesh);
457 }
458 Modifier::Shear { amount, axis, .. } => {
459 let (a, b, _) = axes(*axis);
460 for v in &mut mesh.vertices {
461 v.pos[b] += v.pos[a] * amount;
462 }
463 renormalize(mesh);
464 }
465 Modifier::Smooth { iterations, factor, .. } => {
466 let key = |p: [f32; 3]| ((p[0] * 1000.0).round() as i64, (p[1] * 1000.0).round() as i64, (p[2] * 1000.0).round() as i64);
468 let mut ids = std::collections::HashMap::new();
469 let mut slot = Vec::with_capacity(mesh.vertices.len());
470 for v in &mesh.vertices {
471 let next = ids.len();
472 slot.push(*ids.entry(key(v.pos)).or_insert(next));
473 }
474 let mut neighbours: Vec<std::collections::HashSet<usize>> = vec![Default::default(); ids.len()];
475 for tri in mesh.indices.chunks_exact(3) {
476 let s = [slot[tri[0] as usize], slot[tri[1] as usize], slot[tri[2] as usize]];
477 for i in 0..3 {
478 neighbours[s[i]].insert(s[(i + 1) % 3]);
479 neighbours[s[i]].insert(s[(i + 2) % 3]);
480 }
481 }
482 let mut positions = vec![Vec3::ZERO; ids.len()];
483 for (v, &i) in mesh.vertices.iter().zip(&slot) {
484 positions[i] = Vec3::from(v.pos);
485 }
486 for _ in 0..(*iterations).clamp(1, 10) {
487 let next: Vec<Vec3> = (0..positions.len())
488 .map(|i| {
489 if neighbours[i].is_empty() {
490 return positions[i];
491 }
492 let avg = neighbours[i].iter().map(|&n| positions[n]).sum::<Vec3>() / neighbours[i].len() as f32;
493 positions[i].lerp(avg, factor.clamp(0.0, 1.0))
494 })
495 .collect();
496 positions = next;
497 }
498 for (v, &i) in mesh.vertices.iter_mut().zip(&slot) {
499 v.pos = positions[i].into();
500 }
501 renormalize(mesh);
502 }
503 Modifier::Jitter { amount, seed, .. } => {
504 let key = |p: [f32; 3]| ((p[0] * 1000.0).round() as i64, (p[1] * 1000.0).round() as i64, (p[2] * 1000.0).round() as i64);
505 let mut rng = crate::procedural::Rng::new(*seed as u64);
506 let mut offsets = std::collections::HashMap::new();
507 for v in &mut mesh.vertices {
508 let o = *offsets.entry(key(v.pos)).or_insert_with(|| Vec3::new(rng.f32() - 0.5, rng.f32() - 0.5, rng.f32() - 0.5) * 2.0 * *amount);
510 v.pos = (Vec3::from(v.pos) + o).into();
511 }
512 renormalize(mesh);
513 }
514 Modifier::RadialArray { count, radius, axis, face_outward, .. } => {
515 let count = (*count).clamp(2, 64) as usize;
516 let original = mesh.clone();
517 let (a, b, c) = axes(*axis);
518 for i in 1..count {
519 if mesh.vertices.len() + original.vertices.len() > 600_000 {
520 break;
521 }
522 let angle = std::f32::consts::TAU * i as f32 / count as f32;
523 let mut offset = [0.0f32; 3];
524 offset[b] = angle.cos() * radius;
525 offset[c] = angle.sin() * radius;
526 let mut axis_vec = Vec3::ZERO;
527 axis_vec[a] = 1.0;
528 let spin = if *face_outward { Quat::from_axis_angle(axis_vec, -angle) } else { Quat::IDENTITY };
529 let matrix = Mat4::from_rotation_translation(spin, Vec3::from(offset));
530 if let Ok(copy) = original.transformed(matrix) {
531 let _ = mesh.append(©);
532 }
533 }
534 }
535 Modifier::Solidify { thickness, .. } => {
536 if mesh.vertices.len() < 300_000 {
537 let base = mesh.vertices.len() as u32;
538 let inner: Vec<_> = mesh
539 .vertices
540 .iter()
541 .map(|v| crate::mesh::Vertex { pos: (Vec3::from(v.pos) - Vec3::from(v.normal) * *thickness).into(), normal: (-Vec3::from(v.normal)).into(), uv: v.uv })
542 .collect();
543 let tris: Vec<u32> = mesh.indices.chunks_exact(3).flat_map(|t| [t[0] + base, t[2] + base, t[1] + base]).collect();
544 if !mesh.parts.is_empty() {
545 let shift = mesh.indices.len() as u32;
546 let copy: Vec<_> = mesh.parts.iter().map(|p| crate::mesh::MeshPart { first_index: p.first_index + shift, ..p.clone() }).collect();
547 mesh.parts.extend(copy);
548 }
549 mesh.vertices.extend(inner);
550 mesh.indices.extend(tris);
551 }
552 }
553 }
554}
555
556pub fn build(source: MeshData, stack: &[Modifier]) -> MeshData {
558 let mut mesh = source;
559 for m in stack.iter().filter(|m| m.enabled()) {
560 apply(&mut mesh, m);
561 }
562 mesh
563}
564
565pub fn update(world: &mut World) {
567 for e in world.entities_with::<MeshModifiers>() {
568 let Some(key) = world.get::<Shape>(e).map(Shape::key).or_else(|| world.get::<MeshRenderer>(e).map(|m| m.mesh.clone())).filter(|k| !k.is_empty()) else { continue };
569 let stack = world.get::<MeshModifiers>(e).map(|m| m.0.clone()).unwrap_or_default();
570 let sig = signature(&(&stack, &key));
571 let known = world.get::<ModifierStateCell>(e).map(|c| c.0.sig);
572 if known == Some(sig) && world.has::<DynamicMesh>(e) {
573 continue;
574 }
575 if stack.iter().all(|m| !m.enabled()) {
576 if known.is_some() {
577 world.remove::<DynamicMesh>(e);
578 world.remove::<ModifierStateCell>(e);
579 }
580 continue;
581 }
582 let Some(source) = source_mesh(&key) else { continue };
583 let version = world.get::<DynamicMesh>(e).map_or(0, |d| d.version) + 1;
584 let mesh = build(source, &stack);
585 world.insert(e, DynamicMesh { data: Arc::new(mesh), version });
586 world.insert(e, ModifierStateCell(ModifierState { sig }));
587 }
588}
589
590#[derive(Clone)]
592pub struct ModifierStateCell(pub ModifierState);
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597
598 #[test]
599 fn every_modifier_keeps_the_mesh_valid() {
600 for name in Modifier::NAMES {
601 let source = MeshData::primitive("sphere").unwrap();
602 let before = source.vertices.len();
603 let modifier = Modifier::named(name).unwrap();
604 let out = build(source, &[Modifier::named("Subdivide").unwrap(), modifier]);
605 assert!(out.vertices.len() >= before, "{name}");
606 assert!(out.validate().is_ok(), "{name} produced an invalid mesh");
607 assert!(out.vertices.iter().all(|v| v.pos.iter().all(|c| c.is_finite())), "{name} produced NaN");
608 }
609 }
610}