//////////////////
// IMAGE SHADER //
//////////////////
/////////////////////////////////
// COMMON FUNCTIONS AND VALUES //
/////////////////////////////////

// RAYMARCHING
#define MAX_STEPS 500
#define MAX_DIST 10.0
#define SURF_DIST 0.001
#define NORMAL_CHECK_OFFSET 0.01

// OUTLINE
#define OUTLINE_WIDTH 1.0

// LIGHTING
#define COLOR_SEPARATION 3.0
#define LIGHT_POWER 3.0

// TIMING
#define TIME iTime * 0.25

// SCENE START
#define FADE_IN_DELAY 0.1
#define FADE_IN_LENGTH 1.0

// COMMON CONSTANTS
#define PI 3.141592

// VECTORS
#define FORWARD vec3(0.0, 0.0, 1.0)
#define BACKWARD vec3(0.0, 0.0, -1.0)
#define LEFT vec3(-1.0, 0.0, 0.0)
#define RIGHT vec3(1.0, 0.0, 0.0)
#define UP vec3(0.0, 1.0, 0.0)
#define DOWN vec3(0.0, -1.0, 0.0)

// MOUSE CLICK STATES
bool IsNewClick(vec2 mouseData)
{
    return mouseData.x > 0.0 && mouseData.y < 1.0;
}

bool IsPersistentClick(vec2 mouseData)
{
    return mouseData.x > 0.0 && mouseData.y > 0.0;
}

bool IsNewRelease(vec2 mouseData)
{
    return mouseData.x < 1.0 && mouseData.y > 0.0;
}

bool IsPersistentRelease(vec2 mouseData)
{
    return mouseData.x < 1.0 && mouseData.y < 1.0;
}

bool ReadKey(sampler2D sampler, int key, bool toggle)
{
	return textureLod(sampler, vec2( (float(key)+.5)/256.0, toggle?.75:.25 ), 0.0 ).x > 0.0;
}

// MATRICES
mat2x2 Rot2(float angle)
{
    float s = sin(angle);
    float c = cos(angle);
    return mat2x2(c, -s, s, c);
}

mat3x3 Rot3(float angle, vec3 axis)
{
	float c = cos(angle);
    float s = sin(angle);

	float t = 1.0 - c;
	float x = axis.x;
	float y = axis.y;
	float z = axis.z;

	return mat3x3
	(
        t * x * x + c,      t * x * y - s * z,  t * x * z + s * y,
		t * x * y + s * z,  t * y * y + c,      t * y * z - s * x,
		t * x * z - s * y,  t * y * z + s * x,  t * z * z + c
	);
}

// USEFUL MATH STUFF
float AvgComponentLength(vec3 v)
{
    return (abs(v.x) + abs(v.y) + abs(v.z)) / 3.0;
}

float SmoothBlend(float a, float b, float smoothing)
{
    float h = clamp((b - a) / smoothing + 0.5, 0.0, 1.0);
    float m = h * (1.0 - h) * smoothing;
    return h * a + (1.0 - h) * b - m * 0.5;
}

float nsin(float value)
{
    return sin(value) * 0.5 + 0.5;
}

float ncos(float value)
{
    return cos(value) * 0.5 + 0.5;
}

float sfract(float v)
{
    if (v >= 0.0)
    {
        return v - floor(v);
    }
    return -(v - ceil(v));
}

vec2 sfract(vec2 v)
{
    return vec2(sfract(v.x), sfract(v.y));
}

vec3 sfract(vec3 v)
{
    return vec3(sfract(v.xy), sfract(v.z));
}

float smod(float v, float x)
{
    return x == 0.0 ? v : sfract(v / x) * x;
}

vec2 smod(vec2 v, vec2 x)
{
    return vec2(smod(v.x, x.x), smod(v.y, x.y));
}

vec3 smod(vec3 v, vec3 x)
{
    return vec3(smod(v.xy, x.xy), smod(v.z, x.z));
}

// PRIMITIVES
float Sphere(vec3 p, float r)
{
	return length(p) - r;
}

float Torus(vec3 p, float w, float r)
{
	return length(vec2(length(p.xz) - w, p.y)) - r;
}

float CappedTorus(in vec3 p, in float angle, in float ra, in float rb)
{
    vec2 c = vec2(sin(angle),cos(angle));

    p.x = abs(p.x);
    float k = (c.y*p.x>c.x*p.y) ? dot(p.xy,c) : length(p.xy);
    return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}

float Capsule(vec3 p, float len, float rad)
{
	vec3 a = vec3(len * 0.5, 0.0, 0.0);
	vec3 b = vec3(-len * 0.5, 0.0, 0.0);
	vec3 ab = b - a;
	vec3 ap = p - a;

	float t = dot(ab, ap) / dot(ab, ab);
	t = clamp(t, 0.0, 1.0);

	vec3 c = a + t * ab;
	return length(p - c) - rad;
}

float Cylinder(vec3 p, float l, float r)
{
	vec3 a = vec3(l * 0.5, 0.0, 0.0);
	vec3 b = vec3(-l * 0.5, 0.0, 0.0);
	vec3 ab = b - a;
	vec3 ap = p - a;

	float t = dot(ab, ap) / dot(ab, ab);

	vec3 c = a + t * ab;
	float x = length(p - c) - r;
	float y = (abs(t - 0.5) - 0.5) * length(ab);
	float e = length(max(vec2(x, y), 0.0));
	float i = min(max(x, y), 0.0);

	return e + i;
}

float Gyroid(vec3 p, vec3 repetition, float width, vec2 bias)
{
    p *= repetition;
    float avgRepetition = AvgComponentLength(repetition);
    return abs(dot(sin(p * bias.x), cos(p.zxy * bias.y))) / avgRepetition - width;
}

float Box(vec3 p, vec3 dimensions)
{
	return length(max(abs(p) - (0.5 * dimensions), 0.0));
}

float BoxFrame(vec3 p, vec3 b, float e)
{
  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));
}

float Link(vec3 p, float le, float r1, float r2)
{
  vec3 q = vec3( p.x, max(abs(p.y)-le / 2.0,0.0), p.z );
  return length(vec2(length(q.xy)-r1,q.z)) - r2;
}

float Plane(vec3 p)
{
    return abs(p.y);
}
// Define Objects Here - Based off BigWIngs Torus Knot Tutorial - https://www.shadertoy.com/view/ttXBzB
float SubGetDist(vec3 p, vec3 q)
{
    float segmentation = 10.0;
    float errorCorrection = 0.25;
    float rotations = 4.0; // Multiples of two cause the tori to self-align at end cycle

    //segmentation = nsin(TIME) * segmentation; // uncomment to see segmentation change with time

    float r1 = 1.0, r2 = .1;
    vec2 cp = vec2(length(p.xz)-r1, p.y);
    float angle = atan(q.x, q.z) * segmentation;
    angle = rotations * (angle - mod(angle, PI / 2.0)); // segmentation - comment out this line to see smooth twist

    cp = Rot2(nsin(TIME) * angle) * cp;
    cp.y = abs(cp.y) - r2;

    float d = length(cp) - r2;

    return d * errorCorrection;
}

float GetDist(vec3 p)
{
    return SubGetDist(p, p);
}

vec3 GetNormal(vec3 p)
{
	vec2 e = vec2(NORMAL_CHECK_OFFSET, 0);
	vec3 n = vec3(
		SubGetDist(p + e.xyy, p) - SubGetDist(p - e.xyy, p),
		SubGetDist(p + e.yxy, p) - SubGetDist(p - e.yxy, p),
		SubGetDist(p + e.yyx, p) - SubGetDist(p - e.yyx, p)
		);
	return normalize(n);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    // Keep outline width same in full screen
    float outlineWidth = OUTLINE_WIDTH / iResolution.x;

    vec2 uv = fragCoord/iResolution.xy;
    uv -= vec2(0.5);
    float vignette = smoothstep(0.9, 0.001, length(uv));
    float aspectRatio = iResolution.x / iResolution.y;
    uv.x *= aspectRatio;

    // Camera Stuff
    vec3 camPos = vec3(0.0, 1.0, -2.0);

    mat3x3 rotMat = Rot3(-PI / 5.25, RIGHT);
    vec3 camDir = rotMat * normalize(vec3(uv.x, uv.y, 1.0));

    // Do raymarching
    float dO = 0.0;
	vec3 p;
	bool hit = false;
	bool outline = false;
    float prevDist = MAX_DIST * MAX_DIST;

	for (int i = 0; i < MAX_STEPS; i++)
	{
		p = camPos + camDir * dO;
		float d = GetDist(p);
		dO += d;

		if (!outline && d < outlineWidth * (log2(dO + 1.0)) + SURF_DIST)
		{
			outline = true;
		}

		if (dO > MAX_DIST)
		{
            outline = false;
			break;
		}

		if (d <= SURF_DIST)
		{
            hit = true;
            outline = false;
			break;
		}

        // If at any point we've gotten within the thickness of our outline and
        // then we get further away from a hit we break out and draw the outline
        if (outline && prevDist < d)
        {
            break;
        }

        prevDist = d;
	}

    vec3 col;
    vec3 bgcol = mix(vec3(0.0), vec3(1.0, 1.0, 1.0), smoothstep(-1.0, -.25, -abs(camDir.y)));

    if (hit)
    {
        vec3 normal = GetNormal(p);
        vec3 absNormal = abs(normal);
        col = vec3(
            pow(absNormal.x, COLOR_SEPARATION),
            pow(absNormal.y, COLOR_SEPARATION),
            pow(absNormal.z, COLOR_SEPARATION)
        );

        vec3 lightDir = normalize(vec3(1.0, 1.0, -0.5));
        float scalar = dot(lightDir, normal);
        scalar = clamp(scalar, 0.0, 1.0);
        scalar *= LIGHT_POWER;
        scalar = (scalar + 1.0) / 2.0;
        col *= scalar;
        float distRatio = dO / MAX_DIST;
        col = mix(col, bgcol, distRatio);
    }
    else if (outline)
    {
        col = vec3(0.0);
        col = mix(bgcol, col, smoothstep(MAX_DIST, 0.0, dO));
    }
    else
    {
        col = bgcol;
    }

    // Fade scene in at start
    col *= smoothstep(FADE_IN_DELAY, FADE_IN_DELAY + FADE_IN_LENGTH, iTime);
    col *= vignette;

    // Output to screen
    fragColor = vec4(col, 1.0);
}