text: #include "Lighting.h"
  #include "PipelineState.h"
  #include "CommandContext.h"
  #include "Camera.h"
  #include "BufferManager.h"
  #include "Math/Vector.h"
  
  using namespace Math;
  using namespace Graphics;
  
  namespace Lighting
  {
      LightData m_LightData[MaxLights];
      StructuredBuffer m_LightBuffer;
      ByteAddressBuffer m_LightGrid;
  
      ByteAddressBuffer m_LightGridBitMask;
      uint32_t m_FirstConeLight;
      uint32_t m_FirstConeShadowedLight;
  
      enum { kShadowDim = 512 };
      ColorBuffer m_LightShadowArray;
      ShadowBuffer m_LightShadowTempBuffer;
      Matrix4 m_LightShadowMatrix[MaxLights];
  
      void CreateRandomLights(const Vector3 minBound, const Vector3 maxBound);
      void Shutdown(void);
  }
  
  void Lighting::Shutdown(void)
  {
      m_LightBuffer.Destroy();
      m_LightGrid.Destroy();
      m_LightGridBitMask.Destroy();
      m_LightShadowArray.Destroy();
      m_LightShadowTempBuffer.Destroy();
  }
  
  
  void Lighting::CreateRandomLights( const Vector3 minBound, const Vector3 maxBound )
  {
      Vector3 posScale = maxBound - minBound;
      Vector3 posBias = minBound;
  
      // todo: replace this with MT
      srand(12645);
      auto randUint = []() -> uint32_t
      {
          return rand(); // [0, RAND_MAX]
      };
      auto randFloat = [randUint]() -> float
      {
          return randUint() * (1.0f / RAND_MAX); // convert [0, RAND_MAX] to [0, 1]
      };
      auto randVecUniform = [randFloat]() -> Vector3
      {
          return Vector3(randFloat(), randFloat(), randFloat());
      };
      auto randGaussian = [randFloat]() -> float
      {
          // polar box-muller
          static bool gaussianPair = true;
          static float y2;
  
          if (gaussianPair)
          {
              gaussianPair = false;
  
              float x1, x2, w;
              do
              {
                  x1 = 2 * randFloat() - 1;
                  x2 = 2 * randFloat() - 1;
                  w = x1 * x1 + x2 * x2;
              } while (w >= 1);
  
              w = sqrt(-2 * log(w) / w);
              y2 = x2 * w;
              return x1 * w;
          }
          else
          {
              gaussianPair = true;
              return y2;
          }
      };
      auto randVecGaussian = [randGaussian]() -> Vector3
      {
          return Normalize(Vector3(randGaussian(), randGaussian(), randGaussian()));
      };
  
      const float pi = 3.14159265359f;
      for (uint32_t n = 0; n < MaxLights; n++)
      {
          Vector3 pos = randVecUniform() * posScale + posBias;
          float lightRadius = randFloat() * 800.0f + 200.0f;
  
          Vector3 color = randVecUniform();
          float colorScale = randFloat() * .3f + .3f;
          color = color * colorScale;
  
          uint32_t type;
          // force types to match 32-bit boundaries for the BIT_MASK_SORTED case
          if (n < 32 * 1)
              type = 0;
          else if (n < 32 * 3)
              type = 1;
          else
              type = 2;
  
          Vector3 coneDir = randVecGaussian();
          float coneInner = (randFloat() * .2f + .025f) * pi;
          float coneOuter = coneInner + randFloat() * .1f * pi;
  
          if (type == 1 || type == 2)
          {
              // emphasize cone lights
              color = color * 5.0f;
          }
  
          Math::Camera shadowCamera;
          shadowCamera.SetEyeAtUp(pos, pos + coneDir, Vector3(0, 1, 0));
          shadowCamera.SetPerspectiveMatrix(coneOuter * 2, 1.0f, lightRadius * .05f, lightRadius * 1.0f);
          shadowCamera.Update();
          m_LightShadowMatrix[n] = shadowCamera.GetViewProjMatrix();
          Matrix4 shadowTextureMatrix = Matrix4(AffineTransform(Matrix3::MakeScale( 0.5f, -0.5f, 1.0f ), Vector3(0.5f, 0.5f, 0.0f))) * m_LightShadowMatrix[n];
  
          m_LightData[n].pos[0] = pos.GetX();
          m_LightData[n].pos[1] = pos.GetY();
          m_LightData[n].pos[2] = pos.GetZ();
          m_LightData[n].radiusSq = lightRadius * lightRadius;
          m_LightData[n].color[0] = color.GetX();
          m_LightData[n].color[1] = color.GetY();
          m_LightData[n].color[2] = color.GetZ();
          m_LightData[n].type = type;
          m_LightData[n].coneDir[0] = coneDir.GetX();
          m_LightData[n].coneDir[1] = coneDir.GetY();
          m_LightData[n].coneDir[2] = coneDir.GetZ();
          m_LightData[n].coneAngles[0] = 1.0f / (cos(coneInner) - cos(coneOuter));
          m_LightData[n].coneAngles[1] = cos(coneOuter);
          std::memcpy(m_LightData[n].shadowTextureMatrix, &shadowTextureMatrix, sizeof(shadowTextureMatrix));
          //*(Matrix4*)(m_LightData[n].shadowTextureMatrix) = shadowTextureMatrix;
      }
      // sort lights by type, needed for efficiency in the BIT_MASK approach
  #if 0
      {
          Matrix4 copyLightShadowMatrix[MaxLights];
          memcpy( copyLightShadowMatrix, m_LightShadowMatrix, sizeof( Matrix4 ) * MaxLights );
          LightData copyLightData[MaxLights];
          memcpy( copyLightData, m_LightData, sizeof( LightData ) * MaxLights );
  
          uint32_t sortArray[MaxLights];
          for (uint32_t n = 0; n < MaxLights; n++)
          {
              sortArray[n] = n;
          }
          std::sort( sortArray, sortArray + MaxLights,
              [this]( const uint32_t &a, const uint32_t &b ) -> bool
          {
              return this->m_LightData[a].type < this->m_LightData[b].type;
          } );
          for (uint32_t n = 0; n < MaxLights; n++)
          {
              m_LightShadowMatrix[n] = copyLightShadowMatrix[sortArray[n]];
              m_LightData[n] = copyLightData[sortArray[n]];
          }
      }
  #endif
      for (uint32_t n = 0; n < MaxLights; n++)
      {
          if (m_LightData[n].type == 1)
          {
              m_FirstConeLight = n;
              break;
          }
      }
      for (uint32_t n = 0; n < MaxLights; n++)
      {
          if (m_LightData[n].type == 2)
          {
              m_FirstConeShadowedLight = n;
              break;
          }
      }
      m_LightBuffer.Create(L"m_LightBuffer", MaxLights, sizeof(LightData), m_LightData);
  
      // todo: assumes max resolution of 1920x1080
      uint32_t lightGridCells = Math::DivideByMultiple(1920, kMinLightGridDim) * Math::DivideByMultiple(1080, kMinLightGridDim);
      uint32_t lightGridSizeBytes = lightGridCells * (4 + MaxLights * 4);
      m_LightGrid.Create(L"m_LightGrid", lightGridSizeBytes, 1, nullptr);
  
      uint32_t lightGridBitMaskSizeBytes = lightGridCells * 4 * 4;
      m_LightGridBitMask.Create(L"m_LightGridBitMask", lightGridBitMaskSizeBytes, 1, nullptr);
  
      m_LightShadowArray.CreateArray(L"m_LightShadowArray", kShadowDim, kShadowDim, MaxLights, DXGI_FORMAT_R16_UNORM);
      m_LightShadowTempBuffer.Create(L"m_LightShadowTempBuffer", kShadowDim, kShadowDim);
  }
  
  