/* * 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 namespace PhysX { void EditorForceRegionComponent::EditorForceProxy::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("Type", &EditorForceProxy::m_type) ->Field("ForceWorldSpace", &EditorForceProxy::m_forceWorldSpace) ->Field("ForceLocalSpace", &EditorForceProxy::m_forceLocalSpace) ->Field("ForcePoint", &EditorForceProxy::m_forcePoint) ->Field("ForceSplineFollow", &EditorForceProxy::m_forceSplineFollow) ->Field("ForceSimpleDrag", &EditorForceProxy::m_forceSimpleDrag) ->Field("ForceLinearDamping", &EditorForceProxy::m_forceLinearDamping) ; if (auto editContext = serializeContext->GetEditContext()) { editContext->Class( "Forces", "forces") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorForceProxy::m_type, "Force Type", "") ->EnumAttribute(ForceType::WorldSpace, "World Space") ->EnumAttribute(ForceType::LocalSpace, "Local Space") ->EnumAttribute(ForceType::Point, "Point") ->EnumAttribute(ForceType::SplineFollow, "Spline Follow") ->EnumAttribute(ForceType::SimpleDrag, "Simple Drag") ->EnumAttribute(ForceType::LinearDamping, "Linear Damping") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forceWorldSpace, "World Space Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsWorldSpaceForce) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forceLocalSpace, "Local Space Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsLocalSpaceForce) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forcePoint, "Point Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsPointForce) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forceSplineFollow, "Spline Follow Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsSplineFollowForce) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forceSimpleDrag, "Simple Drag Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsSimpleDragForce) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceProxy::m_forceLinearDamping, "Linear Damping Force", "") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorForceProxy::IsLinearDampingForce) ; } } } const BaseForce& EditorForceRegionComponent::EditorForceProxy::GetCurrentBaseForce() const { auto* deconstThis = const_cast(this); return deconstThis->GetCurrentBaseForce(); } BaseForce& EditorForceRegionComponent::EditorForceProxy::GetCurrentBaseForce() { switch (m_type) { case ForceType::WorldSpace: return m_forceWorldSpace; case ForceType::LocalSpace: return m_forceLocalSpace; case ForceType::Point: return m_forcePoint; case ForceType::SplineFollow: return m_forceSplineFollow; case ForceType::SimpleDrag: return m_forceSimpleDrag; case ForceType::LinearDamping: return m_forceLinearDamping; default: AZ_Error("EditorForceRegionComponent", false, "Unsupported force type"); return m_forceWorldSpace; } } void EditorForceRegionComponent::EditorForceProxy::Activate(AZ::EntityId entityId) { BaseForce& force = GetCurrentBaseForce(); force.Activate(entityId); } void EditorForceRegionComponent::EditorForceProxy::Deactivate() { BaseForce& force = GetCurrentBaseForce(); force.Deactivate(); } AZ::Vector3 EditorForceRegionComponent::EditorForceProxy::CalculateForce(const EntityParams& entity, const RegionParams& region) const { const BaseForce& force = GetCurrentBaseForce(); return force.CalculateForce(entity, region); } bool EditorForceRegionComponent::EditorForceProxy::IsWorldSpaceForce() const { return m_type == ForceType::WorldSpace; } bool EditorForceRegionComponent::EditorForceProxy::IsLocalSpaceForce() const { return m_type == ForceType::LocalSpace; } bool EditorForceRegionComponent::EditorForceProxy::IsPointForce() const { return m_type == ForceType::Point; } bool EditorForceRegionComponent::EditorForceProxy::IsSplineFollowForce() const { return m_type == ForceType::SplineFollow; } bool EditorForceRegionComponent::EditorForceProxy::IsSimpleDragForce() const { return m_type == ForceType::SimpleDrag; } bool EditorForceRegionComponent::EditorForceProxy::IsLinearDampingForce() const { return m_type == ForceType::LinearDamping; } void EditorForceRegionComponent::Reflect(AZ::ReflectContext* context) { EditorForceRegionComponent::EditorForceProxy::Reflect(context); if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Field("Visible", &EditorForceRegionComponent::m_visibleInEditor) ->Field("DebugForces", &EditorForceRegionComponent::m_debugForces) ->Field("Forces", &EditorForceRegionComponent::m_forces) ->Version(2) ; if (auto editContext = serializeContext->GetEditContext()) { // EditorForceRegionComponent editContext->Class( "PhysX Force Region", "The force region component is used to apply a physical force on objects within the region") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "PhysX") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/ForceRegion.png") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/ForceRegion.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/console/lumberyard/physx/force-region") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC("PhysXTriggerService", 0x3a117d7b)) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceRegionComponent::m_visibleInEditor, "Visible", "Always show the component in viewport") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceRegionComponent::m_debugForces, "Debug Forces", "Draws debug arrows when an entity enters a force region. This occurs in gameplay mode to show the force direction on an entity.") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorForceRegionComponent::m_forces, "Forces", "Forces in force region") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorForceRegionComponent::OnForcesChanged) ; } } } void EditorForceRegionComponent::BuildGameEntity(AZ::Entity* gameEntity) { ForceRegion forceRegion; using ForceType = EditorForceRegionComponent::EditorForceProxy::ForceType; // Copy edit component's forces to game-time component. for (auto& forceProxy : m_forces) { forceProxy.Deactivate(); switch (forceProxy.m_type) { case ForceType::WorldSpace: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forceWorldSpace)); break; case ForceType::LocalSpace: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forceLocalSpace)); break; case ForceType::Point: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forcePoint)); break; case ForceType::SplineFollow: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forceSplineFollow)); break; case ForceType::SimpleDrag: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forceSimpleDrag)); break; case ForceType::LinearDamping: forceRegion.AddAndActivateForce(AZStd::make_unique(forceProxy.m_forceLinearDamping)); break; } } gameEntity->CreateComponent(std::move(forceRegion), m_debugForces); } void EditorForceRegionComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("ForceRegionService", 0x3c3e4061)); incompatible.push_back(AZ_CRC("LegacyCryPhysicsService", 0xbb370351)); } void EditorForceRegionComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ForceRegionService", 0x3c3e4061)); } void EditorForceRegionComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC("TransformService", 0x8ee22c50)); required.push_back(AZ_CRC("PhysXTriggerService", 0x3a117d7b)); } void EditorForceRegionComponent::Activate() { AZ::EntityId entityId = GetEntityId(); EditorComponentBase::Activate(); AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(entityId); for (auto& force : m_forces) { force.Activate(entityId); } AZ_Warning("PhysX Force Region" , PhysX::Utils::TriggerColliderExists(entityId) , "Please ensure collider component marked as trigger exists in entity <%s: %s> with force region." , GetEntity()->GetName().c_str() , entityId.ToString().c_str()); } void EditorForceRegionComponent::Deactivate() { for (auto& force : m_forces) { force.Deactivate(); } AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); EditorComponentBase::Deactivate(); } void EditorForceRegionComponent::DisplayEntityViewport(const AzFramework::ViewportInfo& viewportInfo , AzFramework::DebugDisplayRequests& debugDisplayRequests) { if (!IsSelected() && !m_visibleInEditor) { return; } AZ::Entity* forceRegionEntity = GetEntity(); if (!forceRegionEntity) { return; } // Update AABB cache of collider components if they're outdated or dirty. AZ::Aabb aabb; ColliderShapeRequestBus::EventResult(aabb , GetEntityId() , &ColliderShapeRequestBus::Events::GetColliderShapeAabb); const AZ::Entity::ComponentArrayType& enabledComponents = forceRegionEntity->GetComponents(); for (AZ::Component* component : enabledComponents) { EditorColliderComponent* editorColliderComponent = azrtti_cast(component); EditorShapeColliderComponent* editorShapeColliderComponent = azrtti_cast(component); if (!editorColliderComponent && !editorShapeColliderComponent) { continue; } if (editorColliderComponent) { const PhysX::EditorProxyShapeConfig& shapeConfig = editorColliderComponent->GetShapeConfiguration(); AZStd::vector randomPoints; if (shapeConfig.IsBoxConfig()) { AZ::Vector3 dimensions = shapeConfig.m_box.m_dimensions; randomPoints = Utils::Geometry::GenerateBoxPoints(dimensions * -0.5f , dimensions * 0.5f); } else if (shapeConfig.IsCapsuleConfig()) { float height = shapeConfig.m_capsule.m_height; float radius = shapeConfig.m_capsule.m_radius; randomPoints = Utils::Geometry::GenerateCylinderPoints(height - radius * 2.0f , radius); } else if (shapeConfig.IsSphereConfig()) { float radius = shapeConfig.m_sphere.m_radius; randomPoints = Utils::Geometry::GenerateSpherePoints(radius); } else if (shapeConfig.IsAssetConfig()) { const AZ::Vector3 halfExtents = aabb.GetExtents() * 0.5f; randomPoints = Utils::Geometry::GenerateBoxPoints(-halfExtents, halfExtents); } PhysX::Utils::ColliderPointsLocalToWorld(randomPoints , GetWorldTM() , editorColliderComponent->GetColliderConfiguration().m_position , editorColliderComponent->GetColliderConfiguration().m_rotation); DrawForceArrows(randomPoints, debugDisplayRequests); } else if (editorShapeColliderComponent) { DrawForceArrows(editorShapeColliderComponent->GetSamplePoints(), debugDisplayRequests); } } } void EditorForceRegionComponent::DrawForceArrows(const AZStd::vector& arrowPositions , AzFramework::DebugDisplayRequests& debugDisplayRequests) { const AZ::Color color = AZ::Color(0.f, 0.f, 1.f, 1.f); debugDisplayRequests.SetColor(color); EntityParams entityParams; entityParams.m_id.SetInvalid(); entityParams.m_velocity = AZ::Vector3::CreateZero(); entityParams.m_mass = 1.f; const PhysX::RegionParams regionParams = ForceRegionUtil::CreateRegionParams(GetEntityId()); for (const auto& arrowPosition : arrowPositions) { entityParams.m_position = arrowPosition; auto totalForce = AZ::Vector3::CreateZero(); for (const auto& force : m_forces) { totalForce += force.CalculateForce(entityParams, regionParams); } if (!totalForce.IsZero() && totalForce.IsFinite()) { totalForce.Normalize(); totalForce *= 0.5f; const float arrowHeadScale = 1.5f; debugDisplayRequests.DrawArrow(arrowPosition - totalForce , arrowPosition + totalForce , arrowHeadScale); } else { const float ballRadius = 0.05f; debugDisplayRequests.DrawBall(arrowPosition, ballRadius); } } } bool EditorForceRegionComponent::HasSplineFollowForce() const { for (const auto& force : m_forces) { if (force.m_type == EditorForceProxy::ForceType::SplineFollow) { return true; } } return false; } void EditorForceRegionComponent::OnForcesChanged() const { if (HasSplineFollowForce()) { AZ::ConstSplinePtr splinePtr = nullptr; LmbrCentral::SplineComponentRequestBus::EventResult(splinePtr, GetEntityId() , &LmbrCentral::SplineComponentRequestBus::Events::GetSpline); AZ::Entity* entity = GetEntity(); AZ_Warning("PhysX EditorForceRegionComponent" , splinePtr!=nullptr , "Please add spline shape for force region in entity <%s: %s>." , entity->GetName().c_str() , entity->GetId().ToString().c_str()); } ForceRegionNotificationBus::Broadcast( &ForceRegionNotificationBus::Events::OnForceRegionForceChanged, GetEntityId()); } }