#define AA 2

struct Ray
{
    vec3 or;
    vec3 dir;
};

const vec2 eps = vec2(0.0001, 0.0000);

float edge(vec2 p0, vec2 p1, vec2 p)
{
    return (p1.x - p0.x) * (p.y - p0.y) - (p.x - p0.x) * (p1.y - p0.y);
}

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

Ray genRay(vec3 pos, vec3 dir, vec2 frag_coord, float aspect) 
{
    vec3 p = vec3(frag_coord - iResolution.xy * 0.5, iResolution.y);
    p.x *= aspect;

    vec3 ww = normalize(dir);
    vec3 uu = cross(ww, vec3(0, 1, 0));
    vec3 vv = cross(uu, ww);

    return Ray(pos, normalize(p.x * uu + p.y * vv + p.z * ww));
}

/// Looked here: https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
uint hash(uint x)
{
    x += (x << 10u);
    x ^= (x >> 6u);
    x += (x << 3u);
    x ^= (x >> 11u);
    x += (x << 15u);
    return x;
}

/// Looked here: https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
uint hash(uvec2 v)
{
    return hash(v.x ^ hash(v.y));
}

/// Looked here: https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
float floatConstruct(uint m)
{
    const uint ieeeMantissa = 0x007fffffu;
    const uint ieeOne       = 0x3f800000u;

    m &= ieeeMantissa;
    m |= ieeOne;

    return uintBitsToFloat(m) - 1.0;
}

/// Looked here: https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
float random(vec2 v)
{
    return floatConstruct(hash((floatBitsToUint(v))));
}

float noise(vec2 p) {
    vec2    ip  = floor(p);
    vec2    fp  = smoothstep(vec2(0), vec2(1), fract(p));
    float   a   = random(ip + vec2(0, 0));
    float   b   = random(ip + vec2(1, 0));
    float   c   = random(ip + vec2(0, 1));
    float   d   = random(ip + vec2(1, 1));
    return mix(mix(a, b, fp.x), mix(c, d, fp.x), fp.y);
}

float sdSphere(vec3 pos, float r) 
{
    return length(pos) - r;
}

float sdPlane(vec3 pos, float d)
{
    return pos.y + d;
}

float sdCircle(vec2 pos, float r)
{
    return length(pos) - r;
}

bool inTriangle(vec2 p0, vec2 p1, vec2 p2, vec2 p) 
{
    float e1 = clamp(edge(p0, p1, p), -1.0, 0.0);
    float e2 = clamp(edge(p1, p2, p), -1.0, 0.0);
    float e3 = clamp(edge(p2, p0, p), -1.0, 0.0);

    // return true;
    // return false;
    return e3 * e2 * e1 < 0.0;
}

float mapInTriangle(vec3 pos)
{
    vec2 v = pos.xz;

    float noise_value = 3.0 * noise(v * .4);

    v           = rotate(v, radians(15.0));
    noise_value += 0.4 * noise(v * 2.9);
    
    v           = rotate(v, radians(15.0));    
    noise_value += 50.0 * noise(v * 0.05);

    v           = rotate(v, radians(15.0));    
    noise_value += 100.0 * noise(v * 0.005);

    return sdPlane(pos, 10.0 - noise_value * 0.15);
}

vec3 calcNormalInTriangle(vec3 p)
{
    return normalize(vec3(
        mapInTriangle(p + eps.xyy) - mapInTriangle(p - eps.xyy),
        mapInTriangle(p + eps.yxy) - mapInTriangle(p - eps.yxy),
        mapInTriangle(p + eps.yyx) - mapInTriangle(p - eps.yyx)
    ));
}

float traceInTriangle(Ray r, float s, float e) 
{
    float depth = s, dist;
    
    const uint max_step = 216u;
    for (uint i = 0u; i < max_step; ++i) {
        dist = mapInTriangle(r.or + r.dir * depth);

        if (dist < eps.x) {
            return depth;
        }

        depth += dist;

        if (depth > e) {
            return e;
        }
    }

    return e;
}

/// Looked here: http://casual-effects.com/research/McGuire2019ProcGen/McGuire2019ProcGen.pdf
vec3 genSkyColor(float x, float y, float f)
{
    y *= 2.0;
    float h = max(0.0, f - y - pow(abs(x - 0.5), 3.0));   
    return vec3(pow(h, 3.0), pow(h, 7.0), 0.2 + pow(max(0.0, h - 0.1), 10.0)) * 1.5;
}

vec3 renderinginInTriangle(Ray ray)
{
    const float scene_depth = 200.0;

    vec3    col = vec3(0);
    float   t   = traceInTriangle(ray, 0.01, scene_depth);

    if (t < scene_depth) {
        const vec3 ld = normalize(vec3(1, 1, 1));

        vec3 p = ray.or + ray.dir * t;
        vec3 n = calcNormalInTriangle(p);

        col += vec3(0.54, 0.34, 0.65) * max(dot(n, ld), 0.0);
    }

    return col + genSkyColor(ray.dir.x * 0.1, -ray.dir.y, 0.6);
}

float mapOutTriangle(vec3 pos)
{
    return sdPlane(pos, 4.0);
}

float traceOutTriangle(Ray r, float s, float e) 
{
    float depth = s, dist;
    
    const uint max_step = 216u;
    for (uint i = 0u; i < max_step; ++i) {
        dist = mapOutTriangle(r.or + r.dir * depth);

        if (dist < eps.x) {
            return depth;
        }

        depth += dist;

        if (depth > e) {
            return e;
        }
    }

    return e;
}

float quad(vec2 uv, float board) 
{
    vec2 s = vec2(1.0) - step(uv, vec2(board));
    s -= step(vec2(1) - uv, vec2(board));
    return s.x * s.y;
}

vec3 calcSunColor(vec2 p, float t)
{
    float x = p.y * 45.0  + 20.0;
    float s = step(0.5, clamp(sin(clamp(x, 0.0, 3.14 * 8.0)), 0.0, 1.0));

    const float i       = 1.7;
    const vec3  col_1   = vec3(1.0, 0.0, 0.17) * i;
    const vec3  col_2   = vec3(1.0, 0.46, 0.1) * i;

    return mix (
        mix(col_1, col_2, smoothstep(0.0, 1.0, t - 0.1)),
        vec3(0),
        step(0.5,  step(0.0, sdCircle(p - vec2(0., 0.1), 0.7)) + s)
    );
}

vec3 drawSun(vec2 p, float t)
{
    const float bs = 1.0 / 128.0;

    vec3 sum = vec3(0);

    sum += calcSunColor(p + vec2(-bs * 4.0, 0.0), t) * 0.05;
    sum += calcSunColor(p + vec2(-bs * 3.0, 0.0), t) * 0.09;
    sum += calcSunColor(p + vec2(-bs * 2.0, 0.0), t) * 0.12;
    sum += calcSunColor(p + vec2(-bs, 0.0), t) * 0.15;
    sum += calcSunColor(p, t) * 0.16;
    sum += calcSunColor(p + vec2(bs, 0.0), t) * 0.15;
    sum += calcSunColor(p + vec2(bs * 2.0, 0.0), t) * 0.12;
    sum += calcSunColor(p + vec2(bs * 3.0, 0.0), t) * 0.09;
    sum += calcSunColor(p + vec2(bs * 4.0, 0.0), t) * 0.05;

    sum += calcSunColor(p + vec2(0.0, -bs * 4.0), t) * 0.05;
    sum += calcSunColor(p + vec2(0.0, -bs * 3.0), t) * 0.09;
    sum += calcSunColor(p + vec2(0.0, -bs * 2.0), t) * 0.12;
    sum += calcSunColor(p + vec2(0.0, -bs), t) * 0.15;
    sum += calcSunColor(p, t) * 0.16;
    sum += calcSunColor(p + vec2(0.0, bs), t) * 0.15;
    sum += calcSunColor(p + vec2(0.0, bs * 2.0), t) * 0.12;
    sum += calcSunColor(p + vec2(0.0, bs * 3.0), t) * 0.09;
    sum += calcSunColor(p + vec2(0.0, bs * 4.0), t) * 0.05;

    return calcSunColor(p, t) + sum * 1.2;
}

vec3 renderingOutTriangle(Ray ray, vec2 ps, vec2 uv)
{   
    const float scene_depth = 35.0;

    vec3    col = vec3(0);
    float   t   = traceOutTriangle(ray, 0.01, scene_depth);

    if (t < scene_depth) {
        vec2 p = fract((ray.or.xz + ray.dir.xz * t) * 0.3);

        const float b1 = 0.01;
        const float b2 = 0.05;

        col += vec3(mix(vec3(0.0), vec3(0.54, 0.34, 0.65), quad(p, b1) - quad(p, b2)));
        col *= vec3(1.0 - t / scene_depth) * 0.5;
    } 

    return mix(col, drawSun(ps, uv.y), (t / scene_depth) * uv.y * 0.5);
}

/// Looked here: https://www.shadertoy.com/view/lslGzl
vec3 whitePreservingLumaBasedReinhardToneMapping(vec3 color, float white)
{
#if 0
    if (length(color) < eps.x) {
        return color;
    }
#endif

    const float inv_gamma = 1.0 / 2.2;

	float luma              = dot(color, vec3(0.2126, 0.7152, 0.0722));
	float tone_mapped_luma  = luma * (1. + luma / (white*white)) / (1. + luma);

	color *= tone_mapped_luma / luma;

#if 0
    return pow(color, vec3(inv_gamma));
#else 
    return sqrt(color);
#endif
}

void mainImage(out vec4 color, vec2 coord)
{
    const float s   = 0.5;
    const vec2  p0  = vec2(-s, -s);
    const vec2  p1  = vec2(0.0, s);
    const vec2  p2  = vec2(s, -s); 

    vec3 col = vec3(0);

    float   aspect  = iResolution.x / iResolution.y;
    vec2    uv      = coord / iResolution.xy;

    vec2 p = uv * 2.0 - 1.0;
    p.x *= aspect;

#if AA != 0
    const vec2  inv_aa          = vec2(1.0 / float(AA));
    const float two_aa          = float(2 * AA);
    const float pow_2_aa        = two_aa * two_aa;
    const float inv_pow_2_aa    = 1.0 / pow_2_aa;

    vec2 pixs = vec2(1.0) / iResolution.xy;
#endif

    if (inTriangle(p0, p1, p2, p)) {
#if AA == 0
        Ray ray = genRay(vec3(0, 20, -iTime * 10.0), vec3(0, 0., -1), coord, aspect);
        col += renderinginInTriangle(ray);
#else 
        for (int i = -AA; i < AA; ++i) {
            for (int j = -AA; j < AA; ++j) {
                vec2    m   = vec2(i, j) * inv_aa;
                Ray     ray = genRay(vec3(0, 20, -iTime * 10.0), vec3(m * pixs, -1), coord, aspect);

                col += renderinginInTriangle(ray);
            }
        }

        col *= inv_pow_2_aa;
#endif
    } else {
#if AA == 0
        Ray ray = genRay(vec3(0, 0, -iTime * 1.8), vec3(0, 0, -1), coord, aspect);
        col += renderingOutTriangle(ray, p, uv);
#else 
        for (int i = -AA; i < AA; ++i) {
            for (int j = -AA; j < AA; ++j) {
                vec2    m   = vec2(i, j) * inv_aa;
                Ray     ray = genRay(vec3(0, 0, -iTime * 1.8), vec3(m * pixs, -1), coord, aspect);

                col += renderingOutTriangle(ray, p, uv);
            }
        }

        col *= inv_pow_2_aa;
#endif
    }

   color = vec4(whitePreservingLumaBasedReinhardToneMapping(col, 1.0), 1.0);
}
