Skip to content
This repository has been archived by the owner on Nov 29, 2022. It is now read-only.

Commit

Permalink
feat: allow to define layers for collision shapes generated by `Pendi…
Browse files Browse the repository at this point in the history
…ngConvexCollision` (#224)
  • Loading branch information
Shatur committed Mar 17, 2022
1 parent dbf4b30 commit c51c5a1
Showing 1 changed file with 100 additions and 65 deletions.
165 changes: 100 additions & 65 deletions core/src/collision_from_mesh.rs
@@ -1,6 +1,8 @@
use std::collections::LinkedList;

use bevy::{prelude::*, render::mesh::VertexAttributeValues};

use crate::{CollisionShape, RigidBody};
use crate::{CollisionLayers, CollisionShape, RigidBody};

/// Component which indicates that this entity or its children contains meshes which waiting for collision generation.
///
Expand All @@ -16,27 +18,49 @@ use crate::{CollisionShape, RigidBody};
/// .spawn()
/// .insert(Transform::default()) // Required to apply GLTF transforms in Bevy
/// .insert(GlobalTransform::default())
/// .insert(PendingConvexCollision {
/// body_type: RigidBody::Static,
/// border_radius: None,
/// })
/// .insert(PendingConvexCollision::default())
/// .insert(RigidBody::Static)
/// .insert(CollisionLayers::default())
/// .with_children(|parent| {
/// parent.spawn_scene(asset_server.load("cubes.glb#Scene0"));
/// });
/// }
/// ```
#[derive(Component, Reflect)]
#[derive(Component, Clone, Copy, Reflect)]
pub struct PendingConvexCollision {
/// Rigid body type which will be assigned to every scene entity.
#[deprecated(note = "Insert body type component into the entity with this component")]
pub body_type: RigidBody,
/// Border radius that will be used for [`CollisionShape::ConvexHull`].
pub border_radius: Option<f32>,
}

#[allow(deprecated)]
impl Default for PendingConvexCollision {
fn default() -> Self {
Self {
body_type: RigidBody::Static,
border_radius: None,
}
}
}

/// Generates collision and attaches physics body for all entities with [`PendingConvexCollision`].
#[allow(deprecated)]
#[allow(clippy::type_complexity)] // Do not warn about long query
pub(super) fn pending_collision_system(
mut commands: Commands<'_, '_>,
added_scenes: Query<'_, '_, (Entity, &Children, &PendingConvexCollision)>,
added_scenes: Query<
'_,
'_,
(
Entity,
&Children,
&PendingConvexCollision,
Option<&RigidBody>,
Option<&CollisionLayers>,
),
>,
scene_elements: Query<'_, '_, &Children, Without<PendingConvexCollision>>,
mesh_handles: Query<'_, '_, &Handle<Mesh>>,
meshes: Option<Res<'_, Assets<Mesh>>>,
Expand All @@ -45,69 +69,62 @@ pub(super) fn pending_collision_system(
None => return,
Some(m) => m,
};
for (entity, children, pending_collision) in added_scenes.iter() {
if generate_collision(
&mut commands,
pending_collision,
children,
&scene_elements,
&mesh_handles,
&meshes,
) {
// Only delete the component when the meshes are loaded and their is generated
commands.entity(entity).remove::<PendingConvexCollision>();
for (scene, children, pending_collision, rigid_body, collision_layers) in added_scenes.iter() {
let children = recursive_scene_children(children, &scene_elements);
for child in &children {
if let Ok(handle) = mesh_handles.get(*child) {
let mesh = meshes.get(handle).unwrap(); // SAFETY: Mesh already loaded
let vertices = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap() {
VertexAttributeValues::Float32x3(vertices) => vertices,
_ => unreachable!(
"Mesh should have encoded vertices as VertexAttributeValues::Float32x3"
),
};
let mut points = Vec::with_capacity(vertices.len());
for vertex in vertices {
points.push(Vec3::new(vertex[0], vertex[1], vertex[2]));
}
let mut child_commands = commands.entity(*child);
child_commands.insert(pending_collision.body_type).insert(
CollisionShape::ConvexHull {
points,
border_radius: pending_collision.border_radius,
},
);
if let Some(rigid_body) = rigid_body {
child_commands.insert(*rigid_body);
}
if let Some(collision_layers) = collision_layers {
child_commands.insert(*collision_layers);
}
}
}
if !children.is_empty() {
commands
.entity(scene)
.remove::<PendingConvexCollision>()
.remove::<RigidBody>()
.remove::<CollisionLayers>();
}
}
}

/// Recursively generate collision and attach physics body for the specified children.
/// Returns `true` if a mesh was found.
fn generate_collision(
commands: &mut Commands<'_, '_>,
pending_collision: &PendingConvexCollision,
/// Iterates over children hierarchy recursively and returns a plain list of all children.
/// [`LinkedList`] is used here for fast lists concatenation due to recursive iteration.
#[allow(clippy::linkedlist)]
fn recursive_scene_children(
children: &Children,
scene_elements: &Query<'_, '_, &Children, Without<PendingConvexCollision>>,
mesh_handles: &Query<'_, '_, &Handle<Mesh>>,
meshes: &Assets<Mesh>,
) -> bool {
let mut generated = false;
) -> LinkedList<Entity> {
let mut all_children = LinkedList::new();
for child in children.iter() {
if let Ok(children) = scene_elements.get(*child) {
if generate_collision(
commands,
pending_collision,
children,
scene_elements,
mesh_handles,
meshes,
) {
generated = true;
}
}
if let Ok(handle) = mesh_handles.get(*child) {
generated = true;
let mesh = meshes.get(handle).unwrap(); // SAFETY: Mesh already loaded
let vertices = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap() {
VertexAttributeValues::Float32x3(vertices) => vertices,
_ => unreachable!(
"Mesh should have encoded vertices as VertexAttributeValues::Float32x3"
),
};
let mut points = Vec::with_capacity(vertices.len());
for vertex in vertices {
points.push(Vec3::new(vertex[0], vertex[1], vertex[2]));
}
commands
.entity(*child)
.insert(pending_collision.body_type)
.insert(CollisionShape::ConvexHull {
points,
border_radius: pending_collision.border_radius,
});
let mut children = recursive_scene_children(children, scene_elements);
all_children.append(&mut children);
}
all_children.push_back(*child);
}

generated
all_children
}

#[cfg(test)]
Expand Down Expand Up @@ -146,6 +163,7 @@ mod tests {
}

#[test]
#[allow(deprecated)]
fn pending_collision_assignes() {
let mut app = App::new();
app.add_plugin(HeadlessRenderPlugin)
Expand All @@ -155,15 +173,19 @@ mod tests {
let cube = meshes.add(Cube::default().into());
let capsule = meshes.add(Capsule::default().into());

const COLLISION_LAYERS: CollisionLayers = CollisionLayers::from_bits(1, 2);
const BODY_TYPE: RigidBody = RigidBody::Static;
const REQUESTED_COLLISION: PendingConvexCollision = PendingConvexCollision {
body_type: RigidBody::Static,
body_type: RigidBody::Dynamic,
border_radius: None,
};

let parent = app
.world
.spawn()
.insert(REQUESTED_COLLISION)
.insert(BODY_TYPE)
.insert(COLLISION_LAYERS)
.with_children(|parent| {
parent.spawn().insert(cube);
parent.spawn().insert(capsule);
Expand All @@ -172,7 +194,7 @@ mod tests {

let mut query = app
.world
.query::<(&Handle<Mesh>, &RigidBody, &CollisionShape)>();
.query::<(&Handle<Mesh>, &RigidBody, &CollisionShape, &CollisionLayers)>();
assert_eq!(
query.iter(&app.world).count(),
0,
Expand All @@ -188,9 +210,9 @@ mod tests {
);

let meshes = app.world.get_resource::<Assets<Mesh>>().unwrap();
for (mesh_handle, body_type, collision_shape) in query.iter(&app.world) {
for (mesh_handle, body_type, collision_shape, collision_layers) in query.iter(&app.world) {
assert_eq!(
*body_type, REQUESTED_COLLISION.body_type,
*body_type, BODY_TYPE,
"Assigned body type should be equal to specified"
);

Expand Down Expand Up @@ -228,13 +250,26 @@ mod tests {
*border_radius, REQUESTED_COLLISION.border_radius,
"Assigned border radius should be equal to specified"
);

assert_eq!(
*collision_layers, COLLISION_LAYERS,
"Assigned collision layers should be equal to specified"
);
}

assert!(
!app.world
.entity(parent)
.contains::<PendingConvexCollision>(),
"Parent entity should have PendingConvexCollision removed"
"Parent entity should have PendingConvexCollision compoent removed"
);
assert!(
!app.world.entity(parent).contains::<RigidBody>(),
"Parent entity should have RigidBody compoent removed"
);
assert!(
!app.world.entity(parent).contains::<CollisionLayers>(),
"Parent entity should have CollisionLayers compoent removed"
);
}
}

0 comments on commit c51c5a1

Please sign in to comment.