1#[derive(Debug, Clone, PartialEq)]
8pub struct CapsuleProxy {
9 pub center_a: [f32; 3],
11 pub center_b: [f32; 3],
13 pub radius: f32,
15 pub label: String,
17}
18
19impl CapsuleProxy {
20 pub fn new(center_a: [f32; 3], center_b: [f32; 3], radius: f32, label: &str) -> Self {
21 CapsuleProxy {
22 center_a,
23 center_b,
24 radius,
25 label: label.to_string(),
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub struct SphereProxy {
33 pub center: [f32; 3],
34 pub radius: f32,
35 pub label: String,
36}
37
38impl SphereProxy {
39 pub fn new(center: [f32; 3], radius: f32, label: &str) -> Self {
40 SphereProxy {
41 center,
42 radius,
43 label: label.to_string(),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub struct BoxProxy {
51 pub center: [f32; 3],
52 pub half_extents: [f32; 3],
53 pub label: String,
54}
55
56#[derive(Debug, Default, Clone)]
58pub struct BodyProxies {
59 pub capsules: Vec<CapsuleProxy>,
60 pub spheres: Vec<SphereProxy>,
61 pub boxes: Vec<BoxProxy>,
62}
63
64impl BodyProxies {
65 pub fn new() -> Self {
66 BodyProxies::default()
67 }
68
69 pub fn total_count(&self) -> usize {
70 self.capsules.len() + self.spheres.len() + self.boxes.len()
71 }
72}
73
74fn fmt_vec3(v: [f32; 3]) -> String {
78 format!("[{},{},{}]", v[0], v[1], v[2])
79}
80
81fn json_escape(s: &str) -> String {
83 let mut out = String::with_capacity(s.len() + 2);
84 for ch in s.chars() {
85 match ch {
86 '"' => out.push_str("\\\""),
87 '\\' => out.push_str("\\\\"),
88 '\n' => out.push_str("\\n"),
89 '\r' => out.push_str("\\r"),
90 '\t' => out.push_str("\\t"),
91 other => out.push(other),
92 }
93 }
94 out
95}
96
97pub fn proxies_to_json(proxies: &BodyProxies) -> String {
108 let mut out = String::with_capacity(512);
109 out.push_str("{\n \"capsules\": [\n");
110
111 for (i, c) in proxies.capsules.iter().enumerate() {
112 let comma = if i + 1 < proxies.capsules.len() {
113 ","
114 } else {
115 ""
116 };
117 out.push_str(&format!(
118 " {{\"label\":\"{}\",\"center_a\":{},\"center_b\":{},\"radius\":{}}}{}\n",
119 json_escape(&c.label),
120 fmt_vec3(c.center_a),
121 fmt_vec3(c.center_b),
122 c.radius,
123 comma
124 ));
125 }
126
127 out.push_str(" ],\n \"spheres\": [\n");
128
129 for (i, s) in proxies.spheres.iter().enumerate() {
130 let comma = if i + 1 < proxies.spheres.len() {
131 ","
132 } else {
133 ""
134 };
135 out.push_str(&format!(
136 " {{\"label\":\"{}\",\"center\":{},\"radius\":{}}}{}\n",
137 json_escape(&s.label),
138 fmt_vec3(s.center),
139 s.radius,
140 comma
141 ));
142 }
143
144 out.push_str(" ],\n \"boxes\": [\n");
145
146 for (i, b) in proxies.boxes.iter().enumerate() {
147 let comma = if i + 1 < proxies.boxes.len() { "," } else { "" };
148 out.push_str(&format!(
149 " {{\"label\":\"{}\",\"center\":{},\"half_extents\":{}}}{}\n",
150 json_escape(&b.label),
151 fmt_vec3(b.center),
152 fmt_vec3(b.half_extents),
153 comma
154 ));
155 }
156
157 out.push_str(" ]\n}");
158 out
159}
160
161pub fn proxies_from_json(s: &str) -> anyhow::Result<BodyProxies> {
165 let v: serde_json::Value = serde_json::from_str(s)?;
166
167 let parse_vec3 = |arr: &serde_json::Value| -> anyhow::Result<[f32; 3]> {
168 let a = arr
169 .as_array()
170 .ok_or_else(|| anyhow::anyhow!("expected array for vec3"))?;
171 if a.len() != 3 {
172 anyhow::bail!("vec3 must have 3 elements, got {}", a.len());
173 }
174 Ok([
175 a[0].as_f64()
176 .ok_or_else(|| anyhow::anyhow!("expected float"))? as f32,
177 a[1].as_f64()
178 .ok_or_else(|| anyhow::anyhow!("expected float"))? as f32,
179 a[2].as_f64()
180 .ok_or_else(|| anyhow::anyhow!("expected float"))? as f32,
181 ])
182 };
183
184 let get_str = |obj: &serde_json::Value, key: &str| -> anyhow::Result<String> {
185 obj[key]
186 .as_str()
187 .map(|s| s.to_string())
188 .ok_or_else(|| anyhow::anyhow!("missing string field '{key}'"))
189 };
190
191 let get_f32 = |obj: &serde_json::Value, key: &str| -> anyhow::Result<f32> {
192 obj[key]
193 .as_f64()
194 .map(|f| f as f32)
195 .ok_or_else(|| anyhow::anyhow!("missing float field '{key}'"))
196 };
197
198 let mut proxies = BodyProxies::new();
199
200 if let Some(caps) = v["capsules"].as_array() {
201 for c in caps {
202 proxies.capsules.push(CapsuleProxy {
203 label: get_str(c, "label")?,
204 center_a: parse_vec3(&c["center_a"])?,
205 center_b: parse_vec3(&c["center_b"])?,
206 radius: get_f32(c, "radius")?,
207 });
208 }
209 }
210
211 if let Some(spheres) = v["spheres"].as_array() {
212 for s in spheres {
213 proxies.spheres.push(SphereProxy {
214 label: get_str(s, "label")?,
215 center: parse_vec3(&s["center"])?,
216 radius: get_f32(s, "radius")?,
217 });
218 }
219 }
220
221 if let Some(boxes) = v["boxes"].as_array() {
222 for b in boxes {
223 proxies.boxes.push(BoxProxy {
224 label: get_str(b, "label")?,
225 center: parse_vec3(&b["center"])?,
226 half_extents: parse_vec3(&b["half_extents"])?,
227 });
228 }
229 }
230
231 Ok(proxies)
232}