1use crate::render::{
2 Bound, Consts, DebugDrawer, DebugLocals, DebugShadowDrawer, DebugVertex, Mesh, Model, Quad,
3 Renderer, Tri,
4};
5use common::util::srgba_to_linear;
6use hashbrown::{HashMap, HashSet};
7use tracing::warn;
8use vek::*;
9
10#[derive(Debug, PartialEq)]
11pub enum DebugShape {
12 Line([Vec3<f32>; 2], f32),
14 Cylinder {
15 radius: f32,
16 height: f32,
17 },
18 CapsulePrism {
19 p0: Vec2<f32>,
20 p1: Vec2<f32>,
21 head_ratio: f32,
22 radius: f32,
23 height: f32,
24 },
25 TrainTrack {
26 path: CubicBezier3<f32>,
27 rail_width: f32,
28 rail_sep: f32,
29 plank_width: f32,
30 plank_height: f32,
31 plank_sep: f32,
32 },
33}
34
35fn box_along_line(
49 line: LineSegment3<f32>,
50 width: f32,
51 height: f32,
52 color: [f32; 4],
53 mesh: &mut Mesh<DebugVertex>,
54) {
55 let dx = -Vec3::unit_z().cross(line.end - line.start).normalized();
58 let dz = dx.cross(line.end - line.start).normalized();
59 let w = width / 2.0;
60 let h = height / 2.0;
61 let LineSegment3 { start: q, end: r } = line;
62 let a = q - w * dx + h * dz;
63 let b = q + w * dx + h * dz;
64 let c = q - w * dx - h * dz;
65 let d = q + w * dx - h * dz;
66 let e = r - w * dx + h * dz;
67 let f = r + w * dx + h * dz;
68 let g = r - w * dx - h * dz;
69 let h = r + w * dx - h * dz;
70
71 let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
72 let normal = (y - x).cross(z - y).normalized();
73 Quad::<DebugVertex>::new(
74 (x, color, normal).into(),
75 (y, color, normal).into(),
76 (z, color, normal).into(),
77 (w, color, normal).into(),
78 )
79 };
80
81 mesh.push_quad(quad(a, c, d, b));
82 mesh.push_quad(quad(a, b, f, e));
83 mesh.push_quad(quad(a, e, g, c));
84 mesh.push_quad(quad(b, d, h, f));
85 mesh.push_quad(quad(e, f, h, g));
86 mesh.push_quad(quad(d, c, g, h));
87}
88
89impl DebugShape {
90 pub fn mesh(&self) -> Mesh<DebugVertex> {
91 use core::f32::consts::{PI, TAU};
92 let mut mesh = Mesh::new();
93 let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
94 Tri::<DebugVertex>::new(x.into(), y.into(), z.into())
95 };
96 let tri_colored = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, color: [f32; 4]| {
97 let normal = (y - x).cross(z - y).normalized();
98 Tri::<DebugVertex>::new(
99 (x, color, normal).into(),
100 (y, color, normal).into(),
101 (z, color, normal).into(),
102 )
103 };
104 let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
105 Quad::<DebugVertex>::new(x.into(), y.into(), z.into(), w.into())
106 };
107 let quad_colored =
108 |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>, color: [f32; 4]| {
109 let normal = (y - x).cross(z - y).normalized();
110 Quad::<DebugVertex>::new(
111 (x, color, normal).into(),
112 (y, color, normal).into(),
113 (z, color, normal).into(),
114 (w, color, normal).into(),
115 )
116 };
117
118 match self {
119 DebugShape::Line([a, b], width) => {
120 box_along_line(
123 LineSegment3 { start: *a, end: *b },
124 *width,
125 *width,
126 [1.0; 4],
127 &mut mesh,
128 );
129 },
130 DebugShape::Cylinder { radius, height } => {
131 const SUBDIVISIONS: u8 = 16;
132 for i in 0..SUBDIVISIONS {
133 let to = |n: u8| {
135 let angle = TAU * f32::from(n) / f32::from(SUBDIVISIONS);
136
137 Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)
138 };
139
140 let origin = Vec3::zero();
141 let r0 = to(i);
142 let r1 = to(i + 1);
143
144 let h = Vec3::new(0.0, 0.0, *height);
145
146 mesh.push_tri(tri(r1, r0, origin));
148 mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
150 mesh.push_tri(tri(origin + h, r0 + h, r1 + h));
152 }
153 },
154 DebugShape::CapsulePrism {
155 p0,
156 p1,
157 head_ratio,
158 radius,
159 height,
160 } => {
161 const HALF_SECTORS: u8 = 8;
163 const TOTAL: u8 = HALF_SECTORS * 2;
164 const YELLOW: [f32; 4] = [0.6, 0.2, 0.0, 1.0];
167
168 let offset = (p0 - p1).angle_between(Vec2::new(0.0, 1.0));
169 let h = Vec3::new(0.0, 0.0, *height);
170 let neck_point = 1.0 - *head_ratio;
171
172 let draw_cylinder_sector =
173 |mesh: &mut Mesh<DebugVertex>, origin: Vec3<f32>, from: u8, to: u8| {
174 for i in from..to {
175 let to = |n: u8| {
177 let angle = offset + TAU * f32::from(n) / f32::from(TOTAL);
178 let (x, y) = (radius * angle.cos(), radius * angle.sin());
179 let to_edge = Vec3::new(x, y, 0.0);
180
181 origin + to_edge
182 };
183
184 let r0 = to(i);
185 let r1 = to(i + 1);
186
187 mesh.push_tri(tri(r1, r0, origin));
190 mesh.push_quad(quad(r0, r1, r1 + h * neck_point, r0 + h * neck_point));
192 mesh.push_quad(quad_colored(
194 r0 + h * neck_point,
195 r1 + h * neck_point,
196 r1 + h,
197 r0 + h,
198 YELLOW,
199 ));
200 mesh.push_tri(tri_colored(origin + h, r0 + h, r1 + h, YELLOW));
202 }
203 };
204
205 let p0 = Vec3::new(p0.x, p0.y, 0.0);
206 let p1 = Vec3::new(p1.x, p1.y, 0.0);
207 draw_cylinder_sector(&mut mesh, p0, 0, HALF_SECTORS);
209
210 let a = p1 - p0;
213 let a = a / a.magnitude();
215 let a = a * *radius;
217 let orthogonal = Quaternion::rotation_z(PI / 2.0);
219 let shift = orthogonal * a;
220
221 let a0 = p0 + shift;
223 let b0 = p0 - shift;
224 let c0 = p1 - shift;
225 let d0 = p1 + shift;
226
227 let a_mid = a0 + h * neck_point;
229 let a_top = a0 + h;
230 let b_mid = b0 + h * neck_point;
231 let b_top = b0 + h;
232 let c_mid = c0 + h * neck_point;
233 let c_top = c0 + h;
234 let d_mid = d0 + h * neck_point;
235 let d_top = d0 + h;
236
237 mesh.push_quad(quad(d0, c0, b0, a0));
239
240 mesh.push_quad(quad(d0, a0, a_mid, d_mid));
244 mesh.push_quad(quad_colored(d_mid, a_mid, a_top, d_top, YELLOW));
245
246 mesh.push_quad(quad(b0, c0, c_mid, b_mid));
247 mesh.push_quad(quad_colored(b_mid, c_mid, c_top, b_top, YELLOW));
248
249 mesh.push_quad(quad_colored(a_top, b_top, c_top, d_top, YELLOW));
251
252 draw_cylinder_sector(&mut mesh, p1, HALF_SECTORS, TOTAL);
254 },
255 DebugShape::TrainTrack {
256 path,
257 rail_width,
258 rail_sep,
259 plank_width,
260 plank_height,
261 plank_sep,
262 } => {
263 const STEEL_COLOR: [f32; 4] = [0.6, 0.6, 0.6, 1.0];
264 const WOOD_COLOR: [f32; 4] = [0.6, 0.2, 0.0, 1.0];
265 const SUBPLANK_LENGTH: usize = 5;
266 let length = path.length_by_discretization(100);
267 let num_planks = (length / (plank_sep + plank_width)).ceil() as usize;
268 let step_size = 1.0 / (SUBPLANK_LENGTH * num_planks) as f32;
269 for i in 0..(SUBPLANK_LENGTH * num_planks) {
270 let start = path.evaluate(i as f32 * step_size);
271 let end = path.evaluate((i + 1) as f32 * step_size);
272 let center = LineSegment3 { start, end };
273 let dx =
274 *rail_sep * -Vec3::unit_z().cross(center.end - center.start).normalized();
275 let dz = dx.cross(center.end - center.start).normalized();
276 let left = LineSegment3 {
277 start: center.start + dx,
278 end: center.end + dx,
279 };
280 let right = LineSegment3 {
281 start: center.start - dx,
282 end: center.end - dx,
283 };
284 box_along_line(left, *rail_width, *rail_width, STEEL_COLOR, &mut mesh);
285 box_along_line(right, *rail_width, *rail_width, STEEL_COLOR, &mut mesh);
286 if i % SUBPLANK_LENGTH == 0 {
288 let across = LineSegment3 {
289 start: center.start - 1.5 * dx - *rail_width * dz,
290 end: center.start + 1.5 * dx - *rail_width * dz,
291 };
292 box_along_line(across, *plank_width, *plank_height, WOOD_COLOR, &mut mesh);
293 }
294 }
295 },
296 }
297 mesh
298 }
299}
300
301#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
302pub struct DebugShapeId(pub u64);
303
304pub struct Debug {
305 next_shape_id: DebugShapeId,
306 shapes: HashMap<DebugShapeId, DebugShape>,
307 pending: HashSet<DebugShapeId>,
308 pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
309 pending_deletes: HashSet<DebugShapeId>,
310 models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
311 casts_shadow: HashSet<DebugShapeId>,
312}
313
314impl Debug {
315 pub fn new() -> Debug {
316 Debug {
317 next_shape_id: DebugShapeId(0),
318 shapes: HashMap::new(),
319 pending: HashSet::new(),
320 pending_locals: HashMap::new(),
321 pending_deletes: HashSet::new(),
322 models: HashMap::new(),
323 casts_shadow: HashSet::new(),
324 }
325 }
326
327 pub fn add_shape(&mut self, shape: DebugShape) -> DebugShapeId {
328 let id = DebugShapeId(self.next_shape_id.0);
329 self.next_shape_id.0 += 1;
330 if matches!(shape, DebugShape::TrainTrack { .. }) {
331 self.casts_shadow.insert(id);
332 }
333 self.shapes.insert(id, shape);
334 self.pending.insert(id);
335 id
336 }
337
338 pub fn get_shape(&self, id: DebugShapeId) -> Option<&DebugShape> { self.shapes.get(&id) }
339
340 pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) {
341 self.pending_locals.insert(id, (pos, color, ori));
342 }
343
344 pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
345
346 pub fn maintain(&mut self, renderer: &mut Renderer) {
347 for id in self.pending.drain() {
348 if let Some(shape) = self.shapes.get(&id) {
349 if let Some(model) = renderer.create_model(&shape.mesh()) {
350 let locals = renderer.create_debug_bound_locals(&[DebugLocals {
351 pos: [0.0; 4],
352 color: [1.0, 0.0, 0.0, 1.0],
353 ori: [0.0, 0.0, 0.0, 1.0],
354 }]);
355 self.models.insert(id, (model, locals));
356 } else {
357 warn!(
358 "Failed to create model for debug shape {:?}: {:?}",
359 id, shape
360 );
361 }
362 }
363 }
364 for (id, (pos, color, ori)) in self.pending_locals.drain() {
365 if let Some((_, locals)) = self.models.get_mut(&id) {
366 let lc = srgba_to_linear(color.into());
367 let new_locals = [DebugLocals {
368 pos,
369 color: [lc.r, lc.g, lc.b, lc.a],
370 ori,
371 }];
372 renderer.update_consts(locals, &new_locals);
373 } else {
374 warn!(
375 "Tried to update locals for nonexistent debug shape {:?}",
376 id
377 );
378 }
379 }
380 for id in self.pending_deletes.drain() {
381 self.models.remove(&id);
382 self.shapes.remove(&id);
383 }
384 }
385
386 pub fn render<'a>(&'a self, drawer: &mut DebugDrawer<'_, 'a>) {
387 for (model, locals) in self.models.values() {
388 drawer.draw(model, locals);
389 }
390 }
391
392 pub fn render_shadows<'a>(&'a self, drawer: &mut DebugShadowDrawer<'_, 'a>) {
393 for id in self.casts_shadow.iter() {
394 if let Some((model, locals)) = self.models.get(id) {
395 drawer.draw(model, locals);
396 }
397 }
398 }
399}
400
401impl Default for Debug {
402 fn default() -> Debug { Debug::new() }
403}