Merge pull request #315 from dimforge/debug-renderer
Add a basic lines-based debug-renderer
This commit is contained in:
@@ -14,12 +14,13 @@ resolver = "2"
|
||||
#parry3d-f64 = { path = "../parry/crates/parry3d-f64" }
|
||||
# nalgebra = { path = "../nalgebra" }
|
||||
|
||||
|
||||
#kiss3d = { git = "https://github.com/sebcrozet/kiss3d" }
|
||||
#nalgebra = { git = "https://github.com/dimforge/nalgebra", branch = "dev" }
|
||||
parry2d = { git = "https://github.com/dimforge/parry", branch = "split-and-qbvh" }
|
||||
parry3d = { git = "https://github.com/dimforge/parry", branch = "split-and-qbvh" }
|
||||
parry2d-f64 = { git = "https://github.com/dimforge/parry", branch = "split-and-qbvh" }
|
||||
parry3d-f64 = { git = "https://github.com/dimforge/parry", branch = "split-and-qbvh" }
|
||||
parry2d = { git = "https://github.com/dimforge/parry", branch = "master" }
|
||||
parry3d = { git = "https://github.com/dimforge/parry", branch = "master" }
|
||||
parry2d-f64 = { git = "https://github.com/dimforge/parry", branch = "master" }
|
||||
parry3d-f64 = { git = "https://github.com/dimforge/parry", branch = "master" }
|
||||
|
||||
[profile.release]
|
||||
#debug = true
|
||||
|
||||
@@ -28,6 +28,7 @@ simd-is-enabled = [ "vec_map" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
serde-serialize = [ "nalgebra/serde-serialize", "parry2d-f64/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde" ]
|
||||
enhanced-determinism = [ "simba/libm_force", "parry2d-f64/enhanced-determinism", "indexmap" ]
|
||||
debug-render = [ ]
|
||||
|
||||
# Feature used for debugging only.
|
||||
debug-disable-legitimate-fe-exceptions = [ ]
|
||||
|
||||
@@ -28,6 +28,7 @@ simd-is-enabled = [ "vec_map" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
serde-serialize = [ "nalgebra/serde-serialize", "parry2d/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde" ]
|
||||
enhanced-determinism = [ "simba/libm_force", "parry2d/enhanced-determinism", "indexmap" ]
|
||||
debug-render = [ ]
|
||||
|
||||
# Feature used for debugging only.
|
||||
debug-disable-legitimate-fe-exceptions = [ ]
|
||||
|
||||
@@ -28,6 +28,7 @@ simd-is-enabled = [ "vec_map" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
serde-serialize = [ "nalgebra/serde-serialize", "parry3d-f64/serde-serialize", "serde", "bit-vec/serde" ]
|
||||
enhanced-determinism = [ "simba/libm_force", "parry3d-f64/enhanced-determinism" ]
|
||||
debug-render = []
|
||||
|
||||
# Feature used for debugging only.
|
||||
debug-disable-legitimate-fe-exceptions = [ ]
|
||||
|
||||
@@ -28,6 +28,7 @@ simd-is-enabled = [ "vec_map" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
serde-serialize = [ "nalgebra/serde-serialize", "parry3d/serde-serialize", "serde", "bit-vec/serde" ]
|
||||
enhanced-determinism = [ "simba/libm_force", "parry3d/enhanced-determinism" ]
|
||||
debug-render = [ ]
|
||||
|
||||
# Feature used for debugging only.
|
||||
debug-disable-legitimate-fe-exceptions = [ ]
|
||||
|
||||
@@ -38,20 +38,21 @@ bincode = "1"
|
||||
Inflector = "0.11"
|
||||
md5 = "0.7"
|
||||
|
||||
bevy_egui = "0.10"
|
||||
bevy_ecs = "0.6"
|
||||
bevy_egui = "0.13"
|
||||
bevy_ecs = "0.7"
|
||||
#bevy_prototype_debug_lines = "0.7"
|
||||
|
||||
# Dependencies for native only.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
|
||||
# Dependencies for WASM only.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render"]}
|
||||
#bevy_webgl2 = "0.5"
|
||||
|
||||
[dependencies.rapier]
|
||||
package = "rapier2d-f64"
|
||||
path = "../rapier2d-f64"
|
||||
version = "0.12.0-alpha.1"
|
||||
features = [ "serde-serialize" ]
|
||||
features = [ "serde-serialize", "debug-render" ]
|
||||
|
||||
@@ -38,20 +38,21 @@ bincode = "1"
|
||||
Inflector = "0.11"
|
||||
md5 = "0.7"
|
||||
|
||||
bevy_egui = "0.10"
|
||||
bevy_ecs = "0.6"
|
||||
bevy_egui = "0.13"
|
||||
bevy_ecs = "0.7"
|
||||
#bevy_prototype_debug_lines = "0.7"
|
||||
|
||||
# Dependencies for native only.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
|
||||
# Dependencies for WASM only.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render"]}
|
||||
#bevy_webgl2 = "0.5"
|
||||
|
||||
[dependencies.rapier]
|
||||
package = "rapier2d"
|
||||
path = "../rapier2d"
|
||||
version = "0.12.0-alpha.1"
|
||||
features = [ "serde-serialize" ]
|
||||
features = [ "serde-serialize", "debug-render" ]
|
||||
|
||||
@@ -36,20 +36,21 @@ md5 = "0.7"
|
||||
Inflector = "0.11"
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
|
||||
bevy_egui = "0.10"
|
||||
bevy_ecs = "0.6"
|
||||
bevy_egui = "0.13"
|
||||
bevy_ecs = "0.7"
|
||||
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
||||
|
||||
# Dependencies for native only.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
|
||||
# Dependencies for WASM only.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render"]}
|
||||
#bevy_webgl2 = "0.5"
|
||||
|
||||
[dependencies.rapier]
|
||||
package = "rapier3d-f64"
|
||||
path = "../rapier3d-f64"
|
||||
version = "0.12.0-alpha.1"
|
||||
features = [ "serde-serialize" ]
|
||||
features = [ "serde-serialize", "debug-render" ]
|
||||
@@ -40,20 +40,21 @@ md5 = "0.7"
|
||||
Inflector = "0.11"
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
|
||||
bevy_egui = "0.10"
|
||||
bevy_ecs = "0.6"
|
||||
bevy_egui = "0.13"
|
||||
bevy_ecs = "0.7"
|
||||
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
||||
|
||||
# Dependencies for native only.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render", "x11"]}
|
||||
|
||||
# Dependencies for WASM only.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bevy = {version = "0.6", default-features = false, features = ["bevy_winit", "render"]}
|
||||
bevy = {version = "0.7", default-features = false, features = ["bevy_winit", "render"]}
|
||||
#bevy_webgl2 = "0.5"
|
||||
|
||||
[dependencies.rapier]
|
||||
package = "rapier3d"
|
||||
path = "../rapier3d"
|
||||
version = "0.12.0-alpha.1"
|
||||
features = [ "serde-serialize" ]
|
||||
features = [ "serde-serialize", "debug-render" ]
|
||||
@@ -578,33 +578,33 @@ fn do_init_world(testbed: &mut Testbed, use_articulations: bool) {
|
||||
let mut impulse_joints = ImpulseJointSet::new();
|
||||
let mut multibody_joints = MultibodyJointSet::new();
|
||||
|
||||
// create_prismatic_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![20.0, 5.0, 0.0],
|
||||
// 4,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_actuated_prismatic_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![25.0, 5.0, 0.0],
|
||||
// 4,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_revolute_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![20.0, 0.0, 0.0],
|
||||
// 3,
|
||||
// use_articulations,
|
||||
// );
|
||||
create_prismatic_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![20.0, 5.0, 0.0],
|
||||
4,
|
||||
use_articulations,
|
||||
);
|
||||
create_actuated_prismatic_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![25.0, 5.0, 0.0],
|
||||
4,
|
||||
use_articulations,
|
||||
);
|
||||
create_revolute_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![20.0, 0.0, 0.0],
|
||||
3,
|
||||
use_articulations,
|
||||
);
|
||||
create_revolute_joints_with_limits(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
@@ -613,57 +613,57 @@ fn do_init_world(testbed: &mut Testbed, use_articulations: bool) {
|
||||
point![34.0, 0.0, 0.0],
|
||||
use_articulations,
|
||||
);
|
||||
// create_fixed_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![0.0, 10.0, 0.0],
|
||||
// 10,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_actuated_revolute_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![20.0, 10.0, 0.0],
|
||||
// 6,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_actuated_spherical_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![13.0, 10.0, 0.0],
|
||||
// 3,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_spherical_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// 15,
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_spherical_joints_with_limits(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![-5.0, 0.0, 0.0],
|
||||
// use_articulations,
|
||||
// );
|
||||
// create_coupled_joints(
|
||||
// &mut bodies,
|
||||
// &mut colliders,
|
||||
// &mut impulse_joints,
|
||||
// &mut multibody_joints,
|
||||
// point![0.0, 20.0, 0.0],
|
||||
// use_articulations,
|
||||
// );
|
||||
create_fixed_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![0.0, 10.0, 0.0],
|
||||
10,
|
||||
use_articulations,
|
||||
);
|
||||
create_actuated_revolute_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![20.0, 10.0, 0.0],
|
||||
6,
|
||||
use_articulations,
|
||||
);
|
||||
create_actuated_spherical_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![13.0, 10.0, 0.0],
|
||||
3,
|
||||
use_articulations,
|
||||
);
|
||||
create_spherical_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
15,
|
||||
use_articulations,
|
||||
);
|
||||
create_spherical_joints_with_limits(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![-5.0, 0.0, 0.0],
|
||||
use_articulations,
|
||||
);
|
||||
create_coupled_joints(
|
||||
&mut bodies,
|
||||
&mut colliders,
|
||||
&mut impulse_joints,
|
||||
&mut multibody_joints,
|
||||
point![0.0, 20.0, 0.0],
|
||||
use_articulations,
|
||||
);
|
||||
|
||||
/*
|
||||
* Set up the testbed.
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::geometry::{ColliderParent, ColliderSet, CollisionEvent, NarrowPhase};
|
||||
use crate::math::Real;
|
||||
use crate::parry::utils::SortedPair;
|
||||
use crate::pipeline::{EventHandler, QueryPipeline, QueryPipelineMode};
|
||||
use crate::prelude::ActiveEvents;
|
||||
use crate::prelude::{ActiveEvents, CollisionEventFlags};
|
||||
use parry::query::{DefaultQueryDispatcher, QueryDispatcher};
|
||||
use parry::utils::hashmap::HashMap;
|
||||
use std::collections::BinaryHeap;
|
||||
@@ -529,8 +529,18 @@ impl CCDSolver {
|
||||
.contains(ActiveEvents::COLLISION_EVENTS)
|
||||
{
|
||||
// Emit one intersection-started and one intersection-stopped event.
|
||||
events.handle_collision_event(CollisionEvent::Started(toi.c1, toi.c2), None);
|
||||
events.handle_collision_event(CollisionEvent::Stopped(toi.c1, toi.c2, false), None);
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Started(toi.c1, toi.c2, CollisionEventFlags::SENSOR),
|
||||
None,
|
||||
);
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(toi.c1, toi.c2, CollisionEventFlags::SENSOR),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,12 +93,12 @@ impl IntegrationParameters {
|
||||
|
||||
/// The ERP coefficient, multiplied by the inverse timestep length.
|
||||
pub fn erp_inv_dt(&self) -> Real {
|
||||
self.erp / self.dt
|
||||
self.erp * self.inv_dt()
|
||||
}
|
||||
|
||||
/// The joint ERP coefficient, multiplied by the inverse timestep length.
|
||||
pub fn joint_erp_inv_dt(&self) -> Real {
|
||||
self.joint_erp / self.dt
|
||||
self.joint_erp * self.inv_dt()
|
||||
}
|
||||
|
||||
/// The CFM factor to be used in the constraints resolution.
|
||||
|
||||
@@ -299,10 +299,20 @@ impl MultibodyJointSet {
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the multibody identified by its `handle`.
|
||||
pub fn get_mut(&mut self, handle: MultibodyJointHandle) -> Option<(&mut Multibody, usize)> {
|
||||
let link = self.rb2mb.get(handle.0)?;
|
||||
let multibody = self.multibodies.get_mut(link.multibody.0)?;
|
||||
Some((multibody, link.id))
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the multibody identified by its `handle`.
|
||||
///
|
||||
/// This method will bypass any modification-detection automatically done by the MultibodyJointSet.
|
||||
pub fn get_mut_internal(
|
||||
&mut self,
|
||||
handle: MultibodyJointHandle,
|
||||
) -> Option<(&mut Multibody, usize)> {
|
||||
// TODO: modification tracking?
|
||||
let link = self.rb2mb.get(handle.0)?;
|
||||
let multibody = self.multibodies.get_mut(link.multibody.0)?;
|
||||
Some((multibody, link.id))
|
||||
|
||||
@@ -305,20 +305,26 @@ impl RigidBody {
|
||||
self.ccd.ccd_active
|
||||
}
|
||||
|
||||
/// Sets the rigid-body's initial mass properties.
|
||||
/// Sets the rigid-body's additional mass properties.
|
||||
///
|
||||
/// If `wake_up` is `true` then the rigid-body will be woken up if it was
|
||||
/// put to sleep because it did not move for a while.
|
||||
#[inline]
|
||||
pub fn set_mass_properties(&mut self, props: MassProperties, wake_up: bool) {
|
||||
if self.mprops.local_mprops != props {
|
||||
if self.is_dynamic() && wake_up {
|
||||
self.wake_up(true);
|
||||
}
|
||||
|
||||
self.mprops.local_mprops = props;
|
||||
self.update_world_mass_properties();
|
||||
pub fn set_additional_mass_properties(&mut self, props: MassProperties, wake_up: bool) {
|
||||
if let Some(add_mprops) = &mut self.mprops.additional_local_mprops {
|
||||
self.mprops.local_mprops += props;
|
||||
self.mprops.local_mprops -= **add_mprops;
|
||||
**add_mprops = props;
|
||||
} else {
|
||||
self.mprops.additional_local_mprops = Some(Box::new(props));
|
||||
self.mprops.local_mprops += props;
|
||||
}
|
||||
|
||||
if self.is_dynamic() && wake_up {
|
||||
self.wake_up(true);
|
||||
}
|
||||
|
||||
self.update_world_mass_properties();
|
||||
}
|
||||
|
||||
/// The handles of colliders attached to this rigid body.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::dynamics::RigidBodyHandle;
|
||||
use crate::geometry::{ColliderHandle, Contact, ContactManifold};
|
||||
use crate::dynamics::{RigidBodyHandle, RigidBodySet};
|
||||
use crate::geometry::{ColliderHandle, ColliderSet, Contact, ContactManifold};
|
||||
use crate::math::{Point, Real, Vector};
|
||||
use crate::pipeline::EventHandler;
|
||||
use crate::prelude::CollisionEventFlags;
|
||||
use parry::query::ContactManifoldsWorkspace;
|
||||
|
||||
use super::CollisionEvent;
|
||||
@@ -69,22 +70,36 @@ impl IntersectionPair {
|
||||
|
||||
pub(crate) fn emit_start_event(
|
||||
&mut self,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
collider1: ColliderHandle,
|
||||
collider2: ColliderHandle,
|
||||
events: &dyn EventHandler,
|
||||
) {
|
||||
self.start_event_emited = true;
|
||||
events.handle_collision_event(CollisionEvent::new(collider1, collider2, true), None);
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Started(collider1, collider2, CollisionEventFlags::SENSOR),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn emit_stop_event(
|
||||
&mut self,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
collider1: ColliderHandle,
|
||||
collider2: ColliderHandle,
|
||||
events: &dyn EventHandler,
|
||||
) {
|
||||
self.start_event_emited = false;
|
||||
events.handle_collision_event(CollisionEvent::new(collider1, collider2, false), None);
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(collider1, collider2, CollisionEventFlags::SENSOR),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,20 +170,34 @@ impl ContactPair {
|
||||
deepest
|
||||
}
|
||||
|
||||
pub(crate) fn emit_start_event(&mut self, events: &dyn EventHandler) {
|
||||
pub(crate) fn emit_start_event(
|
||||
&mut self,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
events: &dyn EventHandler,
|
||||
) {
|
||||
self.start_event_emited = true;
|
||||
|
||||
events.handle_collision_event(
|
||||
CollisionEvent::new(self.collider1, self.collider2, true),
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Started(self.collider1, self.collider2, CollisionEventFlags::empty()),
|
||||
Some(self),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn emit_stop_event(&mut self, events: &dyn EventHandler) {
|
||||
pub(crate) fn emit_stop_event(
|
||||
&mut self,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
events: &dyn EventHandler,
|
||||
) {
|
||||
self.start_event_emited = false;
|
||||
|
||||
events.handle_collision_event(
|
||||
CollisionEvent::new(self.collider1, self.collider2, false),
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(self.collider1, self.collider2, CollisionEventFlags::empty()),
|
||||
Some(self),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,27 +50,29 @@ pub type PointProjection = parry::query::PointProjection;
|
||||
pub type TOI = parry::query::TOI;
|
||||
pub use parry::shape::SharedShape;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags providing more information regarding a collision event.
|
||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||
pub struct CollisionEventFlags: u32 {
|
||||
/// Flag set if at least one of the colliders involved in the
|
||||
/// collision was a sensor when the event was fired.
|
||||
const SENSOR = 0b0001;
|
||||
/// Flag set if a `CollisionEvent::Stopped` was fired because
|
||||
/// at least one of the colliders was removed.
|
||||
const REMOVED = 0b0010;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, Debug)]
|
||||
/// Events occurring when two colliders start or stop being in contact (or intersecting)
|
||||
/// Events occurring when two colliders start or stop colliding
|
||||
pub enum CollisionEvent {
|
||||
/// Event occurring when two colliders start being in contact (or intersecting)
|
||||
Started(ColliderHandle, ColliderHandle),
|
||||
/// Event occurring when two colliders stop being in contact (or intersecting).
|
||||
///
|
||||
/// The boolean is set to `true` of this event originates from at least one of
|
||||
/// the colliders being removed from the `ColliderSet`.
|
||||
Stopped(ColliderHandle, ColliderHandle, bool),
|
||||
/// Event occurring when two colliders start colliding
|
||||
Started(ColliderHandle, ColliderHandle, CollisionEventFlags),
|
||||
/// Event occurring when two colliders stop colliding.
|
||||
Stopped(ColliderHandle, ColliderHandle, CollisionEventFlags),
|
||||
}
|
||||
|
||||
impl CollisionEvent {
|
||||
pub(crate) fn new(h1: ColliderHandle, h2: ColliderHandle, start: bool) -> Self {
|
||||
if start {
|
||||
Self::Started(h1, h2)
|
||||
} else {
|
||||
Self::Stopped(h1, h2, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a `Started` collision event?
|
||||
pub fn started(self) -> bool {
|
||||
matches!(self, CollisionEvent::Started(..))
|
||||
@@ -84,14 +86,32 @@ impl CollisionEvent {
|
||||
/// The handle of the first collider involved in this collision event.
|
||||
pub fn collider1(self) -> ColliderHandle {
|
||||
match self {
|
||||
Self::Started(h, _) | Self::Stopped(h, _, _) => h,
|
||||
Self::Started(h, _, _) | Self::Stopped(h, _, _) => h,
|
||||
}
|
||||
}
|
||||
|
||||
/// The handle of the second collider involved in this collision event.
|
||||
pub fn collider2(self) -> ColliderHandle {
|
||||
match self {
|
||||
Self::Started(_, h) | Self::Stopped(_, h, _) => h,
|
||||
Self::Started(_, h, _) | Self::Stopped(_, h, _) => h,
|
||||
}
|
||||
}
|
||||
|
||||
/// Was at least one of the colliders involved in the collision a sensor?
|
||||
pub fn sensor(self) -> bool {
|
||||
match self {
|
||||
Self::Started(_, _, f) | Self::Stopped(_, _, f) => {
|
||||
f.contains(CollisionEventFlags::SENSOR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Was at least one of the colliders involved in the collision removed?
|
||||
pub fn removed(self) -> bool {
|
||||
match self {
|
||||
Self::Started(_, _, f) | Self::Stopped(_, _, f) => {
|
||||
f.contains(CollisionEventFlags::REMOVED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::pipeline::{
|
||||
ActiveEvents, ActiveHooks, ContactModificationContext, EventHandler, PairFilterContext,
|
||||
PhysicsHooks,
|
||||
};
|
||||
use crate::prelude::CollisionEventFlags;
|
||||
use parry::query::{DefaultQueryDispatcher, PersistentQueryDispatcher};
|
||||
use parry::utils::IsometryOpt;
|
||||
use std::collections::HashMap;
|
||||
@@ -317,14 +318,24 @@ impl NarrowPhase {
|
||||
}
|
||||
|
||||
if pair.start_event_emited {
|
||||
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), Some(pair));
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(a, b, CollisionEventFlags::REMOVED),
|
||||
Some(pair),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there is no island, don’t wake-up bodies, but do send the Stopped collision event.
|
||||
for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
|
||||
if pair.start_event_emited {
|
||||
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), Some(pair));
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(a, b, CollisionEventFlags::REMOVED),
|
||||
Some(pair),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +343,16 @@ impl NarrowPhase {
|
||||
// Generate Stopped collision events for intersections.
|
||||
for (a, b, pair) in self.intersection_graph.interactions_with(contact_graph_id) {
|
||||
if pair.start_event_emited {
|
||||
events.handle_collision_event(CollisionEvent::Stopped(a, b, true), None);
|
||||
events.handle_collision_event(
|
||||
bodies,
|
||||
colliders,
|
||||
CollisionEvent::Stopped(
|
||||
a,
|
||||
b,
|
||||
CollisionEventFlags::REMOVED | CollisionEventFlags::SENSOR,
|
||||
),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +515,13 @@ impl NarrowPhase {
|
||||
if (co1.flags.active_events | co2.flags.active_events)
|
||||
.contains(ActiveEvents::COLLISION_EVENTS)
|
||||
{
|
||||
intersection.emit_stop_event(pair.collider1, pair.collider2, events)
|
||||
intersection.emit_stop_event(
|
||||
bodies,
|
||||
colliders,
|
||||
pair.collider1,
|
||||
pair.collider2,
|
||||
events,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,7 +547,7 @@ impl NarrowPhase {
|
||||
if (co1.flags.active_events | co2.flags.active_events)
|
||||
.contains(ActiveEvents::COLLISION_EVENTS)
|
||||
{
|
||||
ctct.emit_stop_event(events);
|
||||
ctct.emit_stop_event(bodies, colliders, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -724,9 +750,11 @@ impl NarrowPhase {
|
||||
&& had_intersection != edge.weight.intersecting
|
||||
{
|
||||
if edge.weight.intersecting {
|
||||
edge.weight.emit_start_event(handle1, handle2, events);
|
||||
edge.weight
|
||||
.emit_start_event(bodies, colliders, handle1, handle2, events);
|
||||
} else {
|
||||
edge.weight.emit_stop_event(handle1, handle2, events);
|
||||
edge.weight
|
||||
.emit_stop_event(bodies, colliders, handle1, handle2, events);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -928,9 +956,9 @@ impl NarrowPhase {
|
||||
if pair.has_any_active_contact != had_any_active_contact {
|
||||
if active_events.contains(ActiveEvents::COLLISION_EVENTS) {
|
||||
if pair.has_any_active_contact {
|
||||
pair.emit_start_event(events);
|
||||
pair.emit_start_event(bodies, colliders, events);
|
||||
} else {
|
||||
pair.emit_stop_event(events);
|
||||
pair.emit_stop_event(bodies, colliders, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
src/pipeline/debug_render_pipeline/debug_render_backend.rs
Normal file
83
src/pipeline/debug_render_pipeline/debug_render_backend.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::dynamics::{
|
||||
ImpulseJoint, ImpulseJointHandle, Multibody, MultibodyLink, RigidBody, RigidBodyHandle,
|
||||
};
|
||||
use crate::geometry::Collider;
|
||||
use crate::math::{Isometry, Point, Real, Vector};
|
||||
use crate::prelude::{ColliderHandle, MultibodyJointHandle};
|
||||
use na::Scale;
|
||||
|
||||
/// The object currently being rendered by the debug-renderer.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum DebugRenderObject<'a> {
|
||||
/// A rigid-body is being rendered.
|
||||
RigidBody(RigidBodyHandle, &'a RigidBody),
|
||||
/// A collider is being rendered.
|
||||
Collider(ColliderHandle, &'a Collider),
|
||||
/// An impulse-joint is being rendered.
|
||||
ImpulseJoint(ImpulseJointHandle, &'a ImpulseJoint),
|
||||
/// A multibody joint is being rendered.
|
||||
MultibodyJoint(MultibodyJointHandle, &'a Multibody, &'a MultibodyLink),
|
||||
/// Another element is being rendered.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Trait implemented by graphics backends responsible for rendering the physics scene.
|
||||
///
|
||||
/// The only thing that is required from the graphics backend is to be able to render
|
||||
/// a colored line. Note that the color is only a suggestion and is computed from the
|
||||
/// `DebugRenderStyle`. The backend is free to apply its own style, for example based on
|
||||
/// the `object` being rendered.
|
||||
pub trait DebugRenderBackend {
|
||||
/// Draws a colored line.
|
||||
///
|
||||
/// Note that this method can be called multiple time for the same `object`.
|
||||
fn draw_line(
|
||||
&mut self,
|
||||
object: DebugRenderObject,
|
||||
a: Point<Real>,
|
||||
b: Point<Real>,
|
||||
color: [f32; 4],
|
||||
);
|
||||
|
||||
/// Draws a set of line.
|
||||
fn draw_polyline(
|
||||
&mut self,
|
||||
object: DebugRenderObject,
|
||||
vertices: &[Point<Real>],
|
||||
indices: &[[u32; 2]],
|
||||
transform: &Isometry<Real>,
|
||||
scale: &Vector<Real>,
|
||||
color: [f32; 4],
|
||||
) {
|
||||
for idx in indices {
|
||||
let a = transform * (Scale::from(*scale) * vertices[idx[0] as usize]);
|
||||
let b = transform * (Scale::from(*scale) * vertices[idx[1] as usize]);
|
||||
self.draw_line(object, a, b, color);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a chain of line.
|
||||
fn draw_line_strip(
|
||||
&mut self,
|
||||
object: DebugRenderObject,
|
||||
vertices: &[Point<Real>],
|
||||
transform: &Isometry<Real>,
|
||||
scale: &Vector<Real>,
|
||||
color: [f32; 4],
|
||||
closed: bool,
|
||||
) {
|
||||
for vtx in vertices.windows(2) {
|
||||
let a = transform * (Scale::from(*scale) * vtx[0]);
|
||||
let b = transform * (Scale::from(*scale) * vtx[1]);
|
||||
self.draw_line(object, a, b, color);
|
||||
}
|
||||
|
||||
if closed {
|
||||
if vertices.len() > 2 {
|
||||
let a = transform * (Scale::from(*scale) * vertices[0]);
|
||||
let b = transform * (Scale::from(*scale) * vertices.last().unwrap());
|
||||
self.draw_line(object, a, b, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
481
src/pipeline/debug_render_pipeline/debug_render_pipeline.rs
Normal file
481
src/pipeline/debug_render_pipeline/debug_render_pipeline.rs
Normal file
@@ -0,0 +1,481 @@
|
||||
use super::{outlines, DebugRenderBackend};
|
||||
use crate::dynamics::{
|
||||
GenericJoint, ImpulseJointSet, MultibodyJointSet, RigidBodySet, RigidBodyType,
|
||||
};
|
||||
use crate::geometry::{Ball, ColliderSet, Cuboid, Shape, TypedShape};
|
||||
#[cfg(feature = "dim3")]
|
||||
use crate::geometry::{Cone, Cylinder};
|
||||
use crate::math::{Isometry, Point, Real, Vector, DIM};
|
||||
use crate::pipeline::debug_render_pipeline::debug_render_backend::DebugRenderObject;
|
||||
use crate::pipeline::debug_render_pipeline::DebugRenderStyle;
|
||||
use crate::utils::WBasis;
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags indicating what part of the physics engine should be rendered
|
||||
/// by the debug-renderer.
|
||||
pub struct DebugRenderMode: u32 {
|
||||
/// If this flag is set, the collider shapes will be rendered.
|
||||
const COLLIDER_SHAPES = 1 << 0;
|
||||
/// If this flag is set, the local coordinate axes of rigid-bodies will be rendered.
|
||||
const RIGID_BODY_AXES = 1 << 1;
|
||||
/// If this flag is set, the multibody joints will be rendered.
|
||||
const MULTIBODY_JOINTS = 1 << 2;
|
||||
/// If this flag is set, the impulse joints will be rendered.
|
||||
const IMPULSE_JOINTS = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pipeline responsible for rendering the state of the physics engine for debugging purpose.
|
||||
pub struct DebugRenderPipeline {
|
||||
#[cfg(feature = "dim2")]
|
||||
instances: HashMap<TypeId, Vec<Point<Real>>>,
|
||||
#[cfg(feature = "dim3")]
|
||||
instances: HashMap<TypeId, (Vec<Point<Real>>, Vec<[u32; 2]>)>,
|
||||
/// The style used to compute the line colors for each element
|
||||
/// to render.
|
||||
pub style: DebugRenderStyle,
|
||||
/// Flags controlling what part of the physics engine need to
|
||||
/// be rendered.
|
||||
pub mode: DebugRenderMode,
|
||||
}
|
||||
|
||||
impl Default for DebugRenderPipeline {
|
||||
fn default() -> Self {
|
||||
Self::render_all(DebugRenderStyle::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugRenderPipeline {
|
||||
/// Creates a new debug-render pipeline from a given style and flags.
|
||||
pub fn new(style: DebugRenderStyle, mode: DebugRenderMode) -> Self {
|
||||
Self {
|
||||
instances: outlines::instances(style.subdivisions),
|
||||
style,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new debug-render pipeline that renders everything
|
||||
/// it can from the physics state.
|
||||
pub fn render_all(style: DebugRenderStyle) -> Self {
|
||||
Self::new(style, DebugRenderMode::all())
|
||||
}
|
||||
|
||||
/// Render the scene.
|
||||
pub fn render(
|
||||
&mut self,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
impulse_joints: &ImpulseJointSet,
|
||||
multibody_joints: &MultibodyJointSet,
|
||||
) {
|
||||
self.render_rigid_bodies(backend, bodies);
|
||||
self.render_colliders(backend, bodies, colliders);
|
||||
self.render_joints(backend, bodies, impulse_joints, multibody_joints);
|
||||
}
|
||||
|
||||
/// Render only the joints from the scene.
|
||||
pub fn render_joints(
|
||||
&mut self,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
bodies: &RigidBodySet,
|
||||
impulse_joints: &ImpulseJointSet,
|
||||
multibody_joints: &MultibodyJointSet,
|
||||
) {
|
||||
let mut render_joint = |body1,
|
||||
body2,
|
||||
data: &GenericJoint,
|
||||
mut anchor_color: [f32; 4],
|
||||
mut separation_color: [f32; 4],
|
||||
object| {
|
||||
if let (Some(rb1), Some(rb2)) = (bodies.get(body1), bodies.get(body2)) {
|
||||
let coeff = if (rb1.is_fixed() || rb1.is_sleeping())
|
||||
&& (rb2.is_fixed() || rb2.is_sleeping())
|
||||
{
|
||||
self.style.sleep_color_multiplier
|
||||
} else {
|
||||
[1.0; 4]
|
||||
};
|
||||
|
||||
let frame1 = rb1.position() * data.local_frame1;
|
||||
let frame2 = rb2.position() * data.local_frame2;
|
||||
|
||||
let a = *rb1.translation();
|
||||
let b = frame1.translation.vector;
|
||||
let c = frame2.translation.vector;
|
||||
let d = *rb2.translation();
|
||||
|
||||
for k in 0..4 {
|
||||
anchor_color[k] *= coeff[k];
|
||||
separation_color[k] *= coeff[k];
|
||||
}
|
||||
|
||||
backend.draw_line(object, a.into(), b.into(), anchor_color);
|
||||
backend.draw_line(object, b.into(), c.into(), separation_color);
|
||||
backend.draw_line(object, c.into(), d.into(), anchor_color);
|
||||
}
|
||||
};
|
||||
|
||||
if self.mode.contains(DebugRenderMode::IMPULSE_JOINTS) {
|
||||
for (handle, joint) in impulse_joints.iter() {
|
||||
let anc_color = self.style.impulse_joint_anchor_color;
|
||||
let sep_color = self.style.impulse_joint_separation_color;
|
||||
let object = DebugRenderObject::ImpulseJoint(handle, joint);
|
||||
render_joint(
|
||||
joint.body1,
|
||||
joint.body2,
|
||||
&joint.data,
|
||||
anc_color,
|
||||
sep_color,
|
||||
object,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.mode.contains(DebugRenderMode::MULTIBODY_JOINTS) {
|
||||
for (handle, multibody, link) in multibody_joints.iter() {
|
||||
let anc_color = self.style.multibody_joint_anchor_color;
|
||||
let sep_color = self.style.multibody_joint_separation_color;
|
||||
let parent = multibody.link(link.parent_id().unwrap()).unwrap();
|
||||
let object = DebugRenderObject::MultibodyJoint(handle, multibody, link);
|
||||
render_joint(
|
||||
parent.rigid_body_handle(),
|
||||
link.rigid_body_handle(),
|
||||
&link.joint.data,
|
||||
anc_color,
|
||||
sep_color,
|
||||
object,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render only the rigid-bodies from the scene.
|
||||
pub fn render_rigid_bodies(
|
||||
&mut self,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
bodies: &RigidBodySet,
|
||||
) {
|
||||
for (handle, rb) in bodies.iter() {
|
||||
let object = DebugRenderObject::RigidBody(handle, rb);
|
||||
|
||||
if self.style.rigid_body_axes_length != 0.0
|
||||
&& self.mode.contains(DebugRenderMode::RIGID_BODY_AXES)
|
||||
{
|
||||
let basis = rb.rotation().to_rotation_matrix().into_inner();
|
||||
let coeff = if rb.is_sleeping() {
|
||||
self.style.sleep_color_multiplier
|
||||
} else {
|
||||
[1.0; 4]
|
||||
};
|
||||
let colors = [
|
||||
[0.0 * coeff[0], 1.0 * coeff[1], 0.25 * coeff[2], coeff[3]],
|
||||
[120.0 * coeff[0], 1.0 * coeff[1], 0.1 * coeff[2], coeff[3]],
|
||||
[240.0 * coeff[0], 1.0 * coeff[1], 0.2 * coeff[2], coeff[3]],
|
||||
];
|
||||
let com = rb.mprops.world_com;
|
||||
|
||||
for k in 0..DIM {
|
||||
let axis = basis.column(k) * self.style.rigid_body_axes_length;
|
||||
backend.draw_line(object, com, com + axis, colors[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render only the colliders from the scene.
|
||||
pub fn render_colliders(
|
||||
&mut self,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
) {
|
||||
if self.mode.contains(DebugRenderMode::COLLIDER_SHAPES) {
|
||||
for (h, co) in colliders.iter() {
|
||||
let object = DebugRenderObject::Collider(h, co);
|
||||
let color = if let Some(parent) = co.parent().and_then(|p| bodies.get(p)) {
|
||||
let coeff = if parent.is_sleeping() {
|
||||
self.style.sleep_color_multiplier
|
||||
} else {
|
||||
[1.0; 4]
|
||||
};
|
||||
let c = match parent.body_type {
|
||||
RigidBodyType::Fixed => self.style.collider_fixed_color,
|
||||
RigidBodyType::Dynamic => self.style.collider_dynamic_color,
|
||||
RigidBodyType::KinematicPositionBased
|
||||
| RigidBodyType::KinematicVelocityBased => {
|
||||
self.style.collider_kinematic_color
|
||||
}
|
||||
};
|
||||
|
||||
[
|
||||
c[0] * coeff[0],
|
||||
c[1] * coeff[1],
|
||||
c[2] * coeff[2],
|
||||
c[3] * coeff[3],
|
||||
]
|
||||
} else {
|
||||
self.style.collider_parentless_color
|
||||
};
|
||||
|
||||
self.render_shape(object, backend, co.shape(), co.position(), color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dim2")]
|
||||
fn render_shape(
|
||||
&mut self,
|
||||
object: DebugRenderObject,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
shape: &dyn Shape,
|
||||
pos: &Isometry<Real>,
|
||||
color: [f32; 4],
|
||||
) {
|
||||
match shape.as_typed_shape() {
|
||||
TypedShape::Ball(s) => {
|
||||
let vtx = &self.instances[&TypeId::of::<Ball>()];
|
||||
backend.draw_line_strip(
|
||||
object,
|
||||
vtx,
|
||||
pos,
|
||||
&Vector::repeat(s.radius * 2.0),
|
||||
color,
|
||||
true,
|
||||
)
|
||||
}
|
||||
TypedShape::Cuboid(s) => {
|
||||
let vtx = &self.instances[&TypeId::of::<Cuboid>()];
|
||||
backend.draw_line_strip(object, vtx, pos, &(s.half_extents * 2.0), color, true)
|
||||
}
|
||||
TypedShape::Capsule(s) => {
|
||||
let vtx = s.to_polyline(self.style.subdivisions);
|
||||
backend.draw_line_strip(object, &vtx, pos, &Vector::repeat(1.0), color, true)
|
||||
}
|
||||
TypedShape::Segment(s) => backend.draw_line_strip(
|
||||
object,
|
||||
&[s.a, s.b],
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
false,
|
||||
),
|
||||
TypedShape::Triangle(s) => backend.draw_line_strip(
|
||||
object,
|
||||
&[s.a, s.b, s.c],
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
true,
|
||||
),
|
||||
TypedShape::TriMesh(s) => {
|
||||
for tri in s.triangles() {
|
||||
self.render_shape(object, backend, &tri, pos, color)
|
||||
}
|
||||
}
|
||||
TypedShape::Polyline(s) => backend.draw_polyline(
|
||||
object,
|
||||
s.vertices(),
|
||||
s.indices(),
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
),
|
||||
TypedShape::HalfSpace(s) => {
|
||||
let basis = s.normal.orthonormal_basis()[0];
|
||||
let a = Point::from(basis) * 10_000.0;
|
||||
let b = Point::from(basis) * -10_000.0;
|
||||
backend.draw_line_strip(object, &[a, b], pos, &Vector::repeat(1.0), color, false)
|
||||
}
|
||||
TypedShape::HeightField(s) => {
|
||||
for seg in s.segments() {
|
||||
self.render_shape(object, backend, &seg, pos, color)
|
||||
}
|
||||
}
|
||||
TypedShape::Compound(s) => {
|
||||
for (sub_pos, shape) in s.shapes() {
|
||||
self.render_shape(object, backend, &**shape, &(pos * sub_pos), color)
|
||||
}
|
||||
}
|
||||
TypedShape::ConvexPolygon(s) => {
|
||||
backend.draw_line_strip(object, s.points(), pos, &Vector::repeat(1.0), color, true)
|
||||
}
|
||||
/*
|
||||
* Round shapes.
|
||||
*/
|
||||
TypedShape::RoundCuboid(s) => {
|
||||
let vtx = s.to_polyline(self.style.border_subdivisions);
|
||||
backend.draw_line_strip(object, &vtx, pos, &Vector::repeat(1.0), color, true)
|
||||
}
|
||||
TypedShape::RoundTriangle(s) => {
|
||||
// TODO: take roundness into account.
|
||||
self.render_shape(object, backend, &s.inner_shape, pos, color)
|
||||
}
|
||||
// TypedShape::RoundTriMesh(s) => self.render_shape(backend, &s.inner_shape, pos, color),
|
||||
// TypedShape::RoundHeightField(s) => {
|
||||
// self.render_shape(backend, &s.inner_shape, pos, color)
|
||||
// }
|
||||
TypedShape::RoundConvexPolygon(s) => {
|
||||
let vtx = s.to_polyline(self.style.border_subdivisions);
|
||||
backend.draw_line_strip(object, &vtx, pos, &Vector::repeat(1.0), color, true)
|
||||
}
|
||||
TypedShape::Custom(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dim3")]
|
||||
fn render_shape(
|
||||
&mut self,
|
||||
object: DebugRenderObject,
|
||||
backend: &mut impl DebugRenderBackend,
|
||||
shape: &dyn Shape,
|
||||
pos: &Isometry<Real>,
|
||||
color: [f32; 4],
|
||||
) {
|
||||
match shape.as_typed_shape() {
|
||||
TypedShape::Ball(s) => {
|
||||
let (vtx, idx) = &self.instances[&TypeId::of::<Ball>()];
|
||||
backend.draw_polyline(
|
||||
object,
|
||||
vtx,
|
||||
idx,
|
||||
pos,
|
||||
&Vector::repeat(s.radius * 2.0),
|
||||
color,
|
||||
)
|
||||
}
|
||||
TypedShape::Cuboid(s) => {
|
||||
let (vtx, idx) = &self.instances[&TypeId::of::<Cuboid>()];
|
||||
backend.draw_polyline(object, vtx, idx, pos, &(s.half_extents * 2.0), color)
|
||||
}
|
||||
TypedShape::Capsule(s) => {
|
||||
let (vtx, idx) = s.to_outline(self.style.subdivisions);
|
||||
backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color)
|
||||
}
|
||||
TypedShape::Segment(s) => backend.draw_polyline(
|
||||
object,
|
||||
&[s.a, s.b],
|
||||
&[[0, 1]],
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
),
|
||||
TypedShape::Triangle(s) => backend.draw_line_strip(
|
||||
object,
|
||||
&[s.a, s.b, s.c],
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
true,
|
||||
),
|
||||
TypedShape::TriMesh(s) => {
|
||||
for tri in s.triangles() {
|
||||
self.render_shape(object, backend, &tri, pos, color)
|
||||
}
|
||||
}
|
||||
TypedShape::Polyline(s) => backend.draw_polyline(
|
||||
object,
|
||||
s.vertices(),
|
||||
s.indices(),
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
),
|
||||
TypedShape::HalfSpace(s) => {
|
||||
let basis = s.normal.orthonormal_basis();
|
||||
let a = Point::from(basis[0]) * 10_000.0;
|
||||
let b = Point::from(basis[0]) * -10_000.0;
|
||||
let c = Point::from(basis[1]) * 10_000.0;
|
||||
let d = Point::from(basis[1]) * -10_000.0;
|
||||
backend.draw_polyline(
|
||||
object,
|
||||
&[a, b, c, d],
|
||||
&[[0, 1], [2, 3]],
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
)
|
||||
}
|
||||
TypedShape::HeightField(s) => {
|
||||
for tri in s.triangles() {
|
||||
self.render_shape(object, backend, &tri, pos, color)
|
||||
}
|
||||
}
|
||||
TypedShape::Compound(s) => {
|
||||
for (sub_pos, shape) in s.shapes() {
|
||||
self.render_shape(object, backend, &**shape, &(pos * sub_pos), color)
|
||||
}
|
||||
}
|
||||
TypedShape::ConvexPolyhedron(s) => {
|
||||
let indices: Vec<_> = s
|
||||
.edges()
|
||||
.iter()
|
||||
.map(|e| [e.vertices.x, e.vertices.y])
|
||||
.collect();
|
||||
backend.draw_polyline(
|
||||
object,
|
||||
s.points(),
|
||||
&indices,
|
||||
pos,
|
||||
&Vector::repeat(1.0),
|
||||
color,
|
||||
)
|
||||
}
|
||||
TypedShape::Cylinder(s) => {
|
||||
let (vtx, idx) = &self.instances[&TypeId::of::<Cylinder>()];
|
||||
backend.draw_polyline(
|
||||
object,
|
||||
vtx,
|
||||
idx,
|
||||
pos,
|
||||
&(Vector::new(s.radius, s.half_height, s.radius) * 2.0),
|
||||
color,
|
||||
)
|
||||
}
|
||||
TypedShape::Cone(s) => {
|
||||
let (vtx, idx) = &self.instances[&TypeId::of::<Cone>()];
|
||||
backend.draw_polyline(
|
||||
object,
|
||||
vtx,
|
||||
idx,
|
||||
pos,
|
||||
&(Vector::new(s.radius, s.half_height, s.radius) * 2.0),
|
||||
color,
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Round shapes.
|
||||
*/
|
||||
TypedShape::RoundCuboid(s) => {
|
||||
let (vtx, idx) = s.to_outline(self.style.border_subdivisions);
|
||||
backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color)
|
||||
}
|
||||
TypedShape::RoundTriangle(s) => {
|
||||
// TODO: take roundness into account.
|
||||
self.render_shape(object, backend, &s.inner_shape, pos, color)
|
||||
}
|
||||
// TypedShape::RoundTriMesh(s) => self.render_shape(object, backend, &s.inner_shape, pos, color),
|
||||
// TypedShape::RoundHeightField(s) => {
|
||||
// self.render_shape(object, backend, &s.inner_shape, pos, color)
|
||||
// }
|
||||
TypedShape::RoundCylinder(s) => {
|
||||
let (vtx, idx) =
|
||||
s.to_outline(self.style.subdivisions, self.style.border_subdivisions);
|
||||
backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color)
|
||||
}
|
||||
TypedShape::RoundCone(s) => {
|
||||
let (vtx, idx) =
|
||||
s.to_outline(self.style.subdivisions, self.style.border_subdivisions);
|
||||
backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color)
|
||||
}
|
||||
TypedShape::RoundConvexPolyhedron(s) => {
|
||||
let (vtx, idx) = s.to_outline(self.style.border_subdivisions);
|
||||
backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color)
|
||||
}
|
||||
TypedShape::Custom(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/pipeline/debug_render_pipeline/debug_render_style.rs
Normal file
60
src/pipeline/debug_render_pipeline/debug_render_style.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::math::Real;
|
||||
|
||||
/// A color for debug-rendering.
|
||||
///
|
||||
/// The default colors are provided in HSLA (Hue Saturation Lightness Alpha) format.
|
||||
pub type DebugColor = [f32; 4];
|
||||
|
||||
/// Style used for computing colors when rendering the scene.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct DebugRenderStyle {
|
||||
/// The number of subdivision used to approximate the curved
|
||||
/// parts of a shape with smooth faces.
|
||||
pub subdivisions: u32,
|
||||
/// The number of subdivision used to approimate the curved
|
||||
/// borders of round shapes.
|
||||
pub border_subdivisions: u32,
|
||||
/// The color of colliders attached to dynamic rigid-bodies.
|
||||
pub collider_dynamic_color: DebugColor,
|
||||
/// The color of colliders attached to fixed rigid-bodies.
|
||||
pub collider_fixed_color: DebugColor,
|
||||
/// The color of colliders attached to kinematic rigid-bodies.
|
||||
pub collider_kinematic_color: DebugColor,
|
||||
/// The color of colliders not attached to any rigid-body.
|
||||
pub collider_parentless_color: DebugColor,
|
||||
/// The color of the line between a rigid-body’s center-of-mass and the
|
||||
/// anchors of its attached impulse joints.
|
||||
pub impulse_joint_anchor_color: DebugColor,
|
||||
/// The color of the line between the two anchors of an impulse joint.
|
||||
pub impulse_joint_separation_color: DebugColor,
|
||||
/// The color of the line between a rigid-body’s center-of-mass and the
|
||||
/// anchors of its attached multibody joints.
|
||||
pub multibody_joint_anchor_color: DebugColor,
|
||||
/// The color of the line between the two anchors of a multibody joint.
|
||||
pub multibody_joint_separation_color: DebugColor,
|
||||
/// If a rigid-body is sleeping, its attached entities will have their colors
|
||||
/// multiplied by this array. (For a joint, both attached rigid-bodies must be sleeping
|
||||
/// or non-dynamic for this multiplier to be applied).
|
||||
pub sleep_color_multiplier: [f32; 4],
|
||||
/// The length of the local coordinate axes rendered for a rigid-body.
|
||||
pub rigid_body_axes_length: Real,
|
||||
}
|
||||
|
||||
impl Default for DebugRenderStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
subdivisions: 20,
|
||||
border_subdivisions: 5,
|
||||
collider_dynamic_color: [340.0, 1.0, 0.3, 1.0],
|
||||
collider_kinematic_color: [20.0, 1.0, 0.3, 1.0],
|
||||
collider_fixed_color: [30.0, 1.0, 0.4, 1.0],
|
||||
collider_parentless_color: [30.0, 1.0, 0.4, 1.0],
|
||||
impulse_joint_anchor_color: [240.0, 0.5, 0.4, 1.0],
|
||||
impulse_joint_separation_color: [0.0, 0.5, 0.4, 1.0],
|
||||
multibody_joint_anchor_color: [300.0, 1.0, 0.4, 1.0],
|
||||
multibody_joint_separation_color: [0.0, 1.0, 0.4, 1.0],
|
||||
sleep_color_multiplier: [1.0, 1.0, 0.2, 1.0],
|
||||
rigid_body_axes_length: 0.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/pipeline/debug_render_pipeline/mod.rs
Normal file
8
src/pipeline/debug_render_pipeline/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub use self::debug_render_backend::{DebugRenderBackend, DebugRenderObject};
|
||||
pub use self::debug_render_pipeline::{DebugRenderMode, DebugRenderPipeline};
|
||||
pub use self::debug_render_style::{DebugColor, DebugRenderStyle};
|
||||
|
||||
mod debug_render_backend;
|
||||
mod debug_render_pipeline;
|
||||
mod debug_render_style;
|
||||
pub(self) mod outlines;
|
||||
36
src/pipeline/debug_render_pipeline/outlines.rs
Normal file
36
src/pipeline/debug_render_pipeline/outlines.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::geometry::{Ball, Cuboid};
|
||||
#[cfg(feature = "dim3")]
|
||||
use crate::geometry::{Cone, Cylinder};
|
||||
use crate::math::{Point, Real, Vector};
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "dim2")]
|
||||
pub fn instances(nsubdivs: u32) -> HashMap<TypeId, Vec<Point<Real>>> {
|
||||
let mut result = HashMap::new();
|
||||
result.insert(
|
||||
TypeId::of::<Cuboid>(),
|
||||
Cuboid::new(Vector::repeat(0.5)).to_polyline(),
|
||||
);
|
||||
result.insert(TypeId::of::<Ball>(), Ball::new(0.5).to_polyline(nsubdivs));
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(feature = "dim3")]
|
||||
pub fn instances(nsubdivs: u32) -> HashMap<TypeId, (Vec<Point<Real>>, Vec<[u32; 2]>)> {
|
||||
let mut result = HashMap::new();
|
||||
result.insert(
|
||||
TypeId::of::<Cuboid>(),
|
||||
Cuboid::new(Vector::repeat(0.5)).to_outline(),
|
||||
);
|
||||
result.insert(TypeId::of::<Ball>(), Ball::new(0.5).to_outline(nsubdivs));
|
||||
result.insert(
|
||||
TypeId::of::<Cone>(),
|
||||
Cone::new(0.5, 0.5).to_outline(nsubdivs),
|
||||
);
|
||||
result.insert(
|
||||
TypeId::of::<Cylinder>(),
|
||||
Cylinder::new(0.5, 0.5).to_outline(nsubdivs),
|
||||
);
|
||||
result
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::geometry::{CollisionEvent, ContactPair};
|
||||
use crate::dynamics::RigidBodySet;
|
||||
use crate::geometry::{ColliderSet, CollisionEvent, ContactPair};
|
||||
use crossbeam::channel::Sender;
|
||||
|
||||
bitflags::bitflags! {
|
||||
@@ -27,14 +28,29 @@ pub trait EventHandler: Send + Sync {
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `event` - The collision event.
|
||||
/// * `bodies` - The set of rigid-bodies.
|
||||
/// * `colliders` - The set of colliders.
|
||||
/// * `contact_pair` - The current state of contacts between the two colliders. This is set ot `None`
|
||||
/// if at least one of the collider is a sensor (in which case no contact information
|
||||
/// is ever computed).
|
||||
fn handle_collision_event(&self, event: CollisionEvent, contact_pair: Option<&ContactPair>);
|
||||
fn handle_collision_event(
|
||||
&self,
|
||||
bodies: &RigidBodySet,
|
||||
colliders: &ColliderSet,
|
||||
event: CollisionEvent,
|
||||
contact_pair: Option<&ContactPair>,
|
||||
);
|
||||
}
|
||||
|
||||
impl EventHandler for () {
|
||||
fn handle_collision_event(&self, _event: CollisionEvent, _contact_pair: Option<&ContactPair>) {}
|
||||
fn handle_collision_event(
|
||||
&self,
|
||||
_bodies: &RigidBodySet,
|
||||
_colliders: &ColliderSet,
|
||||
_event: CollisionEvent,
|
||||
_contact_pair: Option<&ContactPair>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A collision event handler that collects events into a crossbeam channel.
|
||||
@@ -50,7 +66,13 @@ impl ChannelEventCollector {
|
||||
}
|
||||
|
||||
impl EventHandler for ChannelEventCollector {
|
||||
fn handle_collision_event(&self, event: CollisionEvent, _: Option<&ContactPair>) {
|
||||
fn handle_collision_event(
|
||||
&self,
|
||||
_bodies: &RigidBodySet,
|
||||
_colliders: &ColliderSet,
|
||||
event: CollisionEvent,
|
||||
_: Option<&ContactPair>,
|
||||
) {
|
||||
let _ = self.event_sender.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,18 @@ pub use physics_hooks::{ActiveHooks, ContactModificationContext, PairFilterConte
|
||||
pub use physics_pipeline::PhysicsPipeline;
|
||||
pub use query_pipeline::{QueryPipeline, QueryPipelineMode};
|
||||
|
||||
#[cfg(feature = "debug-render")]
|
||||
pub use self::debug_render_pipeline::{
|
||||
DebugColor, DebugRenderBackend, DebugRenderMode, DebugRenderObject, DebugRenderPipeline,
|
||||
DebugRenderStyle,
|
||||
};
|
||||
|
||||
mod collision_pipeline;
|
||||
mod event_handler;
|
||||
mod physics_hooks;
|
||||
mod physics_pipeline;
|
||||
mod query_pipeline;
|
||||
mod user_changes;
|
||||
|
||||
#[cfg(feature = "debug-render")]
|
||||
mod debug_render_pipeline;
|
||||
|
||||
@@ -541,6 +541,17 @@ impl PhysicsPipeline {
|
||||
self.clear_modified_colliders(colliders, &mut modified_colliders);
|
||||
}
|
||||
|
||||
// Finally, make sure we update the world mass-properties of the rigid-bodies
|
||||
// that moved. Otherwise, users may end up applying forces wrt. an outdated
|
||||
// center of mass.
|
||||
// TODO: avoid updating the world mass properties twice (here, and
|
||||
// at the beginning of the next timestep) for bodies that were
|
||||
// not modified by the user in the mean time.
|
||||
for handle in islands.active_dynamic_bodies() {
|
||||
let rb = bodies.index_mut_internal(*handle);
|
||||
rb.mprops.update_world_mass_properties(&rb.pos.position);
|
||||
}
|
||||
|
||||
self.counters.step_completed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ impl OrbitCameraPlugin {
|
||||
}
|
||||
impl Plugin for OrbitCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_system(Self::mouse_motion_system.system())
|
||||
.add_system(Self::zoom_system.system())
|
||||
.add_system(Self::update_transform_system.system());
|
||||
app.add_system(Self::mouse_motion_system)
|
||||
.add_system(Self::zoom_system)
|
||||
.add_system(Self::update_transform_system);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ impl OrbitCameraPlugin {
|
||||
}
|
||||
impl Plugin for OrbitCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_system(Self::mouse_motion_system.system())
|
||||
.add_system(Self::zoom_system.system())
|
||||
.add_system(Self::update_transform_system.system());
|
||||
app.add_system(Self::mouse_motion_system)
|
||||
.add_system(Self::zoom_system)
|
||||
.add_system(Self::update_transform_system);
|
||||
}
|
||||
}
|
||||
|
||||
72
src_testbed/debug_render.rs
Normal file
72
src_testbed/debug_render.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use crate::harness::Harness;
|
||||
use crate::lines::DebugLines;
|
||||
use bevy::prelude::*;
|
||||
use rapier::math::{Point, Real};
|
||||
use rapier::pipeline::{
|
||||
DebugRenderBackend, DebugRenderMode, DebugRenderObject, DebugRenderPipeline,
|
||||
};
|
||||
|
||||
pub struct RapierDebugRenderPlugin {
|
||||
depth_test: bool,
|
||||
}
|
||||
|
||||
impl Default for RapierDebugRenderPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
depth_test: cfg!(feature = "dim3"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for RapierDebugRenderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugin(crate::lines::DebugLinesPlugin::with_depth_test(
|
||||
self.depth_test,
|
||||
))
|
||||
.insert_resource(DebugRenderPipeline::new(
|
||||
Default::default(),
|
||||
!DebugRenderMode::RIGID_BODY_AXES,
|
||||
))
|
||||
.add_system_to_stage(CoreStage::Update, debug_render_scene);
|
||||
}
|
||||
}
|
||||
|
||||
struct BevyLinesRenderBackend<'a> {
|
||||
lines: &'a mut DebugLines,
|
||||
}
|
||||
|
||||
impl<'a> DebugRenderBackend for BevyLinesRenderBackend<'a> {
|
||||
#[cfg(feature = "dim2")]
|
||||
fn draw_line(&mut self, _: DebugRenderObject, a: Point<Real>, b: Point<Real>, color: [f32; 4]) {
|
||||
self.lines.line_colored(
|
||||
[a.x as f32, a.y as f32, 1.0e-8 as f32].into(),
|
||||
[b.x as f32, b.y as f32, 1.0e-8 as f32].into(),
|
||||
0.0,
|
||||
Color::hsla(color[0], color[1], color[2], color[3]),
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
fn draw_line(&mut self, _: DebugRenderObject, a: Point<Real>, b: Point<Real>, color: [f32; 4]) {
|
||||
self.lines.line_colored(
|
||||
[a.x as f32, a.y as f32, a.z as f32].into(),
|
||||
[b.x as f32, b.y as f32, b.z as f32].into(),
|
||||
0.0,
|
||||
Color::hsla(color[0], color[1], color[2], color[3]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_render_scene(
|
||||
mut pipeline: ResMut<DebugRenderPipeline>,
|
||||
harness: NonSend<Harness>,
|
||||
mut lines: ResMut<DebugLines>,
|
||||
) {
|
||||
let mut backend = BevyLinesRenderBackend { lines: &mut *lines };
|
||||
pipeline.render(
|
||||
&mut backend,
|
||||
&harness.physics.bodies,
|
||||
&harness.physics.colliders,
|
||||
&harness.physics.impulse_joints,
|
||||
&harness.physics.multibody_joints,
|
||||
);
|
||||
}
|
||||
@@ -19,8 +19,10 @@ mod box2d_backend;
|
||||
mod camera2d;
|
||||
#[cfg(feature = "dim3")]
|
||||
mod camera3d;
|
||||
mod debug_render;
|
||||
mod graphics;
|
||||
pub mod harness;
|
||||
mod lines;
|
||||
pub mod objects;
|
||||
pub mod physics;
|
||||
#[cfg(all(feature = "dim3", feature = "other-backends"))]
|
||||
|
||||
49
src_testbed/lines/debuglines.wgsl
Normal file
49
src_testbed/lines/debuglines.wgsl
Normal file
@@ -0,0 +1,49 @@
|
||||
// This should work, but it's bugged right now so we have to use 2 shaders: https://github.com/bevyengine/bevy/issues/4011
|
||||
#ifdef LINES_3D
|
||||
#import bevy_pbr::mesh_view_bind_group
|
||||
//#import bevy_pbr::mesh_struct
|
||||
#else
|
||||
//#import bevy_sprite::mesh2d_view_bind_group
|
||||
#endif
|
||||
|
||||
struct Vertex {
|
||||
[[location(0)]] pos: vec3<f32>;
|
||||
[[location(1)]] color: u32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] color: vec4<f32>;
|
||||
};
|
||||
|
||||
struct FragmentOutput {
|
||||
[[builtin(frag_depth)]] depth: f32;
|
||||
[[location(0)]] color: vec4<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = view.view_proj * vec4<f32>(vertex.pos, 1.0);
|
||||
//out.color = vertex.color;
|
||||
// https://github.com/bevyengine/bevy/blob/328c26d02c50de0bc77f0d24a376f43ba89517b1/examples/2d/mesh2d_manual.rs#L234
|
||||
out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(8u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment(in: VertexOutput) -> FragmentOutput {
|
||||
var out: FragmentOutput;
|
||||
|
||||
// This should be #ifdef DEPTH_TEST_ENABLED && LINES_3D, but the
|
||||
// preprocessor doesn't support that yet.
|
||||
// Luckily, DEPTH_TEST_ENABLED isn't set in 2d anyway.
|
||||
#ifdef DEPTH_TEST_ENABLED
|
||||
out.depth = in.clip_position.z;
|
||||
#else
|
||||
out.depth = 1.0;
|
||||
#endif
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
||||
31
src_testbed/lines/debuglines2d.wgsl
Normal file
31
src_testbed/lines/debuglines2d.wgsl
Normal file
@@ -0,0 +1,31 @@
|
||||
#import bevy_sprite::mesh2d_view_bind_group
|
||||
[[group(0), binding(0)]]
|
||||
var<uniform> view: View;
|
||||
|
||||
struct Vertex {
|
||||
//[[location(0)]] color: vec4<f32>;
|
||||
[[location(0)]] place: vec3<f32>;
|
||||
[[location(1)]] color: u32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] color: vec4<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = view.view_proj * vec4<f32>(vertex.place, 1.0);
|
||||
// What is this craziness?
|
||||
out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
|
||||
//out.color = vertex.color;
|
||||
//out.color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||
return in.color;
|
||||
}
|
||||
373
src_testbed/lines/mod.rs
Normal file
373
src_testbed/lines/mod.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
#![allow(warnings)]
|
||||
/**
|
||||
*
|
||||
* NOTE: this module and its submodules are only temporary. It is a copy-paste of the bevy-debug-lines
|
||||
* crate: https://github.com/Toqozz/bevy_debug_lines (MIT license)
|
||||
* It has been partially updated to work with bevy 0.7, but hasn’t been released yet.
|
||||
* So, in the mean time, we are keeping a version here that we will replace by the
|
||||
* upstream dependency once:
|
||||
* 1. The version compatible with bevy 0.7 is released to crates.io.
|
||||
* 2. We find a way to make the 2D version work with our examples. The problem
|
||||
* only happens when running our own examples because cargo’s unification of
|
||||
* features will enable the `3d` feature of `bevy_debug_lines` when running
|
||||
* a `2d` example.
|
||||
*
|
||||
*/
|
||||
use bevy::{
|
||||
asset::{Assets, HandleUntyped},
|
||||
pbr::{NotShadowCaster, NotShadowReceiver},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
render::{
|
||||
mesh::{/*Indices,*/ Mesh, VertexAttributeValues},
|
||||
render_phase::AddRenderCommand,
|
||||
render_resource::PrimitiveTopology,
|
||||
render_resource::Shader,
|
||||
},
|
||||
};
|
||||
|
||||
mod render_dim;
|
||||
|
||||
// This module exists to "isolate" the `#[cfg]` attributes to this part of the
|
||||
// code. Otherwise, we would pollute the code with a lot of feature
|
||||
// gates-specific code.
|
||||
#[cfg(feature = "dim3")]
|
||||
mod dim {
|
||||
pub(crate) use crate::lines::render_dim::r3d::{queue, DebugLinePipeline, DrawDebugLines};
|
||||
pub(crate) use bevy::core_pipeline::Opaque3d as Phase;
|
||||
use bevy::{asset::Handle, render::mesh::Mesh};
|
||||
|
||||
pub(crate) type MeshHandle = Handle<Mesh>;
|
||||
pub(crate) fn from_handle(from: &MeshHandle) -> &Handle<Mesh> {
|
||||
from
|
||||
}
|
||||
pub(crate) fn into_handle(from: Handle<Mesh>) -> MeshHandle {
|
||||
from
|
||||
}
|
||||
pub(crate) const SHADER_FILE: &str = include_str!("debuglines.wgsl");
|
||||
pub(crate) const DIMMENSION: &str = "3d";
|
||||
}
|
||||
#[cfg(feature = "dim2")]
|
||||
mod dim {
|
||||
pub(crate) use crate::lines::render_dim::r2d::{queue, DebugLinePipeline, DrawDebugLines};
|
||||
pub(crate) use bevy::core_pipeline::Transparent2d as Phase;
|
||||
use bevy::{asset::Handle, render::mesh::Mesh, sprite::Mesh2dHandle};
|
||||
|
||||
pub(crate) type MeshHandle = Mesh2dHandle;
|
||||
pub(crate) fn from_handle(from: &MeshHandle) -> &Handle<Mesh> {
|
||||
&from.0
|
||||
}
|
||||
pub(crate) fn into_handle(from: Handle<Mesh>) -> MeshHandle {
|
||||
Mesh2dHandle(from)
|
||||
}
|
||||
pub(crate) const SHADER_FILE: &str = include_str!("debuglines2d.wgsl");
|
||||
pub(crate) const DIMMENSION: &str = "2d";
|
||||
}
|
||||
|
||||
// See debuglines.wgsl for explanation on 2 shaders.
|
||||
//pub(crate) const SHADER_FILE: &str = include_str!("debuglines.wgsl");
|
||||
pub(crate) const DEBUG_LINES_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17477439189930443325);
|
||||
|
||||
pub(crate) struct DebugLinesConfig {
|
||||
depth_test: bool,
|
||||
}
|
||||
|
||||
/// Bevy plugin, for initializing stuff.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```.ignore
|
||||
/// use bevy::prelude::*;
|
||||
/// use bevy_prototype_debug_lines::*;
|
||||
///
|
||||
/// App::new()
|
||||
/// .add_plugins(DefaultPlugins)
|
||||
/// .add_plugin(DebugLinesPlugin::default())
|
||||
/// .run();
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively, you can initialize the plugin with depth testing, so that
|
||||
/// debug lines cut through geometry. To do this, use [`DebugLinesPlugin::with_depth_test(true)`].
|
||||
/// ```.ignore
|
||||
/// use bevy::prelude::*;
|
||||
/// use bevy_prototype_debug_lines::*;
|
||||
///
|
||||
/// App::new()
|
||||
/// .add_plugins(DefaultPlugins)
|
||||
/// .add_plugin(DebugLinesPlugin::with_depth_test(true))
|
||||
/// .run();
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DebugLinesPlugin {
|
||||
depth_test: bool,
|
||||
}
|
||||
|
||||
impl DebugLinesPlugin {
|
||||
/// Controls whether debug lines should be drawn with depth testing enabled
|
||||
/// or disabled.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `val` - True if lines should intersect with other geometry, or false
|
||||
/// if lines should always draw on top be drawn on top (the default).
|
||||
pub fn with_depth_test(val: bool) -> Self {
|
||||
Self { depth_test: val }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for DebugLinesPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
use bevy::render::{render_resource::SpecializedMeshPipelines, RenderApp, RenderStage};
|
||||
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
|
||||
shaders.set_untracked(
|
||||
DEBUG_LINES_SHADER_HANDLE,
|
||||
Shader::from_wgsl(dim::SHADER_FILE),
|
||||
);
|
||||
app.init_resource::<DebugLines>();
|
||||
app.add_startup_system(setup)
|
||||
.add_system_to_stage(CoreStage::PostUpdate, update.label("draw_lines"));
|
||||
app.sub_app_mut(RenderApp)
|
||||
.add_render_command::<dim::Phase, dim::DrawDebugLines>()
|
||||
.insert_resource(DebugLinesConfig {
|
||||
depth_test: self.depth_test,
|
||||
})
|
||||
.init_resource::<dim::DebugLinePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<dim::DebugLinePipeline>>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract)
|
||||
.add_system_to_stage(RenderStage::Queue, dim::queue);
|
||||
|
||||
info!("Loaded {} debug lines plugin.", dim::DIMMENSION);
|
||||
}
|
||||
}
|
||||
|
||||
// Number of meshes to separate line buffers into.
|
||||
// We don't really do culling currently but this is a gateway to that.
|
||||
const MESH_COUNT: usize = 4;
|
||||
// Maximum number of points for each individual mesh.
|
||||
const MAX_POINTS_PER_MESH: usize = 2_usize.pow(16);
|
||||
const _MAX_LINES_PER_MESH: usize = MAX_POINTS_PER_MESH / 2;
|
||||
/// Maximum number of points.
|
||||
pub const MAX_POINTS: usize = MAX_POINTS_PER_MESH * MESH_COUNT;
|
||||
/// Maximum number of unique lines to draw at once.
|
||||
pub const MAX_LINES: usize = MAX_POINTS / 2;
|
||||
|
||||
fn setup(mut cmds: Commands, mut meshes: ResMut<Assets<Mesh>>) {
|
||||
// Spawn a bunch of meshes to use for lines.
|
||||
for i in 0..MESH_COUNT {
|
||||
// Create a new mesh with the number of vertices we need.
|
||||
let mut mesh = Mesh::new(PrimitiveTopology::LineList);
|
||||
mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_POSITION,
|
||||
VertexAttributeValues::Float32x3(Vec::with_capacity(MAX_POINTS_PER_MESH)),
|
||||
);
|
||||
mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_COLOR,
|
||||
VertexAttributeValues::Uint32(Vec::with_capacity(MAX_POINTS_PER_MESH)),
|
||||
);
|
||||
// https://github.com/Toqozz/bevy_debug_lines/issues/16
|
||||
//mesh.set_indices(Some(Indices::U16(Vec::with_capacity(MAX_POINTS_PER_MESH))));
|
||||
|
||||
cmds.spawn_bundle((
|
||||
dim::into_handle(meshes.add(mesh)),
|
||||
NotShadowCaster,
|
||||
NotShadowReceiver,
|
||||
Transform::default(),
|
||||
GlobalTransform::default(),
|
||||
Visibility::default(),
|
||||
ComputedVisibility::default(),
|
||||
DebugLinesMesh(i),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
debug_line_meshes: Query<(&dim::MeshHandle, &DebugLinesMesh)>,
|
||||
time: Res<Time>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut lines: ResMut<DebugLines>,
|
||||
) {
|
||||
// For each debug line mesh, fill its buffers with the relevant positions/colors chunks.
|
||||
for (mesh_handle, debug_lines_idx) in debug_line_meshes.iter() {
|
||||
let mesh = meshes.get_mut(dim::from_handle(mesh_handle)).unwrap();
|
||||
use VertexAttributeValues::{Float32x3, Float32x4, Uint32};
|
||||
if let Some(Float32x3(vbuffer)) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) {
|
||||
vbuffer.clear();
|
||||
if let Some(new_content) = lines
|
||||
.positions
|
||||
.chunks(MAX_POINTS_PER_MESH)
|
||||
.nth(debug_lines_idx.0)
|
||||
{
|
||||
vbuffer.extend(new_content);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Uint32(cbuffer)) = mesh.attribute_mut(Mesh::ATTRIBUTE_COLOR) {
|
||||
cbuffer.clear();
|
||||
if let Some(new_content) = lines
|
||||
.colors
|
||||
.chunks(MAX_POINTS_PER_MESH)
|
||||
.nth(debug_lines_idx.0)
|
||||
{
|
||||
cbuffer.extend(new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// https://github.com/Toqozz/bevy_debug_lines/issues/16
|
||||
if let Some(Indices::U16(indices)) = mesh.indices_mut() {
|
||||
indices.clear();
|
||||
if let Some(new_content) = lines.durations.chunks(_MAX_LINES_PER_MESH).nth(debug_lines_idx.0) {
|
||||
indices.extend(
|
||||
new_content.iter().enumerate().map(|(i, _)| i as u16).flat_map(|i| [i * 2, i*2 + 1])
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Processes stuff like getting rid of expired lines and stuff.
|
||||
lines.update(time.delta_seconds());
|
||||
}
|
||||
|
||||
/// Move the DebugLinesMesh marker Component to the render context.
|
||||
fn extract(mut commands: Commands, query: Query<Entity, With<DebugLinesMesh>>) {
|
||||
for entity in query.iter() {
|
||||
commands.get_or_spawn(entity).insert(RenderDebugLinesMesh);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct DebugLinesMesh(usize);
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct RenderDebugLinesMesh;
|
||||
|
||||
/// Bevy resource providing facilities to draw lines.
|
||||
///
|
||||
/// # Usage
|
||||
/// ```.ignore
|
||||
/// use bevy::prelude::*;
|
||||
/// use bevy_prototype_debug_lines::*;
|
||||
///
|
||||
/// // Draws 3 horizontal lines, which disappear after 1 frame.
|
||||
/// fn some_system(mut lines: ResMut<DebugLines>) {
|
||||
/// lines.line(Vec3::new(-1.0, 1.0, 0.0), Vec3::new(1.0, 1.0, 0.0), 0.0);
|
||||
/// lines.line_colored(
|
||||
/// Vec3::new(-1.0, 0.0, 0.0),
|
||||
/// Vec3::new(1.0, 0.0, 0.0),
|
||||
/// 0.0,
|
||||
/// Color::WHITE
|
||||
/// );
|
||||
/// lines.line_gradient(
|
||||
/// Vec3::new(-1.0, -1.0, 0.0),
|
||||
/// Vec3::new(1.0, -1.0, 0.0),
|
||||
/// 0.0,
|
||||
/// Color::WHITE, Color::PINK
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct DebugLines {
|
||||
pub positions: Vec<[f32; 3]>,
|
||||
//pub colors: Vec<[f32; 4]>,
|
||||
pub colors: Vec<u32>,
|
||||
pub durations: Vec<f32>,
|
||||
}
|
||||
|
||||
impl DebugLines {
|
||||
/// Draw a line in world space, or update an existing line
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `start` - The start of the line in world space
|
||||
/// * `end` - The end of the line in world space
|
||||
/// * `duration` - Duration (in seconds) that the line should show for -- a value of
|
||||
/// zero will show the line for 1 frame.
|
||||
pub fn line(&mut self, start: Vec3, end: Vec3, duration: f32) {
|
||||
self.line_colored(start, end, duration, Color::WHITE);
|
||||
}
|
||||
|
||||
/// Draw a line in world space with a specified color, or update an existing line
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `start` - The start of the line in world space
|
||||
/// * `end` - The end of the line in world space
|
||||
/// * `duration` - Duration (in seconds) that the line should show for -- a value of
|
||||
/// zero will show the line for 1 frame.
|
||||
/// * `color` - Line color
|
||||
pub fn line_colored(&mut self, start: Vec3, end: Vec3, duration: f32, color: Color) {
|
||||
self.line_gradient(start, end, duration, color, color);
|
||||
}
|
||||
|
||||
/// Draw a line in world space with a specified gradient color, or update an existing line
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `start` - The start of the line in world space
|
||||
/// * `end` - The end of the line in world space
|
||||
/// * `duration` - Duration (in seconds) that the line should show for -- a value of
|
||||
/// zero will show the line for 1 frame.
|
||||
/// * `start_color` - Line color
|
||||
/// * `end_color` - Line color
|
||||
pub fn line_gradient(
|
||||
&mut self,
|
||||
start: Vec3,
|
||||
end: Vec3,
|
||||
duration: f32,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
if self.positions.len() >= MAX_POINTS {
|
||||
warn!("Tried to add a new line when existing number of lines was already at maximum, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
self.positions.push(start.into());
|
||||
self.positions.push(end.into());
|
||||
//self.colors.push(start_color.into());
|
||||
//self.colors.push(end_color.into());
|
||||
self.colors.push(start_color.as_rgba_u32());
|
||||
self.colors.push(end_color.as_rgba_u32());
|
||||
self.durations.push(duration);
|
||||
}
|
||||
|
||||
// Returns the indices of the start and end positions of the nth line.
|
||||
// The indices can also be used to access color data.
|
||||
fn nth(&self, idx: usize) -> (usize, usize) {
|
||||
let i = idx * 2;
|
||||
(i, i + 1)
|
||||
}
|
||||
|
||||
// Prepare [`ImmediateLinesStorage`] and [`RetainedLinesStorage`] for next
|
||||
// frame.
|
||||
// This clears the immediate mod buffers and tells the retained mode
|
||||
// buffers to recompute expired lines list.
|
||||
fn update(&mut self, dt: f32) {
|
||||
// TODO: an actual line counter wouldn't hurt.
|
||||
let mut i = 0;
|
||||
let mut len = self.durations.len();
|
||||
while i != len {
|
||||
self.durations[i] -= dt;
|
||||
// <= instead of < is fine here because this is always called AFTER sending the
|
||||
// data to the mesh, so we're guaranteed at least a frame here.
|
||||
if self.durations[i] <= 0.0 {
|
||||
let (cur_s, cur_e) = self.nth(i);
|
||||
let (last_s, last_e) = self.nth(len - 1);
|
||||
self.positions.swap(cur_s, last_s);
|
||||
self.positions.swap(cur_e, last_e);
|
||||
self.colors.swap(cur_s, last_s);
|
||||
self.colors.swap(cur_e, last_e);
|
||||
self.durations.swap(i, len - 1);
|
||||
len -= 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.positions.truncate(len * 2);
|
||||
self.colors.truncate(len * 2);
|
||||
self.durations.truncate(len);
|
||||
}
|
||||
}
|
||||
335
src_testbed/lines/render_dim.rs
Normal file
335
src_testbed/lines/render_dim.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
pub mod r3d {
|
||||
use bevy::{
|
||||
core_pipeline::Opaque3d,
|
||||
pbr::{
|
||||
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
|
||||
SetMeshViewBindGroup,
|
||||
},
|
||||
prelude::*,
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::{
|
||||
BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
|
||||
DepthStencilState, FragmentState, FrontFace, MultisampleState, PipelineCache,
|
||||
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor,
|
||||
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
StencilFaceState, StencilState, TextureFormat, VertexAttribute, VertexBufferLayout,
|
||||
VertexFormat, VertexState, VertexStepMode,
|
||||
},
|
||||
texture::BevyDefault,
|
||||
view::{ExtractedView, Msaa},
|
||||
},
|
||||
utils::Hashed,
|
||||
};
|
||||
|
||||
use crate::lines::{DebugLinesConfig, RenderDebugLinesMesh, DEBUG_LINES_SHADER_HANDLE};
|
||||
|
||||
pub(crate) struct DebugLinePipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
//always_in_front: bool,
|
||||
}
|
||||
impl FromWorld for DebugLinePipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
//let config = render_world.get_resource::<DebugLinesConfig>().unwrap();
|
||||
DebugLinePipeline {
|
||||
mesh_pipeline: render_world.get_resource::<MeshPipeline>().unwrap().clone(),
|
||||
shader: DEBUG_LINES_SHADER_HANDLE.typed(),
|
||||
//always_in_front: config.always_in_front,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for DebugLinePipeline {
|
||||
type Key = (bool, MeshPipelineKey);
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(depth_test, key): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
//use VertexFormat::{Float32x3, Float32x4};
|
||||
|
||||
let mut shader_defs = Vec::new();
|
||||
shader_defs.push("LINES_3D".to_string());
|
||||
if depth_test {
|
||||
shader_defs.push("DEPTH_TEST_ENABLED".to_string());
|
||||
}
|
||||
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
let (label, blend, depth_write_enabled);
|
||||
if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) {
|
||||
label = "transparent_mesh_pipeline".into();
|
||||
blend = Some(BlendState::ALPHA_BLENDING);
|
||||
// For the transparent pass, fragments that are closer will be alpha
|
||||
// blended but their depth is not written to the depth buffer.
|
||||
depth_write_enabled = false;
|
||||
} else {
|
||||
label = "opaque_mesh_pipeline".into();
|
||||
blend = Some(BlendState::REPLACE);
|
||||
// For the opaque and alpha mask passes, fragments that are closer
|
||||
// will replace the current fragment value in the output and the depth is
|
||||
// written to the depth buffer.
|
||||
depth_write_enabled = true;
|
||||
}
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![ColorTargetState {
|
||||
format: TextureFormat::bevy_default(),
|
||||
blend,
|
||||
write_mask: ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
layout: Some(vec![self.mesh_pipeline.view_layout.clone()]),
|
||||
primitive: PrimitiveState {
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
unclipped_depth: false,
|
||||
polygon_mode: PolygonMode::Fill,
|
||||
conservative: false,
|
||||
topology: PrimitiveTopology::LineList,
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled,
|
||||
depth_compare: CompareFunction::Greater,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some(label),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn queue(
|
||||
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
debug_line_pipeline: Res<DebugLinePipeline>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<DebugLinePipeline>>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>), With<RenderDebugLinesMesh>>,
|
||||
config: Res<DebugLinesConfig>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
let draw_custom = opaque_3d_draw_functions
|
||||
.read()
|
||||
.get_id::<DrawDebugLines>()
|
||||
.unwrap();
|
||||
let key = MeshPipelineKey::from_msaa_samples(msaa.samples);
|
||||
for (view, mut transparent_phase) in views.iter_mut() {
|
||||
let view_matrix = view.transform.compute_matrix();
|
||||
let view_row_2 = view_matrix.row(2);
|
||||
for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&mut pipeline_cache,
|
||||
&debug_line_pipeline,
|
||||
(config.depth_test, key),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
transparent_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function: draw_custom,
|
||||
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawDebugLines = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
}
|
||||
|
||||
pub mod r2d {
|
||||
use bevy::{
|
||||
asset::Handle,
|
||||
core::FloatOrd,
|
||||
core_pipeline::Transparent2d,
|
||||
prelude::*,
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::{
|
||||
BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
|
||||
DepthStencilState, FragmentState, FrontFace, MultisampleState, PipelineCache,
|
||||
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, Shader,
|
||||
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
StencilFaceState, StencilState, TextureFormat, VertexAttribute, VertexBufferLayout,
|
||||
VertexFormat, VertexState, VertexStepMode,
|
||||
},
|
||||
texture::BevyDefault,
|
||||
view::{Msaa, VisibleEntities},
|
||||
},
|
||||
sprite::{
|
||||
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform,
|
||||
SetMesh2dBindGroup, SetMesh2dViewBindGroup,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::lines::{RenderDebugLinesMesh, DEBUG_LINES_SHADER_HANDLE};
|
||||
|
||||
pub(crate) struct DebugLinePipeline {
|
||||
mesh_pipeline: Mesh2dPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
impl FromWorld for DebugLinePipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
DebugLinePipeline {
|
||||
mesh_pipeline: render_world
|
||||
.get_resource::<Mesh2dPipeline>()
|
||||
.unwrap()
|
||||
.clone(),
|
||||
shader: DEBUG_LINES_SHADER_HANDLE.typed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for DebugLinePipeline {
|
||||
type Key = Mesh2dPipelineKey;
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
/*
|
||||
let mut shader_defs = Vec::new();
|
||||
shader_defs.push("LINES_3D".to_string());
|
||||
if depth_test {
|
||||
shader_defs.push("DEPTH_TEST_ENABLED".to_string());
|
||||
}
|
||||
*/
|
||||
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: vec![],
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![ColorTargetState {
|
||||
format: TextureFormat::bevy_default(),
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
layout: Some(vec![self.mesh_pipeline.view_layout.clone()]),
|
||||
primitive: PrimitiveState {
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
unclipped_depth: false,
|
||||
polygon_mode: PolygonMode::Fill,
|
||||
conservative: false,
|
||||
topology: PrimitiveTopology::LineList,
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn queue(
|
||||
draw2d_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
debug_line_pipeline: Res<DebugLinePipeline>,
|
||||
mut pipeline_cache: ResMut<PipelineCache>,
|
||||
mut specialized_pipelines: ResMut<SpecializedMeshPipelines<DebugLinePipeline>>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
material_meshes: Query<(&Mesh2dUniform, &Mesh2dHandle), With<RenderDebugLinesMesh>>,
|
||||
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>,
|
||||
) {
|
||||
for (view, mut phase) in views.iter_mut() {
|
||||
let draw_mesh2d = draw2d_functions.read().get_id::<DrawDebugLines>().unwrap();
|
||||
let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples);
|
||||
|
||||
for visible_entity in &view.entities {
|
||||
if let Ok((uniform, mesh_handle)) = material_meshes.get(*visible_entity) {
|
||||
if let Some(mesh) = render_meshes.get(&mesh_handle.0) {
|
||||
let mesh_key = msaa_key
|
||||
| Mesh2dPipelineKey::from_primitive_topology(
|
||||
PrimitiveTopology::LineList,
|
||||
);
|
||||
let mesh_z = uniform.transform.w_axis.z;
|
||||
let pipeline = specialized_pipelines
|
||||
.specialize(
|
||||
&mut pipeline_cache,
|
||||
&debug_line_pipeline,
|
||||
mesh_key,
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
phase.add(Transparent2d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_mesh2d,
|
||||
pipeline,
|
||||
sort_key: FloatOrd(mesh_z),
|
||||
batch_range: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawDebugLines = (
|
||||
SetItemPipeline,
|
||||
SetMesh2dViewBindGroup<0>,
|
||||
SetMesh2dBindGroup<1>,
|
||||
DrawMesh2d,
|
||||
);
|
||||
}
|
||||
@@ -210,7 +210,8 @@ impl EntityWithGraphics {
|
||||
// Cuboid mesh
|
||||
//
|
||||
let cuboid = bevy_mesh_from_polyline(Cuboid::new(Vector2::new(1.0, 1.0)).to_polyline());
|
||||
out.insert(ShapeType::Cuboid, meshes.add(cuboid));
|
||||
out.insert(ShapeType::Cuboid, meshes.add(cuboid.clone()));
|
||||
out.insert(ShapeType::RoundCuboid, meshes.add(cuboid));
|
||||
|
||||
//
|
||||
// Ball mesh
|
||||
@@ -228,7 +229,8 @@ impl EntityWithGraphics {
|
||||
// Cuboid mesh
|
||||
//
|
||||
let cuboid = Mesh::from(shape::Cube { size: 2.0 });
|
||||
out.insert(ShapeType::Cuboid, meshes.add(cuboid));
|
||||
out.insert(ShapeType::Cuboid, meshes.add(cuboid.clone()));
|
||||
out.insert(ShapeType::RoundCuboid, meshes.add(cuboid));
|
||||
|
||||
//
|
||||
// Ball mesh
|
||||
@@ -305,12 +307,12 @@ fn bevy_polyline(buffers: (Vec<Point2<Real>>, Option<Vec<[u32; 2]>>)) -> Mesh {
|
||||
|
||||
// Generate the mesh
|
||||
let mut mesh = Mesh::new(PrimitiveTopology::LineStrip);
|
||||
mesh.set_attribute(
|
||||
mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_POSITION,
|
||||
VertexAttributeValues::from(vertices),
|
||||
);
|
||||
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::from(normals));
|
||||
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::from(uvs));
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::from(normals));
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::from(uvs));
|
||||
mesh.set_indices(Some(Indices::U32(indices)));
|
||||
mesh
|
||||
}
|
||||
@@ -348,12 +350,12 @@ fn bevy_mesh(buffers: (Vec<Point3<Real>>, Vec<[u32; 3]>)) -> Mesh {
|
||||
|
||||
// Generate the mesh
|
||||
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
||||
mesh.set_attribute(
|
||||
mesh.insert_attribute(
|
||||
Mesh::ATTRIBUTE_POSITION,
|
||||
VertexAttributeValues::from(vertices),
|
||||
);
|
||||
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::from(normals));
|
||||
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::from(uvs));
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::from(normals));
|
||||
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::from(uvs));
|
||||
mesh.set_indices(Some(Indices::U32(indices)));
|
||||
mesh
|
||||
}
|
||||
@@ -365,6 +367,11 @@ fn collider_mesh_scale(co_shape: &dyn Shape) -> Vec3 {
|
||||
let c = co_shape.as_cuboid().unwrap();
|
||||
Vec3::new(c.half_extents.x as f32, c.half_extents.y as f32, 1.0)
|
||||
}
|
||||
#[cfg(feature = "dim2")]
|
||||
ShapeType::RoundCuboid => {
|
||||
let c = &co_shape.as_round_cuboid().unwrap().inner_shape;
|
||||
Vec3::new(c.half_extents.x as f32, c.half_extents.y as f32, 1.0)
|
||||
}
|
||||
ShapeType::Ball => {
|
||||
let b = co_shape.as_ball().unwrap();
|
||||
Vec3::new(b.radius as f32, b.radius as f32, b.radius as f32)
|
||||
@@ -375,13 +382,18 @@ fn collider_mesh_scale(co_shape: &dyn Shape) -> Vec3 {
|
||||
Vec3::from_slice(c.half_extents.cast::<f32>().as_slice())
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
ShapeType::RoundCuboid => {
|
||||
let c = co_shape.as_round_cuboid().unwrap();
|
||||
Vec3::from_slice(c.inner_shape.half_extents.cast::<f32>().as_slice())
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
ShapeType::Cylinder => {
|
||||
let c = co_shape.as_cylinder().unwrap();
|
||||
Vec3::new(c.radius as f32, c.half_height as f32, c.radius as f32)
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
ShapeType::RoundCylinder => {
|
||||
let c = &co_shape.as_round_cylinder().unwrap().base_shape;
|
||||
let c = &co_shape.as_round_cylinder().unwrap().inner_shape;
|
||||
Vec3::new(c.radius as f32, c.half_height as f32, c.radius as f32)
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
@@ -391,7 +403,7 @@ fn collider_mesh_scale(co_shape: &dyn Shape) -> Vec3 {
|
||||
}
|
||||
#[cfg(feature = "dim3")]
|
||||
ShapeType::RoundCone => {
|
||||
let c = &co_shape.as_round_cone().unwrap().base_shape;
|
||||
let c = &co_shape.as_round_cone().unwrap().inner_shape;
|
||||
Vec3::new(c.radius as f32, c.half_height as f32, c.radius as f32)
|
||||
}
|
||||
_ => Vec3::ONE,
|
||||
@@ -439,7 +451,7 @@ fn generate_collider_mesh(co_shape: &dyn Shape) -> Option<Mesh> {
|
||||
}
|
||||
ShapeType::RoundConvexPolygon => {
|
||||
let poly = co_shape.as_round_convex_polygon().unwrap();
|
||||
bevy_mesh_from_polyline(poly.base_shape.points().to_vec())
|
||||
bevy_mesh_from_polyline(poly.inner_shape.points().to_vec())
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
@@ -472,7 +484,7 @@ fn generate_collider_mesh(co_shape: &dyn Shape) -> Option<Mesh> {
|
||||
}
|
||||
ShapeType::RoundConvexPolyhedron => {
|
||||
let poly = co_shape.as_round_convex_polyhedron().unwrap();
|
||||
bevy_mesh(poly.base_shape.to_trimesh())
|
||||
bevy_mesh(poly.inner_shape.to_trimesh())
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@@ -642,7 +642,7 @@ fn physx_collider_from_rapier_collider(
|
||||
}
|
||||
} else if let Some(convex) = shape
|
||||
.as_convex_polyhedron()
|
||||
.or(shape.as_round_convex_polyhedron().map(|c| &c.base_shape))
|
||||
.or(shape.as_round_convex_polyhedron().map(|c| &c.inner_shape))
|
||||
{
|
||||
let vertices = convex.points();
|
||||
let mut convex_desc;
|
||||
|
||||
@@ -5,7 +5,7 @@ use bevy::prelude::*;
|
||||
|
||||
use crate::physics::{PhysicsEvents, PhysicsSnapshot, PhysicsState};
|
||||
use crate::plugin::TestbedPlugin;
|
||||
use crate::ui;
|
||||
use crate::{debug_render, ui};
|
||||
use crate::{graphics::GraphicsManager, harness::RunState};
|
||||
|
||||
use na::{self, Point2, Point3, Vector3};
|
||||
@@ -26,7 +26,6 @@ use crate::harness::Harness;
|
||||
use crate::physx_backend::PhysxWorld;
|
||||
use bevy::pbr::wireframe::WireframePlugin;
|
||||
use bevy::render::camera::Camera;
|
||||
use bevy::render::options::{WgpuFeatures, WgpuOptions};
|
||||
use bevy_egui::EguiContext;
|
||||
|
||||
#[cfg(feature = "dim2")]
|
||||
@@ -363,16 +362,10 @@ impl TestbedApp {
|
||||
|
||||
app.insert_resource(WindowDescriptor {
|
||||
title,
|
||||
vsync: true,
|
||||
..Default::default()
|
||||
})
|
||||
.insert_resource(ClearColor(Color::rgb(0.15, 0.15, 0.15)))
|
||||
.insert_resource(Msaa { samples: 4 })
|
||||
.insert_resource(WgpuOptions {
|
||||
// Required for wireframes.
|
||||
features: WgpuFeatures::POLYGON_MODE_LINE,
|
||||
..Default::default()
|
||||
})
|
||||
.insert_resource(AmbientLight {
|
||||
brightness: 0.3,
|
||||
..Default::default()
|
||||
@@ -380,7 +373,8 @@ impl TestbedApp {
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(OrbitCameraPlugin)
|
||||
.add_plugin(WireframePlugin)
|
||||
.add_plugin(bevy_egui::EguiPlugin);
|
||||
.add_plugin(bevy_egui::EguiPlugin)
|
||||
.add_plugin(debug_render::RapierDebugRenderPlugin::default());
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
app.add_plugin(bevy_webgl2::WebGL2Plugin);
|
||||
@@ -388,15 +382,15 @@ impl TestbedApp {
|
||||
#[cfg(feature = "other-backends")]
|
||||
app.insert_non_send_resource(self.other_backends);
|
||||
|
||||
app.add_startup_system(setup_graphics_environment.system())
|
||||
app.add_startup_system(setup_graphics_environment)
|
||||
.insert_non_send_resource(self.graphics)
|
||||
.insert_resource(self.state)
|
||||
.insert_non_send_resource(self.harness)
|
||||
.insert_resource(self.builders)
|
||||
.insert_non_send_resource(self.plugins)
|
||||
.add_stage_before(CoreStage::Update, "physics", SystemStage::single_threaded())
|
||||
.add_system_to_stage("physics", update_testbed.system())
|
||||
.add_system(egui_focus.system());
|
||||
.add_system_to_stage("physics", update_testbed)
|
||||
.add_system(egui_focus);
|
||||
init(&mut app);
|
||||
app.run();
|
||||
}
|
||||
@@ -855,11 +849,14 @@ fn setup_graphics_environment(mut commands: Commands) {
|
||||
|
||||
commands
|
||||
.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_matrix(Mat4::face_toward(
|
||||
Vec3::new(-30.0, 30.0, 100.0),
|
||||
Vec3::new(0.0, 10.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
)),
|
||||
transform: Transform::from_matrix(
|
||||
Mat4::look_at_rh(
|
||||
Vec3::new(-30.0, 30.0, 100.0),
|
||||
Vec3::new(0.0, 10.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
)
|
||||
.inverse(),
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(OrbitCamera {
|
||||
@@ -899,9 +896,9 @@ fn setup_graphics_environment(mut commands: Commands) {
|
||||
});
|
||||
}
|
||||
|
||||
fn egui_focus(ui_context: Res<EguiContext>, mut cameras: Query<&mut OrbitCamera>) {
|
||||
fn egui_focus(mut ui_context: ResMut<EguiContext>, mut cameras: Query<&mut OrbitCamera>) {
|
||||
let mut camera_enabled = true;
|
||||
if ui_context.ctx().wants_pointer_input() {
|
||||
if ui_context.ctx_mut().wants_pointer_input() {
|
||||
camera_enabled = false;
|
||||
}
|
||||
for mut camera in cameras.iter_mut() {
|
||||
@@ -921,7 +918,7 @@ fn update_testbed(
|
||||
mut harness: NonSendMut<Harness>,
|
||||
#[cfg(feature = "other-backends")] mut other_backends: NonSendMut<OtherBackends>,
|
||||
mut plugins: NonSendMut<Plugins>,
|
||||
ui_context: Res<EguiContext>,
|
||||
mut ui_context: ResMut<EguiContext>,
|
||||
mut gfx_components: Query<(&mut Transform,)>,
|
||||
mut cameras: Query<(&Camera, &GlobalTransform, &mut OrbitCamera)>,
|
||||
keys: Res<Input<KeyCode>>,
|
||||
@@ -956,11 +953,11 @@ fn update_testbed(
|
||||
// Update UI
|
||||
{
|
||||
let harness = &mut *harness;
|
||||
ui::update_ui(&ui_context, &mut state, harness);
|
||||
ui::update_ui(&mut ui_context, &mut state, harness);
|
||||
|
||||
for plugin in &mut plugins.0 {
|
||||
plugin.update_ui(
|
||||
&ui_context,
|
||||
&mut ui_context,
|
||||
harness,
|
||||
&mut graphics,
|
||||
&mut commands,
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::PhysicsState;
|
||||
use bevy_egui::egui::Slider;
|
||||
use bevy_egui::{egui, EguiContext};
|
||||
|
||||
pub fn update_ui(ui_context: &EguiContext, state: &mut TestbedState, harness: &mut Harness) {
|
||||
egui::Window::new("Parameters").show(ui_context.ctx(), |ui| {
|
||||
pub fn update_ui(ui_context: &mut EguiContext, state: &mut TestbedState, harness: &mut Harness) {
|
||||
egui::Window::new("Parameters").show(ui_context.ctx_mut(), |ui| {
|
||||
if state.backend_names.len() > 1 && !state.example_names.is_empty() {
|
||||
let mut changed = false;
|
||||
egui::ComboBox::from_label("backend")
|
||||
|
||||
Reference in New Issue
Block a user