#version 430 core
// Materials: 19
#define mat_face_details 0
// color: RGBA(0.528, 0.000, 0.000, 1.000)
#define mat_face 1
// color: RGBA(0.000, 0.956, 1.000, 1.000)
#define mat_gold 2
// color: RGBA(1.000, 0.552, 0.000, 1.000)
#define mat_nefertiti_cap 3
// color: RGBA(0.297, 0.423, 1.000, 1.000)
#define mat_japotek 4
// color: RGBA(0.608, 0.951, 1.000, 1.000)
#define mat_skids 5
// color: RGBA(0.996, 1.000, 0.788, 1.000)
#define mat_room 6
// color: RGBA(0.736, 0.736, 0.736, 1.000)
#define mat_chess_blacks 7
// color: RGBA(0.172, 0.242, 0.302, 1.000)
#define mat_chess_whites 8
// color: RGBA(0.623, 0.623, 0.623, 1.000)
#define mat_chess_border 9
// color: RGBA(0.557, 0.190, 0.000, 1.000)
#define mat_whites 10
// color: RGBA(0.741, 1.000, 0.747, 1.000)
#define mat_blacks 11
// color: RGBA(0.763, 0.703, 1.000, 1.000)
#define mat_duck_eye 12
// color: RGBA(0.236, 0.236, 0.236, 1.000)
#define mat_duck 13
// color: RGBA(0.873, 0.877, 0.000, 1.000)
#define mat_chopper 14
// color: RGBA(0.000, 0.388, 1.000, 1.000)
#define mat_rotors 15
// color: RGBA(1.000, 1.000, 1.000, 1.000)
#define mat_catgold 16
// color: RGBA(1.000, 0.679, 0.000, 1.000)
#define mat_RingBase 17
// color: RGBA(1.000, 1.000, 1.000, 1.000)
#define mat_RingSpec 18
// color: RGBA(0.278, 0.982, 1.000, 1.000)

#define SCENE_TEX_DIM 1024
#define MAX_MESHES 32
#define MAX_LIGHTS 4

out vec4 FragColor;

layout (location=0) uniform vec4 uniData;

layout (location=1) uniform sampler2D iChannel0;
layout (location=2) uniform sampler2D iChannel1;
layout (location=3) uniform sampler2D iChannel2;

uniform sampler2D scene_data;
uniform ivec4 scene_part;
uniform ivec4 meshes_info[MAX_MESHES]; // mesh info
uniform ivec4 meshes_surface[MAX_MESHES]; // mesh surface
uniform mat4  meshes_tx[MAX_MESHES]; // mesh info
uniform mat4  meshes_itx[MAX_MESHES]; // mesh info
uniform mat4 camera;
uniform vec4 light_positions[MAX_LIGHTS];
uniform vec4 light_colors[MAX_LIGHTS];
uniform vec4 flash;



struct Ray {
    vec3 origin;
    vec3 direction;
};

struct Hit {
    int triangle;
    float depth;
    vec2 uv;
//    vec3 norm;
};

struct Surface {
    vec2 uv;
    vec3 norm;
    vec4 color;
    int material;
};

  float packColor(vec3 c)
  {
      return dot(c * 255.0, vec3(65536.0, 256.0, 1.0))/float(1<<24);
  }
  float packNormal(vec3 c)
  {
      c = (c  + 1.0)/2.0; 
      return packColor(c);
      //return dot(c*255.0  , float3(65025.0, 256.0, 1.0)) / (float)(65025.0* 255.0);
  }
     float packUV(vec2 uv)
    {
        return dot(uv* 65536.0, vec2(65536.0, 1.0))/float(1<<32);  
    }
  vec4 unpackColor(float p)
  {
      return fract(vec4(1.0, 256.0, 65536.0,16777216.0) * p);
  }
  vec3 unpackVec3(float p)
  {
      return fract(vec3(1.0, 256.0, 65536.0) * p);
  }
  vec2 unpackUV(float p)
  {
      return fract(vec2(1.0, 65536.0) * p);
  }



  vec3 unpackNormal(float p)
  {
    vec3 r = fract(vec3(1.0, 256.0, 65536.0) * p);
      //vec3 r = fract(vec3(1.0, 256.0, 65025.0) * p);
  //    vec3 r = unpackVec3(p);
      return  ((r * 2.0) - 1.0);
  } 

#define multx(m,p) (m*vec4(p,1.0)).xyz
// buffer di vertici come texture di vec4
// buffer di indici ai vertici come ivec4 

vec3 transformPoint(mat4 m, vec3 p) {
  return (m*vec4(p, 1.0)).xyz;
}

vec3 transformDirection(mat4 m, vec3 n) {
  return (m*vec4(n, 0.0)).xyz;
}

Ray transform(mat4 m, Ray r) {
  return Ray(transformPoint(m, r.origin), transformDirection(m, r.direction));
}

int mesh_index;

#define mesh_info meshes_info[mesh_index]
#define vertex_offset mesh_info.x
#define face_offset mesh_info.y
#define bvh_offset mesh_info.z
#define triangle_count mesh_info.w

#define mesh_surface meshes_surface[mesh_index]
#define normals_offset mesh_surface.x
#define uvs_offset mesh_surface.y

vec3 fetchVertex(int vertexIndex)
{
    int pixelIndex = vertex_offset + vertexIndex; // Each vertex uses 4 components (vec4)
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    vec4 vertexData = texelFetch(scene_data, ivec2(x, y), 0);
    return vertexData.xyz; // Assuming the vertex position is stored in the RGB components
}


ivec4 fetchTriangleIndices(int triangleIndex)
{
    int pixelIndex = face_offset + triangleIndex; // Each triangle uses 4 components (ivec4)
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    ivec4 triangleData = ivec4(texelFetch(scene_data, ivec2(x, y), 0));
    return triangleData; // Assuming the triangle vertex indices are stored in the RGBA components
}


struct BVHNode {
    vec3 min;
    int leftBVHIndex;
    vec3 max;
    int splitTriangleIndex;
};

vec4 fetchNormals(int uvIndex)
{
    int pixelIndex = normals_offset + uvIndex; // Each UV uses 4 components (vec4)
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    return  texelFetch(scene_data, ivec2(x, y), 0);
}


vec4 fetchUVs(int uvIndex)
{
    int pixelIndex = uvs_offset + uvIndex; // Each UV uses 4 components (vec4)
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    return  texelFetch(scene_data, ivec2(x, y), 0);
}

BVHNode fetchBVHNode(int inx)
{
    int pixelIndex = bvh_offset + inx * 2; // Each BVHNode uses 2 pixels
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    vec4 pixel0 = texelFetch(scene_data, ivec2(x, y), 0);
    vec4 pixel1 = texelFetch(scene_data, ivec2(x + 1, y), 0);

    BVHNode node;
    node.min = pixel0.xyz;
    node.leftBVHIndex = int(pixel0.w);
    node.max = pixel1.xyz;
    node.splitTriangleIndex = int(pixel1.w);
    return node; 
}


vec3 fetchVertexNormal(int vertexIndex)
{
    int pixelIndex = vertex_offset + vertexIndex; // Each vertex uses 4 components (vec4)
    int y = pixelIndex / SCENE_TEX_DIM;
    int x = pixelIndex % SCENE_TEX_DIM;
    vec4 vertexData = texelFetch(scene_data, ivec2(x, y), 0);
    return unpackNormal(vertexData.w); // Assuming the vertex normal is stored in the .w component
}

vec3 fetchTriangleNormal(int triangleIndex)
{
   return unpackNormal(fetchNormals( triangleIndex).w);
}

vec3 fetchSurfaceNormal(Hit h)
{
    vec4 t=fetchNormals( h.triangle);
    vec3 n1 = unpackNormal(t.x); // normal for p1
    vec3 n2 = unpackNormal(t.y); // normal for p2
    vec3 n3 = unpackNormal(t.z); // normal for p3
    return (((n2-n1)*h.uv.x + (n3-n1)*h.uv.y) + n1);
}

Surface fetchSurface(Hit h)
{
    vec4 t=fetchNormals( h.triangle);
    vec4 uv=fetchUVs( h.triangle);

    // unify texture & normal fetch, so maybe we can optimize them later    
    // fetch t1, n1, t2, n2, t3, n3 from triangleIndex = h.triangle
    vec2 t1 = unpackUV(uv.x); // uv for p1
    vec3 n1 = unpackNormal(t.x); // normal for p1
    vec2 t2 = unpackUV(uv.y); // uv for p2
    vec3 n2 = unpackNormal(t.y); // normal for p2
    vec2 t3 = unpackUV(uv.z); // uv for p3
    vec3 n3 = unpackNormal(t.z); // normal for p3
    int material = int(t.w);
    return Surface(
      ((t2-t1)*h.uv.x + (t3-t1)*h.uv.y) + t1,
      (((n2-n1)*h.uv.x + (n3-n1)*h.uv.y) + n1),
      vec4(unpackVec3(uv.w),1.0),
      material ); 

}

//==============================================================================

vec3 intersectTriangle(Ray ray, vec3 p, vec3 e1, vec3 e2)
{
    // vec3 v1v0 = v1-v0, v2v0 = v2-v0, rov0 = ro-v0;
    // vec3  n = cross( v1v0, v2v0 );
    // vec3  q = cross( rov0, rd );
    // float d = 1.0/dot(  n, rd );
    // float u =   d*dot( -q, v2v0 );
    // float v =   d*dot(  q, v1v0 );
    // float t =   d*dot( -n, rov0 );
 
    // return (u<0.0 || v<0.0 || (u+v)>1.0) ? vec3(-1) : vec3( u, v, t );


    vec3 h = cross(ray.direction, e2);
    float a = dot(e1 , h);
    if (a > 0.00001)
    {
        vec3 s = ray.origin - p;
        float u = dot(s , h);
        if (u >= 0 && u <= a)
        {
            vec3 q = cross(s, e1);
            float v = dot(ray.direction, q);
            if (v >= 0 && u + v <= a)
            {
                float t = dot(e2, q);
                if (t > 0)
                {
                    a = 1.0f/a;
                    return vec3(t, u, v)*a;
                }
            }
        }
    }
    return vec3(-1.0f, 0.0f, 0.0f);
}



Hit intersectTriangles(Ray ray, int begin, int end, float previousIntersection, int skip_triangle_id, bool reverse) 
{
    Hit bestHit = Hit(-1, previousIntersection, vec2(0.0f, 0.0f));
    for (int i = begin; i < end; ++i) 
    {
        if (i != skip_triangle_id) {
            ivec4 triangle = fetchTriangleIndices(i);
            vec3 p = fetchVertex(triangle.x);
            vec3 e1 = fetchVertex(triangle.y) - p;
            vec3 e2 = fetchVertex(triangle.z) - p;
            vec3 d = reverse?intersectTriangle(ray, p, e2, e1):intersectTriangle(ray, p, e1, e2);
            
            if (d.x >= 0 && d.x < bestHit.depth)
            {
                bestHit.triangle = i;
                bestHit.depth = d.x;
                bestHit.uv = reverse?d.zy:d.yz;
              //  bestHit.norm = normalize(cross(e1, e2));
               

            }
        }
    }
    return bestHit;
}

// from Inigo Quilez articles...
bool intersectsAABB(Ray ray, vec3 _min, vec3 _max, float previousIntersection) 
{
    vec3 boxSize = 0.5 * (_max - _min);
    vec3 center = 0.5 * (_max + _min);
    vec3 m = 1.0/ray.direction;
    vec3 n = m*(ray.origin - center);

    vec3 k = abs(m)*boxSize;
    vec3 t1 = -n - k;
    vec3 t2 = -n + k;
    float tN = max(max(t1.x, t1.y), max(t1.z, 0.0f));
    float tF = min(min(t2.x, t2.y), min(t2.z, previousIntersection));
    return (tN<=tF); // Trust me
}

Hit intersectBVH(Ray ray, int skip_triangle_id, bool reverse) 
{
  Hit closestHit = Hit(-1, 3.402823466e+38, vec2(0.0f, 0.0f));
  
  if (triangle_count < 0) closestHit;
  const int MAX_STACK_SIZE = 64;
  int stack[MAX_STACK_SIZE];
  int stackBegin[MAX_STACK_SIZE];
  int stackEnd[MAX_STACK_SIZE];
  int stackPtr = 0;
  
  stack[stackPtr] = 0;
  stackBegin[stackPtr] = 0;
  stackEnd[stackPtr] = triangle_count;
  
  while (stackPtr >= 0)
  {
    int currentIndex = stack[stackPtr];
    int currentBegin = stackBegin[stackPtr];
    int currentEnd = stackEnd[stackPtr];
    
    BVHNode bvhNode = fetchBVHNode(currentIndex);
    if (intersectsAABB(ray, bvhNode.min, bvhNode.max, closestHit.depth))
    {
      if (bvhNode.leftBVHIndex == 0)
      {
        // Leaf node
        Hit intersection = intersectTriangles(ray, currentBegin, currentEnd, closestHit.depth, skip_triangle_id, reverse);
        if (intersection.triangle >= 0) 
        {
          closestHit = intersection;
        }
      }
      else
      {
        // Internal node - push right child first, then left (so left is processed first)
        if (stackPtr < MAX_STACK_SIZE)
        {
          stack[stackPtr] = bvhNode.leftBVHIndex + 1;
          stackBegin[stackPtr] = bvhNode.splitTriangleIndex;
          stackEnd[stackPtr] = currentEnd;
          stackPtr++;
        }
        if (stackPtr < MAX_STACK_SIZE)
        {
          stack[stackPtr] = bvhNode.leftBVHIndex;
          stackBegin[stackPtr] = currentBegin;
          stackEnd[stackPtr] = bvhNode.splitTriangleIndex;
          stackPtr++;
        }
      }
    }
    stackPtr--;
  }
  
  return closestHit;
}

bool intersectBVHShadow(Ray ray, int skip_triangle_id) 
{
  Hit closestHit = Hit(-1, 3.402823466e+38, vec2(0.0f, 0.0f));
  
  if (triangle_count < 0) closestHit;
  const int MAX_STACK_SIZE = 64;
  int stack[MAX_STACK_SIZE];
  int stackBegin[MAX_STACK_SIZE];
  int stackEnd[MAX_STACK_SIZE];
  int stackPtr = 0;
  
  stack[stackPtr] = 0;
  stackBegin[stackPtr] = 0;
  stackEnd[stackPtr] = triangle_count;
  
  while (stackPtr >= 0)
  {
    int currentIndex = stack[stackPtr];
    int currentBegin = stackBegin[stackPtr];
    int currentEnd = stackEnd[stackPtr];
    
    BVHNode bvhNode = fetchBVHNode(currentIndex);
    if (intersectsAABB(ray, bvhNode.min, bvhNode.max, 1.0f))
    {
      if (bvhNode.leftBVHIndex == 0)
      {
        // Leaf node
        Hit intersection = intersectTriangles(ray, currentBegin, currentEnd, 1.0f, skip_triangle_id, false);
        if (intersection.triangle >= 0) 
        {
          return true;
        }
      }
      else
      {
        // Internal node - push right child first, then left (so left is processed first)
        if (stackPtr < MAX_STACK_SIZE)
        {
          stack[stackPtr] = bvhNode.leftBVHIndex + 1;
          stackBegin[stackPtr] = bvhNode.splitTriangleIndex;
          stackEnd[stackPtr] = currentEnd;
          stackPtr++;
        }
        if (stackPtr < MAX_STACK_SIZE)
        {
          stack[stackPtr] = bvhNode.leftBVHIndex;
          stackBegin[stackPtr] = currentBegin;
          stackEnd[stackPtr] = bvhNode.splitTriangleIndex;
          stackPtr++;
        }
      }
    }
    stackPtr--;
  }
  
  return false;
}

#define    iResolution vec3(uniData.xyz)
#define    iTime       uniData.w

struct Material {
  //vec3 specularColor;
  vec3 refractionColor;
  //float specularHardness;
  float reflectivity;
  float density;
  //bool phong;
  bool refractions;
  bool reflections;
};


void mainImage( inout vec4 z, in vec2 w ) 
{
  
  int begin_idx = scene_part.y;
  int end_idx = scene_part.z+1; //begin_idx + 16;

//  int begin_idx = 0;
//  int end_idx = begin_idx + 4;


  vec3 ambientColor = vec3(0.1f, 0.1f, 0.1f);
  float front_scatter = 0.005; // luce grossa quando c'e' una parete (e quando va all'infinito)
  float back_scatter = 0.005; // luce grossa quando va all'infinito

//      mat4 mesh_tx = (mat4(
// 1.0000, 0.0000, 0.0000, 0.0000,
// 0.0000, 1.0000, 0.0000, 0.0000,
// 0.0000, 0.0000, 1.0000, 0.0000,
// 1.2000, 1.1510, 2.8040, 1.0000
//    ));
   
//   mesh_tx = inverse(mesh_tx); 
  //z= vec4(mesh_tx[0][0], mesh_tx[1][0], mesh_tx[2][0], mesh_tx[3][0]);
 // z=vec4(0,mesh_tx[0][0],0,1);
  z = vec4(0.0f, 0.0f, 0.0f, 1.0f);
  
  // Camera ray
 
  Ray cameraRay = Ray(vec3(0.f, 0.f, 0.f), vec3((2*w - iResolution.xy)*vec2(-1.0, 1.0)/iResolution.y, iResolution.z));

  Ray ray = transform(camera, cameraRay);
//  Ray ray = Ray( v,  d);
  int skip_triangle = -1;
  int skip_mesh_idx = -1;
  vec3 intensity = vec3(1.0f, 1.0f, 1.0f);

  // Compute 3 bounces
  for(int i = 0; i < 3; ++i)
  { 
    Hit best_hit;
    best_hit.depth = 1e38;
    int best_idx = -1;
    for(int inx = begin_idx; inx < end_idx; ++inx) {
      mesh_index = inx; // for now we just support 1 mesh, so we take the first one

      Ray localRay = transform(meshes_itx[inx], ray);
      Hit h = intersectBVH(localRay, skip_mesh_idx == inx?skip_triangle:-1, false);
      if(h.triangle>=0) {
        if (h.depth < best_hit.depth) {
          best_idx = inx;
          best_hit = h;
        }
      }
    }

    if (best_idx >= 0) {
      mesh_index = best_idx;
      if (i == 0)
      {
        z.xyz += ambientColor;
      }
      vec3 hitPosition = ray.origin + ray.direction * best_hit.depth;

      Surface tn=fetchSurface(best_hit);
      tn.norm = normalize(transformDirection(meshes_tx[best_idx], tn.norm));

      vec3 r = reflect(normalize(ray.direction), tn.norm);


      // Material Parameters
      //vec3 specularColor;
      vec3 refractionColor;
      float specularHardness;
      float reflectivity;
      float density;
      //bool phong;
      bool refractions;
      bool reflections;

      vec3 diffuseColor = tn.color.rgb;
      switch (tn.material) {
        case mat_duck:
        case mat_chopper:
        case mat_face_details:
        case mat_face:
        case mat_skids:
        case mat_rotors:
        case mat_japotek:
        case mat_RingBase:
        case mat_RingSpec:
        case mat_room:
          //specularColor = vec3(0.0f, 0.0f, 0.0f);
          refractionColor = vec3(0.0f, 0.0f, 0.0f);
          //specularHardness = 20.f;
          reflectivity = 0.0f;
          density = 0.0f;
          //phong = false;        
          refractions = false; //true; //(int(w.x) % 32 > 16);
          reflections = false;
          break;
        case mat_chess_blacks:
        case mat_chess_whites:
        case mat_chess_border:
          //specularColor = vec3(1.0f, 1.0f, 1.0f);
          refractionColor = vec3(0.0f, 0.0f, 0.0f);
          //specularHardness = 20.f;
          reflectivity = 0.0f;
          density = 0.0f;
          //phong = true;        
          refractions = false; //true; //(int(w.x) % 32 > 16);
          reflections = false;
          break;
        case mat_whites:
          refractionColor = diffuseColor;
          //specularHardness = 20.f;
          reflectivity = 0.3f;
          density = 0.2f;
          //phong = true;        
          refractions = true;
          reflections = false;
        case mat_blacks:
          //specularColor = vec3(1.0f, 1.0f, 1.0f);
          refractionColor = diffuseColor;
          //specularHardness = 20.f;
          reflectivity = 0.3f;
          density = 0.4f;
          //phong = true;        
          refractions = true;
          reflections = false;
          break;
        case mat_duck_eye:
        case mat_catgold:
        case mat_gold:
//        case mat_Lit:
          //specularColor = vec3(1.0f, 1.0f, 1.0f);
          refractionColor = vec3(0.0f, 0.0f, 0.0f);
          //specularHardness = 20.f;
          reflectivity = 0.7f;
          density = 0.0f;
          //phong = true;        
          refractions = false;
          reflections = true;
          break;
        default:
          bool crystal = (best_idx == 5) || (best_idx == 7);
          //specularColor = vec3(1.0f, 1.0f, 1.0f);
          refractionColor = vec3(0.9f, 0.6f, 0.99f);
          //specularHardness = 20.f;
          reflectivity = 0.0f;
          density = 0.1f;
          //phong = true;        
          refractions = crystal; //true; //(int(w.x) % 32 > 16);
          reflections = false;
          break;
      }

      for (int l = 0; l < MAX_LIGHTS; ++l) {
        if (light_colors[l].w == 0.0f) continue;

        // volumetric light
        float dnorm = length(ray.direction);
        vec3 normd = ray.direction/dnorm;
        float a = dot((ray.origin - light_positions[l].xyz), normd);
        float t0 = -a/dnorm;
        float rho = length(ray.origin - light_positions[l].xyz - a*normd);
        float k = (a+best_hit.depth*dnorm);
        k *= k;
        if (t0 < 0) { // tmin != t0
          z += light_colors[l]*(back_scatter*(1/sqrt(a*a+rho*rho)-1/sqrt(k+rho*rho))/dnorm);
        } else if (t0 < best_hit.depth) {
          z += light_colors[l]*(back_scatter*(1/rho-1/sqrt(k+rho*rho))/dnorm);
          z += light_colors[l]*(front_scatter*(1/rho-1/sqrt(a*a+rho*rho))/dnorm);
        } else {
          z += light_colors[l]*(front_scatter*(1/sqrt(a*a+rho*rho) - 1/sqrt(k+rho*rho))/dnorm);
        }
        // end of volumetric light

        vec3 lightDir = light_positions[l].xyz - hitPosition; //3D position in space of the surface
        float NdotL = dot(tn.norm, lightDir);
        bool shadow = false;
        if (NdotL >=0.0f) {
          Ray shadowRay = Ray(hitPosition, lightDir);
          for(int inx = begin_idx; inx < end_idx; ++inx) {
            mesh_index = inx;
            if (intersectBVHShadow(transform(meshes_itx[inx], shadowRay), (best_idx==inx)?best_hit.triangle:-1)) {
              shadow = true;
              break;
            }
          }
          mesh_index = best_idx;
          if (!shadow) {
            float invDistance = 1.0f / length(lightDir);
            vec3 totalIntensity = intensity * (light_colors[l].xyz * (invDistance * invDistance));

            float diffuseIntensity = (NdotL * invDistance);
            z.xyz += (totalIntensity * diffuseIntensity) * diffuseColor;
            // if (phong) {
            //   float specularIntensity = pow(max(dot(r, lightDir * invDistance), 0.0f), specularHardness) ;
            //   z.xyz += (totalIntensity * specularIntensity) * specularColor;
            // }
          }
        }
      }
      if (refractions) {
        float ior = 0.9;
        ray.origin = hitPosition;
        ray.direction = normalize(refract(normalize(ray.direction), tn.norm, ior));
        if (dot(ray.direction, ray.direction) > 0)
        {
          r = ray.direction;
          Ray localRay = transform(meshes_itx[best_idx], ray);
          skip_triangle = best_hit.triangle;
          best_hit = intersectBVH(localRay, skip_triangle, true);
          if(best_hit.triangle>=0) {
            hitPosition += ray.direction * best_hit.depth;
            //tn=fetchSurface(best_hit);
            // qui prendo solo la normale della superficie. evito di estrarre la uv che non serve
            tn.norm = fetchSurfaceNormal(best_hit);
            tn.norm = normalize(transformDirection(meshes_tx[best_idx], tn.norm));
            r = refract(normalize(ray.direction), -tn.norm, 1.0f/ior);
            float m = exp(-density * best_hit.depth);
            z.xyz += refractionColor*(1.f-m);
            intensity *= refractionColor*m;
            if (dot(r, r) <= 0)
            {
              r = reflect(ray.direction, tn.norm);
              // we should really reflect until we get out, but that's another story...
            }
          }
        }
      } else if (reflections) {
        intensity *= reflectivity;
      } else {
        return;
      }
      ray.origin = hitPosition;
      ray.direction = r;
      skip_triangle = best_hit.triangle;
      skip_mesh_idx = best_idx;
    }
    else 
    {
      for (int l = 0; l < MAX_LIGHTS; ++l) {
        if (light_colors[l].w == 0.0f) continue;

        // volumetric light
        float dnorm = length(ray.direction);
        vec3 normd = ray.direction/dnorm;
        float a = dot((ray.origin - light_positions[l].xyz), normd);
        float t0 = -a/dnorm;
        float rho = length(ray.origin - light_positions[l].xyz - a*normd);
        if (t0 < 0) { // tmin != t0
          z += light_colors[l]*(back_scatter*(1/length(ray.origin - light_positions[l].xyz))/dnorm);
        } else
          z += light_colors[l]*(back_scatter*(1/rho)/dnorm);
          z += light_colors[l]*(front_scatter*(1/rho - 1/sqrt(a*a+rho*rho))/dnorm);
        }
        // end of volumetric light
    }
  }
}

void main()
{
    FragColor=vec4(0,0,0,1); 
    mainImage(FragColor, gl_FragCoord.xy);
    FragColor.xyz = mix(FragColor.xyz, flash.xyz, flash.w);   

    //FragColor=texelFetch(scene_data, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);

}