
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

#include "afx/arcaneFX.h"

#include "scene/sceneRenderState.h"
#include "T3D/fx/particleEmitter.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightInfo.h"
#include "lighting/lightManager.h"

#include "afx/util/afxParticlePool.h"

void afxParticlePool::prepRenderImage(SceneRenderState* state)
{ 
  const LightInfo *sunlight = LIGHTMGR->getSpecialLight( LightManager::slSunLightType );
  pool_prepBatchRender(state->getRenderPass(), state->getCameraPosition(), sunlight->getAmbient());
};

void afxParticlePool::pool_prepBatchRender(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor)
{
  if (emitters.empty()) 
    return;

  switch (mDataBlock->pool_type)
  {
  case afxParticlePoolData::POOL_TWOPASS :
    pool_renderObject_TwoPass(renderManager, camPos, ambientColor);
    break;
  case afxParticlePoolData::POOL_NORMAL :
  default:
    pool_renderObject_Normal(renderManager, camPos, ambientColor);
  }
}

void afxParticlePool::pool_renderObject_Normal(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor)
{
  S32 n_parts = 0;
  for (S32 i = 0; i < emitters.size(); i++)
    n_parts += emitters[i]->n_parts;

  if (n_parts == 0)
    return;

  ParticleEmitterData* main_emitter_data = emitters[0]->mDataBlock;
  
  main_emitter_data->allocPrimBuffer(n_parts);

  orderedVector.clear();

  MatrixF modelview = GFX->getWorldMatrix();
  Point3F viewvec; modelview.getRow(1, &viewvec);

  for (U32 i=0; i < emitters.size(); i++)
  {
    // add each particle and a distance based sort key to orderedVector
    for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next)
    {
      orderedVector.increment();
      orderedVector.last().p = pp;
      orderedVector.last().k = mDot(pp->pos, viewvec);
      orderedVector.last().emitter = emitters[i];
    }
  }

  // qsort the list into far to near ordering
  dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticlePool), cmpSortParticlePool);

  static Vector<GFXVertexPCT> tempBuff(2048);
  tempBuff.reserve(n_parts*4 + 64); // make sure tempBuff is big enough
  GFXVertexPCT *buffPtr = tempBuff.address(); // use direct pointer (faster)

  Point3F basePoints[4];
  basePoints[0] = Point3F(-1.0, 0.0, -1.0);
  basePoints[1] = Point3F( 1.0, 0.0, -1.0);
  basePoints[2] = Point3F( 1.0, 0.0,  1.0);
  basePoints[3] = Point3F(-1.0, 0.0,  1.0);

  MatrixF camView = GFX->getWorldMatrix();
  camView.transpose();  // inverse - this gets the particles facing camera

  for (U32 i = 0; i < orderedVector.size(); i++)
  {
    Particle* particle = orderedVector[i].p;
    ParticleEmitter* emitter = orderedVector[i].emitter;

    if (emitter->mDataBlock->orientParticles)
      emitter->setupOriented(particle, camPos, ambientColor, buffPtr);
    else
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
    buffPtr+=4;
  }

  // create new VB if emitter size grows
  if( !mVertBuff || n_parts > mCurBuffSize )
  {
    mCurBuffSize = n_parts;
    mVertBuff.set(GFX, n_parts*4, GFXBufferTypeDynamic);
  }
  // lock and copy tempBuff to video RAM
  GFXVertexPCT *verts = mVertBuff.lock();
  dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) );
  mVertBuff.unlock();

  //MeshRenderInst *ri = gRenderInstManager->allocInst<MeshRenderInst>();
  ParticleRenderInst *ri = renderManager->allocInst<ParticleRenderInst>();
  ri->vertBuff = &mVertBuff;
  ri->primBuff = &main_emitter_data->primBuff;
  ri->translucentSort = true;
  ri->type = RenderPassManager::RIT_Particle;
  ri->sortDistSq = getWorldBox().getSqDistanceToPoint( camPos );

  ri->defaultKey = (-sort_priority*100);

  ri->modelViewProj = renderManager->allocUniqueXform(  GFX->getProjectionMatrix() * 
                                                        GFX->getViewMatrix() * 
                                                        GFX->getWorldMatrix() );

  ri->count = n_parts;

  ri->blendStyle = main_emitter_data->blendStyle;

  // use first particle's texture unless there is an emitter texture to override it
  if (main_emitter_data->textureHandle)
    ri->diffuseTex = &*(main_emitter_data->textureHandle);
  else
    ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->getTextureResource());

  ri->softnessDistance = main_emitter_data->softnessDistance;

  // Sort by texture too.
  //ri->defaultKey = ri->diffuseTex ? (U32)ri->diffuseTex : (U32)ri->vertBuff;

  renderManager->addInst( ri );
}

void afxParticlePool::pool_renderObject_TwoPass(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor)
{
  S32 n_parts = 0;
  for (S32 i = 0; i < emitters.size(); i++)
    n_parts += emitters[i]->n_parts;

  if (n_parts == 0)
    return;

  ParticleEmitterData* main_emitter_data = emitters[0]->mDataBlock;
  
  main_emitter_data->allocPrimBuffer(n_parts);

  orderedVector.clear();

  F32 min_d=0.0f, max_d=0.0f;

  for (U32 i=0; i < emitters.size(); i++)
  {
    if (!emitters[i]->mDataBlock->pool_depth_fade || !emitters[i]->mDataBlock->pool_radial_fade)
      continue;

    // add particles to orderedVector and calc distance and min/max distance
    for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next)
    {
      F32 dist = (pp->pos-camPos).len();
      if (dist > max_d)
        max_d = dist;
      else if (dist < min_d)
        min_d = dist;

      orderedVector.increment();
      orderedVector.last().p = pp;
      orderedVector.last().k = dist;
      orderedVector.last().emitter = emitters[i];
    }
  }

  // Add remaining emitters particles to the orderedVector that do not participate in the 
  // above depth computations:
  for (U32 i=0; i < emitters.size(); i++)
  {
    if (emitters[i]->mDataBlock->pool_depth_fade || emitters[i]->mDataBlock->pool_radial_fade)
      continue;

    for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next)
    {
      orderedVector.increment();
      orderedVector.last().p = pp;
      orderedVector.last().k = 0;  // no need to compute depth here
      orderedVector.last().emitter = emitters[i];
    }
  }

  static Vector<GFXVertexPCT> tempBuff(2048);
  tempBuff.reserve(n_parts*4 + 64); // make sure tempBuff is big enough
  GFXVertexPCT *buffPtr = tempBuff.address(); // use direct pointer (faster)

  Point3F basePoints[4];
  basePoints[0] = Point3F(-1.0, 0.0, -1.0);
  basePoints[1] = Point3F( 1.0, 0.0, -1.0);
  basePoints[2] = Point3F( 1.0, 0.0,  1.0);
  basePoints[3] = Point3F(-1.0, 0.0,  1.0);

  MatrixF camView = GFX->getWorldMatrix();
  camView.transpose();  // inverse - this gets the particles facing camera

  //~~~~~~~~~~~~~~~~~~~~//

  Point3F bbox_center; mObjBox.getCenter(&bbox_center);
  F32 d_range = max_d - min_d;
  bool d_safe = (d_range>0.0001);
  F32 d_half = min_d + (d_range*0.5f);

  //~~~~~~~~~~~~~~~~~~~~//

  for (U32 i = 0; i < orderedVector.size(); i++)
  {
    Particle* particle = orderedVector[i].p;
    ParticleEmitter* emitter = orderedVector[i].emitter;

    LinearColorF color_save = particle->color;
    particle->color.set(mDataBlock->base_color.red, mDataBlock->base_color.green, mDataBlock->base_color.blue, mDataBlock->base_color.alpha*particle->color.alpha);
    emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
    particle->color = color_save;

    buffPtr+=4;
  }

  // create new VB if emitter size grows
  if( !mVertBuff || n_parts > mCurBuffSize )
  {
    mCurBuffSize = n_parts;
    mVertBuff.set(GFX, n_parts*4, GFXBufferTypeDynamic);
  }
  // lock and copy tempBuff to video RAM
  GFXVertexPCT *verts = mVertBuff.lock();
  dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) );
  mVertBuff.unlock();

  ParticleRenderInst *ri = renderManager->allocInst<ParticleRenderInst>();
  ri->vertBuff = &mVertBuff;
  ri->primBuff = &main_emitter_data->primBuff;
  ri->translucentSort = true;
  ri->type = RenderPassManager::RIT_Particle;
  ri->sortDistSq = getWorldBox().getSqDistanceToPoint( camPos );

  ri->defaultKey = (-sort_priority*100);

  ri->modelViewProj = renderManager->allocUniqueXform(  GFX->getProjectionMatrix() * 
                                                        GFX->getViewMatrix() * 
                                                        GFX->getWorldMatrix() );

  ri->count = n_parts;

  ri->blendStyle = ParticleRenderInst::BlendNormal;

  // use first particle's texture unless there is an emitter texture to override it
  //if (main_emitter_data->textureHandle)
  //  ri->diffuseTex = &*(main_emitter_data->textureHandle);
  //else
    ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->getTextureExtResource());

  F32 save_sort_dist = ri->sortDistSq;

  ri->softnessDistance = main_emitter_data->softnessDistance;

  renderManager->addInst( ri );

  //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
  //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
  // 2nd-pass

  buffPtr = tempBuff.address();

  bbox_center.z = mObjBox.minExtents.z;
  F32 max_radius = (max_d-min_d)*0.5f;

  // gather fade settings
  bool do_mixed_fades = false;
  bool do_radial_fades = (emitters[0]->mDataBlock->pool_radial_fade && (max_radius>0.0001f));
  bool do_depth_fades = (emitters[0]->mDataBlock->pool_depth_fade && d_safe);
  for (U32 i = 1; i < emitters.size(); i++)
  {
    if ( (do_radial_fades != (emitters[i]->mDataBlock->pool_radial_fade && (max_radius>0.0001f))) ||
      (do_depth_fades != (emitters[i]->mDataBlock->pool_depth_fade && d_safe)))
    {
      do_mixed_fades = true;
      break;
    }
  }

  if (do_mixed_fades)
  {
    for (U32 i = 0; i < orderedVector.size(); i++)
    {
      Particle* particle = orderedVector[i].p;
      ParticleEmitter* emitter = orderedVector[i].emitter;

      F32 bf = 1.0;  // blend factor

      // blend factor due to radius
      if (emitter->mDataBlock->pool_radial_fade && (max_radius>0.0001f))
      {
        F32 p_radius = (particle->pos-bbox_center).len();
        F32 bf_radius = p_radius/max_radius;
        if (bf_radius>1.0f) bf_radius = 1.0f;
        bf *= bf_radius*bf_radius;  // quadratic for faster falloff
      }

      // blend factor, depth based
      if (emitter->mDataBlock->pool_depth_fade && d_safe && (orderedVector[i].k > d_half))
      {
        F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f));
        bf *= bf_depth;
      }

      // overall blend factor weight
      bf *= mDataBlock->blend_weight;

      LinearColorF color_save = particle->color;
      particle->color = particle->color*bf;
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
      particle->color = color_save;

      buffPtr+=4;
    }
  }
  else if (do_radial_fades && do_depth_fades)
  {
    for (U32 i = 0; i < orderedVector.size(); i++)
    {
      Particle* particle = orderedVector[i].p;
      ParticleEmitter* emitter = orderedVector[i].emitter;

      F32 bf = 1.0;  // blend factor

      // blend factor due to radius
      F32 p_radius = (particle->pos-bbox_center).len();
      F32 bf_radius = p_radius/max_radius;
      if (bf_radius>1.0f) bf_radius = 1.0f;
      bf *= bf_radius*bf_radius;  // quadratic for faster falloff

      // blend factor, depth based
      if (orderedVector[i].k > d_half)
      {
        F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f));
        bf *= bf_depth;
      }

      // overall blend factor weight
      bf *= mDataBlock->blend_weight;

      LinearColorF color_save = particle->color;
      particle->color = particle->color*bf;
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
      particle->color = color_save;

      buffPtr+=4;
    }
  }
  else if (do_radial_fades) // && !do_depth_fades
  {
    for (U32 i = 0; i < orderedVector.size(); i++)
    {
      Particle* particle = orderedVector[i].p;
      ParticleEmitter* emitter = orderedVector[i].emitter;

      F32 bf = 1.0;  // blend factor

      // blend factor due to radius
      F32 p_radius = (particle->pos-bbox_center).len();
      F32 bf_radius = p_radius/max_radius;
      if (bf_radius>1.0f) bf_radius = 1.0f;
      bf *= bf_radius*bf_radius;  // quadratic for faster falloff

      // overall blend factor weight
      bf *= mDataBlock->blend_weight;

      LinearColorF color_save = particle->color;
      particle->color = particle->color*bf;
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
      particle->color = color_save;

      buffPtr+=4;
    }
  }
  else if (do_depth_fades) // && !do_radial_fades
  {
    for (U32 i = 0; i < orderedVector.size(); i++)
    {
      Particle* particle = orderedVector[i].p;
      ParticleEmitter* emitter = orderedVector[i].emitter;

      F32 bf = 1.0;  // blend factor

      // blend factor, depth based
      if (orderedVector[i].k > d_half)
      {
        F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f));
        bf *= bf_depth;
      }

      // overall blend factor weight
      bf *= mDataBlock->blend_weight;

      LinearColorF color_save = particle->color;
      particle->color = particle->color*bf;
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
      particle->color = color_save;

      buffPtr+=4;
    }
  }
  else // (no fades)
  {
    for (U32 i = 0; i < orderedVector.size(); i++)
    {
      Particle* particle = orderedVector[i].p;
      ParticleEmitter* emitter = orderedVector[i].emitter;

      F32 bf = mDataBlock->blend_weight;  // blend factor

      LinearColorF color_save = particle->color;
      particle->color = particle->color*bf;
      emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr);
      particle->color = color_save;

      buffPtr+=4;
    }
  }

  // create new VB if emitter size grows
  if( !mVertBuff2 || n_parts > mCurBuffSize2 )
  {
    mCurBuffSize2 = n_parts;
    mVertBuff2.set(GFX, n_parts*4, GFXBufferTypeDynamic);
  }

  // lock and copy tempBuff to video RAM
  verts = mVertBuff2.lock();
  dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) );
  mVertBuff2.unlock();

  ri = renderManager->allocInst<ParticleRenderInst>();
  ri->vertBuff = &mVertBuff2;
  ri->primBuff = &main_emitter_data->primBuff;
  ri->translucentSort = true;
  ri->type = RenderPassManager::RIT_Particle;
  ri->sortDistSq = save_sort_dist;

  ri->defaultKey = (-sort_priority*100) + 1;

  ri->modelViewProj = renderManager->allocUniqueXform(  GFX->getProjectionMatrix() * 
                                                        GFX->getViewMatrix() * 
                                                        GFX->getWorldMatrix() );

  ri->count = n_parts;

  ri->blendStyle = ParticleRenderInst::BlendAdditive;

  // use first particle's texture unless there is an emitter texture to override it
  if (main_emitter_data->textureHandle)
    ri->diffuseTex = &*(main_emitter_data->textureHandle);
  else
    ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->getTextureResource());

  ri->softnessDistance = main_emitter_data->softnessDistance;

  renderManager->addInst( ri );
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
