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

#include "ParticleContainerGPU.h"

#include "../CryCommon/IGPUParticleEngine.h"
#include "../CryCommon/IRenderer.h"
#include "../CryCommon/CREParticleGPU.h"
#include "ParticleSubEmitter.h"
#include "ParticleContainer.h"
#include "ParticleEmitter.h"

#include <AzCore/Math/MathUtils.h>

#include <new>
#include <math.h>

#include "../RenderDll/Common/Defs.h"

#define SHADOW_INSTRUCTION_COUNT 32

struct SImpl_GPUParticleContainer
{
    SImpl_GPUParticleContainer()
        : particleContainer(nullptr)
        , renderer(nullptr)
        , reParticleGPU(nullptr)
        , reReflectionPass(nullptr)
        , instance(nullptr)
        , lodBlendAlphaUpdateTime(0)
    {}

    ~SImpl_GPUParticleContainer()
    {
        ReleaseRenderElements();

        DestroyGPUEmitter();
    }

    void CreateGPUEmitter(IGPUParticleEngine::EmitterTypePtr parentGPUEmitter)
    {
        if (!particleContainer || !renderer || !renderer->GetGPUParticleEngine())
        {
            return;
        }

        if (instance)
        {
            DestroyGPUEmitter();
        }

        const SpawnParams& spawnParams = particleContainer->GetMain().GetSpawnParams();

        instance = renderer->GetGPUParticleEngine()->AddEmitter(&particleContainer->GetParams(), spawnParams, &(particleContainer->GetMain().GetEmitterFlags()), parentGPUEmitter, &target, &(particleContainer->GetMain().GetPhysEnviron()));
        AZ_Assert(instance, "GPU emitter was not created!");
    }

    void DestroyGPUEmitter()
    {
        if (!renderer || !renderer->GetGPUParticleEngine())
        {
            return;
        }

        if (instance)
        {
            renderer->GetGPUParticleEngine()->RemoveEmitter(instance);
            instance = nullptr;
        }
    }

    void ReleaseRenderElements()
    {
        if (!renderer || !renderer->GetGPUParticleEngine())
        {
            return;
        }

        IGPUParticleEngine* particleEngine = renderer->GetGPUParticleEngine();

        if (reParticleGPU)
        {
            particleEngine->QueueRenderElementToReleaseNextFrame(reParticleGPU);
            reParticleGPU = nullptr;
        }

        if (reReflectionPass)
        {
            particleEngine->QueueRenderElementToReleaseNextFrame(reReflectionPass);
            reReflectionPass = nullptr;
        }

        for (int i = 0; i < reShadow.size(); i++)
        {
            particleEngine->QueueRenderElementToReleaseNextFrame(reShadow[i]);
        }
        reShadow.clear();
    }

    CParticleContainer*                 particleContainer;
    IRenderer*                          renderer;
    CREParticleGPU*                     reParticleGPU;
    CREParticleGPU*                     reReflectionPass;
    std::vector<CREParticleGPU*>        reShadow;
    IGPUParticleEngine::EmitterTypePtr  instance;
    float                               lodBlendAlphaUpdateTime;
    ParticleTarget                      target;
};

CParticleContainerGPU::CParticleContainerGPU()
{
    CreateImplementation();
}

CParticleContainerGPU::~CParticleContainerGPU()
{
    DestroyImplementation();
}

void CParticleContainerGPU::CreateImplementation()
{
    CRY_ASSERT(sizeof(m_implBuffer) >= sizeof(SImpl_GPUParticleContainer));
    m_impl = new (m_implBuffer) SImpl_GPUParticleContainer();
}

void CParticleContainerGPU::DestroyImplementation()
{
    if (m_impl)
    {
        m_impl->~SImpl_GPUParticleContainer();
        m_impl = nullptr;
    }
}

void CParticleContainerGPU::Initialize(CParticleContainer* particleContainer)
{
    if (!particleContainer)
    {
        return;
    }

    // Recreate implementation if we are initializing again to relese memory correctly
    if (m_impl->particleContainer)
    {
        DestroyImplementation();
        CreateImplementation();
    }

    m_impl->particleContainer = particleContainer;
    m_impl->renderer = particleContainer->GetRenderer();

    if (!m_impl->renderer)
    {
        m_impl->particleContainer = nullptr;
        return;
    }

    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();

    if (!particleEngine)
    {
        m_impl->particleContainer = nullptr;
        m_impl->renderer = nullptr;
        return;
    }

    IGPUParticleEngine::EmitterTypePtr parentGPUEmitter = nullptr;
    if (m_impl->particleContainer->GetParent() && m_impl->particleContainer->GetParent()->GetGPUData())
    {
        parentGPUEmitter = m_impl->particleContainer->GetParent()->GetGPUData()->m_impl->instance;
    }

    m_impl->CreateGPUEmitter(parentGPUEmitter);

    m_impl->reParticleGPU = static_cast<CREParticleGPU*>(m_impl->renderer->EF_CreateRE(eDATA_GPUParticle));
    m_impl->reParticleGPU->SetInstance(m_impl->instance);

    // create render instruction containers for recursive passes (water reflections)
    m_impl->reReflectionPass = static_cast<CREParticleGPU*>(m_impl->renderer->EF_CreateRE(eDATA_GPUParticle));
    m_impl->reReflectionPass->SetInstance(m_impl->instance);
    m_impl->reReflectionPass->SetPass(EGPUParticlePass::Reflection);

    // create shadow render instructions
    for (int i = 0; i < SHADOW_INSTRUCTION_COUNT; i++)
    {
        m_impl->reShadow.push_back(nullptr);
        m_impl->reShadow[i] = static_cast<CREParticleGPU*>(m_impl->renderer->EF_CreateRE(eDATA_GPUParticle));
        m_impl->reShadow[i]->SetInstance(m_impl->instance);
        m_impl->reShadow[i]->SetPass(EGPUParticlePass::Shadow);
        m_impl->reShadow[i]->SetShadowMode(i);
    }
}

void CParticleContainerGPU::Render(SRendParams const& rParam, SPartRenderParams const& PRParams, const SRenderingPassInfo& passInfo)
{
    CRenderObject* pObj = m_impl->renderer->EF_GetObject_Temp(passInfo.ThreadID());
    if (!pObj)
    {
        // could not obtain temporary render object.
        return;
    }

    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();

    if (!particleEngine)
    {
        CRY_ASSERT(particleEngine);
        return;
    }

    SShaderItem* shaderItem = particleEngine->GetRenderShader();

    if (!shaderItem)
    {
        CRY_ASSERT(shaderItem);
        return;
    }

    //Set the lod blend value for gpu.
    SetLodBlendAlpha(m_impl->particleContainer->ComputeLodBlend(PRParams.m_fCamDistance, m_impl->particleContainer->GetAge() - m_impl->lodBlendAlphaUpdateTime));
    m_impl->lodBlendAlphaUpdateTime = m_impl->particleContainer->GetAge();

    const Matrix34 translation(m_impl->particleContainer->GetMain().GetLocation());
    const Matrix34 scale = Matrix34::CreateScale(Vec3(m_impl->particleContainer->GetMain().GetSpawnParams().fSizeScale));
    pObj->m_II.m_Matrix = translation * scale;

    pObj->m_fSort = 0;  //set to 0 to remove bias in sorting

    // aux windows (particle preview window) has no lighting
    if (!passInfo.IsAuxWindow())
    {
        pObj->m_ObjFlags |= FOB_LIGHTVOLUME;
    }
    else
    {
        // remove flags that are not allowed
        pObj->m_ObjFlags &= (~FOB_SOFT_PARTICLE);
        pObj->m_ObjFlags &= (~FOB_PARTICLE_SHADOWS);
    }

    // create lighting information
    auto* pParams = &m_impl->particleContainer->GetParams();

    CRenderObject* pRenderObject = pObj;
    SRenderObjData* pOD = gEnv->pRenderer->EF_GetObjData(pRenderObject, true, passInfo.ThreadID());
    float fEmissive = pParams->fEmissiveLighting;

    // Ambient color for shader incorporates actual ambient lighting, as well as the constant emissive value.
    pRenderObject->m_II.m_AmbColor = ColorF(fEmissive) + rParam.AmbientColor * pParams->fDiffuseLighting * Cry3DEngineBase::Get3DEngine()->m_fParticlesAmbientMultiplier;
    pRenderObject->m_II.m_AmbColor.a = pParams->fDiffuseLighting * Cry3DEngineBase::Get3DEngine()->m_fParticlesLightMultiplier;

    pRenderObject->m_nTextureID = pParams->nTexId;

    pOD->m_FogVolumeContribIdx[0] = pOD->m_FogVolumeContribIdx[1] = PRParams.m_nFogVolumeContribIdx;

    pOD->m_LightVolumeId = PRParams.m_nDeferredLightVolumeId;


    IF (!!pParams->fHeatScale, 0)
    {
        uint32 nHeatAmount = pParams->fHeatScale.GetStore();
    }

    // Set sort distance based on params and bounding box.
    if (pParams->fSortBoundsScale == PRParams.m_fMainBoundsScale)
    {
        pRenderObject->m_fDistance = PRParams.m_fCamDistance * passInfo.GetZoomFactor();
    }
    else
    {
        pRenderObject->m_fDistance = m_impl->particleContainer->GetMain().GetNearestDistance(passInfo.GetCamera().GetPosition(), pParams->fSortBoundsScale) * passInfo.GetZoomFactor();
    }

    pRenderObject->m_fDistance += pParams->fSortOffset;
    pRenderObject->m_ParticleObjFlags = (pParams->bHalfRes ? CREParticle::ePOF_HALF_RES : 0) | (pParams->bVolumeFog ? CREParticle::ePOF_VOLUME_FOG : 0);

    particleEngine->QueueEmitterNextFrame(m_impl->instance, passInfo.IsAuxWindow());

    particleEngine->SetEmitterTransform(m_impl->instance, pObj->m_II.m_Matrix);

    SCameraInfo camInfo(passInfo);

    if (passInfo.IsShadowPass())
    {
        SRendItemSorter rendItemSorter = SRendItemSorter::CreateShadowPassRendItemSorter(passInfo);
        const int shadowIndex = static_cast<int>(passInfo.GetShadowMapType());
        if (m_impl->reShadow[shadowIndex])
        {
            pRenderObject->m_ObjFlags |= FOB_DYNAMIC_OBJECT;
            m_impl->reShadow[shadowIndex]->SetCameraFOV(camInfo.pCamera->GetFov());
            m_impl->reShadow[shadowIndex]->SetAspectRatio(camInfo.pCamera->GetProjRatio());
            m_impl->reShadow[shadowIndex]->SetWireframeEnabled(rParam.bIsShowWireframe);
            m_impl->renderer->EF_AddEf((CRendElementBase*)m_impl->reShadow[shadowIndex], *shaderItem, pObj, passInfo, EFSLIST_SHADOW_GEN, 0, rendItemSorter);
        }
        else
        {
            CRY_ASSERT(m_impl->reShadow[shadowIndex]);
        }
    }
    else
    {
        CREParticleGPU* re = nullptr;

        if (passInfo.IsRecursivePass())
        {
            const int recursiveLevel = static_cast<int>(passInfo.GetRecursiveLevel());
            AZ_Assert(1 == recursiveLevel, "Going deeper than expected for reflection pass. Maybe we need a vector of REs like reShadow does.");
            re = m_impl->reReflectionPass;
        }
        else
        {
            re = m_impl->reParticleGPU;
        }

        SRendItemSorter rendItemSorter = SRendItemSorter::CreateRendItemSorter(passInfo);

        if (re)
        {
            if (!pParams->DepthOfFieldBlur && !passInfo.IsAuxWindow())
            {
                pObj->m_ObjFlags |= FOB_RENDER_TRANS_AFTER_DOF;
            }
            re->SetCameraFOV(camInfo.pCamera->GetFov());
            re->SetAspectRatio(camInfo.pCamera->GetProjRatio());
            re->SetWireframeEnabled(rParam.bIsShowWireframe);
            m_impl->renderer->EF_AddEf((CRendElementBase*)re, *shaderItem, pObj, passInfo, EFSLIST_TRANSP, 1, rendItemSorter);
        }
        else
        {
            CRY_ASSERT(re);
        }
    }

    //get list of objects for cubemap depth collision if necessary
    if (pParams->eDepthCollision == ParticleParams::EDepthCollision::Cubemap)
    {
        AABB cubemapAABB(m_impl->particleContainer->GetMain().GetPos(), pParams->fCubemapFarDistance);
        PodArray<IShadowCaster*> nodes;

        m_impl->particleContainer->m_pObjManager->MakeDepthCubemapRenderItemList(reinterpret_cast<CVisArea*>(m_impl->particleContainer->GetMain().GetEntityVisArea()),
            cubemapAABB, 0, &nodes, passInfo);

        //create RenderingPassInfo for GPU particle cubemap
        SRenderingPassInfo tempPassInfo = SRenderingPassInfo::CreateTempRenderingInfo(
                SRenderingPassInfo::STATIC_OBJECTS | SRenderingPassInfo::GPU_PARTICLE_COLLISION_CUBEMAP |
                SRenderingPassInfo::TERRAIN | SRenderingPassInfo::ENTITIES, passInfo);

        for (int i = 0; i < nodes.Count(); ++i)
        {
            nodes[i]->Render(rParam, tempPassInfo);
        }
    }
}

void CParticleContainerGPU::OnContainerUpdateLife()
{
    if (!m_impl->renderer)
    {
        CRY_ASSERT(m_impl->renderer);
        return;
    }

    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();

    if (!particleEngine)
    {
        CRY_ASSERT(particleEngine);
        return;
    }

    if (!m_impl->particleContainer)
    {
        CRY_ASSERT(m_impl->particleContainer);
        return;
    }

    if (AZ::IsClose(m_impl->particleContainer->GetAge(), 0.0f, EPSILON))
    {
        if (m_impl->instance)
        {
            particleEngine->ResetEmitter(m_impl->instance);
        }
        else
        {
            CRY_ASSERT(m_impl->instance);
        }
    }
    else if (m_impl->particleContainer->GetAge() >= m_impl->particleContainer->GetMain().GetStopAge())
    {
        // Without this, disabling the emitter was not actually stopping particle generation
        particleEngine->StopEmitter(m_impl->instance);
    }
    else if (m_impl->particleContainer->GetAge() > 0)
    {
        particleEngine->StartEmitter(m_impl->instance);
    }
}

void CParticleContainerGPU::SetLodBlendAlpha(float blendAlpha)
{
    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();
    if (particleEngine)
    {
        particleEngine->SetEmitterLodBlendAlpha(m_impl->instance, blendAlpha);
    }
    else
    {
        CRY_ASSERT(particleEngine);
    }
}

void CParticleContainerGPU::OnEffectChange()
{
    //tell GPUEngine to verify that internal emitter still is set up properly
    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();
    if (particleEngine)
    {
        particleEngine->OnEffectChanged(m_impl->instance);
    }
    else
    {
        CRY_ASSERT(particleEngine);
    }
}

void CParticleContainerGPU::Update()
{
    m_impl->target.bTarget = false;
    if (m_impl->particleContainer)
    {
        m_impl->particleContainer->GetTarget(m_impl->target, m_impl->particleContainer->GetDirectEmitter());
    }
    else
    {
        CRY_ASSERT(m_impl->particleContainer);
    }
}

void CParticleContainerGPU::Prime(float equilibriumAge)
{
    IGPUParticleEngine* particleEngine = m_impl->renderer->GetGPUParticleEngine();
    if (particleEngine)
    {
        particleEngine->PrimeEmitter(m_impl->instance, equilibriumAge);
    }
    else
    {
        CRY_ASSERT(particleEngine);
    }
}