/* * 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 "LmbrCentral_precompiled.h" #if ENABLE_CRY_PHYSICS #include "PhysicsSystemComponent.h" #include <AzFramework/Physics/PhysicsComponentBus.h> #include <LmbrCentral/Physics/CryPhysicsComponentRequestBus.h> #include <LmbrCentral/Rendering/MeshComponentBus.h> #include <LmbrCentral/Rendering/MaterialOwnerBus.h> #include <AzCore/Casting/numeric_cast.h> #include <AzCore/RTTI/BehaviorContext.h> #include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/EditContext.h> #include <AzCore/Script/ScriptContextAttributes.h> #include <AzCore/Component/TransformBus.h> #include <AzCore/Math/Aabb.h> #include <AzCore/Math/MathUtils.h> #include <MathConversion.h> #include <IEntity.h> #include <IEntityRenderState.h> #include <IMaterial.h> #include <IPhysics.h> #include <ISurfaceType.h> #include <physinterface.h> namespace LmbrCentral { using AzFramework::PhysicsComponentNotificationBus; using AzFramework::PhysicsSystemRequestBus; using AzFramework::PhysicsSystemEventBus; using AzFramework::PhysicalEntityTypes; // Class for putting ent_* flags on class PhysicalEntityTypesHolder { public: AZ_TYPE_INFO(PhysicalEntityTypesHolder, "{0D6FF6AB-3C2B-4D44-A6CA-B3C41478EF94}"); AZ_CLASS_ALLOCATOR(PhysicalEntityTypesHolder, AZ::SystemAllocator, 0); PhysicalEntityTypesHolder() = default; ~PhysicalEntityTypesHolder() = default; static PhysicalEntityTypes ToggleEntityTypeMask(PhysicalEntityTypes currentEntityType, PhysicalEntityTypes entityTypeToggleValue) { return static_cast<PhysicalEntityTypes>(currentEntityType ^ entityTypeToggleValue); } }; AZ::u32 EntFromEntityTypes(AZ::u32 types) { // Shortcut when requesting all entities if (types == PhysicalEntityTypes::All) { return ent_all; } AZ::u32 result = 0; if (types & PhysicalEntityTypes::Static) { result |= ent_static; } if (types & PhysicalEntityTypes::Dynamic) { result |= ent_rigid | ent_sleeping_rigid; } if (types & PhysicalEntityTypes::Living) { result |= ent_living; } if (types & PhysicalEntityTypes::Independent) { result |= ent_independent; } if (types & PhysicalEntityTypes::Terrain) { result |= ent_terrain; } return result; } /** * Behavior handler for PhysicsSystemEventBus */ class PhysicsSystemEventBusBehaviorHandler : public PhysicsSystemEventBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(PhysicsSystemEventBusBehaviorHandler, "{BA278D21-0BB0-43B9-8FFA-08BE25316BC4}", AZ::SystemAllocator , OnPrePhysicsUpdate , OnPostPhysicsUpdate ); void OnPrePhysicsUpdate() override { Call(FN_OnPrePhysicsUpdate); } void OnPostPhysicsUpdate() override { Call(FN_OnPostPhysicsUpdate); } }; // Overrides for functions that take indices, to account for Lua's 1-based indexing. namespace RayCastResultScriptOverrides { static const AzFramework::PhysicsSystemRequests::RayCastHit* GetHit(AzFramework::PhysicsSystemRequests::RayCastResult* self, int index) { return self->GetHit(index - 1); } static const AzFramework::PhysicsSystemRequests::RayCastHit* GetPiercingHit(AzFramework::PhysicsSystemRequests::RayCastResult* self, int index) { return self->GetPiercingHit(index - 1); } }; void PhysicsSystemComponent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) { serializeContext->Class<PhysicsSystemComponent, AZ::Component>() ->Version(1) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class<PhysicsSystemComponent>( "CryPhysics Manager", "Allows Component Entities to work with CryPhysics") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Game") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b)) ; } serializeContext->Class<RayCastHit>() ->Version(1) ->Field("distance", &RayCastHit::m_distance) ->Field("position", &RayCastHit::m_position) ->Field("normal", &RayCastHit::m_normal) ->Field("entityId", &RayCastHit::m_entityId) ; serializeContext->Class<RayCastResult>() ->Version(1); serializeContext->Class<RayCastConfiguration>() ->Version(1) ->Field("origin", &RayCastConfiguration::m_origin) ->Field("direction", &RayCastConfiguration::m_direction) ->Field("maxDistance", &RayCastConfiguration::m_maxDistance) ->Field("ignoreEntityIds", &RayCastConfiguration::m_ignoreEntityIds) ->Field("maxHits", &RayCastConfiguration::m_maxHits) ->Field("piercesSurfacesGreaterThan", &RayCastConfiguration::m_piercesSurfacesGreaterThan) ->Field("physicalEntityTypes", &RayCastConfiguration::m_physicalEntityTypes) ; } if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) { // RayCast return type behaviorContext->Class<RayCastHit>("LegacyRayCastHit") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Property("distance", BehaviorValueGetter(&RayCastHit::m_distance), nullptr) ->Property("position", BehaviorValueGetter(&RayCastHit::m_position), nullptr) ->Property("normal", BehaviorValueGetter(&RayCastHit::m_normal), nullptr) ->Property("entityId", BehaviorValueGetter(&RayCastHit::m_entityId), nullptr) ->Method("IsValid", &RayCastHit::IsValid) ; // RayCastConfiguration behaviorContext->Class<RayCastConfiguration>("LegacyRayCastConfiguration") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Property("origin", BehaviorValueProperty(&RayCastConfiguration::m_origin)) ->Property("direction", BehaviorValueProperty(&RayCastConfiguration::m_direction)) ->Property("maxDistance", BehaviorValueProperty(&RayCastConfiguration::m_maxDistance)) ->Property("ignoreEntityIds", BehaviorValueProperty(&RayCastConfiguration::m_ignoreEntityIds)) ->Property("maxHits", BehaviorValueProperty(&RayCastConfiguration::m_maxHits)) ->Property("piercesSurfacesGreaterThan", BehaviorValueProperty(&RayCastConfiguration::m_piercesSurfacesGreaterThan)) ->Property("physicalEntityTypes", BehaviorValueProperty(&RayCastConfiguration::m_physicalEntityTypes)) ; // RayCastResult behaviorContext->Class<RayCastResult>("LegacyRayCastResult") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Method("GetHitCount", &RayCastResult::GetHitCount) ->Method("GetHit", &RayCastResult::GetHit) ->Attribute(AZ::Script::Attributes::MethodOverride, &RayCastResultScriptOverrides::GetHit) ->Method("HasBlockingHit", &RayCastResult::HasBlockingHit) ->Method("GetBlockingHit", &RayCastResult::GetBlockingHit) ->Method("GetPiercingHitCount", &RayCastResult::GetPiercingHitCount) ->Method("GetPiercingHit", &RayCastResult::GetPiercingHit) ->Attribute(AZ::Script::Attributes::MethodOverride, &RayCastResultScriptOverrides::GetPiercingHit) // script operators ->Method("ScriptLength", [](RayCastResult* self) { return aznumeric_cast<int>(self->GetHitCount()); }) ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Length) ->Method("ScriptIndexRead", &RayCastResult::GetHit) ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::IndexRead) ->Attribute(AZ::Script::Attributes::MethodOverride, &RayCastResultScriptOverrides::GetHit) ; // Entity query flags behaviorContext->Class<PhysicalEntityTypesHolder>("PhysicalEntityTypes") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview) ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Constant("Static", BehaviorConstant(AzFramework::PhysicalEntityTypes::Static)) ->Constant("Dynamic", BehaviorConstant(AzFramework::PhysicalEntityTypes::Dynamic)) ->Constant("Living", BehaviorConstant(AzFramework::PhysicalEntityTypes::Living)) ->Constant("Independent", BehaviorConstant(AzFramework::PhysicalEntityTypes::Independent)) ->Constant("Terrain", BehaviorConstant(AzFramework::PhysicalEntityTypes::Terrain)) ->Constant("All", BehaviorConstant(AzFramework::PhysicalEntityTypes::All)) ; // Global function for bitwise toggle to configure ray casting entity types. // Bit manipulation for Lua is disabled for security purposes. behaviorContext->Method("TogglePhysicalEntityTypeMask", &PhysicalEntityTypesHolder::ToggleEntityTypeMask); behaviorContext->EBus<PhysicsSystemRequestBus>("PhysicsSystemRequestBus") ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Event("RayCast", &PhysicsSystemRequestBus::Events::RayCast) ->Event("GatherPhysicalEntitiesInAABB", &PhysicsSystemRequestBus::Events::GatherPhysicalEntitiesInAABB) ->Event("GatherPhysicalEntitiesAroundPoint", &PhysicsSystemRequestBus::Events::GatherPhysicalEntitiesAroundPoint) ; behaviorContext->EBus<PhysicsSystemEventBus>("PhysicsSystemEventBus") ->Handler<PhysicsSystemEventBusBehaviorHandler>() ; } } // private namespace for helper functions namespace PhysicalWorldEventClient { // return whether object involved in event was an AZ::Entity static bool InvolvesAzEntity(const EventPhysMono& event) { return event.iForeignData == PHYS_FOREIGN_ID_COMPONENT_ENTITY; } // return whether either object involved in event was an AZ::Entity static bool InvolvesAzEntity(const EventPhysStereo& event) { return event.iForeignData[0] == PHYS_FOREIGN_ID_COMPONENT_ENTITY || event.iForeignData[1] == PHYS_FOREIGN_ID_COMPONENT_ENTITY; } static AZ::EntityId GetEntityId(const EventPhysMono& event) { if (event.iForeignData == PHYS_FOREIGN_ID_COMPONENT_ENTITY) { return static_cast<AZ::EntityId>(event.pForeignData); } return AZ::EntityId(); } static AZ::EntityId GetEntityId(const EventPhysStereo& event, int entityIndex) { AZ_Assert(entityIndex >= 0 && entityIndex <= 1, "invalid entityI"); if (event.iForeignData[entityIndex] == PHYS_FOREIGN_ID_COMPONENT_ENTITY) { return static_cast<AZ::EntityId>(event.pForeignData[entityIndex]); } return AZ::EntityId(); } static int GetSurfaceId(const EventPhysStereo& event, int entityIndex) { AZ_Assert(entityIndex >= 0 && entityIndex <= 1, "invalid entityI"); _smart_ptr<IMaterial> mat = nullptr; int type = event.iForeignData[entityIndex]; switch (type) { case PHYS_FOREIGN_ID_COMPONENT_ENTITY: { AZ::EntityId id = static_cast<AZ::EntityId>(event.pForeignData[entityIndex]); LmbrCentral::MaterialOwnerRequestBus::EventResult(mat, id, &LmbrCentral::MaterialOwnerRequestBus::Events::GetMaterial); break; } case PHYS_FOREIGN_ID_ENTITY: { IEntity* entity = static_cast<IEntity*>(event.pForeignData[entityIndex]); if (entity != nullptr) { mat = entity->GetMaterial(); } break; } case PHYS_FOREIGN_ID_STATIC: { IRenderNode* pRenderNode = static_cast<IRenderNode*>(event.pForeignData[entityIndex]); if (pRenderNode != nullptr) { mat = pRenderNode->GetMaterial(); } break; } } if (mat == nullptr || mat->GetSurfaceType() == nullptr) { return 0; } return mat->GetSurfaceType()->GetId(); } // An IPhysicalWorld event client can prevent the event // from propagating any further enum EventClientReturnValues { StopEventProcessing = 0, ContinueEventProcessing = 1, }; static int OnCollision(const EventPhys* event) { const EventPhysCollision& collisionIn = static_cast<const EventPhysCollision&>(*event); if (!InvolvesAzEntity(collisionIn)) { return ContinueEventProcessing; } AzFramework::PhysicsComponentNotifications::Collision collision; collision.m_position = LYVec3ToAZVec3(collisionIn.pt); collision.m_normal = LYVec3ToAZVec3(collisionIn.n); collision.m_impulse = collisionIn.normImpulse; for (int senderI : {0, 1}) { AZ::EntityId sender = GetEntityId(collisionIn, senderI); if (sender.IsValid()) { int otherI = 1 - senderI; collision.m_entity = GetEntityId(collisionIn, otherI); collision.m_velocities[0] = LYVec3ToAZVec3(collisionIn.vloc[senderI]); collision.m_velocities[1] = LYVec3ToAZVec3(collisionIn.vloc[otherI]); collision.m_masses[0] = collisionIn.mass[senderI]; collision.m_masses[1] = collisionIn.mass[otherI]; collision.m_surfaces[0] = GetSurfaceId(collisionIn, senderI); collision.m_surfaces[1] = GetSurfaceId(collisionIn, otherI); using CollisionPoint = AzFramework::PhysicsComponentNotifications::CollisionPoint; CollisionPoint point; point.m_position = collision.m_position; point.m_normal = collision.m_normal; point.m_impulse = collision.m_impulse * collision.m_normal; point.m_separation = 0.0f; point.m_internalFaceIndex01 = collision.m_surfaces[0]; point.m_internalFaceIndex02 = collision.m_surfaces[1]; collision.m_collisionPoints = { point }; EBUS_EVENT_ID(sender, PhysicsComponentNotificationBus, OnCollision, collision); } } return ContinueEventProcessing; } static int OnPostStep(const EventPhys* event) { auto& eventIn = static_cast<const EventPhysPostStep&>(*event); if (!InvolvesAzEntity(eventIn)) { return ContinueEventProcessing; } EntityPhysicsEvents::PostStep eventOut; eventOut.m_entity = GetEntityId(eventIn); eventOut.m_entityPosition = LYVec3ToAZVec3(eventIn.pos); eventOut.m_entityRotation = LYQuaternionToAZQuaternion(eventIn.q); eventOut.m_stepTimeDelta = eventIn.dt; eventOut.m_stepId = eventIn.idStep; EBUS_EVENT_ID(eventOut.m_entity, EntityPhysicsEventBus, OnPostStep, eventOut); return ContinueEventProcessing; } } // namespace PhysicalWorldEventClient void PhysicsSystemComponent::Activate() { PhysicsSystemRequestBus::Handler::BusConnect(); CrySystemEventBus::Handler::BusConnect(); } void PhysicsSystemComponent::Deactivate() { PhysicsSystemRequestBus::Handler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); SetEnabled(false); } PhysicsSystemComponent::RayCastResult PhysicsSystemComponent::RayCast(const RayCastConfiguration& configuration) { AZ_Warning("Physics", configuration.m_direction.IsNormalized(), "RayCast direction should be normalized."); // Destination for CryPhysics hits. // CryPhysics always reserves index 0 for the blocking hit. // We ask for 1 extra hit to avoid situation where user asks for two hits, // and no blocking surfaces are encountered, but only 1 piercing hit is // returned because index 0 was reserved for a blocking hit. AZStd::vector<ray_hit> cryHits(configuration.m_maxHits + 1); // Fill out SWRIParams based on RayCastConfiguration IPhysicalWorld::SRWIParams cryParams; cryParams.org = AZVec3ToLYVec3(configuration.m_origin); cryParams.dir = AZVec3ToLYVec3(configuration.m_direction * configuration.m_maxDistance); cryParams.objtypes = EntFromEntityTypes(configuration.m_physicalEntityTypes); cryParams.hits = cryHits.data(); cryParams.nMaxHits = cryHits.size(); cryParams.flags = rwi_pierceability(AZ::GetClamp<decltype(configuration.m_piercesSurfacesGreaterThan)>(configuration.m_piercesSurfacesGreaterThan, 0, sf_max_pierceable)); // Set ignored entities AZStd::vector<IPhysicalEntity*> ignorePhysicalEntities; ignorePhysicalEntities.reserve(configuration.m_ignoreEntityIds.size()); for (const AZ::EntityId& entityId : configuration.m_ignoreEntityIds) { IPhysicalEntity* physicalEntity = nullptr; EBUS_EVENT_ID_RESULT(physicalEntity, entityId, LmbrCentral::CryPhysicsComponentRequestBus, GetPhysicalEntity); if (physicalEntity) { ignorePhysicalEntities.push_back(physicalEntity); } } cryParams.pSkipEnts = ignorePhysicalEntities.data(); cryParams.nSkipEnts = ignorePhysicalEntities.size(); // Perform raycast const int hitCount = m_physicalWorld->RayWorldIntersection(cryParams); // We asked CryPhysics for 1 more hit than user actually wants. // Trim off the extra if necessary. if (hitCount > configuration.m_maxHits) { cryHits.pop_back(); } // Fill out RayCastResults based on cryHits RayCastResult results; for (size_t cryHitsIndex = 0; cryHitsIndex < cryHits.size(); ++cryHitsIndex) { const ray_hit& cryHit = cryHits[cryHitsIndex]; // Note that CryPhysics always puts the blocking hit at index 0. // If no blocking hits occurred then index 0 holds a dummy entry with distance of -1. if (cryHit.dist < 0.f) { if (cryHitsIndex == 0) { continue; } else { break; } } RayCastHit hit; hit.m_distance = cryHit.dist; hit.m_position = LYVec3ToAZVec3(cryHit.pt); hit.m_normal = LYVec3ToAZVec3(cryHit.n); if (cryHit.pCollider && cryHit.pCollider->GetiForeignData() == PHYS_FOREIGN_ID_COMPONENT_ENTITY) { hit.m_entityId = static_cast<AZ::EntityId>(cryHit.pCollider->GetForeignData(PHYS_FOREIGN_ID_COMPONENT_ENTITY)); } if (cryHitsIndex == 0) { results.SetBlockingHit(hit); } else { results.AddPiercingHit(hit); } } return results; } AZStd::vector<AZ::EntityId> PhysicsSystemComponent::GatherPhysicalEntitiesInAABB(const AZ::Aabb& aabb, AZ::u32 query) { AZStd::vector<AZ::EntityId> gatheredEntityIds; IPhysicalEntity** results = nullptr; const int resultCount = m_physicalWorld->GetEntitiesInBox( AZVec3ToLYVec3(aabb.GetMin()), AZVec3ToLYVec3(aabb.GetMax()), results, EntFromEntityTypes(query)); if (resultCount > 0) { AZ_Assert(results != nullptr, "Invalid result list returned with positive count."); for (int i = 0; i < resultCount; ++i) { IPhysicalEntity* physicalEntity = results[i]; AZ_Error("Physics", physicalEntity, "Invalid entity in GetPhysicalEntities query results."); if (physicalEntity && physicalEntity->GetiForeignData() == PHYS_FOREIGN_ID_COMPONENT_ENTITY) { const AZ::EntityId id = static_cast<AZ::EntityId>(physicalEntity->GetForeignData(PHYS_FOREIGN_ID_COMPONENT_ENTITY)); gatheredEntityIds.push_back(id); } } } return gatheredEntityIds; } AZStd::vector<AZ::EntityId> PhysicsSystemComponent::GatherPhysicalEntitiesAroundPoint(const AZ::Vector3& center, float radius, AZ::u32 query) { const float radiusSq = radius * radius; const AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(center, radius); AZStd::vector<AZ::EntityId> gatheredEntityIds = GatherPhysicalEntitiesInAABB(aabb, query); for (auto iter = gatheredEntityIds.begin(); iter != gatheredEntityIds.end(); ) { AZ::Transform transform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(transform, *iter, &AZ::TransformInterface::GetWorldTM); if ((center - transform.GetTranslation()).GetLengthSq() > radiusSq) { iter = gatheredEntityIds.erase(iter); } else { ++iter; } } return gatheredEntityIds; } void PhysicsSystemComponent::OnCrySystemPrePhysicsUpdate() { EBUS_EVENT(PhysicsSystemEventBus, OnPrePhysicsUpdate); } void PhysicsSystemComponent::OnCrySystemPostPhysicsUpdate() { EBUS_EVENT(PhysicsSystemEventBus, OnPostPhysicsUpdate); } void PhysicsSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams&) { m_physicalWorld = system.GetGlobalEnvironment()->pPhysicalWorld; SetEnabled(true); } void PhysicsSystemComponent::OnCrySystemShutdown(ISystem& system) { // Purposely clearing m_physicalWorld before SetEnabled(false). // The IPhysicalWorld is going away, it doesn't matter if we carefully unregister from it. m_physicalWorld = nullptr; SetEnabled(false); } void PhysicsSystemComponent::SetEnabled(bool enable) { // can't be enabled if physical world doesn't exist if (!m_physicalWorld) { m_enabled = false; return; } if (enable == m_enabled) { return; } // Params for calls to IPhysicalWorld::AddEventClient and RemoveEventClient. enum { PARAM_ID, // id of the IPhysicalWorld event type PARAM_HANDLER, // handler function PARAM_LOGGED, // 1 for a logged event version, 0 for immediate PARAM_PRIORITY, // priority (higher means handled first) }; std::tuple<int, int(*)(const EventPhys*), int, float> physicalWorldEventClientParams[] = { std::make_tuple(EventPhysCollision::id, PhysicalWorldEventClient::OnCollision, 1, 1.f), std::make_tuple(EventPhysPostStep::id, PhysicalWorldEventClient::OnPostStep, 1, 1.f), }; // start/stop listening for events for (auto& i : physicalWorldEventClientParams) { if (enable) { m_physicalWorld->AddEventClient(std::get<PARAM_ID>(i), std::get<PARAM_HANDLER>(i), std::get<PARAM_LOGGED>(i), std::get<PARAM_PRIORITY>(i)); } else { m_physicalWorld->RemoveEventClient(std::get<PARAM_ID>(i), std::get<PARAM_HANDLER>(i), std::get<PARAM_LOGGED>(i)); } } m_enabled = enable; } } // namespace LmbrCentral #endif // ENABLE_CRY_PHYSICS