1use crate::choice_enum;
4use crate::reflect::{AssetKind, Field};
5use glam::{EulerRot, Mat4, Quat, Vec3};
6use kvas_ecs::{Entity, World};
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9
10pub trait Component: kvas_ecs::Component + Serialize + DeserializeOwned + Default + Clone {
14 const REMOVABLE: bool = true;
16 fn fields(&mut self) -> Vec<Field<'_>> {
18 Vec::new()
19 }
20}
21
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
24pub struct Parent(pub Entity);
25
26#[derive(Clone, Copy, Debug)]
28pub struct GlobalTransform(pub Mat4);
29
30#[derive(Clone, Default, Serialize, Deserialize)]
32pub struct Name(pub String);
33
34impl Component for Name {
35 const REMOVABLE: bool = false;
36}
37
38#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
51#[serde(default)]
52pub struct Transform {
53 pub position: Vec3,
55 pub rotation: Vec3,
57 pub scale: Vec3,
59}
60
61impl Default for Transform {
62 fn default() -> Self {
63 Self { position: Vec3::ZERO, rotation: Vec3::ZERO, scale: Vec3::ONE }
64 }
65}
66
67impl Transform {
68 pub fn at(position: Vec3) -> Self {
70 Self { position, ..Default::default() }
71 }
72
73 pub fn quat(&self) -> Quat {
75 let r = self.rotation * (std::f32::consts::PI / 180.0);
76 Quat::from_euler(EulerRot::YXZ, r.y, r.x, r.z)
77 }
78
79 pub fn set_quat(&mut self, q: Quat) {
82 let (y, x, z) = q.to_euler(EulerRot::YXZ);
83 self.rotation = Vec3::new(x, y, z) * (180.0 / std::f32::consts::PI);
84 }
85
86 pub fn matrix(&self) -> Mat4 {
88 Mat4::from_scale_rotation_translation(self.scale, self.quat(), self.position)
89 }
90
91 pub fn from_matrix(m: Mat4) -> Self {
94 let (scale, rot, position) = m.to_scale_rotation_translation();
95 let mut t = Self { position, rotation: Vec3::ZERO, scale };
96 t.set_quat(rot);
97 t
98 }
99
100 pub fn forward(&self) -> Vec3 {
102 self.quat() * Vec3::NEG_Z
103 }
104 pub fn right(&self) -> Vec3 {
106 self.quat() * Vec3::X
107 }
108 pub fn up(&self) -> Vec3 {
110 self.quat() * Vec3::Y
111 }
112
113 pub fn is_finite(&self) -> bool {
115 self.position.is_finite() && self.rotation.is_finite() && self.scale.is_finite()
116 }
117
118 pub fn try_from_matrix(matrix: Mat4) -> Option<Self> {
121 if !matrix.is_finite() || matrix.determinant() == 0.0 {
122 return None;
123 }
124 let pose = Self::from_matrix(matrix);
125 if !pose.is_finite() || !pose.matrix().abs_diff_eq(matrix, 1e-4) {
126 return None;
127 }
128 Some(pose)
129 }
130
131 pub fn transform_point(&self, point: Vec3) -> Vec3 {
133 self.matrix().transform_point3(point)
134 }
135
136 pub fn transform_direction(&self, direction: Vec3) -> Vec3 {
138 self.quat() * direction
139 }
140
141 pub fn inverse_transform_point(&self, point: Vec3) -> Option<Vec3> {
143 let matrix = self.matrix();
144 if !point.is_finite() || !matrix.is_finite() || matrix.determinant() == 0.0 {
145 return None;
146 }
147 let local = matrix.inverse().transform_point3(point);
148 local.is_finite().then_some(local)
149 }
150
151 pub fn look_at(&mut self, target: Vec3, up: Vec3) -> bool {
155 if !self.position.is_finite() || !target.is_finite() || !up.is_finite() {
156 return false;
157 }
158 let Some(forward) = (target - self.position).try_normalize() else {
159 return false;
160 };
161 let Some(right) = forward.cross(up).try_normalize() else {
162 return false;
163 };
164 let up = right.cross(forward);
165 self.set_quat(Quat::from_mat3(&glam::Mat3::from_cols(right, up, -forward)).normalize());
166 true
167 }
168
169 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
172 let t = if t.is_finite() { t.clamp(0.0, 1.0) } else { 0.0 };
173 let mut pose =
174 Self { position: self.position.lerp(other.position, t), scale: self.scale.lerp(other.scale, t), rotation: Vec3::ZERO };
175 pose.set_quat(self.quat().slerp(other.quat(), t));
176 pose
177 }
178}
179
180impl Component for Transform {
181 const REMOVABLE: bool = false;
182 fn fields(&mut self) -> Vec<Field<'_>> {
183 vec![
184 Field::vec3("position", &mut self.position),
185 Field::vec3("rotation", &mut self.rotation),
186 Field::vec3("scale", &mut self.scale),
187 ]
188 }
189}
190
191#[derive(Clone, Serialize, Deserialize)]
194#[serde(default)]
195pub struct MeshRenderer {
196 pub mesh: String,
198 pub color: [f32; 4],
200 pub roughness: f32,
202 pub emissive: f32,
204 pub metallic: f32,
206 pub reflectivity: f32,
208 pub cast_shadows: bool,
210 pub user: [f32; 8],
212 pub tiling: f32,
214 pub texture: String,
216 pub normal_map: String,
218 pub orm_map: String,
221 pub normal_strength: f32,
223 pub triplanar: f32,
225 pub shader: String,
227}
228
229impl Default for MeshRenderer {
230 fn default() -> Self {
231 Self {
232 mesh: "primitive:cube".into(),
233 color: [0.8, 0.8, 0.8, 1.0],
234 roughness: 0.5,
235 emissive: 0.0,
236 metallic: 0.0,
237 reflectivity: 0.0,
238 cast_shadows: true,
239 user: [0.0; 8],
240 tiling: 1.0,
241 texture: String::new(),
242 normal_map: String::new(),
243 orm_map: String::new(),
244 normal_strength: 1.0,
245 triplanar: 0.0,
246 shader: String::new(),
247 }
248 }
249}
250
251impl Component for MeshRenderer {
252 fn fields(&mut self) -> Vec<Field<'_>> {
253 let [p1, p2, p3, p4, ..] = &mut self.user;
254 vec![
255 Field::asset("mesh", &mut self.mesh, AssetKind::Mesh),
256 Field::color("color", &mut self.color),
257 Field::f32("roughness", &mut self.roughness),
258 Field::f32("emissive", &mut self.emissive),
259 Field::f32("metallic", &mut self.metallic),
260 Field::f32("reflectivity", &mut self.reflectivity),
261 Field::bool("cast_shadows", &mut self.cast_shadows),
262 Field::f32("tiling", &mut self.tiling),
263 Field::f32("shader param 1", p1),
264 Field::f32("shader param 2", p2),
265 Field::f32("shader param 3", p3),
266 Field::f32("shader param 4", p4),
267 Field::asset("texture", &mut self.texture, AssetKind::Texture),
268 Field::asset("normal_map", &mut self.normal_map, AssetKind::Texture),
269 Field::asset("orm_map", &mut self.orm_map, AssetKind::Texture),
270 Field::f32("normal_strength", &mut self.normal_strength),
271 Field::f32("triplanar", &mut self.triplanar),
272 Field::asset("shader", &mut self.shader, AssetKind::Shader),
273 ]
274 }
275}
276
277choice_enum!(
278 ShapeType { Cube, Sphere, Plane, Cylinder, Capsule, Cone, Torus, Rock } default Cube);
280
281#[derive(Clone, Serialize, Deserialize)]
283#[serde(default)]
284pub struct Shape {
285 pub kind: ShapeType,
287 pub size: Vec3,
289 pub radius: f32,
291 pub radius2: f32,
293 pub height: f32,
295 pub segments: i32,
297 pub rings: i32,
299 pub seed: i32,
301}
302
303impl Default for Shape {
304 fn default() -> Self {
305 Self { kind: ShapeType::Cube, size: Vec3::ONE, radius: 0.5, radius2: 0.5, height: 1.0, segments: 32, rings: 16, seed: 1 }
306 }
307}
308
309impl Shape {
310 pub fn of(kind: ShapeType) -> Self {
312 let base = Self { kind, ..Default::default() };
313 match kind {
314 ShapeType::Capsule => Self { height: 2.0, ..base },
315 ShapeType::Torus => Self { radius: 0.5, radius2: 0.18, segments: 48, rings: 24, ..base },
316 ShapeType::Plane => Self { rings: 16, ..base },
317 ShapeType::Rock => Self { size: Vec3::new(1.6, 1.0, 1.3), radius2: 1.0, segments: 5, ..base },
318 _ => base,
319 }
320 }
321
322 pub fn key(&self) -> String {
324 use crate::reflect::Choice;
325 crate::mesh::ProcParams {
326 kind: ShapeType::OPTIONS[self.kind.to_index()].to_lowercase(),
327 size: self.size,
328 radius: self.radius,
329 radius2: self.radius2,
330 height: self.height,
331 segments: self.segments.max(3) as u32,
332 rings: self.rings.max(2) as u32,
333 seed: self.seed as u32,
334 }
335 .key()
336 }
337}
338
339impl Component for Shape {
340 fn fields(&mut self) -> Vec<Field<'_>> {
341 let kind = self.kind;
342 let mut f = vec![Field::choice("kind", &mut self.kind)];
343 match kind {
344 ShapeType::Cube => f.push(Field::vec3("size", &mut self.size)),
345 ShapeType::Plane => {
346 f.push(Field::vec3("size", &mut self.size));
347 f.push(Field::i32("subdivisions", &mut self.rings));
348 }
349 ShapeType::Sphere => {
350 f.push(Field::f32("radius", &mut self.radius));
351 f.push(Field::i32("segments", &mut self.segments));
352 f.push(Field::i32("rings", &mut self.rings));
353 }
354 ShapeType::Capsule => {
355 f.push(Field::f32("radius", &mut self.radius));
356 f.push(Field::f32("height", &mut self.height));
357 f.push(Field::i32("segments", &mut self.segments));
358 f.push(Field::i32("rings", &mut self.rings));
359 }
360 ShapeType::Cylinder => {
361 f.push(Field::f32("radius", &mut self.radius));
362 f.push(Field::f32("top radius", &mut self.radius2));
363 f.push(Field::f32("height", &mut self.height));
364 f.push(Field::i32("segments", &mut self.segments));
365 }
366 ShapeType::Cone => {
367 f.push(Field::f32("radius", &mut self.radius));
368 f.push(Field::f32("height", &mut self.height));
369 f.push(Field::i32("segments", &mut self.segments));
370 }
371 ShapeType::Rock => {
372 f.push(Field::vec3("size", &mut self.size));
373 f.push(Field::f32("ruggedness", &mut self.radius2));
374 f.push(Field::i32("detail", &mut self.segments));
375 f.push(Field::i32("seed", &mut self.seed));
376 }
377 ShapeType::Torus => {
378 f.push(Field::f32("radius", &mut self.radius));
379 f.push(Field::f32("tube radius", &mut self.radius2));
380 f.push(Field::i32("segments", &mut self.segments));
381 f.push(Field::i32("tube rings", &mut self.rings));
382 }
383 }
384 f
385 }
386}
387
388#[derive(Clone, Copy, Default)]
391pub struct Transient;
392
393#[derive(Clone, Copy, Default)]
395pub struct GrassChunk;
396
397#[derive(Clone, Default)]
400pub struct DynamicMesh {
401 pub data: std::sync::Arc<crate::mesh::MeshData>,
403 pub version: u64,
405}
406
407pub fn mesh_key(world: &World, e: Entity) -> Option<String> {
409 let mr = world.get::<MeshRenderer>(e)?;
410 if world.has::<DynamicMesh>(e) {
411 return Some(format!("dyn:{}", e.to_bits()));
412 }
413 Some(world.get::<Shape>(e).map_or_else(|| mr.mesh.clone(), Shape::key))
414}
415
416#[derive(Clone, Serialize, Deserialize)]
418#[serde(default)]
419pub struct Camera {
420 pub fov: f32,
422 pub near: f32,
424 pub far: f32,
426 pub primary: bool,
428 pub orthographic: bool,
430 pub ortho_size: f32,
432 pub focal_length: f32,
435 pub dof: bool,
437 pub aperture: f32,
439 pub focus_distance: f32,
441 pub autofocus: bool,
443 pub focus_speed: f32,
445 pub exposure_comp: f32,
447 pub letterbox: f32,
449 pub effects: Vec<crate::fx::PostEffect>,
452}
453
454impl Default for Camera {
455 fn default() -> Self {
456 Self { fov: 60.0, near: 0.1, far: 500.0, primary: true, orthographic: false, ortho_size: 10.0, focal_length: 0.0, dof: false, aperture: 2.8, focus_distance: 8.0, autofocus: true, focus_speed: 6.0, exposure_comp: 0.0, letterbox: 0.0, effects: Vec::new() }
457 }
458}
459
460pub fn active_camera(world: &World) -> Option<Entity> {
462 world.iter::<Camera>().find(|(_, c)| c.primary).or_else(|| world.iter::<Camera>().next()).map(|(e, _)| e)
463}
464
465pub fn set_active_camera(world: &mut World, camera: Entity) -> bool {
467 if !world.has::<Camera>(camera) {
468 return false;
469 }
470 let all: Vec<Entity> = world.iter::<Camera>().map(|(e, _)| e).collect();
471 for e in all {
472 if let Some(c) = world.get_mut::<Camera>(e) {
473 c.primary = e == camera;
474 }
475 }
476 true
477}
478
479impl Camera {
480 pub fn vertical_fov(&self) -> f32 {
482 if self.focal_length > 1.0 { (2.0 * (12.0 / self.focal_length).atan()).to_degrees().clamp(2.0, 170.0) } else { self.fov }
483 }
484}
485
486impl Component for Camera {
487 fn fields(&mut self) -> Vec<Field<'_>> {
488 vec![
489 Field::f32("fov", &mut self.fov),
490 Field::f32("near", &mut self.near),
491 Field::f32("far", &mut self.far),
492 Field::bool("primary", &mut self.primary),
493 Field::bool("orthographic", &mut self.orthographic),
494 Field::f32("ortho_size", &mut self.ortho_size),
495 Field::f32("focal_length", &mut self.focal_length),
496 Field::bool("dof", &mut self.dof),
497 Field::f32("aperture", &mut self.aperture),
498 Field::f32("focus_distance", &mut self.focus_distance),
499 Field::bool("autofocus", &mut self.autofocus),
500 Field::f32("focus_speed", &mut self.focus_speed),
501 Field::f32("exposure_comp", &mut self.exposure_comp),
502 Field::f32("letterbox", &mut self.letterbox),
503 ]
504 }
505}
506
507choice_enum!(
508 LightKind { Directional, Point, Spot } default Directional);
510
511#[derive(Clone, Serialize, Deserialize)]
513#[serde(default)]
514pub struct Light {
515 pub kind: LightKind,
517 pub color: [f32; 4],
519 pub intensity: f32,
521 pub range: f32,
523 pub angle: f32,
525 pub softness: f32,
527 pub shadows: bool,
529}
530
531impl Default for Light {
532 fn default() -> Self {
533 Self {
534 kind: LightKind::Directional,
535 color: [1.0, 0.96, 0.9, 1.0],
536 intensity: 1.0,
537 range: 10.0,
538 angle: 35.0,
539 softness: 0.25,
540 shadows: true,
541 }
542 }
543}
544
545impl Component for Light {
546 fn fields(&mut self) -> Vec<Field<'_>> {
547 vec![
548 Field::choice("kind", &mut self.kind),
549 Field::color("color", &mut self.color),
550 Field::f32("intensity", &mut self.intensity),
551 Field::f32("range", &mut self.range),
552 Field::f32("angle", &mut self.angle),
553 Field::f32("softness", &mut self.softness),
554 Field::bool("shadows", &mut self.shadows),
555 ]
556 }
557}
558
559choice_enum!(
560 BodyKind { Dynamic, Fixed, Kinematic } default Dynamic);
562
563#[derive(Clone, Serialize, Deserialize)]
566#[serde(default)]
567pub struct RigidBody {
568 pub kind: BodyKind,
570 pub mass: f32,
572 pub gravity_scale: f32,
574 pub linear_damping: f32,
576 pub angular_damping: f32,
578 pub lock_rotation: bool,
580 pub ccd: bool,
582 #[serde(skip)]
584 pub velocity: Vec3,
585 #[serde(skip)]
587 pub impulse: Vec3,
588 #[serde(skip)]
589 pub set_velocity: Option<Vec3>,
591 #[serde(skip)]
592 pub set_angular_velocity: Option<Vec3>,
594 #[serde(skip)]
597 pub teleport: bool,
598}
599
600impl Default for RigidBody {
601 fn default() -> Self {
602 Self {
603 kind: BodyKind::Dynamic,
604 mass: 1.0,
605 gravity_scale: 1.0,
606 linear_damping: 0.05,
607 angular_damping: 0.05,
608 lock_rotation: false,
609 ccd: false,
610 velocity: Vec3::ZERO,
611 impulse: Vec3::ZERO,
612 set_velocity: None,
613 set_angular_velocity: None,
614 teleport: false,
615 }
616 }
617}
618
619impl Component for RigidBody {
620 fn fields(&mut self) -> Vec<Field<'_>> {
621 vec![
622 Field::choice("kind", &mut self.kind),
623 Field::f32("mass", &mut self.mass),
624 Field::f32("gravity_scale", &mut self.gravity_scale),
625 Field::f32("linear_damping", &mut self.linear_damping),
626 Field::f32("angular_damping", &mut self.angular_damping),
627 Field::bool("lock_rotation", &mut self.lock_rotation),
628 Field::bool("ccd", &mut self.ccd),
629 ]
630 }
631}
632
633choice_enum!(
634 ShapeKind { Box, Sphere, Capsule, Cylinder, Mesh, Hull } default Box);
637
638#[derive(Clone, Serialize, Deserialize)]
643#[serde(default)]
644pub struct Collider {
645 pub shape: ShapeKind,
647 pub size: Vec3,
649 pub friction: f32,
651 pub restitution: f32,
653 pub is_trigger: bool,
655 pub memberships: u32,
657 pub filter: u32,
659}
660
661impl Default for Collider {
662 fn default() -> Self {
663 Self {
664 shape: ShapeKind::Box,
665 size: Vec3::ONE,
666 friction: 0.6,
667 restitution: 0.1,
668 is_trigger: false,
669 memberships: u32::MAX,
670 filter: u32::MAX,
671 }
672 }
673}
674
675impl Component for Collider {
676 fn fields(&mut self) -> Vec<Field<'_>> {
677 vec![
678 Field::choice("shape", &mut self.shape),
679 Field::vec3("size", &mut self.size),
680 Field::f32("friction", &mut self.friction),
681 Field::f32("restitution", &mut self.restitution),
682 Field::bool("is_trigger", &mut self.is_trigger),
683 Field::u32("memberships", &mut self.memberships),
684 Field::u32("filter", &mut self.filter),
685 ]
686 }
687}
688
689choice_enum!(
690 JointKind { Fixed, Ball, Hinge, Spring } default Fixed);
692
693#[derive(Clone, Serialize, Deserialize)]
698#[serde(default)]
699pub struct Joint {
700 pub kind: JointKind,
702 pub target: String,
704 pub anchor: Vec3,
706 pub axis: Vec3,
708 pub use_limits: bool,
710 pub limit_min: f32,
712 pub limit_max: f32,
714 pub rest_length: f32,
716 pub stiffness: f32,
718 pub damping: f32,
720}
721
722impl Default for Joint {
723 fn default() -> Self {
724 Self {
725 kind: JointKind::Fixed,
726 target: String::new(),
727 anchor: Vec3::ZERO,
728 axis: Vec3::Y,
729 use_limits: false,
730 limit_min: -45.0,
731 limit_max: 45.0,
732 rest_length: 1.0,
733 stiffness: 50.0,
734 damping: 5.0,
735 }
736 }
737}
738
739impl Component for Joint {
740 fn fields(&mut self) -> Vec<Field<'_>> {
741 vec![
742 Field::choice("kind", &mut self.kind),
743 Field::entity("target", &mut self.target),
744 Field::vec3("anchor", &mut self.anchor),
745 Field::vec3("axis", &mut self.axis),
746 Field::bool("use_limits", &mut self.use_limits),
747 Field::f32("limit_min", &mut self.limit_min),
748 Field::f32("limit_max", &mut self.limit_max),
749 Field::f32("rest_length", &mut self.rest_length),
750 Field::f32("stiffness", &mut self.stiffness),
751 Field::f32("damping", &mut self.damping),
752 ]
753 }
754}
755
756#[derive(Clone, Default, Serialize, Deserialize)]
758pub struct ScriptSlot {
759 pub name: String,
761 #[serde(default)]
763 pub props: serde_json::Map<String, serde_json::Value>,
764}
765
766#[derive(Clone, Default, Serialize, Deserialize)]
768pub struct Scripts(pub Vec<ScriptSlot>);
769
770impl Component for Scripts {}
771
772macro_rules! name_components {
773 ($($t:ident),+ $(,)?) => {
774 $(impl kvas_ecs::Component for $t { const NAME: &'static str = stringify!($t); })+
775
776 pub fn register_all(world: &mut World) {
778 $(world.register::<$t>();)+
779 }
780 };
781}
782
783pub use crate::audio::AudioSource;
784pub use crate::gui::{UiElement, UiKind};
785pub use crate::mesh_edit::EditableMesh;
786pub use crate::neural::NeuralAgent;
787pub use crate::animation::TransformAnimation;
788pub use crate::cloth::Cloth;
789pub use crate::gameplay::{Lifetime, Tags, Visibility};
790pub use crate::grass::Grass;
791pub use crate::tree::{Tree, TreePart};
792pub use crate::decal::Decal;
793pub use crate::interaction::{Slider, Switch};
794pub use crate::meshmod::{MeshModifiers, ModifierStateCell};
795pub use crate::paint::TexturePaint;
796pub use crate::particles::ParticleEmitter;
797pub use crate::skeletal::{Animator, AnimatorState};
798pub use crate::ik::CharacterIk;
799pub use crate::ragdoll::Ragdoll;
800pub use crate::modifiers::{Bob, ColorCycle, Pulse, Spin, Wobble};
801pub use crate::tween::MoveTween;
802pub use crate::voxel::{VoxelClouds, VoxelSmoke};
803pub use crate::water::VoxelWater;
804
805name_components!(
806 UiElement,
807 EditableMesh,
808 NeuralAgent,
809 TransformAnimation,
810 ParticleEmitter,
811 Animator,
812 AnimatorState,
813 CharacterIk,
814 Ragdoll,
815 TexturePaint,
816 MeshModifiers,
817 ModifierStateCell,
818 AudioSource,
819 MoveTween,
820 Tags,
821 Lifetime,
822 Visibility,
823 Switch,
824 Slider,
825 Transient,
826 GrassChunk,
827 Tree,
828 TreePart,
829 Decal,
830 DynamicMesh,
831 Cloth,
832 Grass,
833 VoxelClouds,
834 VoxelSmoke,
835 VoxelWater,
836 Bob,
837 Spin,
838 Pulse,
839 Wobble,
840 ColorCycle,
841 Parent,
842 GlobalTransform,
843 Name,
844 Transform,
845 MeshRenderer,
846 Shape,
847 Camera,
848 Light,
849 RigidBody,
850 Collider,
851 Joint,
852 Scripts
853);