/* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Smoke particle renderer with volumetric shadows

#ifndef SMOKE_RENDERER_H
#define SMOKE_RENDERER_H

#include "framebufferObject.h"
#include "GLSLProgram.h"
#include "nvMath.h"

using namespace nv;

class SmokeRenderer {
 public:
  SmokeRenderer(int maxParticles);
  ~SmokeRenderer();

  enum DisplayMode { POINTS, SPRITES, VOLUMETRIC, NUM_MODES };

  enum Target { LIGHT_BUFFER, SCENE_BUFFER };

  void setDisplayMode(DisplayMode mode) { mDisplayMode = mode; }

  void setNumParticles(unsigned int x) { mNumParticles = x; }
  void setPositionBuffer(GLuint vbo) { mPosVbo = vbo; }
  void setVelocityBuffer(GLuint vbo) { mVelVbo = vbo; }
  void setColorBuffer(GLuint vbo) { mColorVbo = vbo; }
  void setIndexBuffer(GLuint ib) { mIndexBuffer = ib; }

  void setParticleRadius(float x) { mParticleRadius = x; }
  void setWindowSize(int w, int h);
  void setFOV(float fov) { mFov = fov; }

  void setNumSlices(int x) { m_numSlices = x; }
  void setNumDisplayedSlices(int x) { m_numDisplayedSlices = x; }

  void setAlpha(float x) { m_spriteAlpha = x; }
  void setShadowAlpha(float x) { m_shadowAlpha = x; }
  void setColorAttenuation(vec3f c) { m_colorAttenuation = c; }
  void setLightColor(vec3f c);

  void setDoBlur(bool b) { m_doBlur = b; }
  void setBlurRadius(float x) { m_blurRadius = x; }
  void setDisplayLightBuffer(bool b) { m_displayLightBuffer = b; }

  void beginSceneRender(Target target);
  void endSceneRender(Target target);

  void setLightPosition(vec3f v) { m_lightPos = v; }
  void setLightTarget(vec3f v) { m_lightTarget = v; }

  vec4f getLightPositionEyeSpace() { return m_lightPosEye; }
  matrix4f getShadowMatrix() { return m_shadowMatrix; }

  GLuint getShadowTexture() { return m_lightTexture[m_srcLightTexture]; }

  void calcVectors();
  vec3f getSortVector() { return m_halfVector; }

  void render();
  void debugVectors();

 private:
  void drawPoints(int start, int count, bool sort);
  void drawPointSprites(GLSLProgram *prog, int start, int count, bool shadowed);

  void drawSlice(int i);
  void drawSliceLightView(int i);
  void drawSlices();
  void displayTexture(GLuint tex);
  void compositeResult();
  void blurLightBuffer();
  void depthSort();

  GLuint createTexture(GLenum target, int w, int h, GLint internalformat,
                       GLenum format);
  void createBuffers(int w, int h);
  void createLightBuffer();

  void drawQuad();
  void drawVector(vec3f v);

  // particle data
  unsigned int mMaxParticles;
  unsigned int mNumParticles;

  GLuint mPosVbo;
  GLuint mVelVbo;
  GLuint mColorVbo;
  GLuint mIndexBuffer;

  float mParticleRadius;
  DisplayMode mDisplayMode;

  // window
  unsigned int mWindowW, mWindowH;
  float mAspect, mInvFocalLen;
  float mFov;

  int m_downSample;
  int m_imageW, m_imageH;

  int m_numSlices;
  int m_numDisplayedSlices;
  int m_batchSize;
  int m_sliceNo;

  float m_shadowAlpha;
  float m_spriteAlpha;
  bool m_doBlur;
  float m_blurRadius;
  bool m_displayLightBuffer;

  vec3f m_lightVector, m_lightPos, m_lightTarget;
  vec3f m_lightColor;
  vec3f m_colorAttenuation;
  float m_lightDistance;

  matrix4f m_modelView, m_lightView, m_lightProj, m_shadowMatrix;
  vec3f m_viewVector, m_halfVector;
  bool m_invertedView;
  vec4f m_eyePos;
  vec4f m_halfVectorEye;
  vec4f m_lightPosEye;

  // programs
  GLSLProgram *m_simpleProg;
  GLSLProgram *m_particleProg, *m_particleShadowProg;
  GLSLProgram *m_displayTexProg, *m_blurProg;

  // image buffers
  int m_lightBufferSize;
  GLuint m_lightTexture[2];
  int m_srcLightTexture;
  GLuint m_lightDepthTexture;
  FramebufferObject *m_lightFbo;

  GLuint m_imageTex, m_depthTex;
  FramebufferObject *m_imageFbo;
};

#endif
