Browse Source

Added depth-of-field example.

alteredq 14 years ago
parent
commit
408fad1905

+ 115 - 0
examples/js/ShaderExtras.js

@@ -0,0 +1,115 @@
+var ShaderExtras = {
+
+	/* -------------------------------------------------------------------------
+	//	Depth-of-field shader with bokeh
+	//	ported from GLSL shader by Martins Upitis 
+	//	http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html
+	 ------------------------------------------------------------------------- */
+	
+	'bokeh'	: {
+
+	uniforms: { tColor:   { type: "t", value: 0, texture: null },
+				tDepth:   { type: "t", value: 1, texture: null },
+				focus:    { type: "f", value: 1.0 },
+				aspect:   { type: "f", value: 1.0 },
+				aperture: { type: "f", value: 0.025 },
+				maxblur:  { type: "f", value: 1.0 },
+			  },
+
+	vertex_shader: [
+
+	"varying vec2 vUv;",
+
+	"void main() {",
+		
+		"vUv = vec2( uv.x, 1.0 - uv.y );",
+		"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+		
+	"}"
+
+	].join("\n"),
+
+	fragment_shader: [
+
+	"varying vec2 vUv;",
+	
+	"uniform sampler2D tColor;",
+	"uniform sampler2D tDepth;",
+
+	"uniform float maxblur;",  	// max blur amount
+	"uniform float aperture;",	// aperture - bigger values for shallower depth of field
+	
+	"uniform float focus;",
+	"uniform float aspect;",
+	
+	"void main() {",
+
+	"vec2 aspectcorrect = vec2( 1.0, aspect );",
+
+	"vec4 depth1 = texture2D( tDepth, vUv );",
+
+	"float factor = depth1.x - focus;",
+ 
+	"vec2 dofblur = vec2 ( clamp( factor * aperture, -maxblur, maxblur ) );",
+	
+	"vec2 dofblur9 = dofblur * 0.9;",
+	"vec2 dofblur7 = dofblur * 0.7;",
+	"vec2 dofblur4 = dofblur * 0.4;",
+
+	"vec4 col = vec4( 0.0 );",
+
+	"col += texture2D( tColor, vUv.xy );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.15,  0.37 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.37,  0.15 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.40,  0.0  ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.37, -0.15 ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.15,  0.37 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.37,  0.15 ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.15, -0.37 ) * aspectcorrect ) * dofblur );",
+
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.15,  0.37 ) * aspectcorrect ) * dofblur9 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.37,  0.15 ) * aspectcorrect ) * dofblur9 );",		
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.37, -0.15 ) * aspectcorrect ) * dofblur9 );",		
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur9 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.15,  0.37 ) * aspectcorrect ) * dofblur9 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.37,  0.15 ) * aspectcorrect ) * dofblur9 );",		
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur9 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.15, -0.37 ) * aspectcorrect ) * dofblur9 );",	
+
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur7 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.40,  0.0  ) * aspectcorrect ) * dofblur7 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur7 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur7 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur7 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur7 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur7 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur7 );",
+		 
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur4 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.4,   0.0  ) * aspectcorrect ) * dofblur4 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur4 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur4 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur4 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur4 );",
+	"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur4 );",	
+	"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur4 );",
+
+	"gl_FragColor = col / 41.0;",
+	"gl_FragColor.a = 1.0;",
+	
+	"}"
+	
+	].join("\n")	
+	
+}
+
+};

+ 170 - 0
examples/js/gui/gui.css

@@ -0,0 +1,170 @@
+#guidat { 
+	position: fixed;
+	top: 0; 
+	right: 0;
+	width: auto;
+	z-index: 1001;
+	text-align: right;
+}
+
+.guidat { 
+	color: #fff;
+	opacity: 0.97;
+	text-align: left;
+	float: right;
+	margin-right: 20px;
+	margin-bottom: 20px;
+	background-color: #fff;
+}
+
+.guidat,
+.guidat input { 
+	font: 9.5px Lucida Grande, sans-serif;
+}
+
+.guidat-controllers { 
+	height: 300px;
+	overflow-y: auto;
+	overflow-x: hidden;
+	background-color: rgba(0,0,0,0.1);
+	/*
+	-moz-transition: height .2s ease-out;
+	-webkit-transition: height .2s ease-out;
+	transition: height .2s ease-out;
+	*/
+}
+
+a.guidat-toggle { 
+	text-decoration: none;
+	cursor: pointer;
+	color: #fff;
+	background-color: #222;
+	text-align: center;
+	display: block;
+	padding: 5px;
+
+}
+
+a.guidat-toggle:hover { 
+	background-color: #000;
+}
+
+.guidat-controller { 
+	padding: 3px;
+	height: 25px;
+	clear: left;
+	border-bottom: 1px solid #222;
+	background-color: #111;
+}
+
+.guidat-controller,
+.guidat-controller input,
+.guidat-slider-bg,
+.guidat-slider-fg { 
+	-moz-transition: background-color 0.15s linear;
+	-webkit-transition: background-color 0.15s linear;
+	transition: background-color 0.15s linear;
+}
+
+.guidat-controller.boolean:hover,
+.guidat-controller.function:hover {
+	background-color: #000;
+}
+
+.guidat-controller input { 
+	float: right;
+	outline: none;
+	border: 0;
+	padding: 4px;
+	margin-top: 2px;
+	background-color: #222;
+}
+
+
+.guidat-controller select {
+	margin-top: 4px;
+	float: right;
+}
+
+.guidat-controller input:hover { 
+	background-color: #444;
+}
+
+.guidat-controller input:focus { 
+	background-color: #555;
+}
+
+.guidat-controller.number { 
+	border-left: 5px solid #00aeff ;
+}
+
+.guidat-controller.string { 
+	border-left: 5px solid #1ed36f;
+}
+
+.guidat-controller.string input { 
+	border: 0;
+	color: #1ed36f;
+	margin-right: 2px;
+	width: 148px;
+}
+
+.guidat-controller.boolean { 
+	border-left: 5px solid #54396e;
+}
+
+.guidat-controller.function { 
+	border-left: 5px solid #e61d5f;
+}
+
+.guidat-controller.number input[type=text] {
+	width: 35px;
+	margin-left: 5px;
+	margin-right: 2px;
+	color: #00aeff;
+}
+
+.guidat .guidat-controller.boolean input { 
+	margin-top: 6px;
+	margin-right: 2px;
+	font-size: 20px;
+}
+
+.guidat-controller:last-child {
+	border-bottom: none;
+	-webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5);
+	-moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5);
+	box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5);
+}
+
+.guidat-propertyname { 
+	padding: 5px;
+	padding-top: 7px;
+	cursor: default;
+	display: inline-block;
+}
+
+
+.guidat-slider-bg:hover,
+.guidat-slider-bg.active {
+	background-color: #444;
+}
+
+.guidat-slider-bg:hover .guidat-slider-fg, 
+.guidat-slider-bg.active .guidat-slider-fg { 
+	background-color: #52c8ff;
+}
+
+.guidat-slider-bg { 
+	background-color: #222;
+	cursor: ew-resize;
+	width: 40%;
+	margin-top: 2px;
+	float: right;
+	height: 21px;
+}
+
+.guidat-slider-fg { 
+	background-color: #00aeff;
+	height: 20px;
+}

+ 1051 - 0
examples/js/gui/gui.full.js

@@ -0,0 +1,1051 @@
+var GUI = function() {
+
+	var _this = this;
+	
+	var MIN_WIDTH = 240;
+	var MAX_WIDTH = 500;
+	
+	var controllers = [];
+	var listening = [];
+	
+	var autoListen = true;
+	
+	var listenInterval;
+	
+	// Sum total of heights of controllers in this gui
+	var controllerHeight;
+	
+	var curControllerContainerHeight = 0;
+	
+	var _this = this;
+	
+	var open = false;
+	var width = 280;
+	
+	// Prevents checkForOverflow bug in which loaded gui appearance
+	// settings are not respected by presence of scrollbar.
+	var explicitOpenHeight = false;
+	
+	// How big we get when we open
+	var openHeight;
+	
+	var name;
+	
+	var resizeTo = 0;
+	var resizeTimeout;
+	
+	this.domElement = document.createElement('div');
+	this.domElement.setAttribute('class', 'guidat');
+	this.domElement.style.width = width+'px';
+
+	var controllerContainer = document.createElement('div');
+	controllerContainer.setAttribute('class', 'guidat-controllers');
+	
+	// Firefox hack to prevent horizontal scrolling
+	controllerContainer.addEventListener('DOMMouseScroll', function(e) {
+		
+		var scrollAmount = this.scrollTop;
+		
+		if (e.wheelDelta) {
+			scrollAmount+=e.wheelDelta; 
+		} else if (e.detail) {
+			scrollAmount+=e.detail;
+		}
+			
+		if (e.preventDefault) {
+			e.preventDefault();
+		}
+		e.returnValue = false;
+		
+		controllerContainer.scrollTop = scrollAmount;
+		
+	}, false);
+	
+	controllerContainer.style.height = '0px';
+
+	var toggleButton = document.createElement('a');
+	toggleButton.setAttribute('class', 'guidat-toggle');
+	toggleButton.setAttribute('href', '#');
+	toggleButton.innerHTML = "Show Controls";
+	
+	var toggleDragged = false;
+	var dragDisplacementY = 0;
+	var togglePressed = false;
+	
+	var my, pmy, mx, pmx;
+	
+	var resize = function(e) {
+		pmy = my;
+		pmx = mx;
+		my = e.pageY;
+		mx = e.pageX;
+		
+		var dmy = my - pmy;
+				
+		if (!open) { 
+			if (dmy > 0) {
+				open = true;
+				curControllerContainerHeight = openHeight = 1;
+				toggleButton.innerHTML = name || "Hide Controls";
+			} else {
+				return;
+			}
+		}
+		
+		// TODO: Flip this if you want to resize to the left.
+		var dmx = pmx - mx;
+		
+		if (dmy > 0 && 
+			curControllerContainerHeight > controllerHeight) {
+			var d = GUI.map(curControllerContainerHeight, controllerHeight, controllerHeight + 100, 1, 0);
+			dmy *= d;
+		}
+		
+		toggleDragged = true;
+		dragDisplacementY += dmy;
+		dragDisplacementX += dmx;
+		openHeight += dmy;
+		width += dmx;
+		curControllerContainerHeight += dmy;
+		controllerContainer.style.height = openHeight+'px';
+		width = GUI.constrain(width, MIN_WIDTH, MAX_WIDTH);
+		_this.domElement.style.width = width+'px';
+		checkForOverflow();
+	};
+	
+	toggleButton.addEventListener('mousedown', function(e) {
+		pmy = my = e.pageY;
+		pmx = mx = e.pageX;
+		togglePressed = true;
+		e.preventDefault();
+		dragDisplacementY = 0;
+		dragDisplacementX = 0;
+		document.addEventListener('mousemove', resize, false);
+		return false;
+
+	}, false);
+	
+	
+	toggleButton.addEventListener('click', function(e) {
+		e.preventDefault();
+		return false;
+	}, false);
+	
+	
+	document.addEventListener('mouseup', function(e) {
+		
+		if (togglePressed && !toggleDragged) {
+			
+			_this.toggle();
+
+			// Clears lingering slider column
+			_this.domElement.style.width = (width+1)+'px';
+			setTimeout(function() {
+				_this.domElement.style.width = width+'px';
+			}, 1);
+		}
+		
+		if (togglePressed && toggleDragged) {
+		
+			if (dragDisplacementX == 0) {
+			
+				// Clears lingering slider column
+				_this.domElement.style.width = (width+1)+'px';
+				setTimeout(function() {
+					_this.domElement.style.width = width+'px';
+				}, 1);
+	
+			}
+		
+			if (openHeight > controllerHeight) {
+			
+				clearTimeout(resizeTimeout);
+				openHeight = resizeTo = controllerHeight;
+				beginResize();
+				
+			} else if (controllerContainer.children.length >= 1) {
+
+				var singleControllerHeight = controllerContainer.children[0].offsetHeight;			
+				clearTimeout(resizeTimeout);
+				var target = Math.round(curControllerContainerHeight/singleControllerHeight)*singleControllerHeight-1;
+				resizeTo = target;
+				if (resizeTo <= 0) {
+					_this.hide();
+					openHeight = singleControllerHeight*2;
+				} else { 
+					openHeight = resizeTo;		
+					beginResize();			
+				}
+			}
+			
+			
+			
+		}
+		
+		
+		document.removeEventListener('mousemove', resize, false);
+		e.preventDefault();
+		toggleDragged = false;
+		togglePressed = false;
+		
+		return false;
+
+	}, false);
+	
+	this.domElement.appendChild(controllerContainer);
+	this.domElement.appendChild(toggleButton);
+
+	if (GUI.autoPlace) {
+		if(GUI.autoPlaceContainer == null) {
+			GUI.autoPlaceContainer = document.createElement('div');
+			GUI.autoPlaceContainer.setAttribute("id", "guidat");
+			
+			document.body.appendChild(GUI.autoPlaceContainer);
+		}
+		GUI.autoPlaceContainer.appendChild(this.domElement);
+	}
+
+	this.autoListenIntervalTime = 1000/60;
+
+	var createListenInterval = function() {
+		listenInterval = setInterval(function() {
+			_this.listen();
+		}, this.autoListenIntervalTime);
+	}
+	
+	this.__defineSetter__("autoListen", function(v) {
+		autoListen = v;
+		if (!autoListen) {
+			clearInterval(listenInterval);
+		} else { 
+			if (listening.length > 0) createListenInterval();
+		}
+	});
+	
+	this.__defineGetter__("autoListen", function(v) {
+		return autoListen;
+	});
+	
+	this.listenTo = function(controller) {
+	 	// TODO: check for duplicates
+	 	if (listening.length == 0) {
+	 		createListenInterval();
+	 	}
+		listening.push(controller);
+	};
+	
+	this.unlistenTo = function(controller) {
+		// TODO: test this
+		for(var i = 0; i < listening.length; i++) {
+			if(listening[i] == controller) listening.splice(i, 1);
+		}
+		if(listening.length <= 0) {
+			clearInterval(listenInterval);
+		}
+	};
+	
+	this.listen = function(whoToListenTo) {
+		var arr = whoToListenTo || listening;
+		for (var i in arr) {
+			arr[i].updateDisplay();
+		}
+	};
+	
+	this.listenAll = function() {
+		this.listen(controllers);
+	}
+	
+	this.autoListen = true;
+
+	var alreadyControlled = function(object, propertyName) {
+		for (var i in controllers) {
+			if (controllers[i].object == object &&
+				controllers[i].propertyName == propertyName) {
+				return true;
+			}
+		}
+		return false;
+	};
+
+
+	var construct = function(constructor, args) {
+		function F() {
+		    return constructor.apply(this, args);
+		}
+		F.prototype = constructor.prototype;
+		return new F();
+	};
+
+	this.add = function() {
+
+		var object = arguments[0];
+		var propertyName = arguments[1];
+
+		// Have we already added this?
+		if (alreadyControlled(object, propertyName)) {
+		//	GUI.error("Controller for \"" + propertyName+"\" already added.");
+		//	return;
+		}
+
+		var value = object[propertyName];
+
+		// Does this value exist? Is it accessible?
+		if (value == undefined) {
+			GUI.error(object + " either has no property \""+propertyName+"\", or the property is inaccessible.");
+			return;
+		}
+
+		var type = typeof value;
+		var handler = handlerTypes[type];
+
+		// Do we know how to deal with this data type?
+		if (handler == undefined) {
+			GUI.error("Cannot create controller for data type \""+type+"\"");
+			return;
+		}
+	
+		var args = [this]; // Set first arg (parent) to this 
+		for (var j = 0; j < arguments.length; j++) {
+			args.push(arguments[j]);
+		}
+	
+		var controllerObject = construct(handler, args);
+		
+		// Were we able to make the controller?
+		if (!controllerObject) {
+			GUI.error("Error creating controller for \""+propertyName+"\".");
+			return;
+		}
+
+		// Success.
+		controllerContainer.appendChild(controllerObject.domElement);
+		controllers.push(controllerObject);
+		GUI.allControllers.push(controllerObject);
+
+		// Do we have a saved value for this controller?
+		if (type != "function" && 
+			GUI.saveIndex < GUI.savedValues.length) {
+			controllerObject.setValue(GUI.savedValues[GUI.saveIndex]);
+			GUI.saveIndex++;
+		}
+	
+		// Compute sum height of controllers.
+		checkForOverflow();
+		
+		// Prevents checkForOverflow bug in which loaded gui appearance
+		// settings are not respected by presence of scrollbar.
+		if (!explicitOpenHeight) {
+			openHeight = controllerHeight;
+		}
+		
+		return controllerObject;
+		
+	}
+	
+	var checkForOverflow = function() {
+		controllerHeight = 0;
+		for (var i in controllers) {
+			controllerHeight += controllers[i].domElement.offsetHeight;
+		}
+		if (controllerHeight - 1 > openHeight) {
+			controllerContainer.style.overflowY = "auto";
+		} else {
+			controllerContainer.style.overflowY = "hidden";
+		}	
+	}
+	
+	var handlerTypes = {
+		"number": GUI.NumberController,
+		"string": GUI.StringController,
+		"boolean": GUI.BooleanController,
+		"function": GUI.FunctionController
+	};
+	
+	var alreadyControlled = function(object, propertyName) {
+		for (var i in controllers) {
+			if (controllers[i].object == object &&
+				controllers[i].propertyName == propertyName) {
+				return true;
+			}
+		}
+		return false;
+	};
+	
+	var construct = function(constructor, args) {
+        function F() {
+            return constructor.apply(this, args);
+        }
+        F.prototype = constructor.prototype;
+        return new F();
+    };
+
+	this.reset = function() {
+		// TODO
+	}
+
+	// GUI ... GUI
+	
+	this.toggle = function() {
+		open ? this.hide() : this.show();
+	};
+
+	this.show = function() {
+		toggleButton.innerHTML = name || "Hide Controls";
+		resizeTo = openHeight;
+		clearTimeout(resizeTimeout);
+		beginResize();
+		open = true;
+	}
+
+	this.hide = function() {
+		toggleButton.innerHTML = name || "Show Controls";
+		resizeTo = 0;
+		clearTimeout(resizeTimeout);
+		beginResize();
+		open = false;
+	}
+	
+	this.name = function(n) {
+		name = n;
+		toggleButton.innerHTML = n;
+	}
+	
+	// used in saveURL
+	this.appearanceVars = function() {
+		return [open, width, openHeight, controllerContainer.scrollTop]
+	}
+	
+	var beginResize = function() {
+		//console.log("Resizing from " + curControllerContainerHeight + " to " + resizeTo);
+		curControllerContainerHeight += (resizeTo - curControllerContainerHeight)*0.6;
+		if (Math.abs(curControllerContainerHeight-resizeTo) < 1) {
+			curControllerContainerHeight = resizeTo;
+		} else { 
+			resizeTimeout = setTimeout(beginResize, 1000/30);
+		}
+		controllerContainer.style.height = Math.round(curControllerContainerHeight)+'px';
+		checkForOverflow();
+	}
+	
+	// Load saved appearance:
+
+	if (GUI.guiIndex < GUI.savedAppearanceVars.length) {
+
+	
+		width = parseInt(GUI.savedAppearanceVars[GUI.guiIndex][1]);
+		_this.domElement.style.width = width+"px";
+		
+		openHeight = parseInt(GUI.savedAppearanceVars[GUI.guiIndex][2]);
+		explicitOpenHeight = true;
+		if (eval(GUI.savedAppearanceVars[GUI.guiIndex][0]) == true) {
+			curControllerContainerHeight = openHeight;
+			var t = GUI.savedAppearanceVars[GUI.guiIndex][3]
+			
+			// Hack.
+			setTimeout(function() {
+				controllerContainer.scrollTop = t;
+			}, 0);
+			
+			if (GUI.scrollTop > -1) {
+				document.body.scrollTop = GUI.scrollTop;
+			}
+			resizeTo = openHeight;
+			this.show();
+		}
+
+		GUI.guiIndex++;
+	}
+
+	GUI.allGuis.push(this);
+
+};
+
+// Static members
+
+GUI.autoPlace = true;
+GUI.autoPlaceContainer = null;
+GUI.allControllers = [];
+GUI.allGuis = [];
+
+GUI.saveURL = function() { 
+	title = window.location;
+	url = GUI.replaceGetVar("saveString", GUI.getSaveString());
+	window.location = url;
+};
+
+GUI.scrollTop = -1;
+
+GUI.load = function(saveString) {
+
+	//GUI.savedAppearanceVars = [];
+	var vals = saveString.split(",");
+	var numGuis = parseInt(vals[0]);
+	GUI.scrollTop = parseInt(vals[1]);
+	for (var i = 0; i < numGuis; i++) {
+		var appr = vals.splice(2, 4);
+		GUI.savedAppearanceVars.push(appr);
+	}
+	
+	GUI.savedValues = vals.splice(2, vals.length);
+	
+};
+
+GUI.savedValues = [];
+GUI.savedAppearanceVars = [];
+
+GUI.getSaveString = function() {
+
+	var vals = [];
+	
+	vals.push(GUI.allGuis.length);
+	vals.push(document.body.scrollTop);
+	
+	
+	for (var i in GUI.allGuis) {
+		var av = GUI.allGuis[i].appearanceVars();
+		for (var j = 0; j < av.length; j++) {
+			vals.push(av[j]);
+		}
+	}	
+	
+	for (var i in GUI.allControllers) {
+	
+		// We don't save values for functions.
+		if (GUI.allControllers[i].type == "function") {
+			continue;
+		}
+		
+		var v = GUI.allControllers[i].getValue();
+		
+		// Round numbers so they don't get enormous
+		if (GUI.allControllers[i].type == "number") {
+			v = GUI.roundToDecimal(v, 4);
+		}
+		
+		vals.push(v);
+		
+	}
+	
+	return vals.join(',');
+	
+}
+
+GUI.getVarFromURL = function(v) {
+
+    var vars = [], hash;
+    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
+
+    for (var i = 0; i < hashes.length; i++) {
+ 		hash = hashes[i].split("=")
+        if (hash == undefined) continue;
+		if (hash[0] == v) {
+			return hash[1];
+		}
+    }
+	
+	return null;
+
+};
+
+GUI.replaceGetVar = function(varName, val) {
+
+    var vars = [], hash;
+    var loc = window.location.href;
+    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
+
+    for (var i = 0; i < hashes.length; i++) {
+ 		hash = hashes[i].split("=")
+        if (hash == undefined) continue;
+		if (hash[0] == varName) {
+			return loc.replace(hash[1], val);
+		}
+    }
+	
+	if (window.location.href.indexOf('?') != -1) {
+		return loc + "&"+varName+"="+val;
+	}
+	
+	return loc+"?"+varName+"="+val;
+	
+};
+
+GUI.saveIndex = 0;
+GUI.guiIndex = 0;
+
+GUI.showSaveString = function() {
+	alert(GUI.getSaveString());
+}
+
+// Util functions
+
+GUI.makeUnselectable = function(elem) {
+	elem.onselectstart = function() { return false; };
+	elem.style.MozUserSelect = "none";
+	elem.style.KhtmlUserSelect = "none";
+	elem.unselectable = "on";
+}
+    
+GUI.makeSelectable = function(elem) {
+	elem.onselectstart = function() { };
+	elem.style.MozUserSelect = "auto";
+	elem.style.KhtmlUserSelect = "auto";
+	elem.unselectable = "off";
+}
+
+GUI.map = function(v, i1, i2, o1, o2) {
+	var v = o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
+	return v;
+}
+
+GUI.constrain = function (v, o1, o2) {
+	if (v < o1) v = o1;
+	else if (v > o2) v = o2;
+	return v;
+}
+
+GUI.error = function(str) {
+	if (typeof console.error == 'function') {
+		console.error("[GUI ERROR] " + str);
+	}
+};
+
+GUI.roundToDecimal = function(n, decimals) {
+	var t = Math.pow(10, decimals);
+	return Math.round(n*t)/t;
+}
+
+GUI.extendController = function(clazz) {
+	clazz.prototype = new GUI.Controller();
+	clazz.prototype.constructor = clazz;
+}
+
+if (GUI.getVarFromURL('saveString') != null) GUI.load(GUI.getVarFromURL('saveString'));GUI.Slider = function(numberController, min, max, step, initValue) {
+	
+	var min = min;
+	var max = max;
+	var step = step;
+	
+	var clicked = false;
+	var _this = this;
+	
+	var x, px;
+	
+	this.domElement = document.createElement('div');
+	this.domElement.setAttribute('class', 'guidat-slider-bg');
+	
+	this.fg = document.createElement('div');
+	this.fg.setAttribute('class', 'guidat-slider-fg');
+	
+	this.domElement.appendChild(this.fg);
+	
+	var findPos = function(obj) {
+		var curleft = curtop = 0;
+		if (obj.offsetParent) {
+			do {
+				curleft += obj.offsetLeft;
+				curtop += obj.offsetTop;
+			} while (obj = obj.offsetParent);
+			return [curleft,curtop];
+		}
+	}
+	
+	this.__defineSetter__('value', function(e) {
+		var pct = GUI.map(e, min, max, 0, 100);
+		this.fg.style.width = pct+"%";
+	});
+
+	var onDrag = function(e) {
+		if (!clicked) return;
+		var pos = findPos(_this.domElement);
+		var val = GUI.map(e.pageX, pos[0], pos[0] + _this.domElement.offsetWidth, min, max);
+		val = Math.round(val/step)*step;
+		numberController.setValue(val);
+	}
+	
+	this.domElement.addEventListener('mousedown', function(e) {
+		clicked = true;
+		x = px = e.pageX;
+		_this.domElement.setAttribute('class', 'guidat-slider-bg active');
+		_this.fg.setAttribute('class', 'guidat-slider-fg active');
+		onDrag(e);
+		document.addEventListener('mouseup', mouseup, false);
+	}, false);
+	
+	
+	var mouseup = function(e) { 
+		_this.domElement.setAttribute('class', 'guidat-slider-bg');
+		_this.fg.setAttribute('class', 'guidat-slider-fg');
+		clicked = false;			
+		if (numberController.finishChangeFunction != null) {
+			numberController.finishChangeFunction.call(this, numberController.getValue());
+		}
+		document.removeEventListener('mouseup', mouseup, false);
+	};
+
+	
+	document.addEventListener('mousemove', onDrag, false);
+	
+	this.value = initValue;	
+		
+}
+GUI.Controller = function() {
+
+	this.parent = arguments[0];
+    this.object = arguments[1];
+    this.propertyName = arguments[2];
+
+	if (arguments.length > 0) this.initialValue = this.propertyName[this.object];
+
+    this.domElement = document.createElement('div');
+    this.domElement.setAttribute('class', 'guidat-controller ' + this.type);
+    
+    this.propertyNameElement = document.createElement('span');
+    this.propertyNameElement.setAttribute('class', 'guidat-propertyname');
+    this.name(this.propertyName);
+    this.domElement.appendChild(this.propertyNameElement);
+    
+    GUI.makeUnselectable(this.domElement);
+    
+};
+
+GUI.Controller.prototype.changeFunction = null;
+GUI.Controller.prototype.finishChangeFunction = null;
+
+GUI.Controller.prototype.name = function(n) {
+	this.propertyNameElement.innerHTML = n;
+	return this;
+};
+
+GUI.Controller.prototype.reset = function() {
+	this.setValue(this.initialValue);
+	return this;
+};
+
+GUI.Controller.prototype.listen = function() {
+	this.parent.listenTo(this);
+	return this;
+}
+
+GUI.Controller.prototype.unlisten = function() {
+	this.parent.unlistenTo(this); // <--- hasn't been tested yet
+	return this;
+}
+    
+GUI.Controller.prototype.setValue = function(n) {
+	this.object[this.propertyName] = n;
+	if (this.changeFunction != null) {
+		this.changeFunction.call(this, n);
+	}
+	this.updateDisplay();
+	return this;
+}
+    
+GUI.Controller.prototype.getValue = function() {
+	return this.object[this.propertyName];
+}
+
+GUI.Controller.prototype.updateDisplay = function() {}
+    
+GUI.Controller.prototype.onChange = function(fnc) {
+	this.changeFunction = fnc;
+	return this;
+}
+GUI.Controller.prototype.onFinishChange = function(fnc) {
+	this.finishChangeFunction = fnc;
+	return this;
+}
+
+GUI.Controller.prototype.options = function() {
+	var _this = this;
+	var select = document.createElement('select');
+	if (arguments.length == 1) {
+		var arr = arguments[0];
+		for (var i in arr) {
+			var opt = document.createElement('option');
+			opt.innerHTML = i;
+			opt.setAttribute('value', arr[i]);
+			select.appendChild(opt);
+		}
+	} else { 
+		for (var i = 0; i < arguments.length; i++) {
+			var opt = document.createElement('option');
+			opt.innerHTML = arguments[i];
+			opt.setAttribute('value', arguments[i]);
+			select.appendChild(opt);
+		}
+	}
+	
+	select.addEventListener('change', function() {
+		_this.setValue(this.value);
+		if (_this.finishChangeFunction != null) {
+			_this.finishChangeFunction.call(this, _this.getValue());
+		}
+	}, false);
+	_this.domElement.appendChild(select);
+	return this;
+}
+GUI.BooleanController = function() {
+
+	this.type = "boolean";
+	GUI.Controller.apply(this, arguments);
+
+	var _this = this;
+    var input = document.createElement('input');
+    input.setAttribute('type', 'checkbox');
+    
+    this.domElement.addEventListener('click', function(e) {
+    	input.checked = !input.checked;
+    	e.preventDefault();
+    	_this.setValue(input.checked);
+    }, false);
+    
+    input.addEventListener('mouseup', function(e) {
+    	input.checked = !input.checked; // counteracts default.
+    }, false);
+    
+    this.domElement.style.cursor = "pointer";
+    this.propertyNameElement.style.cursor = "pointer";
+    this.domElement.appendChild(input);
+    
+    this.updateDisplay = function() {
+    	input.checked = _this.getValue();
+    };
+    
+    
+    this.setValue = function(val) {
+		if (typeof val != "boolean") {
+			try { 
+				val = eval(val);
+			} catch (e) {}
+		}
+		return GUI.Controller.prototype.setValue.call(this, val);
+    }
+
+};
+GUI.extendController(GUI.BooleanController);GUI.FunctionController = function() {
+	
+	this.type = "function";
+	
+	var _this = this;
+	
+	GUI.Controller.apply(this, arguments);
+	
+	this.domElement.addEventListener('click', function() {
+		_this.fire();
+	}, false);
+	
+	this.domElement.style.cursor = "pointer";
+	this.propertyNameElement.style.cursor = "pointer";
+	
+	var fireFunction = null;
+	this.onFire = function(fnc) {
+		fireFunction = fnc;
+		return this;
+	}
+	
+	this.fire = function() {
+		if (fireFunction != null) {
+			fireFunction.call(this);
+		}
+		_this.object[_this.propertyName].call(_this.object);
+	};
+	
+};
+GUI.extendController(GUI.FunctionController);
+GUI.NumberController = function() {
+
+	this.type = "number";
+    
+    GUI.Controller.apply(this, arguments);
+    
+    var _this = this;
+    
+    // If we simply click and release a number field, we want to highlight it.
+    // This variable keeps track of whether or not we've dragged
+    var draggedNumberField = false;
+    
+    var clickedNumberField = false;
+    
+    var y = py = 0;
+    
+    var min = arguments[3];
+    var max = arguments[4];
+    var step = arguments[5];
+    
+    if (!step) {
+    	if (min != undefined && max != undefined) {
+    		step = (max-min)*0.01;
+    	} else {
+    		step = 1;
+    	}	
+    }
+    
+    var numberField = document.createElement('input');
+    numberField.setAttribute('id', this.propertyName);
+    numberField.setAttribute('type', 'text');
+    numberField.setAttribute('value', this.getValue());
+    
+    if (step) numberField.setAttribute('step', step);
+    
+    this.domElement.appendChild(numberField);
+    
+    var slider;
+    
+    if (min != undefined && max != undefined) {
+    	slider = new GUI.Slider(this, min, max, step, this.getValue());
+    	this.domElement.appendChild(slider.domElement);
+    }
+    
+    numberField.addEventListener('blur', function(e) {
+        var val = parseFloat(this.value);
+        console.log(val);
+        if (!isNaN(val)) {
+	        _this.setValue(val);
+        }
+    }, false);
+    
+    numberField.addEventListener('mousewheel', function(e) {
+    	e.preventDefault();
+    	_this.setValue(_this.getValue() + Math.abs(e.wheelDeltaY)/e.wheelDeltaY*step);
+    	return false;
+    }, false);
+    
+    numberField.addEventListener('mousedown', function(e) {
+        py = y = e.pageY;
+		clickedNumberField = true;
+        document.addEventListener('mousemove', dragNumberField, false);
+		document.addEventListener('mouseup', mouseup, false);
+    }, false);
+
+	// Handle up arrow and down arrow
+	numberField.addEventListener('keydown', function(e) {
+		switch(e.keyCode) {
+			case 38: 	// up
+				var newVal = _this.getValue() + step;
+				_this.setValue(newVal);
+				break;
+			case 40: 	// down
+				var newVal = _this.getValue() - step;
+				_this.setValue(newVal);
+				break;
+		}
+	}, false);
+    
+    var mouseup = function(e) {
+        document.removeEventListener('mousemove', dragNumberField, false);
+        GUI.makeSelectable(_this.parent.domElement); 
+        GUI.makeSelectable(numberField);
+        if (clickedNumberField && !draggedNumberField) { 
+	        numberField.focus();
+	        numberField.select();
+        }
+        draggedNumberField = false;
+        clickedNumberField = false;
+		if (_this.finishChangeFunction != null) {
+			_this.finishChangeFunction.call(this, _this.getValue());
+		}
+		document.removeEventListener('mouseup', mouseup, false);
+	}
+
+    
+    
+    var dragNumberField = function(e) {
+    	draggedNumberField = true;
+		e.preventDefault();
+
+		// We don't want to be highlighting this field as we scroll.
+		// Or any other fields in this gui for that matter ...
+		// TODO: Make makeUselectable go through each element and child element.
+
+		GUI.makeUnselectable(_this.parent.domElement);
+		GUI.makeUnselectable(numberField);
+		
+		py = y;
+		y = e.pageY;
+		var dy = py - y;
+		var newVal = _this.getValue() + dy*step;	
+		_this.setValue(newVal);
+		return false;
+    }
+    
+    this.options = function() {
+    	_this.noSlider();
+    	_this.domElement.removeChild(numberField);
+    	return GUI.Controller.prototype.options.apply(this, arguments);
+    };
+    
+    this.noSlider = function() {
+    	if (slider) {
+    		_this.domElement.removeChild(slider.domElement);
+    	}
+    	return this;
+    };
+    
+    this.setValue = function(val) {
+    
+		val = parseFloat(val);
+		
+    	if (min != undefined && val <= min) {
+    		val = min;
+    	} else if (max != undefined && val >= max) { 
+    		val = max;
+    	}
+    	
+    	return GUI.Controller.prototype.setValue.call(this, val);
+    	
+    }
+    
+    this.updateDisplay = function() {
+        numberField.value = GUI.roundToDecimal(_this.getValue(), 4);
+        if (slider) slider.value = _this.getValue();
+	}
+};
+
+GUI.extendController(GUI.NumberController);
+GUI.StringController = function() {
+
+	this.type = "string";
+	
+	var _this = this;
+    GUI.Controller.apply(this, arguments);
+    
+    var input = document.createElement('input');
+    
+    var initialValue = this.getValue();
+    
+    input.setAttribute('value', initialValue);
+    input.setAttribute('spellcheck', 'false');
+    
+    this.domElement.addEventListener('mouseup', function() {
+    	input.focus();
+    	input.select();
+    }, false);
+    
+    // TODO: getting messed up on ctrl a
+    input.addEventListener('keyup', function(e) {
+    	if (e.keyCode == 13 && _this.finishChangeFunction != null) {
+			_this.finishChangeFunction.call(this, _this.getValue());
+		}
+        _this.setValue(input.value);
+    }, false);
+    
+    input.addEventListener('blur', function() {
+		if (_this.finishChangeFunction != null) {
+			_this.finishChangeFunction.call(this, _this.getValue());
+		}
+    }, false);
+    
+    this.updateDisplay = function() {
+    	input.value = _this.getValue();
+    }
+    
+    this.options = function() {
+    	_this.domElement.removeChild(input);
+    	return GUI.Controller.prototype.options.apply(this, arguments);
+    };
+    
+    this.domElement.appendChild(input);
+    
+};
+
+GUI.extendController(GUI.StringController);

+ 34 - 0
examples/js/gui/gui.min.js

@@ -0,0 +1,34 @@
+var GUI=function(){var a=this,b=[],d=[],e=true,j,k,h=0;a=this;var l=false,n=280,o=false,i,f,q=0,s;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat");this.domElement.style.width=n+"px";var p=document.createElement("div");p.setAttribute("class","guidat-controllers");p.addEventListener("DOMMouseScroll",function(c){var g=this.scrollTop;if(c.wheelDelta)g+=c.wheelDelta;else if(c.detail)g+=c.detail;c.preventDefault&&c.preventDefault();c.returnValue=false;p.scrollTop=
+g},false);p.style.height="0px";var r=document.createElement("a");r.setAttribute("class","guidat-toggle");r.setAttribute("href","#");r.innerHTML="Show Controls";var u=false,C=0,v=false,w,y,x,z,D=function(c){y=w;z=x;w=c.pageY;x=c.pageX;c=w-y;if(!l)if(c>0){l=true;h=i=1;r.innerHTML=f||"Hide Controls"}else return;var g=z-x;if(c>0&&h>k){var m=GUI.map(h,k,k+100,1,0);c*=m}u=true;C+=c;dragDisplacementX+=g;i+=c;n+=g;h+=c;p.style.height=i+"px";n=GUI.constrain(n,240,500);a.domElement.style.width=n+"px";A()};
+r.addEventListener("mousedown",function(c){y=w=c.pageY;z=x=c.pageX;v=true;c.preventDefault();dragDisplacementX=C=0;document.addEventListener("mousemove",D,false);return false},false);r.addEventListener("click",function(c){c.preventDefault();return false},false);document.addEventListener("mouseup",function(c){if(v&&!u){a.toggle();a.domElement.style.width=n+1+"px";setTimeout(function(){a.domElement.style.width=n+"px"},1)}if(v&&u){if(dragDisplacementX==0){a.domElement.style.width=n+1+"px";setTimeout(function(){a.domElement.style.width=
+n+"px"},1)}if(i>k){clearTimeout(s);i=q=k;t()}else if(p.children.length>=1){var g=p.children[0].offsetHeight;clearTimeout(s);q=Math.round(h/g)*g-1;if(q<=0){a.hide();i=g*2}else{i=q;t()}}}document.removeEventListener("mousemove",D,false);c.preventDefault();return v=u=false},false);this.domElement.appendChild(p);this.domElement.appendChild(r);if(GUI.autoPlace){if(GUI.autoPlaceContainer==null){GUI.autoPlaceContainer=document.createElement("div");GUI.autoPlaceContainer.setAttribute("id","guidat");document.body.appendChild(GUI.autoPlaceContainer)}GUI.autoPlaceContainer.appendChild(this.domElement)}this.autoListenIntervalTime=
+1E3/60;var E=function(){j=setInterval(function(){a.listen()},this.autoListenIntervalTime)};this.__defineSetter__("autoListen",function(c){if(e=c)d.length>0&&E();else clearInterval(j)});this.__defineGetter__("autoListen",function(){return e});this.listenTo=function(c){d.length==0&&E();d.push(c)};this.unlistenTo=function(c){for(var g=0;g<d.length;g++)d[g]==c&&d.splice(g,1);d.length<=0&&clearInterval(j)};this.listen=function(c){c=c||d;for(var g in c)c[g].updateDisplay()};this.listenAll=function(){this.listen(b)};
+this.autoListen=true;var F=function(c,g){for(var m in b)if(b[m].object==c&&b[m].propertyName==g)return true;return false},G=function(c,g){function m(){return c.apply(this,g)}m.prototype=c.prototype;return new m};this.add=function(){var c=arguments[0],g=arguments[1];F(c,g);var m=c[g];if(m==undefined)GUI.error(c+' either has no property "'+g+'", or the property is inaccessible.');else{c=typeof m;m=I[c];if(m==undefined)GUI.error('Cannot create controller for data type "'+c+'"');else{for(var H=[this],
+B=0;B<arguments.length;B++)H.push(arguments[B]);if(m=G(m,H)){p.appendChild(m.domElement);b.push(m);GUI.allControllers.push(m);if(c!="function"&&GUI.saveIndex<GUI.savedValues.length){m.setValue(GUI.savedValues[GUI.saveIndex]);GUI.saveIndex++}A();o||(i=k);return m}else GUI.error('Error creating controller for "'+g+'".')}}};var A=function(){k=0;for(var c in b)k+=b[c].domElement.offsetHeight;p.style.overflowY=k-1>i?"auto":"hidden"},I={number:GUI.NumberController,string:GUI.StringController,"boolean":GUI.BooleanController,
+"function":GUI.FunctionController};F=function(c,g){for(var m in b)if(b[m].object==c&&b[m].propertyName==g)return true;return false};G=function(c,g){function m(){return c.apply(this,g)}m.prototype=c.prototype;return new m};this.reset=function(){};this.toggle=function(){l?this.hide():this.show()};this.show=function(){r.innerHTML=f||"Hide Controls";q=i;clearTimeout(s);t();l=true};this.hide=function(){r.innerHTML=f||"Show Controls";q=0;clearTimeout(s);t();l=false};this.name=function(c){f=c;r.innerHTML=
+c};this.appearanceVars=function(){return[l,n,i,p.scrollTop]};var t=function(){h+=(q-h)*0.6;if(Math.abs(h-q)<1)h=q;else s=setTimeout(t,1E3/30);p.style.height=Math.round(h)+"px";A()};if(GUI.guiIndex<GUI.savedAppearanceVars.length){n=parseInt(GUI.savedAppearanceVars[GUI.guiIndex][1]);a.domElement.style.width=n+"px";i=parseInt(GUI.savedAppearanceVars[GUI.guiIndex][2]);o=true;if(eval(GUI.savedAppearanceVars[GUI.guiIndex][0])==true){h=i;var J=GUI.savedAppearanceVars[GUI.guiIndex][3];setTimeout(function(){p.scrollTop=
+J},0);if(GUI.scrollTop>-1)document.body.scrollTop=GUI.scrollTop;q=i;this.show()}GUI.guiIndex++}GUI.allGuis.push(this)};GUI.autoPlace=true;GUI.autoPlaceContainer=null;GUI.allControllers=[];GUI.allGuis=[];GUI.saveURL=function(){title=window.location;url=GUI.replaceGetVar("saveString",GUI.getSaveString());window.location=url};GUI.scrollTop=-1;
+GUI.load=function(a){a=a.split(",");var b=parseInt(a[0]);GUI.scrollTop=parseInt(a[1]);for(var d=0;d<b;d++){var e=a.splice(2,4);GUI.savedAppearanceVars.push(e)}GUI.savedValues=a.splice(2,a.length)};GUI.savedValues=[];GUI.savedAppearanceVars=[];
+GUI.getSaveString=function(){var a=[];a.push(GUI.allGuis.length);a.push(document.body.scrollTop);for(var b in GUI.allGuis)for(var d=GUI.allGuis[b].appearanceVars(),e=0;e<d.length;e++)a.push(d[e]);for(b in GUI.allControllers)if(GUI.allControllers[b].type!="function"){d=GUI.allControllers[b].getValue();if(GUI.allControllers[b].type=="number")d=GUI.roundToDecimal(d,4);a.push(d)}return a.join(",")};
+GUI.getVarFromURL=function(a){for(var b,d=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),e=0;e<d.length;e++){b=d[e].split("=");if(b!=undefined)if(b[0]==a)return b[1]}return null};
+GUI.replaceGetVar=function(a,b){for(var d,e=window.location.href,j=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),k=0;k<j.length;k++){d=j[k].split("=");if(d!=undefined)if(d[0]==a)return e.replace(d[1],b)}if(window.location.href.indexOf("?")!=-1)return e+"&"+a+"="+b;return e+"?"+a+"="+b};GUI.saveIndex=0;GUI.guiIndex=0;GUI.showSaveString=function(){alert(GUI.getSaveString())};
+GUI.makeUnselectable=function(a){a.onselectstart=function(){return false};a.style.MozUserSelect="none";a.style.KhtmlUserSelect="none";a.unselectable="on"};GUI.makeSelectable=function(a){a.onselectstart=function(){};a.style.MozUserSelect="auto";a.style.KhtmlUserSelect="auto";a.unselectable="off"};GUI.map=function(a,b,d,e,j){return a=e+(j-e)*((a-b)/(d-b))};GUI.constrain=function(a,b,d){if(a<b)a=b;else if(a>d)a=d;return a};
+GUI.error=function(a){typeof console.error=="function"&&console.error("[GUI ERROR] "+a)};GUI.roundToDecimal=function(a,b){var d=Math.pow(10,b);return Math.round(a*d)/d};GUI.extendController=function(a){a.prototype=new GUI.Controller;a.prototype.constructor=a};GUI.getVarFromURL("saveString")!=null&&GUI.load(GUI.getVarFromURL("saveString"));
+GUI.Slider=function(a,b,d,e,j){b=b;d=d;e=e;var k=false,h=this;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-slider-bg");this.fg=document.createElement("div");this.fg.setAttribute("class","guidat-slider-fg");this.domElement.appendChild(this.fg);this.__defineSetter__("value",function(o){this.fg.style.width=GUI.map(o,b,d,0,100)+"%"});var l=function(o){if(k){var i;i=h.domElement;var f=curtop=0;if(i.offsetParent){do{f+=i.offsetLeft;curtop+=i.offsetTop}while(i=
+i.offsetParent);i=[f,curtop]}else i=void 0;o=GUI.map(o.pageX,i[0],i[0]+h.domElement.offsetWidth,b,d);o=Math.round(o/e)*e;a.setValue(o)}};this.domElement.addEventListener("mousedown",function(o){k=true;h.domElement.setAttribute("class","guidat-slider-bg active");h.fg.setAttribute("class","guidat-slider-fg active");l(o);document.addEventListener("mouseup",n,false)},false);var n=function(){h.domElement.setAttribute("class","guidat-slider-bg");h.fg.setAttribute("class","guidat-slider-fg");k=false;a.finishChangeFunction!=
+null&&a.finishChangeFunction.call(this,a.getValue());document.removeEventListener("mouseup",n,false)};document.addEventListener("mousemove",l,false);this.value=j};
+GUI.Controller=function(){this.parent=arguments[0];this.object=arguments[1];this.propertyName=arguments[2];if(arguments.length>0)this.initialValue=this.propertyName[this.object];this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-controller "+this.type);this.propertyNameElement=document.createElement("span");this.propertyNameElement.setAttribute("class","guidat-propertyname");this.name(this.propertyName);this.domElement.appendChild(this.propertyNameElement);
+GUI.makeUnselectable(this.domElement)};GUI.Controller.prototype.changeFunction=null;GUI.Controller.prototype.finishChangeFunction=null;GUI.Controller.prototype.name=function(a){this.propertyNameElement.innerHTML=a;return this};GUI.Controller.prototype.reset=function(){this.setValue(this.initialValue);return this};GUI.Controller.prototype.listen=function(){this.parent.listenTo(this);return this};GUI.Controller.prototype.unlisten=function(){this.parent.unlistenTo(this);return this};
+GUI.Controller.prototype.setValue=function(a){this.object[this.propertyName]=a;this.changeFunction!=null&&this.changeFunction.call(this,a);this.updateDisplay();return this};GUI.Controller.prototype.getValue=function(){return this.object[this.propertyName]};GUI.Controller.prototype.updateDisplay=function(){};GUI.Controller.prototype.onChange=function(a){this.changeFunction=a;return this};GUI.Controller.prototype.onFinishChange=function(a){this.finishChangeFunction=a;return this};
+GUI.Controller.prototype.options=function(){var a=this,b=document.createElement("select");if(arguments.length==1){var d=arguments[0],e;for(e in d){var j=document.createElement("option");j.innerHTML=e;j.setAttribute("value",d[e]);b.appendChild(j)}}else for(e=0;e<arguments.length;e++){j=document.createElement("option");j.innerHTML=arguments[e];j.setAttribute("value",arguments[e]);b.appendChild(j)}b.addEventListener("change",function(){a.setValue(this.value);a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,
+a.getValue())});a.domElement.appendChild(b);return this};
+GUI.BooleanController=function(){this.type="boolean";GUI.Controller.apply(this,arguments);var a=this,b=document.createElement("input");b.setAttribute("type","checkbox");this.domElement.addEventListener("click",function(d){b.checked=!b.checked;d.preventDefault();a.setValue(b.checked)},false);b.addEventListener("mouseup",function(){b.checked=!b.checked},false);this.domElement.style.cursor="pointer";this.propertyNameElement.style.cursor="pointer";this.domElement.appendChild(b);this.updateDisplay=function(){b.checked=
+a.getValue()};this.setValue=function(d){if(typeof d!="boolean")try{d=eval(d)}catch(e){}return GUI.Controller.prototype.setValue.call(this,d)}};GUI.extendController(GUI.BooleanController);
+GUI.FunctionController=function(){this.type="function";var a=this;GUI.Controller.apply(this,arguments);this.domElement.addEventListener("click",function(){a.fire()},false);this.domElement.style.cursor="pointer";this.propertyNameElement.style.cursor="pointer";var b=null;this.onFire=function(d){b=d;return this};this.fire=function(){b!=null&&b.call(this);a.object[a.propertyName].call(a.object)}};GUI.extendController(GUI.FunctionController);
+GUI.NumberController=function(){this.type="number";GUI.Controller.apply(this,arguments);var a=this,b=false,d=false,e=py=0,j=arguments[3],k=arguments[4],h=arguments[5];h||(h=j!=undefined&&k!=undefined?(k-j)*0.01:1);var l=document.createElement("input");l.setAttribute("id",this.propertyName);l.setAttribute("type","text");l.setAttribute("value",this.getValue());h&&l.setAttribute("step",h);this.domElement.appendChild(l);var n;if(j!=undefined&&k!=undefined){n=new GUI.Slider(this,j,k,h,this.getValue());
+this.domElement.appendChild(n.domElement)}l.addEventListener("blur",function(){var f=parseFloat(this.value);console.log(f);isNaN(f)||a.setValue(f)},false);l.addEventListener("mousewheel",function(f){f.preventDefault();a.setValue(a.getValue()+Math.abs(f.wheelDeltaY)/f.wheelDeltaY*h);return false},false);l.addEventListener("mousedown",function(f){py=e=f.pageY;d=true;document.addEventListener("mousemove",i,false);document.addEventListener("mouseup",o,false)},false);l.addEventListener("keydown",function(f){switch(f.keyCode){case 38:f=
+a.getValue()+h;a.setValue(f);break;case 40:f=a.getValue()-h;a.setValue(f)}},false);var o=function(){document.removeEventListener("mousemove",i,false);GUI.makeSelectable(a.parent.domElement);GUI.makeSelectable(l);if(d&&!b){l.focus();l.select()}d=b=false;a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue());document.removeEventListener("mouseup",o,false)},i=function(f){b=true;f.preventDefault();GUI.makeUnselectable(a.parent.domElement);GUI.makeUnselectable(l);py=e;e=f.pageY;f=
+py-e;f=a.getValue()+f*h;a.setValue(f);return false};this.options=function(){a.noSlider();a.domElement.removeChild(l);return GUI.Controller.prototype.options.apply(this,arguments)};this.noSlider=function(){n&&a.domElement.removeChild(n.domElement);return this};this.setValue=function(f){f=parseFloat(f);if(j!=undefined&&f<=j)f=j;else if(k!=undefined&&f>=k)f=k;return GUI.Controller.prototype.setValue.call(this,f)};this.updateDisplay=function(){l.value=GUI.roundToDecimal(a.getValue(),4);if(n)n.value=a.getValue()}};
+GUI.extendController(GUI.NumberController);
+GUI.StringController=function(){this.type="string";var a=this;GUI.Controller.apply(this,arguments);var b=document.createElement("input"),d=this.getValue();b.setAttribute("value",d);b.setAttribute("spellcheck","false");this.domElement.addEventListener("mouseup",function(){b.focus();b.select()},false);b.addEventListener("keyup",function(e){e.keyCode==13&&a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue());a.setValue(b.value)},false);b.addEventListener("blur",function(){a.finishChangeFunction!=
+null&&a.finishChangeFunction.call(this,a.getValue())},false);this.updateDisplay=function(){b.value=a.getValue()};this.options=function(){a.domElement.removeChild(b);return GUI.Controller.prototype.options.apply(this,arguments)};this.domElement.appendChild(b)};GUI.extendController(GUI.StringController);

+ 336 - 0
examples/webgl_postprocessing_dof.html

@@ -0,0 +1,336 @@
+<!DOCTYPE HTML>
+<html lang="en">
+	<head>
+		<title>three.js - postprocessing - depth-of-field - webgl</title>
+		<meta charset="utf-8">
+		<style type="text/css">
+			body {
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+                font-family:Monospace;
+                font-size:13px;
+                text-align:center;
+                font-weight: bold;
+				text-align:center;
+			}
+
+			a {
+				color:#0078ff;
+			}
+			
+            #info {
+				color:#fff;
+                position: absolute;
+                top: 0px; width: 100%;
+                padding: 5px;
+				z-index:100;
+				
+            }			
+		</style>		
+	</head>
+	
+	<body>
+		<script type="text/javascript" src="../build/ThreeExtras.js"></script>
+		
+		<script type="text/javascript" src="js/ShaderExtras.js"></script>
+		<script type="text/javascript" src="js/Stats.js"></script>
+
+		<link href="js/gui/gui.css" media="screen" rel="stylesheet" type="text/css" />
+		<script type="text/javascript" src="js/gui/gui.min.js"></script>
+
+        <div id="info">
+			<a href="http://github.com/mrdoob/three.js" target="_blank">three.js</a> - webgl depth-of-field with bokeh example -
+			shader by <a href="http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html">Martins Upitis</a>
+		</div>
+		
+
+		<script type="text/javascript">
+
+			if ( ! THREE.Detector.webgl ) THREE.Detector.addGetWebGLMessage();
+			
+			window.onload = init;
+
+			var container, stats;
+			var camera, scene, renderer, 
+				materials = [], objects = [], 
+				singleMaterial, zmaterial = [],
+				parameters, i, j, k, h, color, x, y, z, s, n, nobjects,
+				material_depth, material_base, cubeMaterial;
+				
+			var mouseX = 0, mouseY = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+			var height = window.innerHeight - 300;
+			
+			var postprocessing = {
+				
+				enabled  : true
+				
+			};
+			
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.Camera( 70, window.innerWidth / height, 1, 3000 );
+				camera.position.z = 200;
+
+				scene = new THREE.Scene();
+				
+				/*
+				var light = new THREE.DirectionalLight( 0xffffff );
+				light.position.set( 0, 0.5, 1 );
+				light.position.normalize();
+				scene.addLight( light );
+				*/
+				
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, height );
+				container.appendChild( renderer.domElement );
+				
+				material_depth = new THREE.MeshDepthMaterial();
+				
+				var path = "textures/cube/SwedishRoyalCastle/";
+				var format = '.jpg';
+				var urls = [
+						path + 'px' + format, path + 'nx' + format,
+						path + 'py' + format, path + 'ny' + format,
+						path + 'pz' + format, path + 'nz' + format
+					];
+
+				var images = ImageUtils.loadArray( urls );
+
+				var reflectionCube = new THREE.Texture( images );
+
+				parameters = { color: 0xff1100, env_map: reflectionCube, combine: THREE.MixOperationa, reflectivity: 0.3, shading: THREE.FlatShading };
+				//parameters = { color: 0xff1100, shading: THREE.FlatShading };
+				cubeMaterial = new THREE.MeshBasicMaterial( parameters );
+				
+				singleMaterial = false;
+				
+				if( singleMaterial ) zmaterial = [ cubeMaterial ];
+				
+				//var geo = new Cube( 1, 1, 1 );
+				var geo = new Sphere( 1, 20, 10 );	
+				//var geo = new Icosahedron( 2 );
+				
+				material_base = cubeMaterial;
+				
+				renderer.initMaterial( material_base, scene.lights, scene.fog );
+				
+				var xgrid = 14,
+					ygrid = 9,
+					zgrid = 14;
+				
+				nobjects = xgrid * ygrid * zgrid;
+				
+				c = 0;
+				
+				//var s = 0.25;
+				var s = 60;
+				
+				for ( i = 0; i < xgrid; i++ )
+				for ( j = 0; j < ygrid; j++ ) 
+				for ( k = 0; k < zgrid; k++ ) {					
+
+					materials[c] = new THREE.MeshBasicMaterial( parameters );
+					materials[c].program = material_base.program;
+					materials[c].uniforms = Uniforms.clone( THREE.ShaderLib[ 'basic' ].uniforms );
+
+					if ( singleMaterial )
+						mesh = new THREE.Mesh( geo, zmaterial );
+					else
+						mesh = new THREE.Mesh( geo, materials[c] );
+						
+					x = 200 * ( i - xgrid/2 );
+					y = 200 * ( j - ygrid/2 );
+					z = 200 * ( k - zgrid/2 );
+					
+					mesh.position.set( x, y, z );
+					mesh.scale.set( s, s, s );
+					
+					mesh.matrixAutoUpdate = false;
+					mesh.updateMatrix();
+					
+					scene.addObject( mesh );
+					objects.push( mesh );
+					
+					c++;
+					
+				}
+
+				scene.matrixAutoUpdate = false;				
+
+				initPostprocessing();
+				
+				renderer.autoClear = false;
+				
+				renderer.domElement.style.position = 'absolute';
+				renderer.domElement.style.top = "150px";
+				
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				//container.appendChild( stats.domElement );
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
+				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
+				
+				var effectController  = {
+					
+					focus: 		1.0,
+					aperture:	0.025,
+					maxblur:	1.0
+				
+				};
+				
+				var matChanger = function( ) { 
+				
+					postprocessing.bokeh_uniforms["focus"].value = effectController.focus;
+					postprocessing.bokeh_uniforms["aperture"].value = effectController.aperture;
+					postprocessing.bokeh_uniforms["maxblur"].value = effectController.maxblur;
+
+				};
+				
+				var gui = new GUI();				
+				gui.add( effectController, "focus", 0.0, 3.0, 0.025 ).onChange( matChanger );
+				gui.add( effectController, "aperture", 0.001, 0.2, 0.001 ).onChange( matChanger );
+				gui.add( effectController, "maxblur", 0.0, 3.0, 0.025 ).onChange( matChanger );
+				
+				loop();
+				
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = event.clientX - windowHalfX;
+				mouseY = event.clientY - windowHalfY;
+				
+			}
+
+			function onDocumentTouchStart( event ) {
+
+				if ( event.touches.length == 1 ) {
+
+					event.preventDefault();
+
+					mouseX = event.touches[ 0 ].pageX - windowHalfX;
+					mouseY = event.touches[ 0 ].pageY - windowHalfY;
+					
+				}
+			}
+
+			function onDocumentTouchMove( event ) {
+
+				if ( event.touches.length == 1 ) {
+
+					event.preventDefault();
+
+					mouseX = event.touches[ 0 ].pageX - windowHalfX;
+					mouseY = event.touches[ 0 ].pageY - windowHalfY;
+					
+				}
+				
+			}
+			
+			function initPostprocessing() {
+				
+				postprocessing.scene = new THREE.Scene();		
+				
+				postprocessing.camera = new THREE.Camera();
+				postprocessing.camera.projectionMatrix = THREE.Matrix4.makeOrtho( window.innerWidth / - 2, window.innerWidth / 2,  window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 );
+				postprocessing.camera.position.z = 100;
+				
+				var pars = { min_filter: THREE.LinearFilter, mag_filter: THREE.LinearFilter };
+				postprocessing.rtTextureDepth = new THREE.RenderTarget( window.innerWidth, height, pars );
+                postprocessing.rtTextureColor = new THREE.RenderTarget( window.innerWidth, height, pars );
+				
+				var bokeh_shader = ShaderExtras["bokeh"];
+				
+				postprocessing.bokeh_uniforms = Uniforms.clone( bokeh_shader.uniforms );
+
+				postprocessing.bokeh_uniforms["tColor"].texture = postprocessing.rtTextureColor;
+				postprocessing.bokeh_uniforms["tDepth"].texture = postprocessing.rtTextureDepth;
+				postprocessing.bokeh_uniforms["focus"].value = 1.1;
+				postprocessing.bokeh_uniforms["aspect"].value = window.innerWidth / height;
+
+                postprocessing.materialBokeh = new THREE.MeshShaderMaterial( {
+
+                    uniforms: postprocessing.bokeh_uniforms,
+                    vertex_shader: bokeh_shader.vertex_shader,
+                    fragment_shader: bokeh_shader.fragment_shader
+
+                } );
+
+				postprocessing.quad = new THREE.Mesh( new Plane( window.innerWidth, window.innerHeight ), postprocessing.materialBokeh );
+				postprocessing.quad.position.z = -500;
+				postprocessing.scene.addObject( postprocessing.quad );
+
+			}			
+
+			function loop() {
+
+				requestAnimationFrame( loop, renderer.domElement );
+
+				var time = new Date().getTime() * 0.00005;
+				
+				camera.position.x += ( mouseX - camera.position.x ) * 0.036;
+				camera.position.y += ( - (mouseY) - camera.position.y ) * 0.036;
+
+				for( i = 0; i < nobjects; i++ ) {
+					
+					h = ( 360 * ( i/nobjects + time ) % 360 ) / 360;
+					//materials[i].color.setHSV( h, 0.5 + 0.5 * ( i % 20 / 20 ), 1 );				
+					materials[i].color.setHSV( h, 1, 1 );
+					
+				}
+				
+
+				if ( postprocessing.enabled ) {
+				
+					renderer.clear();
+					
+					// Render scene into texture
+					
+					if ( singleMaterial )
+						zmaterial[0] = cubeMaterial;
+					else
+						for( i = 0; i < nobjects; i++ ) objects[i].materials = [ materials[i] ];
+					
+					
+					renderer.render( scene, camera, postprocessing.rtTextureColor );
+					
+					// Render depth into texture
+					
+					if ( singleMaterial )
+						zmaterial[0] = material_depth;
+					else
+						for( i = 0; i < nobjects; i++ ) objects[i].materials = [ material_depth ];
+					
+					renderer.render( scene, camera, postprocessing.rtTextureDepth );
+
+					// Render bokeh composite					
+					
+					renderer.render( postprocessing.scene, postprocessing.camera );
+					
+					
+				} else {
+					
+					renderer.clear();
+					renderer.render( scene, camera );
+					
+				}
+
+				stats.update();
+
+			}
+
+
+		</script>
+	</body>
+</html>