use crate::scene::node::constructor::NodeConstructor;
use crate::{
core::{
algebra::{Isometry2, Translation2, UnitComplex, Vector2},
log::Log,
math::aabb::AxisAlignedBoundingBox,
pool::Handle,
reflect::prelude::*,
type_traits::prelude::*,
uuid::{uuid, Uuid},
uuid_provider,
variable::InheritableVariable,
visitor::prelude::*,
ImmutableString, TypeUuidProvider,
},
graph::SceneGraph,
scene::{
base::{Base, BaseBuilder},
collider::InteractionGroups,
dim2::{
physics::{ContactPair, IntersectionPair, PhysicsWorld},
rigidbody::RigidBody,
},
graph::{physics::CoefficientCombineRule, Graph},
node::{Node, NodeTrait, SyncContext},
Scene,
},
};
use fyrox_graph::constructor::ConstructorProvider;
use rapier2d::geometry::ColliderHandle;
use std::{
cell::Cell,
ops::{Deref, DerefMut},
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
pub struct BallShape {
#[reflect(min_value = 0.001, step = 0.05)]
pub radius: f32,
}
impl Default for BallShape {
fn default() -> Self {
Self { radius: 0.5 }
}
}
#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
pub struct CuboidShape {
#[reflect(min_value = 0.001, step = 0.05)]
pub half_extents: Vector2<f32>,
}
impl Default for CuboidShape {
fn default() -> Self {
Self {
half_extents: Vector2::new(0.5, 0.5),
}
}
}
#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
pub struct CapsuleShape {
pub begin: Vector2<f32>,
pub end: Vector2<f32>,
#[reflect(min_value = 0.001, step = 0.05)]
pub radius: f32,
}
impl Default for CapsuleShape {
fn default() -> Self {
Self {
begin: Default::default(),
end: Vector2::new(0.0, 1.0),
radius: 0.5,
}
}
}
#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
pub struct SegmentShape {
pub begin: Vector2<f32>,
pub end: Vector2<f32>,
}
impl Default for SegmentShape {
fn default() -> Self {
Self {
begin: Default::default(),
end: Vector2::new(0.0, 1.0),
}
}
}
#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
pub struct TriangleShape {
pub a: Vector2<f32>,
pub b: Vector2<f32>,
pub c: Vector2<f32>,
}
impl Default for TriangleShape {
fn default() -> Self {
Self {
a: Default::default(),
b: Vector2::new(1.0, 0.0),
c: Vector2::new(0.0, 1.0),
}
}
}
#[derive(Default, Clone, Copy, PartialEq, Hash, Debug, Visit, Reflect, Eq, TypeUuidProvider)]
#[type_uuid(id = "1d451699-d76e-4774-87ea-dd3e2751cb39")]
pub struct GeometrySource(pub Handle<Node>);
#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
pub struct TrimeshShape {
pub sources: Vec<GeometrySource>,
}
#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
pub struct HeightfieldShape {
pub geometry_source: GeometrySource,
}
#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
pub struct TileMapShape {
pub tile_map: GeometrySource,
pub layer_name: ImmutableString,
}
#[derive(Clone, Debug, Visit, Reflect, AsRefStr, PartialEq, EnumString, VariantNames)]
pub enum ColliderShape {
Ball(BallShape),
Cuboid(CuboidShape),
Capsule(CapsuleShape),
Segment(SegmentShape),
Triangle(TriangleShape),
Trimesh(TrimeshShape),
Heightfield(HeightfieldShape),
TileMap(TileMapShape),
}
uuid_provider!(ColliderShape = "4615485f-f8db-4405-b4a5-437e74b3f5b8");
impl Default for ColliderShape {
fn default() -> Self {
Self::Ball(Default::default())
}
}
impl ColliderShape {
pub fn ball(radius: f32) -> Self {
Self::Ball(BallShape { radius })
}
pub fn cuboid(hx: f32, hy: f32) -> Self {
Self::Cuboid(CuboidShape {
half_extents: Vector2::new(hx, hy),
})
}
pub fn capsule(begin: Vector2<f32>, end: Vector2<f32>, radius: f32) -> Self {
Self::Capsule(CapsuleShape { begin, end, radius })
}
pub fn capsule_x(half_height: f32, radius: f32) -> Self {
let p = Vector2::x() * half_height;
Self::capsule(-p, p, radius)
}
pub fn capsule_y(half_height: f32, radius: f32) -> Self {
let p = Vector2::y() * half_height;
Self::capsule(-p, p, radius)
}
pub fn segment(begin: Vector2<f32>, end: Vector2<f32>) -> Self {
Self::Segment(SegmentShape { begin, end })
}
pub fn triangle(a: Vector2<f32>, b: Vector2<f32>, c: Vector2<f32>) -> Self {
Self::Triangle(TriangleShape { a, b, c })
}
pub fn trimesh(geometry_sources: Vec<GeometrySource>) -> Self {
Self::Trimesh(TrimeshShape {
sources: geometry_sources,
})
}
pub fn heightfield(geometry_source: GeometrySource) -> Self {
Self::Heightfield(HeightfieldShape { geometry_source })
}
}
#[derive(Reflect, Visit, Debug, ComponentProvider)]
#[reflect(derived_type = "Node")]
pub struct Collider {
base: Base,
#[reflect(setter = "set_shape")]
pub(crate) shape: InheritableVariable<ColliderShape>,
#[reflect(min_value = 0.0, step = 0.05, setter = "set_friction")]
pub(crate) friction: InheritableVariable<f32>,
#[reflect(setter = "set_density")]
pub(crate) density: InheritableVariable<Option<f32>>,
#[reflect(min_value = 0.0, step = 0.05, setter = "set_restitution")]
pub(crate) restitution: InheritableVariable<f32>,
#[reflect(setter = "set_is_sensor")]
pub(crate) is_sensor: InheritableVariable<bool>,
#[reflect(setter = "set_collision_groups")]
pub(crate) collision_groups: InheritableVariable<InteractionGroups>,
#[reflect(setter = "set_solver_groups")]
pub(crate) solver_groups: InheritableVariable<InteractionGroups>,
#[reflect(setter = "set_friction_combine_rule")]
pub(crate) friction_combine_rule: InheritableVariable<CoefficientCombineRule>,
#[reflect(setter = "set_restitution_combine_rule")]
pub(crate) restitution_combine_rule: InheritableVariable<CoefficientCombineRule>,
#[visit(skip)]
#[reflect(hidden)]
pub(crate) native: Cell<ColliderHandle>,
}
impl Default for Collider {
fn default() -> Self {
Self {
base: Default::default(),
shape: Default::default(),
friction: Default::default(),
density: Default::default(),
restitution: Default::default(),
is_sensor: Default::default(),
collision_groups: Default::default(),
solver_groups: Default::default(),
friction_combine_rule: Default::default(),
restitution_combine_rule: Default::default(),
native: Cell::new(ColliderHandle::invalid()),
}
}
}
impl Deref for Collider {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for Collider {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl Clone for Collider {
fn clone(&self) -> Self {
Self {
base: self.base.clone(),
shape: self.shape.clone(),
friction: self.friction.clone(),
density: self.density.clone(),
restitution: self.restitution.clone(),
is_sensor: self.is_sensor.clone(),
collision_groups: self.collision_groups.clone(),
solver_groups: self.solver_groups.clone(),
friction_combine_rule: self.friction_combine_rule.clone(),
restitution_combine_rule: self.restitution_combine_rule.clone(),
native: Cell::new(ColliderHandle::invalid()),
}
}
}
impl TypeUuidProvider for Collider {
fn type_uuid() -> Uuid {
uuid!("2b1659ea-a116-4224-bcd4-7931e3ae3b40")
}
}
impl Collider {
pub fn set_shape(&mut self, shape: ColliderShape) -> ColliderShape {
self.shape.set_value_and_mark_modified(shape)
}
pub fn shape(&self) -> &ColliderShape {
&self.shape
}
pub fn shape_value(&self) -> ColliderShape {
(*self.shape).clone()
}
pub fn shape_mut(&mut self) -> &mut ColliderShape {
self.shape.get_value_mut_and_mark_modified()
}
pub fn set_restitution(&mut self, restitution: f32) -> f32 {
self.restitution.set_value_and_mark_modified(restitution)
}
pub fn restitution(&self) -> f32 {
*self.restitution
}
pub fn set_density(&mut self, density: Option<f32>) -> Option<f32> {
self.density.set_value_and_mark_modified(density)
}
pub fn density(&self) -> Option<f32> {
*self.density
}
pub fn set_friction(&mut self, friction: f32) -> f32 {
self.friction.set_value_and_mark_modified(friction)
}
pub fn friction(&self) -> f32 {
*self.friction
}
pub fn set_collision_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
self.collision_groups.set_value_and_mark_modified(groups)
}
pub fn collision_groups(&self) -> InteractionGroups {
*self.collision_groups
}
pub fn set_solver_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
self.solver_groups.set_value_and_mark_modified(groups)
}
pub fn solver_groups(&self) -> InteractionGroups {
*self.solver_groups
}
pub fn set_is_sensor(&mut self, is_sensor: bool) -> bool {
self.is_sensor.set_value_and_mark_modified(is_sensor)
}
pub fn is_sensor(&self) -> bool {
*self.is_sensor
}
pub fn set_friction_combine_rule(
&mut self,
rule: CoefficientCombineRule,
) -> CoefficientCombineRule {
self.friction_combine_rule.set_value_and_mark_modified(rule)
}
pub fn friction_combine_rule(&self) -> CoefficientCombineRule {
*self.friction_combine_rule
}
pub fn set_restitution_combine_rule(
&mut self,
rule: CoefficientCombineRule,
) -> CoefficientCombineRule {
self.restitution_combine_rule
.set_value_and_mark_modified(rule)
}
pub fn restitution_combine_rule(&self) -> CoefficientCombineRule {
*self.restitution_combine_rule
}
pub fn contacts<'a>(
&self,
physics: &'a PhysicsWorld,
) -> impl Iterator<Item = ContactPair> + 'a {
physics.contacts_with(self.native.get())
}
pub fn active_contacts<'a>(
&self,
physics: &'a PhysicsWorld,
) -> impl Iterator<Item = ContactPair> + 'a {
self.contacts(physics)
.filter(|pair| pair.has_any_active_contact)
}
pub fn intersects<'a>(
&self,
physics: &'a PhysicsWorld,
) -> impl Iterator<Item = IntersectionPair> + 'a {
physics.intersections_with(self.native.get())
}
pub fn active_intersects<'a>(
&self,
physics: &'a PhysicsWorld,
) -> impl Iterator<Item = Handle<Self>> + 'a {
let self_handle = self.handle().to_variant();
self.intersects(physics)
.filter(|pair| pair.has_any_active_contact)
.map(move |pair| pair.other(self_handle))
}
pub(crate) fn needs_sync_model(&self) -> bool {
self.shape.need_sync()
|| self.friction.need_sync()
|| self.density.need_sync()
|| self.restitution.need_sync()
|| self.is_sensor.need_sync()
|| self.collision_groups.need_sync()
|| self.solver_groups.need_sync()
|| self.friction_combine_rule.need_sync()
|| self.restitution_combine_rule.need_sync()
}
}
impl ConstructorProvider<Node, Graph> for Collider {
fn constructor() -> NodeConstructor {
NodeConstructor::new::<Self>()
.with_variant("Collider", |_| {
ColliderBuilder::new(BaseBuilder::new().with_name("Collider 2D"))
.with_shape(ColliderShape::Cuboid(Default::default()))
.build_node()
.into()
})
.with_group("Physics 2D")
}
}
impl NodeTrait for Collider {
fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
self.base.local_bounding_box()
}
fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
self.base.world_bounding_box()
}
fn id(&self) -> Uuid {
Self::type_uuid()
}
fn on_removed_from_graph(&mut self, graph: &mut Graph) {
graph.physics2d.remove_collider(self.native.get());
self.native.set(ColliderHandle::invalid());
Log::info(format!(
"Native collider 2D was removed for node: {}",
self.name()
));
}
fn on_unlink(&mut self, graph: &mut Graph) {
if graph.physics2d.remove_collider(self.native.get()) {
self.native.set(ColliderHandle::invalid());
}
}
fn on_local_transform_changed(&self, context: &mut SyncContext) {
if self.native.get() != ColliderHandle::invalid() {
if let Some(native) = context.physics2d.colliders.get_mut(self.native.get()) {
native.set_position_wrt_parent(
Isometry2 {
rotation: UnitComplex::from_angle(
self.local_transform().rotation().euler_angles().2,
),
translation: Translation2 {
vector: self.local_transform().position().xy(),
},
}
.into(),
);
}
}
}
fn sync_native(&self, self_handle: Handle<Node>, context: &mut SyncContext) {
context
.physics2d
.sync_to_collider_node(context.nodes, self_handle, self);
}
fn validate(&self, scene: &Scene) -> Result<(), String> {
let mut message = String::new();
if scene
.graph
.try_get_of_type::<RigidBody>(self.parent())
.is_err()
{
message += "2D Collider must be a direct child of a 3D Rigid Body node, \
otherwise it will not have any effect!";
}
match &*self.shape {
ColliderShape::Trimesh(trimesh) => {
for source in trimesh.sources.iter() {
if !scene.graph.is_valid_handle(source.0) {
message += &format!("Trimesh shape data handle {} is invalid!", source.0);
}
}
}
ColliderShape::Heightfield(heightfield) => {
if !scene.graph.is_valid_handle(heightfield.geometry_source.0) {
message += &format!(
"Heightfield shape data handle {} is invalid!",
heightfield.geometry_source.0
);
}
}
ColliderShape::TileMap(tile_map) => {
if !scene.graph.is_valid_handle(tile_map.tile_map.0) {
message += &format!(
"Tile map shape data handle {} is invalid!",
tile_map.tile_map.0
);
}
}
_ => (),
}
if message.is_empty() {
Ok(())
} else {
Err(message)
}
}
}
pub struct ColliderBuilder {
base_builder: BaseBuilder,
shape: ColliderShape,
friction: f32,
density: Option<f32>,
restitution: f32,
is_sensor: bool,
collision_groups: InteractionGroups,
solver_groups: InteractionGroups,
friction_combine_rule: CoefficientCombineRule,
restitution_combine_rule: CoefficientCombineRule,
}
impl ColliderBuilder {
pub fn new(base_builder: BaseBuilder) -> Self {
Self {
base_builder,
shape: Default::default(),
friction: 0.0,
density: None,
restitution: 0.0,
is_sensor: false,
collision_groups: Default::default(),
solver_groups: Default::default(),
friction_combine_rule: Default::default(),
restitution_combine_rule: Default::default(),
}
}
pub fn with_shape(mut self, shape: ColliderShape) -> Self {
self.shape = shape;
self
}
pub fn with_density(mut self, density: Option<f32>) -> Self {
self.density = density;
self
}
pub fn with_restitution(mut self, restitution: f32) -> Self {
self.restitution = restitution;
self
}
pub fn with_friction(mut self, friction: f32) -> Self {
self.friction = friction;
self
}
pub fn with_sensor(mut self, sensor: bool) -> Self {
self.is_sensor = sensor;
self
}
pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
self.solver_groups = solver_groups;
self
}
pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
self.collision_groups = collision_groups;
self
}
pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
self.friction_combine_rule = rule;
self
}
pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
self.restitution_combine_rule = rule;
self
}
pub fn build_collider(self) -> Collider {
Collider {
base: self.base_builder.build_base(),
shape: self.shape.into(),
friction: self.friction.into(),
density: self.density.into(),
restitution: self.restitution.into(),
is_sensor: self.is_sensor.into(),
collision_groups: self.collision_groups.into(),
solver_groups: self.solver_groups.into(),
friction_combine_rule: self.friction_combine_rule.into(),
restitution_combine_rule: self.restitution_combine_rule.into(),
native: Cell::new(ColliderHandle::invalid()),
}
}
pub fn build_node(self) -> Node {
Node::new(self.build_collider())
}
pub fn build(self, graph: &mut Graph) -> Handle<Collider> {
graph.add_node(self.build_node()).to_variant()
}
}
#[cfg(test)]
mod test {
use crate::core::algebra::Vector2;
use crate::scene::{
base::BaseBuilder,
dim2::{
collider::{ColliderBuilder, ColliderShape},
rigidbody::RigidBodyBuilder,
},
graph::Graph,
rigidbody::RigidBodyType,
};
#[test]
fn test_collider_2d_intersect() {
let mut graph = Graph::new();
let mut create_rigid_body = |is_sensor| {
let cube_half_size = 0.5;
let collider_sensor = ColliderBuilder::new(BaseBuilder::new())
.with_shape(ColliderShape::cuboid(cube_half_size, cube_half_size))
.with_sensor(is_sensor)
.build(&mut graph);
RigidBodyBuilder::new(BaseBuilder::new().with_child(collider_sensor))
.with_body_type(RigidBodyType::Static)
.build(&mut graph);
collider_sensor
};
let collider_sensor = create_rigid_body(true);
let collider_non_sensor = create_rigid_body(false);
graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
assert_eq!(0, graph[collider_sensor].contacts(&graph.physics2d).count());
assert_eq!(
0,
graph[collider_non_sensor]
.contacts(&graph.physics2d)
.count()
);
assert_eq!(
1,
graph[collider_sensor].intersects(&graph.physics2d).count()
);
assert_eq!(
1,
graph[collider_non_sensor]
.intersects(&graph.physics2d)
.count()
);
}
}