/* * 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" #include "CapsuleShapeComponent.h" #include #include #include #include #include #include namespace LmbrCentral { const AZ::u32 g_capsuleDebugShapeSides = 16; const AZ::u32 g_capsuleDebugShapeCapSegments = 8; void CapsuleShape::Reflect(AZ::ReflectContext* context) { CapsuleShapeConfig::Reflect(context); if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("Configuration", &CapsuleShape::m_capsuleShapeConfig) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class("Capsule Shape", "Capsule shape configuration parameters") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->DataElement(AZ::Edit::UIHandlers::Default, &CapsuleShape::m_capsuleShapeConfig, "Capsule Configuration", "Capsule shape configuration") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } } void CapsuleShape::Activate(AZ::EntityId entityId) { m_entityId = entityId; m_currentTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(m_currentTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM); m_intersectionDataCache.InvalidateCache(InvalidateShapeCacheReason::ShapeChange); AZ::TransformNotificationBus::Handler::BusConnect(m_entityId); ShapeComponentRequestsBus::Handler::BusConnect(m_entityId); CapsuleShapeComponentRequestsBus::Handler::BusConnect(m_entityId); } void CapsuleShape::Deactivate() { CapsuleShapeComponentRequestsBus::Handler::BusDisconnect(); ShapeComponentRequestsBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::Handler::BusDisconnect(); } void CapsuleShape::InvalidateCache(InvalidateShapeCacheReason reason) { m_intersectionDataCache.InvalidateCache(reason); } void CapsuleShape::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world) { m_currentTransform = world; m_intersectionDataCache.InvalidateCache(InvalidateShapeCacheReason::TransformChange); ShapeComponentNotificationsBus::Event( m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged, ShapeComponentNotifications::ShapeChangeReasons::TransformChanged); } void CapsuleShape::SetHeight(float height) { m_capsuleShapeConfig.m_height = height; m_intersectionDataCache.InvalidateCache(InvalidateShapeCacheReason::ShapeChange); ShapeComponentNotificationsBus::Event( m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged, ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged); } void CapsuleShape::SetRadius(float radius) { m_capsuleShapeConfig.m_radius = radius; m_intersectionDataCache.InvalidateCache(InvalidateShapeCacheReason::ShapeChange); ShapeComponentNotificationsBus::Event( m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged, ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged); } float CapsuleShape::GetHeight() { return m_capsuleShapeConfig.m_height; } float CapsuleShape::GetRadius() { return m_capsuleShapeConfig.m_radius; } CapsuleInternalEndPoints CapsuleShape::GetCapsulePoints() { m_intersectionDataCache.UpdateIntersectionParams(m_currentTransform, m_capsuleShapeConfig); return { m_intersectionDataCache.m_basePlaneCenterPoint, m_intersectionDataCache.m_topPlaneCenterPoint }; } AZ::Aabb CapsuleShape::GetEncompassingAabb() { m_intersectionDataCache.UpdateIntersectionParams(m_currentTransform, m_capsuleShapeConfig); const AZ::Aabb topAabb(AZ::Aabb::CreateCenterRadius( m_intersectionDataCache.m_topPlaneCenterPoint, m_intersectionDataCache.m_radius)); AZ::Aabb baseAabb(AZ::Aabb::CreateCenterRadius( m_intersectionDataCache.m_basePlaneCenterPoint, m_intersectionDataCache.m_radius)); baseAabb.AddAabb(topAabb); return baseAabb; } void CapsuleShape::GetTransformAndLocalBounds(AZ::Transform& transform, AZ::Aabb& bounds) { float halfHeight = AZ::GetMax(m_capsuleShapeConfig.m_height * 0.5f, m_capsuleShapeConfig.m_radius); const AZ::Vector3 extent(m_capsuleShapeConfig.m_radius, m_capsuleShapeConfig.m_radius, halfHeight); bounds = AZ::Aabb::CreateFromMinMax(-extent, extent); transform = m_currentTransform; } bool CapsuleShape::IsPointInside(const AZ::Vector3& point) { m_intersectionDataCache.UpdateIntersectionParams(m_currentTransform, m_capsuleShapeConfig); const float radiusSquared = powf(m_intersectionDataCache.m_radius, 2.0f); // Check Bottom sphere if (AZ::Intersect::PointSphere(m_intersectionDataCache.m_basePlaneCenterPoint, radiusSquared, point)) { return true; } // If the capsule is infact just a sphere then just stop (because the height of the cylinder <= 2 * radius of the cylinder) if (m_intersectionDataCache.m_isSphere) { return false; } // Check Top sphere if (AZ::Intersect::PointSphere(m_intersectionDataCache.m_topPlaneCenterPoint, radiusSquared, point)) { return true; } // If its not in either sphere check the cylinder return AZ::Intersect::PointCylinder( m_intersectionDataCache.m_basePlaneCenterPoint, m_intersectionDataCache.m_axisVector, powf(m_intersectionDataCache.m_internalHeight, 2.0f), radiusSquared, point); } float CapsuleShape::DistanceSquaredFromPoint(const AZ::Vector3& point) { m_intersectionDataCache.UpdateIntersectionParams(m_currentTransform, m_capsuleShapeConfig); const Lineseg lineSeg( AZVec3ToLYVec3(m_intersectionDataCache.m_basePlaneCenterPoint), AZVec3ToLYVec3(m_intersectionDataCache.m_topPlaneCenterPoint)); float t = 0.0f; float distance = Distance::Point_Lineseg(AZVec3ToLYVec3(point), lineSeg, t); distance -= m_intersectionDataCache.m_radius; return powf(AZStd::max(distance, 0.0f), 2.0f); } bool CapsuleShape::IntersectRay(const AZ::Vector3& src, const AZ::Vector3& dir, AZ::VectorFloat& distance) { m_intersectionDataCache.UpdateIntersectionParams(m_currentTransform, m_capsuleShapeConfig); if (m_intersectionDataCache.m_isSphere) { return AZ::Intersect::IntersectRaySphere( src, dir, m_intersectionDataCache.m_basePlaneCenterPoint, m_intersectionDataCache.m_radius, distance) > 0; } AZ::VectorFloat t; const AZ::VectorFloat rayLength = AZ::VectorFloat(1000.0f); const bool intersection = AZ::Intersect::IntersectSegmentCapsule( src, dir * rayLength, m_intersectionDataCache.m_basePlaneCenterPoint, m_intersectionDataCache.m_topPlaneCenterPoint, m_intersectionDataCache.m_radius, t) > 0; distance = rayLength * t; return intersection; } void CapsuleShape::CapsuleIntersectionDataCache::UpdateIntersectionParamsImpl( const AZ::Transform& currentTransform, const CapsuleShapeConfig& configuration) { const AZ::VectorFloat entityScale = currentTransform.RetrieveScale().GetMaxElement(); m_axisVector = currentTransform.GetBasisZ().GetNormalizedSafe() * entityScale; const float internalCylinderHeight = configuration.m_height - configuration.m_radius * 2.0f; if (internalCylinderHeight > std::numeric_limits::epsilon()) { const AZ::Vector3 currentPositionToPlanesVector = m_axisVector * (internalCylinderHeight * 0.5f); m_topPlaneCenterPoint = currentTransform.GetPosition() + currentPositionToPlanesVector; m_basePlaneCenterPoint = currentTransform.GetPosition() - currentPositionToPlanesVector; m_axisVector = m_axisVector * internalCylinderHeight; m_isSphere = false; } else { m_basePlaneCenterPoint = currentTransform.GetPosition(); m_topPlaneCenterPoint = currentTransform.GetPosition(); m_isSphere = true; } // scale intersection data cache radius by entity transform for internal calculations m_radius = configuration.m_radius * entityScale; m_internalHeight = internalCylinderHeight; } } // namespace LmbrCentral