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

#pragma kernel clear_voxel_hash_table
#pragma kernel clear_auto_smooth_vert_data_table
#pragma kernel clear_voxel_cache
#pragma kernel register_top_nodes
#pragma kernel update_branching_indirect_dispatch_args
#pragma kernel allocate_child_nodes
#pragma kernel update_voxel_indirect_dispatch_args

// yeah...I know
#pragma warning(disable: 4714) // //Shader warning in 'VoxelGen.compute': Program 'allocate_child_nodes', warning X4714 : sum of temp registers and indexable temp registers times 64 threads exceeds the recommended total 16384. Performance may be reduced at kernel allocate_child_nodes(on d3d11)

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

#include "../../Shader/AabbTreeFuncs.cginc"
#include "../../Shader/AutoSmoothDefs.cginc"
#include "../../Shader/BrushFuncs.cginc"
#include "../../Shader/GenPointDefs.cginc"
#include "../../Shader/IndirectArgsDefs.cginc"
#include "../../Shader/Math/MathConst.cginc"
#include "../../Shader/VoxelFuncs.cginc"
#include "../../Shader/VoxelCacheFuncs.cginc"
#include "../../Shader/VoxelHashFuncs.cginc"
#include "../../Shader/VoxelModeDefs.cginc"

[numthreads(kClearThreadGroupSize, 1, 1)]
void clear_voxel_hash_table(int id : SV_DispatchThreadID)
{
  if (id.x >= nodeHashTableSize)
    return;

  nodeHashTable[id.x] = init_voxel_hash_entry();
}

[numthreads(kClearThreadGroupSize, 1, 1)]
void clear_auto_smooth_vert_data_table(int id : SV_DispatchThreadID)
{
  if (id.x >= autoSmoothVertDataPoolSize)
    return;

  autoSmoothVertDataTable[id.x].id = kNullAutoSmoothCacheId;
  autoSmoothVertDataTable[id.x].numNormals = 0;
}

[numthreads(kClearThreadGroupSize, 1, 1)]
void clear_voxel_cache(int id : SV_DispatchThreadID)
{
  if (id.x >= int(voxelCacheSize))
    return;

  voxelCacheIdTable[id.x] = kNullVoxelCacheId;
  voxelCache[id.x].data = kFltMax;
}

[numthreads(kThreadGroupSize, 1, 1)]
void register_top_nodes(int3 id : SV_DispatchThreadID)
{
  int iBrush = id.x;
  if (iBrush >= numBrushes)
    return;

  register_brush_aabb(iBrush);
}

[numthreads(1, 1, 1)]
void update_branching_indirect_dispatch_args(int3 id : SV_DispatchThreadID)
{
  indirectDispatchArgs[0] = 
    max
    (
      1, 
      uint
      (
        min
        (
          nodePoolSize, 
          aNumNodesAllocated[currentNodeDepth + 1]
        ) 
        * (enable2dMode ? (currentNodeBranchingFactor * currentNodeBranchingFactor) : (currentNodeBranchingFactor * currentNodeBranchingFactor * currentNodeBranchingFactor))
        + kThreadGroupSize - 1
      ) / kThreadGroupSize
    );
}

[numthreads(kThreadGroupSize, 1, 1)]
void allocate_child_nodes(uint3 id : SV_DispatchThreadID)
{
  uint f = currentNodeBranchingFactor;
  uint ff = f * f;
  uint fff = ff * f;
  uint iNode = uint(id.x) / (enable2dMode ? ff : fff);
  if (iNode >= uint(aNumNodesAllocated[currentNodeDepth + 1]))
    return;

  for (int i = 1; i <= currentNodeDepth; ++i)
    iNode += aNumNodesAllocated[i];
  if (iNode >= nodePoolSize)
    return;

  uint3 childNodeCoord = (id.x / uint3(1, f, ff)) % f;
  float childSize = currentNodeSize / currentNodeBranchingFactor;
  float3 childCenter = nodePool[iNode].center - ((f / 2) - 0.5f - childNodeCoord) * childSize;
  if (enable2dMode)
    childCenter.z = 0.0f;
  float childDiag = (enable2dMode ? 1.415f : 1.733f) * childSize;
  float halfChildDiag = 0.5f * childDiag;

  SdfBrushMaterial mat;
  float d = sdf_masked_brushes(childCenter, get_brush_mask_index(iNode), mat, false, halfChildDiag);
  if (d == kCull)
    return;

  // ray-traced voxel?
  if (renderMode == kRenderModeRayTracedVoxels 
    && currentNodeDepth == maxNodeDepth - 1)
  {
    if (d > 0.0f)
      return;

    switch (rayTracedVoxelPaddingMode)
    {
      case kVoxelPaddingModeNone:
        if (d < -childDiag)
          return;
        break;

      case kVoxelPaddingModeByDistance:
        if (d < -childDiag - rayTracedVoxelInternalPaddingDistance)
          return;
        break;

      case kVoxelPaddingModeFull:
        break;
    }

    int iChildNode = -1;
    iChildNode = register_alloc_child_node(childCenter, childSize, currentNodeDepth + 1, iNode, childNodeCoord);

    if (iChildNode < 0)
      return;

    float halfChildSize = 0.5f * childSize;
    Aabb childAabb = make_aabb(childCenter - halfChildSize, childCenter + halfChildSize);
    nodePool[iChildNode].iBrushMask = allocate_node_brush_mask(iChildNode, childAabb);

    aGenPoint[iChildNode].sdfValue = min(0.0f, d);
    aGenPoint[iChildNode].material = pack_material(mat);
    aGenPoint[iChildNode].iBrushMask = get_brush_mask_index(iChildNode);

    switch (rayTracedVoxelMode)
    {
    case kVoxelModeFlatCubes:
    case kVoxelModeFlatSpheres:
      {
        float3 n;
        SDF_NORMAL(n, nodePool[iChildNode].center, sdf_masked_brushes, aGenPoint[iChildNode].iBrushMask, 1e-2f * voxelNodeSizes[3]);
        aGenPoint[iChildNode].posNorm.w = pack_normal(n);
      }
      break;
    }

    return;
  }

  if (d > halfChildDiag || (d < -childDiag && !enable2dMode))
  {
    // could deviation from round/chamfer/pipe/engrave/distortion/modifier possibly bring solid surface close to voxel?
    bool deviationClose = false;

    bool bumpToleranceByOneStep = false;
    FOR_EACH_BRUSH(get_brush_mask_index(iNode), 
      switch (aBrush[iBrush].op)
      {
        case kSdfUnionRound:
        case kSdfUnionChamfer:
        case kSdfSubtractRound:
        case kSdfSubtractChamfer:
        case kSdfIntersectRound:
        case kSdfIntersectChamfer:
        case kSdfPipe:
        case kSdfEngrave:
        {
          bumpToleranceByOneStep = true;
          break;
        }
        case kSdfDistort:
        case kSdfModify:
        {
          float deviation = aBrush[iBrush].blend;
          float res = sdf_distortion_modifier_bounds_query(childCenter, aBrush[iBrush]);
          if (res <= childDiag 
              && abs(d) - deviation <= childDiag)
          {
            deviationClose = true;
          }

          break;
        }
      }
      if (deviationClose)
        break;
    );

    if (!deviationClose 
        && bumpToleranceByOneStep 
        && abs(d) - 0.5f * voxelSize <= halfChildDiag)
    {
      deviationClose = true;
    }

    if (!deviationClose)
      return;
  }

  int iChildNode = -1;
  if (renderMode != kRenderModeRayTracedVoxels)
  {
    // fast
    iChildNode = allocate_node(childCenter, currentNodeDepth + 1, iNode, int(id.x));
  }
  else
  {
    // slightly slower, used when per-voxel spatial hash is needed (e.g. voxel raytracing)
    iChildNode = register_alloc_child_node(childCenter, childSize, currentNodeDepth + 1, iNode, childNodeCoord);
  }

  if (iChildNode < 0)
    return;

  float halfChildSize = 0.5f * childSize;
  Aabb childAabb = make_aabb(childCenter - halfChildSize, childCenter + halfChildSize);
  nodePool[iChildNode].iBrushMask = allocate_node_brush_mask(iChildNode, childAabb);
}

[numthreads(1, 1, 1)]
void update_voxel_indirect_dispatch_args(int3 id : SV_DispatchThreadID)
{
  indirectDispatchArgs[0] = 
    max
    (
      1, 
      uint
      (
        min
        (
          nodePoolSize, 
          aNumNodesAllocated[currentNodeDepth + 1]
        ) 
        + kThreadGroupSize - 1
      ) / kThreadGroupSize
    );
}

