mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
1972 lines
79 KiB
Rust
1972 lines
79 KiB
Rust
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
||
//
|
||
// 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.
|
||
|
||
//! Scene physics module.
|
||
|
||
use crate::{
|
||
core::{
|
||
algebra::{
|
||
Isometry3, Matrix4, Point3, Translation, Translation3, UnitQuaternion, UnitVector3,
|
||
Vector2, Vector3,
|
||
},
|
||
arrayvec::ArrayVec,
|
||
instant,
|
||
log::{Log, MessageKind},
|
||
math::Matrix4Ext,
|
||
parking_lot::Mutex,
|
||
pool::Handle,
|
||
reflect::prelude::*,
|
||
variable::{InheritableVariable, VariableFlags},
|
||
visitor::prelude::*,
|
||
BiDirHashMap, SafeLock,
|
||
},
|
||
scene::{
|
||
self,
|
||
collider::{self, ColliderShape, GeometrySource},
|
||
debug::SceneDrawingContext,
|
||
graph::{isometric_global_transform, Graph, NodePool},
|
||
joint::{JointLocalFrames, JointMotorParams, JointParams},
|
||
mesh::{
|
||
buffer::{VertexAttributeUsage, VertexReadTrait},
|
||
Mesh,
|
||
},
|
||
node::{Node, NodeTrait},
|
||
rigidbody::{self, ApplyAction, RigidBodyMassPropertiesType},
|
||
terrain::{Chunk, Terrain},
|
||
},
|
||
utils::raw_mesh::{RawMeshBuilder, RawVertex},
|
||
};
|
||
use rapier3d::geometry::Array2;
|
||
use rapier3d::{
|
||
dynamics::{
|
||
CCDSolver, GenericJoint, GenericJointBuilder, ImpulseJointHandle, ImpulseJointSet,
|
||
IslandManager, JointAxesMask, MultibodyJointHandle, MultibodyJointSet, RigidBody,
|
||
RigidBodyActivation, RigidBodyBuilder, RigidBodyHandle, RigidBodySet, RigidBodyType,
|
||
},
|
||
geometry::{
|
||
Collider, ColliderBuilder, ColliderHandle, ColliderSet, Cuboid, DefaultBroadPhase,
|
||
InteractionGroups, NarrowPhase, Ray, SharedShape,
|
||
},
|
||
parry::{query::ShapeCastOptions, shape::HeightField},
|
||
pipeline::{DebugRenderPipeline, EventHandler, PhysicsPipeline},
|
||
prelude::{HeightFieldCellStatus, JointAxis, MassProperties},
|
||
};
|
||
use std::{
|
||
cell::Cell,
|
||
cmp::Ordering,
|
||
fmt::{Debug, Formatter},
|
||
hash::Hash,
|
||
sync::Arc,
|
||
time::Duration,
|
||
};
|
||
use strum_macros::{AsRefStr, EnumString, VariantNames};
|
||
|
||
use fyrox_graph::{SceneGraph, SceneGraphNode};
|
||
pub use rapier3d::geometry::shape::*;
|
||
use rapier3d::math::{Pose3, Vec3};
|
||
use rapier3d::parry::query::DefaultQueryDispatcher;
|
||
use rapier3d::prelude::FrictionModel;
|
||
|
||
/// Shape-dependent identifier.
|
||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||
pub enum FeatureId {
|
||
/// Shape-dependent identifier of a vertex.
|
||
Vertex(u32),
|
||
/// Shape-dependent identifier of an edge.
|
||
Edge(u32),
|
||
/// Shape-dependent identifier of a face.
|
||
Face(u32),
|
||
/// Unknown identifier.
|
||
Unknown,
|
||
}
|
||
|
||
impl From<rapier3d::geometry::FeatureId> for FeatureId {
|
||
fn from(v: rapier3d::geometry::FeatureId) -> Self {
|
||
match v {
|
||
rapier3d::geometry::FeatureId::Vertex(v) => FeatureId::Vertex(v),
|
||
rapier3d::geometry::FeatureId::Edge(v) => FeatureId::Edge(v),
|
||
rapier3d::geometry::FeatureId::Face(v) => FeatureId::Face(v),
|
||
rapier3d::geometry::FeatureId::Unknown => FeatureId::Unknown,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<rapier2d::geometry::FeatureId> for FeatureId {
|
||
fn from(v: rapier2d::geometry::FeatureId) -> Self {
|
||
match v {
|
||
rapier2d::geometry::FeatureId::Vertex(v) => FeatureId::Vertex(v),
|
||
rapier2d::geometry::FeatureId::Face(v) => FeatureId::Face(v),
|
||
rapier2d::geometry::FeatureId::Unknown => FeatureId::Unknown,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Rules used to combine two coefficients.
|
||
///
|
||
/// # Notes
|
||
///
|
||
/// This is used to determine the effective restitution and friction coefficients for a contact
|
||
/// between two colliders. Each collider has its combination rule of type `CoefficientCombineRule`,
|
||
/// the rule actually used is given by `max(first_combine_rule, second_combine_rule)`.
|
||
#[derive(
|
||
Copy, Clone, Debug, PartialEq, Eq, Visit, Reflect, VariantNames, EnumString, AsRefStr, Default,
|
||
)]
|
||
#[repr(u32)]
|
||
#[reflect(type_uuid = "775d5598-c283-4b44-9cc0-2e23dc8936f4")]
|
||
pub enum CoefficientCombineRule {
|
||
/// The two coefficients are averaged.
|
||
#[default]
|
||
Average = 0,
|
||
/// The smallest coefficient is chosen.
|
||
Min,
|
||
/// The two coefficients are multiplied.
|
||
Multiply,
|
||
/// The greatest coefficient is chosen.
|
||
Max,
|
||
/// The clamped sum of the two coefficients.
|
||
ClampedSum = 4,
|
||
}
|
||
|
||
impl From<rapier3d::dynamics::CoefficientCombineRule> for CoefficientCombineRule {
|
||
fn from(v: rapier3d::dynamics::CoefficientCombineRule) -> Self {
|
||
match v {
|
||
rapier3d::dynamics::CoefficientCombineRule::Average => CoefficientCombineRule::Average,
|
||
rapier3d::dynamics::CoefficientCombineRule::Min => CoefficientCombineRule::Min,
|
||
rapier3d::dynamics::CoefficientCombineRule::Multiply => {
|
||
CoefficientCombineRule::Multiply
|
||
}
|
||
rapier3d::dynamics::CoefficientCombineRule::Max => CoefficientCombineRule::Max,
|
||
rapier3d::dynamics::CoefficientCombineRule::ClampedSum => {
|
||
CoefficientCombineRule::ClampedSum
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Into<rapier3d::dynamics::CoefficientCombineRule> for CoefficientCombineRule {
|
||
fn into(self) -> rapier3d::dynamics::CoefficientCombineRule {
|
||
match self {
|
||
CoefficientCombineRule::Average => rapier3d::dynamics::CoefficientCombineRule::Average,
|
||
CoefficientCombineRule::Min => rapier3d::dynamics::CoefficientCombineRule::Min,
|
||
CoefficientCombineRule::Multiply => {
|
||
rapier3d::dynamics::CoefficientCombineRule::Multiply
|
||
}
|
||
CoefficientCombineRule::Max => rapier3d::dynamics::CoefficientCombineRule::Max,
|
||
CoefficientCombineRule::ClampedSum => {
|
||
rapier3d::dynamics::CoefficientCombineRule::ClampedSum
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Into<rapier2d::dynamics::CoefficientCombineRule> for CoefficientCombineRule {
|
||
fn into(self) -> rapier2d::dynamics::CoefficientCombineRule {
|
||
match self {
|
||
CoefficientCombineRule::Average => rapier2d::dynamics::CoefficientCombineRule::Average,
|
||
CoefficientCombineRule::Min => rapier2d::dynamics::CoefficientCombineRule::Min,
|
||
CoefficientCombineRule::Multiply => {
|
||
rapier2d::dynamics::CoefficientCombineRule::Multiply
|
||
}
|
||
CoefficientCombineRule::Max => rapier2d::dynamics::CoefficientCombineRule::Max,
|
||
CoefficientCombineRule::ClampedSum => {
|
||
rapier2d::dynamics::CoefficientCombineRule::ClampedSum
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Performance statistics for the physics part of the engine.
|
||
#[derive(Debug, Default, Clone)]
|
||
pub struct PhysicsPerformanceStatistics {
|
||
/// A time that was needed to perform a single simulation step.
|
||
pub step_time: Duration,
|
||
|
||
/// A time that was needed to perform all ray casts.
|
||
pub total_ray_cast_time: Cell<Duration>,
|
||
}
|
||
|
||
impl PhysicsPerformanceStatistics {
|
||
/// Resets performance statistics to default values.
|
||
pub fn reset(&mut self) {
|
||
*self = Default::default();
|
||
}
|
||
|
||
/// Returns total amount of time for every part of statistics.
|
||
pub fn total(&self) -> Duration {
|
||
self.step_time + self.total_ray_cast_time.get()
|
||
}
|
||
}
|
||
|
||
/// A ray intersection result.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct Intersection {
|
||
/// A handle of the collider with which intersection was detected.
|
||
pub collider: Handle<collider::Collider>,
|
||
|
||
/// A normal at the intersection position.
|
||
pub normal: Vector3<f32>,
|
||
|
||
/// A position of the intersection in world coordinates.
|
||
pub position: Point3<f32>,
|
||
|
||
/// Additional data that contains a kind of the feature with which
|
||
/// intersection was detected as well as its index.
|
||
///
|
||
/// # Important notes.
|
||
///
|
||
/// FeatureId::Face might have index that is greater than amount of triangles in
|
||
/// a triangle mesh, this means that intersection was detected from "back" side of
|
||
/// a face. To "fix" that index, simply subtract amount of triangles of a triangle
|
||
/// mesh from the value.
|
||
pub feature: FeatureId,
|
||
|
||
/// Distance from the ray origin.
|
||
pub toi: f32,
|
||
}
|
||
|
||
/// A set of options for the ray cast.
|
||
pub struct RayCastOptions {
|
||
/// A ray origin.
|
||
pub ray_origin: Point3<f32>,
|
||
|
||
/// A ray direction. Can be non-normalized.
|
||
pub ray_direction: Vector3<f32>,
|
||
|
||
/// Maximum distance of cast.
|
||
pub max_len: f32,
|
||
|
||
/// Groups to check.
|
||
pub groups: collider::InteractionGroups,
|
||
|
||
/// Whether to sort intersections from closest to farthest.
|
||
pub sort_results: bool,
|
||
}
|
||
|
||
/// A trait for ray cast results storage. It has two implementations: Vec and ArrayVec.
|
||
/// Latter is needed for the cases where you need to avoid runtime memory allocations
|
||
/// and do everything on stack.
|
||
pub trait QueryResultsStorage {
|
||
/// Pushes new intersection in the storage. Returns true if intersection was
|
||
/// successfully inserted, false otherwise.
|
||
fn push(&mut self, intersection: Intersection) -> bool;
|
||
|
||
/// Clears the storage.
|
||
fn clear(&mut self);
|
||
|
||
/// Sorts intersections by given compare function.
|
||
fn sort_intersections_by<C: FnMut(&Intersection, &Intersection) -> Ordering>(&mut self, cmp: C);
|
||
}
|
||
|
||
impl QueryResultsStorage for Vec<Intersection> {
|
||
fn push(&mut self, intersection: Intersection) -> bool {
|
||
self.push(intersection);
|
||
true
|
||
}
|
||
|
||
fn clear(&mut self) {
|
||
self.clear()
|
||
}
|
||
|
||
fn sort_intersections_by<C>(&mut self, cmp: C)
|
||
where
|
||
C: FnMut(&Intersection, &Intersection) -> Ordering,
|
||
{
|
||
self.sort_by(cmp);
|
||
}
|
||
}
|
||
|
||
impl<const CAP: usize> QueryResultsStorage for ArrayVec<Intersection, CAP> {
|
||
fn push(&mut self, intersection: Intersection) -> bool {
|
||
self.try_push(intersection).is_ok()
|
||
}
|
||
|
||
fn clear(&mut self) {
|
||
self.clear()
|
||
}
|
||
|
||
fn sort_intersections_by<C>(&mut self, cmp: C)
|
||
where
|
||
C: FnMut(&Intersection, &Intersection) -> Ordering,
|
||
{
|
||
self.sort_by(cmp);
|
||
}
|
||
}
|
||
|
||
/// Data of the contact.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct ContactData {
|
||
/// The contact point in the local-space of the first shape.
|
||
pub local_p1: Vector3<f32>,
|
||
/// The contact point in the local-space of the second shape.
|
||
pub local_p2: Vector3<f32>,
|
||
/// The distance between the two contact points.
|
||
pub dist: f32,
|
||
/// The impulse, along the contact normal, applied by this contact to the first collider's rigid-body.
|
||
/// The impulse applied to the second collider's rigid-body is given by `-impulse`.
|
||
pub impulse: f32,
|
||
/// The friction impulses along the basis orthonormal to the contact normal, applied to the first
|
||
/// collider's rigid-body.
|
||
pub tangent_impulse: Vector2<f32>,
|
||
}
|
||
|
||
/// A contact manifold between two colliders.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct ContactManifold {
|
||
/// The contacts points.
|
||
pub points: Vec<ContactData>,
|
||
/// The contact normal of all the contacts of this manifold, expressed in the local space of the first shape.
|
||
pub local_n1: Vector3<f32>,
|
||
/// The contact normal of all the contacts of this manifold, expressed in the local space of the second shape.
|
||
pub local_n2: Vector3<f32>,
|
||
/// The first rigid-body involved in this contact manifold.
|
||
pub rigid_body1: Handle<rigidbody::RigidBody>,
|
||
/// The second rigid-body involved in this contact manifold.
|
||
pub rigid_body2: Handle<rigidbody::RigidBody>,
|
||
/// The world-space contact normal shared by all the contact in this contact manifold.
|
||
pub normal: Vector3<f32>,
|
||
}
|
||
|
||
/// Contact info for pair of colliders.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct ContactPair {
|
||
/// The first collider involved in the contact pair.
|
||
pub collider1: Handle<collider::Collider>,
|
||
/// The second collider involved in the contact pair.
|
||
pub collider2: Handle<collider::Collider>,
|
||
/// The set of contact manifolds between the two colliders.
|
||
/// All contact manifold contain themselves contact points between the colliders.
|
||
pub manifolds: Vec<ContactManifold>,
|
||
/// Is there any active contact in this contact pair?
|
||
pub has_any_active_contact: bool,
|
||
}
|
||
|
||
impl ContactPair {
|
||
/// Given the handle of a collider that is expected to be part of the collision,
|
||
/// return the other node involved in the collision.
|
||
/// Often one will have a list of contact pairs for some collider, but that collider
|
||
/// may be the first member of some pairs and the second member of other pairs.
|
||
/// This method simplifies determining which objects a collider has collided with.
|
||
#[inline]
|
||
pub fn other(&self, subject: Handle<collider::Collider>) -> Handle<collider::Collider> {
|
||
if subject == self.collider1 {
|
||
self.collider2
|
||
} else {
|
||
self.collider1
|
||
}
|
||
}
|
||
|
||
fn from_native(c: &rapier3d::geometry::ContactPair, physics: &PhysicsWorld) -> Option<Self> {
|
||
Some(ContactPair {
|
||
collider1: Handle::decode_from_u128(physics.colliders.get(c.collider1)?.user_data),
|
||
collider2: Handle::decode_from_u128(physics.colliders.get(c.collider2)?.user_data),
|
||
manifolds: c
|
||
.manifolds
|
||
.iter()
|
||
.filter_map(|m| {
|
||
Some(ContactManifold {
|
||
points: m
|
||
.points
|
||
.iter()
|
||
.map(|p| ContactData {
|
||
local_p1: p.local_p1.into(),
|
||
local_p2: p.local_p2.into(),
|
||
dist: p.dist,
|
||
impulse: p.data.impulse,
|
||
tangent_impulse: p.data.tangent_impulse,
|
||
})
|
||
.collect(),
|
||
local_n1: m.local_n1.into(),
|
||
local_n2: m.local_n2.into(),
|
||
rigid_body1: m.data.rigid_body1.and_then(|h| {
|
||
physics
|
||
.bodies
|
||
.get(h)
|
||
.map(|b| Handle::decode_from_u128(b.user_data))
|
||
})?,
|
||
rigid_body2: m.data.rigid_body2.and_then(|h| {
|
||
physics
|
||
.bodies
|
||
.get(h)
|
||
.map(|b| Handle::decode_from_u128(b.user_data))
|
||
})?,
|
||
normal: m.data.normal.into(),
|
||
})
|
||
})
|
||
.collect(),
|
||
has_any_active_contact: c.has_any_active_contact(),
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Intersection info for pair of colliders.
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct IntersectionPair {
|
||
/// The first collider involved in the contact pair.
|
||
pub collider1: Handle<collider::Collider>,
|
||
/// The second collider involved in the contact pair.
|
||
pub collider2: Handle<collider::Collider>,
|
||
/// Is there any active contact in this contact pair?
|
||
/// When false, this pair may just mean that bounding boxes are touching.
|
||
pub has_any_active_contact: bool,
|
||
}
|
||
|
||
impl IntersectionPair {
|
||
/// Given the handle of a collider that is expected to be part of the collision,
|
||
/// return the other node involved in the collision.
|
||
/// Often one will have a list of contact pairs for some collider, but that collider
|
||
/// may be the first member of some pairs and the second member of other pairs.
|
||
/// This method simplifies determining which objects a collider has collided with.
|
||
#[inline]
|
||
pub fn other(&self, subject: Handle<collider::Collider>) -> Handle<collider::Collider> {
|
||
if subject == self.collider1 {
|
||
self.collider2
|
||
} else {
|
||
self.collider1
|
||
}
|
||
}
|
||
}
|
||
|
||
pub(super) struct Container<S, A>
|
||
where
|
||
A: Hash + Eq + Clone,
|
||
{
|
||
set: S,
|
||
map: BiDirHashMap<A, Handle<Node>>,
|
||
}
|
||
|
||
fn convert_joint_params(
|
||
params: scene::joint::JointParams,
|
||
local_frame1: Isometry3<f32>,
|
||
local_frame2: Isometry3<f32>,
|
||
) -> GenericJoint {
|
||
let locked_axis = match params {
|
||
JointParams::BallJoint(_) => JointAxesMask::LOCKED_SPHERICAL_AXES,
|
||
JointParams::FixedJoint(_) => JointAxesMask::LOCKED_FIXED_AXES,
|
||
JointParams::PrismaticJoint(_) => JointAxesMask::LOCKED_PRISMATIC_AXES,
|
||
JointParams::RevoluteJoint(_) => JointAxesMask::LOCKED_REVOLUTE_AXES,
|
||
};
|
||
|
||
let mut joint = GenericJointBuilder::new(locked_axis)
|
||
.local_frame1(local_frame1.into())
|
||
.local_frame2(local_frame2.into())
|
||
.build();
|
||
|
||
match params {
|
||
scene::joint::JointParams::BallJoint(v) => {
|
||
if v.x_limits_enabled {
|
||
joint.set_limits(
|
||
JointAxis::AngX,
|
||
[v.x_limits_angles.start, v.x_limits_angles.end],
|
||
);
|
||
}
|
||
if v.y_limits_enabled {
|
||
joint.set_limits(
|
||
JointAxis::AngY,
|
||
[v.y_limits_angles.start, v.y_limits_angles.end],
|
||
);
|
||
}
|
||
if v.z_limits_enabled {
|
||
joint.set_limits(
|
||
JointAxis::AngZ,
|
||
[v.z_limits_angles.start, v.z_limits_angles.end],
|
||
);
|
||
}
|
||
}
|
||
scene::joint::JointParams::FixedJoint(_) => {}
|
||
scene::joint::JointParams::PrismaticJoint(v) => {
|
||
if v.limits_enabled {
|
||
joint.set_limits(JointAxis::LinX, [v.limits.start, v.limits.end]);
|
||
}
|
||
}
|
||
scene::joint::JointParams::RevoluteJoint(v) => {
|
||
if v.limits_enabled {
|
||
joint.set_limits(JointAxis::AngX, [v.limits.start, v.limits.end]);
|
||
}
|
||
}
|
||
}
|
||
|
||
joint
|
||
}
|
||
|
||
/// Creates new trimesh collider shape from given mesh node. It also bakes scale into
|
||
/// vertices of trimesh because rapier does not support collider scaling yet.
|
||
fn make_trimesh(
|
||
owner_inv_transform: Matrix4<f32>,
|
||
owner: Handle<Node>,
|
||
sources: &[GeometrySource],
|
||
nodes: &NodePool,
|
||
) -> Option<SharedShape> {
|
||
let mut mesh_builder = RawMeshBuilder::new(0, 0);
|
||
|
||
// Create inverse transform that will discard rotation and translation, but leave scaling and
|
||
// other parameters of global transform.
|
||
// When global transform of node is combined with this transform, we'll get relative transform
|
||
// with scale baked in. We need to do this because root's transform will be synced with body's
|
||
// but we don't want to bake entire transform including root's transform.
|
||
let root_inv_transform = owner_inv_transform;
|
||
|
||
for &source in sources {
|
||
if let Ok(mesh) = nodes.try_get_or_field_ref::<Mesh>(source.0) {
|
||
let global_transform = root_inv_transform * mesh.global_transform();
|
||
|
||
for surface in mesh.surfaces() {
|
||
let shared_data = surface.data();
|
||
if !shared_data.is_ok() {
|
||
continue;
|
||
}
|
||
let shared_data = shared_data.data_ref();
|
||
|
||
let vertices = &shared_data.vertex_buffer;
|
||
for triangle in shared_data.geometry_buffer.iter() {
|
||
let a = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[0] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
let b = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[1] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
let c = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[2] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
|
||
mesh_builder.insert(a);
|
||
mesh_builder.insert(b);
|
||
mesh_builder.insert(c);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
let raw_mesh = mesh_builder.build();
|
||
|
||
let vertices: Vec<Vec3> = raw_mesh
|
||
.vertices
|
||
.into_iter()
|
||
.map(|v| Vec3::new(v.x, v.y, v.z))
|
||
.collect();
|
||
|
||
let indices = raw_mesh
|
||
.triangles
|
||
.into_iter()
|
||
.map(|t| [t.0[0], t.0[1], t.0[2]])
|
||
.collect::<Vec<_>>();
|
||
|
||
if indices.is_empty() {
|
||
Log::writeln(
|
||
MessageKind::Warning,
|
||
format!(
|
||
"Failed to create triangle mesh collider for {}, it has no vertices!",
|
||
nodes[owner].name()
|
||
),
|
||
);
|
||
|
||
SharedShape::trimesh(vec![Vec3::new(0.0, 0.0, 0.0)], vec![[0, 0, 0]]).ok()
|
||
} else {
|
||
SharedShape::trimesh(vertices, indices).ok()
|
||
}
|
||
}
|
||
|
||
/// Creates new convex polyhedron collider shape from given mesh node. It also bakes scale into
|
||
/// vertices of trimesh because rapier does not support collider scaling yet.
|
||
fn make_polyhedron_shape(owner_inv_transform: Matrix4<f32>, mesh: &Mesh) -> SharedShape {
|
||
let mut mesh_builder = RawMeshBuilder::new(0, 0);
|
||
|
||
// Create inverse transform that will discard rotation and translation, but leave scaling and
|
||
// other parameters of global transform.
|
||
// When global transform of node is combined with this transform, we'll get relative transform
|
||
// with scale baked in. We need to do this because root's transform will be synced with body's
|
||
// but we don't want to bake entire transform including root's transform.
|
||
let root_inv_transform = owner_inv_transform;
|
||
|
||
let global_transform = root_inv_transform * mesh.global_transform();
|
||
|
||
for surface in mesh.surfaces() {
|
||
let shared_data = surface.data();
|
||
let shared_data = shared_data.data_ref();
|
||
|
||
let vertices = &shared_data.vertex_buffer;
|
||
for triangle in shared_data.geometry_buffer.iter() {
|
||
let a = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[0] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
let b = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[1] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
let c = RawVertex::from(
|
||
global_transform
|
||
.transform_point(&Point3::from(
|
||
vertices
|
||
.get(triangle[2] as usize)
|
||
.unwrap()
|
||
.read_3_f32(VertexAttributeUsage::Position)
|
||
.unwrap(),
|
||
))
|
||
.coords,
|
||
);
|
||
|
||
mesh_builder.insert(a);
|
||
mesh_builder.insert(b);
|
||
mesh_builder.insert(c);
|
||
}
|
||
}
|
||
|
||
let raw_mesh = mesh_builder.build();
|
||
|
||
let vertices: Vec<Vec3> = raw_mesh
|
||
.vertices
|
||
.into_iter()
|
||
.map(|v| Vec3::new(v.x, v.y, v.z))
|
||
.collect();
|
||
|
||
let indices = raw_mesh
|
||
.triangles
|
||
.into_iter()
|
||
.map(|t| [t.0[0], t.0[1], t.0[2]])
|
||
.collect::<Vec<_>>();
|
||
|
||
SharedShape::convex_decomposition(&vertices, &indices)
|
||
}
|
||
|
||
/// Creates height field shape from given terrain.
|
||
fn make_heightfield(terrain: &Terrain) -> Option<SharedShape> {
|
||
assert!(!terrain.chunks_ref().is_empty());
|
||
|
||
// HACK: Temporary solution for https://github.com/FyroxEngine/Fyrox/issues/365
|
||
let scale = terrain.local_transform().scale();
|
||
|
||
// Count rows and columns.
|
||
let height_map_size = terrain.height_map_size();
|
||
let chunk_size = height_map_size.map(|x| x - 3);
|
||
let chunk_min = terrain
|
||
.chunks_ref()
|
||
.iter()
|
||
.map(Chunk::grid_position)
|
||
.reduce(|a, b| a.inf(&b));
|
||
let chunk_max = terrain
|
||
.chunks_ref()
|
||
.iter()
|
||
.map(Chunk::grid_position)
|
||
.reduce(|a, b| a.sup(&b));
|
||
let (Some(chunk_min), Some(chunk_max)) = (chunk_min, chunk_max) else {
|
||
return None;
|
||
};
|
||
let row_range = chunk_max.y - chunk_min.y + 1;
|
||
let col_range = chunk_max.x - chunk_min.x + 1;
|
||
let nrows = chunk_size.y * row_range as u32 + 1;
|
||
let ncols = chunk_size.x * col_range as u32 + 1;
|
||
|
||
// Combine height map of each chunk into bigger one.
|
||
let mut data = vec![0.0; (nrows * ncols) as usize];
|
||
for chunk in terrain.chunks_ref() {
|
||
let texture = chunk.heightmap().data_ref();
|
||
let height_map = texture.data_of_type::<f32>().unwrap();
|
||
let pos = (chunk.grid_position() - chunk_min).map(|x| x as u32);
|
||
let (ox, oy) = (pos.x * chunk_size.x, pos.y * chunk_size.y);
|
||
for iy in 0..height_map_size.y - 2 {
|
||
for ix in 0..height_map_size.x - 2 {
|
||
let (x, y) = (ix + 1, iy + 1);
|
||
let value = height_map[(y * height_map_size.x + x) as usize] * scale.y;
|
||
data[((ox + ix) * nrows + oy + iy) as usize] = value;
|
||
}
|
||
}
|
||
}
|
||
let x_scale = terrain.chunk_size().x * scale.x * col_range as f32;
|
||
let z_scale = terrain.chunk_size().y * scale.z * row_range as f32;
|
||
let mut hf = HeightField::new(
|
||
Array2::new(nrows as usize, ncols as usize, data),
|
||
Vec3::new(x_scale, 1.0, z_scale),
|
||
);
|
||
if terrain.holes_enabled() {
|
||
let hole_mask_size = terrain.hole_mask_size();
|
||
for chunk in terrain.chunks_ref() {
|
||
let texture = chunk.hole_mask().map(|t| t.data_ref());
|
||
let hole_mask = texture.as_ref().map(|t| t.data_of_type::<u8>().unwrap());
|
||
let pos = (chunk.grid_position() - chunk_min).map(|x| x as u32);
|
||
let (ox, oy) = (pos.x * chunk_size.x, pos.y * chunk_size.y);
|
||
|
||
for iy in 0..hole_mask_size.y {
|
||
for ix in 0..hole_mask_size.x {
|
||
let is_hole = hole_mask
|
||
.map(|m| m[(iy * hole_mask_size.x + ix) as usize] < 128)
|
||
.unwrap_or_default();
|
||
let (x, y) = (ox + ix, oy + iy);
|
||
if is_hole {
|
||
hf.set_cell_status(
|
||
y as usize,
|
||
x as usize,
|
||
HeightFieldCellStatus::CELL_REMOVED,
|
||
);
|
||
} else {
|
||
hf.set_cell_status(y as usize, x as usize, HeightFieldCellStatus::empty());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// HeightField colliders naturally have their origin at their centers,
|
||
// so to position the collider correctly we must add half of the size to x and z.
|
||
Some(SharedShape::new(hf))
|
||
}
|
||
|
||
// Converts descriptor in a shared shape.
|
||
fn collider_shape_into_native_shape(
|
||
shape: &ColliderShape,
|
||
owner_inv_global_transform: Matrix4<f32>,
|
||
owner_collider: Handle<Node>,
|
||
pool: &NodePool,
|
||
) -> Option<SharedShape> {
|
||
match shape {
|
||
ColliderShape::Ball(ball) => Some(SharedShape::ball(ball.radius)),
|
||
|
||
ColliderShape::Cylinder(cylinder) => {
|
||
Some(SharedShape::cylinder(cylinder.half_height, cylinder.radius))
|
||
}
|
||
ColliderShape::Cone(cone) => Some(SharedShape::cone(cone.half_height, cone.radius)),
|
||
ColliderShape::Cuboid(cuboid) => Some(SharedShape(Arc::new(Cuboid::new(
|
||
cuboid.half_extents.into(),
|
||
)))),
|
||
ColliderShape::Capsule(capsule) => Some(SharedShape::capsule(
|
||
capsule.begin.into(),
|
||
capsule.end.into(),
|
||
capsule.radius,
|
||
)),
|
||
ColliderShape::Segment(segment) => Some(SharedShape::segment(
|
||
segment.begin.into(),
|
||
segment.end.into(),
|
||
)),
|
||
ColliderShape::Triangle(triangle) => Some(SharedShape::triangle(
|
||
triangle.a.into(),
|
||
triangle.b.into(),
|
||
triangle.c.into(),
|
||
)),
|
||
ColliderShape::Trimesh(trimesh) => {
|
||
if trimesh.sources.is_empty() {
|
||
None
|
||
} else {
|
||
make_trimesh(
|
||
owner_inv_global_transform,
|
||
owner_collider,
|
||
&trimesh.sources,
|
||
pool,
|
||
)
|
||
}
|
||
}
|
||
ColliderShape::Heightfield(heightfield) => pool
|
||
.try_get_or_field_ref::<Terrain>(heightfield.geometry_source.0)
|
||
.ok()
|
||
.and_then(make_heightfield),
|
||
ColliderShape::Polyhedron(polyhedron) => pool
|
||
.try_get_or_field_ref::<Mesh>(polyhedron.geometry_source.0)
|
||
.ok()
|
||
.map(|mesh| make_polyhedron_shape(owner_inv_global_transform, mesh)),
|
||
}
|
||
}
|
||
|
||
/// Parameters for a time-step of the physics engine.
|
||
///
|
||
/// # Notes
|
||
///
|
||
/// This is almost one-to-one copy of Rapier's integration parameters with custom attributes for
|
||
/// each parameter.
|
||
#[derive(Copy, Clone, Visit, Reflect, Debug, PartialEq)]
|
||
#[visit(optional)]
|
||
#[reflect(type_uuid = "bd4ca07f-34a5-4cf8-a70e-24919124545b")]
|
||
pub struct IntegrationParameters {
|
||
/// The time step length, default is None - this means that physics simulation will use engine's
|
||
/// time step.
|
||
#[reflect(min_value = 0.0)]
|
||
pub dt: Option<f32>,
|
||
|
||
/// Minimum timestep size when using CCD with multiple substeps (default `1.0 / 60.0 / 100.0`)
|
||
///
|
||
/// When CCD with multiple substeps is enabled, the timestep is subdivided into smaller pieces.
|
||
/// This timestep subdivision won't generate timestep lengths smaller than `min_ccd_dt`.
|
||
///
|
||
/// Setting this to a large value will reduce the opportunity to performing CCD substepping,
|
||
/// resulting in potentially more time dropped by the motion-clamping mechanism. Setting this
|
||
/// to an very small value may lead to numerical instabilities.
|
||
#[reflect(min_value = 0.0)]
|
||
pub min_ccd_dt: f32,
|
||
|
||
/// Amount of penetration the engine wont attempt to correct (default: `0.002m`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub allowed_linear_error: f32,
|
||
|
||
/// Maximum amount of penetration the solver will attempt to resolve in one timestep (default: `10.0`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub normalized_max_corrective_velocity: f32,
|
||
|
||
/// The maximal distance separating two objects that will generate predictive contacts (default: `0.002`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub prediction_distance: f32,
|
||
|
||
/// The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub num_solver_iterations: usize,
|
||
|
||
/// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub num_internal_pgs_iterations: usize,
|
||
|
||
/// Minimum number of dynamic bodies in each active island (default: `128`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub min_island_size: u32,
|
||
|
||
/// Maximum number of substeps performed by the solver (default: `4`).
|
||
#[reflect(min_value = 0.0)]
|
||
pub max_ccd_substeps: u32,
|
||
|
||
/// The coefficient in `[0, 1]` applied to warmstart impulses, i.e., impulses that are used as the
|
||
/// initial solution (instead of 0) at the next simulation step. Default `1.0`.
|
||
pub warmstart_coefficient: f32,
|
||
|
||
/// The approximate size of most dynamic objects in the scene.
|
||
///
|
||
/// This value can be understood as the number of units-per-meter in your physical world compared
|
||
/// to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100
|
||
/// pixels, set the `[`Self::length_unit`]` parameter to 100.0. The physics engine will interpret
|
||
/// it as if 100 pixels is equivalent to 1 meter in its various internal threshold.
|
||
/// (default `1.0`).
|
||
pub length_unit: f32,
|
||
|
||
/// The number of stabilization iterations run at each solver iterations (default: `2`).
|
||
pub num_internal_stabilization_iterations: usize,
|
||
}
|
||
|
||
impl Default for IntegrationParameters {
|
||
fn default() -> Self {
|
||
Self {
|
||
dt: None,
|
||
min_ccd_dt: 1.0 / 60.0 / 100.0,
|
||
warmstart_coefficient: 1.0,
|
||
allowed_linear_error: 0.002,
|
||
normalized_max_corrective_velocity: 10.0,
|
||
prediction_distance: 0.002,
|
||
num_internal_pgs_iterations: 1,
|
||
num_solver_iterations: 4,
|
||
min_island_size: 128,
|
||
max_ccd_substeps: 4,
|
||
length_unit: 1.0,
|
||
num_internal_stabilization_iterations: 4,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Physics world is responsible for physics simulation in the engine. There is a very few public
|
||
/// methods, mostly for ray casting. You should add physical entities using scene graph nodes, such
|
||
/// as RigidBody, Collider, Joint.
|
||
#[derive(Visit, Reflect)]
|
||
#[reflect(type_uuid = "17e663b8-98a4-46db-be71-a336246f571a")]
|
||
pub struct PhysicsWorld {
|
||
/// A flag that defines whether physics simulation is enabled or not.
|
||
pub enabled: InheritableVariable<bool>,
|
||
|
||
/// A set of parameters that define behavior of every rigid body.
|
||
#[visit(optional)]
|
||
pub integration_parameters: InheritableVariable<IntegrationParameters>,
|
||
|
||
/// Current gravity vector. Default is (0.0, -9.81, 0.0)
|
||
pub gravity: InheritableVariable<Vector3<f32>>,
|
||
|
||
/// Performance statistics of a single simulation step.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
pub performance_statistics: PhysicsPerformanceStatistics,
|
||
|
||
// Current physics pipeline.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
pipeline: PhysicsPipeline,
|
||
// Broad phase performs rough intersection checks.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
broad_phase: DefaultBroadPhase,
|
||
// Narrow phase is responsible for precise contact generation.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
narrow_phase: NarrowPhase,
|
||
// A continuous collision detection solver.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
ccd_solver: CCDSolver,
|
||
// Structure responsible for maintaining the set of active rigid-bodies, and putting non-moving
|
||
// rigid-bodies to sleep to save computation times.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
islands: IslandManager,
|
||
// A container of rigid bodies.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
bodies: RigidBodySet,
|
||
// A container of colliders.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
pub(crate) colliders: ColliderSet,
|
||
// A container of impulse joints.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
joints: Container<ImpulseJointSet, ImpulseJointHandle>,
|
||
// A container of multibody joints.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
multibody_joints: Container<MultibodyJointSet, MultibodyJointHandle>,
|
||
// Event handler collects info about contacts and proximity events.
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
event_handler: Box<dyn EventHandler>,
|
||
#[visit(skip)]
|
||
#[reflect(hidden)]
|
||
debug_render_pipeline: Mutex<DebugRenderPipeline>,
|
||
}
|
||
|
||
impl Clone for PhysicsWorld {
|
||
fn clone(&self) -> Self {
|
||
PhysicsWorld {
|
||
enabled: self.enabled.clone(),
|
||
integration_parameters: self.integration_parameters.clone(),
|
||
gravity: self.gravity.clone(),
|
||
..Default::default()
|
||
}
|
||
}
|
||
}
|
||
|
||
fn isometry_from_global_transform(transform: &Matrix4<f32>) -> Isometry3<f32> {
|
||
Isometry3 {
|
||
translation: Translation3::new(transform[12], transform[13], transform[14]),
|
||
rotation: UnitQuaternion::from_matrix_eps(
|
||
&transform.basis(),
|
||
f32::EPSILON,
|
||
16,
|
||
UnitQuaternion::identity(),
|
||
),
|
||
}
|
||
}
|
||
|
||
fn calculate_local_frames(
|
||
joint: &dyn NodeTrait,
|
||
body1: &dyn NodeTrait,
|
||
body2: &dyn NodeTrait,
|
||
) -> (Isometry3<f32>, Isometry3<f32>) {
|
||
let joint_isometry = isometry_from_global_transform(&joint.global_transform());
|
||
|
||
(
|
||
isometry_from_global_transform(&body1.global_transform()).inverse() * joint_isometry,
|
||
isometry_from_global_transform(&body2.global_transform()).inverse() * joint_isometry,
|
||
)
|
||
}
|
||
|
||
fn u32_to_group(v: u32) -> rapier3d::geometry::Group {
|
||
rapier3d::geometry::Group::from_bits(v).unwrap_or_else(rapier3d::geometry::Group::all)
|
||
}
|
||
|
||
/// A filter tha describes what collider should be included or excluded from a scene query.
|
||
#[derive(Copy, Clone, Default)]
|
||
#[allow(clippy::type_complexity)]
|
||
pub struct QueryFilter<'a> {
|
||
/// Flags indicating what particular type of colliders should be excluded from the scene query.
|
||
pub flags: collider::QueryFilterFlags,
|
||
/// If set, only colliders with collision groups compatible with this one will
|
||
/// be included in the scene query.
|
||
pub groups: Option<collider::InteractionGroups>,
|
||
/// If set, this collider will be excluded from the scene query.
|
||
pub exclude_collider: Option<Handle<Node>>,
|
||
/// If set, any collider attached to this rigid-body will be excluded from the scene query.
|
||
pub exclude_rigid_body: Option<Handle<Node>>,
|
||
/// If set, any collider for which this closure returns false will be excluded from the scene query.
|
||
pub predicate: Option<&'a dyn Fn(Handle<Node>, &collider::Collider) -> bool>,
|
||
}
|
||
|
||
/// The result of a time-of-impact (TOI) computation.
|
||
#[derive(Copy, Clone, Debug)]
|
||
pub struct TOI {
|
||
/// The time at which the objects touch.
|
||
pub toi: f32,
|
||
/// The local-space closest point on the first shape at the time of impact.
|
||
///
|
||
/// Undefined if `status` is `Penetrating`.
|
||
pub witness1: Point3<f32>,
|
||
/// The local-space closest point on the second shape at the time of impact.
|
||
///
|
||
/// Undefined if `status` is `Penetrating`.
|
||
pub witness2: Point3<f32>,
|
||
/// The local-space outward normal on the first shape at the time of impact.
|
||
///
|
||
/// Undefined if `status` is `Penetrating`.
|
||
pub normal1: UnitVector3<f32>,
|
||
/// The local-space outward normal on the second shape at the time of impact.
|
||
///
|
||
/// Undefined if `status` is `Penetrating`.
|
||
pub normal2: UnitVector3<f32>,
|
||
/// The way the time-of-impact computation algorithm terminated.
|
||
pub status: collider::TOIStatus,
|
||
}
|
||
|
||
impl PhysicsWorld {
|
||
/// Creates a new instance of the physics world.
|
||
pub(super) fn new() -> Self {
|
||
Self {
|
||
enabled: true.into(),
|
||
pipeline: PhysicsPipeline::new(),
|
||
gravity: Vector3::new(0.0, -9.81, 0.0).into(),
|
||
integration_parameters: IntegrationParameters::default().into(),
|
||
broad_phase: DefaultBroadPhase::new(),
|
||
narrow_phase: NarrowPhase::new(),
|
||
ccd_solver: CCDSolver::new(),
|
||
islands: IslandManager::new(),
|
||
bodies: RigidBodySet::new(),
|
||
colliders: ColliderSet::new(),
|
||
joints: Container {
|
||
set: ImpulseJointSet::new(),
|
||
map: Default::default(),
|
||
},
|
||
multibody_joints: Container {
|
||
set: MultibodyJointSet::new(),
|
||
map: Default::default(),
|
||
},
|
||
event_handler: Box::new(()),
|
||
performance_statistics: Default::default(),
|
||
debug_render_pipeline: Default::default(),
|
||
}
|
||
}
|
||
|
||
/// Update the physics pipeline with a timestep of the given length.
|
||
///
|
||
/// * `dt`: The amount of time that has passed since the previous update.
|
||
/// This may be overriden by [`PhysicsWorld::integration_parameters`] if
|
||
/// `integration_parameters.dt` is `Some`, in which case that value is used
|
||
/// instead of this argument.
|
||
/// * `dt_enabled`: If this is true then `dt` is used as usual, but if this is false
|
||
/// then both the `dt` argument and `integration_parameters.dt` are ignored and
|
||
/// a `dt` of zero is used instead, freezing all physics. This corresponds to the
|
||
/// [`GraphUpdateSwitches::physics_dt`](crate::scene::graph::GraphUpdateSwitches::physics_dt).
|
||
pub(super) fn update(&mut self, dt: f32, dt_enabled: bool) {
|
||
let time = instant::Instant::now();
|
||
let parameter_dt = self.integration_parameters.dt;
|
||
let parameter_dt = if parameter_dt == Some(0.0) {
|
||
None
|
||
} else {
|
||
parameter_dt
|
||
};
|
||
let dt = if dt_enabled {
|
||
parameter_dt.unwrap_or(dt)
|
||
} else {
|
||
0.0
|
||
};
|
||
|
||
if *self.enabled {
|
||
let integration_parameters = rapier3d::dynamics::IntegrationParameters {
|
||
dt,
|
||
min_ccd_dt: self.integration_parameters.min_ccd_dt,
|
||
contact_softness: rapier3d::dynamics::SpringCoefficients::contact_defaults(),
|
||
warmstart_coefficient: self.integration_parameters.warmstart_coefficient,
|
||
length_unit: self.integration_parameters.length_unit,
|
||
normalized_allowed_linear_error: self.integration_parameters.allowed_linear_error,
|
||
normalized_max_corrective_velocity: self
|
||
.integration_parameters
|
||
.normalized_max_corrective_velocity,
|
||
normalized_prediction_distance: self.integration_parameters.prediction_distance,
|
||
num_solver_iterations: self.integration_parameters.num_solver_iterations,
|
||
num_internal_pgs_iterations: self
|
||
.integration_parameters
|
||
.num_internal_pgs_iterations,
|
||
num_internal_stabilization_iterations: self
|
||
.integration_parameters
|
||
.num_internal_stabilization_iterations,
|
||
min_island_size: self.integration_parameters.min_island_size as usize,
|
||
max_ccd_substeps: self.integration_parameters.max_ccd_substeps as usize,
|
||
friction_model: FrictionModel::default(),
|
||
};
|
||
|
||
self.pipeline.step(
|
||
(*self.gravity).into(),
|
||
&integration_parameters,
|
||
&mut self.islands,
|
||
&mut self.broad_phase,
|
||
&mut self.narrow_phase,
|
||
&mut self.bodies,
|
||
&mut self.colliders,
|
||
&mut self.joints.set,
|
||
&mut self.multibody_joints.set,
|
||
&mut self.ccd_solver,
|
||
&(),
|
||
&*self.event_handler,
|
||
);
|
||
}
|
||
|
||
self.performance_statistics.step_time += instant::Instant::now() - time;
|
||
}
|
||
|
||
pub(super) fn add_body(&mut self, owner: Handle<Node>, mut body: RigidBody) -> RigidBodyHandle {
|
||
body.user_data = owner.encode_to_u128();
|
||
self.bodies.insert(body)
|
||
}
|
||
|
||
pub(crate) fn remove_body(&mut self, handle: RigidBodyHandle) {
|
||
self.bodies.remove(
|
||
handle,
|
||
&mut self.islands,
|
||
&mut self.colliders,
|
||
&mut self.joints.set,
|
||
&mut self.multibody_joints.set,
|
||
true,
|
||
);
|
||
}
|
||
|
||
pub(super) fn add_collider(
|
||
&mut self,
|
||
owner: Handle<Node>,
|
||
parent_body: RigidBodyHandle,
|
||
mut collider: Collider,
|
||
) -> ColliderHandle {
|
||
collider.user_data = owner.encode_to_u128();
|
||
self.colliders
|
||
.insert_with_parent(collider, parent_body, &mut self.bodies)
|
||
}
|
||
|
||
pub(crate) fn remove_collider(&mut self, handle: ColliderHandle) -> bool {
|
||
self.colliders
|
||
.remove(handle, &mut self.islands, &mut self.bodies, false)
|
||
.is_some()
|
||
}
|
||
|
||
pub(super) fn add_joint(
|
||
&mut self,
|
||
owner: Handle<Node>,
|
||
body1: RigidBodyHandle,
|
||
body2: RigidBodyHandle,
|
||
joint: GenericJoint,
|
||
) -> ImpulseJointHandle {
|
||
let handle = self.joints.set.insert(body1, body2, joint, false);
|
||
self.joints.map.insert(handle, owner);
|
||
handle
|
||
}
|
||
|
||
pub(crate) fn remove_joint(&mut self, handle: ImpulseJointHandle) {
|
||
if self.joints.set.remove(handle, false).is_some() {
|
||
assert!(self.joints.map.remove_by_key(&handle).is_some());
|
||
}
|
||
}
|
||
|
||
/// Draws physics world. Very useful for debugging, it allows you to see where are
|
||
/// rigid bodies, which colliders they have and so on.
|
||
pub fn draw(&self, context: &mut SceneDrawingContext) {
|
||
self.debug_render_pipeline.safe_lock().render(
|
||
context,
|
||
&self.bodies,
|
||
&self.colliders,
|
||
&self.joints.set,
|
||
&self.multibody_joints.set,
|
||
&self.narrow_phase,
|
||
);
|
||
}
|
||
|
||
/// Casts a ray with given options.
|
||
pub fn cast_ray<S: QueryResultsStorage>(&self, opts: RayCastOptions, query_buffer: &mut S) {
|
||
let time = instant::Instant::now();
|
||
|
||
let query = self.broad_phase.as_query_pipeline(
|
||
&DefaultQueryDispatcher,
|
||
&self.bodies,
|
||
&self.colliders,
|
||
rapier3d::pipeline::QueryFilter::new().groups(InteractionGroups::new(
|
||
u32_to_group(opts.groups.memberships.0),
|
||
u32_to_group(opts.groups.filter.0),
|
||
Default::default(),
|
||
)),
|
||
);
|
||
|
||
query_buffer.clear();
|
||
let ray = Ray::new(
|
||
opts.ray_origin.into(),
|
||
opts.ray_direction
|
||
.try_normalize(f32::EPSILON)
|
||
.unwrap_or_default()
|
||
.into(),
|
||
);
|
||
for (_, collider, intersection) in query.intersect_ray(ray, opts.max_len, true) {
|
||
query_buffer.push(Intersection {
|
||
collider: Handle::decode_from_u128(collider.user_data),
|
||
normal: intersection.normal.into(),
|
||
position: ray.point_at(intersection.time_of_impact).into(),
|
||
feature: intersection.feature.into(),
|
||
toi: intersection.time_of_impact,
|
||
});
|
||
}
|
||
if opts.sort_results {
|
||
query_buffer.sort_intersections_by(|a, b| {
|
||
if a.toi > b.toi {
|
||
Ordering::Greater
|
||
} else if a.toi < b.toi {
|
||
Ordering::Less
|
||
} else {
|
||
Ordering::Equal
|
||
}
|
||
})
|
||
}
|
||
|
||
self.performance_statistics.total_ray_cast_time.set(
|
||
self.performance_statistics.total_ray_cast_time.get()
|
||
+ (instant::Instant::now() - time),
|
||
);
|
||
}
|
||
|
||
/// Casts a shape at a constant linear velocity and retrieve the first collider it hits.
|
||
///
|
||
/// This is similar to ray-casting except that we are casting a whole shape instead of just a
|
||
/// point (the ray origin). In the resulting `TOI`, witness and normal 1 refer to the world
|
||
/// collider, and are in world space.
|
||
///
|
||
/// # Parameters
|
||
///
|
||
/// * `graph` - a reference to the scene graph.
|
||
/// * `shape` - The shape to cast.
|
||
/// * `shape_pos` - The initial position of the shape to cast.
|
||
/// * `shape_vel` - The constant velocity of the shape to cast (i.e. the cast direction).
|
||
/// * `max_toi` - The maximum time-of-impact that can be reported by this cast. This effectively
|
||
/// limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
|
||
/// * `stop_at_penetration` - If set to `false`, the linear shape-cast won’t immediately stop if
|
||
/// the shape is penetrating another shape at its starting point **and** its trajectory is such
|
||
/// that it’s on a path to exist that penetration state.
|
||
/// * `filter`: set of rules used to determine which collider is taken into account by this scene
|
||
/// query.
|
||
pub fn cast_shape(
|
||
&self,
|
||
graph: &Graph,
|
||
shape: &dyn Shape,
|
||
shape_pos: &Isometry3<f32>,
|
||
shape_vel: &Vector3<f32>,
|
||
max_toi: f32,
|
||
stop_at_penetration: bool,
|
||
filter: QueryFilter,
|
||
) -> Option<(Handle<Node>, TOI)> {
|
||
let predicate = |handle: ColliderHandle, _: &Collider| -> bool {
|
||
if let Some(pred) = filter.predicate {
|
||
let h = Handle::decode_from_u128(self.colliders.get(handle).unwrap().user_data);
|
||
pred(
|
||
h,
|
||
graph
|
||
.node(h)
|
||
.self_or_field_ref::<collider::Collider>()
|
||
.unwrap(),
|
||
)
|
||
} else {
|
||
true
|
||
}
|
||
};
|
||
|
||
let filter = rapier3d::pipeline::QueryFilter {
|
||
flags: rapier3d::pipeline::QueryFilterFlags::from_bits(filter.flags.bits()).unwrap(),
|
||
groups: filter.groups.map(|g| {
|
||
InteractionGroups::new(
|
||
u32_to_group(g.memberships.0),
|
||
u32_to_group(g.filter.0),
|
||
Default::default(),
|
||
)
|
||
}),
|
||
exclude_collider: filter
|
||
.exclude_collider
|
||
.and_then(|h| graph.try_get_of_type::<collider::Collider>(h).ok())
|
||
.map(|c| c.native.get()),
|
||
exclude_rigid_body: filter
|
||
.exclude_collider
|
||
.and_then(|h| graph.try_get_of_type::<rigidbody::RigidBody>(h).ok())
|
||
.map(|c| c.native.get()),
|
||
predicate: Some(&predicate),
|
||
};
|
||
|
||
let query = self.broad_phase.as_query_pipeline(
|
||
&DefaultQueryDispatcher,
|
||
&self.bodies,
|
||
&self.colliders,
|
||
filter,
|
||
);
|
||
|
||
let opts = ShapeCastOptions {
|
||
max_time_of_impact: max_toi,
|
||
target_distance: 0.0,
|
||
stop_at_penetration,
|
||
compute_impact_geometry_on_penetration: true,
|
||
};
|
||
|
||
query
|
||
.cast_shape(&Pose3::from(*shape_pos), (*shape_vel).into(), shape, opts)
|
||
.map(|(handle, toi)| {
|
||
(
|
||
Handle::decode_from_u128(self.colliders.get(handle).unwrap().user_data),
|
||
TOI {
|
||
toi: toi.time_of_impact,
|
||
witness1: toi.witness1.into(),
|
||
witness2: toi.witness2.into(),
|
||
normal1: UnitVector3::new_normalize(toi.normal1.into()),
|
||
normal2: UnitVector3::new_normalize(toi.normal2.into()),
|
||
status: toi.status.into(),
|
||
},
|
||
)
|
||
})
|
||
}
|
||
|
||
pub(crate) fn set_rigid_body_position(
|
||
&mut self,
|
||
rigid_body: &scene::rigidbody::RigidBody,
|
||
new_global_transform: &Matrix4<f32>,
|
||
) {
|
||
if let Some(native) = self.bodies.get_mut(rigid_body.native.get()) {
|
||
native.set_position(
|
||
isometry_from_global_transform(new_global_transform).into(),
|
||
// Do not wake up body, it is too expensive and must be done **only** by explicit
|
||
// `wake_up` call!
|
||
false,
|
||
);
|
||
}
|
||
}
|
||
|
||
pub(crate) fn sync_rigid_body_node(
|
||
&mut self,
|
||
rigid_body: &mut scene::rigidbody::RigidBody,
|
||
parent_transform: Matrix4<f32>,
|
||
) {
|
||
if *self.enabled {
|
||
if let Some(native) = self.bodies.get(rigid_body.native.get()) {
|
||
if native.body_type() != RigidBodyType::Fixed {
|
||
let local_transform: Matrix4<f32> = parent_transform
|
||
.try_inverse()
|
||
.unwrap_or_else(Matrix4::identity)
|
||
* Isometry3::from(*native.position()).to_homogeneous();
|
||
|
||
let new_local_rotation = UnitQuaternion::from_matrix_eps(
|
||
&local_transform.basis(),
|
||
f32::EPSILON,
|
||
16,
|
||
UnitQuaternion::identity(),
|
||
);
|
||
let new_local_position = Vector3::new(
|
||
local_transform[12],
|
||
local_transform[13],
|
||
local_transform[14],
|
||
);
|
||
|
||
// Do not touch local transform if position/rotation is not changing. This will
|
||
// prevent redundant update of its global transform, which in its turn save some
|
||
// CPU cycles.
|
||
let local_transform = rigid_body.local_transform();
|
||
if **local_transform.position() != new_local_position
|
||
|| **local_transform.rotation() != new_local_rotation
|
||
{
|
||
rigid_body
|
||
.local_transform_mut()
|
||
.set_position(new_local_position)
|
||
.set_rotation(new_local_rotation);
|
||
}
|
||
|
||
rigid_body
|
||
.lin_vel
|
||
.set_value_with_flags(native.linvel().into(), VariableFlags::MODIFIED);
|
||
rigid_body
|
||
.ang_vel
|
||
.set_value_with_flags(native.angvel().into(), VariableFlags::MODIFIED);
|
||
rigid_body.sleeping = native.is_sleeping();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
pub(crate) fn sync_to_rigid_body_node(
|
||
&mut self,
|
||
handle: Handle<Node>,
|
||
rigid_body_node: &scene::rigidbody::RigidBody,
|
||
) {
|
||
if !rigid_body_node.is_globally_enabled() {
|
||
self.remove_body(rigid_body_node.native.get());
|
||
rigid_body_node.native.set(Default::default());
|
||
return;
|
||
}
|
||
|
||
// Important notes!
|
||
// 1) `get_mut` is **very** expensive because it forces physics engine to recalculate contacts
|
||
// and a lot of other stuff, this is why we need `anything_changed` flag.
|
||
if rigid_body_node.native.get() != RigidBodyHandle::invalid() {
|
||
let mut actions = rigid_body_node.actions.safe_lock();
|
||
if rigid_body_node.need_sync_model() || !actions.is_empty() {
|
||
if let Some(native) = self.bodies.get_mut(rigid_body_node.native.get()) {
|
||
// Sync native rigid body's properties with scene node's in case if they
|
||
// were changed by user.
|
||
rigid_body_node
|
||
.body_type
|
||
.try_sync_model(|v| native.set_body_type(v.into(), false));
|
||
rigid_body_node
|
||
.lin_vel
|
||
.try_sync_model(|v| native.set_linvel(v.into(), true));
|
||
rigid_body_node
|
||
.ang_vel
|
||
.try_sync_model(|v| native.set_angvel(v.into(), true));
|
||
rigid_body_node.mass.try_sync_model(|v| {
|
||
match *rigid_body_node.mass_properties_type {
|
||
RigidBodyMassPropertiesType::Default => {
|
||
native.set_additional_mass(v, false);
|
||
}
|
||
RigidBodyMassPropertiesType::Additional {
|
||
center_of_mass,
|
||
principal_inertia,
|
||
} => {
|
||
native.set_additional_mass_properties(
|
||
MassProperties::new(
|
||
center_of_mass.into(),
|
||
v,
|
||
principal_inertia.into(),
|
||
),
|
||
false,
|
||
);
|
||
}
|
||
};
|
||
});
|
||
rigid_body_node.mass_properties_type.try_sync_model(|v| {
|
||
match v {
|
||
RigidBodyMassPropertiesType::Default => {
|
||
native.set_additional_mass(*rigid_body_node.mass, false);
|
||
}
|
||
RigidBodyMassPropertiesType::Additional {
|
||
center_of_mass,
|
||
principal_inertia,
|
||
} => {
|
||
native.set_additional_mass_properties(
|
||
MassProperties::new(
|
||
center_of_mass.into(),
|
||
*rigid_body_node.mass,
|
||
principal_inertia.into(),
|
||
),
|
||
false,
|
||
);
|
||
}
|
||
};
|
||
});
|
||
rigid_body_node
|
||
.lin_damping
|
||
.try_sync_model(|v| native.set_linear_damping(v));
|
||
rigid_body_node
|
||
.ang_damping
|
||
.try_sync_model(|v| native.set_angular_damping(v));
|
||
rigid_body_node
|
||
.ccd_enabled
|
||
.try_sync_model(|v| native.enable_ccd(v));
|
||
rigid_body_node.can_sleep.try_sync_model(|v| {
|
||
if v {
|
||
*native.activation_mut() = RigidBodyActivation::active();
|
||
} else {
|
||
*native.activation_mut() = RigidBodyActivation::cannot_sleep();
|
||
};
|
||
});
|
||
rigid_body_node
|
||
.translation_locked
|
||
.try_sync_model(|v| native.lock_translations(v, false));
|
||
rigid_body_node.x_rotation_locked.try_sync_model(|v| {
|
||
native.set_enabled_rotations(
|
||
!v,
|
||
!native.is_rotation_locked()[1],
|
||
!native.is_rotation_locked()[2],
|
||
false,
|
||
);
|
||
});
|
||
rigid_body_node.y_rotation_locked.try_sync_model(|v| {
|
||
native.set_enabled_rotations(
|
||
!native.is_rotation_locked()[0],
|
||
!v,
|
||
!native.is_rotation_locked()[2],
|
||
false,
|
||
);
|
||
});
|
||
rigid_body_node.z_rotation_locked.try_sync_model(|v| {
|
||
native.set_enabled_rotations(
|
||
!native.is_rotation_locked()[0],
|
||
!native.is_rotation_locked()[1],
|
||
!v,
|
||
false,
|
||
);
|
||
});
|
||
rigid_body_node
|
||
.dominance
|
||
.try_sync_model(|v| native.set_dominance_group(v));
|
||
rigid_body_node
|
||
.gravity_scale
|
||
.try_sync_model(|v| native.set_gravity_scale(v, false));
|
||
|
||
// We must reset any forces applied at previous update step, otherwise physics engine
|
||
// will keep pushing the rigid body infinitely.
|
||
if rigid_body_node.reset_forces.replace(false) {
|
||
native.reset_forces(false);
|
||
native.reset_torques(false);
|
||
}
|
||
|
||
while let Some(action) = actions.pop_front() {
|
||
match action {
|
||
ApplyAction::Force(force) => {
|
||
native.add_force(force.into(), false);
|
||
rigid_body_node.reset_forces.set(true);
|
||
}
|
||
ApplyAction::Torque(torque) => {
|
||
native.add_torque(torque.into(), false);
|
||
rigid_body_node.reset_forces.set(true);
|
||
}
|
||
ApplyAction::ForceAtPoint { force, point } => {
|
||
native.add_force_at_point(force.into(), point.into(), false);
|
||
rigid_body_node.reset_forces.set(true);
|
||
}
|
||
ApplyAction::Impulse(impulse) => {
|
||
native.apply_impulse(impulse.into(), false)
|
||
}
|
||
ApplyAction::TorqueImpulse(impulse) => {
|
||
native.apply_torque_impulse(impulse.into(), false)
|
||
}
|
||
ApplyAction::ImpulseAtPoint { impulse, point } => {
|
||
native.apply_impulse_at_point(impulse.into(), point.into(), false)
|
||
}
|
||
ApplyAction::WakeUp => native.wake_up(true),
|
||
ApplyAction::NextTranslation(position) => {
|
||
native.set_next_kinematic_translation(position.into())
|
||
}
|
||
ApplyAction::NextRotation(rotation) => {
|
||
native.set_next_kinematic_rotation(rotation.into())
|
||
}
|
||
ApplyAction::NextPosition(position) => {
|
||
native.set_next_kinematic_position(position.into())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
let mut builder = RigidBodyBuilder::new(rigid_body_node.body_type().into())
|
||
.pose(isometry_from_global_transform(&rigid_body_node.global_transform()).into())
|
||
.ccd_enabled(rigid_body_node.is_ccd_enabled())
|
||
.additional_mass(rigid_body_node.mass())
|
||
.angvel((*rigid_body_node.ang_vel).into())
|
||
.linvel((*rigid_body_node.lin_vel).into())
|
||
.linear_damping(*rigid_body_node.lin_damping)
|
||
.angular_damping(*rigid_body_node.ang_damping)
|
||
.can_sleep(rigid_body_node.is_can_sleep())
|
||
.dominance_group(rigid_body_node.dominance())
|
||
.gravity_scale(rigid_body_node.gravity_scale())
|
||
.enabled_rotations(
|
||
!rigid_body_node.is_x_rotation_locked(),
|
||
!rigid_body_node.is_y_rotation_locked(),
|
||
!rigid_body_node.is_z_rotation_locked(),
|
||
);
|
||
|
||
match *rigid_body_node.mass_properties_type {
|
||
RigidBodyMassPropertiesType::Default => {
|
||
builder = builder.additional_mass(*rigid_body_node.mass);
|
||
}
|
||
RigidBodyMassPropertiesType::Additional {
|
||
center_of_mass,
|
||
principal_inertia,
|
||
} => {
|
||
builder = builder.additional_mass_properties(MassProperties::new(
|
||
center_of_mass.into(),
|
||
*rigid_body_node.mass,
|
||
principal_inertia.into(),
|
||
));
|
||
}
|
||
};
|
||
if rigid_body_node.is_translation_locked() {
|
||
builder = builder.lock_translations();
|
||
}
|
||
|
||
rigid_body_node
|
||
.native
|
||
.set(self.add_body(handle, builder.build()));
|
||
|
||
Log::writeln(
|
||
MessageKind::Information,
|
||
format!(
|
||
"Native rigid body was created for node {}",
|
||
rigid_body_node.name()
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
pub(crate) fn sync_to_collider_node(
|
||
&mut self,
|
||
nodes: &NodePool,
|
||
handle: Handle<Node>,
|
||
collider_node: &scene::collider::Collider,
|
||
) {
|
||
if !collider_node.is_globally_enabled() {
|
||
self.remove_collider(collider_node.native.get());
|
||
collider_node.native.set(Default::default());
|
||
return;
|
||
}
|
||
|
||
let anything_changed = collider_node.needs_sync_model();
|
||
|
||
// Important notes!
|
||
// 1) The collider node may lack backing native physics collider in case if it
|
||
// is not attached to a rigid body.
|
||
// 2) `get_mut` is **very** expensive because it forces physics engine to recalculate contacts
|
||
// and a lot of other stuff, this is why we need `anything_changed` flag.
|
||
if collider_node.native.get() != ColliderHandle::invalid() {
|
||
if anything_changed {
|
||
if let Some(native) = self.colliders.get_mut(collider_node.native.get()) {
|
||
collider_node
|
||
.restitution
|
||
.try_sync_model(|v| native.set_restitution(v));
|
||
collider_node.collision_groups.try_sync_model(|v| {
|
||
native.set_collision_groups(InteractionGroups::new(
|
||
u32_to_group(v.memberships.0),
|
||
u32_to_group(v.filter.0),
|
||
Default::default(),
|
||
))
|
||
});
|
||
collider_node.solver_groups.try_sync_model(|v| {
|
||
native.set_solver_groups(InteractionGroups::new(
|
||
u32_to_group(v.memberships.0),
|
||
u32_to_group(v.filter.0),
|
||
Default::default(),
|
||
))
|
||
});
|
||
collider_node
|
||
.friction
|
||
.try_sync_model(|v| native.set_friction(v));
|
||
collider_node
|
||
.is_sensor
|
||
.try_sync_model(|v| native.set_sensor(v));
|
||
collider_node
|
||
.friction_combine_rule
|
||
.try_sync_model(|v| native.set_friction_combine_rule(v.into()));
|
||
collider_node
|
||
.restitution_combine_rule
|
||
.try_sync_model(|v| native.set_restitution_combine_rule(v.into()));
|
||
let mut remove_collider = false;
|
||
collider_node.shape.try_sync_model(|v| {
|
||
let inv_global_transform = isometric_global_transform(nodes, handle)
|
||
.try_inverse()
|
||
.unwrap_or_default();
|
||
|
||
if let Some(shape) = collider_shape_into_native_shape(
|
||
&v,
|
||
inv_global_transform,
|
||
handle,
|
||
nodes,
|
||
) {
|
||
native.set_shape(shape);
|
||
} else {
|
||
remove_collider = true;
|
||
}
|
||
});
|
||
if remove_collider {
|
||
self.remove_collider(collider_node.native.get());
|
||
collider_node.native.set(ColliderHandle::invalid());
|
||
}
|
||
}
|
||
}
|
||
} else if let Ok(parent_body) =
|
||
nodes.try_get_or_field_ref::<scene::rigidbody::RigidBody>(collider_node.parent())
|
||
{
|
||
if parent_body.native.get() != RigidBodyHandle::invalid() {
|
||
let inv_global_transform = isometric_global_transform(nodes, handle)
|
||
.try_inverse()
|
||
.unwrap();
|
||
let rigid_body_native = parent_body.native.get();
|
||
if let Some(shape) = collider_shape_into_native_shape(
|
||
collider_node.shape(),
|
||
inv_global_transform,
|
||
handle,
|
||
nodes,
|
||
) {
|
||
let mut builder = ColliderBuilder::new(shape)
|
||
.position(
|
||
Isometry3 {
|
||
rotation: **collider_node.local_transform().rotation(),
|
||
translation: Translation3 {
|
||
vector: **collider_node.local_transform().position(),
|
||
},
|
||
}
|
||
.into(),
|
||
)
|
||
.friction(collider_node.friction())
|
||
.restitution(collider_node.restitution())
|
||
.collision_groups(InteractionGroups::new(
|
||
u32_to_group(collider_node.collision_groups().memberships.0),
|
||
u32_to_group(collider_node.collision_groups().filter.0),
|
||
Default::default(),
|
||
))
|
||
.friction_combine_rule(collider_node.friction_combine_rule().into())
|
||
.restitution_combine_rule(collider_node.restitution_combine_rule().into())
|
||
.solver_groups(InteractionGroups::new(
|
||
u32_to_group(collider_node.solver_groups().memberships.0),
|
||
u32_to_group(collider_node.solver_groups().filter.0),
|
||
Default::default(),
|
||
))
|
||
.sensor(collider_node.is_sensor());
|
||
|
||
if let Some(density) = collider_node.density() {
|
||
builder = builder.density(density);
|
||
}
|
||
|
||
let native_handle =
|
||
self.add_collider(handle, rigid_body_native, builder.build());
|
||
|
||
collider_node.native.set(native_handle);
|
||
|
||
Log::writeln(
|
||
MessageKind::Information,
|
||
format!(
|
||
"Native collider was created for node {}",
|
||
collider_node.name()
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
pub(crate) fn sync_to_joint_node(
|
||
&mut self,
|
||
nodes: &NodePool,
|
||
handle: Handle<Node>,
|
||
joint: &scene::joint::Joint,
|
||
) {
|
||
if !joint.is_globally_enabled() {
|
||
self.remove_joint(joint.native.get());
|
||
joint.native.set(ImpulseJointHandle(Default::default()));
|
||
return;
|
||
}
|
||
|
||
if let Some(native) = self.joints.set.get_mut(joint.native.get(), false) {
|
||
joint.body1.try_sync_model(|v| {
|
||
if let Ok(rigid_body_node) = nodes.try_get(v) {
|
||
native.body1 = rigid_body_node.native.get();
|
||
}
|
||
});
|
||
joint.body2.try_sync_model(|v| {
|
||
if let Ok(rigid_body_node) = nodes.try_get(v) {
|
||
native.body2 = rigid_body_node.native.get();
|
||
}
|
||
});
|
||
joint.params.try_sync_model(|v| {
|
||
native.data =
|
||
// Preserve local frames.
|
||
convert_joint_params(v, native.data.local_frame1.into(), native.data.local_frame2.into())
|
||
});
|
||
joint.motor_params.try_sync_model(|v|{
|
||
// For prismatic and revolute joints, the free axis is defined to be the x axis.
|
||
// If you want the joint to translate / rotate along a different axis, you can rotate the joint itself.
|
||
let joint_axes: &[JointAxis] = match joint.params.get_value_ref(){
|
||
JointParams::PrismaticJoint(_) => &[JointAxis::LinX][..], // slice the array to unify arrays of different lengths.
|
||
JointParams::RevoluteJoint(_) => &[JointAxis::AngX][..],
|
||
JointParams::BallJoint(_) => &[JointAxis::AngX, JointAxis::AngY, JointAxis::AngZ][..],
|
||
_ => {
|
||
Log::warn("Try to modify motor parameters for unsupported joint type, this operation will be ignored.");
|
||
return;
|
||
}
|
||
};
|
||
for joint_axis in joint_axes {
|
||
// Force based motor model is better in the Fyrox's context
|
||
native.data.set_motor_model(*joint_axis, rapier3d::prelude::MotorModel::ForceBased);
|
||
let JointMotorParams {
|
||
target_vel,
|
||
target_pos,
|
||
stiffness,
|
||
damping,
|
||
max_force
|
||
} = v;
|
||
native.data.set_motor(*joint_axis, target_pos, target_vel, stiffness, damping);
|
||
native.data.set_motor_max_force(*joint_axis, max_force);
|
||
}
|
||
// wake up the bodies connected to the joint to ensure they respond to the motor changes immediately
|
||
// however, the rigid bodies may fall asleep any time later unless Joint::set_motor_* functions are called periodically,
|
||
// or the rigid bodies are set to cannot sleep
|
||
let Some(body1) = self.bodies.get_mut(native.body1) else {
|
||
return;
|
||
};
|
||
body1.wake_up(true);
|
||
let Some(body2) = self.bodies.get_mut(native.body2) else {
|
||
return;
|
||
};
|
||
body2.wake_up(true);
|
||
});
|
||
joint.contacts_enabled.try_sync_model(|v| {
|
||
native.data.set_contacts_enabled(v);
|
||
});
|
||
|
||
let mut local_frames = joint.local_frames.borrow_mut();
|
||
if local_frames.is_none() {
|
||
if let (Ok(body1), Ok(body2)) =
|
||
(nodes.try_get(joint.body1()), nodes.try_get(joint.body2()))
|
||
{
|
||
let (local_frame1, local_frame2) = calculate_local_frames(joint, body1, body2);
|
||
native.data =
|
||
convert_joint_params((*joint.params).clone(), local_frame1, local_frame2);
|
||
*local_frames = Some(JointLocalFrames::new(&local_frame1, &local_frame2));
|
||
}
|
||
}
|
||
} else {
|
||
let body1_handle = joint.body1();
|
||
let body2_handle = joint.body2();
|
||
let params = joint.params().clone();
|
||
|
||
// A native joint can be created iff both rigid bodies are correctly assigned and their respective
|
||
// native bodies exists.
|
||
if let (Some(body1), Some(body2)) = (
|
||
nodes
|
||
.try_get(body1_handle)
|
||
.ok()
|
||
.filter(|b| self.bodies.get(b.native.get()).is_some()),
|
||
nodes
|
||
.try_get(body2_handle)
|
||
.ok()
|
||
.filter(|b| self.bodies.get(b.native.get()).is_some()),
|
||
) {
|
||
let native_body1 = body1.native.get();
|
||
let native_body2 = body2.native.get();
|
||
|
||
if self.bodies.get(native_body1).is_none()
|
||
|| self.bodies.get(native_body2).is_none()
|
||
{
|
||
// A joint may be synced before the connected bodies, this way the connected
|
||
// rigid bodies does not have native rigid bodies, and in this case we should
|
||
// simply skip the joint and initialize it on the next frame.
|
||
return;
|
||
}
|
||
|
||
// Calculate local frames first (if needed).
|
||
let mut local_frames = joint.local_frames.borrow_mut();
|
||
let (local_frame1, local_frame2) = local_frames
|
||
.clone()
|
||
.map(|frames| {
|
||
(
|
||
Isometry3 {
|
||
rotation: frames.body1.rotation,
|
||
translation: Translation {
|
||
vector: frames.body1.position,
|
||
},
|
||
},
|
||
Isometry3 {
|
||
rotation: frames.body2.rotation,
|
||
translation: Translation {
|
||
vector: frames.body2.position,
|
||
},
|
||
},
|
||
)
|
||
})
|
||
.unwrap_or_else(|| calculate_local_frames(joint, body1, body2));
|
||
|
||
let mut native_joint = convert_joint_params(params, local_frame1, local_frame2);
|
||
native_joint.contacts_enabled = joint.is_contacts_enabled();
|
||
let native_handle =
|
||
self.add_joint(handle, native_body1, native_body2, native_joint);
|
||
|
||
joint.native.set(native_handle);
|
||
*local_frames = Some(JointLocalFrames::new(&local_frame1, &local_frame2));
|
||
|
||
Log::writeln(
|
||
MessageKind::Information,
|
||
format!("Native joint was created for node {}", joint.name()),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Intersections checks between regular colliders and sensor colliders
|
||
pub(crate) fn intersections_with(
|
||
&self,
|
||
collider: ColliderHandle,
|
||
) -> impl Iterator<Item = IntersectionPair> + '_ {
|
||
self.narrow_phase
|
||
.intersection_pairs_with(collider)
|
||
.filter_map(|(collider1, collider2, intersecting)| {
|
||
Some(IntersectionPair {
|
||
collider1: Handle::decode_from_u128(self.colliders.get(collider1)?.user_data),
|
||
collider2: Handle::decode_from_u128(self.colliders.get(collider2)?.user_data),
|
||
has_any_active_contact: intersecting,
|
||
})
|
||
})
|
||
}
|
||
|
||
/// Contacts checks between two regular colliders
|
||
pub(crate) fn contacts_with(
|
||
&self,
|
||
collider: ColliderHandle,
|
||
) -> impl Iterator<Item = ContactPair> + '_ {
|
||
self.narrow_phase
|
||
// Note: contacts with will only return the interaction between 2 non-sensor nodes
|
||
// https://rapier.rs/docs/user_guides/rust/advanced_collision_detection/#the-contact-graph
|
||
.contact_pairs_with(collider)
|
||
.filter_map(|c| ContactPair::from_native(c, self))
|
||
}
|
||
|
||
/// Returns an iterator over all contact pairs generated in this frame.
|
||
pub fn contacts(&self) -> impl Iterator<Item = ContactPair> + '_ {
|
||
self.narrow_phase
|
||
.contact_pairs()
|
||
.filter_map(|c| ContactPair::from_native(c, self))
|
||
}
|
||
}
|
||
|
||
impl Default for PhysicsWorld {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl Debug for PhysicsWorld {
|
||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||
write!(f, "PhysicsWorld")
|
||
}
|
||
}
|