#pragma language glsl3
#ifdef VERTEX
 vec4 position(mat4 transformProjection, vec4 vertexPosition) {return transformProjection * vertexPosition; }
#endif
#ifdef PIXEL
#define NUM_CONES 16
uniform sampler2D gbuffer_position;
uniform sampler2D gbuffer_normal;

uniform vec3 volumePosition = vec3(0);
uniform vec3 volumeScale;

uniform sampler3D vbuffer_occlusion;

const int voxelVolumeBits = 7;
uniform int voxelVolumeSize = 128;
uniform float voxelVolumeInverseSize; // Size of a voxel

vec2 tc;
vec3 safeNormalize(vec3 n) {
  float l = max(length(n), 1e-6);
  n /= l;
  return n;
}
vec4 voxelJitterNoise(vec4 p4) {
  p4 = fract(p4 * vec4(443.897, 441.423, 437.195, 444.129));
  p4 += dot(p4, p4.wzxy + vec4(19.19));
  return fract((p4.xxyz + p4.yzzw) * p4.zywx);
}
mat3 vctRotationMatrix(vec3 axis, float angle) {
  axis = normalize(axis);
  float s = sin(angle), c = cos(angle), oc = 1.0 - c;
  vec3 as = axis * s;
  return (mat3(axis.x * axis, axis.y * axis, axis.z * axis) * oc) +
         mat3(c, -as.z, as.y, as.z, c, -as.x, -as.y, as.x, c);
}

vec3 getDirectionWeights(vec3 direction) { return direction * direction; }

vec4 traceVoxelCone(vec3 from, vec3 direction, float aperture, float offset, float maxDistance) {
  direction = normalize(direction);
  
  bvec3 negativeDirection = lessThan(direction, vec3(0.0));
  float doubledAperture = max(voxelVolumeInverseSize, 2.0 * aperture);
  float dist = offset;
  vec3  directionWeights = getDirectionWeights(direction);
  vec3  position = from + (dist * direction);
  float  accumulator = 0;

  maxDistance = min(maxDistance, 1.41421356237);
  while ((dist < maxDistance) && (accumulator < 1.0)) {
    float diameter =max(voxelVolumeInverseSize * 0.5, doubledAperture * dist);
    float lod = max(0.0, log2((diameter * float(voxelVolumeSize))  + 0.0));
    float voxel = textureLod(vbuffer_occlusion, position, lod).r;
    accumulator += (1.0 - accumulator) * voxel ;
    dist += max(diameter, voxelVolumeInverseSize) ;
    position = from + (dist * direction);
  }
  return max(vec4(vec3(1-accumulator),1), vec4(0.0));
}

vec3 indirectDiffuseLight(vec3 from, vec3 normal) {

  const vec3 coneDirections[16] = vec3[16](
 vec3(0.898904, 0.435512, 0.0479745),
 vec3(0.898904, -0.0479745, 0.435512),
 vec3(-0.898904, -0.435512, 0.0479745),
 vec3(-0.898904, 0.0479745, 0.435512),
 vec3(0.0479745, 0.898904, 0.435512),
 vec3(-0.435512, 0.898904, 0.0479745),
 vec3(-0.0479745, -0.898904, 0.435512),
 vec3(0.435512, -0.898904, 0.0479745),
 vec3(0.435512, 0.0479745, 0.898904),
 vec3(-0.435512, -0.0479745, 0.898904),
 vec3(0.0479745, -0.435512, 0.898904),
 vec3(-0.0479745, 0.435512, 0.898904),
 vec3(0.57735, 0.57735, 0.57735),
 vec3(0.57735, -0.57735, 0.57735),
 vec3(-0.57735, 0.57735, 0.57735),
 vec3(-0.57735, -0.57735, 0.57735)
 );
 const float coneWeights[16] = float[16](1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0,1.0 / 16.0
 );
 const float coneApertures[16] = float[16](0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595,0.3141595
 ); 
  const float maxDistance = 0.2;
  float coneOffset = -0.01, offset = 4.0 * voxelVolumeInverseSize;

  normal = normalize(normal);  

  vec3 normalOffset =
      normal * (1.0 + (4.0 * 0.70710678118)) * voxelVolumeInverseSize,
      coneOrigin = from + normalOffset,
      t0 = cross(vec3(0.0, 1.0, 0.0), normal),
      t1 = cross(vec3(0.0, 0.0, 1.0), normal),
      tangent = normalize((length(t0) < length(t1)) ? t1 : t0),
      bitangent = normalize(cross(tangent, normal));

  tangent = safeNormalize(cross(bitangent, normal));
  mat3 tangentSpace = 
  // vctRotationMatrix(normal, voxelJitterNoise(vec4(from + normal + (vec3(gl_FragCoord.xyz) * 1.0), fract(tc.x * 0.01) *0.01)).x) * 
  mat3(tangent, bitangent, normal);
  
  vec4 color = vec4(0.0);

  for (int i = 0; i < NUM_CONES; i++) {
    vec3 direction = tangentSpace * coneDirections[i].xyz;
     // if(dot(direction, tangentSpace[2]) >= 0.0) {
      color += vec4(traceVoxelCone(
        coneOrigin + (coneOffset * direction), 
        direction,
        coneApertures[i], 
        offset, 
        maxDistance).xyz,
        1.0
      ) * coneWeights[i];
    // }
  }
  return color.xyz / max(color.w, 1e-6);
}

vec4 effect(vec4 color, sampler2D tex, vec2 textureCoords, vec2 screen_coords) {
  vec2 coord = screen_coords / love_ScreenSize.xy;
  vec4 position = Texel(gbuffer_position, coord);
  vec3 pos = ((position.xyz-volumePosition)/(volumeScale*2)+0.5);
  vec3 normal = (Texel(gbuffer_normal, coord).xyz*2-vec3(1));
  vec4 texel = Texel(tex, textureCoords);
  tc = coord;

  vec4 result = vec4(indirectDiffuseLight(pos,normal),1);
  
  return result;
}

#endif
