use rapier2d::prelude::*;
use serde_json::{Value, json};
use crate::map::GameMap;
use crate::nav::navigation::NavigationSystem;
use crate::nav::spatial::SpatialGrid;
use crate::physics::round2;
use crate::rng::Rng;
#[allow(clippy::too_many_arguments)]
pub fn engine_json(
world: &PhysicsWorld,
map: &Option<GameMap>,
nav: &Option<NavigationSystem>,
spatial: &SpatialGrid,
rng: &Rng,
accumulator: f32,
time_step: f32,
) -> Value {
json!({
"bodies": bodies_json(world),
"colliders": colliders_json(world),
"map": map_json(map),
"nav": nav_json(nav),
"spatial": spatial_json(spatial),
"rng": { "state": rng.state().to_string() },
"step": {
"timeStep": time_step,
"accumulator": accumulator,
},
})
}
fn bodies_json(world: &PhysicsWorld) -> Value {
let mut rows: Vec<(u32, Value)> = world
.rigid_bodies()
.map(|(handle, body)| {
let translation = body.translation();
let linvel = body.linvel();
(
handle.into_raw_parts().0,
json!({
"handle": handle.into_raw_parts().0,
"tag": (body.user_data & 0xff) as u64,
"userData": body.user_data.to_string(),
"translation": [round2(translation.x), round2(translation.y)],
"rotation": round2(body.rotation().angle()),
"linvel": [round2(linvel.x), round2(linvel.y)],
"angvel": round2(body.angvel()),
"mass": round2(body.mass()),
"bodyType": format!("{:?}", body.body_type()),
"ccd": body.is_ccd_enabled(),
}),
)
})
.collect();
rows.sort_by_key(|(index, _)| *index);
Value::Array(rows.into_iter().map(|(_, row)| row).collect())
}
fn colliders_json(world: &PhysicsWorld) -> Value {
let mut rows: Vec<(u32, Value)> = world
.colliders
.iter()
.map(|(handle, collider)| {
let collision = collider.collision_groups();
let solver = collider.solver_groups();
let mut row = json!({
"handle": handle.into_raw_parts().0,
"isSensor": collider.is_sensor(),
"collisionGroups": groups_hex(collision),
"solverGroups": groups_hex(solver),
"parent": collider.parent().map(|body| body.into_raw_parts().0),
});
merge(&mut row, shape_json(collider.shape()));
(handle.into_raw_parts().0, row)
})
.collect();
rows.sort_by_key(|(index, _)| *index);
Value::Array(rows.into_iter().map(|(_, row)| row).collect())
}
fn shape_json(shape: &dyn Shape) -> Value {
if let Some(cuboid) = shape.as_cuboid() {
let half = cuboid.half_extents;
return json!({
"shape": "cuboid",
"halfExtents": [round2(half.x), round2(half.y)],
});
}
if let Some(ball) = shape.as_ball() {
return json!({ "shape": "ball", "radius": round2(ball.radius) });
}
json!({ "shape": format!("{:?}", shape.shape_type()) })
}
fn map_json(map: &Option<GameMap>) -> Value {
let Some(map) = map else {
return Value::Null;
};
let rows = map.grid.len();
let cols = map.grid.first().map(|row| row.len()).unwrap_or(0);
json!({
"setId": map.set_id,
"step": map.step,
"grid": { "rows": rows, "cols": cols },
"physicsStatic": map.physics_static,
"staticBodies": map.static_body_count(),
"dynamicBodies": map.dynamic_body_count(),
"respawns": map
.respawns
.iter()
.map(|(team, points)| (team.clone(), Value::from(points.len())))
.collect::<serde_json::Map<String, Value>>(),
})
}
fn nav_json(nav: &Option<NavigationSystem>) -> Value {
let Some(nav) = nav else {
return Value::Null;
};
json!({
"nodes": nav.node_count(),
"edges": nav.edge_count(),
"gridStep": nav.grid_step(),
})
}
fn spatial_json(spatial: &SpatialGrid) -> Value {
let cells = spatial.cell_counts();
json!({
"cellSize": spatial.cell_size(),
"cells": cells.len(),
"entities": cells.iter().map(|(_, _, count)| count).sum::<usize>(),
"cellCounts": cells
.iter()
.map(|(cx, cy, count)| json!({ "cell": [cx, cy], "count": count }))
.collect::<Vec<Value>>(),
})
}
fn groups_hex(groups: InteractionGroups) -> String {
format!(
"0x{:08x}/0x{:08x}",
groups.memberships.bits(),
groups.filter.bits()
)
}
fn merge(target: &mut Value, extra: Value) {
let (Some(target), Some(extra)) = (target.as_object_mut(), extra.as_object()) else {
return;
};
for (key, value) in extra {
target.insert(key.clone(), value.clone());
}
}