#ifdef GL_ES
precision mediump float;
#endif

#extension GL_OES_standard_derivatives : enable

const float epsilon = 0.001;
const float PI = 3.14159265;
const int isMove = 1;


// util func
//-------------------------------------------------------------------------------------
float deg2rad(float angle){
    return angle * PI / 180.0;
}

//x 0-1
float saturate(float x){
    return clamp(x,0.0,1.0);
}

vec3 LinearToGamma( in vec3 value, in float gammaFactor ) {
    return pow( value, vec3(1.0 / gammaFactor) );
}

vec3 brightnessContrast(vec3 value, float brightness, float contrast)
{
    return (value - 0.5) * contrast + 0.5 + brightness;
}

//simplex noise
//-------------------------------------------------------------------------------------
vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289(vec4 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x) {
     return mod289(((x*34.0)+1.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

float snoise(vec3 v)
  { 
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);

// First corner
  vec3 i  = floor(v + dot(v, C.yyy) );
  vec3 x0 =   v - i + dot(i, C.xxx) ;

// Other corners
  vec3 g = step(x0.yzx, x0.xyz);
  vec3 l = 1.0 - g;
  vec3 i1 = min( g.xyz, l.zxy );
  vec3 i2 = max( g.xyz, l.zxy );

  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
  //   x1 = x0 - i1  + 1.0 * C.xxx;
  //   x2 = x0 - i2  + 2.0 * C.xxx;
  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
  vec3 x1 = x0 - i1 + C.xxx;
  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y

// Permutations
  i = mod289(i); 
  vec4 p = permute( permute( permute( 
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  float n_ = 0.142857142857; // 1.0/7.0
  vec3  ns = n_ * D.wyz - D.xzx;

  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

  vec4 x_ = floor(j * ns.z);
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  vec4 x = x_ *ns.x + ns.yyyy;
  vec4 y = y_ *ns.x + ns.yyyy;
  vec4 h = 1.0 - abs(x) - abs(y);

  vec4 b0 = vec4( x.xy, y.xy );
  vec4 b1 = vec4( x.zw, y.zw );

  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
  vec4 s0 = floor(b0)*2.0 + 1.0;
  vec4 s1 = floor(b1)*2.0 + 1.0;
  vec4 sh = -step(h, vec4(0.0));

  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  vec3 p0 = vec3(a0.xy,h.x);
  vec3 p1 = vec3(a0.zw,h.y);
  vec3 p2 = vec3(a1.xy,h.z);
  vec3 p3 = vec3(a1.zw,h.w);

//Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;

// Mix final noise value
  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
                                dot(p2,x2), dot(p3,x3) ) );
  }
  
//pbr
//-------------------------------------------------------------------------------------

struct PBRMaterial{
    vec3 albedo;
    vec3 metallic;
    float roughness;
    vec3 emission;
};

struct GeometricContext {
  vec3 position;
  vec3 normal;
  vec3 viewDir;
};

struct IncidentLight {
  vec3 direction;
  vec3 color;
  bool visible;
};

struct DirectionalLight {
  vec3 direction;
  vec3 color;
};

struct PointLight {
  vec3 position;
  vec3 color;
  float visible_distance;
  float decay;
};

struct ReflectedLight {
  vec3 directDiffuse;
  vec3 directSpecular;
  vec3 indirectDiffuse;
  vec3 indirectSpecular;
};

//test lighth visible
bool testLightInRange(const in float lightDistance, const in float cutoffDistance) {
  return any(bvec2(cutoffDistance == 0.0, lightDistance < cutoffDistance));
}

//light decay by distanc
float punctualLightIntensityToIrradianceFactor(const in float lightDistance, const in float cutoffDistance, const in float decayExponent) {
  if (decayExponent > 0.0) {
    return pow(saturate(-lightDistance / cutoffDistance + 1.0), decayExponent);
  }

  return 1.0;
}

//directional light irradiance
void getDirectionalDirectLightIrradiance(const in DirectionalLight directionalLight, out IncidentLight directLight) {
  directLight.color = directionalLight.color;
  directLight.direction = directionalLight.direction;
  directLight.visible = true;
}

//point light irradiance
void getPointDirectLightIrradiance(const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight directLight) {
  vec3 L = pointLight.position - geometryPosition;
  directLight.direction = normalize(L);

  float lightDistance = length(L);
  if (testLightInRange(lightDistance, pointLight.visible_distance)) {
    directLight.color = pointLight.color;
    directLight.color *= punctualLightIntensityToIrradianceFactor(lightDistance, pointLight.visible_distance, pointLight.decay);
    directLight.visible = true;
  } else {
    directLight.color = vec3(0.0);
    directLight.visible = false;
  }
}

//material from colors
vec3 diffuseColor(vec3 albedo, vec3 metallic){
    return mix(albedo, vec3(0.0), metallic);
}

vec3 specularColor(vec3 albedo, vec3 metallic){
    return mix(vec3(0.04), albedo, metallic);
}

// Normalized Lambert
vec3 DiffuseBRDF(vec3 diffuseColor) {
    return diffuseColor / PI;
}



vec3 F_Schlick(vec3 specularColor, vec3 H, vec3 V) {
    return (specularColor + (1.0 - specularColor) * pow(1.0 - saturate(dot(V,H)), 5.0));
}

float D_GGX(float a, float dotNH) {
    float a2 = a*a;
    float dotNH2 = dotNH*dotNH;
    float d = dotNH2 * (a2 - 1.0) + 1.0;
    return a2 / (PI * d * d);
}

float G_Smith_Schlick_GGX(float a, float dotNV, float dotNL) {
    float k = a*a*0.5 + epsilon;
    float gl = dotNL / (dotNL * (1.0 - k) + k);
    float gv = dotNV / (dotNV * (1.0 - k) + k);
    return gl*gv;
}

// Cook-Torrance
vec3 SpecularBRDF(const in IncidentLight directLight, const in GeometricContext geometry, vec3 specularColor, float roughnessFactor) {

    vec3 N = geometry.normal;
    vec3 V = geometry.viewDir;
    vec3 L = directLight.direction;

    float dotNL = saturate(dot(N,L));
    float dotNV = saturate(dot(N,V));
    vec3 H = normalize(L+V);
    float dotNH = saturate(dot(N,H));
    float dotVH = saturate(dot(V,H));
    float dotLV = saturate(dot(L,V));
    float a = roughnessFactor * roughnessFactor;
    
    float D = D_GGX(a, dotNH);
    float G = G_Smith_Schlick_GGX(a, dotNV, dotNL);
    vec3 F = F_Schlick(specularColor, V, H);

    return (F*(G*D))/(4.0*dotNL*dotNV+epsilon);
}

// RenderEquations(RE)
void RE_Direct(const in IncidentLight directLight, const in GeometricContext geometry, const in PBRMaterial material, inout ReflectedLight reflectedLight) {

    float dotNL = saturate(dot(geometry.normal, directLight.direction));
    vec3 irradiance = dotNL * directLight.color;

    // punctual light
    irradiance *= PI;
  
    vec3 diffuse = diffuseColor(material.albedo, material.metallic);
    vec3 specular = specularColor(material.albedo, material.metallic);

    reflectedLight.directDiffuse += irradiance * DiffuseBRDF(diffuse);
//    reflectedLight.directSpecular += specular;
    reflectedLight.directSpecular += irradiance * SpecularBRDF(directLight, geometry, specular, material.roughness);
}

//---------------------------

const int rayLoopCount = 256;
const int aoLoopCount = 8;


uniform float time;
uniform vec2 resolution;
uniform float debugTime;
float app_time;


int materialId = 0;

//transform func
//-------------------------------------------------------------------------------------
mat4 rotateX(float theta) {
    float c = cos(theta);
    float s = sin(theta);

    return mat4(
        vec4(1, 0, 0, 0),
        vec4(0, c, -s, 0),
        vec4(0, s, c, 0),
        vec4(0, 0, 0, 1)
    );
}

mat4 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);

    return mat4(
        vec4(c, 0, s, 0),
        vec4(0, 1, 0, 0),
        vec4(-s, 0, c, 0),
        vec4(0, 0, 0, 1)
    );
}
mat4 rotateZ(float theta) {
    float c = cos(theta);
    float s = sin(theta);

    return mat4(
        vec4(c, -s, 0, 0),
        vec4(s, c, 0, 0),
        vec4(0, 0, 1, 0),
        vec4(0, 0, 0, 1)
    );
}

vec3 rotate(vec3 pos, vec3 angle){
    vec3 theta = angle / 180.0 * PI;
    mat4 m = rotateZ(theta.z)*rotateY(theta.y)*rotateX(theta.x);
    return (m * vec4(pos, 1.0)).xyz;
}

vec3 translate(vec3 pos, vec3 to){
    to.y = -1.0*to.y;
    return pos + to;
}

vec3 scale(vec3 pos, vec3 scale){
    return pos/scale;
}

float opSmoothUnion( float d1, float d2, float k ) {
    float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) - k*h*(1.0-h); }

float opSmoothSubtraction( float d1, float d2, float k ) {
    float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
    return mix( d2, -d1, h ) + k*h*(1.0-h); }

float opSmoothIntersection( float d1, float d2, float k ) {
    float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) + k*h*(1.0-h); }

// dist func
//-------------------------------------------------------------------------------------

float dBox(vec3 p, vec3 size){
    vec3 q = abs(p);
    return length(max(q - size, 0.0));
}


float dSphere(vec3 pos, float size){
    return length(pos) - size; 
}

vec3 onRep(vec3 p, float interval){
    return mod(p, interval) - interval * 0.5;
}

float distPlane(in vec3 p, vec4 n){
    return dot(p, n.xyz) * n.w;
}


float dCylinder(vec3 p, vec3 c){
    p += c;
    return length(p.xz-c.xy)-c.z;
}


float dCappedCylinder( vec3 p, vec2 h ){
  vec2 d = abs(vec2(length(p.xz),p.y)) - h;
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

float dRoundCone( in vec3 p, in float r1, float r2, float h ){
    vec2 q = vec2( length(p.xz), p.y );
    
    float b = (r1-r2)/h;
    float a = sqrt(1.0-b*b);
    float k = dot(q,vec2(-b,a));
    
    if( k < 0.0 ) return length(q) - r1;
    if( k > a*h ) return length(q-vec2(0.0,h)) - r2;
        
    return dot(q, vec2(a,b) ) - r1;
}


// scene
//-------------------------------------------------------------------------------------

const int LIGHT_MAX = 4;
DirectionalLight directionalLights[LIGHT_MAX];
PointLight pointLights[LIGHT_MAX];
int numPointLights;
int numDirectionalLights;

struct Camera{
    vec3 position;
    vec3 target;
    float fov;
    float roll;
    float farClip;
};

uniform Camera debugCam;

Camera cam;

mat3 setCamLookAt(in Camera c){
    vec3  cw = normalize(c.target-c.position);
    vec3  cp = vec3(sin(deg2rad(c.roll)), cos(deg2rad(c.roll)), 0.0);
    vec3  cu = normalize( cross(cw,cp) );
    vec3  cv = normalize( cross(cu,cw) );
    return mat3( cu, cv, cw );
}


mat2 rotate2d(float a)
{
    return mat2(cos(a), sin(a), -sin(a), cos(a));   
}

vec2 fold(in vec2 p, in float s)
{
    float a = PI / s - atan(p.x, p.y);
    float n = PI*2.0 / s;
    a = floor(a / n) * n;
    p *= rotate2d(a);
    return p;    
}


vec3 getTunnelMoveDirection(float t){
    vec3 left = vec3(1.0, 0.0, 0.0);
    vec3 right = vec3(-1.0, 0.0, 0.0);
    vec3 front = vec3(0.0, 0.0, 1.0);
   
    if(t >= 0. && t<10.){
        return front;
    }else if(t>=10. && t<20.){
        return left;
    }else if(t>=20. && t<30.){
        return front;
    }else if(t>=30. && t<40.){
        return right;
    }else{
        return front;
    }
}

vec3 getBirdPos(float t){
    vec3 left = vec3(-10.0, 0.0, 0.0);
    vec3 right = vec3(5.0, 0.0, -5.0);
    vec3 back = vec3(0.0, 0.0, 10.0);
    vec3 front = vec3(0.0, 0.0, -10.0);
    
    if(t >= 0. && t<10.){
        return back;
    }else if(t>=10. && t<20.){
        return left;
    }else if(t>=20. && t<30.){
        return front;
    }else if(t>=30. && t<40.){
        return right;
    }else{
        return back;
    }
}



float sceneDist(vec3 pos){
    float result;
  
    float tunnelSize = 10.0;
    float tunnelRep = 60.0;
    vec3 tunnelPos = pos;
    tunnelPos -= tunnelRep/2.0;
    
    if(isMove == 1){
        float speed = tunnelSize*3.0;

        float t = app_time;
        tunnelPos += getTunnelMoveDirection(t)*t*speed;
    }
    
    tunnelPos = onRep(tunnelPos, tunnelRep);
    
    float tunnelR = tunnelSize*0.8;
    float boxCenter = dBox(tunnelPos, vec3(tunnelSize));
    
    vec3 cylinderPos = tunnelPos;
    cylinderPos = cylinderPos + 0.05*(snoise(cylinderPos*2.0)+1.0/2.0);
    cylinderPos = cylinderPos + 1.65*(snoise(cylinderPos/10.0)+1.0/2.0);
    float cylinderZ = dCylinder(rotate(cylinderPos, vec3(90.0, 0.0, 0.0)), vec3(tunnelR));
    float cylinderX = dCylinder(rotate(cylinderPos, vec3(90.0, 0.0, 90.0)), vec3(tunnelR));
    
    boxCenter = max(-min(cylinderX, cylinderZ), boxCenter);
    
    vec3 boxZPos = rotate(translate(tunnelPos, vec3(0.0, 0.0, 0.0)), vec3(0.0, 0.0, 90.0));
    boxZPos.yz = fold(boxZPos.yz, 4.0);
    float boxZ = dBox(translate(boxZPos, vec3(0.0, 0.0, -tunnelSize*2.0)), vec3(tunnelSize));
    boxZ = max(-min(cylinderX, cylinderZ), boxZ);
        
    vec3 tubePos = tunnelPos;
    
    tubePos.xy = fold(tubePos.xy, 5.0);
    float cylinderTube = dCylinder(rotate(translate(tubePos, vec3(0., tunnelR*0.95, 0.)), vec3(90, 0, 0)), vec3(tunnelR*0.03)); 
    float cylinderTubeSub = dCappedCylinder(rotate(translate(tubePos, vec3(0., tunnelR*0.95, -tunnelSize*1.4)), vec3(90, 0, 0)), vec2(tunnelR*0.045, 0.5));
    cylinderTubeSub = min(cylinderTubeSub, dCappedCylinder(rotate(translate(tubePos, vec3(0., tunnelR*0.95, tunnelSize*1.4)), vec3(90, 0, 0)), vec2(tunnelR*0.045, 0.5)));
    cylinderTube = min(cylinderTube, cylinderTubeSub);
    vec3 decoPos = tunnelPos;
    decoPos.x = onRep(decoPos,3.0).x;

    float decoBox = dBox(rotate(translate(decoPos, vec3(0., -tunnelR+0.15, 0.)), vec3(0, 0, 0)), vec3(tunnelSize/10., 0.15, tunnelSize*0.44)); 
    decoBox = max(-cylinderZ, decoBox);
    
    vec3 partitionPos = tunnelPos;
    partitionPos.xy = fold(partitionPos.xy, 5.0);
    float partitions = dBox(rotate(translate(partitionPos, vec3(0., tunnelR*0.9, -tunnelSize*2.0)), vec3(90, 0, 0)), vec3(tunnelSize, 0.3, tunnelR*0.15)); 
    
    float tunnel = min(boxZ, boxCenter);
    float unit = tunnel;
    unit = min(unit, cylinderTube);
    unit = min(unit, decoBox);
    unit = min(unit, partitions);

    
    float birdBodyAngle = cos(PI*app_time/4.0)*20.0;
    float birdPosX = snoise(vec3(app_time/10.0, 0.3, 0.4)) * 1.0;
    float birdPosY = sin(PI*app_time/6.0) / 3.0;
    float turnTime = 2.0;
    vec3 birdCenter =  mix(getBirdPos(app_time), getBirdPos(app_time+turnTime), fract(app_time/turnTime));
    vec3 birdDir = vec3(0.0, normalize(birdCenter).x*-90.0, 0.0) + vec3(0.0, 180.0, birdBodyAngle);
    vec3 birdPos = scale(rotate(translate(pos, birdCenter+vec3(birdPosX, birdPosY, 0.0)), birdDir), vec3(0.7));
    
   
    float bird = 0.0;
    
    
    float bird1 = dSphere(scale(translate(birdPos, vec3(0.0, 0.0, 0.0)), vec3(1.0, 0.7, 2.0)), 0.5);
    float bird2 = dSphere(scale(rotate(translate(birdPos, vec3(0.0, 0.0, 0.5)), vec3(30.0, 0.0, 0.0)), vec3(1.0, 0.8, 2.0)), 0.5);
    float birdHead = dSphere(scale(rotate(translate(birdPos, vec3(0.0, 0.6, 1.5)), vec3(00.0, 0.0, 0.0)), vec3(1.0, 1.0, 1.5)), 0.3);
    float birdTail = dRoundCone(rotate(translate(birdPos, vec3(0.0, 0.0, -2.7)), vec3(-90, 0.0, 0.0)), 0.04, 0.2, 1.9);
    float birdBeak = dRoundCone(rotate(translate(birdPos, vec3(0.0, 0.55, 2.7)), vec3(90, 0.0, 0.0)), 0.01, 0.05, 0.8);
    vec3 birdEyePos = rotate(translate(birdPos, vec3(0.0, 0.73, 1.8)), vec3(00.0, 0.0, 90.0));
    birdEyePos.xy = fold(birdEyePos.xy, 2.0);
    float birdEye =  dSphere((translate(birdEyePos, vec3(0.0, 0.16, 0.0))), 0.06);
    
    vec3 birdWingPos = scale(rotate(translate(birdPos, vec3(0.0, 0.25, 0.4)), vec3(0.0, 0.0, 90.0)), vec3(0.5, 1.0, 3.0));
    float wingAngle = birdWingPos.y > 0.0 ? -1.0 : 1.0;
    
    int isWing = sin(app_time*PI/2.0) > PI/5.0 ? 1 : 0;
    wingAngle = isWing == 1 ? (-20.0 + 40.0 * sin(5.*app_time*PI))*wingAngle : (-20.0 + 10.0 * sin(app_time*PI))*wingAngle;
    birdWingPos.xy = fold(birdWingPos.xy, 2.0);
    float birdWing = dRoundCone(rotate(translate(birdWingPos, vec3(0.0, 0.4, 0.0)), vec3(13.0, 0.0, wingAngle)), 0.1, 0.3, 1.7);
    float birdBody = opSmoothUnion(bird1, bird2, 0.4);
    bird = opSmoothUnion(birdBody, birdHead, 0.4);
    bird = opSmoothUnion(bird, birdTail, 0.4);
    bird = opSmoothUnion(bird, birdBeak, 0.1);
    bird = opSmoothUnion(bird, birdEye, 0.03);
    bird = min(bird, birdWing);
    
    
    result = min(unit, bird);
    
    
    if(result >= tunnel)
        materialId = 1;
    if(result >= decoBox)
        materialId = 2;
    if(result >= cylinderTube)
        materialId = 3;
    if(result >= partitions)
        materialId = 4;
    if(result >= birdBody-epsilon || result >= birdTail-epsilon)
        materialId = 5;
    if(result >= birdWing || result >= birdHead)
        materialId = 6;
    if(result >= decoBox)
        materialId = 7;


    return result;
}

vec3 getNormal(vec3 pos)
{
    return normalize(vec3(
        sceneDist(pos + vec3(epsilon, 0., 0.)) - sceneDist(pos - vec3(epsilon, 0., 0.)),
        sceneDist(pos + vec3(0., epsilon, 0.)) - sceneDist(pos - vec3(0., epsilon, 0.)),
        sceneDist(pos + vec3(0., 0., epsilon)) - sceneDist(pos - vec3(0., 0., epsilon))
    ));
}


float genAo(vec3 ro, vec3 rd){
    float k = 1.0;
    float occ = 0.0;
    for(int i=0; i<aoLoopCount; i++){
        float len = 0.15 + float(i) * 0.15;
        float dist = sceneDist(rd * len + ro);
        occ += (len - dist) * k;
        k *= 0.5;
    }
    return saturate(1.0-occ);
}




vec3 doBackground(){
    return vec3(0.1);
}

PBRMaterial doPBRMaterial(int matId, vec3 pos){
    PBRMaterial mat;
    mat.albedo = vec3(1.0);
    //tunnel
    mat.emission = vec3(1.0);
    if(matId == 1){
        mat.albedo = vec3(1.0, 1.0, 1.0);
        mat.metallic = vec3(1.0);
        mat.roughness = 0.6;
        
    }else if(matId == 2){
        mat.albedo = vec3(1.0, 1.0, 1.0);
    }else if(matId == 3){
        mat.albedo = vec3(1.0, 1.0, 0.25);
        mat.metallic = vec3(0.0);
        mat.roughness = 0.1;
        mat.emission = vec3(2.0);
    }else if(matId == 4){
        mat.albedo = vec3(0.7, 0.7, 0.65);
        mat.metallic = vec3(0.0);
        mat.roughness = 0.1;
    }else if(matId == 5){ //bird other
        mat.albedo = vec3(0.4, 0.8, 1.0);
        mat.metallic = vec3(0.0);
        mat.roughness = 0.0;

    }else if(matId == 6){ // bird wing
        mat.albedo = vec3(0.8, 0.9, 1.0);
        mat.metallic = vec3(0.0);
        mat.roughness = 0.0;
    }else if(matId == 7){ // bird heada
        mat.albedo = vec3(41.0/255.0, 13.0/255.0, 4.0/255.0);
        mat.metallic = vec3(0.0);
        mat.roughness = 0.0;
    }
    return mat;
}


vec3 pbr(vec3 surfacePos, vec3 normal, vec3 col, int matId){
    DirectionalLight directionalLight;
    directionalLight.direction = vec3(-1.0, 0.3, -0.4);
    directionalLight.color = vec3(1.0);
    
    directionalLights[0] = directionalLight;
    numDirectionalLights = 1;
    
    PointLight pointLight;
    pointLight.visible_distance = 50.0;
    pointLight.decay = 4.4;
    
    vec3 lightColor = vec3(1.0, 1.0, 1.0);
    vec3 cameraLookDir = normalize(cam.target-cam.position);
    for(int i=0; i<LIGHT_MAX; i++){
        pointLights[i] = pointLight;
        if(i==0){
            pointLights[i].position = cameraLookDir*10.0;
            //pointLights[i].position = cam.target*30.0;
            pointLights[i].color = lightColor;
        }
        if(i==1){
            pointLights[i].position = cameraLookDir*30.0 + 5.0 * sin(app_time*PI);
            vec3 c = vec3(1.0, 1.0, 1.0);
            if(app_time >=4.5 && app_time < 10.0){
                c = vec3(1.0, 0.3, 0.0);
            }else if(app_time >= 24.5 && app_time < 30.0){
                c = vec3(1.0, 0.3, 1.0);
            }
            pointLights[i].color = c;
        
        }
        if(i==2){
            pointLights[i].position = cam.target*50.0 + 5.0 * cos(app_time*PI);
            vec3 c = vec3(0.0, 1.0, 0.0);
            if(app_time >= 4.5 && app_time < 10.0){
                c = vec3(1.0, 1.0, 1.0);
            }else if(app_time >= 24.5 && app_time < 30.0){
                c = vec3(0.0, 0.3, 1.0);
            }
            pointLights[i].color = c;
        }
        if(i==3){
            pointLights[i].position = cam.target*40.0 + 5.0 * sin((app_time+PI)*PI);

            vec3 c = vec3(0.0, 0.0, 1.0);
            if(app_time >= 4.5 && app_time < 10.0){
                c = vec3(1.0, 0.3, 0.0);
            }else if(app_time >= 24.5 && app_time < 30.0){
                c = vec3(0.0, 0.5, 1.0);
            }
            pointLights[i].color = c;
        }
        

    }
    numPointLights = 4;
    PBRMaterial material;
    material = doPBRMaterial(matId , surfacePos);

    
    ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
    vec3 emissive = vec3(0.0);
    
    IncidentLight directLight;
    
    GeometricContext geometry;
    geometry.position = surfacePos;
    geometry.normal = normal;
    geometry.viewDir = normalize(cam.position-surfacePos);
    
    // point light
    for (int i=0; i<LIGHT_MAX; ++i) {
        if (i >= numPointLights) break;
        getPointDirectLightIrradiance(pointLights[i], geometry.position, directLight);
        if (directLight.visible) {
            RE_Direct(directLight, geometry, material, reflectedLight);
        }
    }
    

    
    // directional light
    for (int i=0; i<LIGHT_MAX; ++i) {
        if (i >= numDirectionalLights) break;
        getDirectionalDirectLightIrradiance(directionalLights[i], directLight);
        RE_Direct(directLight, geometry, material, reflectedLight);
    }


    vec3 specular = (reflectedLight.directSpecular + reflectedLight.indirectSpecular);
    vec3 diffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;

    col += specular;
    col += diffuse;
    

    return col;
}

vec3 doColor(vec3 pos, int matId){
    vec3 col;
    
    
    vec3 normal = getNormal(pos);
    float depth = distance(pos, cam.position)/cam.farClip;

    vec3 surfacePos = pos + normal * epsilon;
    float ambient = 0.1;
    vec3 lightDir = vec3(1.0, 1.0, 0.0);

    col += pbr(surfacePos, normal, col, matId);
    col += ambient;
    
    //ambient occlusion
    col *= genAo(surfacePos, normal);

    //postprocess
    col = LinearToGamma(col, 1.1);
        
    float luminance = ( 0.298912 * col.r + 0.586611 * col.g + 0.114478 * col.b );
    
    return col;
}

vec3 fog(vec3 origin, float total_d, vec3 fogColor, float intensity){
    vec3 fog = vec3(1.0-total_d/cam.farClip) * intensity;
    return origin * fogColor * fog;
}


vec3 trace(vec2 uv){
    float camShake = (snoise(vec3(0.0, 0.0, sin(fract(time/4.5)*PI)))+1.0)-0.5;
    camShake /= 100.0;
    cam.position = vec3(cos(app_time/10.0*PI)/5.0, sin(app_time/5.0*PI)/5.0, cos(app_time/10.0*PI)*5.0);
    cam.position += vec3(camShake, camShake, 0.0);

    cam.fov = 120.0;
    cam.farClip = 1000.0;

    float turnTime = 1.0;
    cam.target = mix(getTunnelMoveDirection(app_time), getTunnelMoveDirection(app_time+turnTime), fract(app_time/turnTime));
//    cam.target = vec3(1.0, 0.0, 0.0);
    if(isMove == 0) cam = debugCam;
 
    mat3 ca = setCamLookAt(cam);
    float fov = deg2rad(cam.fov / 2.0);
    
    vec3 ro = cam.position;
    vec3 rd = normalize(ca * vec3(uv.xy, fov));    
    vec3 cur = ro;
    
    //trace
    float result;
    float total_d;
    int savedMaterialId;

    for (int i = 0; i < rayLoopCount; i++)
    {
        result = sceneDist(cur);
        total_d += result;
        if (result < epsilon || distance(cur, cam.position) > cam.farClip){
            savedMaterialId = materialId;
            
            break;
            
        }
        cur += rd * result;
    }
    
    if(result >= epsilon){
        return doBackground();
    }
    
    vec3 col = doColor(cur, savedMaterialId);

    col = brightnessContrast(col, 0.5, 2.6);
    col = fog(col, total_d, vec3(1.0), 1.0);
    
    col *= clamp(vec3(2.0-length(uv)), 0.0, 1.0);

    return col;
}

void main( void ) {
    app_time = fract(time/40.0)*40.0;
    vec2 uv = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
    gl_FragColor = vec4(trace(uv), 1.0);
}