/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysXCharacters { void CharacterControllerConfiguration::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("SlopeBehaviour", &CharacterControllerConfiguration::m_slopeBehaviour) ->Field("ContactOffset", &CharacterControllerConfiguration::m_contactOffset) ->Field("ScaleCoeff", &CharacterControllerConfiguration::m_scaleCoefficient) ; if (auto editContext = serializeContext->GetEditContext()) { editContext->Class( "PhysX Character Controller Configuration", "PhysX Character Controller Configuration") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &CharacterControllerConfiguration::m_slopeBehaviour, "Slope Behaviour", "Behaviour of the controller on surfaces above the maximum slope") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) ->EnumAttribute(SlopeBehaviour::PreventClimbing, "Prevent Climbing") ->EnumAttribute(SlopeBehaviour::ForceSliding, "Force Sliding") ->DataElement(AZ::Edit::UIHandlers::Default, &CharacterControllerConfiguration::m_contactOffset, "Contact Offset", "Extra distance outside the controller used for smoother contact resolution") ->Attribute(AZ::Edit::Attributes::Min, 0.01f) ->Attribute(AZ::Edit::Attributes::Step, 0.01f) ->DataElement(AZ::Edit::UIHandlers::Default, &CharacterControllerConfiguration::m_scaleCoefficient, "Scale", "Scalar coefficient used to scale the controller, usually slightly smaller than 1") ->Attribute(AZ::Edit::Attributes::Min, 0.01f) ->Attribute(AZ::Edit::Attributes::Step, 0.01f) ; } } } void CharacterController::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(1) ; } } CharacterController::CharacterController(physx::PxController* pxController) : m_pxController(pxController) { } void CharacterController::SetFilterDataAndShape(const Physics::CharacterConfiguration& characterConfig) { Physics::CollisionGroup collisionGroup; Physics::CollisionRequestBus::BroadcastResult(collisionGroup, &Physics::CollisionRequests::GetCollisionGroupById, characterConfig.m_collisionGroupId); UpdatePxControllerFilters(characterConfig.m_collisionLayer, collisionGroup); physx::PxRigidDynamic* actor = nullptr; physx::PxU32 numShapes = 0; { PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); actor = m_pxController->getActor(); numShapes = actor->getNbShapes(); } if (numShapes != 1) { AZ_Error("PhysX Character Controller", false, "Found %i shapes, expected exactly 1.", numShapes) } else { physx::PxShape* pxShape = nullptr; { PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); actor->getShapes(&pxShape, 1, 0); // wrap the raw PhysX shape so that it is appropriately configured for raycasts etc. PhysX::SystemRequestsBus::BroadcastResult(m_shape, &PhysX::SystemRequests::CreateWrappedNativeShape, pxShape); } if (m_shape) { PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_shape->AttachedToActor(actor); m_shape->SetCollisionLayer(characterConfig.m_collisionLayer); m_shape->SetCollisionGroup(collisionGroup); } } } void CharacterController::SetActorName(const AZStd::string& name) { m_name = name; if (m_pxController) { PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->getActor()->setName(m_name.c_str()); } } void CharacterController::SetUserData(const Physics::CharacterConfiguration& characterConfig) { m_actorUserData = PhysX::ActorData(m_pxController->getActor()); m_actorUserData.SetCharacter(this); m_actorUserData.SetEntityId(characterConfig.m_entityId); } void CharacterController::SetMinimumMovementDistance(float distance) { m_minimumMovementDistance = distance; } void CharacterController::CreateShadowBody(const Physics::CharacterConfiguration& configuration, Physics::World& world) { Physics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_kinematic = true; rigidBodyConfig.m_debugName = configuration.m_debugName + " (Shadow)"; rigidBodyConfig.m_entityId = configuration.m_entityId; m_shadowBody = AZ::Interface::Get()->CreateRigidBody(rigidBodyConfig); world.AddBody(*m_shadowBody); } void CharacterController::SetTag(const AZStd::string& tag) { m_colliderTag = AZ::Crc32(tag); } CharacterController::~CharacterController() { if (m_pxController) { PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_shape = nullptr; //shape has to go before m_pxController m_pxController->release(); } m_pxController = nullptr; m_material = nullptr; } void CharacterController::UpdateObservedVelocity(const AZ::Vector3& observedVelocity) { m_observedVelocity = observedVelocity; } // Physics::Character AZ::Vector3 CharacterController::GetBasePosition() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return AZ::Vector3::CreateZero(); } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); return PxMathConvertExtended(m_pxController->getFootPosition()); } void CharacterController::SetBasePosition(const AZ::Vector3& position) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->setFootPosition(PxMathConvertExtended(position)); if (m_shadowBody) { m_shadowBody->SetTransform(AZ::Transform::CreateTranslation(GetBasePosition())); } } AZ::Vector3 CharacterController::GetCenterPosition() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return AZ::Vector3::CreateZero(); } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eCAPSULE) { auto capsuleController = static_cast(m_pxController); const float halfHeight = 0.5f * capsuleController->getHeight() + capsuleController->getRadius(); return GetBasePosition() + PxMathConvert(halfHeight * m_pxController->getUpDirection()); } if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { auto boxController = static_cast(m_pxController); return GetBasePosition() + AZ::Vector3::CreateAxisZ(boxController->getHalfHeight()); } AZ_Warning("PhysX Character Controller", false, "Unrecognized shape type."); return AZ::Vector3::CreateZero(); } template static T CheckValidAndReturn(physx::PxController* controller, T result) { if (!controller) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); } return result; } float CharacterController::GetStepHeight() const { return CheckValidAndReturn(m_pxController, m_pxController ? m_pxController->getStepOffset() : 0.0f); } void CharacterController::SetStepHeight(float stepHeight) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } if (stepHeight <= 0.0f) { AZ_Warning("PhysX Character Controller", false, "PhysX requires the step height to be positive."); } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->setStepOffset(stepHeight); } AZ::Vector3 CharacterController::GetUpDirection() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return AZ::Vector3::CreateZero(); } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); return PxMathConvert(m_pxController->getUpDirection()); } void CharacterController::SetUpDirection(const AZ::Vector3& upDirection) { AZ_Warning("PhysX Character Controller", false, "Setting up direction is not currently supported."); return; } float CharacterController::GetSlopeLimitDegrees() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return 0.0f; } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); return AZ::RadToDeg(acosf(m_pxController->getSlopeLimit())); } void CharacterController::SetSlopeLimitDegrees(float slopeLimitDegrees) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } float slopeLimitClamped = AZ::GetClamp(slopeLimitDegrees, 0.0f, 90.0f); if (slopeLimitDegrees != slopeLimitClamped) { AZ_Warning("PhysX Character Controller", false, "Slope limit should be in the range 0-90 degrees. " "Value %f was clamped to %f", slopeLimitDegrees, slopeLimitClamped); } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->setSlopeLimit(cosf(AZ::DegToRad(slopeLimitClamped))); } AZ::Vector3 CharacterController::GetVelocity() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return AZ::Vector3::CreateZero(); } return m_observedVelocity; } void CharacterController::SetCollisionLayer(const Physics::CollisionLayer& layer) { if (!m_shape) { AZ_Error("PhysX Character Controller", false, "Attempting to access null shape on character controller."); return; } m_shape->SetCollisionLayer(layer); UpdatePxControllerFilters(layer, m_shape->GetCollisionGroup()); } void CharacterController::SetCollisionGroup(const Physics::CollisionGroup& group) { if (!m_shape) { AZ_Error("PhysX Character Controller", false, "Attempting to access null shape on character controller."); return; } m_shape->SetCollisionGroup(group); UpdatePxControllerFilters(m_shape->GetCollisionLayer(), group); } Physics::CollisionLayer CharacterController::GetCollisionLayer() const { if (!m_shape) { AZ_Error("PhysX Character Controller", false, "Attempting to access null shape on character controller."); return Physics::CollisionLayer::Default; } return m_shape->GetCollisionLayer(); } Physics::CollisionGroup CharacterController::GetCollisionGroup() const { if (!m_shape) { AZ_Error("PhysX Character Controller", false, "Attempting to access null shape on character controller."); return Physics::CollisionGroup::All; } return m_shape->GetCollisionGroup(); } AZ::Crc32 CharacterController::GetColliderTag() const { return m_colliderTag; } AZ::Vector3 CharacterController::TryRelativeMove(const AZ::Vector3& deltaPosition, float deltaTime) { const AZ::Vector3& oldPosition = GetBasePosition(); if (m_pxController) { { PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->move(PxMathConvert(deltaPosition), m_minimumMovementDistance, deltaTime, m_pxControllerFilters); } if (m_shadowBody) { m_shadowBody->SetKinematicTarget(AZ::Transform::CreateTranslation(GetBasePosition())); } const AZ::Vector3& newPosition = GetBasePosition(); m_observedVelocity = deltaTime > 0.0f ? (newPosition - oldPosition) / deltaTime : AZ::Vector3::CreateZero(); return newPosition; } return oldPosition; } void CharacterController::SetRotation(const AZ::Quaternion& rotation) { if (m_shadowBody) { AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(rotation, GetBasePosition()); m_shadowBody->SetKinematicTarget(transform); } } void CharacterController::CheckSupport(const AZ::Vector3& direction, float distance, const Physics::CharacterSupportInfo& supportInfo) { AZ_Warning("PhysX Character Controller", false, "Not yet supported."); } void CharacterController::AttachShape(AZStd::shared_ptr shape) { if (m_shadowBody) { m_shadowBody->AddShape(shape); } } // Physics::WorldBody AZ::EntityId CharacterController::GetEntityId() const { return m_actorUserData.GetEntityId(); } Physics::World* CharacterController::GetWorld() const { return m_pxController ? PhysX::Utils::GetUserData(m_pxController->getScene()) : nullptr; } AZ::Transform CharacterController::GetTransform() const { return AZ::Transform::CreateTranslation(GetPosition()); } void CharacterController::SetTransform(const AZ::Transform& transform) { SetBasePosition(transform.GetTranslation()); } AZ::Vector3 CharacterController::GetPosition() const { return GetBasePosition(); } AZ::Quaternion CharacterController::GetOrientation() const { return AZ::Quaternion::CreateIdentity(); } AZ::Aabb CharacterController::GetAabb() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return AZ::Aabb::CreateNull(); } // use bounding box inflation factor of 1.0f so users can control inflation themselves const float inflationFactor = 1.0f; PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); return PxMathConvert(m_pxController->getActor()->getWorldBounds(inflationFactor)); } Physics::RayCastHit CharacterController::RayCast(const Physics::RayCastRequest& request) { if (m_pxController) { if (physx::PxRigidDynamic* actor = m_pxController->getActor()) { return PhysX::Utils::RayCast::ClosestRayHitAgainstPxRigidActor(request, actor); } } return Physics::RayCastHit(); } AZ::Crc32 CharacterController::GetNativeType() const { return NativeTypeIdentifiers::CharacterController; } void* CharacterController::GetNativePointer() const { return m_pxController; } void CharacterController::AddToWorld(Physics::World& world) { if (m_shadowBody) { m_shadowBody->AddToWorld(world); } } void CharacterController::RemoveFromWorld(Physics::World& world) { physx::PxScene* scene = static_cast(world.GetNativePointer()); if (scene) { PHYSX_SCENE_WRITE_LOCK(scene); scene->removeActor(*m_pxController->getActor()); } if (m_shadowBody) { m_shadowBody->RemoveFromWorld(world); } } // physx::PxControllerFilterCallback bool CharacterController::filter(const physx::PxController& controllerA, const physx::PxController& controllerB) { PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); physx::PxRigidDynamic* actorA = controllerA.getActor(); physx::PxRigidDynamic* actorB = controllerB.getActor(); if (actorA && actorA->getNbShapes() > 0 && actorB && actorB->getNbShapes() > 0) { physx::PxShape* shapeA = nullptr; actorA->getShapes(&shapeA, 1, 0); physx::PxFilterData filterDataA = shapeA->getSimulationFilterData(); physx::PxShape* shapeB = nullptr; actorB->getShapes(&shapeB, 1, 0); physx::PxFilterData filterDataB = shapeB->getSimulationFilterData(); return PhysX::Utils::Collision::ShouldCollide(filterDataA, filterDataB); } return true; } // physx::PxQueryFilterCallback physx::PxQueryHitType::Enum CharacterController::preFilter(const physx::PxFilterData& filterData, const physx::PxShape* shape, const physx::PxRigidActor* actor, physx::PxHitFlags& /*queryFlags*/) { // non-kinematic dynamic bodies should not impede the movement of the character if (actor->getConcreteType() == physx::PxConcreteType::eRIGID_DYNAMIC) { const physx::PxRigidDynamic* rigidDynamic = static_cast(actor); if (!(rigidDynamic->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC)) { return physx::PxQueryHitType::eNONE; } } // all other cases should be determined by collision filters if (PhysX::Utils::Collision::ShouldCollide(*m_pxControllerFilters.mFilterData, shape->getSimulationFilterData())) { return physx::PxQueryHitType::eBLOCK; } return physx::PxQueryHitType::eNONE; } physx::PxQueryHitType::Enum CharacterController::postFilter(const physx::PxFilterData& /*filterData*/, const physx::PxQueryHit& /*hit*/) { // return arbitrary value since this function is not being called as the ePOSTFILTER flag is not set in the // filter flags return physx::PxQueryHitType::eNONE; } // CharacterController specific void CharacterController::Resize(float height) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); } if (height <= 0.0f) { AZ_Error("PhysX Character Controller", false, "PhysX requires controller height to be positive."); return; } // height needs to be adjusted due to differences between LY and PhysX definitions of capsule and box dimensions float adjustedHeight = height; { PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eCAPSULE) { auto capsuleController = static_cast(m_pxController); const float radius = capsuleController->getRadius(); if (height <= 2.0f * radius) { AZ_Error("PhysX Character Controller", false, "Capsule height must exceed twice its radius."); return; } // LY defines capsule height to include the end caps, but PhysX does not adjustedHeight = height - 2.0f * radius; } else { // the PhysX box controller resize function actually treats the height argument as half-height adjustedHeight = 0.5f * height; } } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); m_pxController->resize(adjustedHeight); } float CharacterController::GetHeight() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return 0.0f; } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { return static_cast(m_pxController)->getHalfHeight() * 2.0f; } else if (m_pxController->getType() == physx::PxControllerShapeType::eCAPSULE) { // PhysX capsule height refers to the length of the cylindrical section. // LY capsule height refers to the length including the hemispherical caps. auto capsuleController = static_cast(m_pxController); return capsuleController->getHeight() + 2.0f * capsuleController->getRadius(); } AZ_Error("PhysX Character Controller", false, "Unrecognized controller shape type."); return 0.0f; } void CharacterController::SetHeight(float height) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } physx::PxControllerShapeType::Enum type; { PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); type = m_pxController->getType(); } if (type == physx::PxControllerShapeType::eBOX) { if (height <= 0.0f) { AZ_Error("PhysX Character Controller", false, "PhysX requires controller height to be positive."); return; } auto boxController = static_cast(m_pxController); PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); boxController->setHalfHeight(0.5f * height); } else if (type == physx::PxControllerShapeType::eCAPSULE) { auto capsuleController = static_cast(m_pxController); float radius = capsuleController->getRadius(); if (height <= 2.0f * radius) { AZ_Error("PhysX Character Controller", false, "Capsule height must exceed twice its radius."); return; } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); // PhysX capsule height refers to the length of the cylindrical section. // LY capsule height refers to the length including the hemispherical caps. capsuleController->setHeight(height - 2.0f * radius); } else { AZ_Error("PhysX Character Controller", false, "Unrecognized controller shape type."); } } float CharacterController::GetRadius() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return 0.0f; } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eCAPSULE) { return static_cast(m_pxController)->getRadius(); } AZ_Error("PhysX Character Controller", false, "Radius is only defined for capsule controllers."); return 0.0f; } void CharacterController::SetRadius(float radius) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eCAPSULE) { if (radius <= 0.0f) { AZ_Error("PhysX Character Controller", false, "PhysX requires radius to be positive."); return; } static_cast(m_pxController)->setRadius(radius); } else { AZ_Error("PhysX Character Controller", false, "Radius is only defined for capsule controllers."); } } float CharacterController::GetHalfSideExtent() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return 0.0f; } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { return static_cast(m_pxController)->getHalfSideExtent(); } AZ_Error("PhysX Character Controller", false, "Half side extent is only defined for box controllers."); return 0.0f; } void CharacterController::SetHalfSideExtent(float halfSideExtent) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { if (halfSideExtent <= 0.0f) { AZ_Error("PhysX Character Controller", false, "PhysX requires half side extent to be positive."); return; } static_cast(m_pxController)->setHalfSideExtent(halfSideExtent); } else { AZ_Error("PhysX Character Controller", false, "Half side extent is only defined for box controllers."); } } float CharacterController::GetHalfForwardExtent() const { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return 0.0f; } PHYSX_SCENE_READ_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { return static_cast(m_pxController)->getHalfForwardExtent(); } AZ_Error("PhysX Character Controller", false, "Half forward extent is only defined for box controllers."); return 0.0f; } void CharacterController::SetHalfForwardExtent(float halfForwardExtent) { if (!m_pxController) { AZ_Error("PhysX Character Controller", false, "Invalid character controller."); return; } PHYSX_SCENE_WRITE_LOCK(m_pxController->getScene()); if (m_pxController->getType() == physx::PxControllerShapeType::eBOX) { if (halfForwardExtent <= 0.0f) { AZ_Error("PhysX Character Controller", false, "PhysX requires half forward extent to be positive."); return; } static_cast(m_pxController)->setHalfForwardExtent(halfForwardExtent); } else { AZ_Error("PhysX Character Controller", false, "Half forward extent is only defined for box controllers."); } } void CharacterController::UpdatePxControllerFilters(Physics::CollisionLayer collisionLayer, Physics::CollisionGroup collisionGroup) { PhysX::SystemRequestsBus::BroadcastResult(m_filterData, &PhysX::SystemRequests::CreateFilterData, collisionLayer, collisionGroup); m_pxControllerFilters = physx::PxControllerFilters(&m_filterData); m_pxControllerFilters.mFilterFlags = physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER; m_pxControllerFilters.mFilterCallback = this; m_pxControllerFilters.mCCTFilterCallback = this; } } // namespace PhysXCharacters