veloren_voxygen/scene/
debug.rs

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    /// [Start, End], width
13    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
35/// If (q, r) is the given `line`, append the following mesh to `mesh`, where
36/// the distance between a-b is `width` and b-d is `height`:
37///       e-----f
38///      /|    /|
39///     / |  r/ |
40///    /  |  /  |
41///   /   g-/-- h
42///  /   / /   /
43/// a-----b   /
44/// |  /  |  /
45/// | /q  | /
46/// |/    |/
47/// c-----d
48fn box_along_line(
49    line: LineSegment3<f32>,
50    width: f32,
51    height: f32,
52    color: [f32; 4],
53    mesh: &mut Mesh<DebugVertex>,
54) {
55    // dx is along b-a
56    // dz is along b-d
57    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                //let h = Vec3::new(0.0, 1.0, 0.0);
121                //mesh.push_quad(quad(*a, a + h, b + h, *b));
122                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                    // dot on circle edge
134                    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                    // Draw bottom sector
147                    mesh.push_tri(tri(r1, r0, origin));
148                    // Draw face
149                    mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
150                    // Draw top sector
151                    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                // We split circle in two parts
162                const HALF_SECTORS: u8 = 8;
163                const TOTAL: u8 = HALF_SECTORS * 2;
164                // FIXME: doesn't really work
165                // but it does change the coloring or something
166                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                            // dot on circle edge
176                            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                            // 1. Draw everything up to neck
188                            // Draw bottom sector
189                            mesh.push_tri(tri(r1, r0, origin));
190                            // Draw body face
191                            mesh.push_quad(quad(r0, r1, r1 + h * neck_point, r0 + h * neck_point));
192                            // 2. Draw everything after neck
193                            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                            // Draw top sector
201                            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                // 1) Draw first half-cylinder
208                draw_cylinder_sector(&mut mesh, p0, 0, HALF_SECTORS);
209
210                // 2) Draw cuboid in-between
211                // get main line segment
212                let a = p1 - p0;
213                // normalize
214                let a = a / a.magnitude();
215                // stretch to radius
216                let a = a * *radius;
217                // rotate to 90 degrees to get needed shift
218                let orthogonal = Quaternion::rotation_z(PI / 2.0);
219                let shift = orthogonal * a;
220
221                // bottom points
222                let a0 = p0 + shift;
223                let b0 = p0 - shift;
224                let c0 = p1 - shift;
225                let d0 = p1 + shift;
226
227                // top points
228                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                // Bottom
238                mesh.push_quad(quad(d0, c0, b0, a0));
239
240                // Faces (front and back, no need to draw internal ones)
241                //
242                // Up to neck, and after neck as above.
243                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                // Top
250                mesh.push_quad(quad_colored(a_top, b_top, c_top, d_top, YELLOW));
251
252                // 3) Draw second half-cylinder
253                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                    //box_along_line(center, 0.1, 0.1, [1.0, 0.0, 0.0, 1.0], &mut mesh);
287                    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}