From 47342c81b59c0712d179a4410c983ac01c9d3e3a Mon Sep 17 00:00:00 2001 From: Aceeri Date: Fri, 21 Jul 2023 13:46:19 -0700 Subject: [PATCH 1/7] Backend agnosticism --- Cargo.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4852f52..205356b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,10 @@ version = "0.4.0" [features] default = ["rapier"] debug_lines = [] -rapier = ["bevy_rapier3d"] +rapier3d = ["bevy_rapier3d"] +rapier2d = ["bevy_rapier2d"] +bevy_xpbd_3d = ["bevy_xpbd_3d"] +bevy_xpbd_2d = ["bevy_xpbd_2d"] [dependencies] bevy = { version = "0.11", default-features = false, features = [ @@ -26,6 +29,12 @@ bevy_rapier3d = { version = "0.22", default-features = false, features = [ "async-collider", "dim3", ], optional = true } +bevy_rapier2d = { version = "0.22", default-features = false, features = [ + "async-collider", + "dim3", +], optional = true } +bevy_xpbd_3d = { version = "0.2", optional = true } +bevy_xpbd_2d = { version = "0.2", optional = true } [dev-dependencies] bevy = "0.11" From 4b5d99f39619f3f978a1ff65e21a46e0e2211811 Mon Sep 17 00:00:00 2001 From: Aceeri Date: Mon, 9 Oct 2023 10:40:36 -0700 Subject: [PATCH 2/7] Backend agnosticism Initial splitting More work on agnosticism aaaaaaaaaa backends --- Cargo.toml | 17 +++---- src/backend/mod.rs | 34 ++++++++++++++ src/backend/query.rs | 22 +++++++++ src/{ => backend}/rapier.rs | 16 +++++++ src/backend/rapier/bundle.rs | 60 +++++++++++++++++++++++++ src/backend/rapier/mass.rs | 29 ++++++++++++ src/backend/rapier/mod.rs | 64 ++++++++++++++++++++++++++ src/backend/rapier/query.rs | 5 +++ src/backend/rapier/velocity.rs | 21 +++++++++ src/backend/xpbd/mass.rs | 27 +++++++++++ src/backend/xpbd/mod.rs | 82 ++++++++++++++++++++++++++++++++++ src/backend/xpbd/velocity.rs | 22 +++++++++ src/bundles.rs | 10 ++--- src/controller/ground.rs | 34 +++++++------- src/controller/mod.rs | 1 + src/controller/movement.rs | 2 +- src/controller/orientation.rs | 2 +- src/lib.rs | 11 ++--- src/physics.rs | 28 ------------ src/plugins.rs | 51 ++++++++++++++++++--- 20 files changed, 463 insertions(+), 75 deletions(-) create mode 100644 src/backend/mod.rs create mode 100644 src/backend/query.rs rename src/{ => backend}/rapier.rs (79%) create mode 100644 src/backend/rapier/bundle.rs create mode 100644 src/backend/rapier/mass.rs create mode 100644 src/backend/rapier/mod.rs create mode 100644 src/backend/rapier/query.rs create mode 100644 src/backend/rapier/velocity.rs create mode 100644 src/backend/xpbd/mass.rs create mode 100644 src/backend/xpbd/mod.rs create mode 100644 src/backend/xpbd/velocity.rs diff --git a/Cargo.toml b/Cargo.toml index 205356b..f23a81e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,14 @@ repository = "https://github.com/PROMETHIA-27/bevy_mod_wanderlust" version = "0.4.0" [features] -default = ["rapier"] +default = ["rapier3d"] debug_lines = [] -rapier3d = ["bevy_rapier3d"] -rapier2d = ["bevy_rapier2d"] -bevy_xpbd_3d = ["bevy_xpbd_3d"] -bevy_xpbd_2d = ["bevy_xpbd_2d"] +rapier = [] +rapier3d = ["rapier", "bevy_rapier3d"] +rapier2d = ["rapier", "bevy_rapier2d"] +xpbd = [] +xpbd3d = ["xpbd", "bevy_xpbd_3d"] +xpbd2d = ["xpbd", "bevy_xpbd_2d"] [dependencies] bevy = { version = "0.11", default-features = false, features = [ @@ -31,7 +33,7 @@ bevy_rapier3d = { version = "0.22", default-features = false, features = [ ], optional = true } bevy_rapier2d = { version = "0.22", default-features = false, features = [ "async-collider", - "dim3", + "dim2", ], optional = true } bevy_xpbd_3d = { version = "0.2", optional = true } bevy_xpbd_2d = { version = "0.2", optional = true } @@ -41,7 +43,6 @@ bevy = "0.11" aether_spyglass = "0.2" bevy-inspector-egui = "0.19" bevy_framepace = "0.13" -bevy_rapier3d = { version = "0.22", features = ["debug-render"] } # Enable a small amount of optimization in debug mode [profile.dev] @@ -53,4 +54,4 @@ opt-level = 3 [patch.crates-io] #bevy_rapier3d = { path = "../bevy_rapier/bevy_rapier3d" } -bevy_rapier3d = { git = "https://github.com/dimforge/bevy_rapier", rev = "0ea000b" } \ No newline at end of file +bevy_rapier3d = { git = "https://github.com/dimforge/bevy_rapier", rev = "0ea000b" } diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..6929aef --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,34 @@ +use bevy::{ + utils::HashSet, + prelude::* +}; + +#[cfg(feature = "rapier")] +mod rapier; +#[cfg(feature = "rapier")] +pub use rapier::{ + //apply_forces, + //apply_ground_forces, + //cast_ray, + //cast_shape, + //setup_physics_context, + RapierPhysicsBundle as BackendPhysicsBundle, + SpatialQuery, + Velocity, + Mass, +}; + +#[cfg(feature = "xpbd")] +mod xpbd; +#[cfg(feature = "xpbd")] +pub use xpbd::{ + apply_forces, + apply_ground_forces, + cast_ray, + //cast_shape, + setup_physics_context, + SpatialQuery, + XpbdPhysicsBundle as BackendPhysicsBundle, + Velocity, + Mass, +}; diff --git a/src/backend/query.rs b/src/backend/query.rs new file mode 100644 index 0000000..b75b538 --- /dev/null +++ b/src/backend/query.rs @@ -0,0 +1,22 @@ + +#[derive(Debug, Copy, Clone, Reflect)] +pub struct RayCastResult { + pub entity: Entity, + pub toi: f32, + pub normal: Vec3, + pub point: Vec3, +} + +#[derive(Debug, Copy, Clone, Reflect)] +pub struct ShapeCastResult { + pub entity: Entity, + pub toi: f32, + pub normal1: Vec3, + pub normal2: Vec3, + pub point1: Vec3, + pub point2: Vec3, +} + +pub struct QueryFilter { + pub exclude: HashSet, +} \ No newline at end of file diff --git a/src/rapier.rs b/src/backend/rapier.rs similarity index 79% rename from src/rapier.rs rename to src/backend/rapier.rs index 4e84ba6..deb62d2 100644 --- a/src/rapier.rs +++ b/src/backend/rapier.rs @@ -1,5 +1,8 @@ use crate::{controller::*, physics::*}; use bevy::prelude::*; +#[cfg(feature = "rapier2d")] +use bevy_rapier2d::prelude::*; +#[cfg(feature = "rapier3d")] use bevy_rapier3d::prelude::*; /// Contains common physics settings for character controllers. @@ -104,3 +107,16 @@ pub fn get_velocity_from_rapier(mut query: Query<(&mut ControllerVelocity, &Velo vel.angular = rapier_vel.angvel; } } + +/// *Note: Most users will not need to use this directly. Use [`WanderlustPlugin`](crate::plugins::WanderlustPlugin) instead. +/// Alternatively, if one only wants to disable the system, use [`WanderlustPhysicsTweaks`](WanderlustPhysicsTweaks).* +/// +/// This system adds some tweaks to rapier's physics settings that make the character controller behave better. +pub fn setup_physics_context(mut ctx: ResMut) { + let params = &mut ctx.integration_parameters; + // This prevents any noticeable jitter when running facefirst into a wall. + params.erp = 0.99; + // This prevents (most) noticeable jitter when running facefirst into an inverted corner. + params.max_velocity_iterations = 16; + // TODO: Fix jitter that occurs when running facefirst into a normal corner. +} diff --git a/src/backend/rapier/bundle.rs b/src/backend/rapier/bundle.rs new file mode 100644 index 0000000..89a090d --- /dev/null +++ b/src/backend/rapier/bundle.rs @@ -0,0 +1,60 @@ + +use bevy::prelude::*; +use super::rapier::prelude::*; + +/// Contains common physics settings for character controllers. +#[derive(Bundle)] +pub struct RapierPhysicsBundle { + /// See [`RigidBody`]. + pub rigidbody: RigidBody, + /// See [`Collider`]. + pub collider: Collider, + /// See [`Velocity`]. + pub velocity: Velocity, + /// See [`GravityScale`]. + pub gravity: GravityScale, + /// See [`Sleeping`]. + pub sleeping: Sleeping, + /// See [`Ccd`]. + pub ccd: Ccd, + /// See [`ExternalImpulse`]. + pub force: ExternalImpulse, + /// See [`LockedAxes`]. + pub locked_axes: LockedAxes, + /// See [`Friction`]. + pub friction: Friction, + /// See [`Damping`]. + pub damping: Damping, + /// See [`Restitution`]. + pub restitution: Restitution, + /// See [`ReadMassProperties`]. + pub read_mass_properties: ReadMassProperties, +} + +impl Default for RapierPhysicsBundle { + fn default() -> Self { + Self { + rigidbody: default(), + collider: Collider::capsule(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.5, 0.0), 0.5), + velocity: default(), + gravity: GravityScale(0.0), + sleeping: default(), + ccd: default(), + force: default(), + locked_axes: default(), + friction: Friction { + coefficient: 0.0, + combine_rule: CoefficientCombineRule::Min, + }, + damping: Damping { + linear_damping: 0.0, + angular_damping: 0.0, + }, + restitution: Restitution { + coefficient: 0.0, + combine_rule: CoefficientCombineRule::Min, + }, + read_mass_properties: default(), + } + } +} diff --git a/src/backend/rapier/mass.rs b/src/backend/rapier/mass.rs new file mode 100644 index 0000000..3a20d59 --- /dev/null +++ b/src/backend/rapier/mass.rs @@ -0,0 +1,29 @@ + +use bevy::{ + ecs::query::WorldQuery, + prelude::*, +}; +use super::rapier; + +#[derive(WorldQuery)] +pub struct Mass { + mass_properties: &'static rapier::prelude::ReadMassProperties, +} + +impl<'a> MassItem<'a> { + pub fn mass(&self) -> f32 { + self.mass_properties.0.mass + } + + pub fn inertia(&self) -> Vec3 { + self.mass_properties.0.principal_inertia + } + + pub fn inertia_matrix(&self) -> Mat3 { + self.mass_properties.0.into_rapier(1.0).reconstruct_inertia_matrix().into() + } + + pub fn local_center_of_mass(&self) -> Vec3 { + self.mass_properties.0.local_center_of_mass + } +} \ No newline at end of file diff --git a/src/backend/rapier/mod.rs b/src/backend/rapier/mod.rs new file mode 100644 index 0000000..864b54b --- /dev/null +++ b/src/backend/rapier/mod.rs @@ -0,0 +1,64 @@ +//use crate::{controller::*, physics::*}; +use bevy::prelude::*; + +#[cfg(feature = "rapier2d")] +pub use bevy_rapier2d as rapier; +#[cfg(feature = "rapier3d")] +pub use bevy_rapier3d as rapier; + +mod bundle; +pub use bundle::RapierPhysicsBundle; +mod mass; +pub use mass::*; +mod velocity; +pub use velocity::*; +mod query; +pub use query::*; + +pub use rapier::prelude::Collider; + +/* +/// Apply forces to the controller to make it float, move, jump, etc. +pub fn apply_forces( + mut forces: Query<(&mut ExternalImpulse, &ControllerForce)>, + ctx: Res, +) { + let dt = ctx.integration_parameters.dt; + for (mut impulse, force) in &mut forces { + impulse.impulse += force.linear * dt; + impulse.torque_impulse += force.angular * dt; + } +} + +/// Apply the opposing ground force to the entity we are pushing off of to float. +pub fn apply_ground_forces( + mut impulses: Query<&mut ExternalImpulse>, + ground_forces: Query<(&GroundForce, &GroundCast)>, + ctx: Res, +) { + let dt = ctx.integration_parameters.dt; + for (force, cast) in &ground_forces { + if let GroundCast::Touching(ground) = cast { + if let Some(ground_body) = ctx.collider_parent(ground.entity) { + if let Ok(mut impulse) = impulses.get_mut(ground_body) { + impulse.impulse += force.linear * dt; + impulse.torque_impulse += force.angular * dt; + } + } + } + } +} + +/// *Note: Most users will not need to use this directly. Use [`WanderlustPlugin`](crate::plugins::WanderlustPlugin) instead. +/// Alternatively, if one only wants to disable the system, use [`WanderlustPhysicsTweaks`](WanderlustPhysicsTweaks).* +/// +/// This system adds some tweaks to rapier's physics settings that make the character controller behave better. +pub fn setup_physics_context(mut ctx: ResMut) { + let params = &mut ctx.integration_parameters; + // This prevents any noticeable jitter when running facefirst into a wall. + params.erp = 0.99; + // This prevents (most) noticeable jitter when running facefirst into an inverted corner. + params.max_velocity_iterations = 16; + // TODO: Fix jitter that occurs when running facefirst into a normal corner. +} + */ \ No newline at end of file diff --git a/src/backend/rapier/query.rs b/src/backend/rapier/query.rs new file mode 100644 index 0000000..69ac2ca --- /dev/null +++ b/src/backend/rapier/query.rs @@ -0,0 +1,5 @@ + +use bevy::prelude::*; +use super::rapier::prelude::*; + +pub type SpatialQuery<'w, 's> = Res<'s, RapierContext>; \ No newline at end of file diff --git a/src/backend/rapier/velocity.rs b/src/backend/rapier/velocity.rs new file mode 100644 index 0000000..07d9726 --- /dev/null +++ b/src/backend/rapier/velocity.rs @@ -0,0 +1,21 @@ + +use bevy::{ + prelude::*, + ecs::query::WorldQuery, +}; +use super::rapier; + +#[derive(WorldQuery)] +pub struct Velocity { + velocity: &'static rapier::prelude::Velocity, +} + +impl<'a> VelocityItem<'a> { + pub fn linear(&self) -> Vec3 { + self.velocity.linvel + } + + pub fn angular(&self) -> Vec3 { + self.velocity.angvel + } +} \ No newline at end of file diff --git a/src/backend/xpbd/mass.rs b/src/backend/xpbd/mass.rs new file mode 100644 index 0000000..47bed3b --- /dev/null +++ b/src/backend/xpbd/mass.rs @@ -0,0 +1,27 @@ + +use bevy::{ + ecs::query::WorldQuery, + prelude::*, +}; +use super::xpbd; + +#[derive(WorldQuery)] +pub struct Mass { + mass: &'static xpbd::prelude::Mass, + inertia: &'static xpbd::prelude::Inertia, + center_of_mass: &'static xpbd::prelude::CenterOfMass, +} + +impl<'a> MassItem<'a> { + pub fn mass(&self) -> f32 { + self.mass.0 + } + + pub fn inertia(&self) -> Mat3 { + self.inertia.0 + } + + pub fn local_center_of_mass(&self) -> Vec3 { + self.center_of_mass.0 + } +} \ No newline at end of file diff --git a/src/backend/xpbd/mod.rs b/src/backend/xpbd/mod.rs new file mode 100644 index 0000000..afd996c --- /dev/null +++ b/src/backend/xpbd/mod.rs @@ -0,0 +1,82 @@ + +use bevy::prelude::*; + +#[cfg(feature = "xpbd_3d")] +pub use bevy_xpbd_3d as xpbd; +#[cfg(feature = "xpbd_2d")] +pub use bevy_xpbd_2d as xpbd; + +use xpbd::prelude::*; + +mod mass; +pub use mass::*; +mod velocity; +pub use velocity::*; + +pub use xpbd::prelude::Collider; + +/// Contains common physics settings for character controllers. +#[derive(Bundle)] +pub struct XpbdPhysicsBundle { + /// See [`RigidBody`]. + pub rigidbody: RigidBody, + /// See [`Collider`]. + pub collider: Collider, + /// See [`GravityScale`]. + pub gravity: GravityScale, + /// See [`Friction`]. + pub friction: Friction, + /// See [`Restitution`]. + pub restitution: Restitution, +} + +impl Default for XpbdPhysicsBundle { + fn default() -> Self { + Self { + rigidbody: default(), + collider: Collider::capsule_endpoints( + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 0.5, 0.0), + 0.5, + ), + gravity: GravityScale(0.0), + friction: Friction::new(0.0).with_combine_rule(CoefficientCombine::Min), + restitution: Restitution::new(0.0).with_combine_rule(CoefficientCombine::Min), + } + } +} + +pub fn apply_forces() {} +pub fn apply_ground_forces() {} +pub fn setup_physics_context() {} + +pub type SpatialQuery<'w, 's> = xpbd::prelude::SpatialQuery<'w, 's>; + +use crate::backend::{RayCastResult, Filter}; +pub fn cast_ray( + spatial_query: &SpatialQuery, + origin: Vec3, + direction: Vec3, + max_toi: f32, + solid: bool, + filter: Filter, +) -> Option { + spatial_query.cast_ray( + origin, + direction, + max_toi, + solid, + SpatialQueryFilter { + excluded_entities: filter.exclude, + ..default() + }, + ).map(|result| { + let point = origin + direction * result.time_of_impact; + RayCastResult { + entity: result.entity, + normal: result.normal, + point: point, + toi: result.time_of_impact, + } + }) +} diff --git a/src/backend/xpbd/velocity.rs b/src/backend/xpbd/velocity.rs new file mode 100644 index 0000000..bc12e06 --- /dev/null +++ b/src/backend/xpbd/velocity.rs @@ -0,0 +1,22 @@ + +use bevy::{ + prelude::*, + ecs::query::WorldQuery, +}; +use super::xpbd; + +#[derive(WorldQuery)] +pub struct Velocity { + linear: &'static xpbd::prelude::LinearVelocity, + angular: &'static xpbd::prelude::AngularVelocity, +} + +impl<'a> VelocityItem<'a> { + pub fn linear(&self) -> Vec3 { + **self.linear + } + + pub fn angular(&self) -> Vec3 { + **self.angular + } +} \ No newline at end of file diff --git a/src/bundles.rs b/src/bundles.rs index 8af59c0..af0fcf3 100644 --- a/src/bundles.rs +++ b/src/bundles.rs @@ -1,4 +1,4 @@ -use crate::{Controller, ControllerInput, ControllerPhysicsBundle, RapierPhysicsBundle}; +use crate::{Controller, ControllerInput, ControllerPhysicsBundle}; use bevy::prelude::*; @@ -13,9 +13,8 @@ pub struct ControllerBundle { pub input: ControllerInput, /// See [`PhysicsBundle`] pub physics: ControllerPhysicsBundle, - #[cfg(feature = "rapier")] - /// See [`RapierPhysicsBundle`] - pub rapier_physics: RapierPhysicsBundle, + /// See [`BackendPhysicsBundle`]. + pub backend_physics: crate::backend::BackendPhysicsBundle, /// See [`Transform`] pub transform: Transform, /// See [`GlobalTransform`] @@ -32,8 +31,7 @@ impl Default for ControllerBundle { controller: default(), input: default(), physics: default(), - #[cfg(feature = "rapier")] - rapier_physics: default(), + backend_physics: default(), transform: default(), global_transform: default(), visibility: default(), diff --git a/src/controller/ground.rs b/src/controller/ground.rs index ca8b252..d7aa1d5 100644 --- a/src/controller/ground.rs +++ b/src/controller/ground.rs @@ -9,6 +9,8 @@ use bevy_rapier3d::{ rapier::geometry::ContactManifold, }; +use crate::backend::Collider; + /// How to detect if something below the controller is suitable /// for standing on. #[derive(Component, Reflect)] @@ -319,12 +321,10 @@ pub fn find_ground( MassProperties::default() }; - let local_com = mass.local_center_of_mass; - - let ground_velocity = velocities + let (ground_linear_vel, ground_angular_vel) = velocities .get(ground_entity) - .copied() - .unwrap_or(Velocity::default()); + .map(|velocity| (velocity.linear(), velocity.angular())) + .unwrap_or((Vec3::ZERO, Vec3::ZERO)); let global = globals .get(ground_entity) @@ -437,19 +437,23 @@ impl CastResult { impl CastResult { /// Use the first shape in the shape-cast as the cast result. pub fn from_toi1(toi: Toi) -> Option { - toi.details.map(|details| Self { - toi: toi.toi, - normal: details.normal1, - point: details.witness1, + toi.details.map(|details| { + Self { + toi: toi.toi, + normal: details.normal1, + point: details.witness1, + } }) } /// Use the second shape in the shape-cast as the cast result. pub fn from_toi2(toi: Toi) -> Option { - toi.details.map(|details| Self { - toi: toi.toi, - normal: details.normal2, - point: details.witness2, + toi.details.map(|details| { + Self { + toi: toi.toi, + normal: details.normal2, + point: details.witness2, + } }) } } @@ -669,9 +673,7 @@ impl<'c, 'f> GroundCastParams<'c, 'f> { } let (entity, cast) = (entity, CastResult::from_toi1(toi)); - let Some(cast) = cast else { - return None; - }; + let Some(cast) = cast else { return None; }; gizmos.ray(self.position, self.direction * cast.toi, Color::BLUE); gizmos.sphere( diff --git a/src/controller/mod.rs b/src/controller/mod.rs index f96c7ed..0e5f59d 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -8,6 +8,7 @@ mod movement; mod orientation; use crate::physics::*; +use crate::backend::*; use crate::Spring; pub use {gravity::*, ground::*, input::*, movement::*, orientation::*}; diff --git a/src/controller/movement.rs b/src/controller/movement.rs index 7d13868..f2b6ada 100644 --- a/src/controller/movement.rs +++ b/src/controller/movement.rs @@ -360,7 +360,7 @@ pub fn jump_force( &ControllerVelocity, &ControllerMass, )>, - ctx: Res, + time: Res