use alloc::vec::Vec;
use concinnity_physics::{BodyHandle, CharacterCapsule, CharacterMoveInput, LayerMask, Simulation};
use crate::components::{CharacterRig, RootMotionEvent};
use crate::ecs::{EventCursor, PipelineContext, SkinnedMeshHandle};
use crate::math::vec3::add;
use super::interp::PointInterp;
#[derive(Debug)]
pub(crate) struct RigPhysics {
pub target: SkinnedMeshHandle,
pub handle: BodyHandle,
shape: CharacterCapsule,
pub vy: f32,
center: PointInterp,
written_pos: Option<[f32; 3]>,
}
fn center_of(rig: &CharacterRig) -> [f32; 3] {
[
rig.position[0],
rig.position[1] + rig.half_height + rig.radius,
rig.position[2],
]
}
pub(crate) fn init_rigs(
world: &mut Simulation,
ctx: &PipelineContext,
mask: LayerMask,
rigs: &mut Vec<RigPhysics>,
) {
rigs.extend(ctx.query::<CharacterRig>().filter_map(|rig| {
let center = center_of(rig);
Some(RigPhysics {
target: rig.target,
handle: world.add_character(rig.half_height, rig.radius, center, mask)?,
shape: CharacterCapsule::new(rig.half_height, rig.radius),
vy: 0.0,
center: PointInterp::new(center),
written_pos: Some(rig.position),
})
}));
if !rigs.is_empty() {
tracing::debug!("PhysicsSystem: {} character rig(s)", rigs.len());
}
}
pub(crate) fn drain_motions_into(
ctx: &PipelineContext,
cursor: &mut EventCursor,
out: &mut Vec<RootMotionEvent>,
) {
out.clear();
if let Some(ev) = ctx.events::<RootMotionEvent>() {
out.extend(ev.read(cursor).copied());
}
}
pub(crate) fn sync_rigs(ctx: &mut PipelineContext, rigs: &mut [RigPhysics]) {
for rig_body in rigs.iter_mut() {
let Some(rig) = ctx
.query::<CharacterRig>()
.find(|r| r.target == rig_body.target)
else {
continue;
};
if rig_body.written_pos != Some(rig.position) {
rig_body.center.snap(center_of(rig));
}
}
}
pub(crate) fn tick_rigs(
world: &mut Simulation,
ctx: &mut PipelineContext,
rigs: &mut [RigPhysics],
motions: &[RootMotionEvent],
dt: f32,
gravity: f32,
mask: LayerMask,
) {
for rig_body in rigs.iter_mut() {
let Some(rig) = ctx
.query_mut::<CharacterRig>()
.find(|r| r.target == rig_body.target)
else {
continue;
};
let mut local = [0.0f32; 3];
for motion in motions.iter().filter(|m| m.target == rig_body.target) {
local = add(local, motion.delta);
}
let displacement = add(
rig.world_delta(local),
[
rig.desired_move[0] * dt,
rig.desired_move[1] * dt,
rig.desired_move[2] * dt,
],
);
if rig.jump_velocity > 0.0 {
if rig.grounded {
rig_body.vy = rig.jump_velocity;
}
rig.jump_velocity = 0.0;
}
rig_body.vy -= gravity * dt;
let center = rig_body.center.current();
let desired = [
displacement[0],
displacement[1] + rig_body.vy * dt,
displacement[2],
];
rig_body.shape.resize(rig.half_height, rig.radius);
let moved = world.move_character(
&rig_body.shape,
&CharacterMoveInput {
center,
desired,
dt,
exclude: rig_body.handle,
mask,
},
);
let new_center = add(center, moved.translation);
world.set_kinematic_translation(rig_body.handle, new_center);
if moved.grounded && rig_body.vy < 0.0 {
rig_body.vy = 0.0;
}
rig.grounded = moved.grounded;
rig_body.center.push(new_center);
}
}
pub(crate) fn publish_rigs(ctx: &mut PipelineContext, rigs: &mut [RigPhysics], alpha: f32) {
for rig_body in rigs.iter_mut() {
let Some(rig) = ctx
.query_mut::<CharacterRig>()
.find(|r| r.target == rig_body.target)
else {
continue;
};
let center = rig_body.center.sample(alpha);
let new_pos = [
center[0],
center[1] - rig.half_height - rig.radius,
center[2],
];
rig_body.written_pos = Some(new_pos);
if new_pos != rig.position {
rig.position = new_pos;
rig.moved = true;
}
}
}