Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_pbr: Add DebugLinesPlugin and DebugNormalsPlugin #5734

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions crates/bevy_pbr/src/debug_lines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use bevy_app::{CoreStage, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_ecs::{
prelude::Component,
query::{With, Without},
schedule::ParallelSystemDescriptorCoercion,
system::{Commands, Query, Res, ResMut, Resource},
};
use bevy_math::{Affine3A, Vec3};
use bevy_render::{
prelude::{Color, Mesh},
render_resource::PrimitiveTopology,
};
use bevy_transform::{prelude::GlobalTransform, TransformSystem};
use bevy_utils::default;

use crate::{NotShadowCaster, NotShadowReceiver, PbrBundle, StandardMaterial};

pub struct DebugLinesPlugin;

impl Plugin for DebugLinesPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<DebugLines>()
.add_startup_system(init_debug_lines)
.add_system_to_stage(CoreStage::PreUpdate, reset_debug_lines)
.add_system_to_stage(
CoreStage::PostUpdate,
update_debug_lines_entity.after(draw_debug_normals),
);
}
}

#[derive(Resource)]
pub struct DebugLines {
color: Color,
positions: Vec<[f32; 3]>,
colors: Vec<[f32; 4]>,
mesh_handle: Option<Handle<Mesh>>,
}

impl DebugLines {
pub fn clear(&mut self) {
self.positions.clear();
self.colors.clear();
}

pub fn set_color(&mut self, color: Color) {
self.color = color;
}

pub fn draw_line(&mut self, start: Vec3, end: Vec3) {
self.positions.push(start.to_array());
self.positions.push(end.to_array());
let color = self.color.as_linear_rgba_f32();
self.colors.push(color);
self.colors.push(color);
}

pub fn positions(&self) -> &Vec<[f32; 3]> {
&self.positions
}

pub fn colors(&self) -> &Vec<[f32; 4]> {
&self.colors
}

pub fn update_mesh(&mut self, mesh: &mut Mesh) {
let n_vertices = self.positions.len();
mesh.insert_attribute(
Mesh::ATTRIBUTE_POSITION,
std::mem::take(&mut self.positions),
);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0f32; 3]; n_vertices]);
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, std::mem::take(&mut self.colors));
}
}

impl Default for DebugLines {
fn default() -> Self {
Self {
color: Color::GREEN,
positions: Vec::new(),
colors: Vec::new(),
mesh_handle: None,
}
}
}

#[derive(Component)]
struct DebugLinesMesh;

fn init_debug_lines(
mut commands: Commands,
mut debug_lines: ResMut<DebugLines>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let mesh = meshes.add(Mesh::new(PrimitiveTopology::LineList));
commands
.spawn_bundle(PbrBundle {
mesh: mesh.clone_weak(),
material: materials.add(StandardMaterial {
unlit: true,
..default()
}),
..default()
})
.insert_bundle((NotShadowCaster, NotShadowReceiver, DebugLinesMesh));
debug_lines.mesh_handle = Some(mesh);
}

fn reset_debug_lines(mut debug_lines: ResMut<DebugLines>) {
debug_lines.clear();
}

fn update_debug_lines_entity(
mut debug_lines: ResMut<DebugLines>,
mut meshes: ResMut<Assets<Mesh>>,
) {
let mesh_handle = debug_lines.mesh_handle.as_ref().unwrap();
if let Some(mesh) = meshes.get_mut(mesh_handle) {
debug_lines.update_mesh(mesh);
}
}

pub struct DebugNormalsPlugin;

impl Plugin for DebugNormalsPlugin {
fn build(&self, app: &mut bevy_app::App) {
if app.world.get_resource::<DebugNormalsSettings>().is_none() {
app.init_resource::<DebugNormalsSettings>();
}
Comment on lines +130 to +132
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this check here, init_resource::<_>() will already check this.

app.add_system_to_stage(
CoreStage::PostUpdate,
draw_debug_normals.after(TransformSystem::TransformPropagate),
);
}
}

#[derive(Resource)]
pub struct DebugNormalsSettings {
pub global: bool,
pub color: Color,
pub scale: f32,
Comment on lines +142 to +144
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub global: bool,
pub color: Color,
pub scale: f32,
/// Draw mesh normals on every mesh.
pub global: bool,
/// Color to use for drawing normals.
pub color: Color,
/// How far out the line will extrude to display the normal.
pub scale: f32,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was mainly just confused about the global one until I read the code more.

}

impl Default for DebugNormalsSettings {
fn default() -> Self {
Self {
global: false,
color: Color::GREEN,
scale: 0.01,
}
}
}

#[derive(Component)]
pub struct DebugNormals;

fn draw_debug_normals(
debug_normal_settings: Res<DebugNormalsSettings>,
debug_normals: Query<
(&GlobalTransform, &Handle<Mesh>),
(With<DebugNormals>, Without<DebugLinesMesh>),
>,
no_debug_normals: Query<
(&GlobalTransform, &Handle<Mesh>),
(Without<DebugNormals>, Without<DebugLinesMesh>),
>,
meshes: Res<Assets<Mesh>>,
debug_lines: ResMut<DebugLines>,
) {
let debug_lines = debug_lines.into_inner();
let scale = debug_normal_settings.scale;

debug_lines.set_color(debug_normal_settings.color);

for (transform, mesh) in &debug_normals {
if let Some(mesh) = meshes.get(mesh) {
draw_mesh_normals(&transform.affine(), mesh, scale, debug_lines);
}
}
if debug_normal_settings.global {
for (transform, mesh) in &no_debug_normals {
if let Some(mesh) = meshes.get(mesh) {
draw_mesh_normals(&transform.affine(), mesh, scale, debug_lines);
}
}
}
}
Comment on lines +178 to +190
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (transform, mesh) in &debug_normals {
if let Some(mesh) = meshes.get(mesh) {
draw_mesh_normals(&transform.affine(), mesh, scale, debug_lines);
}
}
if debug_normal_settings.global {
for (transform, mesh) in &no_debug_normals {
if let Some(mesh) = meshes.get(mesh) {
draw_mesh_normals(&transform.affine(), mesh, scale, debug_lines);
}
}
}
}
let mesh_normals_to_draw = if debug_normal_settings.global {
&debug_normals
} else {
&no_debug_normals
};
for (transform, mesh) in &mesh_normals_to_draw {
if let Some(mesh) = meshes.get(mesh) {
draw_mesh_normals(&transform.affine(), mesh, scale, debug_lines);
}
}
}


fn draw_mesh_normals(
transform: &Affine3A,
reference_mesh: &Mesh,
scale: f32,
debug_lines: &mut DebugLines,
) {
let inv_transpose = transform.matrix3.inverse().transpose();
// For each vertex, create a line from the vertex position in the direction of the normal
let ref_positions = reference_mesh
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.unwrap();
let ref_normals = reference_mesh
.attribute(Mesh::ATTRIBUTE_NORMAL)
.unwrap()
.as_float3()
.unwrap();
for (position, normal) in ref_positions.iter().zip(ref_normals.iter()) {
let position = transform.transform_point3(Vec3::from_slice(position));
let normal = inv_transpose * Vec3::from_slice(normal);
debug_lines.draw_line(position, position + scale * normal);
}
}
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod debug_lines;
pub mod wireframe;

mod alpha;
Expand Down