commit 9cc60f724946f91bbe1b917d3a0b394054a5c6ce
parent 564580643eb36c3f7e5397ce3bc604f98ca889f8
Author: Benjamin Amling <b.amling@tarent.de>
Date: Thu, 30 May 2019 11:36:39 +0200
Initial commit
Diffstat:
15 files changed, 23 insertions(+), 1652 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,5 +0,0 @@
-/target
-**/*.rs.bk
-*.iml
-/.idea
-Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,21 +0,0 @@
-[package]
-name = "nphysics-ecs-dumb"
-version = "0.1.0"
-authors = ["kel <distransient@protonmail.com>"]
-edition = "2018"
-
-#[features]
-# default = [ "nphysics3d" ]
-
-[dependencies]
-num-traits = "0.2"
-derive-new = "0.5.6"
-derive_builder = "0.7.0"
-serde = { version = "1.0", features = ["derive"] }
-log = "*"
-amethyst = { git = "https://github.com/amethyst/amethyst", branch = "master", features = ["nightly"] }
-
-#sertmp = ser branch + ncollide3d override to changes branch
-nphysics3d = { git = "https://github.com/jojolepro/nphysics", branch = "sertmp", features = ["serde"] }
-ncollide3d = { git = "https://github.com/jojolepro/ncollide", branch = "changes" }
-nalgebra = "0.17"
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Benjamin Amling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
@@ -1,50 +1,2 @@
-# Nphysics - Amethyst connector
-
-Don't use. Work in progress. Many things are incomplete!
-
-Currently specific to 3d Nphysics and Amethyst due to Amethyst handling of Transforms and to keep iteration quick,
-although I currently plan on allowing 3d and 2d nphysics implementations to be used to configuration settings, and
-both amethyst and plain specs interfaces to be exposed, also behind configuration settings.
-
-## System Sequence
-
-I'll update this as I go along.
-
-1.
- - `"sync_bodies_to_physics_system"` - Synchronize changes to dynamics bodies to physics world
- - `"sync_gravity_to_physics_system"` - Update gravity of physics world from resource
-1. `"sync_colliders_to_physics_system"` - Synchronize collision items to physics world
-1. `"physics_stepper_system"` - Step physics world simulation
-1. `"sync_bodies_from_physics_system"` - Synchronize physics world changes back to components
-
-
-## Current Roadmap
-
-Full *TODO* sheet can be found in [this nphysics issue][todo]
-
-Ongoing work:
-
-- [x] RigidBody Components
-- [x] Collider Components [#2]
-- [x] Proximity and Contact EventChannels [#2]
-- [x] External force property [#3]
-- [x] `log` based logging [#4]
-- [ ] Handling Body Activation & Sleeping [#9]
-- [ ] Multibody-based Component Joints [#10]
-- [ ] Force generator inversion of control [#11]
-- [ ] Time scale and simulation pausing [#12]
-
-Investigating:
-
-- [ ] Proximity & Curve-based external force utility
-- [ ] Constraint-based Joints
-- [ ] Kinematics
-
-[todo]: https://github.com/rustsim/nphysics/issues/149
-[#2]: https://github.com/distransient/nphysics-ecs-dumb/pull/2
-[#3]: https://github.com/distransient/nphysics-ecs-dumb/pull/3
-[#4]: https://github.com/distransient/nphysics-ecs-dumb/pull/4
-[#9]: https://github.com/distransient/nphysics-ecs-dumb/issues/9
-[#10]: https://github.com/distransient/nphysics-ecs-dumb/issues/10
-[#11]: https://github.com/distransient/nphysics-ecs-dumb/issues/11
-[#12]: https://github.com/distransient/nphysics-ecs-dumb/issues/12
+# specs_nphysics
+nphysics integration for the Specs entity component system
diff --git a/examples/amethyst.rs b/examples/amethyst.rs
@@ -1,264 +0,0 @@
-use amethyst::assets::{Handle, Loader};
-use amethyst::core::math::{Matrix3, Vector3};
-use amethyst::core::shrev::{EventChannel, ReaderId};
-use amethyst::core::ecs::world::Builder;
-use amethyst::core::ecs::Join;
-use amethyst::core::{GlobalTransform, Transform, TransformBundle};
-use amethyst::input::{is_close_requested, is_key_down};
-use amethyst::renderer::{
- AmbientColor, Camera, DisplayConfig, DrawShaded, Light, Material, MaterialDefaults, MeshData,
- MeshHandle, Pipeline, PointLight, PosNormTex, RenderBundle, Rgba, ScreenDimensions, Shape,
- Stage, Texture, VirtualKeyCode,
-};
-use amethyst::{
- Application, GameData, GameDataBuilder, SimpleState, SimpleTrans, StateData, StateEvent, Trans,
-};
-use nphysics_ecs_dumb::ncollide::shape::{Ball, ShapeHandle};
-use nphysics_ecs_dumb::nphysics::material::BasicMaterial as PhysicsMaterial;
-use nphysics_ecs_dumb::nphysics::math::Velocity;
-use nphysics_ecs_dumb::nphysics::volumetric::Volumetric;
-use nphysics_ecs_dumb::*;
-use num_traits::identities::One;
-use std::time::Duration;
-
-#[derive(Default)]
-struct GameState {
- pub collision_reader: Option<ReaderId<EntityContactEvent>>,
-}
-
-impl SimpleState for GameState {
- fn on_start(&mut self, data: StateData<GameData>) {
- data.world.register::<DynamicBody>();
- data.world.register::<MeshData>();
- data.world.register::<Handle<Texture>>();
-
- // Create a texture for using.
- let texture = data
- .world
- .read_resource::<Loader>()
- .load_from_data::<Texture, ()>(
- [170.0, 170.0, 255.0, 1.0].into(),
- (),
- &data.world.read_resource(),
- );
-
- let material = Material {
- albedo: texture,
- ..data.world.read_resource::<MaterialDefaults>().0.clone()
- };
-
- // Get resolution of the screen.
- let (x, y) = {
- let resolution = data.world.res.fetch::<ScreenDimensions>();
- (resolution.width(), resolution.height())
- };
-
- let camera_transform = Transform::from(Vector3::new(0.0, 5.0, 5.0));
-
- self.collision_reader = Some(
- data.world
- .write_resource::<EventChannel<EntityContactEvent>>()
- .register_reader(),
- );
-
- // Add Camera
- data.world
- .create_entity()
- .with(Camera::standard_3d(x, y))
- .with(camera_transform)
- .build();
-
- // Add Light
- data.world.add_resource(AmbientColor(Rgba::from([0.2; 3])));
- data.world
- .create_entity()
- .with(Light::Point(PointLight {
- intensity: 50.0,
- color: Rgba::white(),
- radius: 5.0,
- smoothness: 4.0,
- }))
- .with(Transform::from(Vector3::new(2.0, 2.0, -2.0)))
- .build();
-
- let sphere_shape = Shape::Sphere(32, 32).generate::<Vec<PosNormTex>>(None);
- let sphere_handle: MeshHandle = data.world.read_resource::<Loader>().load_from_data(
- sphere_shape,
- (),
- &data.world.read_resource(),
- );
-
- let ball = ShapeHandle::new(Ball::new(1.0));
-
- // Add Sphere (todo: add many, add rigidbodies and colliders)
- data.world
- .create_entity()
- .with(sphere_handle.clone())
- .with(material.clone())
- .with(Transform::from(Vector3::new(0.0, 15.0, -10.0)))
- .with(GlobalTransform::default())
- .with(DynamicBody::new_rigidbody_with_velocity(
- Velocity::linear(0.0, 1.0, 0.0),
- 10.0,
- Matrix3::one(),
- ball.center_of_mass(),
- ))
- .with(
- ColliderBuilder::from(ball.clone())
- .physics_material(PhysicsMaterial::default())
- .build()
- .unwrap(),
- )
- .build();
-
- // Add ground
- data.world
- .create_entity()
- .with(sphere_handle)
- .with(material)
- .with(Transform::from(Vector3::new(0.0, 0.0, -10.0)))
- .with(GlobalTransform::default())
- .with(
- //ColliderBuilder::from(ShapeHandle::new(Cuboid::new(Vector3::new(5.0, 1.0, 5.0))))
- ColliderBuilder::from(ball)
- .physics_material(PhysicsMaterial::default())
- .build()
- .unwrap(),
- )
- .build();
-
- //---------------------------------------------------- nphysics's ball3.rs adapted
-
- /*let mut physics_world = data.world.write_resource::<PhysicsWorld>();
- physics_world.set_gravity(Vector3::new(0.0, -9.81, 0.0));
-
- // Material for all objects.
- let material = PhysicsMaterial::default();
- let ground_shape =
- ShapeHandle::new(Cuboid::new(Vector3::repeat(1.0 - 0.01)));
- let ground_pos = Isometry3::new(Vector3::new(0.0, -0.5, -15.0), nalgebra::zero());
-
- physics_world.add_collider(
- 0.01,
- ground_shape,
- BodyHandle::ground(),
- ground_pos,
- material.clone(),
- );
- let geom = ShapeHandle::new(Ball::new(1.0 - 0.01));
- let inertia = geom.inertia(1.0);
- let center_of_mass = geom.center_of_mass();
-
-
- let pos = Isometry3::new(Vector3::new(0.0, 5.0, -15.0), nalgebra::zero());
- let handle = physics_world.add_rigid_body(pos, inertia, center_of_mass);
- physics_world.add_collider(
- 0.01,
- geom.clone(),
- handle,
- Isometry3::identity(),
- material.clone(),
- );*/
- }
-
- fn handle_event(
- &mut self,
- data: StateData<'_, GameData<'_, '_>>,
- event: StateEvent,
- ) -> SimpleTrans {
- if let StateEvent::Window(event) = &event {
- for _ in data
- .world
- .read_resource::<EventChannel<EntityContactEvent>>()
- .read(self.collision_reader.as_mut().unwrap())
- {
- println!("Collision Event Detected.");
- }
-
- // Exit if user hits Escape or closes the window
- if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) {
- return Trans::Quit;
- }
-
- //
- if is_key_down(&event, VirtualKeyCode::T) {
- *data.world.write_resource::<TimeStep>() = TimeStep::Fixed(1. / 120.);
- println!("Setting timestep to 1./120.");
- }
-
- if is_key_down(&event, VirtualKeyCode::Y) {
- *data.world.write_resource::<TimeStep>() = TimeStep::Fixed(1. / 60.);
- println!("Setting timestep to 1./60.");
- }
-
- if is_key_down(&event, VirtualKeyCode::S) {
- *data.world.write_resource::<TimeStep>() =
- TimeStep::SemiFixed(TimeStepConstraint::new(
- vec![1. / 240., 1. / 120., 1. / 60.],
- 0.4,
- Duration::from_millis(50),
- Duration::from_millis(500),
- ))
- }
-
- // Reset the example
- if is_key_down(&event, VirtualKeyCode::Space) {
- *(
- &mut data.world.write_storage::<Transform>(),
- &data.world.read_storage::<DynamicBody>(),
- )
- .join()
- .next()
- .unwrap()
- .0
- .translation_mut() = Vector3::new(0.0, 15.0, -10.0);
- }
- }
- Trans::None
- }
-}
-
-fn main() -> amethyst::Result<()> {
- amethyst::start_logger(Default::default());
-
- let display_config = DisplayConfig {
- title: "Amethyst + Nphysics".to_string(),
- fullscreen: false,
- dimensions: Some((800, 400)),
- min_dimensions: Some((800, 400)),
- max_dimensions: None,
- icon: None,
- vsync: true,
- multisampling: 0, // Must be multiple of 2, use 0 to disable
- visibility: true,
- always_on_top: false,
- decorations: true,
- maximized: false,
- multitouch: true,
- resizable: true,
- transparent: false,
- loaded_icon: None,
- };
- let pipe = Pipeline::build().with_stage(
- Stage::with_backbuffer()
- .clear_target([0.1, 0.1, 0.1, 1.0], 1.0)
- .with_pass(DrawShaded::<PosNormTex>::new()),
- );
-
- let game_data = GameDataBuilder::default()
- .with_bundle(TransformBundle::new())?
- .with_bundle(
- PhysicsBundle::new()
- .with_dep(&["transform_system"])
- .with_timestep_iter_limit(20),
- )?
- .with_bundle(RenderBundle::new(pipe, Some(display_config)))?;
-
- let application = Application::new("./", GameState::default(), game_data);
-
- assert_eq!(application.is_ok(), true);
-
- application.ok().unwrap().run();
-
- Ok(())
-}
diff --git a/src/bodies.rs b/src/bodies.rs
@@ -1,67 +0,0 @@
-use amethyst::ecs::{Component, FlaggedStorage};
-use nalgebra::Matrix3;
-use nphysics3d::math::{Force, Point, Velocity};
-use nphysics3d::object::{BodyHandle, BodyStatus};
-
-/// Rigid physics body, for use in `PhysicsBody` Component.
-/// Currently only the velocity is read and updated at runtime.
-/// The properties of mass are only written at physics body creation time.
-#[derive(Serialize, Deserialize, Clone, Copy, Debug, new)]
-pub struct DynamicBody {
- #[serde(skip)]
- #[new(default)]
- pub(crate) handle: Option<BodyHandle>,
- pub velocity: Velocity<f32>,
-
- // TODO: update these in the physics system below.
- pub mass: f32,
- pub angular_mass: Matrix3<f32>,
- pub center_of_mass: Point<f32>,
-
- pub external_forces: Force<f32>,
- #[serde(skip)]
- pub body_status: BodyStatus,
-}
-
-impl DynamicBody {
- pub fn new_rigidbody(
- mass: f32,
- angular_mass: Matrix3<f32>,
- center_of_mass: Point<f32>,
- ) -> Self {
- DynamicBody {
- handle: None,
- velocity: Velocity::<f32>::zero(),
- mass,
- angular_mass,
- center_of_mass,
- external_forces: Force::<f32>::zero(),
- body_status: BodyStatus::Dynamic,
- }
- }
-
- pub fn new_rigidbody_with_velocity(
- velocity: Velocity<f32>,
- mass: f32,
- angular_mass: Matrix3<f32>,
- center_of_mass: Point<f32>,
- ) -> Self {
- DynamicBody {
- handle: None,
- velocity,
- mass,
- angular_mass,
- center_of_mass,
- external_forces: Force::<f32>::zero(),
- body_status: BodyStatus::Dynamic,
- }
- }
-
- pub fn handle(&self) -> Option<BodyHandle> {
- self.handle
- }
-}
-
-impl Component for DynamicBody {
- type Storage = FlaggedStorage<Self>;
-}
diff --git a/src/colliders.rs b/src/colliders.rs
@@ -1,74 +0,0 @@
-use amethyst::ecs::{Component, DenseVecStorage, FlaggedStorage};
-use nalgebra::Isometry3;
-use ncollide::{shape::ShapeHandle, world::GeometricQueryType};
-use ncollide3d::world::CollisionGroups;
-use nphysics::material::BasicMaterial;
-use nphysics::object::ColliderHandle;
-
-#[derive(Clone, Serialize, Deserialize, Debug, new)]
-pub enum ColliderType {
- Collider,
- Trigger,
-}
-
-impl Default for ColliderType {
- fn default() -> Self {
- ColliderType::Collider
- }
-}
-
-impl ColliderType {
- pub fn to_geometric_query_type(
- &self,
- margin: f32,
- prediction: f32,
- angular_prediction: f32,
- ) -> GeometricQueryType<f32> {
- match *self {
- ColliderType::Collider => {
- GeometricQueryType::Contacts(margin + prediction * 0.5, angular_prediction)
- }
- ColliderType::Trigger => GeometricQueryType::Proximity(prediction * 0.5),
- }
- }
-}
-
-#[derive(new, Clone, Builder)]
-#[builder(pattern = "owned")]
-pub struct Collider {
- #[new(default)]
- //#[serde(skip)]
- #[builder(default)]
- pub(crate) handle: Option<ColliderHandle>,
- /// Warning: Changing the margin after inserting the entity will have no effect.
- pub margin: f32,
- pub shape: ShapeHandle<f32>,
- pub offset_from_parent: Isometry3<f32>,
- pub physics_material: BasicMaterial<f32>,
- pub collision_group: CollisionGroups,
- pub query_type: ColliderType,
-}
-
-impl From<ShapeHandle<f32>> for ColliderBuilder {
- fn from(shape: ShapeHandle<f32>) -> ColliderBuilder {
- ColliderBuilder::default()
- .margin(0.01)
- .shape(shape)
- .offset_from_parent(Isometry3::identity())
- .query_type(ColliderType::default())
- .physics_material(BasicMaterial::default())
- .collision_group(CollisionGroups::default())
- }
-}
-
-impl ColliderBuilder {
- pub fn trigger(mut self) -> Self {
- // query type = trigger
- self.query_type = Some(ColliderType::Trigger);
- self
- }
-}
-
-impl Component for Collider {
- type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>;
-}
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,31 +0,0 @@
-//! nphysics-ecs-dumb
-//! Straight forward wrapper around nphysics to allow its usage inside of Amethyst's ECS: Specs
-
-pub extern crate ncollide3d as ncollide;
-pub extern crate nphysics3d as nphysics;
-#[macro_use]
-extern crate derive_builder;
-#[macro_use]
-extern crate derive_new;
-#[macro_use]
-extern crate serde;
-
-#[macro_use]
-extern crate log;
-
-pub mod bodies;
-pub mod colliders;
-pub mod systems;
-pub mod time_step;
-
-pub use self::bodies::*;
-pub use self::colliders::*;
-pub use self::systems::*;
-pub use self::time_step::*;
-
-/// The Physical World containing all physical objects.
-pub type PhysicsWorld = self::nphysics::world::World<f32>;
-
-/// Gravity is a type alias for a Vector of dimension 3.
-/// It represents a constant acceleration affecting all physical objects in the scene.
-pub type Gravity = self::nphysics::math::Vector<f32>;
diff --git a/src/systems/mod.rs b/src/systems/mod.rs
@@ -1,92 +0,0 @@
-mod physics_stepper;
-mod sync_bodies_from_physics;
-mod sync_bodies_to_physics;
-mod sync_colliders_to_physics;
-mod sync_gravity_to_physics;
-
-use amethyst::core::bundle::SystemBundle;
-use amethyst::core::ecs::DispatcherBuilder;
-use amethyst::error::Error;
-use core::result::Result;
-
-pub use self::physics_stepper::*;
-pub use self::sync_bodies_from_physics::*;
-pub use self::sync_bodies_to_physics::SyncBodiesToPhysicsSystem;
-pub use self::sync_colliders_to_physics::SyncCollidersToPhysicsSystem;
-pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem;
-
-pub const SYNC_BODIES_TO_PHYSICS_SYSTEM: &str = "sync_bodies_to_physics_system";
-pub const SYNC_GRAVITY_TO_PHYSICS_SYSTEM: &str = "sync_gravity_to_physics_system";
-pub const SYNC_COLLIDERS_TO_PHYSICS_SYSTEM: &str = "sync_colliders_to_physics_system";
-pub const PHYSICS_STEPPER_SYSTEM: &str = "physics_stepper_system";
-pub const SYNC_BODIES_FROM_PHYSICS_SYSTEM: &str = "sync_bodies_from_physics_system";
-
-pub struct PhysicsBundle<'a> {
- dep: &'a [&'a str],
- timestep_iter_limit: i32,
-}
-
-impl Default for PhysicsBundle<'_> {
- fn default() -> Self {
- Self {
- dep: Default::default(),
- timestep_iter_limit: 10,
- }
- }
-}
-
-impl<'a> PhysicsBundle<'a> {
- pub fn new() -> Self {
- Default::default()
- }
-
- pub fn with_dep(mut self, dep: &'a [&'a str]) -> Self {
- self.dep = dep;
- self
- }
-
- /// Set the maximum number of physics timesteps to simulate in a single run of the `PhysicsStepperSystem`
- pub fn with_timestep_iter_limit(mut self, timestep_iter_limit: i32) -> Self {
- self.timestep_iter_limit = timestep_iter_limit;
- self
- }
-}
-
-impl<'a, 'b, 'c> SystemBundle<'a, 'b> for PhysicsBundle<'c> {
- fn build(self, builder: &mut DispatcherBuilder<'a, 'b>) -> Result<(), Error> {
- builder.add(
- SyncBodiesToPhysicsSystem::new(),
- SYNC_BODIES_TO_PHYSICS_SYSTEM,
- self.dep,
- );
- builder.add(
- SyncGravityToPhysicsSystem::new(),
- SYNC_GRAVITY_TO_PHYSICS_SYSTEM,
- self.dep,
- );
-
- builder.add(
- SyncCollidersToPhysicsSystem::new(),
- SYNC_COLLIDERS_TO_PHYSICS_SYSTEM,
- &[SYNC_BODIES_TO_PHYSICS_SYSTEM],
- );
-
- builder.add(
- PhysicsStepperSystem::new(self.timestep_iter_limit),
- PHYSICS_STEPPER_SYSTEM,
- &[
- SYNC_BODIES_TO_PHYSICS_SYSTEM,
- SYNC_GRAVITY_TO_PHYSICS_SYSTEM,
- SYNC_COLLIDERS_TO_PHYSICS_SYSTEM,
- ],
- );
-
- builder.add(
- SyncBodiesFromPhysicsSystem::new(),
- SYNC_BODIES_FROM_PHYSICS_SYSTEM,
- &[PHYSICS_STEPPER_SYSTEM],
- );
-
- Ok(())
- }
-}
diff --git a/src/systems/physics_stepper.rs b/src/systems/physics_stepper.rs
@@ -1,229 +0,0 @@
-use crate::time_step::TimeStep;
-use crate::PhysicsWorld;
-use amethyst::core::Time;
-use amethyst::ecs::{Entity, Read, System, Write, WriteExpect};
-use amethyst::shrev::EventChannel;
-use ncollide3d::events::{ContactEvent, ProximityEvent};
-use std::f32::EPSILON;
-use std::time::Instant;
-
-// TODO: why is this here
-// Might want to replace by better types.
-pub type EntityContactEvent = (Entity, Entity, ContactEvent);
-pub type EntityProximityEvent = (Entity, Entity, ProximityEvent);
-
-/// Falloff factor for calculating the moving average step time.
-const AVERAGE_STEP_TIME_FALLOFF: f32 = 0.33;
-/// Factor to apply to available physics time before decreasing the timestep. Makes sure that the
-/// timestep isn't switched too eagerly.
-const TIME_STEP_DECREASE_HYSTERESIS: f32 = 1.5;
-
-/// Simulates a step of the physics world.
-pub struct PhysicsStepperSystem {
- timestep_iter_limit: i32,
- time_accumulator: f32,
- avg_step_time: Option<f32>,
-}
-
-impl Default for PhysicsStepperSystem {
- fn default() -> Self {
- PhysicsStepperSystem {
- timestep_iter_limit: 10,
- time_accumulator: 0.,
- avg_step_time: None,
- }
- }
-}
-
-impl PhysicsStepperSystem {
- pub fn new(timestep_iter_limit: i32) -> Self {
- PhysicsStepperSystem {
- timestep_iter_limit,
- time_accumulator: 0.,
- avg_step_time: None,
- }
- }
-}
-
-impl<'a> System<'a> for PhysicsStepperSystem {
- type SystemData = (
- WriteExpect<'a, PhysicsWorld>,
- Read<'a, Time>,
- Write<'a, TimeStep>,
- Write<'a, EventChannel<EntityContactEvent>>,
- Write<'a, EventChannel<EntityProximityEvent>>,
- );
-
- // Simulate world using the current time frame
- fn run(&mut self, data: Self::SystemData) {
- let (
- mut physical_world,
- time,
- mut intended_timestep,
- mut contact_events,
- mut proximity_events,
- ) = data;
-
- let (timestep, mut change_timestep) = match &mut *intended_timestep {
- TimeStep::Fixed(timestep) => (*timestep, false),
- TimeStep::SemiFixed(constraint) => {
- let mut timestep = (constraint.current_timestep(), false);
- if let Some(avg_step) = self.avg_step_time {
- // If the timestep is smaller than it takes to simulate that step, we have a problem.
- // As simulated time is affected by the time scale, simulated time step / time scale
- // is the maximum real time the step may take, so we take that into account here. We
- // also take into account the maximum fraction of time physics are allowed to take
- let adjusted_step_time =
- avg_step * time.time_scale() / constraint.max_physics_time_fraction();
- constraint.set_running_slow(constraint.current_timestep() < adjusted_step_time);
- if constraint.should_increase_timestep() {
- match constraint.increase_timestep() {
- Err(error) => {
- warn!("Failed to increase physics timestep! Error: {}", error);
- }
- Ok(new_timestep) => {
- info!("Increasing physics timestep to {:.8} seconds", new_timestep);
- timestep = (new_timestep, true);
- }
- }
- } else if let Some(smaller_timestep) = constraint.smaller_timestep() {
- // Check if we have enough time to simulate with a smaller timestep.
- constraint.set_running_fast(
- smaller_timestep > adjusted_step_time * TIME_STEP_DECREASE_HYSTERESIS,
- );
- if constraint.should_decrease_timestep() {
- match constraint.decrease_timestep() {
- Err(error) => {
- warn!("Failed to decrease physics timestep! Error: {}", error);
- }
- Ok(new_timestep) => {
- info!(
- "Decreasing physics timestep to {:.8} seconds",
- new_timestep
- );
- timestep = (new_timestep, true);
- }
- }
- }
- }
- }
- timestep
- }
- };
-
- if (physical_world.timestep() - timestep).abs() > EPSILON && !change_timestep {
- warn!("Physics world timestep out of sync with intended timestep! Physics timestep: {}, Requested timestep: {}", physical_world.timestep(), timestep);
- change_timestep = true;
- }
-
- if change_timestep {
- trace!("Changing physics timestep to {}", timestep);
- // reset average when changing timestep
- self.avg_step_time = None;
- physical_world.set_timestep(timestep);
- }
-
- self.time_accumulator += time.delta_seconds();
- let mut steps = 0;
-
- while steps <= self.timestep_iter_limit && self.time_accumulator >= timestep {
- let physics_time = Instant::now();
-
- trace!(
- "Stepping physics system. Step: {}, Timestep: {}, Time accumulator: {}",
- steps,
- timestep,
- self.time_accumulator
- );
-
- physical_world.step();
-
- trace!("iterating collision events.");
-
- let collision_world = physical_world.collider_world();
-
- let contact_ev = collision_world.contact_events().iter().cloned().flat_map(|ev| {
- trace!("Emitting contact event: {:?}", ev);
-
- let (handle1, handle2) = match ev {
- ContactEvent::Started(h1, h2) => (h1, h2),
- ContactEvent::Stopped(h1, h2) => (h1, h2),
- };
- let coll1 = physical_world.collider(handle1);
- let coll2 = physical_world.collider(handle2);
- if let (Some(c1), Some(c2)) = (coll1, coll2) {
- // TODO: Check if the data is in fact the one we want. There might be
- // user-inserted one.
- let e1 = c1.user_data().map(|data| data.downcast_ref::<Entity>().unwrap());
- let e2 = c2.user_data().map(|data| data.downcast_ref::<Entity>().unwrap());
- if let (Some(e1), Some(e2)) = (e1, e2) {
- Some((*e1, *e2, ev))
- } else {
- error!("Failed to find entity for collider during proximity event iteration. Was the entity removed?");
- None
- }
- } else {
- error!("Failed to fetch the rigid body from the physical world using the collider handle of the collision event. Was the entity removed?.");
- None
- }
- }).collect::<Vec<_>>();
-
- contact_events.iter_write(contact_ev.into_iter());
-
- let proximity_ev = collision_world
- .proximity_events()
- .iter()
- .cloned()
- .flat_map(|ev| {
- trace!("Emitting proximity event: {:?}", ev);
- println!("hello there");
- let coll1 = physical_world.collider(ev.collider1);
- let coll2 = physical_world.collider(ev.collider2);
- if let (Some(c1), Some(c2)) = (coll1, coll2) {
- // TODO: Check if the data is in fact the one we want. There might be
- // user-inserted one.
- let e1 = c1.user_data().map(|data| data.downcast_ref::<Entity>().unwrap());
- let e2 = c2.user_data().map(|data| data.downcast_ref::<Entity>().unwrap());
- if let (Some(e1), Some(e2)) = (e1, e2) {
- Some((*e1, *e2, ev))
- } else {
- error!("Failed to find entity for collider during proximity event iteration. Was the entity removed?");
- None
- }
- } else {
- error!("Failed to fetch the rigid body from the physical world using the collider handle of the collision event. Was the entity removed?.");
- None
- }
- }).collect::<Vec<_>>();
-
- proximity_events.iter_write(proximity_ev.into_iter());
-
- let physics_time = physics_time.elapsed();
- let physics_time =
- physics_time.as_secs() as f32 + physics_time.subsec_nanos() as f32 * 1e-9;
- self.avg_step_time = Some(match self.avg_step_time {
- None => physics_time,
- Some(avg) => {
- // calculate exponentially weighted moving average
- // basic formula: AVG_n = alpha * value_n + (1 - alpha) * AVG_n-1
- avg + AVERAGE_STEP_TIME_FALLOFF * (physics_time - avg)
- }
- });
- self.time_accumulator -= timestep;
- steps += 1;
- }
-
- trace!(
- "Average time per physics step: {:.8} seconds",
- self.avg_step_time.unwrap_or_default()
- );
-
- if steps > self.timestep_iter_limit {
- // This shouldn't normally happen. If it does, one of the following might be true:
- // - TimeStep::Fixed was chosen too small
- // - TimeStep::SemiFixed can't increase the timestep
- // - Game itself is running slow, not leaving enough time for physics
- warn!("Physics running slow!");
- }
- }
-}
diff --git a/src/systems/sync_bodies_from_physics.rs b/src/systems/sync_bodies_from_physics.rs
@@ -1,125 +0,0 @@
-use crate::bodies::DynamicBody;
-use crate::colliders::Collider;
-use crate::PhysicsWorld;
-use amethyst::core::{GlobalTransform, Transform};
-use amethyst::ecs::world::EntitiesRes;
-use amethyst::ecs::{Entities, Entity, Join, ReadExpect, ReadStorage, System, WriteStorage};
-use nalgebra::Vector3;
-use nphysics3d::object::{Body, BodyPart, ColliderHandle};
-
-#[derive(Default)]
-pub struct SyncBodiesFromPhysicsSystem;
-
-impl SyncBodiesFromPhysicsSystem {
- pub fn new() -> Self {
- Default::default()
- }
-}
-
-impl<'a> System<'a> for SyncBodiesFromPhysicsSystem {
- type SystemData = (
- Entities<'a>,
- ReadExpect<'a, PhysicsWorld>,
- WriteStorage<'a, GlobalTransform>,
- WriteStorage<'a, DynamicBody>,
- WriteStorage<'a, Transform>,
- );
-
- fn run(&mut self, data: Self::SystemData) {
- let (
- _entities,
- physical_world,
- mut global_transforms,
- mut physics_bodies,
- mut local_transforms,
- ) = data;
-
- trace!("Synchronizing bodies from physical world.");
-
- // Apply the updated values of the simulated world to our Components
- #[allow(unused_mut)]
- for (mut global_transform, mut body, mut local_transform) in (
- &mut global_transforms,
- &mut physics_bodies,
- (&mut local_transforms).maybe(),
- )
- .join()
- {
- if let Some(updated_body_handle) = body.handle() {
- if let Some(updated_body) = physical_world.rigid_body(updated_body_handle) {
- if !updated_body.is_active() || updated_body.is_static() {
- trace!(
- "Skipping synchronizing data from non-dynamic body: {:?}",
- updated_body.handle()
- );
- continue;
- }
-
- trace!(
- "Synchronizing RigidBody from handle: {:?}",
- updated_body.handle()
- );
-
- trace!(
- "Synchronized RigidBody's updated position: {:?}",
- updated_body.position()
- );
-
- global_transform.0 = updated_body
- .position()
- .to_homogeneous()
- .prepend_nonuniform_scaling(
- &local_transform
- .as_ref()
- .map(|tr| *tr.scale())
- .unwrap_or_else(|| Vector3::new(1.0, 1.0, 1.0)),
- );
-
- if let Some(ref mut local_transform) = local_transform {
- *local_transform.isometry_mut() = *updated_body.position();
- }
-
- trace!(
- "Synchronized RigidBody's updated velocity: {:?}",
- updated_body.velocity()
- );
- body.velocity = *updated_body.velocity();
-
- trace!(
- "Synchronized RigidBody's updated inertia: {:?}",
- updated_body.inertia()
- );
- let inertia = updated_body.inertia();
- body.mass = inertia.linear;
- body.angular_mass = inertia.angular;
-
- trace!(
- "Synchronized RigidBody's updated center of mass: {:?}",
- updated_body.center_of_mass()
- );
- body.center_of_mass = updated_body.center_of_mass();
- } else {
- error!("Found body without pair in physics world!");
- }
- } else {
- error!("Found body without handle!");
- }
- }
- }
-}
-
-// TODO: why is this here
-pub fn entity_from_handle(
- entities: &EntitiesRes,
- colliders: &ReadStorage<Collider>,
- handle: ColliderHandle,
-) -> Option<Entity> {
- (&*entities, colliders)
- .join()
- .find(|(_, c)| {
- c.handle
- .expect("Collider has no handle and wasn't removed.")
- == handle
- })
- .map(|(e, _)| e)
-}
diff --git a/src/systems/sync_bodies_to_physics.rs b/src/systems/sync_bodies_to_physics.rs
@@ -1,216 +0,0 @@
-use crate::bodies::DynamicBody;
-use crate::PhysicsWorld;
-use amethyst::core::GlobalTransform;
-use amethyst::ecs::storage::{ComponentEvent, GenericReadStorage, MaskedStorage};
-use amethyst::ecs::{
- BitSet, Component, Entities, Join, ReadStorage, ReaderId, Resources, Storage, System,
- SystemData, Tracked, WriteExpect, WriteStorage,
-};
-use core::ops::Deref;
-use nalgebra::try_convert;
-use nalgebra::Isometry3;
-use nphysics3d::math::Isometry;
-
-use nphysics3d::object::{Body, RigidBodyDesc};
-
-#[derive(Default)]
-pub struct SyncBodiesToPhysicsSystem {
- transforms_reader_id: Option<ReaderId<ComponentEvent>>,
- physics_bodies_reader_id: Option<ReaderId<ComponentEvent>>,
-}
-
-impl SyncBodiesToPhysicsSystem {
- pub fn new() -> Self {
- Default::default()
- }
-}
-
-impl<'a> System<'a> for SyncBodiesToPhysicsSystem {
- type SystemData = (
- WriteExpect<'a, PhysicsWorld>,
- Entities<'a>,
- ReadStorage<'a, GlobalTransform>,
- WriteStorage<'a, DynamicBody>,
- );
-
- fn run(&mut self, data: Self::SystemData) {
- let (mut physical_world, entities, transforms, mut physics_bodies) = data;
-
- let mut inserted_transforms = BitSet::new();
- let mut modified_transforms = BitSet::new();
- let mut inserted_physics_bodies = BitSet::new();
- let mut modified_physics_bodies = BitSet::new();
-
- // Get change flag events for transforms, removing deleted ones from the physics world.
- trace!("Iterating transform storage events.");
- iterate_events(
- &transforms,
- self.transforms_reader_id.as_mut().unwrap(),
- &mut inserted_transforms,
- &mut modified_transforms,
- &mut physical_world,
- &entities,
- &physics_bodies,
- );
-
- // Get change flag events for physics bodies, removing deleted ones from the physics world.
- trace!("Iterating physics body storage events.");
- iterate_events(
- &physics_bodies,
- self.physics_bodies_reader_id.as_mut().unwrap(),
- &mut inserted_physics_bodies,
- &mut modified_physics_bodies,
- &mut physical_world,
- &entities,
- &physics_bodies,
- );
-
- // Update simulation world with the value of Components flagged as changed
- #[allow(unused_mut)]
- for (entity, transform, mut body, id) in (
- &entities,
- &transforms,
- &mut physics_bodies,
- &modified_transforms
- | &inserted_transforms
- | &modified_physics_bodies
- | &inserted_physics_bodies,
- )
- .join()
- {
- if inserted_transforms.contains(id) || inserted_physics_bodies.contains(id) {
- trace!("Detected inserted dynamics body with id {}", id);
-
- // Just inserted. Remove old one and insert new.
- if let Some(handle) = body.handle {
- if physical_world.rigid_body(handle).is_some() {
- trace!("Removing body marked as inserted that already exists with handle: {:?}", handle);
- physical_world.remove_bodies(&[handle]);
- }
- }
-
- /*body.handle = Some(physical_world.add_rigid_body(
- try_convert(transform.0).unwrap(),
- Inertia::new(body.mass, body.angular_mass),
- body.center_of_mass,
- ));*/
-
- let iso: Isometry3<f32> = try_convert(transform.0).unwrap();
-
- body.handle = Some(
- RigidBodyDesc::new()
- .position(iso)
- //.gravity_enabled(false)
- .status(body.body_status)
- //.name("my rigid body".to_owned())
- .velocity(body.velocity)
- //.angular_inertia(3.0)
- .mass(1.2)
- //.local_inertia(Inertia::new(1.0, 3.0))
- .local_center_of_mass(body.center_of_mass)
- //.sleep_threshold(None)
- //.kinematic_translations(Vector2::new(true, false))
- //.kinematic_rotation(true)
- .user_data(entity)
- .build(&mut physical_world)
- .handle(),
- );
-
- trace!("Inserted rigid body to world with values: {:?}", body);
-
- //let physical_body = physical_world.rigid_body_mut(body.handle.unwrap()).unwrap();
-
- //physical_body.set_velocity(body.velocity);
-
- // TODO
- //physical_body.apply_force(&body.external_forces);
- //body.external_forces = Force::<f32>::zero();
-
- trace!("Velocity and external forces applied, external forces reset to zero, for body with handle: {:?}", body.handle);
- } else if modified_transforms.contains(id) || modified_physics_bodies.contains(id) {
- trace!("Detected changed dynamics body with id {}", id);
- if let Some(physical_body) = physical_world.rigid_body_mut(body.handle.unwrap()) {
- // if you changed the mass properties at all... too bad!
- match try_convert(transform.0) {
- Some(p) => {
- let position: Isometry<f32> = p;
- trace!(
- "Updating rigid body in physics world with isometry: {}",
- position
- );
- physical_body.set_position(position);
-
- physical_body.set_velocity(body.velocity);
-
- // TODO
- //physical_body.apply_force(&body.external_forces);
- //body.external_forces = Force::<f32>::zero();
-
- physical_body.set_status(body.body_status);
- }
- None => error!(
- "Failed to convert entity position from `Transform` to physics systems"
- ),
- }
- }
- }
- }
- }
-
- fn setup(&mut self, res: &mut Resources) {
- Self::SystemData::setup(res);
-
- let mut transform_storage: WriteStorage<GlobalTransform> = SystemData::fetch(&res);
- self.transforms_reader_id = Some(transform_storage.register_reader());
-
- let mut physics_body_storage: WriteStorage<DynamicBody> = SystemData::fetch(&res);
- self.physics_bodies_reader_id = Some(physics_body_storage.register_reader());
- }
-}
-
-fn iterate_events<T, D, S>(
- tracked_storage: &Storage<T, D>,
- reader: &mut ReaderId<ComponentEvent>,
- inserted: &mut BitSet,
- modified: &mut BitSet,
- world: &mut PhysicsWorld,
- entities: &Entities,
- bodies: &S,
-) where
- T: Component,
- T::Storage: Tracked,
- D: Deref<Target = MaskedStorage<T>>,
- S: GenericReadStorage<Component = DynamicBody>,
-{
- let events = tracked_storage.channel().read(reader);
-
- for event in events {
- match event {
- ComponentEvent::Modified(id) => {
- modified.add(*id);
- }
- ComponentEvent::Inserted(id) => {
- inserted.add(*id);
- }
- ComponentEvent::Removed(id) => {
- match bodies.get(entities.entity(*id)) {
- Some(body) => {
- match body.handle() {
- Some(handle) => {
- trace!("Removing body with id: {}", id);
-
- world.remove_bodies(&[handle]);
- }
- None => {
- error!("Missing handle in body: {}", id);
- }
- };
- }
- None => {
- error!("Missing body with id: {}", id);
- }
- };
- }
- };
- }
-}
diff --git a/src/systems/sync_colliders_to_physics.rs b/src/systems/sync_colliders_to_physics.rs
@@ -1,232 +0,0 @@
-use crate::bodies::DynamicBody;
-use crate::Collider;
-use crate::PhysicsWorld;
-use amethyst::core::Transform;
-use amethyst::ecs::storage::{ComponentEvent, GenericReadStorage, MaskedStorage};
-use amethyst::ecs::{
- BitSet, Component, Entities, Join, ReadStorage, ReaderId, Resources, Storage, System,
- SystemData, Tracked, WriteExpect, WriteStorage,
-};
-use core::ops::Deref;
-use nphysics::material::MaterialHandle;
-use nphysics::object::{BodyHandle, BodyPartHandle, ColliderDesc};
-
-#[derive(Default, new)]
-pub struct SyncCollidersToPhysicsSystem {
- #[new(default)]
- colliders_reader_id: Option<ReaderId<ComponentEvent>>,
-}
-
-impl<'a> System<'a> for SyncCollidersToPhysicsSystem {
- type SystemData = (
- WriteExpect<'a, PhysicsWorld>,
- Entities<'a>,
- ReadStorage<'a, Transform>,
- ReadStorage<'a, DynamicBody>,
- WriteStorage<'a, Collider>,
- );
-
- fn run(&mut self, data: Self::SystemData) {
- let (mut physical_world, entities, transforms, rigid_bodies, mut colliders) = data;
- // TODO: Check for inserted/removed rigid_bodies. parent sync https://nphysics.org/rustdoc/nphysics3d/world/struct.World.html?search=#method.add_collider
- let mut inserted_colliders = BitSet::new();
- let mut modified_colliders = BitSet::new();
-
- iterate_events(
- &colliders,
- self.colliders_reader_id.as_mut().unwrap(),
- &mut inserted_colliders,
- &mut modified_colliders,
- &mut physical_world,
- &entities,
- &colliders,
- );
-
- for (entity, mut collider, id, tr) in (
- &entities,
- &mut colliders,
- &inserted_colliders | &modified_colliders,
- &transforms,
- )
- .join()
- {
- if inserted_colliders.contains(id) {
- trace!("Detected inserted collider with id {:?}", id);
- // Just inserted. Remove old one and insert new.
- if collider.handle.is_some()
- && physical_world.collider(collider.handle.unwrap()).is_some()
- {
- physical_world.remove_colliders(&[collider.handle.unwrap()]);
- }
-
- let parent = if let Some(rb) = rigid_bodies.get(entity) {
- trace!("Attaching inserted collider to rigid body: {:?}", entity);
-
- rb.handle.expect(
- "You should normally have a body handle at this point. This is a bug.",
- )
- } else {
- BodyHandle::ground()
- };
- let position = if parent.is_ground() {
- tr.isometry() * collider.offset_from_parent
- } else {
- collider.offset_from_parent
- };
-
- //trace!("Inserted collider to world with values: {:?}", collider);
-
- let prediction = physical_world.prediction();
- let angular_prediction = 0.09;
-
- let parent_part_handle = physical_world
- .rigid_body(parent)
- .map(|body| body.part_handle())
- .unwrap_or_else(BodyPartHandle::ground);
-
- collider.handle = Some(
- ColliderDesc::new(collider.shape.clone())
- .user_data(entity)
- .margin(collider.margin)
- .position(position)
- .material(MaterialHandle::new(collider.physics_material))
- .build_with_parent(parent_part_handle, &mut physical_world)
- .unwrap()
- .handle(),
- );
-
- let collision_world = physical_world.collider_world_mut();
-
- collision_world.as_collider_world_mut().set_query_type(
- collider.handle.unwrap(),
- collider.query_type.to_geometric_query_type(
- collider.margin,
- prediction,
- angular_prediction,
- ),
- );
-
- let collider_object = collision_world
- .collider_mut(collider.handle.unwrap())
- .unwrap();
-
- let collider_handle = collider_object.handle();
-
- collision_world.set_collision_groups(collider_handle, collider.collision_group);
- } else if modified_colliders.contains(id) || modified_colliders.contains(id) {
- trace!("Detected changed collider with id {:?}", id);
-
- let prediction = physical_world.prediction();
- let angular_prediction = 0.09;
-
- let collision_world = physical_world.collider_world_mut();
- let collider_handle = collision_world
- .collider(collider.handle.unwrap())
- .unwrap()
- .handle();
-
- collision_world.set_collision_groups(collider_handle, collider.collision_group);
- collision_world
- .as_collider_world_mut()
- .set_shape(collider_handle, collider.shape.clone());
-
- let collider_object = collision_world
- .collider_mut(collider.handle.unwrap())
- .unwrap();
-
- let parent = if let Some(rb) = rigid_bodies.get(entity) {
- trace!("Updating collider to rigid body: {:?}", entity);
-
- rb.handle().expect(
- "You should normally have a body handle at this point. This is a bug.",
- )
- } else {
- trace!("Updating collider to ground.");
-
- BodyHandle::ground()
- };
-
- let position = if parent.is_ground() {
- tr.isometry() * collider.offset_from_parent
- } else {
- collider.offset_from_parent
- };
-
- collider_object.set_position(position);
-
- collision_world.as_collider_world_mut().set_query_type(
- collider.handle.unwrap(),
- collider.query_type.to_geometric_query_type(
- collider.margin,
- prediction,
- angular_prediction,
- ),
- );
-
- // TODO: Material changes & more complex mats than BasicMaterial
- /*collider_object
- .user_data_mut()
- .set_material(collider.physics_material.clone());*/
- }
- }
-
- colliders
- .channel()
- .read(&mut self.colliders_reader_id.as_mut().unwrap())
- .for_each(|_| ());
- }
-
- fn setup(&mut self, res: &mut Resources) {
- Self::SystemData::setup(res);
-
- let mut collider_storage: WriteStorage<Collider> = SystemData::fetch(&res);
- self.colliders_reader_id = Some(collider_storage.register_reader());
- }
-}
-
-fn iterate_events<T, D, S>(
- tracked_storage: &Storage<T, D>,
- reader: &mut ReaderId<ComponentEvent>,
- inserted: &mut BitSet,
- modified: &mut BitSet,
- world: &mut PhysicsWorld,
- entities: &Entities,
- colliders: &S,
-) where
- T: Component,
- T::Storage: Tracked,
- D: Deref<Target = MaskedStorage<T>>,
- S: GenericReadStorage<Component = Collider>,
-{
- let events = tracked_storage.channel().read(reader);
-
- for event in events {
- match event {
- ComponentEvent::Modified(id) => {
- modified.add(*id);
- }
- ComponentEvent::Inserted(id) => {
- inserted.add(*id);
- }
- ComponentEvent::Removed(id) => {
- match colliders.get(entities.entity(*id)) {
- Some(collider) => {
- match collider.handle {
- Some(handle) => {
- trace!("Removing collider with id: {}", id);
-
- world.remove_colliders(&[handle]);
- }
- None => {
- error!("Missing handle in collider: {}", id);
- }
- };
- }
- None => {
- error!("Missing collider with id: {}", id);
- }
- };
- }
- };
- }
-}
diff --git a/src/systems/sync_gravity_to_physics.rs b/src/systems/sync_gravity_to_physics.rs
@@ -1,29 +0,0 @@
-use crate::{Gravity, PhysicsWorld};
-use amethyst::ecs::{ReadExpect, Resources, System, SystemData, WriteExpect};
-
-#[derive(Default)]
-pub struct SyncGravityToPhysicsSystem;
-
-impl SyncGravityToPhysicsSystem {
- pub fn new() -> Self {
- Default::default()
- }
-}
-
-impl<'a> System<'a> for SyncGravityToPhysicsSystem {
- type SystemData = (WriteExpect<'a, PhysicsWorld>, ReadExpect<'a, Gravity>);
-
- fn run(&mut self, (mut world, gravity): Self::SystemData) {
- world.set_gravity(*gravity);
- }
-
- fn setup(&mut self, res: &mut Resources) {
- Self::SystemData::setup(res);
-
- res.entry::<Gravity>()
- .or_insert_with(|| Gravity::new(0.0, -9.80665, 0.0));
-
- res.entry::<PhysicsWorld>()
- .or_insert_with(PhysicsWorld::new);
- }
-}
diff --git a/src/time_step.rs b/src/time_step.rs
@@ -1,217 +0,0 @@
-use std::{
- cmp::Ordering,
- time::{Duration, Instant},
-};
-
-/// The type of time step to use for the physics simulation.
-pub enum TimeStep {
- /// Physics will always use the given timestep.
- Fixed(f32),
- /// Physics use one of the given timesteps, changing when physics are falling behind.
- SemiFixed(TimeStepConstraint),
-}
-
-impl Default for TimeStep {
- fn default() -> Self {
- TimeStep::Fixed(1. / 120.)
- }
-}
-
-/// Error when trying to change the actual timestep for a semi-fixed timestep.
-#[derive(Debug)]
-pub enum TimeStepChangeError {
- /// No smaller timestep available.
- MinimumTimestepReached,
- /// No larger timestep available.
- MaximumTimestepReached,
-}
-
-impl std::fmt::Display for TimeStepChangeError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- TimeStepChangeError::MaximumTimestepReached => {
- write!(f, "No larger timestep available!")
- }
- TimeStepChangeError::MinimumTimestepReached => {
- write!(f, "No smaller timestep available!")
- }
- }
- }
-}
-
-/// Constraints for a semi-fixed timestep.
-pub struct TimeStepConstraint {
- /// Vector of possible timesteps to use.
- time_steps: Vec<f32>,
- /// Index of the currently used timestep.
- current_index: usize,
- /// Fraction of frame time physics are allowed to take.
- max_physics_time_fraction: f32,
- /// Minimum time the simulation has to be running slow before the timestep is changed.
- minimum_time_running_slow: Duration,
- /// Minimum time the simulation has to be running fast before the timestep is changed.
- minimum_time_running_fast: Duration,
- /// Time when the simulation started running slow.
- running_slow_since: Option<Instant>,
- /// Time when the simulation started running fast.
- running_fast_since: Option<Instant>,
-}
-
-impl TimeStepConstraint {
- /// Creates a new `TimeStepConstraint` from the specified timesteps to use, the maximum physics
- /// time fraction and the minimum times before changing timestep.
- ///
- /// If physics take more than the specified fraction of simulated time, the timestep will be
- /// increased. If physics with a smaller timestep would still take less than the specified
- /// fraction of simulated time, the timestep will be decreased. Timesteps will only be changed
- /// if physics are running fast/slow for the specified durations.
- ///
- /// # Examples
- ///
- /// ```
- /// let game_data = GameDataBuilder::default()
- /// .with_bundle(
- /// PhysicsBundle::new()
- /// .with_timestep(TimeStep::SemiFixed(TimeStepConstraint::new(
- /// vec![1. / 240., 1. / 120., 1. / 60.],
- /// 0.4,
- /// Duration::from_millis(50),
- /// Duration::from_millis(500),
- /// )))
- /// )?
- /// ...
- /// );
- /// ```
- /// Here physics will start out with a timestep of 1/240 seconds. If physics take more than 40% of
- /// this timestep to simulate, it's running slowly. If it's running slowly for 50ms in a row, the
- /// timestep will be increased to 1/120 seconds. If physics are running fast for 500ms in a row, the
- /// timestep will be decreased again.
- ///
- /// # Panics
- ///
- /// This constructor will panic if no timesteps are given or if any negative timesteps are specified.
- pub fn new(
- time_steps: impl Into<Vec<f32>>,
- max_physics_time_fraction: f32,
- minimum_time_running_slow: Duration,
- minimum_time_running_fast: Duration,
- ) -> Self {
- let mut time_steps = time_steps.into();
- assert!(
- !time_steps.is_empty(),
- "No timesteps given in TimeStepConstraint"
- );
- time_steps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
- time_steps.dedup();
- assert!(time_steps[0] > 0., "Negative timesteps are not allowed");
-
- Self {
- time_steps,
- current_index: 0,
- max_physics_time_fraction,
- minimum_time_running_slow,
- minimum_time_running_fast,
- running_slow_since: None,
- running_fast_since: None,
- }
- }
-
- /// Increase the timestep. This corresponds to fewer updates per second.
- ///
- /// Shouldn't be called from outside the `PhysicsStepperSystem`, otherwise bad things may happen.
- pub fn increase_timestep(&mut self) -> Result<f32, TimeStepChangeError> {
- if self.current_index >= self.time_steps.len() - 1 {
- return Err(TimeStepChangeError::MaximumTimestepReached);
- }
- self.current_index += 1;
- self.running_slow_since = None;
- self.running_fast_since = None;
- Ok(self.current_timestep())
- }
-
- /// Decrease the timestep. This corresponds to more updates per second.
- ///
- /// Shouldn't be called from outside the `PhysicsStepperSystem`, otherwise bad things may happen.
- pub fn decrease_timestep(&mut self) -> Result<f32, TimeStepChangeError> {
- if self.current_index == 0 {
- return Err(TimeStepChangeError::MinimumTimestepReached);
- }
- self.current_index -= 1;
- self.running_slow_since = None;
- self.running_fast_since = None;
- Ok(self.current_timestep())
- }
-
- /// Get the currently used timestep.
- pub fn current_timestep(&self) -> f32 {
- self.time_steps[self.current_index]
- }
-
- /// Get next smaller timestep.
- pub fn smaller_timestep(&self) -> Option<f32> {
- if self.current_index == 0 {
- None
- } else {
- Some(self.time_steps[self.current_index - 1])
- }
- }
-
- /// Get the fraction of frame time physics are allowed to take.
- pub fn max_physics_time_fraction(&self) -> f32 {
- self.max_physics_time_fraction
- }
-
- /// Set whether physics are currently running slow or not. Intended to be called every frame as changing
- /// values only happens when `is_running_slow` changes between calls.
- ///
- /// Shouldn't be called from outside the `PhysicsStepperSystem`, otherwise bad things may happen.
- pub fn set_running_slow(&mut self, is_running_slow: bool) {
- self.running_slow_since = match (self.running_slow_since, is_running_slow) {
- (None, true) => {
- warn!("Physics seem to be running slow! Timestep will be changed if we keep running slow.");
- Some(Instant::now())
- }
- (Some(_), false) => {
- debug!("Physics aren't running slow anymore.");
- None
- }
- (_, _) => self.running_slow_since,
- };
- }
-
- /// Set whether physics are currently running fast or not. Intended to be called every frame as changing
- /// values only happens when `is_running_fast` changes between calls.
- ///
- /// Shouldn't be called from outside the `PhysicsStepperSystem`, otherwise bad things may happen.
- pub fn set_running_fast(&mut self, is_running_fast: bool) {
- self.running_fast_since = match (self.running_fast_since, is_running_fast) {
- (None, true) => {
- debug!("Physics seem to be running fast. Timestep will be changed if we keep running fast.");
- Some(Instant::now())
- }
- (Some(_), false) => {
- debug!("Physics aren't running fast anymore.");
- None
- }
- (_, _) => self.running_fast_since,
- };
- }
-
- /// Get whether physics have been running slow for the specified minimum time and thus the timestep should
- /// be increased.
- pub fn should_increase_timestep(&self) -> bool {
- match self.running_slow_since {
- None => false,
- Some(time) => time.elapsed() > self.minimum_time_running_slow,
- }
- }
-
- /// Get whether physics have been running fast for the specified minimum time and thus the timestep should
- /// be decreased.
- pub fn should_decrease_timestep(&self) -> bool {
- match self.running_fast_since {
- None => false,
- Some(time) => time.elapsed() > self.minimum_time_running_fast,
- }
- }
-}