use std::ops;
use glam::Vec3;
use rapier3d::{
data::{Arena, Index},
dynamics::{
CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
RigidBodyBuilder, RigidBodyHandle, RigidBodySet,
},
geometry::{ColliderBuilder, ColliderHandle, ColliderSet, DefaultBroadPhase, NarrowPhase},
pipeline::PhysicsPipeline,
};
use crate::{
game::Context,
packet::{CreateInstance, Packet, UpdateInstance},
server::InstanceId,
Body, GpuDrum, GpuModel, Instance, InstanceBuilder,
};
#[cfg(feature = "client")]
use crate::render::model::GpuInstance;
#[derive(Debug, Clone, Copy)]
pub struct BoundingBox {
pub min: Vec3,
pub max: Vec3,
}
impl BoundingBox {
pub fn to_cuboid(&self, scale: Vec3) -> ColliderBuilder {
ColliderBuilder::cuboid(
(self.max.x - self.min.x) * 0.5 * scale.x,
(self.max.y - self.min.y) * 0.5 * scale.y,
(self.max.z - self.min.z) * 0.5 * scale.z,
)
}
}
pub struct PhysicsState {
pub pipeline: PhysicsPipeline,
pub integration: IntegrationParameters,
pub islands: IslandManager,
pub broad_phase: DefaultBroadPhase,
pub narrow_phase: NarrowPhase,
pub impulse_joints: ImpulseJointSet,
pub multibody_joints: MultibodyJointSet,
pub ccd_solver: CCDSolver,
pub rigid_bodies: RigidBodySet,
pub colliders: ColliderSet,
pub(crate) steps_since_last_update: u32,
}
impl Default for PhysicsState {
fn default() -> Self {
Self {
pipeline: PhysicsPipeline::new(),
integration: IntegrationParameters::default(),
islands: IslandManager::new(),
broad_phase: DefaultBroadPhase::new(),
narrow_phase: NarrowPhase::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
ccd_solver: CCDSolver::new(),
rigid_bodies: RigidBodySet::new(),
colliders: ColliderSet::new(),
steps_since_last_update: 0,
}
}
}
impl PhysicsState {
pub const GRAVITY: Vec3 = Vec3::new(0.0, -9.81, 0.0);
pub const TICKS_PER_UPDATE: u32 = 10;
pub fn step(&mut self) {
self.pipeline.step(
&nalgebra::Vector3::from(Self::GRAVITY),
&self.integration,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut self.rigid_bodies,
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.ccd_solver,
None,
&(),
&(),
);
}
fn add_instance(
&mut self,
instance: InstanceBuilder,
model_id: u32,
instance_id: u32,
) -> Instance {
let mut new_instance = Instance {
scale: instance.scale,
collider: None,
body: Body::Static {
position: instance.position,
rotation: instance.rotation,
},
model_id,
instance_id,
};
if let Some(body) = instance.rigidbody {
let body = self.rigid_bodies.insert(body);
if let Some(collider) = instance.collider {
new_instance.collider = Some(self.colliders.insert_with_parent(
collider,
body,
&mut self.rigid_bodies,
));
}
new_instance.body = Body::Rigid(body);
} else if let Some(collider) = instance.collider {
let (axis, angle) = instance.rotation.to_axis_angle();
new_instance.collider = Some(
self.colliders.insert(
collider
.position(instance.position.into())
.rotation((axis * angle).into())
.mass(1.0),
),
);
}
new_instance
}
}
pub trait IndexExt {
fn to_u128(&self) -> u128;
fn from_u128(id: u128) -> Self;
}
impl IndexExt for Index {
fn to_u128(&self) -> u128 {
let (idx, gen) = self.into_raw_parts();
(idx as u128) << 32 | gen as u128
}
fn from_u128(id: u128) -> Self {
let idx = (id >> 32) as u32;
let gen = id as u32;
Self::from_raw_parts(idx, gen)
}
}
impl<M> Context<M> {
pub fn physics_update(&mut self) {
self.physics.step();
self.physics.steps_since_last_update += 1;
for body in self
.physics
.islands
.active_dynamic_bodies()
.iter()
.chain(self.physics.islands.active_kinematic_bodies())
{
let Some(body) = self.physics.rigid_bodies.get(*body) else {
continue;
};
if body.user_data == u128::MAX {
continue;
}
let instance: InstanceHandle = body.user_data.into();
#[cfg(feature = "client")]
if let Some(mut pair) = instance.resolve_mut(&mut self.drum, &mut self.instances) {
pair.update_gpu(&self.physics);
}
#[cfg(feature = "server")]
{
if self.physics.steps_since_last_update < PhysicsState::TICKS_PER_UPDATE {
continue;
}
let Some(id) = self.instance_ids.get(&instance) else {
continue;
};
let (position, rotation) = (*body.position()).into();
let _ = self.packet_tx.send(Packet::UpdateInstance {
id: *id,
delta: UpdateInstance {
position,
rotation,
scale: None,
body: None,
},
});
}
}
#[cfg(feature = "server")]
if self.physics.steps_since_last_update >= PhysicsState::TICKS_PER_UPDATE {
self.physics.steps_since_last_update = 0;
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn add_rigidbody(
&mut self,
rigidbody: RigidBodyBuilder,
collider: ColliderBuilder,
) -> (RigidBodyHandle, ColliderHandle) {
let body = self
.physics
.rigid_bodies
.insert(rigidbody.user_data(u128::MAX).build());
let collider = self.physics.colliders.insert_with_parent(
collider.build(),
body,
&mut self.physics.rigid_bodies,
);
(body, collider)
}
#[allow(clippy::needless_pass_by_value)]
pub fn add_collider(&mut self, collider: ColliderBuilder) -> ColliderHandle {
self.physics.colliders.insert(collider.build())
}
pub fn remove_instance_local(&mut self, id: InstanceId) -> Option<Instance> {
let instance = self.handles.remove(&id)?;
self.instance_ids.remove(&instance);
let instance = self.instances.remove(*instance)?;
if let Body::Rigid(handle) = instance.body {
self.physics.rigid_bodies.remove(
handle,
&mut self.physics.islands,
&mut self.physics.colliders,
&mut self.physics.impulse_joints,
&mut self.physics.multibody_joints,
true,
);
} else if let Some(collider) = instance.collider {
self.physics.colliders.remove(
collider,
&mut self.physics.islands,
&mut self.physics.rigid_bodies,
true,
);
}
let model = &mut self.drum.models[instance.model_id as usize];
#[cfg(feature = "client")]
{
let _ = model.instances.swap_remove(instance.instance_id as usize);
}
model.handles.swap_remove(instance.instance_id as usize);
let handle = model.handles[instance.instance_id as usize];
if let Some(other) = self.instances.get_mut(*handle) {
other.instance_id = instance.instance_id;
}
Some(instance)
}
#[cfg(not(feature = "server"))]
pub fn add_instance(&mut self, model_id: u32, instance: InstanceBuilder) {
let _ = self.packet_tx.send(Packet::CreateInstance {
id: InstanceId::NONE,
options: CreateInstance::from_builder(&instance, model_id),
});
}
#[cfg(feature = "server")]
pub fn add_instance(&mut self, model_id: u32, instance: InstanceBuilder) -> InstanceHandle {
use std::sync::atomic::Ordering;
let instance_id = InstanceId::new(self.next_instance_id.fetch_add(1, Ordering::SeqCst));
let _ = self.packet_tx.send(Packet::CreateInstance {
id: instance_id,
options: CreateInstance::from_builder(&instance, model_id),
});
let handle = self.add_instance_local(model_id, instance);
self.instance_ids.insert(handle, instance_id);
self.handles.insert(instance_id, handle);
handle
}
pub fn add_instance_local(
&mut self,
model_id: u32,
mut instance: InstanceBuilder,
) -> InstanceHandle {
let model = &mut self.drum.models[model_id as usize];
let instance_id = model.handles.len() as u32;
let handle = self.instances.insert_with(|handle| {
let handle = InstanceHandle::new(handle);
let (axis, angle) = instance.rotation.to_axis_angle();
instance.collider = instance
.collider
.or_else(|| Some(model.bounds.to_cuboid(instance.scale).mass(1.0)));
instance.rigidbody = instance.rigidbody.map(|body| {
body.position(instance.position.into())
.rotation((axis * angle).into())
.user_data(handle.into())
});
let instance = self.physics.add_instance(instance, model_id, instance_id);
model.add_gpu_instance(&instance, handle, &self.physics);
instance
});
InstanceHandle::new(handle)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct InstanceHandle {
handle: Index,
}
pub struct InstancePair<'d> {
#[cfg(feature = "client")]
pub gpu: &'d GpuInstance,
pub instance: &'d Instance,
}
pub struct InstancePairMut<'d> {
pub instance: &'d mut Instance,
#[cfg(feature = "client")]
pub gpu: &'d mut GpuInstance,
#[cfg(feature = "client")]
dirty: &'d mut bool,
}
impl InstancePairMut<'_> {
pub fn update<F>(&mut self, physics: &mut PhysicsState, update: F)
where
F: FnOnce(&mut Instance, &mut PhysicsState),
{
update(self.instance, physics);
self.update_gpu(physics);
}
pub fn update_gpu(&mut self, physics: &PhysicsState) {
#[cfg(feature = "client")]
{
*self.gpu = self.instance.to_gpu(physics);
*self.dirty = true;
}
}
}
impl InstanceHandle {
#[must_use]
pub fn new(handle: Index) -> Self {
Self { handle }
}
#[must_use]
pub fn resolve<'d>(
&self,
drum: &'d GpuDrum,
instances: &'d Arena<Instance>,
) -> Option<InstancePair<'d>> {
let instance = instances.get(self.handle)?;
#[cfg(feature = "client")]
let model = drum.models.get(instance.model_id as usize)?;
Some(InstancePair {
#[cfg(feature = "client")]
gpu: model.instances.get(instance.instance_id as usize)?,
instance,
})
}
pub fn resolve_mut<'d>(
&self,
drum: &'d mut GpuDrum,
instances: &'d mut Arena<Instance>,
) -> Option<InstancePairMut<'d>> {
let instance = instances.get_mut(self.handle)?;
#[cfg(feature = "client")]
let model = drum.models.get_mut(instance.model_id as usize)?;
Some(InstancePairMut {
#[cfg(feature = "client")]
gpu: model.instances.get_mut(instance.instance_id as usize)?,
#[cfg(feature = "client")]
dirty: &mut model.dirty,
instance,
})
}
#[must_use]
pub fn resolve_model<'d>(
&self,
drum: &'d GpuDrum,
instances: &Arena<Instance>,
) -> Option<&'d GpuModel> {
let instance = instances.get(self.handle)?;
drum.models.get(instance.model_id as usize)
}
pub fn resolve_model_mut<'d>(
&self,
drum: &'d mut GpuDrum,
instances: &Arena<Instance>,
) -> Option<&'d mut GpuModel> {
let instance = instances.get(self.handle)?;
drum.models.get_mut(instance.model_id as usize)
}
pub fn update<F, M>(&self, ctx: &mut Context<M>, update: F)
where
F: FnOnce(&mut Instance, &mut PhysicsState),
{
let Some(mut pair) = self.resolve_mut(&mut ctx.drum, &mut ctx.instances) else {
return;
};
pair.update(&mut ctx.physics, update);
}
}
impl From<u128> for InstanceHandle {
fn from(id: u128) -> Self {
Self {
handle: Index::from_u128(id),
}
}
}
impl From<InstanceHandle> for u128 {
fn from(id: InstanceHandle) -> u128 {
id.handle.to_u128()
}
}
impl ops::Deref for InstanceHandle {
type Target = Index;
fn deref(&self) -> &Self::Target {
&self.handle
}
}