#define getNormal getNormalHex
#define V vec3
#define W vec2
#define F float
#define FAR 330.
#define INFINITY 1e32
#define t iTime
#define mt iChannelTime[1]

//#define FOG 1.

#define PI 3.14159265
//#define TAU (2*PI)
#define PHI (1.618033988749895)

F
    Z = 0.,
    J = 1.,
	vol = 0.,
	noise = 0.;

float hash12(vec2 p) {
	float h = dot(p,vec2(127.1,311.7));
    return fract(sin(h)*43758.5453123);
}
float noise_3(in vec3 p) {
    vec3 i = floor(p);
    vec3 f = fract(p);
	vec3 u = f*f*(3.0-2.0*f);

    vec2 ii = i.xy + i.z * vec2(5.0);
    float a = hash12( ii + vec2(0.0,0.0) );
	float b = hash12( ii + vec2(1.0,0.0) );
    float c = hash12( ii + vec2(0.0,1.0) );
	float d = hash12( ii + vec2(1.0,1.0) );
    float v1 = mix(mix(a,b,u.x), mix(c,d,u.x), u.y);

    ii += vec2(5.0);
    a = hash12( ii + vec2(0.0,0.0) );
	b = hash12( ii + vec2(1.0,0.0) );
    c = hash12( ii + vec2(0.0,1.0) );
	d = hash12( ii + vec2(1.0,1.0) );
    float v2 = mix(mix(a,b,u.x), mix(c,d,u.x), u.y);

    return max(mix(v1,v2,u.z),.0);
}


float B(vec3 x)
{
    float r = 0.0;
    float w = 1.0, s = 1.0;
    for (int i=0; i<5; i++)
    {
        w *= 0.5;
        s *= 2.0;
        r += w * noise_3(s * x);
    }
    return r;
}

#define fromRGB(a, b, c) vec3(F(a), F(b), F(c)) / 255.;

vec3
    light = vec3(0. ,0., 1.),
	lightDir;

vec3 lightColour = normalize(vec3(1.8, 1.0, 0.3) );

vec3 saturate(vec3 a) { return clamp(a, 0.0, 1.0); }
vec2 saturate(vec2 a) { return clamp(a, 0.0, 1.0); }
float saturate(float a) { return clamp(a, 0.0, 1.0); }

vec3 Rep( vec3 p, vec3 c )
{
    return mod(p,c)-0.5*c;
}

// Repeat only a few times: from indices <start> to <stop> (similar to above, but more flexible)
float pModInterval1(inout float p, float size, float start, float stop) {
	float halfsize = size*0.5;
	float c = floor((p + halfsize)/size);
	p = mod(p+halfsize, size) - halfsize;
	if (c > stop) { //yes, this might not be the best thing numerically.
		p += size*(c - stop);
		c = stop;
	}
	if (c <start) {
		p += size*(c - start);
		c = start;
	}
	return c;
}



float smin( float a, float b, float k )
{
    float res = exp( -k*a ) + exp( -k*b );
    return -log( res )/k ;
}

void pR(inout vec2 p, float a) {
	p = cos(a)*p + sin(a)*vec2(p.y, -p.x);
}

float opU2( float d1, float d2 ) {
    if (d1 < d2) return d1;
    return d2;
}

vec3 opU2( vec3 d1, vec3 d2 ) {
    if (d1.x < d2.x) return d1;
    return d2;
}

struct geometry {
    float dist;
    float materialIndex;
    float specular;
    float diffuse;
    vec3 color;
    float mirror;
};

geometry geoU(geometry g1, geometry g2) {
    if (g1.dist < g2.dist) return g1;
    return g2;
}

vec3 opS2( vec3 d1, vec3 d2 ){
    if (-d2.x > d1.x) return -d2;
    return d1;
}

vec3 opI2( vec3 d1, vec3 d2 ) {
 	if (d1.x > d2.x) return d1;
    return d2;
}

// Maximum/minumum elements of a vector
float vmax(vec2 v) {
	return max(v.x, v.y);
}

float vmax(vec3 v) {
	return max(max(v.x, v.y), v.z);
}

float vmax(vec4 v) {
	return max(max(v.x, v.y), max(v.z, v.w));
}

// Sign function that doesn't return 0
float sgn(float x) {
	return (x<0.)?-1.:1.;
}

vec2 sgn(vec2 v) {
	return vec2((v.x<0.)?-1.:1., (v.y<0.)?-1.:1.);
}


// Repeat space along one axis. Use like this to repeat along the x axis:
// <float cell = pMod1(p.x,5);> - using the return value is optional.
float pMod1(inout float p, float size) {
	float halfsize = size*0.5;
	float c = floor((p + halfsize)/size);
	p = mod(p + halfsize, size) - halfsize;
	return c;
}


// Repeat in two dimensions
vec2 pMod2(inout vec2 p, vec2 size) {
	vec2 c = floor((p + size*0.5)/size);
	p = mod(p + size*0.5,size) - size*0.5;
	return c;
}
// Repeat around the origin by a fixed angle.
// For easier use, num of repetitions is use to specify the angle.
float pModPolar(inout vec2 p, float repetitions) {
	float angle = 2.*PI/repetitions;
	float a = atan(p.y, p.x) + angle/2.;
	float r = length(p);
	float c = floor(a/angle);
	a = mod(a,angle) - angle/2.;
	p = vec2(cos(a), sin(a))*r;
	// For an odd number of repetitions, fix cell index of the cell in -x direction
	// (cell index would be e.g. -5 and 5 in the two halves of the cell):
	if (abs(c) >= (repetitions/2.)) c = abs(c);
	return c;
}

// Mirror at an axis-aligned plane which is at a specified distance <dist> from the origin.
float pMirror (inout float p, float dist) {
	float s = sgn(p);
	p = abs(p)-dist;
	return s;
}

vec2 pMirrorOctant (inout vec2 p, vec2 dist) {
	vec2 s = sgn(p);
	pMirror(p.x, dist.x);
	pMirror(p.y, dist.y);
	if (p.y > p.x)
		p.xy = p.yx;
	return s;
}
// Box: correct distance to corners
float fBox(vec3 p, vec3 b) {
	vec3 d = abs(p) - b;
	return length(max(d, vec3(0))) + vmax(min(d, vec3(0)));
}

// Same as above, but in two dimensions (an endless box)
float fBox2Cheap(vec2 p, vec2 b) {
	return vmax(abs(p)-b);
}

float fCross(vec3 p, vec3 size) {
    float obj = fBox(p, size);
    obj = opU2(obj, fBox(p, size.zxy));
    obj = opU2(obj, fBox(p, size.yzx));

               return obj;
}


float fSphere(vec3 p, float r) {
	return length(p) - r;
}


geometry map(vec3 p) {
    //p.y += sin(t * 1.+ p.z / 10.) * 3. + sin(p.x / 3.);
    vec3 bp = p;
    vec3 fp = p;
    vec3 op = p;
    float localNoise = B(p / 15.) * 5.;


    p.y -= localNoise;

    // ----------
    geometry box;


    bp.y += -23.;
    bp.z += -10. - t * 20.;

    pR(bp.xz, t);
    pR(bp.yx, t * 4.6);

    pModPolar(bp.xy, 3.);
    pMirrorOctant(bp.xz, vec2(.3) + sin(t / 4.) / 2.);
    bp.x *= 1.4;

    box.dist = fBox(bp, vec3(4., 9., 1.) * .6 + vol);
    box.dist = mix(box.dist, fSphere(bp, 2. + localNoise * .4), 1. - min(vol + t * 0.01, 1.));
    box.materialIndex = 4.;
   // box.space = bp;
    box.color = vec3(1.7) + sin(t * 14.);
    box.diffuse = 2.;
    box.specular = 4.;
    box.mirror = .6;


	// ------------
    geometry floor;

    vec3 floorP = p;

    floorP.y += -5. - min(floorP.z * 0.005 , 5.) - sin(p.z * 0.01) * 3.;
    floor.dist = fBox2Cheap(floorP.xy, vec2(128., 2.5)),
    floor.materialIndex = 0.;
    //floor.space = p;
    floor.color = vec3(1., .8, .6);//* localNoise;
    floor.diffuse = 8.;
    floor.specular = 4.0;
    floor.mirror = 0.;

    // ------------
    geometry water;

    fp = op;
    fp.y -= 11. ;//+ sin(localNoise * 0.3) * 0.2 + 0.1 * (sin(fp.z + localNoise) + sin(fp.x));
    water.dist = fBox2Cheap(fp.xy, vec2(128., 3.)),
    water.materialIndex = 5.;
    //water.space = fp;
    water.color = fromRGB(126, 165, 179);
    water.diffuse = 2.;
    water.specular = 17.;
    water.mirror = 0.3;

    // ----------
    geometry obj;

    p.xz -= 25.;
    p.y += 140. - min(p.z / 4., 140.);

    vec2 pM = pMod2(p.xz, vec2(50.));

    pMirrorOctant(p.zy,
                  vec2(
                      1. * mod(pM.x, 14.),
                      5. + ceil(13. * (sin(pM.x) * 3.+ 1.))
                  )
                 );
    pMirrorOctant(p.xz, vec2(13., 18. + mod(pM.y * 5., 16.)));

    pR(p.zy, 1.17 + p.x / 10. );

    p.x += 2.5 ;

    pModPolar(p.xz, 12.5 - (sin(pM.x) * 10.+ (sin(1. / 3.) * 10.)));

    pMirrorOctant(p.zy, vec2(8.4, 6.));
    pMirrorOctant(p.xy, vec2(3.5, 5.));

    p.yx += 2.;
    //
    //
    p.x += sin(p.z / 100.) * 40.;

    obj.dist = fBox2Cheap(p.xy, vec2(4.4, 2.5));
    obj.color = vec3(0., 1., 0.);
    //obj.space = p;
    obj.color = vec3(1.);
    obj.diffuse = 5.;
    obj.specular = 0.2;
    obj.mirror = 0.;
    //p.z -= 3.;

    geometry obj2;
    obj2.dist = fBox(p, vec3(6., 7.4, 1.3));
    obj2.color = vec3(1., 0., 1.);
    //obj2.space = p;
    obj2.color = vec3(1.);
    obj2.diffuse = 8.;
    obj2.specular = 1.;
    obj2.mirror = 0.;
    obj2.dist = smin(obj.dist, obj2.dist, sin(p.z / 10.) / 3. + .5);

    floor.dist = smin(obj.dist, floor.dist, .45);

    p = op;
    p.x -= 2.;

    p = mod(p, 5.);// p.z = mod(p.z, 12.) - .1;//sin(p.z);
    p.y -= 25.;
    //;;p.z -= 100.;
    geometry obj3;
    obj3.dist = fBox(p, vec3(3., 7.4,1.3));
    obj3.color = vec3(1., 0., 1.);
    //obj3.space = p;
    obj3.color = vec3(1.);
    obj3.diffuse = 8.;
    obj3.specular = 1.;
    obj3.mirror = 0.;

    obj = geoU(obj, obj2);
    obj = geoU(obj, obj3);
    obj = geoU(obj, floor);
    obj = geoU(obj, water);
    obj = geoU(obj, box);


    //-----
    p = op;
    p.y -= 5.;
    p.xz = mod(p.xz, 40.) - 20.;
    if (op.z > 1030.) {
        p.x += p.z * 0.1;
    pMirrorOctant(p.yz, vec2(12.5, 20.));
    pModPolar(p.zx, 5.);

    obj2.dist = fBox(p, vec3(1., 15., 6.));
    obj2.mirror = .2;
    obj2.color = vec3(1., 0., 1.);

    obj2.color += ceil(sin(mod(op.x / 3. + op.z / 2.- t * 4., 5.))) * 4.;
    obj2.specular = 3.;

    obj = geoU(obj, obj2);
    }
    return obj;
}


float t_min = 0.001;
float t_max = FAR;
const int MAX_ITERATIONS = 70;

geometry trace(vec3 o, vec3 d, int maxI) {

    float omega = 1.3;
    float t = t_min;
    float candidate_error = INFINITY;
    float candidate_t = t_min;
    float previousRadius = 0.;
    float stepLength = 0.;
    float pixelRadius = 1. / 250.;
    float functionSign = map(o).dist < 0. ? -1. : +1.;
    geometry mp;

    for (int i = 0; i < MAX_ITERATIONS; ++i) {
        if (maxI > 0 && i > maxI) break;
        mp = map(d * t + o);
        float signedRadius = functionSign * mp.dist;
        float radius = abs(signedRadius);
        bool sorFail = omega > 1. &&
        (radius + previousRadius) < stepLength;
        if (sorFail) {
            stepLength -= omega * stepLength;
            omega = 1.;
        } else {
        stepLength = signedRadius * omega;
        }
        previousRadius = radius;
        float error = radius / t;
        if (!sorFail && error < candidate_error) {
            candidate_t = t;
            candidate_error = error;
        }
        if (!sorFail && error < pixelRadius || t > t_max) break;
        t += stepLength;
   	}

    mp.dist = candidate_t;

    if (
        (t > t_max || candidate_error > pixelRadius)
    	) mp.dist = INFINITY;


    return mp;
}


float softShadow(vec3 ro, vec3 lp, float k) {
    const int maxIterationsShad = 8;
    vec3 rd = (lp - ro); // Unnormalized direction ray.

    float shade = 1.;
    float dist = 4.5;
    float end = max(length(rd), .01);
    float stepDist = end / float(maxIterationsShad);

    rd /= end;
    for (int i = 0; i < maxIterationsShad; i++) {
        float h = map(ro + rd * dist).dist;
        //shade = min(shade, k*h/dist);
        shade = min(shade, smoothstep(0.0, 1.0, k * h / dist));
        dist += min(h, stepDist * 2.);
        if (h < 0.001 || dist > end) break;
    }
    return min(max(shade, 0.6), 1.0);
}


vec3 getNormalHex(vec3 pos)
{
	float d=map(pos).dist, e = .001;
	return normalize(
        vec3(
            map(
                pos+vec3(e,0,0)).dist-d,
                map(pos+vec3(0,e,0)).dist-d,
                map(pos+vec3(0,0,e)).dist-d
        	)
    	);
}

float getAO(vec3 hitp, vec3 normal, float dist)
{
    return clamp(map(hitp + normal * dist).dist / dist, .4, 1.);
}

vec3 clouds(vec3 rd, vec3 ro) {
    vec2 uv = rd.xz / rd.y;
   //ro.z /= 2.;
    vec3 clouds = vec3(
        B(
            vec3(
                uv + vec2(0., ro.z  * .01), 9.
            )
        ) * 1.6

    );
 	clouds = pow(clouds, vec3(2.)) + vec3(sin(uv.y) / 2. + .5, .5, 0.);
    clouds.b += sin(ro.z * 0.01);
    return clouds * max(0., rd.y);
}


vec3 Sky(in vec3 rd, bool showSun, vec3 lightDir, vec3 ro)
{

    float sunSize = 3.5;
    float sunAmount = max(dot(rd, lightDir), 0.4);
    float v = pow(1.2 - max(rd.y, 0.0), 1.1);
    vec3 cl = vec3(0.);//fromRGB(0,136,254);
    //cl.b *= sin(p.z * 0.3);
    vec3 sky = mix(cl, vec3(.1, .2, .3) * 1., v);

    sky += lightColour * sunAmount * sunAmount * 1. + lightColour * min(pow(sunAmount, 122.0)* sunSize, 0.2 * sunSize);
    sky += vec3(3., 0., 0.) * rd.y;
    return clamp(sky, 0.0, 1.0) + clouds(rd, ro);;
}

vec3 doColor( in vec3 sp, in vec3 rd, in vec3 sn, in vec3 lp, geometry obj) {
	vec3 sceneCol = vec3(.0);
    lp = sp + lp;
    vec3 ld = lp - sp; // Light direction vector.
    float lDist = max(length(ld / 2.), 0.001); // Light to surface distance.
    ld /= lDist; // Normalizing the light vector.

    // Attenuating the light, based on distance.
    float atten = 1. / (1.0 + lDist * 0.025 + lDist * lDist * 0.2);

    // Standard diffuse term.
    float diff = max(dot(sn, ld), obj.diffuse);
    // Standard specualr term.
    float spec = max(dot(reflect(-ld, sn), -rd), obj.specular / 2.);


    // Coloring the object. You could set it to a single color, to
    // make things simpler, if you wanted.
    vec3 objCol = obj.color;//getObjectColor(sp, sn, obj);

    // Combining the above terms to produce the final scene color.
    //sceneCol += ();// * atten;

    // Return the color. Done once every pass... of which there are
    // only two, in this particular instance.

    return objCol * (diff + .15) * spec * .1;
}




void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    F 	mat = 0.,
        camShY = 0.;

    vec2 uv = fragCoord.xy / iResolution.xy - .5;

    if (abs(uv.y) > .35) {
     	fragColor *= 0.;
        return;
    }

    uv *= 4.;

    light = vec3(0., 77., 100.);

    vec3
        vuv = vec3(cos(t) / 6., 1., sin(t) * .3 ), // up
    	ro = vec3(-2. + sin(t),  22. + camShY, t * 20.);// + vec3(iMouse.x / 20.,iMouse.y / 10. - 1., 1.); // pos

    //ro.z += camShY;
    vec3
        vrp =  vec3(sin(t), + sin(t * 2.), +11.) + ro +
        	vec3(
                -2.,
                -4. + sin(t) / 3.,
                0. + sin(t / 3.) / 4.), // lookat    */

    	vpn = normalize(vrp - ro),
    	u = normalize(cross(vuv, vpn)),
    	rd = normalize(
            vpn + uv.x * u * iResolution.x/iResolution.y + uv.y * cross(vpn, u)
        ),
        hit,
        ord = rd;

    vec3 sceneColor = vec3(0.);

    geometry tr = trace(ro, rd, 0);

    //float fog = smoothstep(FAR * FOG, 0., tr.dist) * 1.;
    hit = ro + rd * tr.dist;

    vec3 sn = getNormal(hit);

    float sh = softShadow(hit, hit + light, 3.);

    float
        ao = getAO(hit, sn, .2);

    ao *= saturate(getAO(hit + sn * .2, sn, .5));
    ao *= saturate(getAO(hit + sn * 1., sn, 3.0));

	vec3 sky = Sky(rd, true, normalize(light), ro) * 1.;

    if (tr.dist < FAR) {
        sceneColor = (doColor(hit, rd, sn, light, tr) * 1.) * 1.;
        sceneColor *= ao;
        sceneColor *= sh;
        sceneColor = mix(sceneColor, sky, saturate(tr.dist * 4. / FAR));
        sceneColor = mix(sceneColor, lightColour, 0.1);

        if (tr.mirror > 0.) {
            float mirror = tr.mirror;
            vec3 refSceneColor = sceneColor;
            rd = reflect(rd, sn);// + sin(t));

            tr = trace(hit + rd * .02, rd, 99);
            if (tr.dist < FAR) {
                hit = hit + rd * tr.dist;
                sn = getNormal(hit);
                refSceneColor = mix(sceneColor, abs(doColor(hit, rd, sn, light, tr)), mirror);
            } else {
             	sky = Sky(rd, true, normalize(light), ro);
                refSceneColor = mix(refSceneColor, sky, mirror * 2.);

            }

            sceneColor = mix(sceneColor, refSceneColor, mirror);

        } else {
            sceneColor = mix(sceneColor, sky, .0);
        }

    } else {
        sceneColor = sky;
    }

    if (t < 20.) {
        float ot = min(t, 20.),den1 = 0., f, distC = 1.0;

        vec3 steamColor1 = vec3(1.4);

        vec3 rro;

        ro.z /= 8.;

        for (float i = 0.; i <= 10.; i++) {

            rro = ro + ord * distC;// + steamColor2;

            f = B(rro);
            f *= 1.8;
            den1 += pow(f, 2.) * .004 * (20. - ot) / 3.;


            distC += .01;
            if (distC > tr.dist) break;// ro += rd * distC;
        }

        sceneColor = mix(sceneColor, steamColor1,  clamp(den1, 0., 1.) * 2.);

    }

    fragColor = vec4(clamp(sceneColor * (1. - length(uv) / 3.5), 0.0, 1.0), 1.0);
    fragColor = pow(fragColor, vec4(1.2));
}
