/******************************************************************************/
/*
  Project   - MudBun
  Publisher - Long Bunny Labs
              http://LongBunnyLabs.com
  Author    - Ming-Lun "Allen" Chou
              http://AllenChou.net
*/
/******************************************************************************/

#ifndef MUDBUN_MARCHING_CUBES_FUNCS
#define MUDBUN_MARCHING_CUBES_FUNCS

#include "MarchingCubesDefs.cginc"

#include "BrushFuncs.cginc"
#include "NormalFuncs.cginc"
#include "SDF/SDF.cginc"

// cubeMat = whole-cube properties (for flat normal mode only)
// tStmtPre = statements pre-processing "iTri" for new triangle
// vStmt = statements processing "iVert", "aVertPos", "aVertNorm", and "aVertaMat" for new triangle
// tStmtPost = statements post-processing "iTri" a new triangle
#define MARCHING_CUBES(                                                        \
  center, size, sdf_func, iBrushMask, smoothNormal, cubeMat,                   \
  tStmtPre, vStmt, tStmtPost                                                   \
)                                                                              \
{                                                                              \
  int cubeIndex = 0;                                                           \
  float d[8];                                                                  \
  SdfBrushMaterial aMat[8];                                                    \
  cubeMat = init_brush_material();                                             \
  {                                                                            \
    [loop] for (int iVert = 0; iVert < 8; ++iVert)                             \
    {                                                                          \
      float3 vertPos = center + size * vertPosLs[iVert];                       \
      d[iVert] = sdf_func(vertPos, iBrushMask, aMat[iVert]);                   \
      cubeIndex |= (int(step(0.0f, -d[iVert])) << iVert);                      \
    }                                                                          \
                                                                               \
    if (!smoothNormal)                                                         \
      cubeMat = aMat[0];                                                       \
  }                                                                            \
                                                                               \
  int iTriListBase = cubeIndex * 16;                                           \
  [loop] for (int iTri = 0; iTri < kMarchingCubesTrisPerVoxel; ++iTri)         \
  {                                                                            \
    int iTriBase = iTri * 3;                                                   \
    int aiEdge[3];                                                             \
    aiEdge[0] = triTable[iTriListBase + iTriBase];                             \
    if (aiEdge[0] < 0)                                                         \
      break;                                                                   \
                                                                               \
    aiEdge[1] = triTable[iTriListBase + iTriBase + 1];                         \
    aiEdge[2] = triTable[iTriListBase + iTriBase + 2];                         \
                                                                               \
    tStmtPre                                                                   \
                                                                               \
    float3 aVertPos[3];                                                        \
    float3 aVertNorm[3];                                                       \
    float3 aEdgeCenter[3];                                                     \
    float3 goodNorm = float3(0.0f, 0.0f, 0.0f);                                \
    SdfBrushMaterial aVertMat[3];                                              \
    [loop] for (int jVert = 0; jVert < 3; ++jVert)                             \
    {                                                                          \
      int iEdgeVert0 = vertTable[aiEdge[jVert] * 2];                           \
      int iEdgeVert1 = vertTable[aiEdge[jVert] * 2 + 1];                       \
      float3 p0Ls = vertPosLs[iEdgeVert0];                                     \
      float3 p1Ls = vertPosLs[iEdgeVert1];                                     \
      float t = -d[iEdgeVert0] / (d[iEdgeVert1] - d[iEdgeVert0]);              \
      aVertPos[jVert] = center + size * lerp(p0Ls, p1Ls, t);                   \
      aEdgeCenter[jVert] = center + size * 0.5f * (p0Ls + p1Ls);               \
      if (smoothNormal)                                                        \
      {                                                                        \
        aVertMat[jVert] = lerp(aMat[iEdgeVert0], aMat[iEdgeVert1], t);         \
        SDF_NORMAL(aVertNorm[jVert], aVertPos[jVert], sdf_func, iBrushMask, normalDifferentiationStep); \
        if (dot(aVertNorm[jVert], aVertNorm[jVert]) > kEpsilon)                \
          goodNorm = aVertNorm[jVert];                                         \
      }                                                                        \
    }                                                                          \
                                                                               \
    if (smoothNormal)                                                          \
    {                                                                          \
      for (int kVert = 0; kVert < 3; ++kVert)                                  \
      {                                                                        \
        if (dot(aVertNorm[kVert], aVertNorm[kVert]) <= kEpsilon)               \
          aVertNorm[kVert] = goodNorm;                                         \
      }                                                                        \
    }                                                                          \
    else                                                                       \
    {                                                                          \
      float3 flatNorm =                                                        \
        normalize                                                              \
        (                                                                      \
          cross(aVertPos[1] - aVertPos[0], aVertPos[2] - aVertPos[0])          \
        );                                                                     \
      for (int kVert = 0; kVert < 3; ++kVert)                                  \
        aVertNorm[kVert] = flatNorm;                                           \
    }                                                                          \
                                                                               \
    for (int iVert = 0; iVert < 3; ++iVert)                                    \
    {                                                                          \
      vStmt                                                                    \
    }                                                                          \
                                                                               \
    tStmtPost                                                                  \
  }                                                                            \
}


// cubeMat = whole-cube properties (for flat normal mode only)
// tStmtPre = statements pre-processing "iTri" for new triangle
// vStmt = statements processing "iVert", "aVertPos", "aVertNorm", and "aVertaMat" for new triangle
// tStmtPost = statements post-processing "iTri" a new triangle
#define MARCHING_CUBES_2D(                                                     \
  center, size, sdf_func, iBrushMask, smoothNormal, cubeMat,                   \
  tStmtPre, vStmt, tStmtPost                                                   \
)                                                                              \
{                                                                              \
  int squareIndex = 0;                                                         \
  float d[4];                                                                  \
  float dCenter;                                                               \
  SdfBrushMaterial aMat[4];                                                    \
  cubeMat = init_brush_material();                                             \
  {                                                                            \
    [loop] for (int iVert = 0; iVert < 4; ++iVert)                             \
    {                                                                          \
      float3 vertPos = center + size * vertPosLs2d[iVert];                     \
      d[iVert] = sdf_func(vertPos, iBrushMask, aMat[iVert]);                   \
      squareIndex |= (int(step(0.0f, -d[iVert])) << iVert);                    \
    }                                                                          \
  }                                                                            \
  if (!smoothNormal)                                                           \
    dCenter = sdf_func(center, iBrushMask, cubeMat);                           \
                                                                               \
  int iTriListBase = squareIndex * 12;                                         \
  [loop] for (int iTri = 0; iTri < 4; ++iTri)                                  \
  {                                                                            \
    int iTriBase = iTri * 3;                                                   \
    int aiVert[3];                                                             \
    aiVert[0] = triTable2d[iTriListBase + iTriBase];                           \
    if (aiVert[0] < 0)                                                         \
      break;                                                                   \
                                                                               \
    aiVert[1] = triTable2d[iTriListBase + iTriBase + 1];                       \
    aiVert[2] = triTable2d[iTriListBase + iTriBase + 2];                       \
                                                                               \
    tStmtPre                                                                   \
                                                                               \
    float3 aVertPos[3];                                                        \
    float3 aVertNorm[3];                                                       \
    float aVertSdfValue[3];                                                    \
    SdfBrushMaterial aVertMat[3];                                              \
    [loop] for (int jVert = 0; jVert < 3; ++jVert)                             \
    {                                                                          \
      if (aiVert[jVert] < 4)                                                   \
      {                                                                        \
        aVertPos[jVert] = center + size * vertPosLs2d[aiVert[jVert]];          \
        aVertMat[jVert] = aMat[aiVert[jVert]];                                 \
        aVertSdfValue[jVert] = d[aiVert[jVert]];                               \
      }                                                                        \
      else                                                                     \
      {                                                                        \
        int iVert0 = aiVert[jVert] - 4;                                        \
        int iVert1 = ((aiVert[jVert] == 7) ? 0 : (aiVert[jVert] - 3));         \
        float3 p0Ls = vertPosLs2d[iVert0];                                     \
        float3 p1Ls = vertPosLs2d[iVert1];                                     \
        float t = -d[iVert0] / (d[iVert1] - d[iVert0]);                        \
        aVertPos[jVert] = center + size * lerp(p0Ls, p1Ls, t);                 \
        aVertMat[jVert] = lerp(aMat[iVert0], aMat[iVert1], t);                 \
        aVertSdfValue[jVert] = lerp(d[iVert0], d[iVert1], t);                  \
      }                                                                        \
    }                                                                          \
                                                                               \
    float3 aVertNorm2d[3];                                                     \
    float3 norm;                                                               \
    float3 norm2d;                                                             \
    if (!smoothNormal)                                                         \
    {                                                                          \
      SDF_NORMAL_2D(norm2d, center, sdf_func, iBrushMask, normalDifferentiationStep); \
      norm = normal_2d_blend(norm2d, dCenter);                                 \
    }                                                                          \
    for (int kVert = 0; kVert < 3; ++kVert)                                    \
    {                                                                          \
      int iVertNorm = aiVert[kVert];                                           \
      if (smoothNormal)                                                        \
      {                                                                        \
        SDF_NORMAL_2D(norm2d, aVertPos[kVert], sdf_func, iBrushMask, normalDifferentiationStep); \
        norm = normal_2d_blend(norm2d, d[iVertNorm]);                          \
      }                                                                        \
      aVertNorm[kVert] = norm;                                                 \
      aVertNorm2d[kVert] = norm2d;                                             \
    }                                                                          \
                                                                               \
    for (int iVert = 0; iVert < 3; ++iVert)                                    \
    {                                                                          \
      vStmt                                                                    \
    }                                                                          \
                                                                               \
    tStmtPost                                                                  \
  }                                                                            \
}

#endif

