#version 430

layout(binding=0) uniform sampler2D tex;
layout(binding=1) uniform sampler2D texBump;
layout(binding=2) uniform sampler2D texEnv;
layout(binding=3) uniform sampler2D texPrevFrame;
layout(binding=4) uniform sampler2D texPrevBlurFrame;
layout(binding=5) uniform sampler2D texPrevNorm;
layout(binding=6) uniform sampler2D texRep2;
layout(binding=7) uniform sampler2D texRep3;



layout(binding=3, r32i) uniform iimage2D pardexFree;
layout(binding=4, rgba32f) uniform image2D pardexPos;
layout(binding=5, rgba32f) uniform image2D pardexAge;
layout(binding=6, rgba32f) uniform image2D pardexVel;
layout(binding=7, r32i) uniform iimage2D meshDepth;
layout(binding=2, r32i) uniform iimage2D meshDepthPrev;


layout(binding=0, offset=0) uniform atomic_uint ac_active;
layout(binding=1, offset=0) uniform atomic_uint ac_free;
layout(binding=2, offset=0) uniform atomic_uint ac_emits;


in vec4 posG;
in vec3 normalG;
in vec3 normalWSG;
in vec2 uvG;
in vec3 tangentG;
in vec3 colorG;
in vec4 posW;
in vec4 posView;
in vec4 posInst;
in vec4 randG;
in vec3 mirrorG;


layout(location = 0) out vec4 frag;
layout(location = 1) out vec4 frag2;
layout(location = 2) out vec4 frag3; // emitBuf


uniform float g_instLayer = 0.0;
uniform float g_time;


uniform float g_uvOfsX = 0.0;
uniform float g_uvOfsY = 0.0;
uniform vec4 g_uvScale = vec4(1.0);

uniform float g_texBrightness = 1.0;

uniform float g_emitNew = 0.0;

uniform float g_prevAmount = 0.0;
uniform float g_prevBlurAmount = 0.0;


uniform float g_cubesDiv = 16.0;
uniform float g_spread = 8.0;
uniform float g_worldPos = 1.0;

uniform float g_genUV = 0.0;


uniform mat4 modelViewMatrix;
uniform mat4 modelViewInvMatrix;
uniform mat4 viewInvMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 projectionInvMatrix;

uniform float g_windowWidth = 1280.0;
uniform float g_windowHeight = 720.0;


uniform float g_prevBlurType = 1.0;


uniform float g_bump = 0.0;

uniform float g_animHeight = 0.0;

const float zFar = 1.0;
const float zNear = 0.0;

vec4 CalcEyeFromWindow(in vec3 windowSpace) {
    vec3 ndcPos;
    vec4 viewport = vec4(0.0, 0.0, g_windowWidth, g_windowHeight);
    ndcPos.xy = ((2.0 * windowSpace.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;
//    ndcPos.z = (2.0 * windowSpace.z - zNear - zFar) / (zFar - zNear);
    ndcPos.z = (2.0 * windowSpace.z - zNear - zFar) / (zFar - zNear);
    vec4 clipPos;
    clipPos.w = projectionMatrix[3][2]/(ndcPos.z-(projectionMatrix[2][2]/projectionMatrix[2][3]));
    clipPos.xyz = ndcPos * clipPos.w;
    return projectionInvMatrix * clipPos;
}

#define PI 3.1415926

vec2 latlong(vec3 v) {
  v = normalize(v);
  float theta = acos(v.z); // +z is up
  float phi = atan(v.y, v.x) + PI;
  return vec2(phi, theta)*vec2(.1591549, .6366198);
}


vec4 rotateXY(vec4 p, float a) {
  vec4 r = p;
  r.x = cos(a)*p.x - sin(a)*p.y;
  r.y = sin(a)*p.x + cos(a)*p.y;
  return r;
}

vec3 rotateXY3(vec3 p, float a) {
  return rotateXY(vec4(p, 0.0), a).xyz;
}

// google glsl rand gave this, thanks and credit flies to
// http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
float rand(vec2 co){
  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

uniform vec4 g_mirrorX = vec4(0.0, 0.0, 0.0, 0.0); // .x: mirrorX on/off, .y: mirrorXPos, .z: mirrorXClip
uniform vec4 g_mirrorY = vec4(0.0, 0.0, 0.0, 0.0); // .x: mirrorY on/off, .y: mirrorYPos, .z: mirrorYClip
uniform vec4 g_mirrorZ = vec4(0.0, 0.0, 0.0, 0.0); // .x: mirrorZ on/off, .y: mirrorZPos, .z: mirrorZClip


uniform float g_texAmbient;

uniform float g_alpha;

uniform vec4 g_uvRandom;

uniform mat4 modelMatrix;
uniform float g_timeStep;


// pala flow emit stuff
uniform float g_emitThr = 0.0;
uniform float g_emitAmp = 1.0;





// BUMPERS begin
uniform float texSub = 0.0;
uniform float texMult = 1.0;
vec3 texOp(vec3 t) {
    t.rgb -= vec3(texSub);
    t.rgb = clamp(t.rgb, 0.0, 10000.0);
    t.rgb *= texMult;
    return t;
}

uniform float bumpTexInd = 0.0;
uniform float bumpStrength = 1.0;
uniform float bumpTexDelta = 1.0;
uniform float bumpUvScale = 1.0;
vec2 getBumpG(sampler2D s, vec2 uv) {
    if (abs(bumpUvScale) < 0.0001) {
        uv.xy *= g_uvScale.xy;
    } else {
        uv *= bumpUvScale;
    }
    vec2 d = vec2(bumpTexDelta)/textureSize(s, 0);
    return vec2(texOp(texture2D(s, uv+d.xy).rgb).g-texOp(texture2D(s, uv-d.xy).rgb).g,
                texOp(texture2D(s, uv+d.yx).rgb).g-texOp(texture2D(s, uv-d.yx).rgb).g)*bumpStrength;
}
// BUMPERS end







// BEGIN NOISE
// TODO #include "noise.h"

uniform float noiseOct = 6.0;
uniform float noiseOctMul = 2.0;
uniform float noiseOctAmp = 0.5;
uniform float noiseSplineEnabled = 1.0;

// from pouet raymarching thread by las of mercury
float perlin(vec3 p) {
    vec3 i = floor(p);
    vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.);
    vec3 f = cos((p-i)*3.14159265)*(-.5)+.5;
    a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x);
    a.xy = mix(a.xz, a.yw, f.y);
    return mix(a.x, a.y, f.z)-0.0;
}
float turb3(vec3 c) {
        float r=0.0;
        float s=0.5;
        for (int i=0;i<noiseOct;i++) {
                r+=s*(perlin(c));
                c*=noiseOctMul;
                s*=noiseOctAmp;
        }
        return r;
}

vec3 posd(float pw) {
    return vec3(sin(pw)*17.0, cos(pw*1.7)*13.0, sin(pw*0.78+cos(pw*0.4))*11.0)*32.0;
}

float getSplineValue(float t, float p0, float pt0, float p1, float pt1) {
    float t2 = t*t;
    float t3 = t*t*t;
    float p = (2.0*t3-3.0*t2+1.0)*p0+(t3-2.0*t2+t)*pt0+(-2.0*t3+3.0*t2)*p1+(t3-t2)*pt1;
    return p;
}

float perlin4(vec4 p) {
//    vec4 pf1 = floor(p);
//    vec4 pf2 = floor(p+vec4(1.0));

    float p1 = perlin(p.xyz+posd(floor(p.w)+1.0));
    float p2 = perlin(p.xyz+posd(floor(p.w)+2.0));

    if (noiseSplineEnabled > 0.5) {
        float p0 = perlin(p.xyz+posd(floor(p.w)));
        float p3 = perlin(p.xyz+posd(floor(p.w)+3.0));
        float ki = fract(p.w);
        return getSplineValue(ki, p1, p2-p0, p2, p3-p1);
    } else {
        float ki = smoothstep(0.0, 1.0, fract(p.w));
        return p1*(1.0-ki)+p2*(ki);
    }
}

float turb4(vec4 c) {
        float r=0.0;
        float s=0.5;
        for (int i=0;i<int(noiseOct);i++) {
                r+=s*(perlin4(c));
                c*=noiseOctMul;
                s*=noiseOctAmp;
        }
        return r;
}

// end of "noise.h"


// BEGIN PARDEX emit
float atanSafe(float y, float x) {
    float ret=0.0;
    if (x!=0.0) {
        if (x>0.0) {
            ret=atan(y/x);
        } else	{
            ret=atan(y/x)+3.141592;
        }
    } else {
        if (y>=0.0) {
            ret=0.5*3.141592;
        } else {
            ret=-0.5*3.141592;
        }
    }
    return ret;
}


ivec2 getTrip(int idt) {
    ivec2 triBufSize = imageSize(pardexPos)/2;
    ivec2 trip;
    trip.y = idt/triBufSize.x;
    trip.x = idt-trip.y*triBufSize.x;
    return trip;
}

uniform float emitAmount = 0.0;
uniform float emitAmountFromBright = 0.0;
uniform float emitAge = 1.0;
uniform float emitAgeFromBright = 0.0;
uniform float emitCamDebug = 0.0;

uniform float emitVelDirOrient = 1.0;

uniform float emitVelDirAmp = 0.0;
uniform float emitVelDirX = 0.0;
uniform float emitVelDirY = 0.0;
uniform float emitVelDirZ = 0.0;




// END PARDEX emit


uniform float noisePos = 0.0;
uniform float noiseOfsX = 0.0;
uniform float noiseOfsY = 0.0;
uniform float noiseFreq = 0.50;

uniform float dispAmp = 0.15;
uniform float dispExp = 5.0;


void applyDisp(inout float no) {
    no = (sign(no)*pow(abs(no), dispExp));
}

// END NOISE











// TEXREPE begins

vec2 rotateXY2(vec2 p, float a) {
  vec2 r = p;
  r.x = cos(a)*p.x - sin(a)*p.y;
  r.y = sin(a)*p.x + cos(a)*p.y;
  return r;
}

uniform float g_repTex1 = 0.0;
uniform float g_repUVBase1 = 1.0;
uniform float g_repUVOp1 = 0.0;
uniform float g_repUVAmp1 = 0.0;
uniform float g_repUVSca1 = 1.0;
uniform float g_repUVOfsX1 = 0.0;
uniform float g_repUVOfsY1 = 0.0;
uniform float g_repUVRot1 = 0.0;

uniform float g_repTex2 = 0.0;
uniform float g_repUVBase2 = 0.0;
uniform float g_repUVOp2 = 0.0;
uniform float g_repUVAmp2 = 0.0;
uniform float g_repUVSca2 = 1.0;
uniform float g_repUVOfsX2 = 0.0;
uniform float g_repUVOfsY2 = 0.0;
uniform float g_repUVRot2 = 0.0;

uniform float g_repTex3 = 0.0;
uniform float g_repUVBase3 = 0.0;
uniform float g_repUVOp3 = 0.0;
uniform float g_repUVAmp3 = 0.0;
uniform float g_repUVSca3 = 1.0;
uniform float g_repUVOfsX3 = 0.0;
uniform float g_repUVOfsY3 = 0.0;
uniform float g_repUVRot3 = 0.0;

uniform float g_repPow = 1.0;



vec4 texRepe(sampler2D tRep1, sampler2D tRep2, sampler2D tRep3, vec2 uv) {
   vec4 r = vec4(0.0);
   vec2 uvr;
   vec4 rep;

   //     float repLum = dot(vec3(0.299, 0.587, 0.114), rep.rgb);
   //     if (repLum > 0.001) {
   //       r.rgb /= repLum;
   //       repLum = pow(repLum, 1.0);
   //       r.rgb *= repLum;
   //     }

   float hp = 0.5*3.141592;

   if (g_repUVOp1 > 0.5) {
     uvr = rotateXY2(uv, hp*g_repUVRot1/90.0)+vec2(g_repUVOfsX1, g_repUVOfsY1);
     if (g_repTex1 < -0.5) {
       rep = vec4(1.0, 0.0, 0.0, 1.0);


       float evo = 0.0;
       float movex = 0.0;
       vec4 pp = posW;
       float no = turb4(vec4(pp.xyz*noiseFreq+vec3(noiseOfsX, noiseOfsY+g_time*movex, 0.0), noisePos+g_time*evo));
       float no2 = turb4(vec4(pp.zxy*noiseFreq*1.32+vec3(noiseOfsX+0.76, noiseOfsY+1.23+g_time*movex, 0.0), noisePos+1.72+g_time*evo));
       float no3 = turb4(vec4(pp.yzx*noiseFreq*1.92+vec3(noiseOfsX+1.26, noiseOfsY+0.53+g_time*movex, 0.0), noisePos+0.32+g_time*0.5*evo));

       applyDisp(no);
       applyDisp(no2);
       applyDisp(no3);


       vec3 noi = vec3(no, no2, no3);
       float noiAmp = dot(noi, vec3(0.333))+0.5;
       rep.rgba *= noiAmp;


     } else if (g_repTex1 < 0.5) {
       rep = texture2D(tRep1, uvr*g_repUVSca1);
     } else {
       rep = texture2D(tRep1, uvr*g_repUVSca1);
     }
     if (g_repUVOp1 < 1.5) {
      r.rgb += vec3(g_repUVBase1)+rep.rgb*rep.a*g_repUVAmp1;
     } else {
      r.rgb += (r.rgb+vec3(g_repUVBase1))*rep.rgb*rep.a*g_repUVAmp1;
     }
   }

   if (g_repUVOp2 > 0.5) {
     uvr = rotateXY2(uv, hp*g_repUVRot2/90.0)+vec2(g_repUVOfsX2, g_repUVOfsY2);
     if (g_repTex2 < 0.5) {
       rep = texture2D(tRep1, uvr*g_repUVSca2);
     } else {
       rep = texture2D(tRep2, uvr*g_repUVSca2);
     }
     if (g_repUVOp2 < 1.5) {
      r.rgb += vec3(g_repUVBase2)+rep.rgb*rep.a*g_repUVAmp2;
     } else {
      r.rgb *= vec3(g_repUVBase2)+rep.rgb*rep.a*g_repUVAmp2;
     }
   }
   if (g_repUVOp3 > 0.5) {
     uvr = rotateXY2(uv, hp*g_repUVRot3/90.0)+vec2(g_repUVOfsX3, g_repUVOfsY3);
     if (g_repTex3 < 0.5) {
       rep = texture2D(tRep1, uvr*g_repUVSca3);
     } else {
       rep = texture2D(tRep3, uvr*g_repUVSca3);
     }
     if (g_repUVOp3 < 1.5) {
      r.rgb += vec3(g_repUVBase3)+rep.rgb*rep.a*g_repUVAmp3;
     } else {
      r.rgb *= vec3(g_repUVBase3)+rep.rgb*rep.a*g_repUVAmp3;
     }
   }
   r = clamp(r, 0.0, 10000.0);
   r = pow(r, vec4(g_repPow));
   r.a = 1.0;
   return r;
}
// TEXREPE ends



uniform float normalAlpha = 1.0;


uniform float emitIntersect = 0.0;
uniform float emitIntExp = 1.0;


void main() {

  vec3 normal = normalG;
  vec2 uv = uvG;
  vec3 tangent = tangentG;
  vec3 color = colorG;

  float mmz = g_mirrorZ.z;
  if (mmz > 1.5 && mirrorG.z < 0.0) {
    mmz *= -1.0;
  }

  if (mirrorG.x*(posW.x-g_mirrorX.y)*g_mirrorX.z > 0.0 ||
      mirrorG.y*(posW.y-g_mirrorY.y)*g_mirrorY.z > 0.0 ||
      mirrorG.z*(posW.z-g_mirrorZ.y)*mmz > 0.0) {
          discard;
  }


  ivec2 triBufSize = imageSize(pardexPos)/2;

//  if (posW.x-g_mirrorX.y < 0.0) discard;

  vec2 randUv = g_uvScale.xy+vec2(randG.x);
  randUv = min(max(randUv, vec2(g_uvRandom.y)), vec2(g_uvRandom.z));

  vec2 myUv = posInst.xy*randUv+vec2(0.5, 0.5);
  if (g_genUV < 0.5) {
    myUv = uv*randUv;
  }
  myUv += randG.yz;
  myUv.y *= -1.0;
  myUv.x += g_uvOfsX;
  myUv.y += g_uvOfsY+g_animHeight*floor(fract((floor(randG.w/1.0-0.5)/4.0)+0.01)*4.0);


  vec4 nv4 = (viewInvMatrix*vec4(normal, 0.0));
//   nv4.xyz /= nv4.w;
  vec3 nv = nv4.xyz;

  if (normal.z < 0.0) {
  //  discard;
  }

// vec4 diffuse = texture2D(tex, myUv.xy);
 vec4 diffuse = texRepe(tex, texRep2, texRep3, myUv);

 int curD = int(-posView.z*1000.0);
 int prevD = imageAtomicMin(meshDepth, ivec2(gl_FragCoord), curD);


 float surfBr = dot(diffuse.rgb, vec3(1.0));
 float emitAm = emitAmount;
 if (emitAmountFromBright > 0.0001) {
   emitAm += emitAmountFromBright*surfBr;
 }
 float emitAgeMod = emitAge;
 if (emitAgeFromBright > 0.0001) {
   emitAgeMod += emitAgeFromBright*surfBr;
 }

 emitAm = clamp(emitAm, 0.0, 10000.0);

 int prevDK = imageLoad(meshDepthPrev, ivec2(gl_FragCoord)).x;

 float dedi = abs(float(prevDK-curD));
// float emd = emitIntersect*pow(1.0/(dedi*2.0+0.10), emitIntExp);
 float emd = 0.0;
 if (emitIntersect > 0.001) {
   emd = 100.0*pow(clamp(1.0-dedi/emitIntersect, 0.0, 10.0), emitIntExp);
 }
 emitAm += emd;


// emitAm = 1.0;


float r = rand(gl_FragCoord.xy);

float ts = clamp(g_timeStep/0.01667, 0.0, 3.0);

//ts = clamp(2.0, 0.0, 3.0);

if (r > 1.0-emitAm*0.01*ts) {
    // emit a pardicle
    int emitNumber = int(atomicCounterIncrement(ac_emits));
    int freeLeft = int(atomicCounter(ac_free));
    if (emitNumber < freeLeft) {
    //if (true) {
        int freeIndex = imageLoad(pardexFree, getTrip(emitNumber)).x;

      //  int activeNumber = int(atomicCounterIncrement(ac_active));
      //  int activeIndex = freeIndex+1;
      //  imageStore(pardexActive, getTrip(activeNumber), ivec4(activeIndex));

        ivec2 emitUV = getTrip(freeIndex);

        float ageLeft;
     //   ageLeft = pow(dot(posModel.xyz, posModel.xyz)*4.0, 2.0);
     //   ageLeft = clamp(ageLeft, 0.1, 4.0);
        ageLeft = emitAgeMod;

        imageStore(pardexPos, emitUV, vec4(posW)+vec4(0.0, 0.0, -0.20, 0.0));
        imageStore(pardexPos, emitUV+ivec2(triBufSize.x, 0), diffuse);
        imageStore(pardexPos, emitUV+ivec2(0, triBufSize.y), vec4(nv, 0.0));

        float emitAgeLeft = ageLeft*rand(uv);
        imageStore(pardexAge, emitUV, vec4(emitAgeLeft, emitAgeLeft, 0.0, 0.0)); // .x will count down to 0.0, .y will remain in emit value

        vec4 ev = emitVelDirAmp*vec4(emitVelDirX, emitVelDirY, emitVelDirZ, 0.0);

        ev.xyz += emitVelDirOrient*normalize(normalWSG.xyz);


        imageStore(pardexVel, emitUV, ev);
  //      imageStore(pardexVel, emitUV, vec4(0.0, 0.0, -100.0, 0.0));

  //      imageStore(pardexVel, emitUV, vec4(-10.0*vel, 0.0));


         frag.rgb = vec3(1.50, 1.5, 0.5);
    }
}


  frag.x = gl_FragCoord.z;

  float na1 = atanSafe(nv.y, nv.x);
  float nkm = sqrt(dot(nv.xy, nv.xy));
  float na2 = -0.5*3.141592;
  if (abs(nkm) > 0.001) {
    na2 = acos(nkm);
  } else {
    if (nv.z > 0.0) {
      na2 = 0.5*3.141592;
    } else {
      na2 = -0.5*3.141592;
    }
  }

  frag.yz = vec2(na1, na2);
  frag.w = 1.0;


//  frag.a = 1.;
//   frag.r = 1.0+float(curD)*0.001;
//   frag.a = 1.0;

  if (curD >= prevD) {
    discard;
  }

}

