1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
#![deny(future_incompatible, nonstandard_style)] #![warn(missing_docs, rust_2018_idioms, clippy::pedantic)] #![allow(clippy::module_name_repetitions)] //! Core components and resources to use Heron use bevy::core::FixedTimestep; use bevy::prelude::*; pub use constraints::RotationConstraints; pub use ext::*; pub use gravity::Gravity; pub use velocity::{Acceleration, AxisAngle, Velocity}; mod constraints; pub mod ext; mod gravity; pub mod utils; mod velocity; /// Physics stages for user systems. These stages are executed once per physics step. /// /// That usually means they don't run each frame and may run more than once in a single frame. /// /// In general, end-users shouldn't have to deal with these stages directly. /// /// Instead, it is possible to call the [`add_physiscs_system`](ext::AppBuilderExt::add_physics_system) extension function on `AppBuilder` /// to register systems that should run during the physics update. pub mod stage { /// The root **`Schedule`** stage pub const ROOT: &str = "heron-physics"; /// A **child** `SystemStage` running before each physics step. /// /// Use this stage to modify rigid-body transforms or any other physics component. /// /// **This is not a root stage**. So you cannot simply call `add_system_to_stage` on bevy's app builder. /// Instead consider calling the [`add_physiscs_system`](crate::ext::AppBuilderExt::add_physics_system) extension function. pub const UPDATE: &str = "heron-before-step"; } /// Plugin that registers stage resources and components. /// /// It does **NOT** enable physics behavior. #[derive(Debug, Copy, Clone)] pub struct CorePlugin { /// Number of physics step per second. `None` means to run physics step as part of the application update instead. pub steps_per_second: Option<f64>, } impl Default for CorePlugin { fn default() -> Self { Self::from_steps_per_second(60) } } impl CorePlugin { /// Configure how many times per second the physics world needs to be updated /// /// # Panics /// /// Panic if the number of `steps_per_second` is 0 #[must_use] pub fn from_steps_per_second(steps_per_second: u8) -> Self { assert!( steps_per_second > 0, "Invalid number of step per second: {}", steps_per_second ); Self { steps_per_second: Some(steps_per_second.into()), } } } impl Plugin for CorePlugin { fn build(&self, app: &mut AppBuilder) { app.init_resource::<Gravity>() .register_type::<Body>() .register_type::<BodyType>() .register_type::<PhysicMaterial>() .register_type::<Velocity>() .register_type::<RotationConstraints>() .add_stage_before(CoreStage::PostUpdate, crate::stage::ROOT, { let mut schedule = Schedule::default(); if let Some(steps_per_second) = self.steps_per_second { schedule = schedule .with_run_criteria(FixedTimestep::steps_per_second(steps_per_second)) } schedule.with_stage(crate::stage::UPDATE, SystemStage::parallel()) }); } } /// Components that defines a body subject to physics and collision /// /// # Example /// /// ``` /// # use bevy::prelude::*; /// # use heron_core::*; /// fn spawn(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { /// commands.spawn_bundle(todo!("Spawn your sprite/mesh, incl. at least a GlobalTransform")) /// .insert(Body::Sphere { radius: 1.0 }); /// } /// ``` #[derive(Debug, Clone, Reflect)] pub enum Body { /// A sphere (or circle in 2d) shape defined by its radius Sphere { /// Radius of the sphere radius: f32, }, /// A capsule shape Capsule { /// Distance from the center of the capsule to the center of an hemisphere. half_segment: f32, /// Radius of the hemispheres radius: f32, }, /// A cuboid/rectangular shape Cuboid { /// The **half** extends on each axis. (x = half width, y = half height, z = half depth) /// /// In 2d the `z` axis is ignored half_extends: Vec3, }, /// A convex polygon/polyhedron shape ConvexHull { /// A vector of points describing the convex hull points: Vec<Vec3>, }, } impl Default for Body { fn default() -> Self { Self::Sphere { radius: 1.0 } } } /// Component that defines the *type* of rigid body. /// /// # Example /// /// ``` /// # use bevy::prelude::*; /// # use heron_core::*; /// fn spawn(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { /// commands.spawn_bundle(todo!("Spawn your sprite/mesh, incl. at least a GlobalTransform")) /// .insert(Body::Sphere { radius: 1.0 }) // Make a body (is dynamic by default) /// .insert(BodyType::Static); // Make it static (so that it doesn't move and is not affected by forces like gravity) /// } /// ``` #[derive(Debug, Copy, Clone, Eq, PartialEq, Reflect)] pub enum BodyType { /// A dynamic body is normally affected by physic forces and affect the other bodies normally too. /// /// This is the most "natural" type in the sense that, in real life, everything is dynamic. /// /// It is the default type. Dynamic, /// A static body is not affected by physic forces and doesn't move. But it does affect the other bodies. /// /// This effectively behaves like a dynamic body with infinite mass and zero velocity. /// /// It is well suited for terrain and static obstacles. Static, /// A kinematic body is not moved by the physics engine. But it can have user-defined velocity. /// /// It affects the other bodies normally but is not affected by them. /// /// If the transform is updated, then a velocity will be automatically calculated, producing /// realistic interaction with other bodies. /// /// It can also have a velocity be applied. /// /// It is well-suited for moving platforms. Kinematic, /// A sensor is not affected by physics forces and doesn't affect other bodies either. /// /// Other bodies will be able to penetrate the sensor. But it still participates in collision events. /// /// A sensor is useful when we are only interested in collision events. /// One may, for example, add a sensor to detect when the player reaches a certain area. Sensor, } impl Default for BodyType { fn default() -> Self { Self::Dynamic } } impl BodyType { /// Returns true if this body type can be moved by [`Velocity`] #[must_use] pub fn can_have_velocity(self) -> bool { match self { BodyType::Dynamic | BodyType::Kinematic => true, BodyType::Static | BodyType::Sensor => false, } } } /// An event fired when the collision state between two entities changed /// /// # Example /// /// ``` /// # use bevy::prelude::*; /// # use heron_core::*; /// fn detect_collisions(mut events: EventReader<CollisionEvent>) { /// for event in events.iter() { /// match event { /// CollisionEvent::Started(entity1, entity2) => println!("Entity {:?} and {:?} started to collide", entity1, entity2), /// CollisionEvent::Stopped(entity1, entity2) => println!("Entity {:?} and {:?} stopped to collide", entity1, entity2), /// } /// } /// } /// ``` #[derive(Debug, Copy, Clone, PartialEq)] pub enum CollisionEvent { /// The two entities started to collide Started(Entity, Entity), /// The two entities no longer collide Stopped(Entity, Entity), } /// Component that defines the physics properties of the rigid body /// /// # Example /// /// ``` /// # use bevy::prelude::*; /// # use heron_core::*; /// fn spawn(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) { /// commands.spawn_bundle(todo!("Spawn your sprite/mesh, incl. at least a GlobalTransform")) /// .insert(Body::Sphere { radius: 1.0 }) // Make a body (is dynamic by default) /// .insert(PhysicMaterial { /// restitution: 0.5, // Define the restitution. Higher value means more "bouncy" /// density: 2.0, // Define the density. Higher value means heavier. /// friction: 0.5, // Define the friction. Higher value means higher friction. /// }); /// } /// ``` #[derive(Debug, Copy, Clone, PartialEq, Reflect)] pub struct PhysicMaterial { /// Coefficient of restitution. Affect how much it "bounces" when colliding with other objects. /// /// The higher the value, the more "bouncy". /// /// Typical values are between 0 (perfectly inelastic) and 1 (perfectly elastic) pub restitution: f32, /// Density. It affects how much the body resists forces. /// /// The higher the value, the heavier. /// /// Value must be greater than 0. Except for sensor and static bodies, in which case the value is ignored. pub density: f32, /// Friction. It affects the relative motion of two bodies in contact. /// /// The higher the value, the higher friction. /// /// Typical values are between 0 (ideal) and 1 (max friction) pub friction: f32, } impl PhysicMaterial { /// Perfectly inelastic restitution coefficient, all kinematic energy is lost on collision. (Do not bounce at all) pub const PERFECTLY_INELASTIC_RESTITUTION: f32 = 0.0; /// Perfectly elastic restitution coefficient, all kinematic energy is restated in movement. (Very bouncy) pub const PERFECTLY_ELASTIC_RESTITUTION: f32 = 1.0; } impl Default for PhysicMaterial { fn default() -> Self { Self { restitution: Self::PERFECTLY_INELASTIC_RESTITUTION, density: 1.0, friction: 0.0, } } }