#version 330 core
out vec4 FragColor;

uniform float time;
uniform vec2 resolution;
uniform vec3 position;
uniform vec2 viewDir;
uniform vec3 user;

uniform float act;
uniform float rayMaxDistance;
uniform sampler2D texture02;
uniform sampler2D textsTexture;

uniform vec3 introCubePosition;
uniform vec3 introTextPosition;

uniform vec3 cameraPositionU;
uniform vec3 cameraLookAtU;

uniform float tunnelObstacleScale;

#define PI 3.14159265359
#define RGB(r, g, b) vec3(r / 255.0, g / 255.0, b / 255.0)

float time2 = time / 2;
float time3 = time / 3;
float time4 = time / 4;
float time5 = time / 5;

/*
 *  act = 1, tunnel,  rayMaxDistance = 50
 *  act = 2, julian, cubed, rayMaxDistance = 50
 *  act = 3, text box
 *  act = 4, twisted column, rayMaxDistance = 100
 *  act = 5, shadow testing
 *  act = 6, intro, rayMaxDistance = 10
 *  act = 7, jumping sausages, rayMaxDistance = 300 or more
 *  act = 8, outro, rayMaxDistance = 10
 */

const int NUMBER_OF_ACTS = 8;

float cameraFov = 0.75;

const vec3 cameraPositions[NUMBER_OF_ACTS] = vec3[NUMBER_OF_ACTS]
(
    vec3(0.0, 0.0, 5.0),
    vec3(0.0, 0.0, 20.0),
    vec3(0.0, 0.0, 2.0),
    vec3(0.0, 5.0, 10.0),
    vec3(0.0, 2.0, 4.0),
    vec3(0.0, 0.0, 5.0),
    vec3(0.0, 0.0, 5.0),
    vec3(0.0, 0.0, 5.0)
);
vec3 cameraPosition = cameraPositions[int(act) - 1]; 

const vec3 cameraLookAts[NUMBER_OF_ACTS] = vec3[NUMBER_OF_ACTS]
(
    vec3(0.0, 0.0, -1.0),
    vec3(0.0, 0.0, 0.0),
    vec3(0.0, 0.0, 0.0),
    vec3(0.0, 0.0, 0.0),
    vec3(0.0, 0.0, 0.0),
    vec3(0.0, 0.0, -1.0),
    vec3(0.0, 0.0, -1.0),
    vec3(0.0, 0.0, -1.0)
);
vec3 cameraLookAt = cameraLookAts[int(act) - 1]; 

const vec3 omniLightPositions[NUMBER_OF_ACTS] = vec3[NUMBER_OF_ACTS]
(
    vec3(0.0, 20.0, 0.0),
    vec3(0.0, 20.0, 20.0),
    vec3(0.0, 10.0, 20.0),
    vec3(0.0, 10.0, 20.0),
    vec3(0.0, 10.0, -5.0),
    vec3(0.0, 20.0, 20.0),
    vec3(0.0, 20.0, 20.0),
    vec3(0.0, 10.0, 10.0)
);
vec3 omniLightPosition = omniLightPositions[int(act) - 1]; 

const float RAY_MAX_STEPS = 128.0;
const float RAY_MIN_THRESHOLD = 0.001;
const float RAY_MAX_THRESHOLD = 0.005;
const float RAY_MAX_THRESHOLD_DISTANCE = 0.5;


vec3 fogColor = vec3(0.5);
float fogIntensity = 0.00;

//palette
#define YELLOW vec3(0.98, 0.92, 0.17)
#define RED vec3(0.96, 0.15, 0.54)
#define PINK vec3(0.92, 0.0, 1.0)
#define BLUE vec3(0.09, 0.52, 0.97)
#define DARK vec3(0.24, 0.08, 0.30)
#define BLACK vec3(0.00, 0.00, 0.00)
#define WHITE vec3(1.00, 1.00, 1.00)

struct vec2Tuple {
    vec2 first;
    vec2 second;
};

struct vec3Tuple {
    vec3 first;
    vec3 second;
};

struct textureOptions {
    int index;
    vec3 baseColor;
    vec3 offset;
    vec3 scale;
    bool normalMap;
};

struct ambientOptions {
    vec3 color;
    float strength;
};

struct diffuseOptions {
    vec3 color;
    float strength;
};

struct specularOptions {
    vec3 color;
    float strength;
    float shininess;
};

struct pointLight {
    vec3 position;
    vec3 color;
    float strength;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float constant;
    float linear;
    float quadratic;
};

struct shadowOptions {
    bool enabled;
    float lowerLimit;
    float upperLimit;
    float limit;
    float hardness;
};

struct aoOptions {
    bool enabled;
    float limit;
    float factor;
};

struct material {
    ambientOptions ambient;
    diffuseOptions diffuse;
    specularOptions specular;
    shadowOptions shadow;
    aoOptions ao;
    textureOptions texture;
};

struct entity {
    bool needNormals;
    vec4 trap;
    float dist;
    vec3 point;
    vec3 normal;
    material material;
};

struct hit {
    float steps;
    float dist;

    entity entity;
};
const int NUMBER_OF_POINT_LIGHTS = 2;
pointLight pointLights[NUMBER_OF_POINT_LIGHTS];

float rand(vec2 co){
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

float hash(vec2 p) {
     return fract(sin(1.0 + dot(p, vec2(127.1, 311.7))) * 43758.545);
}

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 noise2D(in vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    // Four corners in 2D of a tile
    float a = rand(i);
    float b = rand(i + vec2(1.0, 0.0));
    float c = rand(i + vec2(0.0, 1.0));
    float d = rand(i + vec2(1.0, 1.0));

    vec2 u = f * f * (3.0 - 2.0 * f);

    return mix(a, b, u.x) +
            (c - a)* u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

float fbm2D(vec2 point, float amplitude, float gain, float lacunarity, int octaves) {
    float value = 0.0;
    vec2 p1 = point;
    for (int i = 0; i < octaves; i++) {
        value += amplitude * noise2D(p1);
        p1 *= lacunarity;
        amplitude *= gain;
    }
    return value;
}

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) ) );
}

float fbm3D(vec3 P, float frequency, float lacunarity, int octaves, float addition) {
    float t = 0.0;
    float amplitude = 1.0;
    float amplitudeSum = 0.0;
    float frequency1 = frequency;
    for(int k = 0; k < octaves; k++)
    {
        t += min(snoise(P * frequency1) + addition, 1.0) * amplitude;
        amplitudeSum += amplitude;
        amplitude *= 0.5;
        frequency1 *= lacunarity;
    }
    return t / amplitudeSum;
}

float map(float value, float inMin, float inMax, float outMin, float outMax) {
    return min(outMax, outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin));
}

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 opSmoothUnion(float d1, float d2) {
    return opSmoothUnion(d1, d2, 0.0);
}

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 opSmoothSubtraction(float d1, float d2) {
    return opSmoothSubtraction(d1, d2, 0.0);
}

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);
}

float opSmoothIntersection(float d1, float d2) {
    return opSmoothIntersection(d1, d2, 0.0);
}

entity opSmoothUnion(entity m1, entity m2, float k, float threshold) {
    float h = opSmoothUnion(m1.dist, m2.dist, k);
    if (smoothstep(m1.dist, m2.dist, h + threshold) > 0.5) {
        m2.dist = h;
        return m2;
    }
    else {
        m1.dist = h;
        return m1;
    }
}

entity opSmoothSubtraction(entity m1, entity m2, float k, float threshold) {
    float h = opSmoothSubtraction(m1.dist, m2.dist, k);
    if (smoothstep(m1.dist, m2.dist, h + threshold) > 0.5) {
        m2.dist = h;
        return m2;
    }
    else {
        m1.dist = h;
        return m1;
    }
}

entity opSmoothIntersection(entity m1, entity m2, float k, float threshold) {
    float h = opSmoothIntersection(m1.dist, m2.dist, k);
    if (smoothstep(m1.dist, m2.dist, h + threshold) > 0.5) {
        m2.dist = h;
        return m2;
    }
    else {
        m1.dist = h;
        return m1;
    }
}

vec3 opTwist(vec3 p, float angle) {
    float c = cos(angle * p.y);
    float s = sin(angle * p.y);
    mat2 m = mat2(c, -s, s, c);
    vec3 q = vec3(m * p.xz, p.y);
    return q;
}

vec3 opBend(vec3 p, float angle) {
    float c = cos(angle * p.y);
    float s = sin(angle * p.y);
    mat2 m = mat2(c, -s, s, c);
    vec3 q = vec3(m * p.xy, p.z);
    return q;
}

vec3 opElongate(vec3 p, vec3 h) {
    return p - clamp(p, -h, h);
}

float sdPlane(vec3 p, vec3 n, float h) {
    return dot(p, n) + h;
}

float sdSphere(vec3 p, float radius) {
    return length(p) - radius;
}

float sdEllipsoid(vec3 p, vec3 r) {
    float k0 = length(p / r);
    float k1 = length(p / (r * r));
    return k0 * (k0 - 1.0) / k1;
}

float sdBox(vec3 p, vec3 b, float r) {   
    vec3 d = abs(p) - b;
    return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0)) - r;
}

float sdTorus(vec3 p, vec2 t) {   
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q) - t.y;
}

float sdCylinder(vec3 p, vec3 c, float r) {
    return length(p.xz - c.xy) - c.z - r;
}

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

float sdRoundCone(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;
}

float sdCapsule(vec3 p, vec3 a, vec3 b, float r) {   
    vec3 pa = p - a, ba = b - a;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    return length(pa - ba * h) - r;
}

float sdHexagon(vec3 p, vec2 h, float r) {
    vec3 q = abs(p);
    return max(q.z - h.y, max((q.x * 0.866025 + q.y * 0.5), q.y) - h.x) - r;
}

float sdPyramid(vec3 p, float h) {
    float m2 = h * h + 0.25;

    p.xz = abs(p.xz);
    p.xz = (p.z > p.x) ? p.zx : p.xz;
    p.xz -= 0.5;

    vec3 q = vec3(p.z, h * p.y - 0.5 * p.x, h * p.x + 0.5 * p.y);

    float s = max(-q.x, 0.0);
    float t = clamp((q.y - 0.5 * p.z) / (m2 + 0.25), 0.0, 1.0);

    float a = m2 * (q.x + s) * (q.x + s) + q.y * q.y;
    float b = m2 * (q.x + 0.5 * t) * (q.x + 0.5 * t) + (q.y - m2 * t) * (q.y - m2 * t);

    float d2 = min(q.y, -q.x * m2 - q.y * 0.5) > 0.0 ? 0.0 : min(a,b);

    return sqrt((d2 + q.z * q.z) / m2) * sign(max(q.z, -p.y));
}

float sdOctahedron(vec3 p, float s) {
    p = abs(p);
    float m = p.x + p.y + p.z - s;
    vec3 q;
    if(3.0 * p.x < m ) q = p.xyz;
    else if(3.0 * p.y < m ) q = p.yzx;
    else if(3.0 * p.z < m ) q = p.zxy;
    else return m*0.57735027;

    float k = clamp(0.5 * (q.z - q.y + s), 0.0, s); 
    return length(vec3(q.x, q.y - s + k, q.z - k)); 
}

float sdBoundingBox(vec3 p, vec3 b, float e, float r) {
    p = abs(p) - b;
    vec3 q = abs(p + e) - e;
    return min(min(
        length(max(vec3(p.x, q.y, q.z), 0.0)) + min(max(p.x, max(q.y, q.z)), 0.0),
        length(max(vec3(q.x, p.y, q.z), 0.0)) + min(max(q.x, max(p.y, q.z)), 0.0)),
        length(max(vec3(q.x, q.y, p.z), 0.0)) + min(max(q.x, max(q.y, p.z)), 0.0)) -r;
}

float sdMandlebulb(vec3 p, vec3 pos, float pwr, float dis, float bail, int it) {
    vec3 z = p + pos;
 
    float dr = 1.0;
    float r = 0.0;
    float power = pwr + dis;
    for (int i = 0; i < it; i++) {
        r = length(z);
        if (r > bail) break;
        
        // convert to polar coordinates
        float theta = acos(z.z/r);
        float phi = atan(z.y,z.x);
        dr =  pow(r, power - 1.0) * power * dr + 1.0;
        
        // scale and rotate the point
        float zr = pow(r, power);
        theta = theta*power;
        phi = phi*power;
        
        // convert back to cartesian coordinates
        z = zr * vec3(sin(theta)*cos(phi), sin(phi)*sin(theta), cos(theta));
        
        z += (p + pos);
    }
    return (0.5 * log(r) * r / dr);
}

float sdGyroid(vec3 p, float t, float b) {
    return abs(dot(sin(p), cos(p.zxy)) - b) - t;
}

float sdMenger(vec3 p) {
    float sz = 1.0;
    float d = sdBox(p, vec3(sz), 0.0);

    float s = 1.0;
    for(int i = 0; i < 3; i++)
    {
        
        vec3 a = mod(p * s, 2.0) - sz;
        s *= 3.0;
        vec3 p1 = sz - 3.0 * abs(a);

        float da = sdBox(p1.xyz, vec3(sz * 2.0, sz, sz), 0.0);
        float db = sdBox(p1.yzx, vec3(sz, sz * 2.0, sz), 0.0);
        float dc = sdBox(p1.zxy, vec3(sz, sz, sz * 2.0), 0.0);
        float c = min(da, min(db, dc)) / s;
        d = max(d, c);
    }

    return d;
}

float sdJulian(vec3 p, out vec4 oTrap, vec4 c) {
    vec4 z = vec4(p, 0.0);
    float md2 = 1.0;
    float mz2 = dot(z, z);

    vec4 trap = vec4(abs(z.xyz), dot(z, z));

    float n = 1.0;
    for(int i = 0; i < 12; i++)
    {
        // dz -> 2·z·dz, meaning |dz| -> 2·|z|·|dz|
        // Now we take the 2.0 out of the loop and do it at the end with an exp2
        md2 *= 4.0 * mz2;
        z = vec4(z.x * z.x - z.y * z.y - z.z * z.z - z.w * z.w,
                 2.0 * z.x * z.y,
                 2.0 * z.x * z.z,
                 2.0 * z.x * z.w ) + c;  

        trap = min(trap, vec4(abs(z.xyz), dot(z, z)));

        mz2 = dot(z, z);
        if(mz2 > 14.0) break;
        n += 1.0;
    }
    
    oTrap = trap;

    return 0.25 * sqrt(mz2 / md2) * log(mz2); 
}

float sdLineSeg(vec3 p, vec3 a, vec3 b, float w) {
    vec3 ap = p - a, ab = b - a;
    float t = clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0);
    vec3 c = a + ab * t;
    return length(p - c) - w;
}

float displacement(vec3 p, vec3 m) {
    return sin(m.x * p.x) * sin(m.y * p.y) * sin(m.z * p.z);
}

float impulse(float x, float k) {
    float h = k * x;
    return h * exp(1.0 - h);
}

float sinc(float x, float k) {
    float a = PI * k * x - 1.0;
    return sin(a) / a;
}

vec3 rotX(vec3 p, float a) {
    float s = sin(a);
    float c = cos(a);
    return vec3(
        p.x,
        c*p.y-s*p.z,
        s*p.y+c*p.z
    );
}

vec3 rotY(vec3 p, float a) {
    float s = sin(a);
    float c = cos(a);
    return vec3(
        c*p.x+s*p.z,
        p.y,
        -s*p.x+c*p.z
    );
}
 
vec3 rotZ(vec3 p, float a) {
    float s = sin(a);
    float c = cos(a);
    return vec3(
        c*p.x-s*p.y,
        s*p.x+c*p.y,
        p.z
    );
}

vec3 rot(vec3 p, vec3 a) {
    return rotX(rotY(rotZ(p, a.z), a.y), a.x);
}

vec3 twistX(vec3 p, float angle) {
    return rotX(p, p.x * angle);
}

vec3 twistY(vec3 p, float angle) {
    return rotY(p, p.y * angle);
}

vec3 twistZ(vec3 p, float angle) {
    return rotZ(p, p.z * angle);
}

vec3 translate(vec3 p, vec3 p1) {
    return p + (p1 * -1.0);
}

vec3 scale(vec3 p, float s) {
    vec3 p1 = p;
    p1 /= s;
    return p1;
} 

vec3Tuple repeat(vec3 p, vec3 size) {
    vec3 c = floor((p + size * 0.5 ) / size);
    vec3 path1 = mod(p + size * 0.5, size) - size * 0.5;
    return vec3Tuple(path1, c);
}

vec3Tuple repeatLimit(vec3 p, float c, vec3 size) {
    vec3 c1 = floor((p + size * 0.5 ) / size);
    vec3 path1 = p - c * clamp(round(p / c), -size, size);
    return vec3Tuple(path1, c1);
}

vec2Tuple repeatPolar(vec2 p, float repetitions) {
	float angle = 2 * PI / repetitions;
	float a = atan(p.y, p.x) + angle / 2.0;
	float r = length(p);
	float c = floor(a / angle);
	a = mod(a, angle) - angle / 2.0;
	vec2 path = 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.0)) {
        c = abs(c);
    } 
	return vec2Tuple(path, vec2(c));
}

entity opUnion(entity m1, entity m2) {
    return m1.dist < m2.dist ? m1 : m2;
}

entity opSubtraction(entity m1, entity m2) {
    if(-m1.dist > m2.dist) {
        m1.dist *= -1.0;
        return m1;
    }
    else {
        return m2;
    }
    
}

entity opIntersection(entity m1, entity m2) {
    return m1.dist > m2.dist ? m1 : m2;
}

vec3 planeFold(vec3 z, vec3 n, float d) {
    vec3 z1 = z;
	z1 -= 2.0 * min(0.0, dot(z1, n) - d) * n;
    return z1;
}

vec3 absFold(vec3 z, vec3 c) {
    vec3 z1 = z;
	z1.xyz = abs(z1.xyz - c) + c;
    return z1;
}

vec3 sierpinskiFold(vec3 z) {
    vec3 z1 = z;
	z1.xy -= min(z1.x + z1.y, 0.0);
	z1.xz -= min(z1.x + z1.z, 0.0);
	z1.yz -= min(z1.y + z1.z, 0.0);
    return z1;
}

vec3 mengerFold(vec3 z) {
    vec3 z1 = z;
	float a = min(z1.x - z1.y, 0.0);
	z1.x -= a;
	z1.y += a;
	a = min(z1.x - z1.z, 0.0);
	z1.x -= a;
	z1.z += a;
	a = min(z1.y - z1.z, 0.0);
	z1.y -= a;
	z1.z += a;
    return z1;
}

vec3 sphereFold(vec3 z, float minR, float maxR) {
    vec3 z1 = z;
    /*
	float r = length(z1);
    if(r < minR) {
        z1 *= minR * minR / (r * r);
    }
    //return z1 * maxR;
    */
    /*
    float r2 = dot(z1.xyz, z1.xyz);
    if(r2 != 0) {
	    z1 *= max(maxR / max(minR, r2), 1.0);
    }
    return z1;
    */
    float zDot = dot(z1, z1);
    if(zDot < minR) {
        z1 *= maxR / minR;
    }
    else if(zDot < maxR) {
        z1 *= maxR / zDot;
    }
    return z1;
    
}

vec3 boxFold(vec3 z, vec3 r) {
    vec3 z1 = z;
	z1.xyz = clamp(z1, -r, r) * 2.0 - z1;
    return z1;
}

entity mCustom(vec3 path, float dist, material material) {
    entity m;
    m.dist = dist;
    m.point = path;
    m.material = material;
    return m;
}

entity mPlane(vec3 path, vec3 n, float h, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdPlane(p1, n, h) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mSphere(vec3 path, float radius, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdSphere(p1, radius) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mBox(vec3 path, vec3 size, float r, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdBox(p1, size, r) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mTorus(vec3 path, vec2 dim, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdTorus(p1, dim) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mCylinder(vec3 path, vec3 size, float r, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdCylinder(p1, size, r) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mCappedCylinder(vec3 path, vec2 size, float r, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdCappedCylinder(p1, size, r) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mHexagon(vec3 path, vec2 size, float r, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdHexagon(p1, size, r) * scale;
    m.point = p1;
    m.material = material;
    m.needNormals = true;
    return m;
}

entity mPyramid(vec3 path, float height, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdPyramid(p1, height) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mOctahedron(vec3 path, float height, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdOctahedron(p1, height) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mGyroid(vec3 path, float scale, float thickness, float bias, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdGyroid(p1, thickness, bias) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mMenger(vec3 path, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdMenger(p1) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mJulian(vec3 path, vec4 c, float scale, material material) {
    entity m;
    vec4 trap;
    vec3 p1 = path / scale;
    m.dist = sdJulian(p1, trap, c) * scale;
    m.point = p1;
    m.material = material;
    m.trap = trap;
    return m;
}

entity mLineSeg(vec3 path, vec3 a, vec3 b, float w, float scale, material material) {
    entity m;
    vec3 p1 = path / scale;
    m.dist = sdLineSeg(p1, a, b, w) * scale;
    m.point = p1;
    m.material = material;
    return m;
}

entity mDebugPointLights(vec3 point) {
    material plMat = material(
        ambientOptions(
            vec3(1.0, 1.0, 1.0),
            1.5
        ),
        diffuseOptions(
            vec3(1.0, 1.0, 1.0),
            1.0
        ),
        specularOptions(
            vec3(1.0, 1.0, 1.0),
            0.0,
            0.0
        ),
        shadowOptions(
            false,
            0.0,
            0.0,
            0.0,
            0.0
        ),
        aoOptions(
            false,
            0.0,
            0.0
        ),
        textureOptions(
            0,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(5.0, 5.0, 5.0),
            false
        )
    );

    float size = 1.0;
    entity pointLightEntities;

    pointLightEntities.dist = sdSphere(translate(point, pointLights[0].position), size);
    pointLightEntities.point = translate(point, pointLights[0].position);
    plMat.ambient.color = pointLights[0].color;
    pointLightEntities.material = plMat;
    pointLightEntities.needNormals = false;

    for(int i = 1; i < pointLights.length(); i++) {
        entity pointLightEntity;
        pointLightEntity.dist = sdSphere(translate(point, pointLights[i].position), size);
        pointLightEntity.point = translate(point, pointLights[i].position);
        plMat.ambient.color = pointLights[1].color;
        pointLightEntity.material = plMat;
        pointLightEntity.needNormals = false;
        pointLightEntities = opUnion(pointLightEntities, pointLightEntity);
    }
    
    return pointLightEntities;
}

entity mFractal(vec3 path, int iter, material material) {
    vec3 p1 = path;
    //Skaalaus voisi olla tarpeen tässäkin
    /*
    vec3 offset = z;
	float dr = 1.0;
	for (int n = 0; n < Iterations; n++) {
		boxFold(z,dr);       // Reflect
		sphereFold(z,dr);    // Sphere Inversion
 		
                z=Scale*z + offset;  // Scale & Translate
                dr = dr*abs(Scale)+1.0;
	}
	float r = length(z);
	return r/abs(dr);
    */
   
    iter = 3;
    for(int i = 1; i <= iter; i++) {
        //p1 = rotX(p1, 0.25);
        //p1 = rotY(p1, 0.05);
        //p1 = rotZ(p1, 1.2);
        //p1 = boxFold(p1, vec3(0.0, 0.0, 0.0));
     
       
        //p1 = sierpinskiFold(p1);
        //p1 = sphereFold(p1, 15.0, 35.0);
        //p1 = mengerFold(p1);
        //p1 = absFold(p1, vec3(0.0, 1.5, 0.0));
  
       // p1 = planeFold(p1, normalize(vec3(0.0, 1.0, 0.0)), 0.0);
         
        p1 = boxFold(p1, vec3(2.0, 2.0, 2.0));

        p1 = rotX(p1, PI * 0.25);
        p1 = rotY(p1, PI * 0.2);
         p1 = rotZ(p1, PI * 0.1);
        p1 = translate(p1, vec3(0.0, .0, 0.0));
        
       
   
    }
     //Huom tässä voi olla joku monimutkaisempikin muoto
    entity m = mBox(
        p1, 
        vec3(5.0, 1.0, 1.0),
        0.1,
        1.0,
        material
    );
    
    entity box = mBox(
        path,
        vec3(8.0),
        0.0,
        1.0,
        material
    );
    entity comp = opIntersection(box, m);
    //comp.dist += sin(path.z * 0.1) * 3.0;
    return m;
}

entity mRealFractal(vec3 point, vec3 size, int iter, float scale, material material) {
    vec3 p = point.xzy * scale;
	for(int i = 0; i < iter; i++)
	{
		p = 2.0 * clamp(p, -size, size) - p;
		float r1 = dot(p, p);
        float r2 = dot(p, p + sin(p.z * .1)); //Alternate fractal
		float r = r2;
        float k = max((2.0 / r), .027);
		p *= k;
		scale *= k;
	}

	float l = length(p.xy);
	float rxy = l - 4.0;
	float n = l * p.z;
	rxy = max(rxy, -(n) / 4.0) / abs(scale);
    entity e = mCustom(p, rxy, material);
	return e;
}

entity mHexagonFloor(vec3 path) {
    material m1 = material(
        ambientOptions(
            vec3(0.2, 0.5, 0.5),
            0.5
        ),
        diffuseOptions(
            vec3(0.5, 0.5, 0.5),
            0.1
        ),
        specularOptions(
            vec3(1.0, 1.0, 1.0),
            0.0,
            0.0
        ),
        shadowOptions(
            false,
            0.1,
            0.4,
            0.5,
            0.5
        ),
        aoOptions(
            false,
            0.3,
            15.0
        ),
        textureOptions(
            0,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(5.0, 5.0, 5.0),
            false
        )
    );
    vec3 size = vec3(2.5, 2.5, 0.0);
    vec3Tuple r = repeat(path, size);
    vec3 pos = r.first;
    if(mod(r.second.y, 2) == 0)
    {
        pos = translate(pos, vec3(0.00, 0.0, 0.00));
    }
    else {
        pos = translate(pos, vec3(0.0, 0.0, 0.0));
    }
    entity rh = mHexagon(
        pos,
        vec2(1.0, 1.0),
        0.0,
        1.0,
        m1
    );
    rh.dist *= 0.5;
    return rh;
}

entity mHoleSphere(vec3 path, float scale) {
    path /= scale;
    material m1 = material(
        ambientOptions(
            vec3(0.2, 0.5, 0.2),
            0.5
        ),
        diffuseOptions(
            vec3(0.2, 0.5, 0.2),
            0.34
        ),
        specularOptions(
            vec3(1.0, 1.0, 1.0),
            10.0,
            15.0
        ),
        shadowOptions(
            false,
            10.0,
            0.4,
            0.5,
            0.5
        ),
        aoOptions(
            true,
            0.3,
            15.0
        ),
        textureOptions(
            0,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(5.0, 5.0, 5.0),
            false
        )
    );

    vec2 hSize = vec2(1.0, 5.0);
    float hSmooth = 0.0;
    float h1 = sdHexagon(
        path,
        hSize,
        hSmooth
    );

    float h2 = sdHexagon(
        rotY(path, PI * 0.5),
        hSize,
        hSmooth
    );

    float h3 = sdHexagon(
        rotY(path, PI * 0.25),
        hSize,
        hSmooth
    );

    float h4 = sdHexagon(
        rotY(path, PI * 0.75),
        hSize,
        hSmooth
    );

    float h5 = sdHexagon(
        rotX(path, PI * 0.5),
        hSize,
        hSmooth
    );

    float h6 = sdHexagon(
        rotX(path, PI * 0.25),
        hSize,
        hSmooth
    );

    float h7 = sdHexagon(
        rotX(path, PI * 0.75),
        hSize,
        hSmooth
    );

    float h8 = sdHexagon(
        rotY(rotZ(path, PI * 0.75), PI * 0.5),
        hSize,
        hSmooth
    );

    float h9 = sdHexagon(
        rotY(rotZ(path, PI * 0.25), PI * 0.5),
        hSize,
        hSmooth
    );

    float hs = 
        opSmoothUnion(h1,
            opSmoothUnion(h2,
                opSmoothUnion(h3, 
                    opSmoothUnion(h4,
                        opSmoothUnion(h5, 
                            opSmoothUnion(h6,
                                opSmoothUnion(h7,
                                    opSmoothUnion(h8, h9)
                                )
                            )
                        )
                    )
                )
            )
        );
    
    float s1 = sdSphere(
        path,
        4.0
    );

    float s2 = sdSphere(
        path,
        3.8
    );

    float ss = opSmoothSubtraction(s2, s1, 0.0);
    float completeDist = opSmoothSubtraction(hs, ss, 0.0);
    entity complete = mCustom(
        path,
        completeDist * scale,
        m1
    );
    complete.needNormals = true;

    return complete;
}

entity swirlyColumn(vec3 path, vec3 location) {
    material mm1 = material(
        ambientOptions(
            vec3(0.0),
            0.0
        ),
        diffuseOptions(
            BLUE,
            0.25
        ),
        specularOptions(
            WHITE,
            1.0,
            10.0
        ),
        shadowOptions(
            false,
            0.2,
            1.9,
            2.0,
            0.5
        ),
        aoOptions(
            false,
            0.3,
            5.0
        ),
        textureOptions(
            2,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(5.0, 5.0, 5.0),
            false
        )
    );

    vec3 path1 = path;
    path1 = rotY(path1, cos(path1.y * .12 + time2 + location.x) * sin(path1.y * .025 + time2 + location.z) * 2.0);
    float disp2 = cos((path1.y * 0.23) + (time2 + location.x * 3.6));
    float disp1 = sin((path1.y * 0.1) + (time2 + location.z * 5.0));
    path1 = translate(path1, vec3(disp1 * disp2 * 3.0, 0.0, 0.0));

    entity column = mBox(
        path1,
        vec3(1.0, 100.0, 1.0),
        0.2, 
        3.0,
        mm1
    );

    //box1.dist *= 0.5;
    column.needNormals = true;
    return column;
}

entity mDisk(vec3 path) {
    material bodyMaterial = material(
        ambientOptions(
            vec3(0.46, 0.46, 0.80),
            1.0
        ),
        diffuseOptions(
            vec3(0.46, 0.46, 0.80),
            0.1
        ),
        specularOptions(
            vec3(0.508273, 0.508273, 0.508273),
            0.0,
            0.0
        ),
        shadowOptions(
            false,
            0.1,
            0.2,
            2.0,
            0.5
        ),
        aoOptions(
            false,
            0.3,
            5.0
        ),
        textureOptions(
            0,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(1.0, 1.0, 1.0),
            false
        )
    );

    material labelMaterial = material(
        ambientOptions(
            vec3(0.8, 0.8, 0.8),
            1.0
        ),
        diffuseOptions(
            vec3(0.8, 0.8, 0.8),
            0.3
        ),
        specularOptions(
            vec3(0.508273, 0.508273, 0.508273),
            0.0,
            0.0
        ),
        shadowOptions(
            false,
            0.1,
            0.2,
            2.0,
            0.5
        ),
        aoOptions(
            false,
            0.3,
            5.0
        ),
        textureOptions(
            2,
            vec3(1.0, 1.0, 1.0),
            vec3(1.0, 1.0, 1.0),
            vec3(1.0, 1.0, 1.0),
            false
        )
    );

    material metalMaterial = material(
        ambientOptions(
            vec3(0.4, 0.4, 0.4),
            1.0
        ),
        diffuseOptions(
            vec3(0.4, 0.4, 0.4),
            0.5
        ),
        specularOptions(
            vec3(0.774597, 0.774597, 0.774597),
            1.0,
            60.0
        ),
        shadowOptions(
            false,
            0.1,
            0.2,
            2.0,
            0.5
        ),
        aoOptions(
            false,
            0.3,
            5.0
        ),
        textureOptions(
            0,
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.0, 0.0),
            vec3(1.0, 1.0, 1.0),
            false
        )
    );

    vec3 diskSize = vec3(8.9, 9.3, 0.20);
    vec3 holeSize = vec3(0.49, 0.381, 1.0);
    vec3 labelSize = vec3(6.943, 5.5, 0.05);
    vec3 sliderCreviceSize = vec3(6.096, 3.133, 0.3);
    vec3 sliderSize = vec3(4.741, 3.2, 0.35);
    vec3 sliderHoleSize = vec3(1.185, 2.455, 1.0);
    vec3 notchSize = vec3(1.0, 1.0, 0.6);

    entity bodyHull = mBox(
        path,
        diskSize,
        0.15,
        1.0,
        bodyMaterial
    );
    bodyHull.needNormals = true;

    entity leftHoleHull = mBox(
        translate(path, vec3(8.128, -8.10, 0.95)),
        holeSize,
        0.0,
        1.0,
        bodyMaterial
    );
    leftHoleHull.needNormals = true;

    entity rightHoleHull = mBox(
        translate(path, vec3(-8.128, -8.10, 0.0)),
        holeSize,
        0.0,
        1.0,
        bodyMaterial
    );
    rightHoleHull.needNormals = true;

    entity labelHull = mBox(
        translate(path, vec3(0.0, -3.9, 0.5)),
        labelSize,
        0.1,
        1.0,
        labelMaterial
    );
   
    entity sliderCreviceHull = opUnion(
        mBox(
            translate(path, vec3(0.8, 6.35, 0.7)),
            sliderCreviceSize,
            0.1,
            1.0,
            bodyMaterial
        ),
        mBox(
            translate(path, vec3(0.8, 6.35, -0.7)),
            sliderCreviceSize,
            0.1,
            1.0,
            bodyMaterial
        )
    );

    entity sliderHull = opSubtraction(
        mBox(
            translate(path, vec3(-2.7, 6.0, 0.0)),
            sliderHoleSize,
            0.0,
            1.0,
            metalMaterial
        ),
        mBox(
            translate(path, vec3(-0.55, 6.30, 0.0)),
            sliderSize,
            0.05,
            1.0,
            metalMaterial
        )
    );

    vec3 hubPath = translate(rotX(path, PI / 2), vec3(0.0, 0.35, 0.0));
    entity hub = opSubtraction(
        mCappedCylinder(
            hubPath,
            vec2(0.5, 1.5),
            0.0,
            1.0,
            metalMaterial
        ),
        opSubtraction(
            mBox(
                translate(hubPath, vec3(1.6, 0.0, 0.0)),
                vec3(0.5, 2.0, 0.9),
                0.0, 
                1.0,
                metalMaterial
            ),
            mCappedCylinder(
                hubPath,
                vec2(2.5, 0.05),
                0.0,
                1.0,
                metalMaterial
            )
        )
    );
    
    entity notchHull = mBox(
        rotZ(translate(path, vec3(-9.128, 9.45, 0.0)), 0.785398),
        notchSize,
        0.0,
        1.0,
        bodyMaterial
    );
    entity complete = opUnion(opSubtraction(notchHull, opUnion(sliderHull, opSubtraction(sliderCreviceHull, opSubtraction(labelHull, opSubtraction(rightHoleHull, opSubtraction(leftHoleHull, bodyHull)))))), hub);
    complete.needNormals = true;
    return complete;
}

entity scene(vec3 path, vec2 uv) {       
    if (act == 1) {
        material tunnelM = material(
            ambientOptions(
                BLUE,
                0.5
            ),
            diffuseOptions(
                YELLOW,
                0.25
            ),
            specularOptions(
                RED,
                1.0,
                10.0
            ),
            shadowOptions(
                false,
                10.0,
                30.0,
                0.02,
                0.5
            ),
            aoOptions(
                false,
                0.0,
                0.0
            ),
            textureOptions(
                1,
                vec3(1.0, 1.0, 1.0),
                vec3(time * 10.0, 0.0, 0.0),
                vec3(1.0, 0.1, 0.1),
                false
            )
        );

        vec3 tunnelInnerRot = rotZ(path, (sin(time2)  + sin(time3 + 10.5)) * PI);
        vec3 tunnelPos = rotY(translate(tunnelInnerRot, vec3(20.0, sin(time4 / 2.4) * 3.5, 0.0)), -time);

        entity tunnel = mTorus(
            tunnelPos,
            vec2(20.0, 5.0),
            1.0,
            tunnelM
        );
        tunnel.needNormals = true;
        tunnel.dist = abs(tunnel.dist) - 1.0;

        material obstacleM = material(
            ambientOptions(
                RED,
                0.5
            ),
            diffuseOptions(
                YELLOW,
                0.5
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                10.0,
                30.0,
                0.02,
                0.5
            ),
            aoOptions(
                false,
                0.0,
                0.0
            ),
            textureOptions(
                0,
                vec3(1.0, 1.0, 1.0),
                vec3(1.0, 1.0, 1.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );

        vec3 obstaclePos = rotY(translate(tunnelInnerRot, vec3(20.0, sin(time4 / 2.4) * 3.5, 0.0)), -time4);
        
        float fbm1 = fbm3D(
            obstaclePos,
            0.05,
            1.1,
            2,
            0.0
        );

        entity obstacle = mTorus(
            obstaclePos + fbm1 * (sin(obstaclePos.x) + sin(obstaclePos.z)),
            vec2(20.0, 0.25 * tunnelObstacleScale),
            1.0,
            obstacleM
        );
        obstacle.dist *= 0.5;
        obstacle.needNormals = true;
        return opUnion(obstacle, tunnel);
    }
    else if (act == 2) { 
        material mm1 = material(
            ambientOptions(
                vec3(0.60, 0.06, 0.82),
                0.5
            ),
            diffuseOptions(
                vec3(0.60 / 2.0, 0.06 / 2.0, 0.82 / 2.0),
                2.5
            ),
            specularOptions(
                vec3(0.60 / 1.0, 0.06 / 1.0, 0.82 / 1.0),
                //vec3(0.5),
                20.0,
                5.0
            ),
            shadowOptions(
                false,
                0.2,
                1.9,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                0.3,
                5.0
            ),
            textureOptions(
                0,
                vec3(1.0, 1.0, 1.0),
                vec3(0.0, 0.0, 0.0),
                vec3(5.0, 5.0, 5.0),
                false
            )
        );

        vec3Tuple pathTuple = repeatLimit(path, 1.25, vec3(50.0, 50.0, 50.0));
        entity boxes = mBox(
            pathTuple.first,
            vec3(1.0, 1.0, 1.0),
            0.1, 
            0.50,
            mm1
        );

        //boxes.dist *= 0.5;
        boxes.needNormals = true;

        vec3 path1 = rotX(rotY(rotZ(path, time4), time3), time2);
        entity julian = mJulian(
            path1,
            //0.45 * cos(vec4(0.5, 3.9, 1.4, 1.1) + time2 * vec4(1.2, 1.7, 1.3, 2.5)) - vec4(0.3, 0.0, 0.0, 0.0),
            0.36 *
            cos(vec4(0.9, 3.9, 1.4, 1.1) +
            14 * 2200 *
            //time2 * 
            vec4(1.2, 1.7, 1.3, 2.5)) - 
            vec4(0.35, 0.0, 0.0, 0.0),

            18.0,
            mm1
        );

        //julian.dist *= 0.5;
        julian.needNormals = true;
        
        return opIntersection(boxes, julian);    
    }
    else if (act == 3) {
        material mm1 = material(
            ambientOptions(
                vec3(1.00, 1.00, 1.00),
                0.5
            ),
            diffuseOptions(
                vec3(1.0, 1.0, 1.0),
                0.0
            ),
            specularOptions(
                vec3(1.0, 1.0, 1.0),
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                0.2,
                1.9,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                0.3,
                5.0
            ),
            textureOptions(
                3,
                vec3(1.0, 1.0, 1.0),
                vec3(0.85, 0.0, 1.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );
        entity textCube = mBox(
            rotY(path, PI + (PI / 7.0)),
            vec3(1.0, 0.5, 1.0),
            0.05,
            1.0,
            mm1
        );
        textCube.needNormals = true;
        return textCube;
    }
    else if (act == 4) { 
        vec3Tuple r = repeat(path, vec3(20.0, 0.0, 20.0));
        return swirlyColumn(r.first, r.second);
    }
    else if (act == 5) {
        material torusM = material(
            ambientOptions(
                DARK,
                1.0
            ),
            diffuseOptions(
                DARK,
                1.0
            ),
            specularOptions(
                WHITE,
                20.0,
                70.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                0,
                WHITE,
                vec3(0.0, 0.0, 0.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );
        entity torus = mTorus(
            rot(path, vec3(1.0) * time),
            vec2(1.0, 0.5),
            1.0,
            torusM
        );
        torus.needNormals = true;

        material hexagonM = material(
            ambientOptions(
                YELLOW,
                1.0
            ),
            diffuseOptions(
                YELLOW,
                1.0
            ),
            specularOptions(
                WHITE,
                20.0,
                70.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                0,
                WHITE,
                vec3(0.0, 0.0, 0.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );
        entity hexagon = mHexagon(
            rot(translate(path, vec3(3.0, 0.0, 0.0)), vec3(1.0) * time),
            vec2(1.0, 0.5),
            0.0,
            1.0,
            hexagonM
        );
        hexagon.needNormals = true;

        material boxM = material(
            ambientOptions(
                RED,
                1.0
            ),
            diffuseOptions(
                RED,
                1.0
            ),
            specularOptions(
                WHITE,
                20.0,
                70.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                0,
                WHITE,
                vec3(0.0, 0.0, 0.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );
        entity box = mBox(
            rot(translate(path, vec3(-3.0, 0.0, 0.0)), vec3(1.0) * time),
            vec3(1.0, 1.0, 1.0),
            0.0,
            1.0,
            boxM
        );
        box.needNormals = true;

        material groundM = material(
            ambientOptions(
                BLUE,
                1.0
            ),
            diffuseOptions(
                BLUE,
                0.5
            ),
            specularOptions(
                BLUE,
                0.0,
                0.0
            ),
            shadowOptions(
                true,
                0.1,
                2.0,
                0.001,
                16.0
            ),
            aoOptions(
                false,
                0.01,
                0.02
            ),
            textureOptions(
                0,
                WHITE,
                vec3(0.0, 0.0, 0.0),
                vec3(1.0, 1.0, 1.0),
                false
            )
        );
        entity ground = mPlane(
            translate(path, vec3(0.0, -2.0, 0.0)),
            vec3(0.0, 1.0, 0.0),
            1.0, 
            1.0,
            groundM
        );
        ground.needNormals = true;
        return opUnion(ground, opUnion(opUnion(hexagon, box), torus));
    }
    else if (act == 6) {
        material scrollerM = material(
            ambientOptions(
                WHITE,
                1.0
            ),
            diffuseOptions(
                WHITE,
                1.0
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                0.2,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                5,
                WHITE,
                vec3(
                    introTextPosition.x,
                    introTextPosition.y + (sin(path.z * 8.5 + time * 2.0) / 160.0),
                    introTextPosition.z),
                vec3(20.0, -20.0, -20.0),
                false
            )
        );

        entity scroller = mBox(
            rot(
                translate(
                    rot(
                        path,
                        vec3(0.0, 0.0, PI * 0.5)
                    ),
                    vec3(3.0, 0.0, 0.0)
                ),
                vec3(0.0, PI * 0.25, 0.0)  
            ),

            vec3(0.5, 4.0, 0.25),
            0.1,
            1.0,
            scrollerM
        );
        scroller.needNormals = true;

        material spinningCubeM = material(
            ambientOptions(
                WHITE,
                0.2
            ),
            diffuseOptions(
                WHITE,
                1.0
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                4,
                WHITE,
                vec3(1.0, 1.0, 1.0),
                vec3(0.1, 0.1, 0.1),
                false
            )
        );
        entity spinningCube = mBox(
            rot(translate(path, introCubePosition), vec3(time2)),
            vec3(1.5),
            0.05, 
            1.0,
            spinningCubeM
        );
        spinningCube.needNormals = true;
        return opUnion(spinningCube, scroller);
    }
    else if (act == 7) {
        material groundM = material(
            ambientOptions(
                vec3(0.65, 0.06, 0.72),
                0.5
            ),
            diffuseOptions(
                vec3(0.65 / 2.0, 0.06 / 2.0, 0.72 / 2.0),
                2.5
            ),
            specularOptions(
                vec3(0.65 / 1.0, 0.06 / 1.0, 0.72 / 1.0),
                2.0,
                2.0
            ),
            shadowOptions(
                false,
                0.2,
                1.9,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                0.3,
                5.0
            ),
            textureOptions(
                7,
                vec3(1.0, 1.0, 1.0),
                vec3(0.0, 0.0, -time * 5.0),
                vec3(5.0, 5.0, 5.0),
                false
            )
        );
        
        material coinM = material(
            ambientOptions(
                BLUE,
                0.5
            ),
            diffuseOptions(
                PINK,
                0.5
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                10.0,
                30.0,
                0.02,
                0.5
            ),
            aoOptions(
                false,
                0.0,
                0.0
            ),
            textureOptions(
                6,
                vec3(1.0, 1.0, 1.0),
                vec3(time * 10.0, 0.0, 0.0),
                vec3(1.0, 0.1, 0.1),
                false
            )
        );

        vec3Tuple r1 = repeat(path, vec3(20.0, 25.0, 20.0));
        vec3Tuple r2 = repeat(path, vec3(40.0, 25.0, 20.0));
        vec3Tuple r3 = repeat(path, vec3(0.0, 25.0, 0.0));

        entity ground = mBox(
            translate(r3.first, vec3(0.0, 0.0, 0.0)),
            vec3(500.0, 1.0, 500.0),
            0.0, 
            1.0,
            groundM
        );
        ground.needNormals = true;
        
        entity ball = mSphere(
            translate(r1.first, vec3(0.0, 0.0, 0.0)), 6.0, 1, coinM
        );
        ball.needNormals = true;

        entity coin = mBox(
            translate(rotX(r1.first, (3.35 * time + 0.1)), vec3(0.0, 0.0, 0.0)),
            vec3(9.0, 1.0, 9.0),
            0.0, 
            1.0,
            coinM
        );
        coin.needNormals = true;

        entity mato = mTorus(
            rotZ(translate(rotX(r2.first, PI/2), vec3(-10, 0.0, 0.0)), -0.44),
            vec2(5.0, 0.6),
            2.0,
            coinM
        );
        mato.needNormals = true;

        entity cutoff = mBox(
            translate(rotZ(translate(rotY(r2.first, -0.4), vec3(-7.5, 0.0, -1.2)), 3.35 * time + 0.1+PI/2), vec3(10.0, 0.0, 0.0)),
            vec3(5.0, 4.0, 5.0),
            0.0, 
            1.0,
            coinM
        );
        cutoff.needNormals = true;

        return opUnion(opIntersection(ball, coin), opUnion(opSubtraction(ball, ground), opIntersection(cutoff, mato)));
    }
    else if (act == 8) {
        material greetsBoxM = material(
            ambientOptions(
                WHITE,
                0.5
            ),
            diffuseOptions(
                WHITE,
                0.5
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                0.2,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                5,
                WHITE,
                vec3(
                    -0.07,
                    -0.43 + (sin(path.z * 2.5 + time * 2.0) / 120.0),
                    0.0
                ),
                vec3(14.0, -14.0, 20.0),
                false
            )
        );

        entity greetsBox = mBox(
            rot(
                translate(
                    rot(
                        path,
                        vec3(0.0, 0.0, PI * 0.5)
                    ),
                    vec3(0.8, -1.1, 0.8)
                ),
                vec3(PI * -0.10, PI * 0.10, 0.0)  
            ),
            vec3(0.5, 4.0, 0.25),
            0.1,
            1.0,
            greetsBoxM
        );
        greetsBox.needNormals = true;

        material creditsBoxM = material(
            ambientOptions(
                WHITE,
                0.5
            ),
            diffuseOptions(
                WHITE,
                0.5
            ),
            specularOptions(
                WHITE,
                0.0,
                0.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                0.2,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                5,
                WHITE,
                vec3(
                    -0.50,
                    -0.51 + (sin(path.x * 2.0 + time * 2.0) / 60.0),
                    0.0
                ),
                vec3(11.5, -11.5, -20.0),
                false
            )
        );

        entity creditsBox = mBox(
            rot(
                translate(
                    rot(
                        path,
                        vec3(0.0, 0.0, PI * 0.5)
                    ),
                    vec3(-0.8, 1.1, 1.0)
                ),
                vec3(PI * 0.10, PI * -0.10, 0.0)  
            ),
            vec3(0.5, 4.0, 0.25),
            0.1,
            1.0,
            creditsBoxM
        );
        creditsBox.needNormals = true;

        material spinningCubeM = material(
            ambientOptions(
                WHITE,
                0.2
            ),
            diffuseOptions(
                WHITE,
                0.2
            ),
            specularOptions(
                WHITE,
                1.0,
                10.0
            ),
            shadowOptions(
                false,
                0.1,
                0.2,
                2.0,
                0.5
            ),
            aoOptions(
                false,
                1.0,
                1.0
            ),
            textureOptions(
                0,
                WHITE,
                vec3(1.0, 1.0, 1.0),
                vec3(0.1, 0.1, 0.1),
                false
            )
        );
        vec3 cubesPos = translate(rotZ(rotY(rotX(path, PI / 2.0), sin(time2)), time), vec3(0.0, 0.0, 0.0));
        vec2Tuple spinningCubeRepeat = repeatPolar(
            cubesPos.xy,
            10.0
        );
        vec3 spinningCubePosition = vec3(spinningCubeRepeat.first, cubesPos.z) - vec3(8.0, 0.0, 0.0);
        entity spinningCubes = mBox(
            rot(
                spinningCubePosition,
                vec3(1.0, 1.3, 1.8) * time + spinningCubeRepeat.second.x
            ),
            vec3(1.5),
            0.05, 
            1.0,
            spinningCubeM
        );
        spinningCubes.needNormals = true;
     
        return opUnion(opUnion(greetsBox, creditsBox), spinningCubes);
    }
} 

vec3 calculatePointNormals(vec3 point, float threshold) {
    const vec2 k = vec2(1, -1);
    return normalize(
        k.xyy * scene(point + k.xyy * threshold, vec2(0)).dist + 
        k.yyx * scene(point + k.yyx * threshold, vec2(0)).dist + 
        k.yxy * scene(point + k.yxy * threshold, vec2(0)).dist + 
        k.xxx * scene(point + k.xxx * threshold, vec2(0)).dist
    );
}

hit raymarch(vec3 rayOrigin, vec3 rayDirection, vec2 uv) {
    hit h;
    for(float i = 0.0; i <= RAY_MAX_STEPS; i++) {
        vec3 point = rayOrigin + rayDirection * h.dist;
        h.entity = scene(point, uv);
        h.steps += 1.0;
        h.dist += h.entity.dist;
        float threshold = map(h.dist, 0.0, RAY_MAX_THRESHOLD_DISTANCE, RAY_MIN_THRESHOLD, RAY_MAX_THRESHOLD);
        if(abs(h.entity.dist) < threshold) {
            if(h.entity.needNormals == true) {
                h.entity.normal = calculatePointNormals(point, threshold);
            }
            break;

        }
        if(h.dist > rayMaxDistance) {
            break;
        }
    }
    
    return h;
}

float shadows(vec3 ro, vec3 lp, float mint, float maxt, float k) {
    float res = 1.0;
    float ph = 1e20;
    vec3 rd = normalize(ro - lp);
    for(float t = mint; t < maxt;)
    {
        float h = scene(ro + (rd * t), vec2(0)).dist;
        if(abs(h) < 0.001)
            return 0.0;
        float y = h * h / (2.0 * ph);
        float d = sqrt(h * h - y * y);
        res = min(res, k * d / max(0.0, t - y));
  
        ph = h;
        t += h;    
    }
    return res;
}

//REGION_START Texturing
vec4 textureCube(sampler2D sam, in vec3 p, in vec3 n) {
	vec4 x = texture(sam, p.yz);
	vec4 y = texture(sam, p.zx);
	vec4 z = texture(sam, p.yx);
    vec3 a = abs(n);
	return (x*a.x + y*a.y + z*a.z) / (a.x + a.y + a.z);
}

vec2 planarMapping(vec3 p) {
    vec3 p1 = normalize(p);
    vec2 r = vec2(0.0);
    if(abs(p1.x) == 1.0) {
        r = vec2((p1.z + 1.0) / 2.0, (p1.y + 1.0) / 2.0);
    }
    else if(abs(p1.y) == 1.0) {
        r = vec2((p1.x + 1.0) / 2.0, (p1.z + 1.0) / 2.0);
    }
    else {
        r = vec2((p1.x + 1.0) / 2.0, (p1.y + 1.0) / 2.0);
    }
    return r;
}

vec2 cylindiricalMapping(vec3 p) {
    return vec2(atan(p.y / p.x), p.z);
}

vec2 scaledMapping(vec2 t, vec2 o, vec2 s) {
    return -vec2((t.x / o.x) + s.x, (t.y / o.y) + s.y);
}

float noise(float v, float amplitude, float frequency, float time) {
    float r = sin(v * frequency);
    float t = 0.01*(-time*130.0);
    r += sin(v*frequency*2.1 + t)*4.5;
    r += sin(v*frequency*1.72 + t*1.121)*4.0;
    r += sin(v*frequency*2.221 + t*0.437)*5.0;
    r += sin(v*frequency*3.1122+ t*4.269)*2.5;
    r *= amplitude*0.06;
    
    return r;
}

vec3 hsv2rgb(vec3 c) {
    vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
    rgb = rgb*rgb*(3.0-2.0*rgb);
    return c.z * mix( vec3(1.0), rgb, c.y);
}

vec3 rainbow(vec3 position) {
    float d = length(position);
	//vec3 color = hsv2rgb(vec3(time * 0.005 * position.x ,0.25+sin(time+position.x)*0.5+0.5,d));
    vec3 color = hsv2rgb(vec3(time * 1.3 + position.z , 1.8, 1.4));
    return color;
}

vec3 dunno(vec3 position) {
    float color = 0.0;
	color += sin(position.x * cos(time / 15.0) * 80.0) + cos(position.y * cos(time / 15.0) * 10.0);
	color += sin(position.y * sin(time / 10.0) * 40.0) + cos(position.x * sin(time / 25.0) * 40.0);
	color += sin(position.x * sin(time / 5.0) * 10.0) + sin(position.y * sin(time / 35.0) * 80.0);
	color *= sin(time / 10.0) * 0.5;
    return  vec3(color, color * 0.5, sin(color + time / 3.0) * 0.75);
}

vec3 crossTexture(vec3 p, vec3 color, float strength, float density) {
	float f = (strength / abs(sin(p.x * density))) + (strength / abs(sin(p.y * density))) + (strength / abs(sin(p.z * density)));
    return vec3(f * color);
}

vec3 generateTunnelTexture(vec3 p, vec3 color, float strength, float density) {
	float tunnelLines = (strength / abs(sin(p.x * density))) + (strength / abs(sin(p.y * density))) + (strength / abs(sin(p.z * density)));
    vec3 tunnelTexture = texture(texture02, planarMapping(p) * 20.0).rgb;
    return vec3(tunnelLines * tunnelTexture * color);
}

vec3 diskLabelTexture(vec3 p) {
    float s = step(0.1, mod(p.y, 2.0));
    vec3 color = hsv2rgb(vec3(time * 1.3 + p.z , 1.8, 1.4));
    return vec3(s);
}

vec3 background(vec2 uv, vec3 eye, vec3 rayDirection, hit h) {
    if (act == 2)
        return vec3(1.0, 1.0, 0.0) * -uv.x;
    else if (act == 3)
        return vec3(1.0, 0.0, 0.7) * smoothstep(-0.5, 0.3, uv.y);
    else if (act == 6) {
        return vec3(0.2, 0.34, 0.7) * smoothstep(-0.9, 0.3, uv.y);
    }
    else if (act == 8) {
        return vec3(0.2, 0.34, 0.7) * smoothstep(-0.9, 0.3, uv.y);
    }
	return vec3(0.0);
}

/*
Top left pixel is the most significant bit.
Bottom right pixel is the least significant bit.

 ███  |
█   █ |
█   █ |  
█   █ |
█████ |
█   █ |
█   █ |

011100 
100010
100010  
100010
111110
100010
100010

011100 (upper 21 bits)
100010 -> 011100 100010 100010 100 -> 935188
100010  
100
   010 (lower 21 bits)
111110 -> 010 111110 100010 100010 -> 780450
100010
100010

vec2(935188.0,780450.0)
*/
//Automatically generated from the sprite sheet here: http://uzebox.org/wiki/index.php?title=File:Font6x8.png
const vec2 GLYPH_SIZE = vec2(6.0, 7.0);
const vec2 GLYPH_SPACING = vec2(6.0, 9.0);

float textWidth(float c) {
    return c * GLYPH_SPACING.x;
}

float textHeight(float c) {
    return c * GLYPH_SPACING.y;
}
void beginText(float x, float y, inout vec2 printPos, inout vec2 startPos) {
    printPos = floor(vec2(x, y));
    startPos = floor(vec2(x, y));
}

float extract_bit(float n, float b)
{
	b = clamp(b,-1.0,22.0);
	return floor(mod(floor(n / pow(2.0,floor(b))),2.0));   
}

vec3 glyph(vec2 ch, vec2 uv, inout vec2 pos)
{
    vec2 uvM = floor(uv - pos);
	float bit = (GLYPH_SIZE.x - uvM.x - 1.0) + uvM.y * GLYPH_SIZE.x;  
	bool bounds = all(greaterThanEqual(uvM, vec2(0))) && all(lessThan(uvM, GLYPH_SIZE)); 
	float px = bounds ? extract_bit(ch.x, bit - 21.0) + extract_bit(ch.y, bit) : 0.0;
	pos.x += GLYPH_SPACING.x;
	return vec3(px);
}

vec3 glyph0(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(935221.0,731292.0), uv, printPos) * textColor;
}

vec3 glyph1(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(274497.0,33308.0), uv, printPos) * textColor;
}

vec3 glyph2(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(934929.0,1116222.0), uv, printPos) * textColor;
}

vec3 glyph3(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(934931.0,1058972.0), uv, printPos) * textColor;
}

vec3 glyph4(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(137380.0,1302788.0), uv, printPos) * textColor;
}

vec3 glyph5(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(2048263.0,1058972.0), uv, printPos) * textColor;
}

vec3 glyph6(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(401671.0,1190044.0), uv, printPos) * textColor;
}

vec3 glyph7(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(2032673.0,66576.0), uv, printPos) * textColor;
}

vec3 glyph8(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(935187.0,1190044.0), uv, printPos) * textColor;
}

vec3 glyph9(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(935187.0,1581336.0), uv, printPos) * textColor;
}

vec3 glypha(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(224.0,649374.0), uv, printPos) * textColor;
}

vec3 glyphb(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065444.0,665788.0), uv, printPos) * textColor;
}

vec3 glyphc(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,657564.0), uv, printPos) * textColor;
}

vec3 glyphd(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(66804.0,665758.0), uv, printPos) * textColor;
}

vec3 glyphe(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,772124.0), uv, printPos) * textColor;
}

vec3 glyphf(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(401543.0,1115152.0), uv, printPos) * textColor;
}

vec3 glyphg(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(244.0,665474.0), uv, printPos) * textColor;
}

vec3 glyphh(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065444.0,665762.0), uv, printPos) * textColor;
}

vec3 glyphi(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(262209.0,33292.0), uv, printPos) * textColor;
}

vec3 glyphj(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(131168.0,1066252.0), uv, printPos) * textColor;
}

vec3 glyphk(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065253.0,199204.0), uv, printPos) * textColor;
}

vec3 glyphl(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(266305.0,33292.0), uv, printPos) * textColor;
}

vec3 glyphm(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(421.0,698530.0), uv, printPos) * textColor;
}

vec3 glyphn(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(452.0,1198372.0), uv, printPos) * textColor;
}

vec3 glypho(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,665756.0), uv, printPos) * textColor;
}

vec3 glyphp(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(484.0,667424.0), uv, printPos) * textColor;
}

vec3 glyphq(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(244.0,665474.0), uv, printPos) * textColor;
}

vec3 glyphr(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(354.0,590904.0), uv, printPos) * textColor;
}

vec3 glyphs(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,114844.0), uv, printPos) * textColor;
}

vec3 glypht(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(8674.0,66824.0), uv, printPos) * textColor;
}

vec3 glyphu(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(292.0,1198868.0), uv, printPos) * textColor;
}

vec3 glyphv(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(276.0,664840.0), uv, printPos) * textColor;
}

vec3 glyphw(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(276.0,700308.0), uv, printPos) * textColor;
}

vec3 glyphx(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(292.0,1149220.0), uv, printPos) * textColor;
}

vec3 glyphy(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(292.0,1163824.0), uv, printPos) * textColor;
}

vec3 glyphz(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(480.0,1148988.0), uv, printPos) * textColor;
}

vec3 glyphA(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(224.0,649374.0), uv, printPos) * textColor;
}

vec3 glyphB(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065444.0,665788.0), uv, printPos) * textColor;
}

vec3 glyphC(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,657564.0), uv, printPos) * textColor;
}

vec3 glyphD(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(66804.0,665758.0), uv, printPos) * textColor;
}

vec3 glyphE(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,772124.0), uv, printPos) * textColor;
}

vec3 glyphF(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(401543.0,1115152.0), uv, printPos) * textColor;
}

vec3 glyphG(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(244.0,665474.0), uv, printPos) * textColor;
}

vec3 glyphH(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065444.0,665762.0), uv, printPos) * textColor;
}

vec3 glyphI(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(262209.0,33292.0), uv, printPos) * textColor;
}

vec3 glyphJ(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(131168.0,1066252.0), uv, printPos) * textColor;
}

vec3 glyphK(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1065253.0,199204.0), uv, printPos) * textColor;
}

vec3 glyphL(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(266305.0,33292.0), uv, printPos) * textColor;
}

vec3 glyphM(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(421.0,698530.0), uv, printPos) * textColor;
}

vec3 glyphN(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(452.0,1198372.0), uv, printPos) * textColor;
}

vec3 glyphO(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,665756.0), uv, printPos) * textColor;
}

vec3 glyphP(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(484.0,667424.0), uv, printPos) * textColor;
}

vec3 glyphQ(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(244.0,665474.0), uv, printPos) * textColor;
}

vec3 glyphR(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(354.0,590904.0), uv, printPos) * textColor;
}

vec3 glyphS(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(228.0,114844.0), uv, printPos) * textColor;
}

vec3 glyphT(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(8674.0,66824.0), uv, printPos) * textColor;
}

vec3 glyphU(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(292.0,1198868.0), uv, printPos) * textColor;
}

vec3 glyphV(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1131796.0,664840.0), uv, printPos) * textColor;
}

vec3 glyphW(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1131861.0,699028.0), uv, printPos) * textColor;
}

vec3 glyphX(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1131681.0,84130.0), uv, printPos) * textColor;
}

vec3 glyphY(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1131794.0,1081864.0), uv, printPos) * textColor;
}

vec3 glyphZ(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1968194.0,133180.0), uv, printPos) * textColor;
}

vec3 glyphspc(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(0.0,0.0), uv, printPos) * textColor;
}

vec3 glyphexc(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(276705.0,32776.0), uv, printPos) * textColor;
}

vec3 glyphquo(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1797408.0,0.0), uv, printPos) * textColor;
}

vec3 glyphhsh(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(10738.0,1134484.0), uv, printPos) * textColor;
}

vec3 glyphdol(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(538883.0,19976.0), uv, printPos) * textColor;
}

vec3 glyphpct(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1664033.0,68006.0), uv, printPos) * textColor;
}

vec3 glyphamp(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(545090.0,174362.0), uv, printPos) * textColor;
}

vec3 glyphapo(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(798848.0,0.0), uv, printPos) * textColor;
}

vec3 glyphlbr(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(270466.0,66568.0), uv, printPos) * textColor;
}

vec3 glyphrbr(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(528449.0,33296.0), uv, printPos) * textColor;
}

vec3 glyphast(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(10471.0,1688832.0), uv, printPos) * textColor;
}

vec3 glyphcrs(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(4167.0,1606144.0), uv, printPos) * textColor;
}

vec3 glyphper(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(0.0,1560.0), uv, printPos) * textColor;
}

vec3 glyphdsh(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(7.0,1572864.0), uv, printPos) * textColor;
}

vec3 glyphcom(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(0.0,1544.0), uv, printPos) * textColor;
}

vec3 glyphlsl(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(1057.0,67584.0), uv, printPos) * textColor;
}

vec3 glyphcol(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(195.0,1560.0), uv, printPos) * textColor;
}

vec3 glyphscl(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(195.0,1544.0), uv, printPos) * textColor;
}

vec3 glyphles(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(135300.0,66052.0), uv, printPos) * textColor;
}

vec3 glyphequ(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(496.0,3968.0), uv, printPos) * textColor;
}

vec3 glyphgrt(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(528416.0,541200.0), uv, printPos) * textColor;
}

vec3 glyphque(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(934929.0,1081352.0), uv, printPos) * textColor;
}

vec3 glyphats(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(935285.0,714780.0), uv, printPos) * textColor;
}

vec3 glyphlsb(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(925826.0,66588.0), uv, printPos) * textColor;
}

vec3 glyphrsl(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(16513.0,16512.0), uv, printPos) * textColor;
}

vec3 glyphrsb(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(919584.0,1065244.0), uv, printPos) * textColor;
}

vec3 glyphpow(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(272656.0,0.0), uv, printPos) * textColor;
}

vec3 glyphusc(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(0.0,62.0), uv, printPos) * textColor;
}

vec3 glyphlpa(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(401542.0,66572.0), uv, printPos) * textColor;
}

vec3 glyphbar(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(266304.0,33288.0), uv, printPos) * textColor;
}

vec3 glyphrpa(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(788512.0,1589528.0), uv, printPos) * textColor;
}

vec3 glyphtid(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(675840.0,0.0), uv, printPos) * textColor;
}

vec3 glyphlar(vec2 uv, inout vec2 printPos, vec3 textColor) {
    return glyph(vec2(8387.0,1147904.0), uv, printPos) * textColor;
}

vec3 textTexture(vec3 p)
{
    vec3 col = vec3(0.0);
    vec2 res = vec2(256.0, 256.0) / 1.0;
    vec2 uv = p.xy * 30.0;
    vec2 startPos = vec2(0);
    vec2 printPos = vec2(0);
    vec3 textColor = vec3(1); 	
    float typingDelay = 0.75;
    
    beginText(GLYPH_SIZE.x * 0.0, GLYPH_SIZE.y * 0.0, printPos, startPos);
	textColor = vec3(1.0, 0.0, 0.0);
    
    if (time > typingDelay * 1)
        col += glyph1(uv, printPos, textColor);
    if (time > typingDelay * 2)
        col += glyph3(uv, printPos, textColor);
    if (time > typingDelay * 3)
        col += glyph3(uv, printPos, textColor);
    if (time > typingDelay * 4)
        col += glyph7(uv, printPos, textColor);
    printPos.y -= GLYPH_SPACING.y;
    printPos.x = 0.0;
    textColor = vec3(0.0, 1.0, 0.0);
    float pulse = smoothstep(0.0, 0.5, sin(10.0 * time));
    if (time > typingDelay * 5 && pulse >= 1.0) {
        col += glyphf(uv, printPos, textColor);
        col += glyphu(uv, printPos, textColor);
        col += glyphc(uv, printPos, textColor);
        col += glyphk(uv, printPos, textColor);
        col += glyphi(uv, printPos, textColor);
        col += glyphn(uv, printPos, textColor);
        col += glyphg(uv, printPos, textColor);
        col += glyphs(uv, printPos, textColor);
        col += glyphexc(uv, printPos, textColor);
    }
    
    return col;
}

vec3 generateCirclesTexture(vec3 p, float t)
{
    struct circle
    {
        vec2 position;
        float size;
        vec3 color;
    } circles[3];
    vec2 mapped = planarMapping(p);
    vec2 position = (mapped - 0.5) / 1.0; 
    circles[0].position = vec2(sin(t) * cos(t / 3.0), cos(t * 2.0) * sin(t / 2.0));
    circles[0].size = 100.0;
    circles[0].color = vec3(0.5, 0.25, 0.25);
    
    circles[1].position = vec2(sin(t / 2.5) * sin(t * 1.5), sin(t * 2.0) * sin(t / 4.0));
    circles[1].size = 50.0;
    circles[1].color = vec3(0.5, 0.0, 0.5);

    circles[2].position = vec2(sin(t) * cos(t / 2.0), sin(t * 2.0) * sin(t / 1.5));
    circles[2].size = 50.0;
    circles[2].color = vec3(0.5, 0.5, 0.0);
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    for(int i = 0; i < 3; i++)
    {
        float dist = length(position - circles[i].position);
        float circle = sin(dist * circles[i].size);
        color += circle * circles[i].color;
    }
    return color;
}

vec3 generateTextsTexture(vec3 p) {
    //return texture(testTexture, planarMapping(p) * 2.0).rgb;
    return 1.0 - textureCube(textsTexture, p, vec3(1.0, 1.0, 1.0)).rgb;
}

//REGION_END Texturing

float attenuation(vec3 lightPosition, vec3 position, float constant, float linear, float quadratic) {
    float distance = length(lightPosition - position);
    float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance)); 
    return attenuation;
}

vec3 ambient(ambientOptions ambientOptions) {
    return ambientOptions.color * ambientOptions.strength;
} 

vec3 diffuse(vec3 normal, vec3 hit, vec3 lightDir, diffuseOptions diffuseOptions) {
    if (diffuseOptions.strength <= 0.0)
    {
        return vec3(0.0);
    }
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff * diffuseOptions.color * diffuseOptions.strength;
    return diffuse;
}

vec3 specular(vec3 normal, vec3 hit, vec3 lightDir, vec3 eye, specularOptions specularOptions) {
    if (specularOptions.strength <= 0.0)
    {
        return vec3(0.0);
    }
    vec3 viewDir = normalize(eye - hit);
    vec3 halfwayDir = normalize(lightDir + viewDir);

    float spec = pow(max(dot(normal, halfwayDir), 0.0), specularOptions.shininess);
    vec3 specular = spec * specularOptions.strength * specularOptions.color;
    return specular;
} 

vec3 calculateOmniLight(vec3 normal, vec3 eye, vec3 lightPosition, vec3 hit, ambientOptions ambientOptions, diffuseOptions diffuseOptions, specularOptions specularOptions) {
    vec3 lightDir = normalize(lightPosition - hit);
    vec3 ambient = ambient(ambientOptions);
    vec3 diffuse = diffuse(normal, hit, lightDir, diffuseOptions);
    vec3 specular = specular(normal, hit, lightDir, eye, specularOptions);

    vec3 lights = (ambient + diffuse + specular);
    return lights;
}

vec3 calculatePointLights(vec3 normal, vec3 eye, pointLight light, vec3 hit, float attenuation, diffuseOptions diffuseOptions, specularOptions specularOptions) {
    vec3 lightDir = normalize(light.position - hit);
    vec3 diffuse = diffuse(normal, hit, lightDir, diffuseOptions);
    vec3 specular = specular(normal, hit, lightDir, eye, specularOptions);

    diffuse *= attenuation;
    specular *= attenuation;

    vec3 lights = (diffuse + specular) * (light.color * light.strength);
    return lights;
}

vec3 calculateShadows(vec3 origin, vec3 lightPos, shadowOptions shadowOptions) {
    if(shadowOptions.enabled == false) {
        return vec3(1.0);
    }
    float res = 1.0;
    float ph = 1e20;
    vec3 lightDir = normalize(origin + lightPos);
    for(float t = shadowOptions.lowerLimit; t < shadowOptions.upperLimit;)
    {
        float h = scene(origin + (lightDir * t), vec2(0)).dist;
        if(h < shadowOptions.limit)
            return vec3(0.0);
            
        float y = h * h / (2.0 * ph);
        float d = sqrt(h * h - y * y);
        res = min(res, shadowOptions.hardness * d / max(0.0, t - y));
  
        ph = h;
        t += h;    
    }
    return vec3(res);
}

vec3 ao(vec3 point, vec3 normal, aoOptions aoOptions) {
	float aoOut = 1.0;
	for (float i = 0.0; i < aoOptions.limit; i++) {
		aoOut -= (i * aoOptions.factor - scene(point + normal * i * aoOptions.factor, vec2(0.0)).dist) / pow(2.0, i);
	}
	return vec3(aoOut);
}

vec3 ao2(vec3 pos, vec3 nor) {
	float occ = 0.0;
    float sca = 1.0;
    for (int i = 0; i < 5; i++)
    {
        float h = 0.01 + 0.12 * float(i) / 4.0;
        float d = scene(pos + h * nor, vec2(0)).dist;
        occ += (h - d) * sca;
        sca *= 0.95;
        if(occ > 0.35) break;
    }
    return vec3(clamp(1.0 - 3.0 * occ, 0.0, 1.0) * (0.5 + 0.5 * nor.y));
}

vec3 fog(vec3 original, vec3 color, float dist, float b) {
    return mix(original, color, 1.0 - exp(-dist * b));
}

vec3 generateTexture(vec3 point, textureOptions textureOptions) {
    vec3 r = textureOptions.baseColor;
    vec3 p = (point / textureOptions.scale) + textureOptions.offset;

    if (textureOptions.index == 1) 
        return generateTunnelTexture(p, r, 0.25, 0.20);
    else if (textureOptions.index == 2)
        return diskLabelTexture(point);
    else if (textureOptions.index == 3)
        return textTexture(p);
    else if (textureOptions.index == 4)
        return generateCirclesTexture(p, time5);
    else if (textureOptions.index == 5)
        return generateTextsTexture(p);
    else if (textureOptions.index == 6) 
        return crossTexture(p, r, 0.15, 0.15);
    else if (textureOptions.index == 7) 
        return crossTexture(p, r, 0.3, 0.5);
    return r;
}

vec3 calculateNormal(vec3 n, vec3 point, material material) {
    vec3 normal = n;
    if(material.texture.normalMap == true) {
        normal *= generateTexture(point, material.texture);
    }
    return normal;
}

vec4 processColor(hit h, vec3 rd, vec3 eye, vec2 uv) { 
    if(h.dist > rayMaxDistance) {
        return vec4(background(uv, eye, rd, h), 0.0);
    }

    entity entity = h.entity;
    material em = entity.material;
    float depth = smoothstep(0.0, rayMaxDistance, h.dist);
    vec3 texture = generateTexture(entity.point, em.texture);

    vec3 result = texture * (1.0 - depth);
    if (entity.needNormals == true) {
        vec3 normal = calculateNormal(entity.normal, entity.point, entity.material);
        vec3 lights = calculateOmniLight(normal, eye, omniLightPosition, entity.point, em.ambient, em.diffuse, em.specular);     
        vec3 shadows = calculateShadows(entity.point, omniLightPosition, em.shadow);
  
        for (int i = 0; i < NUMBER_OF_POINT_LIGHTS; i++) {
            pointLight pLight = pointLights[i];
            float attenuation = attenuation(pLight.position, entity.point, pLight.constant, pLight.linear, pLight.quadratic);
            lights += calculatePointLights(normal, eye, pLight, entity.point, attenuation, em.diffuse, em.specular);
        }

        lights *= shadows;
        result *= lights;

        if(em.ao.enabled)
        {
            result *= ao(entity.point, normal, em.ao);
        }
    }      
    
    result = fog(result, fogColor, h.dist, fogIntensity);

    return vec4(result, depth);
}

vec4 drawMarching(vec2 uv) {
    vec3 forward = normalize(cameraLookAtU - cameraPositionU);   
    vec3 right = normalize(vec3(forward.z, 0.0, -forward.x));
    vec3 up = normalize(cross(forward, right)); 
    
    vec3 rayDirection = normalize(forward + cameraFov * uv.x * right + cameraFov * uv.y * up);
    hit marchHit = raymarch(cameraPositionU, rayDirection, uv);
    return processColor(marchHit, rayDirection, cameraPositionU, uv); 
}

void initialize() {
    if (act == 1) {
        pointLights[0].position = vec3(-2.0, 0.0, 0.0);
        pointLights[0].color = BLUE;
        pointLights[0].strength = 6.0;
        pointLights[0].constant = 1.0;
        //Distance 200
        pointLights[0].linear = 0.027;
        pointLights[0].quadratic = 0.0028;

        pointLights[1].position = vec3(2.0, 0.0, 0.0);
        pointLights[1].color = WHITE;
        pointLights[1].strength = 6.0;
        pointLights[1].constant = 1.0;
        //Distance 200
        pointLights[1].linear = 0.027;
        pointLights[1].quadratic = 0.0028;  
    }
    if (act == 6) {
        pointLights[0].position = vec3(-4.0, 0.0, 0.0);
        pointLights[0].color = WHITE;
        pointLights[0].strength = 6.0;
        pointLights[0].constant = 1.0;
        //Distance 200
        pointLights[0].linear = 0.27;
        pointLights[0].quadratic = 0.028;

        pointLights[1].position = vec3(4.0, 0.0, 0.0);
        pointLights[1].color = WHITE;
        pointLights[1].strength = 6.0;
        pointLights[1].constant = 1.0;
        //Distance 200
        pointLights[1].linear = 0.27;
        pointLights[1].quadratic = 0.028;  
    }
    if (act == 8) {
        pointLights[0].position = vec3(-2.0, 1.0, 2.0);
        pointLights[0].color = RED;
        pointLights[0].strength = 16.0;
        pointLights[0].constant = 1.0;
        //Distance 200
        pointLights[0].linear = 0.27;
        pointLights[0].quadratic = 0.028;

        pointLights[1].position = vec3(2.0, -1.0, 2.0);
        pointLights[1].color = BLUE;
        pointLights[1].strength = 16.0;
        pointLights[1].constant = 1.0;
        //Distance 200
        pointLights[1].linear = 0.27;
        pointLights[1].quadratic = 0.028;  
    }
}

void main() {
    float aspectRatio = resolution.x / resolution.y;
    vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
    uv.x *= aspectRatio;

    initialize();

    FragColor = drawMarching(uv);
}
