#pragma language glsl3
#ifdef VERTEX
 vec4 position(mat4 transformProjection, vec4 vertexPosition) {return transformProjection * vertexPosition; }
#endif
#ifdef PIXEL
#define NUM_CONES 8

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

uniform sampler2D gbuffer_position;
uniform sampler2D gbuffer_normal;

uniform sampler3D vbuffer_light0;
uniform sampler3D vbuffer_light1;
uniform sampler3D vbuffer_light2;
uniform sampler3D vbuffer_light3;
uniform sampler3D vbuffer_light4;
uniform sampler3D vbuffer_light5;
uniform sampler3D vbuffer_light6;

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);
  vec4  accumulator = vec4(0.0);

  vec3 weight = getDirectionWeights(direction);
  maxDistance = min(maxDistance, 1.41421356237);
  float occluded = 0;
  while ((dist < maxDistance) && (accumulator.a < 1)) {
    float diameter = max(voxelVolumeInverseSize * 0.5, doubledAperture * dist);
    float lod = max(0.0, log2((diameter * float(voxelVolumeSize)) ));
    vec4 voxel = mix(
      texture(vbuffer_light0,position),(
        (negativeDirection.x ? textureLod(vbuffer_light4, position, lod-1) : textureLod(vbuffer_light1, position, lod-1)) * weight.x +
        (negativeDirection.y ? textureLod(vbuffer_light5, position, lod-1) : textureLod(vbuffer_light2, position, lod-1)) * weight.y +
        (negativeDirection.z ? textureLod(vbuffer_light6, position, lod-1) : textureLod(vbuffer_light3, position, lod-1)) * weight.z
      ),
     min(lod,1)
    );
    accumulator += (1.0 - accumulator.a) * (voxel);
    dist += max(diameter, voxelVolumeInverseSize);
    position = from + (dist * direction);
  }

  return max(accumulator, vec4(0.0));
}

vec3 indirectSpecularLight(vec3 from,
 vec3 normal,
 vec3 viewDirection,
 float aperture,
 float maxDistance){
 normal = normalize(normal);
 viewDirection = normalize(viewDirection);
 return traceVoxelCone(
    from + (normal * 2.0 * voxelVolumeInverseSize),
    normalize(reflect(viewDirection, normal)),
    aperture,
    2.0 * voxelVolumeInverseSize,
    maxDistance
 ).xyz;
}
vec3 indirectRefractiveLight(vec3 from,
 vec3 normal,
 vec3 viewDirection,
 float aperture,
 float indexOfRefraction,
 float maxDistance){
 normal = normalize(normal);
 viewDirection = normalize(viewDirection);
 return traceVoxelCone(from + (normal * 1.0 * voxelVolumeInverseSize),
 normalize(refract(viewDirection, normal, 1.0 / indexOfRefraction)),
 aperture,
 1.0 * voxelVolumeInverseSize,
 maxDistance).xyz;
} 

vec3 indirectDiffuseLight(vec3 from, vec3 normal) {
  const vec3 coneDirections[8] = vec3[8](
 vec3(0.57735, 0.57735, 0.57735),
 vec3(-0.57735, -0.57735, 0.57735),
 vec3(-0.903007, 0.182696, 0.388844),
 vec3(0.903007, -0.182696, 0.388844),
 vec3(0.388844, -0.903007, 0.182696),
 vec3(-0.388844, 0.903007, 0.182696),
 vec3(-0.182696, 0.388844, 0.903007),
 vec3(0.182696, -0.388844, 0.903007)
 );
 const float coneWeights[8] = float[8](
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0,
 1.0 / 8.0
 );
 const float coneApertures[8] = float[8](
 0.4363325,
 0.4363325,
 0.4363325,
 0.4363325,
 0.4363325,
 0.4363325,
 0.4363325,
 0.4363325
 ); 
  
  // const vec3 coneDirections[5] = vec3[5](
  //   vec3(0.0, 0.0, 1.0), 
  //   vec3(0.0, 0.707106781, 0.707106781),
  //   vec3(0.0, -0.707106781, 0.707106781), 
  //   vec3(0.707106781, 0.0, 0.707106781),
  //   vec3(-0.707106781, 0.0, 0.707106781)
  // );
  // const float coneWeights[5] = float[5](0.4, 0.15, 0.15, 0.15, 0.15);
  // const float coneApertures[5] = float[5](/* tan(45) */ 1,1,1,1,1);

  const float maxDistance = 2.0;
  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) *100.0)).x) * 
  mat3(tangent, bitangent, normal);
  
  vec4 color = vec4(0.0);


  for (int i = 0; i < NUM_CONES; i++) {
    vec3 direction = tangentSpace * coneDirections[i];
     // if(dot(direction, tangentSpace[2]) >= 0.0) {
      color += vec4(
        traceVoxelCone(
          coneOrigin + (coneOffset * direction), 
          direction,
          coneApertures[i], 
          offset, 
          maxDistance
        ).xyz,
        1
      ) * 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 normal = normalize(Texel(gbuffer_normal, coord).xyz*2-vec3(1));

  vec4 texel = Texel(tex, textureCoords);
  if (texel.a==0){
    discard;
  }
  tc = coord;

  vec3 from = (position.xyz-volumePosition)/(volumeScale*2)+0.5;
  vec3 eye = (eyePosition-volumePosition)/(volumeScale*2)+0.5;
  vec4 specular = vec4(indirectSpecularLight(from, normal, from-eye, 0.01, 2),1);
  vec4 result = specular+texel;
  // vec4 result = vec4(Texel(gbuffer_normal, coord).xyz*2-vec3(1),1);
  // vec4 result = vec4(indirectRefractiveLight(from, normal, position.xyz-eyePosition, 0.01,1,2),1);
  // vec4 result =vec4(indirectDiffuseLight(((position.xyz-volumePosition)/(volumeScale*2)+0.5),normal),1);

  return result;
}

#endif
