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

#pragma kernel surface_nets_move_point
#pragma kernel surface_nets_move_point_2d

#include "../../Shader/ComputeCommon.cginc"

#include "../../Shader/AutoSmoothFuncs.cginc"
#include "../../Shader/BrushFuncs.cginc"
#include "../../Shader/DualMeshingFuncs.cginc"
#include "../../Shader/GenPointDefs.cginc"
#include "../../Shader/IndirectArgsDefs.cginc"
#include "../../Shader/Math/MathConst.cginc"
#include "../../Shader/MeshingModeDefs.cginc"
#include "../../Shader/NormalFuncs.cginc"
#include "../../Shader/RenderModeDefs.cginc"

// https://0fps.net/2012/07/12/smooth-voxel-terrain-part-2/

float surfaceNetsDualQuadsBlend;
int surfaceNetsBinarySearchIterations;
int surfaceNetsGradientDescentIterations;
float surfaceNetsGradientDescentFactor;

static int aEdgeVertIndex3d[12][2] = 
{
  { 0, 1 }, 
  { 1, 5 }, 
  { 5, 4 }, 
  { 4, 0 }, 
  { 2, 3 }, 
  { 3, 7 }, 
  { 7, 6 }, 
  { 6, 2 }, 
  { 0, 2 }, 
  { 1, 3 }, 
  { 5, 7 }, 
  { 4, 6 }, 
}; 

static int aEdgeVertIndex2d[4][2] = 
{
  { 0, 1 }, 
  { 1, 2 }, 
  { 2, 3 }, 
  { 3, 0 }, 
};

[numthreads(kThreadGroupSize, 1, 1)]
void surface_nets_move_point(int3 id : SV_DispatchThreadID)
{
#if defined(MUDBUN_DISABLE_SURFACE_NETS) || defined(MUDBUN_FAST_ITERATION)
  return;
#endif

  if (id.x >= indirectDrawArgs[0])
    return;

  int iGenPoint = id.x;

  if (surfaceNetsDualQuadsBlend >= 1.0f)
  {
    if (renderMode == kRenderModeQuadSplats)
      aGenPoint[iGenPoint].material.size *= 0.70711f;
    return;
  }

  float h = 0.5f * voxelSize;
  float3 center = aGenPoint[iGenPoint].posNorm.xyz;
  float3 minCornerOffset = -h;
  int iBrushMask = aGenPoint[iGenPoint].iBrushMask;
  
  float3 aCornerOffset[8] = 
  {
    float3(-h, -h, -h), 
    float3( h, -h, -h), 
    float3(-h,  h, -h), 
    float3( h,  h, -h), 
    float3(-h, -h,  h), 
    float3( h, -h,  h), 
    float3(-h,  h,  h), 
    float3( h,  h,  h), 
  };

  float aCornerRes[8];
  SdfBrushMaterial mat;
  [loop] for (int iCorner = 0; iCorner < 8; ++iCorner)
  {
    aCornerRes[iCorner] = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat);
  }

  float3 avgEdgeOffset = 0.0f;
  int numEdges = 0;
  [loop] for (int iEdge = 0; iEdge < 12; ++iEdge)
  {
    int iVert0 = aEdgeVertIndex3d[iEdge][0];
    int iVert1 = aEdgeVertIndex3d[iEdge][1];
    float res0 = aCornerRes[iVert0];
    float res1 = aCornerRes[iVert1];
    if (res0 * res1 > 0)
      continue;

    ++numEdges;

    float3 offset0 = aCornerOffset[iVert0];
    float3 offset1 = aCornerOffset[iVert1];

    float3 edgeOffset;
    if (res0 == 0.0f && res1 == 0.0f)
    {
      edgeOffset = max(offset0, offset1);
    }
    else if (res0 == 0.0f)
    {
      edgeOffset = offset0;
    }
    else if (res1 == 0.0f)
    {
      edgeOffset = offset1;
    }
    else if (surfaceNetsBinarySearchIterations <= 0)
    {
      // lerp approximation
      float t = -res0 / (res1 - res0);
      edgeOffset = lerp(offset0, offset1, t);
    }
    /*
    else
    {
      // binary search
      edgeOffset = 0.5f * (offset0 + offset1);
      [loop] for (int iSearch = 0; iSearch < surfaceNetsBinarySearchIterations; ++iSearch)
      {
        float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat);
        if (res0 * resT < 0.0f)
        {
          res1 = resT;
          offset1 = edgeOffset;
        }
        else if (resT * res1 < 0.0f)
        {
          res0 = resT;
          offset0 = edgeOffset;
        }
        edgeOffset = 0.5f * (offset0 + offset1);
      }
    }
    */

    avgEdgeOffset += edgeOffset;
  }

  if (numEdges <= 0)
    return;

  avgEdgeOffset /= numEdges;
  float3 avgEdgePos = center + avgEdgeOffset;

  // gradient descent
  /*
  if (surfaceNetsGradientDescentIterations > 0)
  {
    float3 n;
    SDF_NORMAL_FULL(n, avgEdgePos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
    [loop] for (int iDescent = 0; iDescent < surfaceNetsGradientDescentIterations; ++iDescent)
    {
      float d = sdf_masked_brushes(avgEdgePos, iBrushMask, mat);
      avgEdgeOffset -= surfaceNetsGradientDescentFactor * n * d;
      avgEdgePos = center + avgEdgeOffset;
    }
  }
  */

  aGenPoint[iGenPoint].posNorm.xyz = lerp(avgEdgePos, aGenPoint[iGenPoint].posNorm.xyz, surfaceNetsDualQuadsBlend);

  if (renderMode == kRenderModeQuadSplats)
    aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * surfaceNetsDualQuadsBlend;
}

[numthreads(kThreadGroupSize, 1, 1)]
void surface_nets_move_point_2d(int3 id : SV_DispatchThreadID)
{
#if defined(MUDBUN_DISABLE_SURFACE_NETS) || defined(MUDBUN_FAST_ITERATION)
  return;
#endif

  if (id.x >= indirectDrawArgs[0])
    return;

  int iGenPoint = id.x;

  if (surfaceNetsDualQuadsBlend >= 1.0f)
  {
    if (renderMode == kRenderModeQuadSplats)
      aGenPoint[iGenPoint].material.size *= 0.70711f;
    return;
  }

  float h = 0.5f * voxelSize;
  float3 center = aGenPoint[iGenPoint].posNorm.xyz;
  float3 minCornerOffset = -h;
  int iBrushMask = aGenPoint[iGenPoint].iBrushMask;
  
  float3 aCornerOffset[4] = 
  {
    float3(-h, -h, 0.0f), 
    float3(-h,  h, 0.0f),
    float3( h,  h, 0.0f),
    float3( h, -h, 0.0f),
  };

  bool anyOutside = false;
  float aCornerRes[4];
  SdfBrushMaterial mat;
  [loop] for (int iCorner = 0; iCorner < 4; ++iCorner)
  {
    float cornerRes = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat);
    aCornerRes[iCorner] = cornerRes;
    if (cornerRes >= 0.0f)
      anyOutside = true;
  }

  if (!anyOutside)
    return;

  float3 avgEdgeOffset = 0.0f;
  int numEdges = 0;
  [loop] for (int iEdge = 0; iEdge < 4; ++iEdge)
  {
    int iVert0 = aEdgeVertIndex2d[iEdge][0];
    int iVert1 = aEdgeVertIndex2d[iEdge][1];
    float res0 = aCornerRes[iVert0];
    float res1 = aCornerRes[iVert1];
    if (res0 * res1 > 0)
      continue;

    ++numEdges;

    float3 offset0 = aCornerOffset[iVert0];
    float3 offset1 = aCornerOffset[iVert1];

    float3 edgeOffset;
    if (res0 == 0.0f && res1 == 0.0f)
    {
      edgeOffset = max(offset0, offset1);
    }
    else if (res0 == 0.0f)
    {
      edgeOffset = offset0;
    }
    else if (res1 == 0.0f)
    {
      edgeOffset = offset1;
    }
    else if (surfaceNetsBinarySearchIterations <= 0)
    {
      // lerp approximation
      float t = -res0 / (res1 - res0);
      edgeOffset = lerp(offset0, offset1, t);
    }
    /*
    else
    {
      // binary search
      edgeOffset = 0.5f * (offset0 + offset1);
      [loop] for (int iSearch = 0; iSearch < surfaceNetsBinarySearchIterations; ++iSearch)
      {
        float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat);
        if (res0 * resT < 0.0f)
        {
          res1 = resT;
          offset1 = edgeOffset;
        }
        else if (resT * res1 < 0.0f)
        {
          res0 = resT;
          offset0 = edgeOffset;
        }
        edgeOffset = 0.5f * (offset0 + offset1);
      }
    }
    */

    avgEdgeOffset += edgeOffset;
  }

  if (numEdges <= 0)
    return;

  avgEdgeOffset /= numEdges;
  float3 avgEdgePos = center + avgEdgeOffset;

  // gradient descent
  /*
  if (surfaceNetsGradientDescentIterations > 0)
  {
    float3 n;
    SDF_NORMAL_2D(n, avgEdgePos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
    [loop] for (int iDescent = 0; iDescent < surfaceNetsGradientDescentIterations; ++iDescent)
    {
      float d = sdf_masked_brushes(avgEdgePos, iBrushMask, mat);
      avgEdgeOffset -= surfaceNetsGradientDescentFactor * n * d;
      avgEdgePos = center + avgEdgeOffset;
    }
  }
  */

  aGenPoint[iGenPoint].posNorm.xyz = lerp(avgEdgePos, aGenPoint[iGenPoint].posNorm.xyz, surfaceNetsDualQuadsBlend);
  aGenPoint[iGenPoint].sdfValue = sdf_masked_brushes(avgEdgePos, iBrushMask, mat) + surfaceShift;

  if (renderMode == kRenderModeQuadSplats)
    aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * surfaceNetsDualQuadsBlend;
}

