

layout(binding=0, rgba16ui)
#ifdef LOGIC_MODIFIER
  uniform uimage3D outputData;
#else
  uniform writeonly uimage3D outputData;
#endif
  
layout(local_size_x = LOCAL_X_MAX, local_size_y = LOCAL_Y_MAX, local_size_z = LOCAL_Z_MAX) in;



shared bool bit_local [4*4*4];


/////////////////////////////////////////////////////////////////////////////
// main
//

void main()
{

  // Produces one output texel.
  
  uvec3
    localGroupSize = uvec3 (LOCAL_X_MAX, LOCAL_Y_MAX, LOCAL_Z_MAX);
 
  uint
    uID = gl_WorkGroupID.x;

  // XYZ group to be extracted from uID
  uvec3
    workgroupSize = uvec3 (GROUP_ONE_X_MAX, GROUP_ONE_Y_MAX, GROUP_ONE_Z_MAX);

  uint
    uCube = gl_WorkGroupID.y;

  uint
    u1;

  uint
    u2;

  if (uCube % 2 == 0)
  {
    u1 = coordinateSetValue[uCube/2].x;
    u2  = coordinateSetValue[uCube/2].y;
  }
  else
  {
    u1 = coordinateSetValue[uCube/2].z;
    u2  = coordinateSetValue[uCube/2].w;
  }

  /*
    Following bit packing magic detailed and also run in HueVirtualTextureSpaceLeaping.cpp.
  */

  uvec3
    uvecInMin = uvec3 ( (u1 >> 0u) & ((1u << 9u) - 1u), (u1 >> 9u) & ((1u << 9u) - 1u),  (u1 >> (9u + 9u)) & ((1u << 9u) - 1u) );

  uint
    uLOD = (u1 >> (9u + 9u + 9u)) &  ((1u << 4u) - 1u);

  uint
    uEdgeX = (u1 >> (9u + 9u + 9u  + 4u)) & ((1u << 1u) - 1u);
  
  uint
    uEdgeYZ = (u2 >> 30u)  & (( 1u << 2u) -1u);

  uint
    uEdge = uEdgeX | (uEdgeYZ << 1u);

  uvec3
    uvecOutMin = uvec3 ( (u2 >> 0u) & (( 1 << 10u) -1u), (u2 >> 10u)  & (( 1u << 10u) -1u), (u2 >> 20u)  & (( 1u << 10u) - 1u) );

  uvec3                           
    uvecCornerSize = uvec3 (cornerTileSize[uLOD].x, cornerTileSize[uLOD].y, cornerTileSize[uLOD].z); 

  bool
    isEdgeX = (uEdge & 0x01) != 0,
    isEdgeY = (uEdge & 0x02) != 0,
    isEdgeZ = (uEdge & 0x04) != 0;

  uvec3                           
    uvecInSize = uvec3 (isEdgeX ? uvecCornerSize.x : uSubTileSize, isEdgeY ? uvecCornerSize.y : uSubTileSize, isEdgeZ ? uvecCornerSize.z : uSubTileSize);

  uvec3
    cubeInMin = uSubTileSize * uvecInMin,
    cubeInMax = cubeInMin + uvecInSize;

  uvec3
    cubeOutMin = uSpaceLeapingTileSize * uvecOutMin;

  uint
    uBitCubeSize = uSubTileSize / (4u * uSpaceLeapingTileSize);


  // id => xyz on output space leaping cube
  uint
    uXOut =  uID % workgroupSize.x,
	  uYOut = (uID /workgroupSize.x) % workgroupSize.y,
	  uZOut = (uID /workgroupSize.x/ workgroupSize.y) % workgroupSize.z;

  ivec3
    cubeOutOffset = ivec3 (uXOut, uYOut, uZOut);

  ivec3
    cubeOut = ivec3 (cubeOutMin.x, cubeOutMin.y, cubeOutMin.z) + cubeOutOffset;
  
  uvec3
    cubeInMinThisOutput = cubeInMin + (4u * uBitCubeSize) * cubeOutOffset;

  uint
    i = gl_LocalInvocationID.x,
  	j = gl_LocalInvocationID.y,
  	k = gl_LocalInvocationID.z;

  uvec3
    cCornerTexelInMinThisThread = cubeInMinThisOutput + uvec3 (uBitCubeSize * i, uBitCubeSize * j, uBitCubeSize * k);

  int
    border = 1;

  ivec3
    iMin = ivec3 ( int (cCornerTexelInMinThisThread.x) - border, int (cCornerTexelInMinThisThread.y) - border, int (cCornerTexelInMinThisThread.z) - border);

  ivec3   
	iMax = ivec3 (iMin.x + uBitCubeSize +  2 * border, iMin.y + uBitCubeSize +  2 * border, iMin.z + uBitCubeSize +  2 * border);

  iMin.x = max(iMin.x, int(cubeInMin.x));
  iMax.x = min(iMax.x, int(cubeInMax.x));

  iMin.y = max(iMin.y, int(cubeInMin.y));
  iMax.y = min(iMax.y, int(cubeInMax.y));

  iMin.z = max(iMin.z, int(cubeInMin.z));
  iMax.z = min(iMax.z, int(cubeInMax.z));

  uint
    bit_offset = k * 4u * 4u + j * 4u + i;

  bit_local[bit_offset] = IsInvisible(iMin, iMax);

  barrier();

  // Gather set bit_local into the singel 4x4x4 texel using a single thread per workgroup

  if (i == 0 && j == 0 && k == 0)
  {
	uint
	  vec[4];

	vec[0] = 0;
	vec[1] = 0;
	vec[2] = 0;
	vec[3] = 0;

	for (k = 0u; k < 4u; k++)
	{
	  for (j = 0u; j < 4u; j++)
	  {
		for (i = 0u; i < 4u; i++)
		{
		  uint
		    ibitindex = k * 4u * 4u + j * 4u + i;

          if (bit_local[ibitindex])
		  {
			uint
			  thisbit = 4u * j + i;

		    uint
			  mask = 1u << thisbit;

            vec[k] = vec[k] | mask;
		  }
		}
	  }
    }

    uvec4
	  thisInvisible = uvec4 (vec[0], vec[1], vec[2], vec[3]);

#ifdef LOGIC_MODIFIER

    uvec4
      readInvisible = imageLoad(outputData, cubeOut);

    uvec4
      readVisible = ~readInvisible,
      thisVisible = ~thisInvisible;
  
    thisVisible = thisVisible LOGIC_MODIFIER readVisible;

    thisInvisible = ~thisVisible;

#endif
	
	imageStore(outputData, cubeOut, thisInvisible);
  }
}

