<!DOCTYPE html>
<!--
// Title: Fusioned Bismuth (TokyoDemoFest 2017 GLSL Graphics Compo 3rd place)
// Copyright (c) 2017 gam0022
// License: Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
-->
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<title>three.js webgl - raymarching - carbon</title>
		<style type="text/css">
			body {
				background-color: black;
				margin: 0;
				padding: 0;
			}

			a { color: skyblue }

			canvas {
				display: block;
				position: absolute;
				top: 0;
				left: 0;
				right: 0;
				bottom: 0;
				margin: auto;
			}

			#info {
				color: white;
				font-size: 13px;
				position: absolute;
				bottom: 10px;
				width: 100%;
				text-align: center;
				z-index: 100;
			}
		</style>
	</head>
	<body>

		<div id="info">
			<a href="http://threejs.org" target="_blank">three.js</a> - webgl raymarching demo - Fusioned Bismuth by <a href="https://github.com/gam0022" target="_blank">gam0022</a><br>
			<a href="http://tokyodemofest.jp/2017/" target="_blank">Tokyo Demo Fest 2017</a> GLSL Graphics compo 3rd place (<a href="http://gam0022.net/blog/2017/02/24/tdf2017/" target="_blank">article in Japanese</a>)
		</div>

		<script id="fragment_shader" type="x-shader/x-fragment">
// Title: Fusioned Bismuth (TokyoDemoFest 2017 GLSL Graphics Compo 3rd place)
// Copyright (c) 2017 gam0022
// License: Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
//
// Thanks
// - DE used folding by gaz (https://www.shadertoy.com/view/Mlf3Wj)
// - Just snow by baldand (https://www.shadertoy.com/view/ldsGDn)
// - Sky and Ground by morgan3d (https://www.shadertoy.com/view/4sKGWt)

precision highp float;

#define DEBUG 1
#define SHADER_TOY 0

// uniforms
#if SHADER_TOY == 0
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
#endif

// debug for camera
#if DEBUG
uniform bool debugCamera;
uniform vec3 cameraPos;
uniform vec3 cameraDir;
#endif

// consts
const float INF = 1e+10;
const float EPS = 1e-2;
const float EPS_N = 1e-3;
const float OFFSET = EPS * 100.0;

const float PI = 3.14159265359;
const float PI2 = 6.28318530718;
const float PIH = 1.57079632679;
const float PIQ = 0.78539816339;


// globals
const vec3 lightDir = vec3( -0.48666426339228763, 0.8111071056538127, -0.3244428422615251 );
float lTime;

// ray
struct Ray {
	vec3 origin;
	vec3 direction;
};

// camera
struct Camera {
	vec3 eye, target;
	vec3 forward, right, up;
	float zoom;
};

Ray cameraShootRay(Camera c, vec2 uv) {
    c.forward = normalize(c.target - c.eye);
    c.right = normalize(cross(c.forward, c.up));
    c.up    = normalize(cross(c.right, c.forward));

    Ray r;
    r.origin = c.eye;
    r.direction = normalize(uv.x * c.right + uv.y * c.up + c.zoom * c.forward);

    return r;
}

// intersection
struct Intersection {
	bool hit;
	vec3 position;
	float distance;
	vec3 normal;
	vec2 uv;
	float count;

	//int material;
	vec3 color;
	float metalness;
};

//#define METAL_MATERIAL   0
//#define MIRROR_MATERIAL  1


// util
#define saturate(x) clamp(x, 0.0, 1.0)

float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }

float noise(vec2 x) {
	vec2 i = floor(x), f = fract(x);

	float a = hash(i);
	float b = hash(i + vec2(1.0, 0.0));
	float c = hash(i + vec2(0.0, 1.0));
	float d = hash(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;
}

// https://www.shadertoy.com/view/4sKGWt
float fbm(vec2 p) {
	const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8);

	p.xy += 0.1 * lTime;

	float f = 0.5000 * noise(p); p = m2 * p * 2.02;
	f += 0.2500 * noise(p); p = m2 * p * 2.03;
	f += 0.1250 * noise(p); p = m2 * p * 2.01;
	f += 0.0625 * noise(p);
	return f / 0.9375;
}

float easeInCubic( float t ) {
	return t * t * t;
}

float easeInOutCubic(float t) {
	return t < 0.5 ? 4.0 * t * t * t : (t - 1.0) * (2.0 * t - 2.0) * (2.0 * t - 2.0) + 1.0;
}

float gauss(float x) {
	float a = 50.0;
	return exp(-x * x / a);
}


// Distance Functions

// operations
//vec3 opRep( vec3 p, float interval ) {
//	return mod( p, interval ) - 0.5 * interval;
//}
#define opRep(p, interval) (mod(p, interval) - 0.5 * interval)

//vec2 opRepLimit(vec2 p, float interval, float limit) {
//	return mod(clamp(p, -limit, limit), interval) - 0.5 * interval;
//}
#define opRepLimit(p, interval, limit) (mod(clamp(p, -limit, limit), interval) - 0.5 * interval)

// https://www.shadertoy.com/view/Mlf3Wj
mat2 rotate(in float a) {
    float s=sin(a),c=cos(a);
    return mat2(c,s,-s,c);
}

vec2 fold(in vec2 p, in float s) {
    float a = PI / s - atan(p.x, p.y);
    float n = PI2 / s;
    a = floor(a / n) * n;
    p *= rotate(a);
    return p;
}

float smin(float d1, float d2, float k) {
	float h = exp(-k * d1) + exp(-k * d2);
	return -log(h) / k;
}

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

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

float dBar( vec2 p, float interval, float width) {
	return length( max( abs( opRep( p, interval ) ) - width, 0.0 ) );
}


#define META_SCENE_END_TIME (32.0)
#define UFO_SCENE_END_TIME (117.0)
#define SNOW_SCENE_END_TIME (182.0)

float dFract(inout vec3 p) {
	float radius = 1.0;
	float radiusScale = 0.45 * abs(sin(0.3 * lTime));
	float d = sdBox(p, vec3(radius));
	for (int i = 0; i < 5; i++) {
		vec3 q = abs(p) + normalize(vec3(-1.0)) * radius * (1.0 + radiusScale);
		d = min(d, sdBox(p, vec3(radius)));
		p = q;
		radius *= radiusScale;
	}
	return d;
}

float dTree(vec3 p) {
	float scale = 0.6 * saturate(1.5 * sin(0.05 * (lTime - UFO_SCENE_END_TIME - 2.0)));
	float width = mix(0.3 * scale, 0.0, saturate(p.y));
	vec3 size = vec3(width, 1.0, width);
	float d = sdBox(p, size);
	for (int i = 0; i < 10; i++) {
		vec3 q = p;
		q.x = abs(q.x);
		q.y -= 0.5 * size.y;
		q.xy *= rotate(-1.2);
		d = min(d, sdBox(p, size));
		p = q;
		size *= scale;
	}
	return d;
}

float dSnowCrystal(inout vec3 p) {
	p.xy = fold(p.xy, 6.0);
	return dTree(p);
}

float dMix(inout vec3 p) {
	float sphere = dSphere(p, 1.5);
	float box = sdBox(p, vec3(1.1));
	float d = mix(sphere, box, 0.5 + clamp(sin(PIQ * lTime), -0.5, 0.5));
	return d;
}

float dMeta(inout vec3 p) {
	// copy from cameraControl
	vec3 target = vec3(0.0);
	float t1 = lTime - META_SCENE_END_TIME;
	target.y = clamp(-0.7 * t1, -2.8, 100.0);

	float a = max(0.0, 2.0 - lTime * 0.1) + cos(lTime * 0.3);
	float b = 0.2 * sin(lTime);
	float d1 = dSphere(p - target - vec3(a, 0, b), 1.0);
	float d2 = dSphere(p - target + vec3(a, 0, b), 1.0);
	float d = smin(d1, d2, 1.0);
	return d;
}

float hWave(vec2 p, float t) {
	float h = 1.0;
	float a = 1.0;
	float b = 6.0;

	for(float i = 0.0; i < 3.0; i++) {
		float f = pow(2.0, i);
		h += 1.0 / f * (sin(f * a * p.x + b * t) + sin(f * a * p.y + b * t));
	}

	return h;
}

float dWing(in vec3 p) {
	float t = lTime - META_SCENE_END_TIME;
	float l = length(p.xz);
	float fusion = gauss((lTime - META_SCENE_END_TIME - 5.0) * 2.0);

	float a = 0.1 + 0.06 * (1.0 + sin(PI * t + l));
	float b = min(0.2 * t, 10.0) * gauss(l) + 0.1 * fusion * hWave(p.xz, t);
	p.y += -b + 15.0;

	vec3 p1 = p;
	p1.xz = opRepLimit(p.xz, 1.0, 20.0);

	vec3 p2 = p;
	p2 = opRep(p, 0.5);

	float d =   sdBox(p1, vec3(0.2 + a * 3.0, 12.0 - a,       0.2 + a));
	d = min(d,  sdBox(p1, vec3(0.4 - a,       13.0 - 4.0 * a, 0.1 + a)));
	d = max(d, -sdBox(p1, vec3(0.3 - a,       14.0 - 4.0 * a, a)));
	d = max(d, -sdBox(p2, vec3(0.8 * a, 1.0 - a, 0.8 * a)));
	return d;
}

float dUfo(inout vec3 p) {
	float t = max((lTime - META_SCENE_END_TIME - 10.0) * 0.5, 1.0);
	float t1 = floor(t);
	float t2 = t1 + easeInOutCubic(t - t1);

	p.xz = fold(p.xz, min(t2, 10.0));
	p.z -= 0.5;

	float d = dWing(p);
	//float t3 = lTime - META_SCENE_END_TIME;
	//if (t3 < 10.0) {
	//	d -= mix(0.08, 0.0, saturate(t3 * 0.1)) * fbm(10.0 * p.xz + 5.0 * p.y);
	//}
	return d;
}

float dScene(vec3 p) {
	if (lTime < UFO_SCENE_END_TIME - 2.0) {
		float d = dMeta(p);
		d = smin(d, dUfo(p), clamp(lTime - META_SCENE_END_TIME, 1.0, 15.0));
		return d;
	} else {
		//return dFract(p);
		return dSnowCrystal(p);
	}
}

float dSceneBump(vec3 p) {
	float d = dScene(p);

	float t3 = lTime - META_SCENE_END_TIME;
	if (t3 < 10.0) {
		d -= mix(0.08, 0.0, saturate(t3 * 0.1)) * fbm(10.0 * p.xz + 5.0 * p.y);
	}

	return d;
}


// color functions
vec3 hsv2rgb(vec3 c) {
	vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
	vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
	return c.z * mix(K.xxx, saturate(p - K.xxx), c.y);
}

//vec3 calcNormal( vec3 p ) {
//	vec2 e = vec2( 1.0, -1.0 ) * 0.001;
//	return normalize(
//		e.xyy * dScene( p + e.xyy ) + e.yyx * dScene( p + e.yyx ) +
//			e.yxy * dScene( p + e.yxy ) + e.xxx * dScene( p + e.xxx ) );
//}

#define calcNormal(p, dFunc) normalize(vec2(EPS_N, -EPS_N).xyy * dFunc(p + vec2(EPS_N, -EPS_N).xyy) + vec2(EPS_N, -EPS_N).yyx * dFunc(p + vec2(EPS_N, -EPS_N).yyx ) + vec2(EPS_N, -EPS_N).yxy * dFunc(p + vec2(EPS_N, -EPS_N).yxy) + vec2(EPS_N, -EPS_N).xxx * dFunc(p + vec2(EPS_N, -EPS_N).xxx))

float calcRate(float a, float b) {
	return a / (a + b);
}

void setMaterialAndUv(inout Intersection intersection, vec3 p) {
	//intersection.material = METAL_MATERIAL;

	float d1 = dUfo(p);
	float d2 = dMeta(p);
	float rate = calcRate(abs(d1), abs(d2));
	intersection.metalness = rate;

	float t = lTime - META_SCENE_END_TIME;
	intersection.metalness = mix(intersection.metalness, 1.0, saturate(t * 0.05));
	intersection.metalness = mix(intersection.metalness, 2.0, gauss((t - 13.0) * 3.0));

	//if ( false && lTime <= META_SCENE_END_TIME ) {
	//	intersection.material = MIRROR_MATERIAL;
	//} else {
	//	dUfo(p);
	//	intersection.material = METAL_MATERIAL;
	//	intersection.uv = p.xz;
	//}
}

void intersectScene(inout Intersection intersection, inout Ray ray ) {
	float d;
	intersection.distance = 0.0;
	vec3 p = ray.origin;

	for (float i = 0.0; i < 128.0; i++) {
		d = dScene(p);
		intersection.distance += d;
		p = ray.origin + intersection.distance * ray.direction;
		intersection.count = i;
		if (abs(d) < EPS || intersection.distance > 100.0) break;
	}

	if (abs(d) < EPS) {
		intersection.hit = true;
		intersection.position = p;
		intersection.normal = calcNormal(p, dScene);
		setMaterialAndUv(intersection, p);
	}
}

float calcAo(in vec3 p, in vec3 n){
	float sca = 1.0, occ = 0.0;
	for(float i=0.; i<5.; i++){
		float hr = 0.05 + i * 0.08;
		float dd = dScene(n * hr + p);
		occ += (hr - dd) * sca;
		sca *= 0.5;
	}
	return saturate(1.0 - occ);
}

float calcShadow(in vec3 p, in vec3 rd) {
	float d;
	float distance = OFFSET;
	float bright = 1.0;
	float shadowIntensity = 0.5;
	float shadowSharpness = 16.0;

	for (int i = 0; i < 30; i++) {
		d = dScene(p + rd * distance);
		if (d < EPS) return shadowIntensity;
		bright = min(bright, shadowSharpness * d / distance);
		distance += d;
	}

	return shadowIntensity + (1.0 - shadowIntensity) * bright;
}


// Just snow by baldand 
// https://www.shadertoy.com/view/ldsGDn
#define LAYERS 50
#define DEPTH .5
#define WIDTH .3
#define SPEED .6

float screenSpaceSnow(vec2 uv) {
	const mat3 p = mat3(13.323122,23.5112,21.71123,21.1212,28.7312,11.9312,21.8112,14.7212,61.3934);
	float acc = 0.0;
	float dof = 5.*sin(lTime*.1);
	for (int i=0;i<LAYERS;i++) {
		float fi = float(i);
		vec2 q = uv*(1.+fi*DEPTH);
		q += vec2(q.y*(WIDTH*mod(fi*7.238917,1.)-WIDTH*.5),SPEED*lTime/(1.+fi*DEPTH*.03));
		vec3 n = vec3(floor(q),31.189+fi);
		vec3 m = floor(n)*.00001 + fract(n);
		vec3 mp = (31415.9+m)/fract(p*m);
		vec3 r = fract(mp);
		vec2 s = abs(mod(q,1.)-.5+.9*r.xy-.45);
		s += .01*abs(2.*fract(10.*q.yx)-1.);
		float d = .6*max(s.x-s.y,s.x+s.y)+max(s.x,s.y)-.01;
		float edge = .005+.05*min(.5*abs(fi-5.-dof),1.);
		acc += smoothstep(edge,-edge,d)*(r.x/(1.+.02*fi*DEPTH));
	}
	return acc;
}

void calcRadiance(inout Intersection intersection, inout Ray ray, int bounce) {
	intersection.hit = false;
	intersectScene(intersection, ray);

	if ( intersection.hit ) {
		float diffuse = clamp(dot(lightDir, intersection.normal), 0.2, 1.0) * 0.5 + 0.5;
		float specular = pow(saturate(dot(reflect(lightDir, intersection.normal), ray.direction)), 10.0);
		float ao = calcAo(intersection.position, intersection.normal);
		float shadow = calcShadow(intersection.position, lightDir);

		vec3 fleshNormal = calcNormal(intersection.position, dSceneBump);
		float fleshDiffuse = clamp(dot(lightDir, fleshNormal), 0.2, 1.0) * 0.5 + 0.5;
		vec3 fleshBase =  mix(vec3(1.0, 0.2, 0.2), vec3(0.3, 0.0, 0.0), fbm(2.0 * intersection.position.xz));
		vec3 flesh = fleshBase * fleshDiffuse * ao * shadow + 0.1 * specular;

		float v = (META_SCENE_END_TIME <= lTime && lTime <= UFO_SCENE_END_TIME) ? 0.8 : 0.5;
		vec3 metalBase = hsv2rgb(vec3(0.1 * intersection.count, 0.3 * sin(lTime), v));
		vec3 metal = metalBase * diffuse * ao * shadow + 0.1 * specular;

		intersection.color = mix(flesh, metal, intersection.metalness);
		intersection.normal = mix(fleshNormal, intersection.normal, intersection.metalness);

		// fog
		intersection.color = mix(intersection.color, 0.8 * vec3(0.7, 0.75, 0.8), min(1.0, pow(0.02 * intersection.distance, 2.0)));
	} else {
		vec3 sunnySky = vec3(0.4, 0.55, 0.8);
		vec3 cloudySky = vec3(0.7);
		vec3 cloud = vec3(1.0, 0.95, 1.0);
		vec3 base = mix(sunnySky, cloudySky, step(UFO_SCENE_END_TIME - 20.0, lTime)) * (1.0 - 0.8 * ray.direction.y) * 0.9;

		// Sun
		float sundot = saturate(dot(ray.direction, lightDir));
		base += 0.25 * vec3(1.0, 0.7, 0.4) * pow(sundot, 8.0);
		base += 0.75 * vec3(1.0, 0.8, 0.5) * pow(sundot, 64.0);

		// Clouds
		float rd = ray.direction.y + 0.3;
		intersection.color = mix(base, cloud, 0.5 *
			smoothstep(0.5, 0.8, fbm((ray.origin.xz + ray.direction.xz * (250000.0 - ray.origin.y) / rd) * 0.000008)));

		intersection.color = mix(intersection.color, vec3(0.7, 0.75, 0.8), pow(1.0 - max(rd, 0.0), 4.0));
	}

	// Whiteout
	//intersection.color = mix(intersection.color, vec3(1.0), gauss((lTime - UFO_SCENE_END_TIME) * 1.0));
	intersection.color = mix(intersection.color, vec3(1.0), gauss((lTime - SNOW_SCENE_END_TIME) * 0.5));
}

void cameraControl(inout Camera camera) {
	// time
	float t1 = lTime - META_SCENE_END_TIME;
	float t2 = t1 - 25.0;
	float t3 = t2 - 16.0;
	float t4 = t3 - 20.0;

	float t5 = lTime - UFO_SCENE_END_TIME;
	float t6 = t5 - 7.0;
	float t7 = t6 - 10.0;

	// blend

	// ufo
	float b1 = easeInOutCubic(saturate(t1 * 0.2));
	float b2 = easeInOutCubic(saturate(t2 * 0.4));
	float b3 = easeInOutCubic(saturate(t3 * 0.4));
	float b4 = easeInOutCubic(saturate(t4 * 0.2));

	// snow
	float b5 = easeInOutCubic(saturate(t5 * 0.2));
	float b6 = easeInOutCubic(saturate(t6 * 0.5));
	float b7 = easeInOutCubic(saturate(t7));

	// camera target
	camera.target = vec3(0.0);

	camera.target.y = clamp(-0.7 * t1, -2.8, 100.0);
	camera.target.y = mix(camera.target.y, 1.0 + t2 * 0.3, b2);
	camera.target.y = mix(camera.target.y, 0.0, b3);
	camera.target.y = mix(camera.target.y, 0.0 + 0.6 * t4, b4);

	camera.target.y = mix(camera.target.y, 0.0, b5);
	camera.target.x = mix(camera.target.x, -0.7, b6);
	camera.target.x = mix(camera.target.x, 0.0, b7);

	// camera position
	vec3 p0 = camera.target + vec3(0.0, 0.0, -5.0); // down
	vec3 p1 = vec3(1.0, 0.3 * abs(t1 - 0.5), 1.0);  // look down
	vec3 p2 = vec3(2.5 + t2 * 0.9, 0.5 + t2 * 0.1, 0.0); // side
	vec3 p3 = vec3(0.01, 4.0 + t3 * 0.2, 0.0); // zoom
	vec3 p4 = vec3(1.5, 10.0 + t4 * 0.1, 2.0); // leave

	vec3 p5 = vec3(0.0, 0.1, 6.0 - t5 * 0.3);
	float v = 0.3 + t5 * 0.07;
	vec3 p6 = vec3(1.7 * cos(v) - 1.0, 0.1, (0.5 + t6 * 0.01) * sin(v));
	vec3 p7 = vec3(0.0, 0.0, 0.5 + t7 * 0.1);

	camera.eye = mix(p0,         p1, b1);
	camera.eye = mix(camera.eye, p2, b2);
	camera.eye = mix(camera.eye, p3, b3);
	camera.eye = mix(camera.eye, p4, b4);
	camera.eye = mix(camera.eye, p5, b5);
	camera.eye = mix(camera.eye, p6, b6);
	camera.eye = mix(camera.eye, p7, b7);
}

void main(void) {
	// local time
	lTime = mod(time, SNOW_SCENE_END_TIME + 1.0);

	// fragment position
	vec2 uv = ( gl_FragCoord.xy * 2.0 - resolution ) / min( resolution.x, resolution.y );

	// camera and ray
	Camera camera;
	cameraControl(camera);
#if DEBUG
	if (debugCamera) {
		camera.eye    = cameraPos;
		camera.target = cameraPos + cameraDir;
	}
#endif

	camera.up = vec3(0.0, 1.0, 0.0);// y-up
	camera.zoom = 1.3;
	Ray ray = cameraShootRay(camera, uv);

	vec3 color = vec3(0.0);
	float reflection = 1.0;
	Intersection intersection;

	for (int bounce = 0; bounce <= 2; bounce++) {
		calcRadiance(intersection, ray, bounce);

		color += reflection * intersection.color;
		if (!intersection.hit /* || intersection.material != METAL_MATERIAL*/) break;

		reflection *= (intersection.metalness * 0.7);
		ray.origin = intersection.position + intersection.normal * OFFSET;
		ray.direction = normalize(reflect(ray.direction, intersection.normal));
	}

	color += screenSpaceSnow(uv) * saturate((lTime - UFO_SCENE_END_TIME + 20.0) * 0.1);

	gl_FragColor = vec4(color, 1.0);
}

		</script>

		<script id="vertex_shader" type="x-shader/x-vertex">

			attribute vec3 position;

			void main(void) {

				gl_Position = vec4(position, 1.0);

			}

		</script>

		<script src="js/three.min.js"></script>
		<script src="js/controls/FlyControls.js"></script>
		<script src="js/controls/OrbitControls.js"></script>

		<script src="js/libs/stats.min.js"></script>
		<script src="js/libs/dat.gui.min.js"></script>

		<script>

			var camera, scene, flyControls, orbitControls, renderer;
			var prevCameraMatrixWorld;
			var geometry, material, plane;
			var mouse = new THREE.Vector2( 0.5, 0.5 );
			var canvas;
			var stats;

			var clock = new THREE.Clock();

			var config = {
				saveImage: function() {
					render(true);
					window.open( canvas.toDataURL() );
				},
				soucePlain: function() {
					outputShaderToTab( function(text){ return text; });
				},
				souceRelease: function() {
					outputShaderToTab( function(text){
						return text.replace(/#define DEBUG 1/, '#define DEBUG 0');
					});
				},
				souceShaderToy: function() {
					outputShaderToTab( function(text){
						return text.
							replace(/#define DEBUG 1/, '#define DEBUG 0').
							replace(/#define SHADER_TOY 0/, '#define SHADER_TOY 1').
							replace(/void main\(void\)/, 'void mainImage( out vec4 fragColor, in vec2 fragCoord )').
							replace(/\b(resolution)\b/g, 'iResolution.xy').
							replace(/\b(mouse)\b/g, 'iMouse').
							replace(/\b(gl_FragCoord)\b/g, 'fragCoord').
							replace(/\b(gl_FragColor)\b/g, 'fragColor').
							replace(/\b(time)\b/g, 'iGlobalTime');
					});
				},
				camera: 'Auto',
				resolution: 256,
				aspectRatio: 4/3,
				pixelRatio: 1,

				time: 0,
				//time: 105,
				//time: 142,

				pause: false,
			};

			init();
			animate();

			function init() {

				scene = new THREE.Scene();
				camera = new THREE.PerspectiveCamera(35, 800/600);
				camera.position.z = 5;
				camera.lookAt( new THREE.Vector3( 0.0, 0.0, 0.0 ) );

				geometry = new THREE.PlaneBufferGeometry( 2.0, 2.0 );
				material = new THREE.RawShaderMaterial( {
					uniforms: {
						resolution: { type: 'v2', value: new THREE.Vector2( config.resolution, config.resolution ) },
						mouse: { type: 'v2', value: mouse },
						time: { type: 'f', value: 0.0 },
						debugCamera: { type: 'i', value: config.camera !== 'Auto' },
						cameraPos: { type: 'v3', value: camera.getWorldPosition() },
						cameraDir: { type: 'v3', value: camera.getWorldDirection() },
					},
					vertexShader: document.getElementById( 'vertex_shader' ).textContent,
					fragmentShader: document.getElementById( 'fragment_shader' ).textContent
				} );
				plane = new THREE.Mesh( geometry, material );
				plane.frustumCulled = false;
				scene.add( plane );

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( config.pixelRatio );
				renderer.setSize( config.resolution * config.aspectRatio, config.resolution );

				canvas = renderer.domElement;
				canvas.addEventListener( 'mousemove', onMouseMove );
				window.addEventListener( 'resize', onWindowResize );
				document.body.appendChild( canvas );

				//flyControls = new THREE.FlyControls( camera, canvas );
				//flyControls.autoForward = true;
				//flyControls.dragToLook = false;
				//flyControls.rollSpeed = Math.PI / 12;
				//flyControls.movementSpeed = 0.1;

				orbitControls = new THREE.OrbitControls( camera, canvas );
				orbitControls.enablePan = true;
				//orbitControls.keyPanSpeed = 0.01;
				orbitControls.enableDamping = false;
				//orbitControls.dampingFactor = 0.015;
				orbitControls.enableZoom = true;
				//orbitControls.zoomSpeed = 0.001;
				//orbitControls.rotateSpeed = 0.8;
				orbitControls.autoRotate = false;
				orbitControls.autoRotateSpeed = 0.0;
				orbitControls.target = new THREE.Vector3( 0.0, 0.0, 0.0 );

				var gui = new dat.GUI();
				gui.add( config, 'saveImage' ).name( 'Save as PNG' );
				//gui.add( config, 'soucePlain' ).name( 'view Shader' );
				gui.add( config, 'souceRelease' ).name( 'GLSL sandbox' );
				gui.add( config, 'souceShaderToy' ).name( 'ShaderToy' );
				gui.add( config, 'camera', [ 'Auto', 'Orbit' ] ).name( 'Camera' );
				gui.add( config, 'resolution', [ 256, 512, 800, 'full' ] ).name( 'Resolution' ).onChange( function( value ) {
					onWindowResize();
					render(true);
				} );
				gui.add( config, 'aspectRatio', { '16:9': 16/9, '4:3' : 4/3, '2:1': 2, '1:1': 1.0 } ).name( 'Aspect Ratio' ).onChange( function( value ) {
					onWindowResize();
					render(true);
				} );
				gui.add( config, 'pixelRatio', { '1/4x': 0.25, '1/2x' : 0.5, '1x': 1.0, '2x': 2.0 } ).name( 'Pixel Ratio' ).onChange( function( value ) {
					onWindowResize();
					render(true);
				} );
				gui.add( config, 'time', 0, 182 ).step( 0.1 ).name( 'time' ).listen();
				gui.add( config, 'pause' ).name( 'Pause' );

				stats = new Stats();
				document.body.appendChild( stats.domElement );

			}

			function animate( timestamp ) {
				var delta = clock.getDelta();

				if ( !config.pause ) {
					config.time += delta;
				}

				stats.begin();

				var needsUpdate = config.time !== material.uniforms.time.value;

				switch ( config.camera ) {
					case 'Auto':
						// none
						break;
					case 'Orbit':
						orbitControls.update();

						if (camera && prevCameraMatrixWorld && !camera.matrixWorld.equals(prevCameraMatrixWorld)) {
							needsUpdate = true;
						}
						prevCameraMatrixWorld = camera.matrixWorld.clone();
						break;
					case 'Fly':
						flyControls.update(delta);
						break;
				}

				render(needsUpdate);

				stats.end();

				requestAnimationFrame( animate );
			}

			function render(needsUpdate) {
				material.uniforms.resolution.value = new THREE.Vector2( canvas.width, canvas.height );
				material.uniforms.mouse.value = mouse;
				material.uniforms.debugCamera.value = config.camera !== 'Auto';
				material.uniforms.cameraPos.value = camera.getWorldPosition();
				material.uniforms.cameraDir.value = camera.getWorldDirection();

				material.uniforms.time.value = config.time;

				if (needsUpdate) {
					renderer.render( scene, camera );
				}

				if (false) {
					var pos = camera.getWorldPosition();
					var dir = camera.getWorldDirection();

					document.getElementById( 'info' ).innerHTML = ''
						+ pos.x + ', ' + pos.y + ', ' + pos.z + '<br />'
						+ dir.x + ', ' + dir.y + ', ' + dir.z + '<br />'
						+ material.uniforms.time.value;
				}
			}

			function onMouseMove( e ) {
				mouse.x = e.offsetX / canvas.width;
				mouse.y = e.offsetY / canvas.height;
			}

			function onWindowResize( e ) {
				if ( config.resolution === 'full' ) {
					canvas.width  = window.innerWidth;
					canvas.height = window.innerHeight;
				} else {
					canvas.width  = config.aspectRatio * config.resolution;
					canvas.height = config.resolution;
				}
				renderer.setSize( canvas.width, canvas.height );
				renderer.setPixelRatio( config.pixelRatio );
			}

			function outputTextToTab(text) {
				var type = 'text/plain';
				window.open('data:' + type + ';base64,' + window.btoa(text));
			}

			function outputShaderToTab(filter) {
				var rawShader = document.getElementById( 'fragment_shader' ).textContent;
				var text = filter( rawShader );
				outputTextToTab( text );
			}
		</script>

	</body>
</html>