viewport_lib/runtime/plugins/physics_lite/
plugin.rs1use crate::interaction::selection::NodeId;
4use crate::runtime::context::RuntimeStepContext;
5use crate::runtime::output::ContactEvent;
6use crate::runtime::plugin::{RuntimePlugin, phase};
7use crate::scene::aabb::Aabb;
8
9#[derive(Debug, Clone)]
11pub struct PhysicsBody {
12 pub node_id: NodeId,
14 pub velocity: glam::Vec3,
16 pub gravity_scale: f32,
18 pub restitution: f32,
20 pub bounds: Option<Aabb>,
23}
24
25impl PhysicsBody {
26 pub fn new(node_id: NodeId) -> Self {
28 Self {
29 node_id,
30 velocity: glam::Vec3::ZERO,
31 gravity_scale: 1.0,
32 restitution: 0.7,
33 bounds: None,
34 }
35 }
36
37 pub fn with_velocity(mut self, v: glam::Vec3) -> Self {
39 self.velocity = v;
40 self
41 }
42
43 pub fn with_gravity_scale(mut self, s: f32) -> Self {
45 self.gravity_scale = s;
46 self
47 }
48
49 pub fn with_restitution(mut self, r: f32) -> Self {
51 self.restitution = r;
52 self
53 }
54
55 pub fn with_bounds(mut self, bounds: Aabb) -> Self {
57 self.bounds = Some(bounds);
58 self
59 }
60}
61
62pub struct PhysicsLitePlugin {
90 pub bodies: Vec<PhysicsBody>,
92 pub gravity: glam::Vec3,
94}
95
96impl Default for PhysicsLitePlugin {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl PhysicsLitePlugin {
103 pub fn new() -> Self {
105 Self {
106 bodies: Vec::new(),
107 gravity: glam::Vec3::new(0.0, 0.0, -9.81),
108 }
109 }
110
111 pub fn with_gravity(mut self, gravity: glam::Vec3) -> Self {
113 self.gravity = gravity;
114 self
115 }
116
117 pub fn add_body(&mut self, body: PhysicsBody) {
119 self.bodies.push(body);
120 }
121
122 pub fn body_mut(&mut self, node_id: NodeId) -> Option<&mut PhysicsBody> {
124 self.bodies.iter_mut().find(|b| b.node_id == node_id)
125 }
126}
127
128impl RuntimePlugin for PhysicsLitePlugin {
129 fn priority(&self) -> i32 {
130 phase::SIMULATE
131 }
132
133 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
134 for body in &mut self.bodies {
135 let Some(node) = ctx.scene.node(body.node_id) else {
136 continue;
137 };
138 let world = node.world_transform();
139 let (scale, rotation, mut pos) = world.to_scale_rotation_translation();
140
141 body.velocity += ctx.dt * body.gravity_scale * self.gravity;
143
144 pos += body.velocity * ctx.dt;
146
147 if let Some(ref bounds) = body.bounds {
149 let mut normal = glam::Vec3::ZERO;
150 let mut bounced = false;
151
152 if pos.x < bounds.min.x && body.velocity.x < 0.0 {
153 pos.x = bounds.min.x;
154 body.velocity.x = -body.velocity.x * body.restitution;
155 normal = glam::Vec3::X;
156 bounced = true;
157 } else if pos.x > bounds.max.x && body.velocity.x > 0.0 {
158 pos.x = bounds.max.x;
159 body.velocity.x = -body.velocity.x * body.restitution;
160 normal = -glam::Vec3::X;
161 bounced = true;
162 }
163
164 if pos.y < bounds.min.y && body.velocity.y < 0.0 {
165 pos.y = bounds.min.y;
166 body.velocity.y = -body.velocity.y * body.restitution;
167 normal = glam::Vec3::Y;
168 bounced = true;
169 } else if pos.y > bounds.max.y && body.velocity.y > 0.0 {
170 pos.y = bounds.max.y;
171 body.velocity.y = -body.velocity.y * body.restitution;
172 normal = -glam::Vec3::Y;
173 bounced = true;
174 }
175
176 if pos.z < bounds.min.z && body.velocity.z < 0.0 {
177 pos.z = bounds.min.z;
178 body.velocity.z = -body.velocity.z * body.restitution;
179 normal = glam::Vec3::Z;
180 bounced = true;
181 } else if pos.z > bounds.max.z && body.velocity.z > 0.0 {
182 pos.z = bounds.max.z;
183 body.velocity.z = -body.velocity.z * body.restitution;
184 normal = -glam::Vec3::Z;
185 bounced = true;
186 }
187
188 if bounced {
189 ctx.output.contact_events.push(ContactEvent {
190 node_a: body.node_id,
191 node_b: NodeId::MAX,
192 world_normal: normal,
193 impulse: body.velocity.length() * (1.0 + body.restitution),
194 contact_point: pos,
195 });
196 }
197 }
198
199 let new_t = glam::Affine3A::from_scale_rotation_translation(scale, rotation, pos);
200 ctx.writeback.set(body.node_id, new_t);
201 }
202 }
203}