1use 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 let mut static_by_level = vec![0usize; map.level_count()];
136
137 for &level in map.static_levels() {
138 if let Some(count) = static_by_level.get_mut(level as usize) {
139 *count += 1;
140 }
141 }
142
143 json!({
144 "setId": map.set_id,
145 "step": map.step,
146 "grid": { "rows": rows, "cols": cols },
147 "physicsStatic": map.physics_static,
148 "staticBodies": map.static_body_count(),
149 "dynamicBodies": map.dynamic_body_count(),
150 "levels": map.level_count(),
151 "layered": map.is_layered(),
152 "staticByLevel": static_by_level,
153 "ramps": map
154 .levels()
155 .runs()
156 .iter()
157 .map(|run| json!({
158 "axis": run.axis,
159 "sign": run.sign,
160 "from": run.from,
161 "to": run.to,
162 "min": round2(run.min),
163 "max": round2(run.max),
164 }))
165 .collect::<Vec<Value>>(),
166 "dynamicLevels": map.dynamic_levels(),
167 "respawns": map
168 .respawns
169 .iter()
170 .map(|(team, points)| (team.clone(), Value::from(points.len())))
171 .collect::<serde_json::Map<String, Value>>(),
172 })
173}
174
175fn nav_json(nav: &Option<NavigationSystem>) -> Value {
176 let Some(nav) = nav else {
177 return Value::Null;
178 };
179
180 json!({
181 "nodes": nav.node_count(),
182 "edges": nav.edge_count(),
183 "gridStep": nav.grid_step(),
184 "nodesByLevel": nav.nodes_by_level(),
185 "rampEdges": nav.ramp_edge_count(),
186 "ledgeEdges": nav.ledge_edge_count(),
187 })
188}
189
190fn spatial_json(spatial: &SpatialGrid) -> Value {
191 let cells = spatial.cell_counts();
192
193 json!({
194 "cellSize": spatial.cell_size(),
195 "cells": cells.len(),
196 "entities": cells.iter().map(|(_, _, count)| count).sum::<usize>(),
197 "cellCounts": cells
198 .iter()
199 .map(|(cx, cy, count)| json!({ "cell": [cx, cy], "count": count }))
200 .collect::<Vec<Value>>(),
201 })
202}
203
204fn groups_hex(groups: InteractionGroups) -> String {
205 format!(
206 "0x{:08x}/0x{:08x}",
207 groups.memberships.bits(),
208 groups.filter.bits()
209 )
210}
211
212fn merge(target: &mut Value, extra: Value) {
214 let (Some(target), Some(extra)) = (target.as_object_mut(), extra.as_object()) else {
215 return;
216 };
217
218 for (key, value) in extra {
219 target.insert(key.clone(), value.clone());
220 }
221}