#![allow(clippy::unnecessary_cast)]
use crate::body::{body_get_transform, create_body};
use crate::core::{hash, HASH_INIT};
use crate::human::{create_human, Human, BONE_COUNT};
use crate::math_functions::{Pos, WorldTransform, VEC3_ONE};
use crate::mesh::{create_grid_mesh, create_torus_mesh, destroy_mesh, MeshData};
use crate::shape::create_mesh_shape;
use crate::types::{default_body_def, default_shape_def};
use crate::world::{world_get_awake_body_count, world_get_body_events, World};
pub const RAGDOLL_GROUP_SIZE: usize = 2;
pub const RAGDOLL_GRID_COUNT: usize = 2;
const GRID_SIZE: f32 = 15.0;
#[derive(Debug, Clone, Default)]
pub struct RagdollGroup {
pub humans: [Human; RAGDOLL_GROUP_SIZE],
}
#[derive(Debug)]
pub struct FallingRagdollData {
pub groups: [RagdollGroup; RAGDOLL_GRID_COUNT * RAGDOLL_GRID_COUNT],
pub grid_mesh: Option<MeshData>,
pub torus_mesh: Option<MeshData>,
pub column_count: i32,
pub column_index: i32,
pub step_count: i32,
pub sleep_step: i32,
pub hash: u32,
}
impl Default for FallingRagdollData {
fn default() -> Self {
FallingRagdollData {
groups: std::array::from_fn(|_| RagdollGroup::default()),
grid_mesh: None,
torus_mesh: None,
column_count: 0,
column_index: 0,
step_count: 0,
sleep_step: 0,
hash: 0,
}
}
}
pub fn hash_world_transform(h: u32, xf: &WorldTransform) -> u32 {
let mut bytes = Vec::with_capacity(40);
bytes.extend_from_slice(&xf.p.x.to_le_bytes());
bytes.extend_from_slice(&xf.p.y.to_le_bytes());
bytes.extend_from_slice(&xf.p.z.to_le_bytes());
bytes.extend_from_slice(&xf.q.v.x.to_le_bytes());
bytes.extend_from_slice(&xf.q.v.y.to_le_bytes());
bytes.extend_from_slice(&xf.q.v.z.to_le_bytes());
bytes.extend_from_slice(&xf.q.s.to_le_bytes());
hash(h, &bytes)
}
fn create_group(
data: &mut FallingRagdollData,
world: &mut World,
row_index: usize,
column_index: usize,
) {
debug_assert!(row_index < RAGDOLL_GRID_COUNT && column_index < RAGDOLL_GRID_COUNT);
let group_index = row_index * RAGDOLL_GRID_COUNT + column_index;
let span = RAGDOLL_GRID_COUNT as f32 * GRID_SIZE;
let group_distance = 1.0 * span / RAGDOLL_GRID_COUNT as f32;
let mut position = Pos {
x: (-0.5 * span + group_distance * (column_index as f32 + 0.5)) as _,
y: 15.0 as _,
z: (-0.5 * span + group_distance * (row_index as f32 + 0.5)) as _,
};
let friction_torque = 5.0;
let hertz = 1.0;
let damping_ratio = 0.7;
let colorize = false;
for i in 0..RAGDOLL_GROUP_SIZE {
let human = &mut data.groups[group_index].humans[i];
create_human(
human,
world,
position,
friction_torque,
hertz,
damping_ratio,
group_index as i32,
0,
colorize,
);
position.x = (position.x as f32 + 0.75) as _;
}
}
pub fn create_falling_ragdolls(world: &mut World) -> FallingRagdollData {
let mut data = FallingRagdollData::default();
let half_mesh_grid_rows = 4;
let mesh_grid_cell_width = GRID_SIZE / (2.0 * half_mesh_grid_rows as f32);
data.grid_mesh = create_grid_mesh(
2 * half_mesh_grid_rows,
2 * half_mesh_grid_rows,
mesh_grid_cell_width,
0,
true,
);
data.torus_mesh = create_torus_mesh(16, 16, 0.25 * GRID_SIZE, 1.0);
let span = GRID_SIZE * RAGDOLL_GRID_COUNT as f32;
let mut body_def = default_body_def();
let shape_def = default_shape_def();
let grid_mesh = data.grid_mesh.as_ref().expect("grid mesh").clone();
let torus_mesh = data.torus_mesh.as_ref().expect("torus mesh").clone();
body_def.position.x = (-0.5 * span + 0.5 * GRID_SIZE) as _;
for i in 0..RAGDOLL_GRID_COUNT {
body_def.position.z = (-0.5 * span + 0.5 * GRID_SIZE) as _;
for j in 0..RAGDOLL_GRID_COUNT {
let body = create_body(world, &body_def);
create_mesh_shape(world, body, &shape_def, &grid_mesh, VEC3_ONE);
create_mesh_shape(world, body, &shape_def, &torus_mesh, VEC3_ONE);
create_group(&mut data, world, i, j);
body_def.position.z = (body_def.position.z as f32 + GRID_SIZE) as _;
}
body_def.position.x = (body_def.position.x as f32 + GRID_SIZE) as _;
}
data
}
pub fn update_falling_ragdolls(world: &World, data: &mut FallingRagdollData) -> bool {
if data.hash == 0 {
let body_events = world_get_body_events(world);
if body_events.is_empty() {
let awake_count = world_get_awake_body_count(world);
debug_assert!(awake_count == 0);
data.hash = HASH_INIT;
for i in 0..RAGDOLL_GRID_COUNT {
for j in 0..RAGDOLL_GRID_COUNT {
for k in 0..RAGDOLL_GROUP_SIZE {
let group_index = i * RAGDOLL_GRID_COUNT + j;
let human = &data.groups[group_index].humans[k];
for b in 0..BONE_COUNT {
let body_id = human.bones[b].body_id;
let xf = body_get_transform(world, body_id);
data.hash = hash_world_transform(data.hash, &xf);
}
}
}
}
data.sleep_step = data.step_count;
}
}
data.step_count += 1;
data.hash != 0
}
pub fn destroy_falling_ragdolls(data: &mut FallingRagdollData) {
if let Some(mesh) = data.grid_mesh.take() {
destroy_mesh(mesh);
}
if let Some(mesh) = data.torus_mesh.take() {
destroy_mesh(mesh);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::default_world_def;
#[test]
fn create_and_destroy_falling_ragdolls() {
let mut world = World::new(&default_world_def());
let mut data = create_falling_ragdolls(&mut world);
assert!(data.grid_mesh.is_some());
assert!(data.torus_mesh.is_some());
assert!(!data.groups[0].humans[0].bones[0].body_id.is_null());
destroy_falling_ragdolls(&mut data);
assert!(data.grid_mesh.is_none());
}
#[test]
fn hash_world_transform_is_stable() {
let xf = WorldTransform {
p: Pos {
x: 1.0 as _,
y: 2.0 as _,
z: 3.0 as _,
},
q: crate::math_functions::QUAT_IDENTITY,
};
let h1 = hash_world_transform(HASH_INIT, &xf);
let h2 = hash_world_transform(HASH_INIT, &xf);
assert_eq!(h1, h2);
assert_ne!(h1, HASH_INIT);
}
}