// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// By David Hoskins, 2022.
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// By David Hoskins, 2022.


#define TSIZE 256.
#define TWRAP 255

#define UI0 1597334673U
#define UI1 3812015801U
#define UI2 uvec2(UI0, UI1)
#define UI3 uvec3(UI0, UI1, 2798796415U)
#define UI4 uvec4(UI3, 1979697957U)
#define UIF (1.0 / float(0xffffffffU))


#define SUN_COLOUR vec3(1., .9, .8)
#define FOG_COLOUR vec3(.1, .13, .16)


#define tri(x) abs(fract(x)-.5)*2.
//---------------------------------------------------------------------------------------------------------------
float hash12(uvec2 q)
{
	q *= UI2;
	uint n = (q.x ^ q.y) * UI0;
	return float(n) * UIF;
}
float hash12(vec2 p)
{
	uvec2 q = uvec2(ivec2(p)) * UI2;
	uint n = (q.x ^ q.y) * UI0;
	return float(n) * UIF;
}


vec2 hash21(float p)
{
	uvec2 n = uint(int(p)) * UI2;
	n = (n.x ^ n.y) * UI2;
	return vec2(n) * UIF;
}

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

vec3 hash32(vec2 q)
{
	uvec3 n = uvec3(ivec3(q.xyx)) * UI3;
	n = (n.x ^ n.y ^ n.z) * UI3;
	return vec3(n) * UIF;
}

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

vec3 hash33(vec3 p)
{
	uvec3 q = uvec3(ivec3(p)) * UI3;
	q = (q.x ^ q.y ^ q.z)*UI3;
	return vec3(q) * UIF;
}

float hash13(vec3 p)
{
	uvec3 q = uvec3(ivec3(p)) * UI3;
	uint n = (q.x ^ q.y ^ q.z) * UI0;
	return float(n) * UIF;
}




vec3 cameraPos, cameraTar;
vec3 camRay;
float gTime;
float fade;

#define ANG2 1.33
#define ANG3 1.4
#define ZERO min(0, iFrame)
const mat2 rotMat = mat2(cos(ANG2), sin(ANG3), -sin(ANG3), cos(ANG2)) * 2.;
const vec3 MOON_DIR = normalize(vec3(-.5, .4, -.5));
const vec3 MOON_DIR2 = normalize(MOON_DIR + vec3(.02, -0.002, 0.0));

#define MOUSE_SCRUB

//----------------------------------------------------------------------------------------------------------
// Thanks for palette, iq...
// https://www.shadertoy.com/view/ll2GD3
vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d )
{
    return a + b*cos( 6.28318*(c*t+d) );
}
vec3 getColour(float id)
 {

    return pal(id, vec3(0.25,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.33,0.67) );

}


const int   SEEDS = 50;
const float STEP_SIZE = 150.;
#define SIZE .3

//----------------------------------------------------------------------------------------------------------
vec2 distanceRayPoint(vec3 ro, vec3 rd, vec3 p)
{
    p -= ro;
    float h = dot(p,rd);
    return vec2(length(p-rd*h), h);
}

//----------------------------------------------------------------------------------------------------------
vec3 getOffset(in vec3 id, float si)
{
    return clamp(tri(id+gTime*.1), si*.25, (1.0-si*.25));
}

// Find a single seed at ground position.
// Needs surrounding squares to prevent edge cutoff...

//----------------------------------------------------------------------------------------------------------
vec3 singleSeed(in vec3 ro)
{
    ro.y += gTime*250.0+3.; // A very subtle +3 increase for ground lights to arrive early!

    vec3 p = ro / STEP_SIZE, sm3at = vec3(0);
    float i = 0.0;
    float sb = .1;
    vec3 col = vec3(0);

    vec3 pos = floor(p);
    for (int z = -1; z <= 1; z++)
    {
        for (int y = -1; y <= 1; y++)
        {
            for (int x = -1; x <= 1; x++)
            {
                vec3 p2     = pos;
                p2         += vec3(x, y, z);
                vec3 id     = hash33(p2*19.31);
                float si    = (id.x+id.z)*.3+.04;
                vec3 offset = getOffset(id, si);
                vec3 mat    = getColour(id.x+id.y);
                p2 += offset;
                p2-= p;
                i = dot(p2,p2);
                if (i < si*.2)
                {
                    col += mat*smoothstep(si*.05, 0.0, i)*fade;
                    i= max((i-si*.15),0.0)*4.;
                     col += sin(smoothstep(si*.3,si*.1,i)*3.14)*.2*fade;//* smoothstep(si*.1,si*.15,i);//smoothstep(0.014, .013, i)*fade*.4;
                }
            }
        }
    }


    return col;
}

//----------------------------------------------------------------------------------------------------------
// Render all seeds...
// This uses iq's 'Voxel Edges' renderer, it visits every voxel and adds a randomly positioned point,
// producing a evenly distributed cloud of seeds.
// I believe I was inspired by iapafoto, who does the same here:
// https://www.shadertoy.com/view/Xl2BRR
vec3 floatingSeeds(in vec3 ro, in vec3 rd, in float tmax)
{

    vec2 d;
    ro.y += 250.*gTime; // ...float down

    ro /= STEP_SIZE;
	vec3 pos = floor(ro),
         ri = 1./rd,
		 rs = sign(rd),
		 dis = (pos-ro + 0.5 +rs*.5) * ri,
         sum = vec3(0), col = sum;

	for( int i = 0; i < SEEDS; i++ )
    {
        vec3 id = hash33(pos*19.31);
        float si = (id.x+id.z)*.3+.04;

        vec3 offset = getOffset(id, si);

        d = distanceRayPoint(ro, rd, pos+offset);

        float d2 = d.y * STEP_SIZE - tmax;

        vec3 mat =  getColour(id.x+id.y);

        if (d2 < 0.0 && d.y > 0.0)
        {
            col = mat * pow(smoothstep(si, 0.0,d.x),80.)*15.;
            col *= fade;
            sum += col;
        }
        fade *= .92;
        // step through voxel borders...
		vec3 mm = step(dis.xyz, dis.yzx) * step(dis.xyz, dis.zxy);
		dis += mm * rs * ri;
        pos += mm * rs;

	}

	return sum;
}

//----------------------------------------------------------------------------------------------------------
float moon(vec3 dir)
{
    float d = clamp(pow(max(dot(MOON_DIR, camRay), 0.), 8000.0)*1., 0.0, .002)*1000.;
    float d2 = pow(max(dot(MOON_DIR2, camRay), 0.), 4000.0)*30.;
    return clamp(d-d2, 0.0, 1.);
}


//----------------------------------------------------------------------------------------------------------
vec3 getSky(vec3 dir)
{
	vec3 col = mix(vec3(FOG_COLOUR), vec3(0.01, 0.01,0.03),clamp((1.-exp(-dir.y))*3., 0.0, 1.));

    col += moon(dir)* vec3(.35,.3,.2);
    return col;
}

//----------------------------------------------------------------------------------------------------------
vec3 noiseD(in vec2 x)
{
    vec2 f = fract(x);
    vec2 u = f*f*f*(f*(f*6.0-15.0)+10.0);
    vec2 du = 30.*f*f*(f*(f-2.0)+1.);

	vec4 n = texelFetch(iChannel0, ivec2(floor(x)) & TWRAP, 0);
	return vec3(n.x + n.y * u.x + n.z * u.y + n.w * u.x*u.y,
				du * (n.yz + n.w*u.yx));
}

//----------------------------------------------------------------------------------------------------------
float terrain( in vec2 p, float z)
{

    float a = 0.0;

    p+= vec2(-260,300);

    vec2  d = vec2(0.0);
    float h = length(p);

    // Mountains and flats depending on distance from 0
    float b = smoothstep(0.0, 2000.0, h)*500.0;
    b*= (1.0+smoothstep(3000.0,6000.0,h)*.6);

    p = p*0.0005;

    // Decrease iteration detail with distance..
    int iter = 13-int(log2(z*.05+2.0));

    iter = clamp(iter, 1, 12);

	for (int i = ZERO; i < iter; i++)
	{
       vec3 n = noiseD(p);

        d += n.yz;
        a += b*n.x/(.7+dot(d,d));
		b *= 0.51;
        p = rotMat*p;
        //p -= n.yz*.3;
	}

	return a;
}

//-------------------------------------------------------------------------------------------------------
float map(vec3 p, float z)
{
    return  p.y-terrain(p.xz, z);
}

//-------------------------------------------------------------------------------------------------------
// March the whole scene...
#define FAR 10000.
float rayMarch(in vec3 rO, in vec3 rD, in float t)
{
	float d;
    float adj = .5;// Estimate error adjust starts very small

    for(int j = ZERO; j < 200 && t < FAR; j++)
	{
        d = map(rO + t*rD, t);
        if (d < .01) break;
        t += d * adj;
        adj += .003;
	}

	return t;
}


//----------------------------------------------------------------------------------------------------------
vec3 getNormal(vec3 pos, float ds)
{

    float c = map(pos, 0.);
    vec2 eps_zero = vec2(max(ds, .01), 0.0);
    return normalize(vec3(map(pos + eps_zero.xyy, 0.0),
                          map(pos + eps_zero.yxy, 0.0),
                          map(pos + eps_zero.yyx, 0.0)) - c);
}


//----------------------------------------------------------------------------------------------------------
vec3 getCamera(vec2 uv)
{

	cameraPos = vec3(sin(gTime*.37)*300., 500.+sin(gTime)*250., cos(gTime*.83)*300.);
	cameraTar = cameraPos+vec3(sin(gTime*.5)*150.,sin(gTime*.33+3.3)*90.-7., cos(gTime*.5)*150.);

    vec3 cw = normalize(cameraTar-cameraPos);
	vec3 cu = normalize(cross(cw,vec3(0,1,0)));
	vec3 cv = normalize(cross(cu,cw));
    return normalize(uv.x*cu + uv.y*cv + cw * 1.);
}


//----------------------------------------------------------------------------------------------------------
vec3 lighting(vec3 p, vec3 nor)
{
    float l = max(dot(MOON_DIR, nor), 0.0);
    vec3 ref = reflect(nor, camRay);
    l += pow(max(dot(ref, MOON_DIR), 0.0), 30.0);

    return vec3(l);

}

//----------------------------------------------------------------------------------------------------------
vec3 material(vec3 p, vec3 nor)
{
    vec3 g = texture(iChannel3, p.xz*0.004).xyz;
    vec3 mat = g*.2+vec3(0,0,.035);

    g = texture(iChannel1, p.xz*0.002).xyz;
    g = g*vec3(.15,.16,.1);
    mat  = mix(mat, g, smoothstep(.8, 1.0, nor.y));
    mat  = mix(mat, vec3(.0,.0,0.0), smoothstep(.9, .8, nor.y) *  smoothstep(.7, .8, nor.y));

    return mat+vec3(0.04,0.04,nor.y*.1);
}

//----------------------------------------------------------------------------------------------------------
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    gTime = iTime * .25+272.;
    #ifdef MOUSE_SCRUB
    gTime += iMouse.x*30./iResolution.x;
    #endif
    gTime += hash12(fragCoord)*.005;

    vec2 uv = (fragCoord-iResolution.xy*.5)/iResolution.y;
    camRay = getCamera(uv);


    fade = min(1.0, iTime*.02);

    vec3 col = getSky(camRay);

    float dis = rayMarch(cameraPos, camRay, hash12(fragCoord)*4.);
    float d = 0.0;
    if (dis < FAR)
    {
        vec3 pos = cameraPos+camRay * dis;
        d = dis*.0001;
        vec3 nor = getNormal(pos, d*d*100.);

        // Do all the lighting...
        // I got enough contrast from the texturing/lighting so I left out the shadows...
        col = material(pos,nor) * lighting(pos, nor);
        col += singleSeed(pos);
        col =  mix(FOG_COLOUR, col, exp(-d*d));
    }

    col += floatingSeeds(cameraPos, camRay, dis-hash12(fragCoord)*5.);
    col =  mix(FOG_COLOUR, col, exp(-d*d));
    col = clamp(col, 0.0, 1.0);  // ...Don't want of over egg it.
    col = col*col*(3.0-2.0*col); // ...Stretch for higher contrast
    col *= smoothstep(0.0, 8.0, iTime);

    fragColor = vec4(sqrt(col),1.0);
}
