/* Start Header -------------------------------------------------------
Purpose: - This is a sandbox for showing loaded objects and others.
         - It's implemented a hybrid rendering pipeline that employs a Deferred
           Rendering method for the objects of interest, coupled with the usual
           Forward Rendering for auxiliary rendering.
         - (2/23/2020) recursive_directory_iterator is added for loading
           many objects in a folder (Power Plant)
         - (4/16/2020) Adaptive Octree
Language: ISO C++17 Standard (/std:c++17)
Platform: 16.0, x64, Window10
Project: Spatial Algorithms - Hybrid Rendering
Author: Minsu Kang
Creation date: 1/13/2020
End Header --------------------------------------------------------*/

  .
  .
  .

bool SceneHybridRendering::Init_GBuffer_Configration()
{
  glGenFramebuffers(1, &gBufferFBO);
  glBindFramebuffer(GL_FRAMEBUFFER, gBufferFBO);

  // position buffer (GL_RGB)
  glGenTextures(1, &gPosition);
  glBindTexture(GL_TEXTURE_2D, gPosition);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, this->_windowWidth, this->_windowHeight, 0, GL_RGB, GL_FLOAT, NULL);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);

  // normal buffer (GL_RGB)
  glGenTextures(1, &gNormal);
  glBindTexture(GL_TEXTURE_2D, gNormal);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, this->_windowWidth, this->_windowHeight, 0, GL_RGB, GL_FLOAT, NULL);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);

  // color (albedo) and specular buffer (GL_RGBA)
  glGenTextures(1, &gAlbedoSpec);
  glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this->_windowWidth, this->_windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);

  // tell OpenGL which color attachments we'll use (of this framebuffer) for rendering 
  unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
  glDrawBuffers(3, attachments);

  // create and attach depth buffer (renderbuffer)
  unsigned int rboDepth;
  glGenRenderbuffers(1, &rboDepth);
  glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, this->_windowWidth, this->_windowHeight);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);

  // finally check if framebuffer is complete
  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
  {
    printf("gbuffer program failed to compile");
    return false;
  }

  // restore default FBO
  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  // shader configuration
  // --------------------
  shaderFSQBuffer->use();
  shaderFSQBuffer->setInt("gPosition", 0);
  shaderFSQBuffer->setInt("gNormal", 1);
  shaderFSQBuffer->setInt("gAlbedoSpec", 2);

  return true;
}

int SceneHybridRendering::Render(const float dt)
{
  .
  .
  .

  // "gBufferFBO" is user defined FBO
  Render_Deferred_Objects(gBufferFBO);

  // Copy gBufferFBO's depth buffer into default OpenGL depth buffer (0)
  if (bCopyDepthInfo) // Toggle this capability with input from user (GUI).
    Copy_Depth_Info(gBufferFBO, 0);

  // This will draw all dubug data.
  Render_Debug_Objects();

  return 0;
}

// Implement the Deferred Shading pipeline for rendering the OBJ files using FBO’s
void SceneHybridRendering::Render_Deferred_Objects(const GLint fbo)
{
  /////////////////////////////////////////////////////////////////////////////
  // First pass: Generate the G-buffer using multiple render targets.
  glBindFramebuffer(GL_FRAMEBUFFER, gBufferFBO);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glm::mat4 projection = glm::perspective(glm::radians(m_pCamera->Zoom), (float)this->_windowWidth / (float)this->_windowHeight, 0.1f, 100.0f);
  glm::mat4 view = m_pCamera->GetViewMatrix();
  glm::mat4 model = glm::mat4(1.0f);

  shaderGBuffer->use();
  shaderGBuffer->setMat4("projection", projection);
  shaderGBuffer->setMat4("view", view);

  for (auto pObj : pObjectList)
  {
    model = glm::mat4(1.0f);
    model = glm::translate(model, pObj->position);
    // model = glm::rotate(model, angleOfCenterObjRotation, glm::vec3(0.0f, 1.0f, 0.0f));
    model = glm::scale(model, pObj->scale);
    shaderGBuffer->setMat4("model", model);
    shaderGBuffer->setVec3("color", pObj->color);
    shaderGBuffer->setInt("renderTarget", renderTarget);
    pObj->Draw(shaderGBuffer);
  }

  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  /////////////////////////////////////////////////////////////////////////////
  // Second Pass: Use the render targets from Pass 1 as input textures to the
  //              Lighting Pass. The Vertex Shader will be a straight pass -
  //              through to render a Full Screen Quad(FSQ).
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  shaderFSQBuffer->use();
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, gPosition);
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, gNormal);
  glActiveTexture(GL_TEXTURE2);
  glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);

  // Uniform Blocks (as before) to pass light information.
  int lightIndex = 0;
  const float linear = 0.7;
  const float quadratic = 1.8;
  for (auto pLight : pLightList)
  {
    if (pLight->isActivated == false)
      continue;

    shaderFSQBuffer->setVec3("lights[" + std::to_string(lightIndex) + "].Position", pLight->position);
    shaderFSQBuffer->setVec3("lights[" + std::to_string(lightIndex) + "].Color", pLight->color);

    // update attenuation parameters
    shaderFSQBuffer->setFloat("lights[" + std::to_string(lightIndex) + "].Linear", linear);
    shaderFSQBuffer->setFloat("lights[" + std::to_string(lightIndex) + "].Quadratic", quadratic);

    ++lightIndex;
  }

  // send general light data
  shaderFSQBuffer->setInt("currLightsNum", numOfSpheres);
  shaderFSQBuffer->setVec3("viewPos", m_pCamera->Position);
  shaderFSQBuffer->setInt("renderTarget", renderTarget);

  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // FSQ should be always randered!
  RenderFSQ(); // FSQBuffer shader on a quad (FSQ)
}

.
.
.