// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "RigidBody.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Colliders/Collider.h" #include "Engine/Physics/Physics.h" #include "Engine/Serialization/Serialization.h" #include #include #include #include RigidBody::RigidBody(const SpawnParams& params) : PhysicsActor(params) , _actor(nullptr) , _mass(1.0f) , _linearDamping(0.01f) , _angularDamping(0.05f) , _maxAngularVelocity(7.0f) , _massScale(1.0f) , _centerOfMassOffset(Vector3::Zero) , _constraints(RigidbodyConstraints::None) , _enableSimulation(true) , _isKinematic(false) , _useCCD(false) , _enableGravity(true) , _startAwake(true) , _updateMassWhenScaleChanges(false) , _overrideMass(false) { } void RigidBody::SetIsKinematic(const bool value) { if (value == GetIsKinematic()) return; _isKinematic = value; if (_actor) _actor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, value); } void RigidBody::SetLinearDamping(float value) { if (Math::NearEqual(value, _linearDamping)) return; _linearDamping = value; if (_actor) { _actor->setLinearDamping(value); } } void RigidBody::SetAngularDamping(float value) { if (Math::NearEqual(value, _angularDamping)) return; _angularDamping = value; if (_actor) { _actor->setAngularDamping(value); } } void RigidBody::SetEnableSimulation(bool value) { if (value == GetEnableSimulation()) return; _enableSimulation = value; if (_actor) { const bool isActive = _enableSimulation && IsActiveInHierarchy(); _actor->setActorFlag(PxActorFlag::eDISABLE_SIMULATION, !isActive); // Auto wake up if (isActive && GetStartAwake()) WakeUp(); } } void RigidBody::SetUseCCD(bool value) { if (value == GetUseCCD()) return; _useCCD = value; if (_actor) _actor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, value); } void RigidBody::SetEnableGravity(bool value) { if (value == GetEnableGravity()) return; _enableGravity = value; if (_actor) { _actor->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, !value); if (value) WakeUp(); } } void RigidBody::SetStartAwake(bool value) { _startAwake = value; } void RigidBody::SetUpdateMassWhenScaleChanges(bool value) { _updateMassWhenScaleChanges = value; } void RigidBody::SetMaxAngularVelocity(float value) { if (Math::NearEqual(value, _maxAngularVelocity)) return; _maxAngularVelocity = value; if (_actor) _actor->setMaxAngularVelocity(value); } void RigidBody::SetOverrideMass(bool value) { if (value == GetOverrideMass()) return; _overrideMass = value; UpdateMass(); } void RigidBody::SetMass(float value) { if (Math::NearEqual(value, _mass)) return; _mass = value; // Auto enable override _overrideMass = true; UpdateMass(); } void RigidBody::SetMassScale(float value) { if (Math::NearEqual(value, _massScale)) return; _massScale = value; if (!_overrideMass) UpdateMass(); } void RigidBody::SetCenterOfMassOffset(const Vector3& value) { if (Vector3::NearEqual(value, _centerOfMassOffset)) return; _centerOfMassOffset = value; if (_actor) { PxTransform pose = _actor->getCMassLocalPose(); pose.p += C2P(_centerOfMassOffset); _actor->setCMassLocalPose(pose); } } void RigidBody::SetConstraints(const RigidbodyConstraints value) { if (value == _constraints) return; _constraints = value; if (_actor) { _actor->setRigidDynamicLockFlags(static_cast(value)); } } Vector3 RigidBody::GetLinearVelocity() const { return _actor ? P2C(_actor->getLinearVelocity()) : Vector3::Zero; } void RigidBody::SetLinearVelocity(const Vector3& value) const { if (_actor) _actor->setLinearVelocity(C2P(value), GetStartAwake()); } Vector3 RigidBody::GetAngularVelocity() const { return _actor ? P2C(_actor->getAngularVelocity()) : Vector3::Zero; } void RigidBody::SetAngularVelocity(const Vector3& value) const { if (_actor) _actor->setAngularVelocity(C2P(value), GetStartAwake()); } float RigidBody::GetMaxDepenetrationVelocity() const { return _actor ? _actor->getMaxDepenetrationVelocity() : 0; } void RigidBody::SetMaxDepenetrationVelocity(const float value) const { if (_actor) _actor->setMaxDepenetrationVelocity(value); } float RigidBody::GetSleepThreshold() const { return _actor ? _actor->getSleepThreshold() : 0; } void RigidBody::SetSleepThreshold(const float value) const { if (_actor) _actor->setSleepThreshold(value); } Vector3 RigidBody::GetCenterOfMass() const { return _actor ? P2C(_actor->getCMassLocalPose().p) : Vector3::Zero; } bool RigidBody::IsSleeping() const { return _actor ? _actor->isSleeping() : false; } void RigidBody::Sleep() const { if (_actor && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy() && _actor->getScene()) { _actor->putToSleep(); } } void RigidBody::WakeUp() const { if (_actor && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy() && _actor->getScene()) { _actor->wakeUp(); } } void RigidBody::UpdateMass() { // Skip if no actor created if (_actor == nullptr) return; // Physical material float densityKGPerCubicUU = 1.0f; float raiseMassToPower = 0.75f; // TODO: link physical material or expose density parameter PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU); // Grab old mass so we can apply new mass while maintaining inertia tensor const float oldMass = _actor->getMass(); float newMass; if (_overrideMass == false) { const float usePow = Math::Clamp(raiseMassToPower, ZeroTolerance, 1.0f); newMass = Math::Pow(oldMass, usePow); // Apply user-defined mass scaling newMass *= Math::Clamp(_massScale, 0.01f, 100.0f); _mass = newMass; } else { // Min weight of 1g newMass = Math::Max(_mass, 0.001f); } ASSERT(newMass > 0.0f); const float massRatio = newMass / oldMass; const PxVec3 inertiaTensor = _actor->getMassSpaceInertiaTensor(); _actor->setMassSpaceInertiaTensor(inertiaTensor * massRatio); _actor->setMass(newMass); } void RigidBody::AddForce(const Vector3& force, ForceMode mode) const { if (_actor && GetEnableSimulation()) { _actor->addForce(C2P(force), static_cast(mode)); } } void RigidBody::AddRelativeForce(const Vector3& force, ForceMode mode) const { AddForce(Vector3::Transform(force, _transform.Orientation), mode); } void RigidBody::AddTorque(const Vector3& torque, ForceMode mode) const { if (_actor && GetEnableSimulation()) { _actor->addTorque(C2P(torque), static_cast(mode)); } } void RigidBody::AddRelativeTorque(const Vector3& torque, ForceMode mode) const { AddTorque(Vector3::Transform(torque, _transform.Orientation), mode); } void RigidBody::SetSolverIterationCounts(int32 minPositionIters, int32 minVelocityIters) const { if (_actor) { _actor->setSolverIterationCounts(Math::Clamp(minPositionIters, 1, 255), Math::Clamp(minVelocityIters, 1, 255)); } } void RigidBody::ClosestPoint(const Vector3& position, Vector3& result) const { Vector3 tmp; float minDistanceSqr = MAX_float; result = Vector3::Maximum; for (int32 i = 0; i < Children.Count(); i++) { const auto collider = dynamic_cast(Children[i]); if (collider && collider->GetAttachedRigidBody() == this) { collider->ClosestPoint(position, tmp); const auto dstSqr = Vector3::DistanceSquared(position, tmp); if (dstSqr < minDistanceSqr) { minDistanceSqr = dstSqr; result = tmp; } } } } void RigidBody::OnCollisionEnter(const Collision& c) { CollisionEnter(c); } void RigidBody::OnCollisionExit(const Collision& c) { CollisionExit(c); } void RigidBody::OnTriggerEnter(PhysicsColliderActor* c) { TriggerEnter(c); } void RigidBody::OnTriggerExit(PhysicsColliderActor* c) { TriggerExit(c); } void RigidBody::CreateActor() { ASSERT(_actor == nullptr); // Create rigid body const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation)); _actor = CPhysX->createRigidDynamic(trans); _actor->userData = this; // Setup flags #if WITH_PVD PxActorFlags actorFlags = PxActorFlag::eVISUALIZATION; #else PxActorFlags actorFlags = static_cast(0); #endif const bool isActive = _enableSimulation && IsActiveInHierarchy(); if (!isActive) actorFlags |= PxActorFlag::eDISABLE_SIMULATION; if (!_enableGravity) actorFlags |= PxActorFlag::eDISABLE_GRAVITY; _actor->setActorFlags(actorFlags); if (_useCCD) _actor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true); if (_isKinematic) _actor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, true); // Apply properties _actor->setLinearDamping(_linearDamping); _actor->setAngularDamping(_angularDamping); _actor->setMaxAngularVelocity(_maxAngularVelocity); _actor->setRigidDynamicLockFlags(static_cast(_constraints)); // Find colliders to attach for (int32 i = 0; i < Children.Count(); i++) { auto collider = dynamic_cast(Children[i]); if (collider && collider->CanAttach(this)) { collider->Attach(this); } } // Setup mass (calculate or use overriden value) UpdateMass(); // Apply the Center Of Mass offset if (!_centerOfMassOffset.IsZero()) { PxTransform pose = _actor->getCMassLocalPose(); pose.p += C2P(_centerOfMassOffset); _actor->setCMassLocalPose(pose); } // Register actor const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy(); Physics::AddActor(_actor, putToSleep); // Update cached data UpdateBounds(); } void RigidBody::UpdateScale() { const Vector3 scale = GetScale(); if (Vector3::NearEqual(_cachedScale, scale)) return; _cachedScale = scale; // Check if update mass if (_updateMassWhenScaleChanges && !_overrideMass) UpdateMass(); } void RigidBody::Serialize(SerializeStream& stream, const void* otherObj) { // Base PhysicsActor::Serialize(stream, otherObj); SERIALIZE_GET_OTHER_OBJ(RigidBody); SERIALIZE_BIT_MEMBER(OverrideMass, _overrideMass); SERIALIZE_MEMBER(Mass, _mass); SERIALIZE_MEMBER(LinearDamping, _linearDamping); SERIALIZE_MEMBER(AngularDamping, _angularDamping); SERIALIZE_MEMBER(MaxAngularVelocity, _maxAngularVelocity); SERIALIZE_MEMBER(CenterOfMassOffset, _centerOfMassOffset); SERIALIZE_MEMBER(MassScale, _massScale); SERIALIZE_MEMBER(Constraints, _constraints); SERIALIZE_BIT_MEMBER(EnableSimulation, _enableSimulation); SERIALIZE_BIT_MEMBER(IsKinematic, _isKinematic); SERIALIZE_BIT_MEMBER(UseCCD, _useCCD); SERIALIZE_BIT_MEMBER(EnableGravity, _enableGravity); SERIALIZE_BIT_MEMBER(StartAwake, _startAwake); SERIALIZE_BIT_MEMBER(UpdateMassWhenScaleChanges, _updateMassWhenScaleChanges); } void RigidBody::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { // Base PhysicsActor::Deserialize(stream, modifier); DESERIALIZE_BIT_MEMBER(OverrideMass, _overrideMass); DESERIALIZE_MEMBER(Mass, _mass); DESERIALIZE_MEMBER(LinearDamping, _linearDamping); DESERIALIZE_MEMBER(AngularDamping, _angularDamping); DESERIALIZE_MEMBER(MaxAngularVelocity, _maxAngularVelocity); DESERIALIZE_MEMBER(CenterOfMassOffset, _centerOfMassOffset); DESERIALIZE_MEMBER(MassScale, _massScale); DESERIALIZE_MEMBER(Constraints, _constraints); DESERIALIZE_BIT_MEMBER(EnableSimulation, _enableSimulation); DESERIALIZE_BIT_MEMBER(IsKinematic, _isKinematic); DESERIALIZE_BIT_MEMBER(UseCCD, _useCCD); DESERIALIZE_BIT_MEMBER(EnableGravity, _enableGravity); DESERIALIZE_BIT_MEMBER(StartAwake, _startAwake); DESERIALIZE_BIT_MEMBER(UpdateMassWhenScaleChanges, _updateMassWhenScaleChanges); } PxActor* RigidBody::GetPhysXActor() { return (PxActor*)_actor; } PxRigidActor* RigidBody::GetRigidActor() { return (PxRigidActor*)_actor; } void RigidBody::BeginPlay(SceneBeginData* data) { CreateActor(); // Base PhysicsActor::BeginPlay(data); } void RigidBody::EndPlay() { // Detach all the shapes PxShape* shapes[8]; while (_actor && _actor->getNbShapes() > 0) { const uint32 count = _actor->getShapes(shapes, 8, 0); for (uint32 i = 0; i < count; i++) { _actor->detachShape(*shapes[i], false); } } // Base PhysicsActor::EndPlay(); if (_actor) { // Remove actor Physics::RemoveActor(_actor); _actor = nullptr; } } void RigidBody::OnActiveInTreeChanged() { // Base PhysicsActor::OnActiveInTreeChanged(); if (_actor) { const bool isActive = _enableSimulation && IsActiveInHierarchy(); _actor->setActorFlag(PxActorFlag::eDISABLE_SIMULATION, !isActive); // Auto wake up if (isActive && GetStartAwake()) WakeUp(); // Clear velocities and the forces on disabled else if (!IsActiveInHierarchy()) Sleep(); } } void RigidBody::OnTransformChanged() { // Update physics is not during physics state synchronization if (!_isUpdatingTransform && _actor) { // Base (skip PhysicsActor call to optimize) Actor::OnTransformChanged(); const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation)); if (GetIsKinematic() && GetEnableSimulation()) _actor->setKinematicTarget(trans); else _actor->setGlobalPose(trans, true); UpdateScale(); UpdateBounds(); } else { // Base PhysicsActor::OnTransformChanged(); } }