use crate::interaction::selection::NodeId;
use crate::runtime::context::RuntimeStepContext;
use crate::runtime::output::ContactEvent;
use crate::runtime::plugin::{phase, RuntimePlugin};
use crate::scene::aabb::Aabb;
#[derive(Debug, Clone)]
pub struct PhysicsBody {
pub node_id: NodeId,
pub velocity: glam::Vec3,
pub gravity_scale: f32,
pub restitution: f32,
pub bounds: Option<Aabb>,
}
impl PhysicsBody {
pub fn new(node_id: NodeId) -> Self {
Self {
node_id,
velocity: glam::Vec3::ZERO,
gravity_scale: 1.0,
restitution: 0.7,
bounds: None,
}
}
pub fn with_velocity(mut self, v: glam::Vec3) -> Self {
self.velocity = v;
self
}
pub fn with_gravity_scale(mut self, s: f32) -> Self {
self.gravity_scale = s;
self
}
pub fn with_restitution(mut self, r: f32) -> Self {
self.restitution = r;
self
}
pub fn with_bounds(mut self, bounds: Aabb) -> Self {
self.bounds = Some(bounds);
self
}
}
pub struct PhysicsLitePlugin {
pub bodies: Vec<PhysicsBody>,
pub gravity: glam::Vec3,
}
impl Default for PhysicsLitePlugin {
fn default() -> Self {
Self::new()
}
}
impl PhysicsLitePlugin {
pub fn new() -> Self {
Self {
bodies: Vec::new(),
gravity: glam::Vec3::new(0.0, 0.0, -9.81),
}
}
pub fn with_gravity(mut self, gravity: glam::Vec3) -> Self {
self.gravity = gravity;
self
}
pub fn add_body(&mut self, body: PhysicsBody) {
self.bodies.push(body);
}
pub fn body_mut(&mut self, node_id: NodeId) -> Option<&mut PhysicsBody> {
self.bodies.iter_mut().find(|b| b.node_id == node_id)
}
}
impl RuntimePlugin for PhysicsLitePlugin {
fn priority(&self) -> i32 {
phase::SIMULATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
for body in &mut self.bodies {
let Some(node) = ctx.scene.node(body.node_id) else {
continue;
};
let world = node.world_transform();
let (scale, rotation, mut pos) = world.to_scale_rotation_translation();
body.velocity += ctx.dt * body.gravity_scale * self.gravity;
pos += body.velocity * ctx.dt;
if let Some(ref bounds) = body.bounds {
let mut normal = glam::Vec3::ZERO;
let mut bounced = false;
if pos.x < bounds.min.x && body.velocity.x < 0.0 {
pos.x = bounds.min.x;
body.velocity.x = -body.velocity.x * body.restitution;
normal = glam::Vec3::X;
bounced = true;
} else if pos.x > bounds.max.x && body.velocity.x > 0.0 {
pos.x = bounds.max.x;
body.velocity.x = -body.velocity.x * body.restitution;
normal = -glam::Vec3::X;
bounced = true;
}
if pos.y < bounds.min.y && body.velocity.y < 0.0 {
pos.y = bounds.min.y;
body.velocity.y = -body.velocity.y * body.restitution;
normal = glam::Vec3::Y;
bounced = true;
} else if pos.y > bounds.max.y && body.velocity.y > 0.0 {
pos.y = bounds.max.y;
body.velocity.y = -body.velocity.y * body.restitution;
normal = -glam::Vec3::Y;
bounced = true;
}
if pos.z < bounds.min.z && body.velocity.z < 0.0 {
pos.z = bounds.min.z;
body.velocity.z = -body.velocity.z * body.restitution;
normal = glam::Vec3::Z;
bounced = true;
} else if pos.z > bounds.max.z && body.velocity.z > 0.0 {
pos.z = bounds.max.z;
body.velocity.z = -body.velocity.z * body.restitution;
normal = -glam::Vec3::Z;
bounced = true;
}
if bounced {
ctx.output.contact_events.push(ContactEvent {
node_a: body.node_id,
node_b: NodeId::MAX,
world_normal: normal,
impulse: body.velocity.length() * (1.0 + body.restitution),
contact_point: pos,
});
}
}
let new_t =
glam::Affine3A::from_scale_rotation_translation(scale, rotation, pos);
ctx.writeback.set(body.node_id, new_t);
}
}
}