/* * 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 "PrimitiveColliderComponent.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace LmbrCentral { using AzFramework::ColliderComponentRequestBus; using AzFramework::ColliderComponentEventBus; extern AZ::Transform GetTransformForColliderGeometry(const IPhysicalEntity& physicalEntity, const AZ::EntityId& colliderEntityId); ISurfaceTypeManager* GetSurfaceTypeManager() { if (gEnv && gEnv->p3DEngine && gEnv->p3DEngine->GetMaterialManager()) { return gEnv->p3DEngine->GetMaterialManager()->GetSurfaceTypeManager(); } return nullptr; } AZStd::vector GetSurfaceTypeNames() { AZStd::vector names; if (ISurfaceTypeManager* pSurfaceManager = GetSurfaceTypeManager()) { if (ISurfaceTypeEnumerator* surfaceEnumerator = pSurfaceManager->GetEnumerator()) { for (ISurfaceType* surfaceType = surfaceEnumerator->GetFirst(); surfaceType; surfaceType = surfaceEnumerator->GetNext()) { // put default material at start of list if (surfaceType->GetId() == 0) { names.insert(names.begin(), surfaceType->GetName()); } else { names.emplace_back(surfaceType->GetName()); } } } } // make empty string the very first entry. It's the default value of PrimitiveColliderConfig::m_surfaceTypeName names.insert(names.begin(), ""); // alphabetize everything after the empty string and the default material. if (names.size() >= 2) { AZStd::sort(names.begin() + 2, names.end()); } return names; } void PrimitiveColliderComponent::Reflect(AZ::ReflectContext* context) { AzFramework::PrimitiveColliderConfig::Reflect(context); AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(1) ->Field("Configuration", &PrimitiveColliderComponent::m_configuration) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { editContext->Class( "Primitive Collider", "The Primitive Collider component specifies that the collider geometry is provided by a primitive shape component") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Physics (Legacy)") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/PrimitiveCollider.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/PrimitiveCollider.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-physics-primitive-collider.html") ->DataElement(0, &PrimitiveColliderComponent::m_configuration, "Configuration", "") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->Constant("PrimitiveColliderComponentTypeId", BehaviorConstant(AzFramework::PrimitiveColliderComponentTypeId)); } } //! Create IGeometry. Returns IGeometry wrapped in a _smart_ptr. //! If unsuccessful _smart_ptr is empty. static _smart_ptr CreatePrimitiveGeometry(int primitiveType, const primitives::primitive& primitive) { IGeomManager& geometryManager = *gEnv->pPhysicalWorld->GetGeomManager(); _smart_ptr geometry = geometryManager.CreatePrimitive(primitiveType, &primitive); // IGeomManager::CreatePrimitive() returns a raw pointer with a refcount of 1. // Putting the IGeometry* in a _smart_ptr increments the refcount to 2. // Therefore, we decrement the refcount back to 1 so the thing we return // will behave like people expect. if (geometry) { geometry->Release(); } return geometry; } int PrimitiveColliderComponent::AddEntityShapeToPhysicalEntity(IPhysicalEntity& physicalEntity, int nextPartId, const AZ::EntityId& entityId) { AZ::Crc32 shapeType; ShapeComponentRequestsBus::EventResult(shapeType, entityId, &ShapeComponentRequests::GetShapeType); if (shapeType == AZ_CRC("Sphere", 0x55f96687)) { SphereShapeConfig config; SphereShapeComponentRequestsBus::EventResult( config, entityId, &SphereShapeComponentRequests::GetSphereConfiguration); primitives::sphere sphere; sphere.center.Set(0.f, 0.f, 0.f); sphere.r = config.m_radius; return AddPrimitiveFromEntityToPhysicalEntity(entityId, physicalEntity, nextPartId, primitives::sphere::type, sphere); } if (shapeType == AZ_CRC("Box", 0x08a9483a)) { BoxShapeConfig config; BoxShapeComponentRequestsBus::EventResult(config, entityId, &BoxShapeComponentRequests::GetBoxConfiguration); primitives::box box; box.Basis.SetIdentity(); box.bOriented = 0; box.center.Set(0.f, 0.f, 0.f); // box.m_size[i] is "half length" of side // config.m_dimensions[i] is "total length" of side box.size = AZVec3ToLYVec3(config.m_dimensions * 0.5f); return AddPrimitiveFromEntityToPhysicalEntity(entityId, physicalEntity, nextPartId, primitives::box::type, box); } if (shapeType == AZ_CRC("Cylinder", 0x9b045bea)) { CylinderShapeConfig config; CylinderShapeComponentRequestsBus::EventResult( config, entityId, &CylinderShapeComponentRequests::GetCylinderConfiguration); primitives::cylinder cylinder; cylinder.center.Set(0.0f, 0.0f, 0.0f); cylinder.axis.Set(0.0f, 0.0f, 1.0f); cylinder.r = config.m_radius; // cylinder.hh is "half height" // config.m_height is "total height" cylinder.hh = 0.5f * config.m_height; return AddPrimitiveFromEntityToPhysicalEntity(entityId, physicalEntity, nextPartId, primitives::cylinder::type, cylinder); } if (shapeType == AZ_CRC("Capsule", 0xc268a183)) { CapsuleShapeConfig config; CapsuleShapeComponentRequestsBus::EventResult( config, entityId, &CapsuleShapeComponentRequests::GetCapsuleConfiguration); primitives::capsule capsule; capsule.center.Set(0.f, 0.f, 0.f); capsule.axis.Set(0.f, 0.f, 1.f); capsule.r = config.m_radius; // config.m_height specifies "total height" of capsule. // capsule.hh is the "half height of the straight section of a capsule". // config.hh == (2 * capsule.hh) + (2 * capsule.r) capsule.hh = AZStd::max(0.f, (0.5f * config.m_height) - config.m_radius); return AddPrimitiveFromEntityToPhysicalEntity(entityId, physicalEntity, nextPartId, primitives::capsule::type, capsule); } if (shapeType == AZ_CRC("Compound", 0x8bde16f0)) { CompoundShapeConfiguration config; CompoundShapeComponentRequestsBus::EventResult( config, entityId, &CompoundShapeComponentRequests::GetCompoundShapeConfiguration); // Connect to EntityBus of child shapes. // If the child is active, OnEntityActivated will fire immediately // and we'll add the child's shape to physicalEntity. // If the child activates later, we won't try to add its shape, // we'll just send out a notification that the collider has changed. m_recipientOfNewlyActivatedShapes = &physicalEntity; m_recipientOfNewlyActivatedShapesNextPartId = nextPartId; m_recipientOfNewlyActivatedShapesFinalPartId = NoPartsAdded; for (const AZ::EntityId& childEntityId : config.GetChildEntities()) { AZ::EntityBus::MultiHandler::BusConnect(childEntityId); } m_recipientOfNewlyActivatedShapes = nullptr; return m_recipientOfNewlyActivatedShapesFinalPartId; } return NoPartsAdded; } void PrimitiveColliderComponent::OnEntityActivated(const AZ::EntityId& entityId) { // A compound shape's child entity has activated. // If m_recipientOfNewlyActivatedShapes is set, add colliders to it. // Otherwise, just send out a notification that the collider has changed. if (m_recipientOfNewlyActivatedShapes) { int finalPartId = AddEntityShapeToPhysicalEntity(*m_recipientOfNewlyActivatedShapes, m_recipientOfNewlyActivatedShapesNextPartId, entityId); if (finalPartId != NoPartsAdded) { m_recipientOfNewlyActivatedShapesFinalPartId = finalPartId; m_recipientOfNewlyActivatedShapesNextPartId = finalPartId + 1; } } else { // Check that child does in fact have shape on it before announcing shape change. AZ::Crc32 shapeType; ShapeComponentRequestsBus::EventResult(shapeType, entityId, &ShapeComponentRequests::GetShapeType); if (shapeType != AZ::Crc32()) { ColliderComponentEventBus::Event(GetEntityId(), &AzFramework::ColliderComponentEvents::OnColliderChanged); } } AZ::EntityBus::MultiHandler::BusDisconnect(entityId); } int PrimitiveColliderComponent::AddColliderToPhysicalEntity(IPhysicalEntity& physicalEntity, int nextPartId) { return AddEntityShapeToPhysicalEntity(physicalEntity, nextPartId, GetEntityId()); } int PrimitiveColliderComponent::AddPrimitiveFromEntityToPhysicalEntity( const AZ::EntityId& entityId, IPhysicalEntity& physicalEntity, int nextPartId, int primitiveType, const primitives::primitive& primitive) { _smart_ptr geometry = CreatePrimitiveGeometry(primitiveType, primitive); if (!geometry) { return NoPartsAdded; } phys_geometry* physGeometry = gEnv->pPhysicalWorld->GetGeomManager()->RegisterGeometry(geometry); if (!physGeometry) { return NoPartsAdded; } // configure pe_geomparams pe_geomparams geometryParameters; geometryParameters.flags = geom_collides | geom_floats | geom_colltype_ray; // set surface type if (!m_configuration.m_surfaceTypeName.empty()) { if (ISurfaceType* surfaceType = GetSurfaceTypeManager()->GetSurfaceTypeByName(m_configuration.m_surfaceTypeName.c_str())) { geometryParameters.surface_idx = surfaceType->GetId(); } } // set full local transform matrix // some (but not all!) meshes support non-uniform scaling AZ::Transform geometryAzTransform = GetTransformForColliderGeometry(physicalEntity, GetEntityId()); Matrix34 geometryTransform = AZTransformToLYTransform(geometryAzTransform); geometryParameters.pMtx3x4 = &geometryTransform; // add geometry int finalPartId = physicalEntity.AddGeometry(physGeometry, &geometryParameters, nextPartId); // release our phys_geometry reference (IPhysicalEntity should have // a reference now) gEnv->pPhysicalWorld->GetGeomManager()->UnregisterGeometry(physGeometry); return finalPartId; } void PrimitiveColliderComponent::Activate() { ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); ColliderComponentRequestBus::Handler::BusConnect(GetEntityId()); } void PrimitiveColliderComponent::Deactivate() { ColliderComponentRequestBus::Handler::BusDisconnect(); ShapeComponentNotificationsBus::Handler::BusDisconnect(); } bool PrimitiveColliderComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) { if (auto config = azrtti_cast(baseConfig)) { m_configuration = *config; return true; } return false; } bool PrimitiveColliderComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const { if (auto outConfig = azrtti_cast(outBaseConfig)) { *outConfig = m_configuration; return true; } return false; } void PrimitiveColliderComponent::OnShapeChanged(ShapeComponentNotifications::ShapeChangeReasons changeReason) { if (changeReason == ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged) { AZ_Warning("[Primitive Collider Component]", false, "Primitive collider component does not currently support dynamic changes to collider shape...Entity %s [%llu]..." , m_entity->GetName().c_str(), m_entity->GetId()); } } } // namespace LmbrCentral namespace AzFramework { void PrimitiveColliderConfig::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(1) ->Field("SurfaceTypeName", &PrimitiveColliderConfig::m_surfaceTypeName) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { editContext->Class("Primitive Collider Configuration", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::ComboBox, &PrimitiveColliderConfig::m_surfaceTypeName, "Surface Type", "The collider will use this surface type in the physics simulation.") ->Attribute(AZ::Edit::Attributes::StringList, &LmbrCentral::GetSurfaceTypeNames) ; } } if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->Class() ->Property("SurfaceTypeName", BehaviorValueProperty(&PrimitiveColliderConfig::m_surfaceTypeName)) ; } } } // namespace AzFramework #endif // ENABLE_CRY_PHYSICS