/*
* 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 "PhysX_precompiled.h"
#include "ForceRegionForces.h"

namespace PhysX
{
    static const float s_forceRegionZeroValue = 0.0f;
    static const float s_forceRegionMaxDamping = 100.0f; // Large values create an oscillation that sends the body too far out. Legacy renderer's Octree may throw errors.
    static const float s_forceRegionMaxDensity = 400.0f; // Large values create an oscillation that sends the body too far out. Legacy renderer's Octree may throw errors. Maximum density is defined as a value capable of slowing down a radius 1 ball weighing 1 ton.
    static const float s_forceRegionMaxValue = 1000000.0f;
    static const float s_forceRegionMinValue = -s_forceRegionMaxValue;
    static const float s_forceRegionMaxDampingRatio = 1.5f;
    static const float s_forceRegionMinFrequency = 0.1f;
    static const float s_forceRegionMaxFrequency = 10.0f;


    ForceWorldSpace::ForceWorldSpace(const AZ::Vector3& direction, const float magnitude)
        : m_direction(direction)
        , m_magnitude(magnitude)
    {
    }

    void ForceWorldSpace::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForceWorldSpace, BaseForce>()
                ->Field("Direction", &ForceWorldSpace::m_direction)
                ->Field("Magnitude", &ForceWorldSpace::m_magnitude)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForceWorldSpace>(
                    "World Space Force", "Applies a force in world space")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Vector3, &ForceWorldSpace::m_direction, "Direction", "Direction of the force in world space")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceWorldSpace::m_magnitude, "Magnitude", "Magnitude of the force in world space")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForceWorldSpaceRequestBus>("ForceWorldSpaceRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetDirection", &ForceWorldSpaceRequestBus::Events::SetDirection)
                ->Event("GetDirection", &ForceWorldSpaceRequestBus::Events::GetDirection)
                ->Event("SetMagnitude", &ForceWorldSpaceRequestBus::Events::SetMagnitude)
                ->Event("GetMagnitude", &ForceWorldSpaceRequestBus::Events::GetMagnitude)
                ;
        }
    }

    AZ::Vector3 ForceWorldSpace::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        return m_direction.GetNormalized() * m_magnitude * entity.m_mass;
    }

    void ForceWorldSpace::SetDirection(const AZ::Vector3& direction)
    {
        m_direction = direction;
        NotifyChanged();
    }

    AZ::Vector3 ForceWorldSpace::GetDirection() const
    {
        return m_direction;
    }

    void ForceWorldSpace::SetMagnitude(float magnitude)
    {
        m_magnitude = magnitude;
        NotifyChanged();
    }

    float ForceWorldSpace::GetMagnitude() const
    {
        return m_magnitude;
    }

    ForceLocalSpace::ForceLocalSpace(const AZ::Vector3& direction, const float magnitude) :
        m_direction(direction)
        , m_magnitude(magnitude)
    {
    }

    void ForceLocalSpace::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForceLocalSpace, BaseForce>()
                ->Field("Direction", &ForceLocalSpace::m_direction)
                ->Field("Magnitude", &ForceLocalSpace::m_magnitude)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForceLocalSpace>(
                    "Local Space Force", "Applies a force in the volume's local space")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Vector3, &ForceLocalSpace::m_direction, "Direction", "Direction of the force in local space")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceLocalSpace::m_magnitude, "Magnitude", "Magnitude of the force in local space")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForceLocalSpaceRequestBus>("ForceLocalSpaceRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetDirection", &ForceLocalSpaceRequestBus::Events::SetDirection)
                ->Event("GetDirection", &ForceLocalSpaceRequestBus::Events::GetDirection)
                ->Event("SetMagnitude", &ForceLocalSpaceRequestBus::Events::SetMagnitude)
                ->Event("GetMagnitude", &ForceLocalSpaceRequestBus::Events::GetMagnitude)
                ;
        }
    }

    AZ::Vector3 ForceLocalSpace::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        return region.m_rotation * m_direction.GetNormalized() * m_magnitude * entity.m_mass;
    }

    void ForceLocalSpace::SetDirection(const AZ::Vector3& direction)
    {
        m_direction = direction;
        NotifyChanged();
    }

    AZ::Vector3 ForceLocalSpace::GetDirection() const
    {
        return m_direction;
    }

    void ForceLocalSpace::SetMagnitude(float magnitude)
    {
        m_magnitude = magnitude;
        NotifyChanged();
    }

    float ForceLocalSpace::GetMagnitude() const
    {
        return m_magnitude;
    }

    ForcePoint::ForcePoint(float magnitude) 
        : m_magnitude(magnitude)
    {
    }

    void ForcePoint::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForcePoint, BaseForce>()
                ->Field("Magnitude", &ForcePoint::m_magnitude)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForcePoint>(
                    "Point Force", "Applies a force relative to the center of the volume")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForcePoint::m_magnitude, "Magnitude", "Magnitude of the point force")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForcePointRequestBus>("ForcePointRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetMagnitude", &ForcePointRequestBus::Events::SetMagnitude)
                ->Event("GetMagnitude", &ForcePointRequestBus::Events::GetMagnitude)
                ;
        }
    }

    AZ::Vector3 ForcePoint::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        return (entity.m_position - region.m_aabb.GetCenter()).GetNormalizedSafe() * m_magnitude;
    }

    void ForcePoint::SetMagnitude(float magnitude)
    {
        m_magnitude = magnitude;
        NotifyChanged();
    }

    float ForcePoint::GetMagnitude() const
    {
        return m_magnitude;
    }

    ForceSplineFollow::ForceSplineFollow(float dampingRatio
        , float frequency
        , float targetSpeed
        , float lookAhead) :
        m_dampingRatio(dampingRatio)
        , m_frequency(frequency)
        , m_targetSpeed(targetSpeed)
        , m_lookAhead(lookAhead)
    {
    }

    void ForceSplineFollow::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForceSplineFollow, BaseForce>()
                ->Field("DampingRatio", &ForceSplineFollow::m_dampingRatio)
                ->Field("Frequency", &ForceSplineFollow::m_frequency)
                ->Field("TargetSpeed", &ForceSplineFollow::m_targetSpeed)
                ->Field("Lookahead", &ForceSplineFollow::m_lookAhead)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForceSplineFollow>(
                    "Spline Follow Force", "Applies a force to make objects follow a spline at a given speed")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceSplineFollow::m_dampingRatio, "Damping Ratio", "Amount of damping applied to an entity that is moving towards a spline")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionZeroValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxDampingRatio)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceSplineFollow::m_frequency, "Frequency", "Frequency at which an entity moves towards a spline")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinFrequency)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxFrequency)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceSplineFollow::m_targetSpeed, "Target Speed", "Speed at which entities in the force region move along a spline")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionMinValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceSplineFollow::m_lookAhead, "Lookahead", "Distance at which entities look ahead in their path to reach a point on a spline")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionZeroValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxValue)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForceSplineFollowRequestBus>("ForceSplineFollowRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetDampingRatio", &ForceSplineFollowRequestBus::Events::SetDampingRatio)
                ->Event("GetDampingRatio", &ForceSplineFollowRequestBus::Events::GetDampingRatio)
                ->Event("SetFrequency", &ForceSplineFollowRequestBus::Events::SetFrequency)
                ->Event("GetFrequency", &ForceSplineFollowRequestBus::Events::GetFrequency)
                ->Event("SetTargetSpeed", &ForceSplineFollowRequestBus::Events::SetTargetSpeed)
                ->Event("GetTargetSpeed", &ForceSplineFollowRequestBus::Events::GetTargetSpeed)
                ->Event("SetLookAhead", &ForceSplineFollowRequestBus::Events::SetLookAhead)
                ->Event("GetLookAhead", &ForceSplineFollowRequestBus::Events::GetLookAhead)
                ;
        }
    }

    AZ::Vector3 ForceSplineFollow::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        if (region.m_spline)
        {
            AZ::Quaternion rotateInverse = region.m_rotation;
            if (!rotateInverse.IsIdentity())
            {
                rotateInverse.InvertFull();
            }

            AZ::Vector3 scaleInverse = region.m_scale;
            scaleInverse = scaleInverse.GetReciprocal();

            AZ::Vector3 position = entity.m_position + entity.m_velocity * m_lookAhead;
            AZ::Vector3 localPos = position - region.m_position;
            localPos = rotateInverse * localPos;
            localPos = localPos * scaleInverse;

            AZ::SplineAddress address = region.m_spline->GetNearestAddressPosition(localPos).m_splineAddress;
            AZ::Vector3 splinePosition = region.m_spline->GetPosition(address);
            AZ::Vector3 splineTangent = region.m_spline->GetTangent(address);

            splinePosition = region.m_scale * splinePosition;
            splineTangent = region.m_scale * splineTangent;

            splinePosition = region.m_rotation * splinePosition;
            splineTangent = region.m_rotation * splineTangent;

            // http://www.matthewpeterkelly.com/tutorials/pdControl/index.html
            float kp = aznumeric_cast<float>(pow((2.0f * AZ::Constants::Pi * m_frequency), 2));
            float kd = 2.0f * m_dampingRatio * (2.0f * AZ::Constants::Pi * m_frequency);

            AZ::Vector3 targetVelocity = splineTangent * m_targetSpeed;
            AZ::Vector3 currentVelocity = entity.m_velocity;

            AZ::Vector3 targetPosition = splinePosition + region.m_position;
            AZ::Vector3 currentPosition = entity.m_position;

            return kp * (targetPosition - currentPosition) + kd * (targetVelocity - currentVelocity);
        }
        else
        {
            return AZ::Vector3::CreateZero();
        }
    }

    void ForceSplineFollow::Activate(AZ::EntityId entityId)
    {
        BaseForce::Activate(entityId);
        ForceSplineFollowRequestBus::Handler::BusConnect(entityId);
        m_loggedMissingSplineWarning = false;
    }

    void ForceSplineFollow::SetDampingRatio(float ratio)
    {
        m_dampingRatio = ratio;
        NotifyChanged();
    }

    float ForceSplineFollow::GetDampingRatio() const
    {
        return m_dampingRatio;
    }

    void ForceSplineFollow::SetFrequency(float frequency)
    {
        m_frequency = frequency;
        NotifyChanged();
    }

    float ForceSplineFollow::GetFrequency() const
    {
        return m_frequency;
    }

    void ForceSplineFollow::SetTargetSpeed(float targetSpeed)
    {
        m_targetSpeed = targetSpeed;
        NotifyChanged();
    }

    float ForceSplineFollow::GetTargetSpeed() const
    {
        return m_targetSpeed;
    }

    void ForceSplineFollow::SetLookAhead(float lookAhead)
    {
        m_lookAhead = lookAhead;
        NotifyChanged();
    }

    float ForceSplineFollow::GetLookAhead() const
    {
        return m_lookAhead;
    }

    ForceSimpleDrag::ForceSimpleDrag(float dragCoefficient, float volumeDensity) 
        : m_dragCoefficient(dragCoefficient)
        , m_volumeDensity(volumeDensity)
    {
    }

    void ForceSimpleDrag::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForceSimpleDrag, BaseForce>()
                ->Field("Drag Coefficient", &ForceSimpleDrag::m_dragCoefficient)
                ->Field("Volume Density", &ForceSimpleDrag::m_volumeDensity)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForceSimpleDrag>(
                    "Simple Drag Force", "Simulates a drag force on entities")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceSimpleDrag::m_volumeDensity, "Region Density", "Density of the region")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionZeroValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxDensity)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForceSimpleDragRequestBus>("ForceSimpleDragRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetDensity", &ForceSimpleDragRequestBus::Events::SetDensity)
                ->Event("GetDensity", &ForceSimpleDragRequestBus::Events::GetDensity)
                ;
        }
    }

    AZ::Vector3 ForceSimpleDrag::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        // Aproximate shape as a sphere
        AZ::Vector3 center;
        AZ::VectorFloat radius;
        entity.m_aabb.GetAsSphere(center, radius);

        const AZ::VectorFloat crossSectionalArea = AZ::Constants::Pi * radius * radius;
        const AZ::Vector3 velocitySquared = entity.m_velocity * entity.m_velocity;

        // Wikipedia: https://en.wikipedia.org/wiki/Drag_coefficient
        // Fd = 1/2 * p * u^2 * cd * A
        const AZ::Vector3 dragForce = 0.5f * m_volumeDensity * velocitySquared * m_dragCoefficient * crossSectionalArea;

        // The drag force is defined as being in the same direction as the flow velocity. Since the entity is moving
        // and the volume flow is stationary, this just becomes opposite to the entity's velocity. Causing the object to slow
        // down.
        const AZ::Vector3 direction(-entity.m_velocity.GetX().GetSign(), -entity.m_velocity.GetY().GetSign(), -entity.m_velocity.GetZ().GetSign());

        return dragForce * direction.GetNormalized();
    }

    void ForceSimpleDrag::SetDensity(float density)
    {
        m_volumeDensity = density;
        NotifyChanged();
    }

    float ForceSimpleDrag::GetDensity() const
    {
        return m_volumeDensity;
    }

    ForceLinearDamping::ForceLinearDamping(float damping) 
        : m_damping(damping)
    {
    }

    void ForceLinearDamping::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<ForceLinearDamping, BaseForce>()
                ->Field("Damping", &ForceLinearDamping::m_damping)
                ;

            if (auto editContext = serializeContext->GetEditContext())
            {
                editContext->Class<ForceLinearDamping>(
                    "Linear Damping Force", "Applies an opposite force to the entity's velocity")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &ForceLinearDamping::m_damping, "Damping", "Amount of damping applied to an opposite force")
                      ->Attribute(AZ::Edit::Attributes::Min, s_forceRegionZeroValue)
                      ->Attribute(AZ::Edit::Attributes::Max, s_forceRegionMaxDamping)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->EBus<ForceLinearDampingRequestBus>("ForceLinearDampingRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
                ->Attribute(AZ::Script::Attributes::Module, "physics")
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
                ->Attribute(AZ::Script::Attributes::Category, "PhysX")
                ->Event("SetDamping", &ForceLinearDampingRequestBus::Events::SetDamping)
                ->Event("GetDamping", &ForceLinearDampingRequestBus::Events::GetDamping)
                ;
        }
    }

    AZ::Vector3 ForceLinearDamping::CalculateForce(const EntityParams& entity, const RegionParams& region) const
    {
        return entity.m_velocity * -m_damping * entity.m_mass;
    }

    void ForceLinearDamping::SetDamping(float damping)
    {
        m_damping = damping;
        NotifyChanged();
    }

    float ForceLinearDamping::GetDamping() const
    {
        return m_damping;
    }

    void BaseForce::Reflect(AZ::SerializeContext& context)
    {
        context.Class<BaseForce>();
    }

    AZ::Vector3 BaseForce::CalculateForce(const EntityParams& /*entityParams*/, const RegionParams& /*volumeParams*/) const
    {
        return AZ::Vector3::CreateZero();
    }

    void BaseForce::NotifyChanged()
    {
        ForceRegionNotificationBus::Broadcast(
            &ForceRegionNotificationBus::Events::OnForceRegionForceChanged, m_entityId);
    }
}