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        radius: f32,
22        height: f32,
23    },
24    TrainTrack {
25        path: CubicBezier3<f32>,
26        rail_width: f32,
27        rail_sep: f32,
28        plank_width: f32,
29        plank_height: f32,
30        plank_sep: f32,
31    },
32}
33
34/// If (q, r) is the given `line`, append the following mesh to `mesh`, where
35/// the distance between a-b is `width` and b-d is `height`:
36///       e-----f
37///      /|    /|
38///     / |  r/ |
39///    /  |  /  |
40///   /   g-/-- h
41///  /   / /   /
42/// a-----b   /
43/// |  /  |  /
44/// | /q  | /
45/// |/    |/
46/// c-----d
47fn box_along_line(
48    line: LineSegment3<f32>,
49    width: f32,
50    height: f32,
51    color: [f32; 4],
52    mesh: &mut Mesh<DebugVertex>,
53) {
54    // dx is along b-a
55    // dz is along b-d
56    let dx = -Vec3::unit_z().cross(line.end - line.start).normalized();
57    let dz = dx.cross(line.end - line.start).normalized();
58    let w = width / 2.0;
59    let h = height / 2.0;
60    let LineSegment3 { start: q, end: r } = line;
61    let a = q - w * dx + h * dz;
62    let b = q + w * dx + h * dz;
63    let c = q - w * dx - h * dz;
64    let d = q + w * dx - h * dz;
65    let e = r - w * dx + h * dz;
66    let f = r + w * dx + h * dz;
67    let g = r - w * dx - h * dz;
68    let h = r + w * dx - h * dz;
69
70    let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
71        let normal = (y - x).cross(z - y).normalized();
72        Quad::<DebugVertex>::new(
73            (x, color, normal).into(),
74            (y, color, normal).into(),
75            (z, color, normal).into(),
76            (w, color, normal).into(),
77        )
78    };
79
80    mesh.push_quad(quad(a, c, d, b));
81    mesh.push_quad(quad(a, b, f, e));
82    mesh.push_quad(quad(a, e, g, c));
83    mesh.push_quad(quad(b, d, h, f));
84    mesh.push_quad(quad(e, f, h, g));
85    mesh.push_quad(quad(d, c, g, h));
86}
87
88impl DebugShape {
89    pub fn mesh(&self) -> Mesh<DebugVertex> {
90        use core::f32::consts::{PI, TAU};
91        let mut mesh = Mesh::new();
92        let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
93            Tri::<DebugVertex>::new(x.into(), y.into(), z.into())
94        };
95        let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
96            Quad::<DebugVertex>::new(x.into(), y.into(), z.into(), w.into())
97        };
98
99        match self {
100            DebugShape::Line([a, b], width) => {
101                //let h = Vec3::new(0.0, 1.0, 0.0);
102                //mesh.push_quad(quad(*a, a + h, b + h, *b));
103                box_along_line(
104                    LineSegment3 { start: *a, end: *b },
105                    *width,
106                    *width,
107                    [1.0; 4],
108                    &mut mesh,
109                );
110            },
111            DebugShape::Cylinder { radius, height } => {
112                const SUBDIVISIONS: u8 = 16;
113                for i in 0..SUBDIVISIONS {
114                    // dot on circle edge
115                    let to = |n: u8| {
116                        let angle = TAU * f32::from(n) / f32::from(SUBDIVISIONS);
117
118                        Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)
119                    };
120
121                    let origin = Vec3::zero();
122                    let r0 = to(i);
123                    let r1 = to(i + 1);
124
125                    let h = Vec3::new(0.0, 0.0, *height);
126
127                    // Draw bottom sector
128                    mesh.push_tri(tri(r1, r0, origin));
129                    // Draw face
130                    mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
131                    // Draw top sector
132                    mesh.push_tri(tri(origin + h, r0 + h, r1 + h));
133                }
134            },
135            DebugShape::CapsulePrism {
136                p0,
137                p1,
138                radius,
139                height,
140            } => {
141                // We split circle in two parts
142                const HALF_SECTORS: u8 = 8;
143                const TOTAL: u8 = HALF_SECTORS * 2;
144
145                let offset = (p0 - p1).angle_between(Vec2::new(0.0, 1.0));
146                let h = Vec3::new(0.0, 0.0, *height);
147
148                let draw_cylinder_sector =
149                    |mesh: &mut Mesh<DebugVertex>, origin: Vec3<f32>, from: u8, to: u8| {
150                        for i in from..to {
151                            // dot on circle edge
152                            let to = |n: u8| {
153                                let angle = offset + TAU * f32::from(n) / f32::from(TOTAL);
154                                let (x, y) = (radius * angle.cos(), radius * angle.sin());
155                                let to_edge = Vec3::new(x, y, 0.0);
156
157                                origin + to_edge
158                            };
159
160                            let r0 = to(i);
161                            let r1 = to(i + 1);
162
163                            // Draw bottom sector
164                            mesh.push_tri(tri(r1, r0, origin));
165                            // Draw face
166                            mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
167                            // Draw top sector
168                            mesh.push_tri(tri(origin + h, r0 + h, r1 + h));
169                        }
170                    };
171
172                let p0 = Vec3::new(p0.x, p0.y, 0.0);
173                let p1 = Vec3::new(p1.x, p1.y, 0.0);
174                // 1) Draw first half-cylinder
175                draw_cylinder_sector(&mut mesh, p0, 0, HALF_SECTORS);
176
177                // 2) Draw cuboid in-between
178                // get main line segment
179                let a = p1 - p0;
180                // normalize
181                let a = a / a.magnitude();
182                // stretch to radius
183                let a = a * *radius;
184                // rotate to 90 degrees to get needed shift
185                let orthogonal = Quaternion::rotation_z(PI / 2.0);
186                let shift = orthogonal * a;
187
188                // bottom points
189                let a0 = p0 + shift;
190                let b0 = p0 - shift;
191                let c0 = p1 - shift;
192                let d0 = p1 + shift;
193
194                // top points
195                let a1 = a0 + h;
196                let b1 = b0 + h;
197                let c1 = c0 + h;
198                let d1 = d0 + h;
199
200                // Bottom
201                mesh.push_quad(quad(d0, c0, b0, a0));
202
203                // Faces
204                // (we need only two of them, because other two are inside)
205                mesh.push_quad(quad(d0, a0, a1, d1));
206                mesh.push_quad(quad(b0, c0, c1, b1));
207
208                // Top
209                mesh.push_quad(quad(a1, b1, c1, d1));
210
211                // 3) Draw second half-cylinder
212                draw_cylinder_sector(&mut mesh, p1, HALF_SECTORS, TOTAL);
213            },
214            DebugShape::TrainTrack {
215                path,
216                rail_width,
217                rail_sep,
218                plank_width,
219                plank_height,
220                plank_sep,
221            } => {
222                const STEEL_COLOR: [f32; 4] = [0.6, 0.6, 0.6, 1.0];
223                const WOOD_COLOR: [f32; 4] = [0.6, 0.2, 0.0, 1.0];
224                const SUBPLANK_LENGTH: usize = 5;
225                let length = path.length_by_discretization(100);
226                let num_planks = (length / (plank_sep + plank_width)).ceil() as usize;
227                let step_size = 1.0 / (SUBPLANK_LENGTH * num_planks) as f32;
228                for i in 0..(SUBPLANK_LENGTH * num_planks) {
229                    let start = path.evaluate(i as f32 * step_size);
230                    let end = path.evaluate((i + 1) as f32 * step_size);
231                    let center = LineSegment3 { start, end };
232                    let dx =
233                        *rail_sep * -Vec3::unit_z().cross(center.end - center.start).normalized();
234                    let dz = dx.cross(center.end - center.start).normalized();
235                    let left = LineSegment3 {
236                        start: center.start + dx,
237                        end: center.end + dx,
238                    };
239                    let right = LineSegment3 {
240                        start: center.start - dx,
241                        end: center.end - dx,
242                    };
243                    box_along_line(left, *rail_width, *rail_width, STEEL_COLOR, &mut mesh);
244                    box_along_line(right, *rail_width, *rail_width, STEEL_COLOR, &mut mesh);
245                    //box_along_line(center, 0.1, 0.1, [1.0, 0.0, 0.0, 1.0], &mut mesh);
246                    if i % SUBPLANK_LENGTH == 0 {
247                        let across = LineSegment3 {
248                            start: center.start - 1.5 * dx - *rail_width * dz,
249                            end: center.start + 1.5 * dx - *rail_width * dz,
250                        };
251                        box_along_line(across, *plank_width, *plank_height, WOOD_COLOR, &mut mesh);
252                    }
253                }
254            },
255        }
256        mesh
257    }
258}
259
260#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
261pub struct DebugShapeId(pub u64);
262
263pub struct Debug {
264    next_shape_id: DebugShapeId,
265    shapes: HashMap<DebugShapeId, DebugShape>,
266    pending: HashSet<DebugShapeId>,
267    pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
268    pending_deletes: HashSet<DebugShapeId>,
269    models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
270    casts_shadow: HashSet<DebugShapeId>,
271}
272
273impl Debug {
274    pub fn new() -> Debug {
275        Debug {
276            next_shape_id: DebugShapeId(0),
277            shapes: HashMap::new(),
278            pending: HashSet::new(),
279            pending_locals: HashMap::new(),
280            pending_deletes: HashSet::new(),
281            models: HashMap::new(),
282            casts_shadow: HashSet::new(),
283        }
284    }
285
286    pub fn add_shape(&mut self, shape: DebugShape) -> DebugShapeId {
287        let id = DebugShapeId(self.next_shape_id.0);
288        self.next_shape_id.0 += 1;
289        if matches!(shape, DebugShape::TrainTrack { .. }) {
290            self.casts_shadow.insert(id);
291        }
292        self.shapes.insert(id, shape);
293        self.pending.insert(id);
294        id
295    }
296
297    pub fn get_shape(&self, id: DebugShapeId) -> Option<&DebugShape> { self.shapes.get(&id) }
298
299    pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) {
300        self.pending_locals.insert(id, (pos, color, ori));
301    }
302
303    pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
304
305    pub fn maintain(&mut self, renderer: &mut Renderer) {
306        for id in self.pending.drain() {
307            if let Some(shape) = self.shapes.get(&id) {
308                if let Some(model) = renderer.create_model(&shape.mesh()) {
309                    let locals = renderer.create_debug_bound_locals(&[DebugLocals {
310                        pos: [0.0; 4],
311                        color: [1.0, 0.0, 0.0, 1.0],
312                        ori: [0.0, 0.0, 0.0, 1.0],
313                    }]);
314                    self.models.insert(id, (model, locals));
315                } else {
316                    warn!(
317                        "Failed to create model for debug shape {:?}: {:?}",
318                        id, shape
319                    );
320                }
321            }
322        }
323        for (id, (pos, color, ori)) in self.pending_locals.drain() {
324            if let Some((_, locals)) = self.models.get_mut(&id) {
325                let lc = srgba_to_linear(color.into());
326                let new_locals = [DebugLocals {
327                    pos,
328                    color: [lc.r, lc.g, lc.b, lc.a],
329                    ori,
330                }];
331                renderer.update_consts(locals, &new_locals);
332            } else {
333                warn!(
334                    "Tried to update locals for nonexistent debug shape {:?}",
335                    id
336                );
337            }
338        }
339        for id in self.pending_deletes.drain() {
340            self.models.remove(&id);
341            self.shapes.remove(&id);
342        }
343    }
344
345    pub fn render<'a>(&'a self, drawer: &mut DebugDrawer<'_, 'a>) {
346        for (model, locals) in self.models.values() {
347            drawer.draw(model, locals);
348        }
349    }
350
351    pub fn render_shadows<'a>(&'a self, drawer: &mut DebugShadowDrawer<'_, 'a>) {
352        for id in self.casts_shadow.iter() {
353            if let Some((model, locals)) = self.models.get(id) {
354                drawer.draw(model, locals);
355            }
356        }
357    }
358}
359
360impl Default for Debug {
361    fn default() -> Debug { Debug::new() }
362}