vimp_engine_core/
debug.rs1use rapier2d::prelude::*;
13use serde_json::{Value, json};
14
15use crate::map::GameMap;
16use crate::nav::navigation::NavigationSystem;
17use crate::nav::spatial::SpatialGrid;
18use crate::physics::round2;
19use crate::rng::Rng;
20
21#[allow(clippy::too_many_arguments)]
23pub fn engine_json(
24 world: &PhysicsWorld,
25 map: &Option<GameMap>,
26 nav: &Option<NavigationSystem>,
27 spatial: &SpatialGrid,
28 rng: &Rng,
29 accumulator: f32,
30 time_step: f32,
31) -> Value {
32 json!({
33 "bodies": bodies_json(world),
34 "colliders": colliders_json(world),
35 "map": map_json(map),
36 "nav": nav_json(nav),
37 "spatial": spatial_json(spatial),
38 "rng": { "state": rng.state().to_string() },
39 "step": {
40 "timeStep": time_step,
41 "accumulator": accumulator,
42 },
43 })
44}
45
46fn bodies_json(world: &PhysicsWorld) -> Value {
50 let mut rows: Vec<(u32, Value)> = world
51 .rigid_bodies()
52 .map(|(handle, body)| {
53 let translation = body.translation();
54 let linvel = body.linvel();
55
56 (
57 handle.into_raw_parts().0,
58 json!({
59 "handle": handle.into_raw_parts().0,
60 "tag": (body.user_data & 0xff) as u64,
61 "userData": body.user_data.to_string(),
62 "translation": [round2(translation.x), round2(translation.y)],
63 "rotation": round2(body.rotation().angle()),
64 "linvel": [round2(linvel.x), round2(linvel.y)],
65 "angvel": round2(body.angvel()),
66 "mass": round2(body.mass()),
67 "bodyType": format!("{:?}", body.body_type()),
68 "ccd": body.is_ccd_enabled(),
69 }),
70 )
71 })
72 .collect();
73
74 rows.sort_by_key(|(index, _)| *index);
75
76 Value::Array(rows.into_iter().map(|(_, row)| row).collect())
77}
78
79fn colliders_json(world: &PhysicsWorld) -> Value {
82 let mut rows: Vec<(u32, Value)> = world
83 .colliders
84 .iter()
85 .map(|(handle, collider)| {
86 let collision = collider.collision_groups();
87 let solver = collider.solver_groups();
88 let mut row = json!({
89 "handle": handle.into_raw_parts().0,
90 "isSensor": collider.is_sensor(),
91 "collisionGroups": groups_hex(collision),
92 "solverGroups": groups_hex(solver),
93 "parent": collider.parent().map(|body| body.into_raw_parts().0),
94 });
95
96 merge(&mut row, shape_json(collider.shape()));
97
98 (handle.into_raw_parts().0, row)
99 })
100 .collect();
101
102 rows.sort_by_key(|(index, _)| *index);
103
104 Value::Array(rows.into_iter().map(|(_, row)| row).collect())
105}
106
107fn shape_json(shape: &dyn Shape) -> Value {
111 if let Some(cuboid) = shape.as_cuboid() {
112 let half = cuboid.half_extents;
113
114 return json!({
115 "shape": "cuboid",
116 "halfExtents": [round2(half.x), round2(half.y)],
117 });
118 }
119
120 if let Some(ball) = shape.as_ball() {
121 return json!({ "shape": "ball", "radius": round2(ball.radius) });
122 }
123
124 json!({ "shape": format!("{:?}", shape.shape_type()) })
125}
126
127fn map_json(map: &Option<GameMap>) -> Value {
128 let Some(map) = map else {
129 return Value::Null;
130 };
131
132 let rows = map.grid.len();
133 let cols = map.grid.first().map(|row| row.len()).unwrap_or(0);
134
135 json!({
136 "setId": map.set_id,
137 "step": map.step,
138 "grid": { "rows": rows, "cols": cols },
139 "physicsStatic": map.physics_static,
140 "staticBodies": map.static_body_count(),
141 "dynamicBodies": map.dynamic_body_count(),
142 "respawns": map
143 .respawns
144 .iter()
145 .map(|(team, points)| (team.clone(), Value::from(points.len())))
146 .collect::<serde_json::Map<String, Value>>(),
147 })
148}
149
150fn nav_json(nav: &Option<NavigationSystem>) -> Value {
151 let Some(nav) = nav else {
152 return Value::Null;
153 };
154
155 json!({
156 "nodes": nav.node_count(),
157 "edges": nav.edge_count(),
158 "gridStep": nav.grid_step(),
159 })
160}
161
162fn spatial_json(spatial: &SpatialGrid) -> Value {
163 let cells = spatial.cell_counts();
164
165 json!({
166 "cellSize": spatial.cell_size(),
167 "cells": cells.len(),
168 "entities": cells.iter().map(|(_, _, count)| count).sum::<usize>(),
169 "cellCounts": cells
170 .iter()
171 .map(|(cx, cy, count)| json!({ "cell": [cx, cy], "count": count }))
172 .collect::<Vec<Value>>(),
173 })
174}
175
176fn groups_hex(groups: InteractionGroups) -> String {
177 format!(
178 "0x{:08x}/0x{:08x}",
179 groups.memberships.bits(),
180 groups.filter.bits()
181 )
182}
183
184fn merge(target: &mut Value, extra: Value) {
186 let (Some(target), Some(extra)) = (target.as_object_mut(), extra.as_object()) else {
187 return;
188 };
189
190 for (key, value) in extra {
191 target.insert(key.clone(), value.clone());
192 }
193}