/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysX { void RigidBodyComponent::Reflect(AZ::ReflectContext* context) { RigidBody::Reflect(context); AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(1) ->Field("RigidBodyConfiguration", &RigidBodyComponent::m_configuration) ; } if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("RigidBodyRequestBus") ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Module, "physics") ->Attribute(AZ::Script::Attributes::Category, "PhysX") ->Event("EnablePhysics", &Physics::RigidBodyRequests::EnablePhysics) ->Event("DisablePhysics", &RigidBodyRequests::DisablePhysics) ->Event("IsPhysicsEnabled", &RigidBodyRequests::IsPhysicsEnabled) ->Event("GetCenterOfMassWorld", &RigidBodyRequests::GetCenterOfMassWorld) ->Event("GetCenterOfMassLocal", &RigidBodyRequests::GetCenterOfMassLocal) ->Event("GetMass", &RigidBodyRequests::GetMass) ->Event("GetInverseMass", &RigidBodyRequests::GetInverseMass) ->Event("SetMass", &RigidBodyRequests::SetMass) ->Event("SetCenterOfMassOffset", &RigidBodyRequests::SetCenterOfMassOffset) ->Event("GetLinearVelocity", &RigidBodyRequests::GetLinearVelocity) ->Event("SetLinearVelocity", &RigidBodyRequests::SetLinearVelocity) ->Event("GetAngularVelocity", &RigidBodyRequests::GetAngularVelocity) ->Event("SetAngularVelocity", &RigidBodyRequests::SetAngularVelocity) ->Event("GetLinearVelocityAtWorldPoint", &RigidBodyRequests::GetLinearVelocityAtWorldPoint) ->Event("ApplyLinearImpulse", &RigidBodyRequests::ApplyLinearImpulse) ->Event("ApplyLinearImpulseAtWorldPoint", &RigidBodyRequests::ApplyLinearImpulseAtWorldPoint) ->Event("ApplyAngularImpulse", &RigidBodyRequests::ApplyAngularImpulse) ->Event("GetLinearDamping", &RigidBodyRequests::GetLinearDamping) ->Event("SetLinearDamping", &RigidBodyRequests::SetLinearDamping) ->Event("GetAngularDamping", &RigidBodyRequests::GetAngularDamping) ->Event("SetAngularDamping", &RigidBodyRequests::SetAngularDamping) ->Event("IsAwake", &RigidBodyRequests::IsAwake) ->Event("ForceAsleep", &RigidBodyRequests::ForceAsleep) ->Event("ForceAwake", &RigidBodyRequests::ForceAwake) ->Event("GetSleepThreshold", &RigidBodyRequests::GetSleepThreshold) ->Event("SetSleepThreshold", &RigidBodyRequests::SetSleepThreshold) ->Event("IsKinematic", &RigidBodyRequests::IsKinematic) ->Event("SetKinematic", &RigidBodyRequests::SetKinematic) ->Event("SetKinematicTarget", &RigidBodyRequests::SetKinematicTarget) ->Event("IsGravityEnabled", &RigidBodyRequests::IsGravityEnabled) ->Event("SetGravityEnabled", &RigidBodyRequests::SetGravityEnabled) ->Event("SetSimulationEnabled", &RigidBodyRequests::SetSimulationEnabled) ->Event("GetAabb", &RigidBodyRequests::GetAabb) ; behaviorContext->Class()->RequestBus("RigidBodyRequestBus"); } } RigidBodyComponent::RigidBodyComponent() { InitPhysicsTickHandler(); } RigidBodyComponent::RigidBodyComponent(const AzPhysics::RigidBodyConfiguration& config, AzPhysics::SceneHandle sceneHandle) : m_configuration(config) , m_attachedSceneHandle(sceneHandle) { InitPhysicsTickHandler(); } void RigidBodyComponent::Init() { } void RigidBodyComponent::SetupConfiguration() { // Get necessary information from the components and fill up the configuration structure auto entityId = GetEntityId(); AZ::Transform lyTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(lyTransform, entityId, &AZ::TransformInterface::GetWorldTM); m_configuration.m_position = lyTransform.GetTranslation(); m_configuration.m_orientation = lyTransform.GetRotation(); m_configuration.m_entityId = entityId; m_configuration.m_debugName = GetEntity()->GetName(); } void RigidBodyComponent::Activate() { if (m_attachedSceneHandle == AzPhysics::InvalidSceneHandle) { Physics::DefaultWorldBus::BroadcastResult(m_attachedSceneHandle, &Physics::DefaultWorldRequests::GetDefaultSceneHandle); } AZ::TransformBus::EventResult(m_staticTransformAtActivation, GetEntityId(), &AZ::TransformInterface::IsStaticTransform); if (m_staticTransformAtActivation) { AZ_Warning("PhysX Rigid Body Component", false, "It is not valid to have a PhysX Rigid Body Component " "when the Transform Component is marked static. Entity \"%s\" will behave as a static rigid body.", GetEntity()->GetName().c_str()); // If we never connect to the rigid body request bus, then that bus will have no handler and we will behave // as if there were no rigid body component, i.e. a static rigid body will be created, which is the behaviour // we want if the transform component static checkbox is ticked. return; } AzFramework::EntityContextId gameContextId = AzFramework::EntityContextId::CreateNull(); AzFramework::GameEntityContextRequestBus::BroadcastResult(gameContextId, &AzFramework::GameEntityContextRequestBus::Events::GetGameEntityContextId); // Determine if we're currently instantiating a slice // During slice instantiation, it's possible this entity will be activated before its parent. To avoid this, we want to wait to enable physics // until the entire slice has been instantiated. To do so, we start listening to the EntityContextEventBus for an OnSliceInstantiated event AZ::Data::AssetId instantiatingAsset; instantiatingAsset.SetInvalid(); AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(instantiatingAsset, gameContextId, &AzFramework::SliceEntityOwnershipServiceRequestBus::Events::CurrentlyInstantiatingSlice); if (instantiatingAsset.IsValid()) { // Start listening for game context events if (!gameContextId.IsNull()) { AzFramework::SliceGameEntityOwnershipServiceNotificationBus::Handler::BusConnect(); } } else { // Create and setup rigid body & associated bus handlers CreatePhysics(); // Add to world EnablePhysics(); } } void RigidBodyComponent::Deactivate() { if (m_staticTransformAtActivation) { return; } if (auto* sceneInterface = AZ::Interface::Get()) { sceneInterface->RemoveSimulatedBody(m_attachedSceneHandle, m_rigidBodyHandle); } Physics::RigidBodyRequestBus::Handler::BusDisconnect(); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); m_sceneFinishSimHandler.Disconnect(); AZ::TickBus::Handler::BusDisconnect(); } void RigidBodyComponent::OnTick(float deltaTime, AZ::ScriptTimePoint /*currentTime*/) { if (m_configuration.m_interpolateMotion) { AZ::Vector3 newPosition = AZ::Vector3::CreateZero(); AZ::Quaternion newRotation = AZ::Quaternion::CreateIdentity(); m_interpolator->GetInterpolated(newPosition, newRotation, deltaTime); AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldRotationQuaternion, newRotation); AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTranslation, newPosition); } } int RigidBodyComponent::GetTickOrder() { return AZ::ComponentTickBus::TICK_PHYSICS; } void RigidBodyComponent::InitPhysicsTickHandler() { m_sceneFinishSimHandler = AzPhysics::SceneEvents::OnSceneSimulationFinishHandler([this]( [[maybe_unused]] AzPhysics::SceneHandle sceneHandle, float fixedDeltatime ) { this->PostPhysicsTick(fixedDeltatime); }, aznumeric_cast(AzPhysics::SceneEvents::PhysicsStartFinishSimulationPriority::Physics)); } void RigidBodyComponent::PostPhysicsTick(float fixedDeltaTime) { // When transform changes, Kinematic Target is updated with the new transform, so don't set the transform again. // But in the case of setting the Kinematic Target directly, the transform needs to reflect the new kinematic target // User sets kinematic Target ---> Update transform // User sets transform ---> Update kinematic target if (!IsPhysicsEnabled() || (IsKinematic() && !m_isLastMovementFromKinematicSource)) { return; } auto* sceneInterface = AZ::Interface::Get(); if (sceneInterface == nullptr) { AZ_Error("RigidBodyComponent", false, "PostPhysicsTick, SceneInterface is null"); return; } AzPhysics::SimulatedBody* rigidBody = sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_rigidBodyHandle); if (rigidBody == nullptr) { AZ_Error("RigidBodyComponent", false, "Unable to retrieve simulated rigid body"); return; } AZ::Transform transform = rigidBody->GetTransform(); if (m_configuration.m_interpolateMotion) { m_interpolator->SetTarget(transform.GetTranslation(), rigidBody->GetOrientation(), fixedDeltaTime); } else { AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldRotationQuaternion, rigidBody->GetOrientation()); AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTranslation, rigidBody->GetPosition()); } m_isLastMovementFromKinematicSource = false; } void RigidBodyComponent::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world) { // Note: OnTransformChanged is not safe at the moment due to TransformComponent design flaw. // It is called when the parent entity is activated after the children causing rigid body // to move through the level instantly. if (AzPhysics::RigidBody* body = GetRigidBody()) { if (body->m_simulating && (body->IsKinematic() && !m_isLastMovementFromKinematicSource)) { body->SetKinematicTarget(world); } else if (!body->m_simulating) { m_rigidBodyTransformNeedsUpdateOnPhysReEnable = true; } } } void RigidBodyComponent::CreatePhysics() { BodyConfigurationComponentBus::EventResult(m_configuration, GetEntityId(), &BodyConfigurationComponentRequests::GetRigidBodyConfiguration); // Create rigid body SetupConfiguration(); // Add shapes AZStd::vector> shapes; ColliderComponentRequestBus::EnumerateHandlersId(GetEntityId(), [&shapes](ColliderComponentRequests* handler) { AZStd::vector> newShapes = handler->GetShapes(); shapes.insert(shapes.end(), newShapes.begin(), newShapes.end()); return true; }); m_configuration.m_colliderAndShapeData = shapes; auto* sceneInterface = AZ::Interface::Get(); if (sceneInterface != nullptr) { m_configuration.m_startSimulationEnabled = false; //enable physics will enable this when called. m_rigidBodyHandle = sceneInterface->AddSimulatedBody(m_attachedSceneHandle, &m_configuration); } // Listen to the PhysX system for events concerning this entity. if (sceneInterface != nullptr) { sceneInterface->RegisterSceneSimulationFinishHandler(m_attachedSceneHandle, m_sceneFinishSimHandler); } AZ::TickBus::Handler::BusConnect(); AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); Physics::RigidBodyRequestBus::Handler::BusConnect(GetEntityId()); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId()); } void RigidBodyComponent::EnablePhysics() { if (IsPhysicsEnabled()) { return; } auto* sceneInterface = AZ::Interface::Get(); if (sceneInterface == nullptr) { AZ_Error("RigidBodyComponent", false, "Unable to enable physics, SceneInterface is null"); return; } SetSimulationEnabled(true); AZ::Transform transform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformInterface::GetWorldTM); if (m_rigidBodyTransformNeedsUpdateOnPhysReEnable) { if (AzPhysics::SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_rigidBodyHandle)) { body->SetTransform(transform); } m_rigidBodyTransformNeedsUpdateOnPhysReEnable = false; } AZ::Quaternion rotation = AZ::Quaternion::CreateIdentity(); AZ::TransformBus::EventResult(rotation, GetEntityId(), &AZ::TransformInterface::GetWorldRotationQuaternion); m_interpolator = std::make_unique(); m_interpolator->Reset(transform.GetTranslation(), rotation); Physics::RigidBodyNotificationBus::Event(GetEntityId(), &Physics::RigidBodyNotificationBus::Events::OnPhysicsEnabled); } void RigidBodyComponent::DisablePhysics() { SetSimulationEnabled(false); Physics::RigidBodyNotificationBus::Event(GetEntityId(), &Physics::RigidBodyNotificationBus::Events::OnPhysicsDisabled); } bool RigidBodyComponent::IsPhysicsEnabled() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->m_simulating; } return false; } void RigidBodyComponent::ApplyLinearImpulse(const AZ::Vector3& impulse) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->ApplyLinearImpulse(impulse); } } void RigidBodyComponent::ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldSpacePoint) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->ApplyLinearImpulseAtWorldPoint(impulse, worldSpacePoint); } } void RigidBodyComponent::ApplyAngularImpulse(const AZ::Vector3& impulse) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->ApplyAngularImpulse(impulse); } } AZ::Vector3 RigidBodyComponent::GetLinearVelocity() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetLinearVelocity(); } return AZ::Vector3::CreateZero(); } void RigidBodyComponent::SetLinearVelocity(const AZ::Vector3& velocity) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetLinearVelocity(velocity); } } AZ::Vector3 RigidBodyComponent::GetAngularVelocity() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetAngularVelocity(); } return AZ::Vector3::CreateZero(); } void RigidBodyComponent::SetAngularVelocity(const AZ::Vector3& angularVelocity) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetAngularVelocity(angularVelocity); } } AZ::Vector3 RigidBodyComponent::GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetLinearVelocityAtWorldPoint(worldPoint); } return AZ::Vector3::CreateZero(); } AZ::Vector3 RigidBodyComponent::GetCenterOfMassWorld() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetCenterOfMassWorld(); } return AZ::Vector3::CreateZero(); } AZ::Vector3 RigidBodyComponent::GetCenterOfMassLocal() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetCenterOfMassLocal(); } return AZ::Vector3::CreateZero(); } AZ::Matrix3x3 RigidBodyComponent::GetInverseInertiaWorld() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetInverseInertiaWorld(); } return AZ::Matrix3x3::CreateZero(); } AZ::Matrix3x3 RigidBodyComponent::GetInverseInertiaLocal() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetInverseInertiaLocal(); } return AZ::Matrix3x3::CreateZero(); } float RigidBodyComponent::GetMass() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetMass(); } return 0.0f; } float RigidBodyComponent::GetInverseMass() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetInverseMass(); } return 0.0f; } void RigidBodyComponent::SetMass(float mass) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetMass(mass); } } void RigidBodyComponent::SetCenterOfMassOffset(const AZ::Vector3& comOffset) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetCenterOfMassOffset(comOffset); } } float RigidBodyComponent::GetLinearDamping() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetLinearDamping(); } return 0.0f; } void RigidBodyComponent::SetLinearDamping(float damping) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetLinearDamping(damping); } } float RigidBodyComponent::GetAngularDamping() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetAngularDamping(); } return 0.0f; } void RigidBodyComponent::SetAngularDamping(float damping) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetAngularDamping(damping); } } bool RigidBodyComponent::IsAwake() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->IsAwake(); } return false; } void RigidBodyComponent::ForceAsleep() { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->ForceAsleep(); } } void RigidBodyComponent::ForceAwake() { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->ForceAwake(); } } bool RigidBodyComponent::IsKinematic() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->IsKinematic(); } return false; } void RigidBodyComponent::SetKinematic(bool kinematic) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetKinematic(kinematic); } } void RigidBodyComponent::SetKinematicTarget(const AZ::Transform& targetPosition) { m_isLastMovementFromKinematicSource = true; if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetKinematicTarget(targetPosition); } } bool RigidBodyComponent::IsGravityEnabled() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->IsGravityEnabled(); } return false; } void RigidBodyComponent::SetGravityEnabled(bool enabled) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetGravityEnabled(enabled); } } void RigidBodyComponent::SetSimulationEnabled(bool enabled) { if (auto* sceneInterface = AZ::Interface::Get()) { if (enabled) { sceneInterface->EnableSimulationOfBody(m_attachedSceneHandle, m_rigidBodyHandle); } else { sceneInterface->DisableSimulationOfBody(m_attachedSceneHandle, m_rigidBodyHandle); } } } float RigidBodyComponent::GetSleepThreshold() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetSleepThreshold(); } return 0.0f; } void RigidBodyComponent::SetSleepThreshold(float threshold) { if (AzPhysics::RigidBody* body = GetRigidBody()) { body->SetSleepThreshold(threshold); } } AZ::Aabb RigidBodyComponent::GetAabb() const { if (const AzPhysics::RigidBody* body = GetRigidBodyConst()) { return body->GetAabb(); } return AZ::Aabb::CreateNull(); } AzPhysics::RigidBody* RigidBodyComponent::GetRigidBody() { return azdynamic_cast(GetSimulatedBody()); } AzPhysics::SimulatedBody* RigidBodyComponent::GetSimulatedBody() { if (auto* sceneInterface = AZ::Interface::Get()) { return sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_rigidBodyHandle); } return nullptr; } const AzPhysics::RigidBody* RigidBodyComponent::GetRigidBodyConst() const { if (auto* sceneInterface = AZ::Interface::Get()) { return azdynamic_cast( sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_rigidBodyHandle)); } return nullptr; } AzPhysics::SimulatedBodyHandle RigidBodyComponent::GetSimulatedBodyHandle() const { return m_rigidBodyHandle; } AzPhysics::SceneQueryHit RigidBodyComponent::RayCast(const AzPhysics::RayCastRequest& request) { if (AzPhysics::RigidBody* body = GetRigidBody()) { return body->RayCast(request); } return AzPhysics::SceneQueryHit(); } void RigidBodyComponent::OnSliceInstantiated(const AZ::Data::AssetId&, const AZ::SliceComponent::SliceInstanceAddress&, const AzFramework::SliceInstantiationTicket&) { CreatePhysics(); EnablePhysics(); AzFramework::SliceGameEntityOwnershipServiceNotificationBus::Handler::BusDisconnect(); } void RigidBodyComponent::OnSliceInstantiationFailed(const AZ::Data::AssetId&, const AzFramework::SliceInstantiationTicket&) { // Enable physics even in the case of instantiation failure. If we've made it this far, the // entity is valid and should be activated normally. CreatePhysics(); EnablePhysics(); AzFramework::SliceGameEntityOwnershipServiceNotificationBus::Handler::BusDisconnect(); } void TransformForwardTimeInterpolator::Reset(const AZ::Vector3& position, const AZ::Quaternion& rotation) { m_currentRealTime = m_currentFixedTime = 0.0f; m_integralTime = 0; m_targetTranslation = AZ::LinearlyInterpolatedSample(); m_targetRotation = AZ::LinearlyInterpolatedSample(); m_targetTranslation.SetNewTarget(position, 1); m_targetTranslation.GetInterpolatedValue(1); m_targetRotation.SetNewTarget(rotation, 1); m_targetRotation.GetInterpolatedValue(1); } void TransformForwardTimeInterpolator::SetTarget(const AZ::Vector3& position, const AZ::Quaternion& rotation, float fixedDeltaTime) { m_currentFixedTime += fixedDeltaTime; AZ::u32 currentIntegral = FloatToIntegralTime(m_currentFixedTime + fixedDeltaTime * 2.0f); m_targetTranslation.SetNewTarget(position, currentIntegral); m_targetRotation.SetNewTarget(rotation, currentIntegral); static const float resetTimeThreshold = 1.0f; if (m_currentFixedTime > resetTimeThreshold) { m_currentFixedTime -= resetTimeThreshold; m_currentRealTime -= resetTimeThreshold; m_integralTime += static_cast(FloatToIntegralResolution * resetTimeThreshold); } } void TransformForwardTimeInterpolator::GetInterpolated(AZ::Vector3& position, AZ::Quaternion& rotation, float realDeltaTime) { m_currentRealTime += realDeltaTime; AZ::u32 currentIntegral = FloatToIntegralTime(m_currentRealTime); position = m_targetTranslation.GetInterpolatedValue(currentIntegral); rotation = m_targetRotation.GetInterpolatedValue(currentIntegral); } } // namespace PhysX