concinnity_core/components/
prop_body.rs1use crate::ecs::AudioClipHandle;
4use crate::ecs::asset_id::AssetId;
5use crate::ecs::asset_id::de_opt_asset_ref;
6use crate::ecs::de_opt_audio_clip_handle;
7
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23#[serde(default)]
24pub struct PropBody {
25 #[serde(deserialize_with = "de_opt_asset_ref")]
28 pub prop_name: Option<AssetId>,
29 pub mass: f32,
32 pub friction: f32,
34 pub restitution: f32,
36 pub gravity_scale: f32,
38 pub linear_damping: f32,
40 #[serde(deserialize_with = "de_opt_audio_clip_handle")]
44 pub impact_clip: Option<AudioClipHandle>,
45 pub impact_volume: f32,
47}
48
49impl Default for PropBody {
50 fn default() -> Self {
51 Self {
52 prop_name: None,
53 mass: 0.0,
54 friction: 0.5,
55 restitution: 0.0,
56 gravity_scale: 1.0,
57 linear_damping: 0.05,
58 impact_clip: None,
59 impact_volume: 1.0,
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn a_blank_body_falls_under_full_gravity_without_bouncing() {
70 let b = PropBody::default();
71 assert_eq!(b.gravity_scale, 1.0);
72 assert_eq!(b.friction, 0.5);
73 assert_eq!(b.restitution, 0.0);
74 assert_eq!(b.linear_damping, 0.05);
75 assert_eq!(b.mass, 0.0);
77 assert!(b.prop_name.is_none());
78 }
79
80 #[test]
81 fn a_bouncy_floating_body_parses_and_round_trips_through_postcard() {
82 crate::test_support::install_resolvers();
83 let b: PropBody = serde_json::from_str(
84 r#"{"prop_name":"ball","mass":2.5,"friction":0.1,"restitution":0.9,
85 "gravity_scale":0,"linear_damping":0.2}"#,
86 )
87 .unwrap();
88 assert_eq!(b.prop_name, Some(AssetId(4)));
89 assert_eq!(b.gravity_scale, 0.0);
90
91 let bytes = postcard::to_allocvec(&b).unwrap();
92 let back: PropBody = postcard::from_bytes(&bytes).unwrap();
93 assert_eq!(back.prop_name, Some(AssetId(4)));
94 assert_eq!(back.mass, 2.5);
95 assert_eq!(back.friction, 0.1);
96 assert_eq!(back.restitution, 0.9);
97 assert_eq!(back.linear_damping, 0.2);
98 }
99}