@@ -36,16 +36,36 @@ const DEBUG_LINES_SHADER_HANDLE: HandleUntyped =
36
36
///
37
37
/// App::new()
38
38
/// .add_plugins(DefaultPlugins)
39
- /// .add_plugin(DebugLinesPlugin)
39
+ /// .add_plugin(DebugLinesPlugin::default())
40
+ /// .run();
41
+ /// ```
42
+ ///
43
+ /// Alternatively, you can initialize the plugin without depth testing, so that
44
+ /// debug lines are always visible, even when behind other objects. For this,
45
+ /// you need to use the [`DebugLinesPlugin::always_in_front`] constructor.
46
+ /// ```
47
+ /// use bevy::prelude::*;
48
+ /// use bevy_prototype_debug_lines::*;
49
+ ///
50
+ /// App::new()
51
+ /// .add_plugins(DefaultPlugins)
52
+ /// .add_plugin(DebugLinesPlugin::always_in_front())
40
53
/// .run();
41
54
/// ```
42
55
#[ derive( Debug , Default , Clone ) ]
43
56
pub struct DebugLinesPlugin {
44
- always_on_top : bool ,
57
+ always_in_front : bool ,
45
58
}
46
59
impl DebugLinesPlugin {
47
- pub fn draw_on_top ( always_on_top : bool ) -> Self {
48
- DebugLinesPlugin { always_on_top }
60
+ /// Always show debug lines in front of other objects
61
+ ///
62
+ /// This disables depth culling for the debug line, so that they
63
+ /// are always visible, regardless of whether there are other objects in
64
+ /// front.
65
+ pub fn always_in_front ( ) -> Self {
66
+ DebugLinesPlugin {
67
+ always_in_front : true ,
68
+ }
49
69
}
50
70
}
51
71
impl Plugin for DebugLinesPlugin {
@@ -55,8 +75,8 @@ impl Plugin for DebugLinesPlugin {
55
75
DEBUG_LINES_SHADER_HANDLE ,
56
76
Shader :: from_wgsl ( include_str ! ( "debuglines.wgsl" ) ) ,
57
77
) ;
58
- app. init_resource :: < ImmLinesStorage > ( ) ;
59
- app. init_resource :: < RetLinesStorage > ( ) ;
78
+ app. init_resource :: < ImmediateLinesStorage > ( ) ;
79
+ app. init_resource :: < RetainedLinesStorage > ( ) ;
60
80
app. add_startup_system ( setup_system)
61
81
. add_system_to_stage ( CoreStage :: Last , update_debug_lines_mesh. label ( "draw_lines" ) ) ;
62
82
app. sub_app_mut ( RenderApp )
@@ -126,8 +146,7 @@ fn update_debug_lines_mesh(
126
146
Retained ( i) => lines. retained . fill_attributes ( time, mesh, i) ,
127
147
}
128
148
}
129
- // This needs to be done after `fill_attributes` because of the immediate buffer clears
130
- lines. mark_expired ( ) ;
149
+ lines. frame_init ( ) ;
131
150
}
132
151
133
152
/// Initialize [`DebugLinesMesh`]'s [`Mesh`].
@@ -157,7 +176,8 @@ fn extract_debug_lines(mut commands: Commands, query: Query<Entity, With<DebugLi
157
176
/// Marker Component for the [`Entity`] associated with the meshes rendered with the
158
177
/// debuglines.wgsl shader.
159
178
///
160
- /// Stores the index of the mesh for the logic of `ImmLinesStorage` and `RetLinesStorage`
179
+ /// Stores the index of the mesh for the logic of [`ImmediateLinesStorage`] and
180
+ /// [`RetainedLinesStorage`]
161
181
#[ derive( Component ) ]
162
182
enum DebugLinesMesh {
163
183
/// Meshes for duration=0.0 lines
@@ -190,11 +210,11 @@ struct RenderDebugLinesMesh;
190
210
/// user **should absolutely not interact with this**.
191
211
#[ derive( Debug , Default ) ]
192
212
#[ doc( hidden) ]
193
- pub struct ImmLinesStorage {
213
+ pub struct ImmediateLinesStorage {
194
214
positions : Vec < [ f32 ; 3 ] > ,
195
215
colors : Vec < [ f32 ; 4 ] > ,
196
216
}
197
- impl ImmLinesStorage {
217
+ impl ImmediateLinesStorage {
198
218
fn add_at ( & mut self , line_index : usize , position : Line < Vec3 > , color : Line < Color > ) {
199
219
let i = line_index * 2 ;
200
220
self . colors [ i] = color. start . into ( ) ;
@@ -216,7 +236,11 @@ impl ImmLinesStorage {
216
236
}
217
237
}
218
238
219
- fn mark_expired ( & mut self ) {
239
+ /// Cull all lines that shouldn't be rendered anymore
240
+ ///
241
+ /// Since all lines in `ImmediateLinesStorage` should be removed each frame, this
242
+ /// simply set the length of the positions and colors vectors to 0.
243
+ fn frame_init ( & mut self ) {
220
244
self . positions . clear ( ) ;
221
245
self . colors . clear ( ) ;
222
246
}
@@ -235,6 +259,7 @@ impl ImmLinesStorage {
235
259
}
236
260
}
237
261
262
+ /// Copy line descriptions into mesh attribute buffers
238
263
fn fill_attributes ( & self , mesh : & mut Mesh , mesh_index : usize ) {
239
264
use VertexAttributeValues :: { Float32x3 , Float32x4 } ;
240
265
if let Some ( Float32x3 ( vbuffer) ) = mesh. attribute_mut ( Mesh :: ATTRIBUTE_POSITION ) {
@@ -256,65 +281,81 @@ impl ImmLinesStorage {
256
281
/// The [`DebugLines`] storage.
257
282
#[ derive( Debug , Default ) ]
258
283
#[ doc( hidden) ]
259
- pub struct RetLinesStorage {
260
- inner : ImmLinesStorage ,
261
- /// The timestamp in seconds after which a line should not be rendered anymore.
284
+ pub struct RetainedLinesStorage {
285
+ lines : ImmediateLinesStorage ,
286
+ /// The timestamp after which a line should not be rendered anymore.
262
287
///
263
- /// `timestamp[i]` corresponds to `inner.add_at(i)`
264
- timestamp : Vec < f32 > ,
288
+ /// It is represented as the number of seconds since the game started.
289
+ /// `expire_time[i]` corresponds to the i-th line in `lines` buffer.
290
+ expire_time : Vec < f32 > ,
265
291
/// Index of lines that can be safely overwritten
266
292
expired : Vec < u32 > ,
293
+ /// Whether we have computed expired lines this frame
294
+ expired_marked : bool ,
267
295
}
268
- impl RetLinesStorage {
269
- fn add_line ( & mut self , position : Line < Vec3 > , color : Line < Color > , timestamp : f32 ) {
296
+ impl RetainedLinesStorage {
297
+ fn add_line ( & mut self , position : Line < Vec3 > , color : Line < Color > , time : f32 , duration : f32 ) {
298
+ if !self . expired_marked {
299
+ self . expired_marked = true ;
300
+ self . mark_expired ( time) ;
301
+ }
302
+ let expire_time = time + duration;
270
303
if let Some ( replaceable) = self . expired . pop ( ) {
271
304
let i = replaceable as usize ;
272
- self . inner . add_at ( i, position, color) ;
273
- self . timestamp [ i] = timestamp ;
274
- } else if self . timestamp . len ( ) >= MAX_LINES {
305
+ self . lines . add_at ( i, position, color) ;
306
+ self . expire_time [ i] = expire_time ;
307
+ } else if self . expire_time . len ( ) >= MAX_LINES {
275
308
let i = MAX_LINES - 1 ;
276
- self . inner . add_at ( i, position, color) ;
277
- self . timestamp [ i] = timestamp ;
309
+ self . lines . add_at ( i, position, color) ;
310
+ self . expire_time [ i] = expire_time ;
278
311
} else {
279
- self . inner . push ( position, color) ;
280
- self . timestamp . push ( timestamp ) ;
312
+ self . lines . push ( position, color) ;
313
+ self . expire_time . push ( expire_time ) ;
281
314
}
282
315
}
283
316
317
+ /// Fill the mesh indice buffer
318
+ ///
319
+ /// We only add the indices of points for the non-expired lines.
284
320
fn fill_indices ( & self , time : f32 , buffer : & mut Vec < u16 > , mesh : usize ) {
285
321
buffer. clear ( ) ;
286
- if let Some ( new_content) = self . timestamp . chunks ( MAX_LINES_PER_MESH ) . nth ( mesh) {
322
+ if let Some ( new_content) = self . expire_time . chunks ( MAX_LINES_PER_MESH ) . nth ( mesh) {
287
323
buffer. extend (
288
324
new_content
289
325
. iter ( )
290
326
. enumerate ( )
291
- . filter ( |( _, e ) | * * e >= time)
327
+ . filter ( |( _, expires_at ) | * * expires_at >= time)
292
328
. map ( |( i, _) | i as u16 )
293
329
. flat_map ( |i| [ i * 2 , i * 2 + 1 ] ) ,
294
330
) ;
295
331
}
296
332
}
297
333
298
334
fn mark_expired ( & mut self , time : f32 ) {
335
+ self . expired . clear ( ) ;
299
336
self . expired . extend (
300
- self . timestamp
337
+ self . expire_time
301
338
. iter ( )
302
339
. enumerate ( )
303
- . filter ( |( i, e ) | * * e < time && i % 2 == 0 )
340
+ . filter ( |( i, expires_at ) | * * expires_at < time && i % 2 == 0 )
304
341
. map ( |( i, _) | i as u32 / 2 ) ,
305
342
) ;
306
343
}
307
344
345
+ fn frame_init ( & mut self ) {
346
+ self . expired_marked = false ;
347
+ }
348
+
308
349
fn fill_attributes ( & self , time : f32 , mesh : & mut Mesh , mesh_index : usize ) {
309
350
use VertexAttributeValues :: { Float32x3 , Float32x4 } ;
310
351
if let Some ( Indices :: U16 ( indices) ) = mesh. indices_mut ( ) {
311
352
self . fill_indices ( time, indices, mesh_index) ;
312
353
}
313
354
if let Some ( Float32x3 ( vbuffer) ) = mesh. attribute_mut ( Mesh :: ATTRIBUTE_POSITION ) {
314
- self . inner . fill_vertexes ( vbuffer, mesh_index) ;
355
+ self . lines . fill_vertexes ( vbuffer, mesh_index) ;
315
356
}
316
357
if let Some ( Float32x4 ( cbuffer) ) = mesh. attribute_mut ( Mesh :: ATTRIBUTE_COLOR ) {
317
- self . inner . fill_colors ( cbuffer, mesh_index) ;
358
+ self . lines . fill_colors ( cbuffer, mesh_index) ;
318
359
}
319
360
}
320
361
}
@@ -345,8 +386,8 @@ impl RetLinesStorage {
345
386
/// ```
346
387
#[ derive( SystemParam ) ]
347
388
pub struct DebugLines < ' w , ' s > {
348
- immediate : ResMut < ' w , ImmLinesStorage > ,
349
- retained : ResMut < ' w , RetLinesStorage > ,
389
+ immediate : ResMut < ' w , ImmediateLinesStorage > ,
390
+ retained : ResMut < ' w , RetainedLinesStorage > ,
350
391
time : Res < ' w , Time > ,
351
392
#[ system_param( ignore) ]
352
393
_lifetimes : PhantomData < & ' s ( ) > ,
@@ -401,30 +442,34 @@ impl<'w, 's> DebugLines<'w, 's> {
401
442
if duration == 0.0 {
402
443
self . immediate . add_line ( positions, colors) ;
403
444
} else {
404
- let timestamp = self . time . time_since_startup ( ) . as_secs_f32 ( ) + duration ;
405
- self . retained . add_line ( positions, colors, timestamp ) ;
445
+ let time = self . time . time_since_startup ( ) . as_secs_f32 ( ) ;
446
+ self . retained . add_line ( positions, colors, time , duration ) ;
406
447
}
407
448
}
408
449
409
- fn mark_expired ( & mut self ) {
410
- let time = self . time . time_since_startup ( ) . as_secs_f32 ( ) ;
411
- self . immediate . mark_expired ( ) ;
412
- self . retained . mark_expired ( time) ;
450
+ /// Prepare [`ImmediateLinesStorage`] and [`RetainedLinesStorage`] for next
451
+ /// frame.
452
+ ///
453
+ /// This clears the immediate mod buffers and tells the retained mod
454
+ /// buffers to recompute expired lines list.
455
+ fn frame_init ( & mut self ) {
456
+ self . immediate . frame_init ( ) ;
457
+ self . retained . frame_init ( ) ;
413
458
}
414
459
}
415
460
416
461
struct DebugLinePipeline {
417
462
mesh_pipeline : MeshPipeline ,
418
463
shader : Handle < Shader > ,
419
- always_on_top : bool ,
464
+ always_in_front : bool ,
420
465
}
421
466
impl FromWorld for DebugLinePipeline {
422
467
fn from_world ( render_world : & mut World ) -> Self {
423
468
let dbl_plugin = render_world. get_resource :: < DebugLinesPlugin > ( ) . unwrap ( ) ;
424
469
DebugLinePipeline {
425
470
mesh_pipeline : render_world. get_resource :: < MeshPipeline > ( ) . unwrap ( ) . clone ( ) ,
426
471
shader : DEBUG_LINES_SHADER_HANDLE . typed ( ) ,
427
- always_on_top : dbl_plugin. always_on_top ,
472
+ always_in_front : dbl_plugin. always_in_front ,
428
473
}
429
474
}
430
475
}
@@ -459,7 +504,7 @@ impl SpecializedPipeline for DebugLinePipeline {
459
504
descriptor. fragment . as_mut ( ) . unwrap ( ) . shader = self . shader . clone_weak ( ) ;
460
505
descriptor. primitive . topology = PrimitiveTopology :: LineList ;
461
506
descriptor. primitive . cull_mode = None ;
462
- let depth_rate = if self . always_on_top {
507
+ let depth_rate = if self . always_in_front {
463
508
f32:: INFINITY
464
509
} else {
465
510
1.0
0 commit comments