Skip to content

Commit

Permalink
Merge #224
Browse files Browse the repository at this point in the history
224: Fix rapier debug render r=zicklag a=zicklag

Fixes #132.

Found out that #132 was caused by the fact that the debug lines plugin that rapier uses to manage debug rendering requires that 4 entities be kept alive to contain the debug lines.

The issue is when we try to reset the world by deleting all entities other than the camera, those entities get deleted, too. And we can't filter those entities out because their distinguishing component is private. Also, there isn't a way to re-create those entities after adding the plugin.

This works around the issue by implementing a custom debug lines renderer for Rapier that uses Egui to draw the lines instead.

It's simple and mostly copied from rapier's plugin.

Co-authored-by: Zicklag <zicklag@katharostech.com>
  • Loading branch information
bors[bot] and zicklag authored Aug 12, 2022
2 parents 51116f0 + 152ff63 commit b2cbe33
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 17 deletions.
23 changes: 11 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ fn main() {
.add_plugin(PlatformPlugin)
.add_plugin(LocalizationPlugin)
.add_plugin(LoadingPlugin)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0))
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.add_plugin(InputManagerPlugin::<PlayerAction>::default())
.add_plugin(InputManagerPlugin::<MenuAction>::default())
.add_plugin(AttackPlugin)
Expand All @@ -139,17 +139,16 @@ fn main() {

// Add debug plugins if enabled
if engine_config.debug_tools {
app.add_plugin(RapierDebugRenderPlugin::default())
.insert_resource(DebugRenderContext {
enabled: false,
..default()
})
.add_plugin(InspectableRapierPlugin)
.insert_resource(WorldInspectorParams {
enabled: false,
..default()
})
.add_plugin(WorldInspectorPlugin::new());
app.insert_resource(DebugRenderContext {
enabled: false,
..default()
})
.add_plugin(InspectableRapierPlugin)
.insert_resource(WorldInspectorParams {
enabled: false,
..default()
})
.add_plugin(WorldInspectorPlugin::new());
}

// Register assets and loaders
Expand Down
3 changes: 2 additions & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ impl Plugin for UIPlugin {
);

if ENGINE_CONFIG.debug_tools {
app.add_system(debug_tools::debug_tools_window);
app.add_system(debug_tools::debug_tools_window)
.add_system_to_stage(CoreStage::Last, debug_tools::rapier_debug_render);
}
}
}
Expand Down
130 changes: 126 additions & 4 deletions src/ui/debug_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ use bevy::prelude::*;
use bevy_egui::*;
use bevy_fluent::Localization;
use bevy_inspector_egui::WorldInspectorParams;
use bevy_rapier2d::prelude::DebugRenderContext;
use bevy_rapier2d::{
plugin::RapierContext,
prelude::{ColliderDebugColor, DebugRenderContext},
rapier::{
math::{Point, Real},
prelude::{DebugRenderBackend, DebugRenderObject},
},
};

use crate::localization::LocalizationExt;

Expand All @@ -17,11 +24,20 @@ pub fn debug_tools_window(
) {
let ctx = egui_context.ctx_mut();

// Toggle window visibility
// Toggle debug window visibility
if input.just_pressed(KeyCode::F12) {
*visible = !*visible;
}

// Shortcut to toggle collision shapes without having to use the menu
if input.just_pressed(KeyCode::F10) {
rapier_debug.enabled = !rapier_debug.enabled;
}
// Shortcut to toggle the inspector without having to use the menu
if input.just_pressed(KeyCode::F9) {
inspector.enabled = !inspector.enabled;
}

// Display debug tool window
egui::Window::new(localization.get("debug-tools"))
// ID is needed because title comes from localizaition which can change
Expand All @@ -31,13 +47,119 @@ pub fn debug_tools_window(
// Show collision shapes
ui.checkbox(
&mut rapier_debug.enabled,
localization.get("show-collision-shapes"),
format!("{} ( F10 )", localization.get("show-collision-shapes")),
);

// Show world inspector
ui.checkbox(
&mut inspector.enabled,
localization.get("show-world-inspector"),
format!("{} ( F9 )", localization.get("show-world-inspector")),
);
});
}

/// Renders the rapier debug display
pub fn rapier_debug_render(
rapier_context: Res<RapierContext>,
mut egui_context: ResMut<EguiContext>,
mut rapier_debug: ResMut<DebugRenderContext>,
camera: Query<(&Camera, &GlobalTransform)>,
custom_colors: Query<&ColliderDebugColor>,
) {
if !rapier_debug.enabled {
return;
}
let (camera, camera_transform) = camera.single();

// Create a frameless panel to allow us to render over anywhere on the screen
egui::CentralPanel::default()
.frame(egui::Frame::none())
.show(egui_context.ctx_mut(), |ui| {
let painter = ui.painter();

let mut backend = RapierEguiRenderBackend {
egui_size: ui.available_size(),
camera,
camera_transform,
custom_colors,
context: &rapier_context,
painter,
};

rapier_debug.pipeline.render(
&mut backend,
&rapier_context.bodies,
&rapier_context.colliders,
&rapier_context.impulse_joints,
&rapier_context.multibody_joints,
&rapier_context.narrow_phase,
);
});
}

/// Rapier debug rendering backend that uses Egui to draw the lines
struct RapierEguiRenderBackend<'world, 'state, 'a, 'b, 'c> {
egui_size: egui::Vec2,
custom_colors: Query<'world, 'state, &'a ColliderDebugColor>,
context: &'b RapierContext,
camera: &'c Camera,
camera_transform: &'c GlobalTransform,
painter: &'c egui::Painter,
}

impl<'world, 'state, 'a, 'b, 'c> RapierEguiRenderBackend<'world, 'state, 'a, 'b, 'c> {
/// Helper to grab the objects custom collider color if it exists
fn object_color(&self, object: DebugRenderObject, default: [f32; 4]) -> egui::Color32 {
let color = match object {
DebugRenderObject::Collider(h, ..) => self.context.colliders.get(h).and_then(|co| {
self.custom_colors
.get(Entity::from_bits(co.user_data as u64))
.map(|co| co.0)
.ok()
}),
_ => None,
};

let color = color.map(|co| co.as_hsla_f32()).unwrap_or(default);

egui::Rgba::from_rgba_premultiplied(color[0], color[1], color[2], color[3]).into()
}
}

impl<'world, 'state, 'a, 'b, 'c> DebugRenderBackend
for RapierEguiRenderBackend<'world, 'state, 'a, 'b, 'c>
{
/// Draw a debug line
fn draw_line(
&mut self,
object: DebugRenderObject,
a: Point<Real>,
b: Point<Real>,
color: [f32; 4],
) {
// Convert world coordinates to normalized device coordinates
let a = self
.camera
.world_to_ndc(self.camera_transform, Vec3::new(a[0], a[1], 0.0));
let b = self
.camera
.world_to_ndc(self.camera_transform, Vec3::new(b[0], b[1], 0.0));

if let (Some(a), Some(b)) = (a, b) {
// Invert y and convert to egui vec2
let a = egui::Vec2::new(a.x, -a.y);
let b = egui::Vec2::new(b.x, -b.y);

// Map NDC coordinates to egui points
let half_size = self.egui_size / 2.0;
let a = a * half_size + half_size;
let b = b * half_size + half_size;

// Paint the line
self.painter.line_segment(
[a.to_pos2(), b.to_pos2()],
(1.0, self.object_color(object, color)),
)
}
}
}

0 comments on commit b2cbe33

Please sign in to comment.