Skip to content

Commit

Permalink
feat: RotationContraints component (jcornaz#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz authored Mar 9, 2021
1 parent ed90487 commit 2686a5d
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 29 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ The format is inspired from [Keep a Changelog], and this project adheres to [Sem

## [Unreleased]

The default color for debug shape is less transparent. That should make easier to see the debug shapes.
### RotationConstraints component

A new `RotationConstraints` component make possible to prevent rotation around the given axes.

### Others

* The opacity has been increased for the default color of debug shapes.


## [0.2.0] - 2021-03-07
Expand Down
88 changes: 88 additions & 0 deletions core/src/constraints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use bevy::reflect::Reflect;

/// Component that restrict what rotations can be caused by forces.
///
/// Note that angular velocity may still be applied programmatically. This only restrict how rotation
/// can change when force/torques are applied.
///
/// # Example
///
/// ```
/// # use bevy::prelude::*;
/// # use heron_core::*;
///
/// fn spawn(commands: &mut Commands) {
/// commands.spawn(todo!("Spawn your sprite/mesh, incl. at least a GlobalTransform"))
/// .with(Body::Sphere { radius: 1.0 })
/// .with(RotationConstraints::lock()); // Prevent rotation caused by forces
/// }
/// ```
#[derive(Debug, Copy, Clone, Reflect)]
pub struct RotationConstraints {
/// Set to true to prevent rotations around the x axis
pub allow_x: bool,

/// Set to true to prevent rotations around the y axis
pub allow_y: bool,

/// Set to true to prevent rotations around the Z axis
pub allow_z: bool,
}

impl Default for RotationConstraints {
fn default() -> Self {
Self::allow()
}
}

impl RotationConstraints {
/// Lock rotations around all axes
#[must_use]
pub fn lock() -> Self {
Self {
allow_x: false,
allow_y: false,
allow_z: false,
}
}

/// Allow rotations around all axes
#[must_use]
pub fn allow() -> Self {
Self {
allow_x: true,
allow_y: true,
allow_z: true,
}
}

/// Allow rotation around the x axis only (and prevent rotating around the other axes)
#[must_use]
pub fn restrict_to_x_only() -> Self {
Self {
allow_x: true,
allow_y: false,
allow_z: false,
}
}

/// Allow rotation around the y axis only (and prevent rotating around the other axes)
#[must_use]
pub fn restrict_to_y_only() -> Self {
Self {
allow_x: false,
allow_y: true,
allow_z: false,
}
}

/// Allow rotation around the z axis only (and prevent rotating around the other axes)
#[must_use]
pub fn restrict_to_z_only() -> Self {
Self {
allow_x: false,
allow_y: false,
allow_z: true,
}
}
}
2 changes: 0 additions & 2 deletions core/src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![allow(clippy::module_name_repetitions)]

//! Extensions to bevy API
use bevy::app::AppBuilder;
Expand Down
5 changes: 5 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#![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::{AxisAngle, Velocity};

mod constraints;
pub mod ext;
mod gravity;
pub mod utils;
Expand Down Expand Up @@ -79,6 +82,7 @@ impl Plugin for CorePlugin {
.register_type::<BodyType>()
.register_type::<PhysicMaterial>()
.register_type::<Velocity>()
.register_type::<RotationConstraints>()
.add_stage_after(bevy::app::stage::UPDATE, crate::stage::ROOT, {
let mut schedule = Schedule::default();

Expand Down Expand Up @@ -218,6 +222,7 @@ impl BodyType {
/// }
/// }
/// }
/// ```
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CollisionEvent {
/// The two entities started to collide
Expand Down
68 changes: 44 additions & 24 deletions rapier/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy::math::prelude::*;
use bevy::transform::prelude::*;
use fnv::FnvHashMap;

use heron_core::{Body, BodyType, PhysicMaterial, Velocity};
use heron_core::{Body, BodyType, PhysicMaterial, RotationConstraints, Velocity};

use crate::convert::{IntoBevy, IntoRapier};
use crate::rapier::dynamics::{
Expand All @@ -29,17 +29,37 @@ pub(crate) fn create(
Option<&BodyType>,
Option<&Velocity>,
Option<&PhysicMaterial>,
Option<&RotationConstraints>,
),
Without<BodyHandle>,
>,
) {
for (entity, body, transform, body_type, velocity, material) in query.iter() {
for (entity, body, transform, body_type, velocity, material, rotation_constraints) in
query.iter()
{
let body_type = body_type.cloned().unwrap_or_default();

let mut builder = RigidBodyBuilder::new(body_status(body_type))
.user_data(entity.to_bits().into())
.position((transform.translation, transform.rotation).into_rapier());

#[allow(unused_variables)]
if let Some(RotationConstraints {
allow_x,
allow_y,
allow_z,
}) = rotation_constraints.cloned()
{
#[cfg(feature = "2d")]
if !allow_z {
builder = builder.lock_rotations();
}
#[cfg(feature = "3d")]
{
builder = builder.restrict_rotations(allow_x, allow_y, allow_z);
}
}

if let Some(v) = velocity {
#[cfg(feature = "2d")]
{
Expand Down Expand Up @@ -76,33 +96,33 @@ pub(crate) fn create(
}

#[allow(clippy::type_complexity)]
pub(crate) fn recreate_collider(
pub(crate) fn remove_bodies(
commands: &mut Commands,
mut bodies: ResMut<'_, RigidBodySet>,
mut colliders: ResMut<'_, ColliderSet>,
mut query: Query<
mut joints: ResMut<'_, JointSet>,
changed: Query<
'_,
(
Entity,
&Body,
&mut BodyHandle,
Option<&BodyType>,
Option<&PhysicMaterial>,
),
Or<(Mutated<Body>, Changed<BodyType>, Changed<PhysicMaterial>)>,
(Entity, &BodyHandle),
Or<(
Mutated<Body>,
Changed<RotationConstraints>,
Changed<BodyType>,
Changed<PhysicMaterial>,
)>,
>,
removed: Query<'_, (Entity, &BodyHandle), Without<RotationConstraints>>,
) {
for (entity, body_def, mut handle, body_type, material) in query.iter_mut() {
colliders.remove(handle.collider, &mut bodies, true);
handle.collider = colliders.insert(
build_collider(
entity,
&body_def,
body_type.cloned().unwrap_or_default(),
material.cloned().unwrap_or_default(),
),
handle.rigid_body,
&mut bodies,
);
for (entity, handle) in changed.iter() {
bodies.remove(handle.rigid_body, &mut colliders, &mut joints);
commands.remove_one::<BodyHandle>(entity);
}

for entity in removed.removed::<RotationConstraints>() {
if let Ok((entity, handle)) = removed.get(*entity) {
bodies.remove(handle.rigid_body, &mut colliders, &mut joints);
commands.remove_one::<BodyHandle>(entity);
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion rapier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ impl Plugin for RapierPlugin {
.add_resource(JointSet::new())
.stage(heron_core::stage::ROOT, |schedule: &mut Schedule| {
schedule
.add_stage(
"heron-remove-invalid-bodies",
SystemStage::serial().with_system(body::remove_bodies.system()),
)
.add_stage(
"heron-pre-step",
SystemStage::serial()
Expand All @@ -123,7 +127,6 @@ impl Plugin for RapierPlugin {
.system(),
)
.with_system(body::remove.system())
.with_system(body::recreate_collider.system())
.with_system(body::update_rapier_position.system())
.with_system(velocity::update_rapier_velocity.system())
.with_system(body::update_rapier_status.system())
Expand Down
119 changes: 119 additions & 0 deletions rapier/tests/constraints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#![cfg(feature = "2d")]

use bevy::core::CorePlugin;
use bevy::prelude::*;
use bevy::reflect::TypeRegistryArc;

use heron_core::{Body, RotationConstraints};
use heron_rapier::rapier::dynamics::{IntegrationParameters, RigidBodySet};
use heron_rapier::{BodyHandle, RapierPlugin};

fn test_app() -> App {
let mut builder = App::build();
builder
.init_resource::<TypeRegistryArc>()
.add_plugin(CorePlugin)
.add_plugin(RapierPlugin {
step_per_second: None,
parameters: IntegrationParameters::default(),
});
builder.app
}

#[test]
fn rotation_is_not_constrained_without_the_component() {
let mut app = test_app();

let entity = app
.world
.spawn((GlobalTransform::default(), Body::Sphere { radius: 10.0 }));

app.update();

let bodies = app.resources.get::<RigidBodySet>().unwrap();

assert!(
bodies
.get(app.world.get::<BodyHandle>(entity).unwrap().rigid_body())
.unwrap()
.effective_world_inv_inertia_sqrt
> 0.0
);
}

#[test]
fn rotation_can_be_locked_at_creation() {
let mut app = test_app();

let entity = app.world.spawn((
GlobalTransform::default(),
Body::Sphere { radius: 10.0 },
RotationConstraints::lock(),
));

app.update();

let bodies = app.resources.get::<RigidBodySet>().unwrap();

assert_eq!(
bodies
.get(app.world.get::<BodyHandle>(entity).unwrap().rigid_body())
.unwrap()
.effective_world_inv_inertia_sqrt,
0.0
);
}

#[test]
fn rotation_can_be_locked_after_creation() {
let mut app = test_app();

let entity = app
.world
.spawn((GlobalTransform::default(), Body::Sphere { radius: 10.0 }));

app.update();

app.world
.insert_one(entity, RotationConstraints::lock())
.unwrap();

app.update();

let bodies = app.resources.get::<RigidBodySet>().unwrap();

assert_eq!(
bodies
.get(app.world.get::<BodyHandle>(entity).unwrap().rigid_body())
.unwrap()
.effective_world_inv_inertia_sqrt,
0.0
);
}

#[test]
fn rotation_is_unlocked_if_component_is_removed() {
let mut app = test_app();

let entity = app.world.spawn((
GlobalTransform::default(),
Body::Sphere { radius: 10.0 },
RotationConstraints::lock(),
));

app.update();

app.world.remove_one::<RotationConstraints>(entity).unwrap();

app.update();

let bodies = app.resources.get::<RigidBodySet>().unwrap();

assert!(
bodies
.get(app.world.get::<BodyHandle>(entity).unwrap().rigid_body())
.unwrap()
.effective_world_inv_inertia_sqrt
> 0.0
);
}
Loading

0 comments on commit 2686a5d

Please sign in to comment.