use std::sync::{Arc, Mutex, MutexGuard};
use rapier3d::dynamics::{RigidBody, RigidBodyHandle};
use rapier3d::geometry::{Collider, ColliderHandle, CollisionEvent};
use rapier3d::math::Vector;
use viewport_lib::runtime::plugin::phase;
use viewport_lib::{NodeId, RuntimePlugin, RuntimeStepContext};
use crate::events::{EventCollector, RapierContactEvent};
use crate::state::RapierState;
#[inline]
fn lock_state(state: &Mutex<RapierState>) -> MutexGuard<'_, RapierState> {
state.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[derive(Clone)]
pub struct RapierPlugin {
state: Arc<Mutex<RapierState>>,
}
impl Default for RapierPlugin {
fn default() -> Self {
Self::new()
}
}
impl RapierPlugin {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(RapierState::new(Vector::new(0.0, 0.0, -9.81)))),
}
}
pub fn with_gravity(self, g: glam::Vec3) -> Self {
lock_state(&self.state).gravity = g;
self
}
pub fn add_body(&mut self, node_id: NodeId, body: RigidBody, collider: Collider) {
lock_state(&self.state).add_body(node_id, body, collider);
}
pub fn remove_body(&mut self, node_id: NodeId) {
lock_state(&self.state).remove_body(node_id);
}
pub fn clear_bodies(&mut self) {
lock_state(&self.state).clear_bodies();
}
pub fn body_handle(&self, node_id: NodeId) -> Option<RigidBodyHandle> {
lock_state(&self.state).node_to_body.get(&node_id).copied()
}
pub fn collider_handle(&self, node_id: NodeId) -> Option<ColliderHandle> {
lock_state(&self.state).node_to_collider.get(&node_id).copied()
}
pub fn with_body_mut<R>(
&mut self,
node_id: NodeId,
f: impl FnOnce(&mut RigidBody) -> R,
) -> Option<R> {
let mut state = lock_state(&self.state);
let handle = state.node_to_body.get(&node_id).copied()?;
let body = state.bodies.get_mut(handle)?;
Some(f(body))
}
pub fn with_collider_mut<R>(
&mut self,
node_id: NodeId,
f: impl FnOnce(&mut Collider) -> R,
) -> Option<R> {
let mut state = lock_state(&self.state);
let handle = state.node_to_collider.get(&node_id).copied()?;
let collider = state.colliders.get_mut(handle)?;
Some(f(collider))
}
pub fn with_state_mut<R>(&mut self, f: impl FnOnce(&mut RapierState) -> R) -> R {
f(&mut lock_state(&self.state))
}
pub fn with_state<R>(&self, f: impl FnOnce(&RapierState) -> R) -> R {
f(&lock_state(&self.state))
}
pub fn into_plugins(self) -> (RapierPreparePlugin, RapierSimulatePlugin) {
(
RapierPreparePlugin {
state: Arc::clone(&self.state),
},
RapierSimulatePlugin {
state: Arc::clone(&self.state),
},
)
}
}
pub struct RapierPreparePlugin {
state: Arc<Mutex<RapierState>>,
}
impl RuntimePlugin for RapierPreparePlugin {
fn priority(&self) -> i32 {
phase::PREPARE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
let mut state = lock_state(&self.state);
let synced: Vec<(NodeId, RigidBodyHandle, bool)> = state
.node_to_body
.iter()
.filter_map(|(&node_id, &handle)| {
let body = state.bodies.get(handle)?;
if body.is_kinematic() {
Some((node_id, handle, true))
} else if body.is_fixed() {
Some((node_id, handle, false))
} else {
None
}
})
.collect();
for (node_id, handle, is_kinematic) in synced {
let Some(node) = ctx.scene.node(node_id) else {
continue;
};
let m = node.world_transform();
let (_, rotation, translation) = m.to_scale_rotation_translation();
let pose = rapier3d::math::Pose { rotation, translation };
if let Some(body) = state.bodies.get_mut(handle) {
if is_kinematic {
body.set_next_kinematic_position(pose);
} else {
body.set_position(pose, true);
}
}
}
}
}
pub struct RapierSimulatePlugin {
state: Arc<Mutex<RapierState>>,
}
impl RuntimePlugin for RapierSimulatePlugin {
fn priority(&self) -> i32 {
phase::SIMULATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
let dt = ctx.dt;
let mut state = lock_state(&self.state);
state.integration_params.dt = dt;
let collector = EventCollector::default();
let RapierState {
ref mut pipeline,
ref gravity,
ref integration_params,
ref mut islands,
ref mut broad_phase,
ref mut narrow_phase,
ref mut bodies,
ref mut colliders,
ref mut impulse_joints,
ref mut multibody_joints,
ref mut ccd_solver,
..
} = *state;
pipeline.step(
*gravity,
integration_params,
islands,
broad_phase,
narrow_phase,
bodies,
colliders,
impulse_joints,
multibody_joints,
ccd_solver,
&(),
&collector,
);
let dynamic_bodies: Vec<(NodeId, glam::Affine3A)> = state
.node_to_body
.iter()
.filter_map(|(&node_id, &handle)| {
let body = state.bodies.get(handle)?;
if body.is_dynamic() {
let pose = body.position();
let transform =
glam::Affine3A::from_rotation_translation(pose.rotation, pose.translation);
Some((node_id, transform))
} else {
None
}
})
.collect();
for (node_id, transform) in dynamic_bodies {
ctx.writeback.set_preserve_scale(node_id, transform);
}
let collisions = collector
.collisions
.into_inner()
.unwrap_or_else(|poisoned| poisoned.into_inner());
for event in collisions {
let (h1, h2, started) = match event {
CollisionEvent::Started(h1, h2, _) => (h1, h2, true),
CollisionEvent::Stopped(h1, h2, _) => (h1, h2, false),
};
let node_a = state.node_for_collider(h1);
let node_b = state.node_for_collider(h2);
if let (Some(node_a), Some(node_b)) = (node_a, node_b) {
ctx.output.events.emit(RapierContactEvent {
node_a,
node_b,
started,
});
}
}
}
}