/*
* 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 "Water_precompiled.h"

#include <Water/WaterOceanComponentData.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>

#include <Cry3DEngine/Environment/OceanEnvironmentBus.h>

#if WATER_GEM_EDITOR
#include "EditorDefs.h"
#include <Material/MaterialManager.h>
#endif

namespace Water
{
    static int s_numberOfOceanComponentsActivated = 0;

    void WaterOceanComponentData::Reflect(AZ::ReflectContext* context)
    {
        General::Reflect(context);
        Caustics::Reflect(context);
        Animation::Reflect(context);
        Reflection::Reflect(context);
        Fog::Reflect(context);
        Advanced::Reflect(context);

        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<WaterOceanComponentData>()
                ->Version(1)
                ->Field("general", &WaterOceanComponentData::m_general)
                ->Field("animation", &WaterOceanComponentData::m_animation)
                ->Field("fog", &WaterOceanComponentData::m_fog)
                ->Field("caustics", &WaterOceanComponentData::m_caustics)
                ->Field("reflection", &WaterOceanComponentData::m_reflection)
                ->Field("advanced", &WaterOceanComponentData::m_advanced)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<WaterOceanComponentData>("Infinite Ocean", "Provides an infinite ocean. (Preview)")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_general, "General", "General ocean settings")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_animation, "Animation", "Ocean animations for the waves and texture scrolling")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_fog, "Fog", "The parameters of the ocean fog")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_caustics, "Caustics", "Ocean caustics effects on surfaces below the water")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_reflection, "Reflection", "Reflection flags and parameters for the ocean")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &WaterOceanComponentData::m_advanced, "Advanced", "Advanced settings")
                    ;
            }
        }

        AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
        if (behaviorContext)
        {
            behaviorContext->Class<WaterOceanComponentData>()
                ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
                ->RequestBus("OceanEnvironmentRequestBus");
        }
    }

    WaterOceanComponentData::WaterOceanComponentData()
    {
        m_enabled = false;
    }

    WaterOceanComponentData::~WaterOceanComponentData()
    {
        // Ensure that we're disconnected from the bus on destruction.  We can end up still connected here
        // if a WaterOceanComponent has been created with a copy of connected WaterOceanComponentData.
        // The new copy doesn't get an Activate or a Deactivate call, but will be connected.
        AZ::OceanEnvironmentBus::Handler::BusDisconnect();

        m_enabled = false;
    }

    _smart_ptr<IMaterial> WaterOceanComponentData_LoadMaterial(const AZStd::string& materialName)
    {
        _smart_ptr<IMaterial> pMaterial;

#if WATER_GEM_EDITOR
        CMaterial* cMaterial = GetIEditor()->GetMaterialManager()->LoadMaterial(materialName.c_str(), false);
        if (cMaterial)
        {
            pMaterial = cMaterial->GetMatInfo();
        }
#else
        ISystem* system = GetISystem();
        if (system && system->GetI3DEngine() && system->GetI3DEngine()->GetMaterialManager())
        {
            pMaterial = system->GetI3DEngine()->GetMaterialManager()->LoadMaterial(materialName.c_str(), false);
        }
#endif

        return pMaterial;
    }

    void WaterOceanComponentData_UpdateOceanMaterial(const AZStd::string& materialName)
    {
        ISystem* system = GetISystem();
        if (system && system->GetI3DEngine())
        {
            auto pMaterial = WaterOceanComponentData_LoadMaterial(materialName);
            if (pMaterial)
            {
                system->GetI3DEngine()->ChangeOceanMaterial(pMaterial);
            }
        }
    }

    void WaterOceanComponentData_UpdateOceanBuoyancy(float height)
    {
        ISystem* system = GetISystem();
        if (system && system->GetI3DEngine())
        {
            // updates the buoyancy area associated with the ocean.
            system->GetI3DEngine()->ChangeOceanWaterLevel(height);
        }
    }

    void WaterOceanComponentData::Activate()
    {
        ++s_numberOfOceanComponentsActivated;
        AZ_Error("Error", s_numberOfOceanComponentsActivated == 1, "Only one ocean component should be active at a time.");

        m_enabled = true;
        AZ::OceanEnvironmentBus::Handler::BusConnect();
        WaterOceanComponentData_UpdateOceanBuoyancy(m_general.m_height);
        WaterOceanComponentData_UpdateOceanMaterial(m_general.m_oceanMaterialAsset.GetAssetPath());

        I3DEngine* engine = GetISystem() ? GetISystem()->GetI3DEngine() : nullptr;
        if (engine)
        {
            engine->EnableOceanRendering(true); // Sets a bool that allows the ocean to render
            auto pMaterial = WaterOceanComponentData_LoadMaterial(m_general.m_oceanMaterialAsset.GetAssetPath());
            engine->CreateOcean(pMaterial, m_general.m_height);
        }
    }

    void WaterOceanComponentData::Deactivate()
    {
        --s_numberOfOceanComponentsActivated;

        m_enabled = false;
        AZ::OceanEnvironmentBus::Handler::BusDisconnect();
        WaterOceanComponentData_UpdateOceanBuoyancy(AZ::OceanConstants::s_HeightMin);

        // turn off ocean and delete it
        I3DEngine* engine = GetISystem() ? GetISystem()->GetI3DEngine() : nullptr;
        if (engine)
        {
            engine->DeleteOcean();
            engine->EnableOceanRendering(false); // turns off ocean rendering
        }
    }

    bool WaterOceanComponentData::OceanIsEnabled() const
    {
        return m_enabled;
    }

    float WaterOceanComponentData::GetOceanLevel() const
    {
        return m_general.m_height;
    }

    float WaterOceanComponentData::GetOceanLevelOrDefault(const float defaultValue) const
    {
        return OceanIsEnabled() ? m_general.m_height : defaultValue;
    }

    float WaterOceanComponentData::GetWaterLevel(const Vec3& pvPos) const
    {
        ISystem* system = GetISystem();
        if (system && system->GetI3DEngine())
        {
            return system->GetI3DEngine()->GetWaterLevel(&pvPos);
        }
        return 0.0f;
    }

    float WaterOceanComponentData::GetAccurateOceanHeight(const Vec3& pCurrPos) const
    {
        ISystem* system = GetISystem();
        if (system && system->GetI3DEngine())
        {
            return system->GetI3DEngine()->GetAccurateOceanHeight(pCurrPos);
        }
        return 0.0f;
    }

    int WaterOceanComponentData::GetWaterTessellationAmount() const
    {
        return m_advanced.m_waterTessellationAmount;
    }

    const AZStd::string& WaterOceanComponentData::GetOceanMaterialName() const
    {
        return m_general.m_oceanMaterialAsset.GetAssetPath();
    }

    void WaterOceanComponentData::SetOceanMaterialName(const AZStd::string& matName)
    {
        m_general.m_oceanMaterialAsset.SetAssetPath(matName.c_str());
        WaterOceanComponentData_UpdateOceanMaterial(matName);
    }

    float WaterOceanComponentData::GetAnimationWindDirection() const
    {
        return m_animation.m_windDirection;
    }

    float WaterOceanComponentData::GetAnimationWindSpeed() const
    {
        return m_animation.m_windSpeed;
    }

    float WaterOceanComponentData::GetAnimationWavesSpeed() const
    {
        return m_animation.m_wavesSpeed;
    }

    float WaterOceanComponentData::GetAnimationWavesSize() const
    {
        return m_animation.m_wavesSize;
    }

    float WaterOceanComponentData::GetAnimationWavesAmount() const
    {
        return m_animation.m_wavesAmount;
    }

    void WaterOceanComponentData::ApplyReflectRenderFlags(int& flags) const
    {
        if (m_reflection.m_entities)
        {
            flags |= SRenderingPassInfo::ENTITIES;
        }
        if (m_reflection.m_particles)
        {
            flags |= SRenderingPassInfo::PARTICLES;
        }
        if (m_reflection.m_terrainDetailMaterials)
        {
            flags |= SRenderingPassInfo::TERRAIN_DETAIL_MATERIALS;
        }
        if (m_reflection.m_staticObjects)
        {
            flags |= SRenderingPassInfo::STATIC_OBJECTS;
        }
    }

    bool WaterOceanComponentData::GetReflectionAnisotropic() const
    {
        return m_reflection.m_anisotropic;
    }

    float WaterOceanComponentData::GetReflectResolutionScale() const
    {
        return m_reflection.m_resolutionScale;
    }

    bool WaterOceanComponentData::GetReflectRenderFlag(ReflectionFlags flag) const
    {
        switch (flag)
        {
        case OceanEnvironmentRequests::ReflectionFlags::Entities:
            return m_reflection.m_entities;
        case OceanEnvironmentRequests::ReflectionFlags::Particles:
            return m_reflection.m_particles;
        case OceanEnvironmentRequests::ReflectionFlags::TerrainDetailMaterials:
            return m_reflection.m_terrainDetailMaterials;
        case OceanEnvironmentRequests::ReflectionFlags::StaticObjects:
            return m_reflection.m_staticObjects;
        default:
            return false;
        }
    }

    bool WaterOceanComponentData::GetUseOceanBottom() const
    {
        return m_general.m_useOceanBottom;
    }

    bool WaterOceanComponentData::GetGodRaysEnabled() const
    {
        return m_advanced.m_godraysEnabled;
    }

    float WaterOceanComponentData::GetUnderwaterDistortion() const
    {
        return m_advanced.m_underwaterDistortion;
    }

    bool  WaterOceanComponentData::GetCausticsEnabled() const
    {
        return m_caustics.m_enabled;
    }

    float WaterOceanComponentData::GetCausticsDepth() const
    {
        return m_caustics.m_depth;
    }

    float WaterOceanComponentData::GetCausticsIntensity() const
    {
        return m_caustics.m_intensity;
    }

    float WaterOceanComponentData::GetCausticsTiling() const
    {
        return m_caustics.m_tiling;
    }

    float WaterOceanComponentData::GetCausticsDistanceAttenuation() const
    {
        return m_caustics.m_distanceAttenuation;
    }

    AZ::Color WaterOceanComponentData::GetFogColor() const
    {
        return m_fog.m_color;
    }

    float WaterOceanComponentData::GetFogColorMultiplier() const
    {
        return m_fog.m_colorMultiplier;
    }

    AZ::Color WaterOceanComponentData::GetNearFogColor() const
    {
        return m_fog.m_nearFogColor;
    }

    AZ::Color WaterOceanComponentData::GetFogColorPremultiplied() const
    {
        return m_fog.m_color * m_fog.m_colorMultiplier;
    }

    float WaterOceanComponentData::GetFogDensity() const
    {
        // A fog density of 0.0f causes shader problems, so return a value slightly above zero if that's the case.
        return AZ::GetMax(m_fog.m_density, 0.0001f);
    }

    void WaterOceanComponentData::SetOceanLevel(float oceanLevel)
    {
        m_general.m_height = oceanLevel;
        WaterOceanComponentData_UpdateOceanBuoyancy(m_general.m_height);
    }

    void WaterOceanComponentData::SetFogColor(const AZ::Color& fogColor)
    {
        m_fog.m_color = fogColor;
    }

    void WaterOceanComponentData::SetFogColorMultiplier(float fogMultiplier)
    {
        m_fog.m_colorMultiplier = fogMultiplier;
    }

    void WaterOceanComponentData::SetNearFogColor(const AZ::Color& nearColor)
    {
        m_fog.m_nearFogColor = nearColor;
    }

    void WaterOceanComponentData::SetFogDensity(float density)
    {
        m_fog.m_density = density;
    }

    void WaterOceanComponentData::SetWaterTessellationAmount(int amount)
    {
        m_advanced.m_waterTessellationAmount = amount;
    }

    void WaterOceanComponentData::SetAnimationWindDirection(float dir)
    {
        m_animation.m_windDirection = dir;
    }

    void WaterOceanComponentData::SetAnimationWindSpeed(float speed)
    {
        m_animation.m_windSpeed = speed;
    }

    void WaterOceanComponentData::SetAnimationWavesSpeed(float speed)
    {
        m_animation.m_wavesSpeed = speed;
    }

    void WaterOceanComponentData::SetAnimationWavesSize(float size)
    {
        m_animation.m_wavesSize = size;
    }

    void WaterOceanComponentData::SetAnimationWavesAmount(float amount)
    {
        m_animation.m_wavesAmount = amount;
    }

    void WaterOceanComponentData::SetReflectRenderFlag(ReflectionFlags flag, bool value)
    {
        switch (flag)
        {
        case OceanEnvironmentRequests::ReflectionFlags::Entities:
            m_reflection.m_entities = value;
            break;
        case OceanEnvironmentRequests::ReflectionFlags::Particles:
            m_reflection.m_particles = value;
            break;
        case OceanEnvironmentRequests::ReflectionFlags::TerrainDetailMaterials:
            m_reflection.m_terrainDetailMaterials = value;
            break;
        case OceanEnvironmentRequests::ReflectionFlags::StaticObjects:
            m_reflection.m_staticObjects = value;
            break;
        default:
            return;
        }
    }

    void WaterOceanComponentData::SetReflectResolutionScale(float scale)
    {
        m_reflection.m_resolutionScale = scale;
    }

    void WaterOceanComponentData::SetReflectionAnisotropic(bool enabled)
    {
        m_reflection.m_anisotropic = enabled;
    }

    void WaterOceanComponentData::SetUseOceanBottom(bool use)
    {
        m_general.m_useOceanBottom = use;
    }

    void WaterOceanComponentData::SetGodRaysEnabled(bool enabled)
    {
        m_advanced.m_godraysEnabled = enabled;
    }

    void WaterOceanComponentData::SetUnderwaterDistortion(float distortion)
    {
        m_advanced.m_underwaterDistortion = distortion;
    }

    void WaterOceanComponentData::SetCausticsEnabled(bool enable)
    {
        m_caustics.m_enabled = enable;
    }

    void WaterOceanComponentData::SetCausticsDepth(float depth)
    {
        m_caustics.m_depth = depth;
    }

    void WaterOceanComponentData::SetCausticsIntensity(float intensity)
    {
        m_caustics.m_intensity = intensity;
    }

    void WaterOceanComponentData::SetCausticsTiling(float tiling)
    {
        m_caustics.m_tiling = tiling;
    }

    void WaterOceanComponentData::SetCausticsDistanceAttenuation(float dist)
    {
        m_caustics.m_distanceAttenuation = dist;
    }

    // General
    //
    void WaterOceanComponentData::General::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<General>()
                ->Version(1, &VersionConverter)
                ->Field("useOceanBottom", &General::m_useOceanBottom)
                ->Field("oceanMaterial", &General::m_oceanMaterialAsset)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<General>("General", "General")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "General")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &General::m_oceanMaterialAsset, "Water Material", "The material assigned for rendering the ocean (note: must be a water shader, like default_ocean.mtl)")
                        ->Attribute(AZ::Edit::Attributes::ChangeNotify, &General::OnOceanMaterialChanged)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &General::m_useOceanBottom, "Enable Ocean Bottom", "Toggles the infinite plane beneath the ocean")
                    ;
            }
        }
    }

    WaterOceanComponentData::General::General()
    {
        AZStd::string currentOceanMaterialName = m_oceanMaterialAsset.GetAssetPath();
        if (currentOceanMaterialName.empty())
        {
            const AZStd::string defaultOceanMaterial = "EngineAssets/Materials/Water/Ocean_default.mtl";
            m_oceanMaterialAsset.SetAssetPath(defaultOceanMaterial.c_str());
        }
        OnOceanMaterialChanged();
    }

    bool WaterOceanComponentData::General::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
    {
        if (classElement.GetVersion() == 0)
        {
            // height was removed in version 1 to be driven instead by the transform component, so just remove it.
            int heightIndex = classElement.FindElement(AZ_CRC("height", 0xf54de50f));
            classElement.RemoveElement(heightIndex);
        }
        return true;
    }

    void WaterOceanComponentData::General::OnOceanMaterialChanged()
    {
        WaterOceanComponentData_UpdateOceanMaterial(m_oceanMaterialAsset.GetAssetPath());
    }
      
    // Animation
    //
    void WaterOceanComponentData::Animation::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<Animation>()
                ->Version(0)
                ->Field("animationWavesAmount", &Animation::m_wavesAmount)
                ->Field("animationWavesSize", &Animation::m_wavesSize)
                ->Field("animationWavesSpeed", &Animation::m_wavesSpeed)
                ->Field("animationWindDirection", &Animation::m_windDirection)
                ->Field("animationWindSpeed", &Animation::m_windSpeed)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<Animation>("Animation", "Animation")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "Animation")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Animation::m_wavesAmount, "Waves Amount", "Controls the frequency of ocean waves (higher values, the waves are smaller and closer together)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_animationWavesAmountMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_animationWavesAmountMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Animation::m_wavesSize, "Waves Size", "Controls the height of ocean waves (higher values, work better with lower Waves Amount)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_animationWavesSizeMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_animationWavesSizeMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Animation::m_wavesSpeed, "Waves Speed", "Controls the speed of ocean wave movement (elasticity of surface)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_animationWavesSpeedMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_animationWavesSpeedMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Animation::m_windDirection, "Wind Direction", "Controls the direction of ocean wind, set in radians (micro detail direction)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_animationWindDirectionMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_animationWindDirectionMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Animation::m_windSpeed, "Wind Speed", "Controls the speed of ocean wind (how fast micro detail scrolls)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_animationWindSpeedMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_animationWindSpeedMax)
                ;
            }
        }
    }

    // Reflection
    //
    void WaterOceanComponentData::Reflection::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<Reflection>()
                ->Version(0)
                ->Field("reflectionResolutionScale", &Reflection::m_resolutionScale)
                ->Field("reflectEntities", &Reflection::m_entities)
                ->Field("reflectStaticObjects", &Reflection::m_staticObjects)
                ->Field("reflectTerrainDetailMaterials", &Reflection::m_terrainDetailMaterials)
                ->Field("reflectParticles", &Reflection::m_particles)
                ->Field("reflectionAnisotropic", &Reflection::m_anisotropic)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<Reflection>("Reflection", "Reflection")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "Reflection")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Reflection::m_resolutionScale, "Resolution Scale", "The scale of the screen resolution to use for rendering ocean reflections")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
                        ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.05f)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Reflection::m_entities, "Reflect Entities", "Controls whether the ocean will render entities in reflection pass")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Reflection::m_staticObjects, "Reflect Objects", "Controls whether the ocean will render static mesh objects in reflection pass")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Reflection::m_terrainDetailMaterials, "Reflect Terrain", "Controls whether the ocean will use the terrain detail materials, when terrain is rendered in reflection pass")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Reflection::m_particles, "Reflect Particles", "Controls whether the ocean will render particles in reflection pass")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Reflection::m_anisotropic, "Anisotropic", "Turns on anisotropic filter for rendered ocean reflections")
                    ;
            }
        }
    }

    // Fog
    //
    void WaterOceanComponentData::Fog::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<Fog>()
                ->Version(0)
                ->Field("fogColor", &Fog::m_color)
                ->Field("fogColorMultiplier", &Fog::m_colorMultiplier)
                ->Field("fogDensity", &Fog::m_density)
                ->Field("nearFogColor", &Fog::m_nearFogColor)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<Fog>("Fog", "Fog")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "Fog")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Fog::m_color, "Color", "The color used to render the ocean's fog (below water)")
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Fog::m_colorMultiplier, "Color Multiplier", "A multiplier that influences the intensity of the ocean fog color")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_OceanFogColorMultiplierMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_OceanFogColorMultiplierMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Fog::m_density, "Density", "Controls density of the ocean fog (with higher values, the transperancy falls off more quickly)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_OceanFogDensityMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_OceanFogDensityMax)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Fog::m_nearFogColor, "Near Fog Color", "The ocean fog color attenuates to this color near shallow depths (it is recommended to be left at the default color, or near black.)")
                    ;
            }
        }
    }

    // Advanced
    //
    void WaterOceanComponentData::Advanced::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<Advanced>()
                ->Version(0)
                ->Field("waterTessellationAmount", &Advanced::m_waterTessellationAmount)
                ->Field("godraysEnabled", &Advanced::m_godraysEnabled)
                ->Field("underwaterDistortion", &Advanced::m_underwaterDistortion)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<Advanced>("Advanced", "Advanced")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "Advanced")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, false)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Advanced::m_waterTessellationAmount, "Tessellation", "Sets the amount of ocean water surface geometry tessellation.")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_waterTessellationAmountMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_waterTessellationAmountMax)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Advanced::m_godraysEnabled, "Godrays Enabled", "Enables under ocean god rays")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Advanced::m_underwaterDistortion, "Underwater Distortion", "Controls the amount the rendering of the scene is distortied, when the camera is underwater")
                    ;
            }
        }
    }

    // Caustics
    //
    void WaterOceanComponentData::Caustics::Reflect(AZ::ReflectContext* context)
    {
        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serialize->Class<Caustics>()
                ->Version(0)
                ->Field("causticsEnabled", &Caustics::m_enabled)
                ->Field("causticDepth", &Caustics::m_depth)
                ->Field("causticIntensity", &Caustics::m_intensity)
                ->Field("causticTiling", &Caustics::m_tiling)
                ->Field("causticDistanceAttenuation", &Caustics::m_distanceAttenuation)
                ;

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<Caustics>("Caustics", "Caustics")
                    ->ClassElement(AZ::Edit::ClassElements::Group, "Caustic")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &Caustics::m_enabled, "Enables Caustics", "Applies the caustics effect of the ocean on geometry below the water surface")
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Caustics::m_distanceAttenuation, "Distance Attenuation", "Controls the attenuation distance (from camera) the caustic effects of the ocean is applied")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_CausticsDistanceAttenMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_CausticsDistanceAttenMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Caustics::m_depth, "Depth", "The depth below the ocean (calculated in meters) that influences when the caustic effect of the ocean is applied")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_CausticsDepthMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_CausticsDepthMax)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Caustics::m_intensity, "Intensity", "Controls the intensity of the light in the ocean caustics effect. 0 will turn caustics off completely. Near-zero values still show caustics rather strongly (going from 0.00 to 0.01 is a sudden change)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_CausticsIntensityMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_CausticsIntensityMax)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &Caustics::m_tiling, "Tiling", "Controls the amount the ocean caustic effect is tiled (lower valules spread it out, higher values tighten the detail)")
                        ->Attribute(AZ::Edit::Attributes::Min, AZ::OceanConstants::s_CausticsTilingMin)
                        ->Attribute(AZ::Edit::Attributes::Max, AZ::OceanConstants::s_CausticsTilingMax)
                    ;

            }
        }
    }

}