Prechádzať zdrojové kódy

Abelnation RectAreaLight with ltc approximation (#10041)

* sketch of spherical distributions with linearly transformed cosine

* adding prelim example sketches for cosine dists

* makeSkew method added

* add polygon light sandbox example to repo for reference

* formatting

* notes files

* adding skeleton files for area point light

* debug directional light integrated into area light example page

* initial AreaLightHelper functional in example

* makeShape functions for Polygon and add as choices to example

* annotating TODO's with name. partial work on AreaLight shader components

* adding TODOs and placeholders for all places where AreaLight code needs to be added

* fix typo

* RectAreaLight shading works in preliminary fashion

* updates to example

* Preliminary RectAreaLight implementation (no shadows, no distance/decay)

* Integrate RectAreaLight with MeshStandardMaterial

* moving rectarealight brdf data to an example file.  rest of implementation left in place

* TubeBufferGeometry: Removed invisible char (#9943)

* Remove reference to THREE in IcosahedronGeometry.js (#9945)

Fix broken references to THREE namespace in geometries.

* Updated builds.

* Updated package.json.

* Resolved some issues from the first merge

* Added demo from https://github.com/mrdoob/three.js/pull/9234
Noticed that the code from @abelnation is not his latest...
more merging to come!!!

* Fixed issues from merge

* Fixed issues from merge...
webgl_lights_arealight improved
RectAreaLightHelper update bugs fixed

* Removing built js files that cause conflicts

* Use FileLoader.setMimeType() from MMDLoader (#9990)

* MMDPhysics improvement (#9989)

* MMDPhysics improvement

* Add property defined check

* Shoe physic bodies in mmd example by default.

* Updated builds.

* Improved documentation for constants / Materials (#9993)

* Improved documentation for Materials / Material (#9994)

* Added defaults to docs / perspectiveCamera (#10007)

* added constanst / animation (#10005)

* Fixed error in <head> for Docs / AnimationAction, AnimationClip, and AnimationMixer (#10004)

* Added default values for zoom, near and far properties of docs / orthographic camera (#10006)

* Improved documentation for Constants / Textures (#10001)

* Improved documentation for Materials / Material

* Moved Texture Combine Operations to constanst / materials

* updated Basic, Lambert and Phong .combine property to point to Material constant page

* Improved documentation for constants / textures

* Added Encoding constants to Textures constants page

* Ccdik solver optimization (#10010)

* Optimize CCDIKSolver

* Remove lines I should have not commit

* Remove lines I should have not committed

* added missing toJSON method (#10020)

* Added missing toJson method (#10019)

* added missing toJSON method (#10018)

* created documentation for VideoTexture (#10016)

* Created doc for CanvasTexture (#10015)

* Add CCDIKHelper (#9996)

* Add CCDIKHelper

* Clean up MMDPhysics.js

* Fix typo

* Update OBJLoader.html (#10009)

Spelling correction.

* Fix typo in comment (#10021)

* Improved documentation for docs / Texture (#10012)

* Improved documentation for docs / Texture

* Removed duplicate needsUpate

* Improved docs for Clock (#10008)

* Created new document page Constants / Renderer (#10002)

* Created constants / renderer

* renamed Renderer.html to WebGLRenderer.html

* Improved documentation for CompressedTexture (#10014)

* AudioContext: Added getContext() and setContext().

* Updated builds.

* Simplified AudioContext.

* Updated builds.

* BufferAttribute.onUpload() clean up.

* Renamed docs /constants / WebGLRenderer to Renderer (#10028)

* fixes #10026 (#10027)

* capture bufferAttribute.array properties at first upload (#9972)

* save typed array info in attribute properties

* use saved attribute properties

* remove unused variable

* Discard attribute typed arrays for buffered geometries that are not modified after initial rendering (#9512)

* add setDiscardBuffer method to BufferGeometry

* added discard support to BufferAttribute

* add mechanism for discard of BufferAttribute TypedArrays

* use more elegant method for creating dummy typed array.

* fix typo

* Update BufferGeometry.js

fix brain fade

* rework to use callbacks (phase 1)

* rework part 2

* remove build file

* support setting onUploadCallback from Geometry

* remove repeated calculation from renderer

* remove now redundant getter

* remove geoemtry interface

* document discard mechanism.

* merge fixes

* restore return.this

* drop unneeded call()

* rename discard() method to disposeArray()

* Improved documentation for WebGLRenderer (#10030)

* added missing methods

* Finished add methods

* redid changed to WebGLRenderer.html

* removed unused texture.sourceFile property (#10024)

* glTFLoader: Removed hack. See #10024.

* Updated builds.

* add link to project wiki in README.md (#9987)

* Added deprecated msg/fixed link (#10025)

* Created documentation for DepthTexture (#10017)

* Created documentation for DepthTexture

* pulled upstream

* added missing comma to docs/list.js

* Deprecated UniformsUtils. See #8016.

* Updated builds.

* MeshBasicMaterial: Add support for lightMap (#9975)

* Updated builds.
Samuel Sylvester 8 rokov pred
rodič
commit
346f38c566
69 zmenil súbory, kde vykonal 7244 pridanie a 727 odobranie
  1. 313 313
      build/three.js
  2. 313 313
      build/three.modules.js
  3. 3 0
      examples/files.js
  4. 21 0
      examples/js/lights/RectAreaLightUniformsLib.js
  5. 12 0
      examples/polygon_light_example/NOTES.md
  6. 28 0
      examples/polygon_light_example/css/ace_theme_tweaks.css
  7. 25 0
      examples/polygon_light_example/css/jquery.ui.resizable.css
  8. 16 0
      examples/polygon_light_example/css/plugin.css
  9. 66 0
      examples/polygon_light_example/css/sandbox.css
  10. 35 0
      examples/polygon_light_example/css/super_picker.css
  11. 193 0
      examples/polygon_light_example/css/super_slider.css
  12. 836 0
      examples/polygon_light_example/index.html
  13. 0 0
      examples/polygon_light_example/js/ace/ace.js
  14. 0 0
      examples/polygon_light_example/js/ace/mode-glsl.js
  15. 0 0
      examples/polygon_light_example/js/ace/theme-clouds_midnight.js
  16. 457 0
      examples/polygon_light_example/js/color_picker.js
  17. 15 0
      examples/polygon_light_example/js/jquery-1.6.1.min.js
  18. 98 0
      examples/polygon_light_example/js/jquery-ui-1.8.13.custom.min.js
  19. 11 0
      examples/polygon_light_example/js/jquery.mousewheel.min.js
  20. 0 0
      examples/polygon_light_example/js/ltc_tables.js
  21. 134 0
      examples/polygon_light_example/js/macton/cameracontroller.js
  22. 663 0
      examples/polygon_light_example/js/macton/macton-gl-utils.js
  23. 142 0
      examples/polygon_light_example/js/macton/macton-utils.js
  24. 373 0
      examples/polygon_light_example/js/macton/matrix4x4.js
  25. 164 0
      examples/polygon_light_example/js/macton/webgl-utils.js
  26. 163 0
      examples/polygon_light_example/js/super_picker.js
  27. 279 0
      examples/polygon_light_example/js/super_slider.js
  28. 541 0
      examples/polygon_light_example/shaders/ltc/ltc.fs
  29. 6 0
      examples/polygon_light_example/shaders/ltc/ltc.vs
  30. 99 0
      examples/polygon_light_example/shaders/ltc/ltc_blit.fs
  31. 6 0
      examples/polygon_light_example/shaders/ltc/ltc_blit.vs
  32. 383 0
      examples/webgl_lights_arealight.html
  33. 397 0
      examples/webgl_lights_rectarealight.html
  34. 238 0
      examples/webgl_math_spherical_distribution.html
  35. 1 1
      package.json
  36. 2 0
      src/Three.js
  37. 109 0
      src/extras/helpers/RectAreaLightHelper.js
  38. 50 0
      src/lights/RectAreaLight.js
  39. 18 0
      src/lights/RectAreaLightShadow.js
  40. 15 0
      src/math/Matrix4.js
  41. 180 0
      src/math/Polygon.js
  42. 19 0
      src/math/Spherical.js
  43. 20 0
      src/math/distributions/CosineSphericalDistribution.js
  44. 92 0
      src/math/distributions/LinearlyTransformedSphericalDistribution.js
  45. 50 0
      src/math/distributions/SphericalDistribution.js
  46. 130 90
      src/renderers/WebGLRenderer.js
  47. 295 0
      src/renderers/shaders/ShaderChunk/bsdfs.glsl
  48. 18 1
      src/renderers/shaders/ShaderChunk/common.glsl
  49. 10 0
      src/renderers/shaders/ShaderChunk/lights_lambert_vertex.glsl
  50. 19 1
      src/renderers/shaders/ShaderChunk/lights_pars.glsl
  51. 27 0
      src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl
  52. 24 0
      src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl
  53. 13 0
      src/renderers/shaders/ShaderChunk/lights_template.glsl
  54. 4 0
      src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl
  55. 6 0
      src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl
  56. 6 0
      src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl
  57. 6 0
      src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl
  58. 18 3
      src/renderers/shaders/UniformsLib.js
  59. 13 1
      src/renderers/webgl/WebGLLights.js
  60. 3 2
      src/renderers/webgl/WebGLProgram.js
  61. 2 1
      src/renderers/webgl/WebGLPrograms.js
  62. 7 0
      src/renderers/webgl/WebGLShadowMap.js
  63. 4 0
      src/textures/Texture.js
  64. 38 0
      test/unit/lights/RectAreaLight.tests.js
  65. 2 0
      utils/converters/fbx/convert_to_threejs.py
  66. 3 0
      utils/exporters/blender/addons/io_three/constants.py
  67. 4 0
      utils/exporters/blender/addons/io_three/exporter/api/object.py
  68. 4 0
      utils/exporters/blender/addons/io_three/exporter/object.py
  69. 2 1
      utils/exporters/blender/tests/scripts/js/review.js

+ 313 - 313
build/three.js

@@ -39369,320 +39369,320 @@
 
 	};
 
-	/**
-	 * @author alteredq / http://alteredqualia.com/
-	 */
-
+	/**
+	 * @author alteredq / http://alteredqualia.com/
+	 */
+
 	function MorphBlendMesh( geometry, material ) {
-
-		Mesh.call( this, geometry, material );
-
-		this.animationsMap = {};
-		this.animationsList = [];
-
-		// prepare default animation
-		// (all frames played together in 1 second)
-
-		var numFrames = this.geometry.morphTargets.length;
-
-		var name = "__default";
-
-		var startFrame = 0;
-		var endFrame = numFrames - 1;
-
-		var fps = numFrames / 1;
-
-		this.createAnimation( name, startFrame, endFrame, fps );
-		this.setAnimationWeight( name, 1 );
-
-	}
-
-	MorphBlendMesh.prototype = Object.create( Mesh.prototype );
-	MorphBlendMesh.prototype.constructor = MorphBlendMesh;
-
-	MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
-
-		var animation = {
-
-			start: start,
-			end: end,
-
-			length: end - start + 1,
-
-			fps: fps,
-			duration: ( end - start ) / fps,
-
-			lastFrame: 0,
-			currentFrame: 0,
-
-			active: false,
-
-			time: 0,
-			direction: 1,
-			weight: 1,
-
-			directionBackwards: false,
-			mirroredLoop: false
-
-		};
-
-		this.animationsMap[ name ] = animation;
-		this.animationsList.push( animation );
-
-	};
-
-	MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
-
-		var pattern = /([a-z]+)_?(\d+)/i;
-
-		var firstAnimation, frameRanges = {};
-
-		var geometry = this.geometry;
-
-		for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
-
-			var morph = geometry.morphTargets[ i ];
-			var chunks = morph.name.match( pattern );
-
-			if ( chunks && chunks.length > 1 ) {
-
-				var name = chunks[ 1 ];
-
-				if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity };
-
-				var range = frameRanges[ name ];
-
-				if ( i < range.start ) range.start = i;
-				if ( i > range.end ) range.end = i;
-
-				if ( ! firstAnimation ) firstAnimation = name;
-
-			}
-
-		}
-
-		for ( var name in frameRanges ) {
-
-			var range = frameRanges[ name ];
-			this.createAnimation( name, range.start, range.end, fps );
-
-		}
-
-		this.firstAnimation = firstAnimation;
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.direction = 1;
-			animation.directionBackwards = false;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.direction = - 1;
-			animation.directionBackwards = true;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.fps = fps;
-			animation.duration = ( animation.end - animation.start ) / animation.fps;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.duration = duration;
-			animation.fps = ( animation.end - animation.start ) / animation.duration;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.weight = weight;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.time = time;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
-
-		var time = 0;
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			time = animation.time;
-
-		}
-
-		return time;
-
-	};
-
-	MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
-
-		var duration = - 1;
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			duration = animation.duration;
-
-		}
-
-		return duration;
-
-	};
-
-	MorphBlendMesh.prototype.playAnimation = function ( name ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.time = 0;
-			animation.active = true;
-
-		} else {
-
-			console.warn( "THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()" );
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.stopAnimation = function ( name ) {
-
-		var animation = this.animationsMap[ name ];
-
-		if ( animation ) {
-
-			animation.active = false;
-
-		}
-
-	};
-
-	MorphBlendMesh.prototype.update = function ( delta ) {
-
-		for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
-
-			var animation = this.animationsList[ i ];
-
-			if ( ! animation.active ) continue;
-
-			var frameTime = animation.duration / animation.length;
-
-			animation.time += animation.direction * delta;
-
-			if ( animation.mirroredLoop ) {
-
-				if ( animation.time > animation.duration || animation.time < 0 ) {
-
-					animation.direction *= - 1;
-
-					if ( animation.time > animation.duration ) {
-
-						animation.time = animation.duration;
-						animation.directionBackwards = true;
-
-					}
-
-					if ( animation.time < 0 ) {
-
-						animation.time = 0;
-						animation.directionBackwards = false;
-
-					}
-
-				}
-
-			} else {
-
-				animation.time = animation.time % animation.duration;
-
-				if ( animation.time < 0 ) animation.time += animation.duration;
-
-			}
-
-			var keyframe = animation.start + _Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
-			var weight = animation.weight;
-
-			if ( keyframe !== animation.currentFrame ) {
-
-				this.morphTargetInfluences[ animation.lastFrame ] = 0;
-				this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
-
-				this.morphTargetInfluences[ keyframe ] = 0;
-
-				animation.lastFrame = animation.currentFrame;
-				animation.currentFrame = keyframe;
-
-			}
-
-			var mix = ( animation.time % frameTime ) / frameTime;
-
-			if ( animation.directionBackwards ) mix = 1 - mix;
-
-			if ( animation.currentFrame !== animation.lastFrame ) {
-
-				this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
-				this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
-
-			} else {
-
-				this.morphTargetInfluences[ animation.currentFrame ] = weight;
-
-			}
-
-		}
-
+
+		Mesh.call( this, geometry, material );
+
+		this.animationsMap = {};
+		this.animationsList = [];
+
+		// prepare default animation
+		// (all frames played together in 1 second)
+
+		var numFrames = this.geometry.morphTargets.length;
+
+		var name = "__default";
+
+		var startFrame = 0;
+		var endFrame = numFrames - 1;
+
+		var fps = numFrames / 1;
+
+		this.createAnimation( name, startFrame, endFrame, fps );
+		this.setAnimationWeight( name, 1 );
+
+	}
+
+	MorphBlendMesh.prototype = Object.create( Mesh.prototype );
+	MorphBlendMesh.prototype.constructor = MorphBlendMesh;
+
+	MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
+
+		var animation = {
+
+			start: start,
+			end: end,
+
+			length: end - start + 1,
+
+			fps: fps,
+			duration: ( end - start ) / fps,
+
+			lastFrame: 0,
+			currentFrame: 0,
+
+			active: false,
+
+			time: 0,
+			direction: 1,
+			weight: 1,
+
+			directionBackwards: false,
+			mirroredLoop: false
+
+		};
+
+		this.animationsMap[ name ] = animation;
+		this.animationsList.push( animation );
+
+	};
+
+	MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
+
+		var pattern = /([a-z]+)_?(\d+)/i;
+
+		var firstAnimation, frameRanges = {};
+
+		var geometry = this.geometry;
+
+		for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+			var morph = geometry.morphTargets[ i ];
+			var chunks = morph.name.match( pattern );
+
+			if ( chunks && chunks.length > 1 ) {
+
+				var name = chunks[ 1 ];
+
+				if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity };
+
+				var range = frameRanges[ name ];
+
+				if ( i < range.start ) range.start = i;
+				if ( i > range.end ) range.end = i;
+
+				if ( ! firstAnimation ) firstAnimation = name;
+
+			}
+
+		}
+
+		for ( var name in frameRanges ) {
+
+			var range = frameRanges[ name ];
+			this.createAnimation( name, range.start, range.end, fps );
+
+		}
+
+		this.firstAnimation = firstAnimation;
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.direction = 1;
+			animation.directionBackwards = false;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.direction = - 1;
+			animation.directionBackwards = true;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.fps = fps;
+			animation.duration = ( animation.end - animation.start ) / animation.fps;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.duration = duration;
+			animation.fps = ( animation.end - animation.start ) / animation.duration;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.weight = weight;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.time = time;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
+
+		var time = 0;
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			time = animation.time;
+
+		}
+
+		return time;
+
+	};
+
+	MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
+
+		var duration = - 1;
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			duration = animation.duration;
+
+		}
+
+		return duration;
+
+	};
+
+	MorphBlendMesh.prototype.playAnimation = function ( name ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.time = 0;
+			animation.active = true;
+
+		} else {
+
+			console.warn( "THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()" );
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.stopAnimation = function ( name ) {
+
+		var animation = this.animationsMap[ name ];
+
+		if ( animation ) {
+
+			animation.active = false;
+
+		}
+
+	};
+
+	MorphBlendMesh.prototype.update = function ( delta ) {
+
+		for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
+
+			var animation = this.animationsList[ i ];
+
+			if ( ! animation.active ) continue;
+
+			var frameTime = animation.duration / animation.length;
+
+			animation.time += animation.direction * delta;
+
+			if ( animation.mirroredLoop ) {
+
+				if ( animation.time > animation.duration || animation.time < 0 ) {
+
+					animation.direction *= - 1;
+
+					if ( animation.time > animation.duration ) {
+
+						animation.time = animation.duration;
+						animation.directionBackwards = true;
+
+					}
+
+					if ( animation.time < 0 ) {
+
+						animation.time = 0;
+						animation.directionBackwards = false;
+
+					}
+
+				}
+
+			} else {
+
+				animation.time = animation.time % animation.duration;
+
+				if ( animation.time < 0 ) animation.time += animation.duration;
+
+			}
+
+			var keyframe = animation.start + _Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
+			var weight = animation.weight;
+
+			if ( keyframe !== animation.currentFrame ) {
+
+				this.morphTargetInfluences[ animation.lastFrame ] = 0;
+				this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
+
+				this.morphTargetInfluences[ keyframe ] = 0;
+
+				animation.lastFrame = animation.currentFrame;
+				animation.currentFrame = keyframe;
+
+			}
+
+			var mix = ( animation.time % frameTime ) / frameTime;
+
+			if ( animation.directionBackwards ) mix = 1 - mix;
+
+			if ( animation.currentFrame !== animation.lastFrame ) {
+
+				this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
+				this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
+
+			} else {
+
+				this.morphTargetInfluences[ animation.currentFrame ] = weight;
+
+			}
+
+		}
+
 	};
 
 	/**

+ 313 - 313
build/three.modules.js

@@ -39363,320 +39363,320 @@ Spherical.prototype = {
 
 };
 
-/**
- * @author alteredq / http://alteredqualia.com/
- */
-
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
 function MorphBlendMesh( geometry, material ) {
-
-	Mesh.call( this, geometry, material );
-
-	this.animationsMap = {};
-	this.animationsList = [];
-
-	// prepare default animation
-	// (all frames played together in 1 second)
-
-	var numFrames = this.geometry.morphTargets.length;
-
-	var name = "__default";
-
-	var startFrame = 0;
-	var endFrame = numFrames - 1;
-
-	var fps = numFrames / 1;
-
-	this.createAnimation( name, startFrame, endFrame, fps );
-	this.setAnimationWeight( name, 1 );
-
-}
-
-MorphBlendMesh.prototype = Object.create( Mesh.prototype );
-MorphBlendMesh.prototype.constructor = MorphBlendMesh;
-
-MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
-
-	var animation = {
-
-		start: start,
-		end: end,
-
-		length: end - start + 1,
-
-		fps: fps,
-		duration: ( end - start ) / fps,
-
-		lastFrame: 0,
-		currentFrame: 0,
-
-		active: false,
-
-		time: 0,
-		direction: 1,
-		weight: 1,
-
-		directionBackwards: false,
-		mirroredLoop: false
-
-	};
-
-	this.animationsMap[ name ] = animation;
-	this.animationsList.push( animation );
-
-};
-
-MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
-
-	var pattern = /([a-z]+)_?(\d+)/i;
-
-	var firstAnimation, frameRanges = {};
-
-	var geometry = this.geometry;
-
-	for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
-
-		var morph = geometry.morphTargets[ i ];
-		var chunks = morph.name.match( pattern );
-
-		if ( chunks && chunks.length > 1 ) {
-
-			var name = chunks[ 1 ];
-
-			if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity };
-
-			var range = frameRanges[ name ];
-
-			if ( i < range.start ) range.start = i;
-			if ( i > range.end ) range.end = i;
-
-			if ( ! firstAnimation ) firstAnimation = name;
-
-		}
-
-	}
-
-	for ( var name in frameRanges ) {
-
-		var range = frameRanges[ name ];
-		this.createAnimation( name, range.start, range.end, fps );
-
-	}
-
-	this.firstAnimation = firstAnimation;
-
-};
-
-MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.direction = 1;
-		animation.directionBackwards = false;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.direction = - 1;
-		animation.directionBackwards = true;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.fps = fps;
-		animation.duration = ( animation.end - animation.start ) / animation.fps;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.duration = duration;
-		animation.fps = ( animation.end - animation.start ) / animation.duration;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.weight = weight;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.time = time;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
-
-	var time = 0;
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		time = animation.time;
-
-	}
-
-	return time;
-
-};
-
-MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
-
-	var duration = - 1;
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		duration = animation.duration;
-
-	}
-
-	return duration;
-
-};
-
-MorphBlendMesh.prototype.playAnimation = function ( name ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.time = 0;
-		animation.active = true;
-
-	} else {
-
-		console.warn( "THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()" );
-
-	}
-
-};
-
-MorphBlendMesh.prototype.stopAnimation = function ( name ) {
-
-	var animation = this.animationsMap[ name ];
-
-	if ( animation ) {
-
-		animation.active = false;
-
-	}
-
-};
-
-MorphBlendMesh.prototype.update = function ( delta ) {
-
-	for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
-
-		var animation = this.animationsList[ i ];
-
-		if ( ! animation.active ) continue;
-
-		var frameTime = animation.duration / animation.length;
-
-		animation.time += animation.direction * delta;
-
-		if ( animation.mirroredLoop ) {
-
-			if ( animation.time > animation.duration || animation.time < 0 ) {
-
-				animation.direction *= - 1;
-
-				if ( animation.time > animation.duration ) {
-
-					animation.time = animation.duration;
-					animation.directionBackwards = true;
-
-				}
-
-				if ( animation.time < 0 ) {
-
-					animation.time = 0;
-					animation.directionBackwards = false;
-
-				}
-
-			}
-
-		} else {
-
-			animation.time = animation.time % animation.duration;
-
-			if ( animation.time < 0 ) animation.time += animation.duration;
-
-		}
-
-		var keyframe = animation.start + _Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
-		var weight = animation.weight;
-
-		if ( keyframe !== animation.currentFrame ) {
-
-			this.morphTargetInfluences[ animation.lastFrame ] = 0;
-			this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
-
-			this.morphTargetInfluences[ keyframe ] = 0;
-
-			animation.lastFrame = animation.currentFrame;
-			animation.currentFrame = keyframe;
-
-		}
-
-		var mix = ( animation.time % frameTime ) / frameTime;
-
-		if ( animation.directionBackwards ) mix = 1 - mix;
-
-		if ( animation.currentFrame !== animation.lastFrame ) {
-
-			this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
-			this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
-
-		} else {
-
-			this.morphTargetInfluences[ animation.currentFrame ] = weight;
-
-		}
-
-	}
-
+
+	Mesh.call( this, geometry, material );
+
+	this.animationsMap = {};
+	this.animationsList = [];
+
+	// prepare default animation
+	// (all frames played together in 1 second)
+
+	var numFrames = this.geometry.morphTargets.length;
+
+	var name = "__default";
+
+	var startFrame = 0;
+	var endFrame = numFrames - 1;
+
+	var fps = numFrames / 1;
+
+	this.createAnimation( name, startFrame, endFrame, fps );
+	this.setAnimationWeight( name, 1 );
+
+}
+
+MorphBlendMesh.prototype = Object.create( Mesh.prototype );
+MorphBlendMesh.prototype.constructor = MorphBlendMesh;
+
+MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
+
+	var animation = {
+
+		start: start,
+		end: end,
+
+		length: end - start + 1,
+
+		fps: fps,
+		duration: ( end - start ) / fps,
+
+		lastFrame: 0,
+		currentFrame: 0,
+
+		active: false,
+
+		time: 0,
+		direction: 1,
+		weight: 1,
+
+		directionBackwards: false,
+		mirroredLoop: false
+
+	};
+
+	this.animationsMap[ name ] = animation;
+	this.animationsList.push( animation );
+
+};
+
+MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
+
+	var pattern = /([a-z]+)_?(\d+)/i;
+
+	var firstAnimation, frameRanges = {};
+
+	var geometry = this.geometry;
+
+	for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+		var morph = geometry.morphTargets[ i ];
+		var chunks = morph.name.match( pattern );
+
+		if ( chunks && chunks.length > 1 ) {
+
+			var name = chunks[ 1 ];
+
+			if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity };
+
+			var range = frameRanges[ name ];
+
+			if ( i < range.start ) range.start = i;
+			if ( i > range.end ) range.end = i;
+
+			if ( ! firstAnimation ) firstAnimation = name;
+
+		}
+
+	}
+
+	for ( var name in frameRanges ) {
+
+		var range = frameRanges[ name ];
+		this.createAnimation( name, range.start, range.end, fps );
+
+	}
+
+	this.firstAnimation = firstAnimation;
+
+};
+
+MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.direction = 1;
+		animation.directionBackwards = false;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.direction = - 1;
+		animation.directionBackwards = true;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.fps = fps;
+		animation.duration = ( animation.end - animation.start ) / animation.fps;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.duration = duration;
+		animation.fps = ( animation.end - animation.start ) / animation.duration;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.weight = weight;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.time = time;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
+
+	var time = 0;
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		time = animation.time;
+
+	}
+
+	return time;
+
+};
+
+MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
+
+	var duration = - 1;
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		duration = animation.duration;
+
+	}
+
+	return duration;
+
+};
+
+MorphBlendMesh.prototype.playAnimation = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.time = 0;
+		animation.active = true;
+
+	} else {
+
+		console.warn( "THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()" );
+
+	}
+
+};
+
+MorphBlendMesh.prototype.stopAnimation = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.active = false;
+
+	}
+
+};
+
+MorphBlendMesh.prototype.update = function ( delta ) {
+
+	for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
+
+		var animation = this.animationsList[ i ];
+
+		if ( ! animation.active ) continue;
+
+		var frameTime = animation.duration / animation.length;
+
+		animation.time += animation.direction * delta;
+
+		if ( animation.mirroredLoop ) {
+
+			if ( animation.time > animation.duration || animation.time < 0 ) {
+
+				animation.direction *= - 1;
+
+				if ( animation.time > animation.duration ) {
+
+					animation.time = animation.duration;
+					animation.directionBackwards = true;
+
+				}
+
+				if ( animation.time < 0 ) {
+
+					animation.time = 0;
+					animation.directionBackwards = false;
+
+				}
+
+			}
+
+		} else {
+
+			animation.time = animation.time % animation.duration;
+
+			if ( animation.time < 0 ) animation.time += animation.duration;
+
+		}
+
+		var keyframe = animation.start + _Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
+		var weight = animation.weight;
+
+		if ( keyframe !== animation.currentFrame ) {
+
+			this.morphTargetInfluences[ animation.lastFrame ] = 0;
+			this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
+
+			this.morphTargetInfluences[ keyframe ] = 0;
+
+			animation.lastFrame = animation.currentFrame;
+			animation.currentFrame = keyframe;
+
+		}
+
+		var mix = ( animation.time % frameTime ) / frameTime;
+
+		if ( animation.directionBackwards ) mix = 1 - mix;
+
+		if ( animation.currentFrame !== animation.lastFrame ) {
+
+			this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
+			this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
+
+		} else {
+
+			this.morphTargetInfluences[ animation.currentFrame ] = weight;
+
+		}
+
+	}
+
 };
 
 /**

+ 3 - 0
examples/files.js

@@ -67,6 +67,8 @@ var files = {
 		"webgl_lights_pointlights2",
 		"webgl_lights_spotlight",
 		"webgl_lights_spotlights",
+		"webgl_lights_arealight",
+		"webgl_lights_rectarealight",
 		"webgl_lines_colors",
 		"webgl_lines_cubes",
 		"webgl_lines_dashed",
@@ -155,6 +157,7 @@ var files = {
 		"webgl_materials_variations_physical",
 		"webgl_materials_video",
 		"webgl_materials_wireframe",
+		"webgl_math_spherical_distribution",
 		"webgl_mirror",
 		"webgl_mirror_nodes",
 		"webgl_modifier_simplifier",

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 21 - 0
examples/js/lights/RectAreaLightUniformsLib.js


+ 12 - 0
examples/polygon_light_example/NOTES.md

@@ -0,0 +1,12 @@
+
+Notes on the sandbox example
+-----------------------------
+
+- relies on webgl extensions
+  - getcontext('experimental-webgl')
+  - oes_texture_float
+  - oes_texture_float_linear
+
+- pre-computed brdf values
+  - ltc_mat: 64 x 64 x 4 floats
+  - ltc_mag: 64 x 64 x 1 float

+ 28 - 0
examples/polygon_light_example/css/ace_theme_tweaks.css

@@ -0,0 +1,28 @@
+.ace-clouds-midnight .ace_gutter {
+  width: 50px;
+  background: #191919;
+  color: #333;
+  overflow : hidden;
+}
+
+.ace-clouds-midnight .ace_support.ace_function {
+  color:#B58900;
+}
+
+.ace-clouds-midnight .ace_constant.ace_language {
+  color:#B58900;
+}
+
+.ace-clouds-midnight .ace_variable.ace_language {
+  color:#1F74CF;
+}
+
+.ace_gutter-cell.ace_error {
+  background-image: url("../images/error_icon.png");
+}
+
+.ace_marker-layer .ace_error {
+	position: absolute;
+	z-index: 5;
+	background: rgba(255, 0, 0, 0.5);
+}

+ 25 - 0
examples/polygon_light_example/css/jquery.ui.resizable.css

@@ -0,0 +1,25 @@
+/*
+ * jQuery UI Resizable 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;
+	/* http://bugs.jqueryui.com/ticket/7233
+	 - Resizable: resizable handles fail to work in IE if transparent and content overlaps
+	*/
+	background-image:url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=);
+}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}

+ 16 - 0
examples/polygon_light_example/css/plugin.css

@@ -0,0 +1,16 @@
+#colorjack_square {
+	border: 1px solid #404040;
+	border-radius: 6px;
+	background: #191919;
+	box-shadow: 3px 3px 3px rgba(0,0,0,0.35);
+	-webkit-box-shadow: 3px 3px 3px rgba(0,0,0,0.35);
+	-moz-box-shadow: 3px 3px 3px rgba(0,0,0,0.35);
+	cursor: default;
+	display: block;
+	font-family: arial, helvetica, sans-serif;
+	font-size: 11px;
+	position: relative;
+	width: 215px;
+	height: 10px;
+	z-index:1;
+}

+ 66 - 0
examples/polygon_light_example/css/sandbox.css

@@ -0,0 +1,66 @@
+body {
+	background-color: #000000;
+	margin: 0px;
+	overflow: hidden;
+}
+
+
+#editor {
+	width: 100%;
+	height: 100%;
+	margin: 0px;
+	border: 2px solid #404040;
+	border-radius: 6px
+}
+
+
+.canvas {
+	border: 2px solid #404040;
+	border-radius: 6px
+}
+
+
+#params {
+	display: table;
+	float: left;
+	position: relative;
+	padding: 8px 8px 0px;
+	margin: 8px;
+	background: #191919;
+	border: 2px solid #404040;
+	border-radius: 6px
+}
+
+
+#status {
+	width: 484px;
+	float: left;
+	position: relative;
+	padding: 8px;
+	margin: 16px 0px 0px 0px;
+	background: #191919;
+	border: 2px solid #404040;
+	border-radius: 6px
+}
+
+#status_text {
+	font-family: helvetica, arial, sans-serif;
+	color: #999;
+	font-size: 12px
+}
+
+
+.checkbox_label {
+	font-family: helvetica, arial, sans-serif;
+	clear: both;
+	color: #999;
+	display: block;
+	float: left;
+	font-size: 12px;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+}
+
+.checkbox_label:hover {
+	color: #FFF;
+}

+ 35 - 0
examples/polygon_light_example/css/super_picker.css

@@ -0,0 +1,35 @@
+/* Default styling for SuperPicker controls */
+
+.sp_picker {
+	border: 0px solid #333;
+	border-radius: 6px;
+	min-height: 14px;
+	clear: both;
+	float: left;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	}
+.sp_label {
+	font-family: helvetica, arial, sans-serif;
+	clear: both;
+	color: #999;
+	display: block;
+	float: left;
+	font-size: 12px;
+	margin: 0 0 1px 0;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	}
+.sp_label:hover {
+	color: #FFF;
+	}
+
+.sp_swatch {
+	background: #ff0000;
+	border-radius: 6px;
+	width: 32px;
+	height: 32px;
+	float: left;
+	position: relative;
+	clear: left;
+}

+ 193 - 0
examples/polygon_light_example/css/super_slider.css

@@ -0,0 +1,193 @@
+/* Default styling for SuperSlider controls */
+
+.ss_slider {
+	border: 0px solid #333;
+	border-radius: 6px;
+	min-height: 14px;
+	clear: both;
+	float: left;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	}
+.ss_label {
+	font-family: helvetica, arial, sans-serif;
+	clear: both;
+	color: #999;
+	display: block;
+	float: left;
+	font-size: 12px;
+	margin: 0 0 1px 0;
+	padding: 0;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	}
+.ss_label:hover {
+	color: #FFF;
+	}
+.ss_track {
+	background-color: #404040;
+	border-radius: 4px;
+	padding: 1px;
+	width: 150px;
+	height: 12px;
+	float: left;
+	position: relative;
+	clear: left;
+	-webkit-user-select: none;
+	-webkit-user-drag: none;
+	-moz-user-select: none;
+	}
+.ss_track:hover {
+	background-color: #666;
+	}
+.ss_handle {
+	background-color: #999;
+	border-radius: 4px;
+	width: 20px;
+	height: 12px;
+	position: relative;
+	left: 0;
+	top: 0;
+	cursor: pointer;
+	}
+.ss_handle:focus,
+.ss_handle:hover {
+	background-color: #FFF;
+	cursor: pointer;
+	}
+.ss_input {
+	font-family: helvetica, arial, sans-serif;
+	border-radius: 4px;
+	width: 3em;
+	font-size: 11px;
+	color: #999;
+	border: 1px solid #404040;
+	background: transparent;
+	outline: none;
+	padding: 0;
+	line-height: 1;
+	vertical-align: top;
+	margin: 0 0 0 10px;
+	float: left;
+	text-align: right;
+	-webkit-user-select: text;
+	-moz-user-select: text;
+	}
+.ss_input:focus {
+	background-color: #333;
+	color: #FFF;
+	}
+
+
+/*
+// New
+
+.ss_slider
+{
+    padding: 5px;
+    border: 1px solid #333;
+    border-radius: 2px;
+    min-height: 14px;
+    clear: both;
+    margin: 5px;
+    float: left;
+    -webkit-user-select:none;
+    -moz-user-select:none
+}
+
+.ss_label{
+	font-family: helvetica, arial, sans-serif;
+	clear:both;
+	color: #999;
+    display: block;
+    float: left;
+    font - size: 12px;
+	margin: 0 0 3px 0;
+    padding: 0;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5)
+}
+
+.ss_slider: hover.ss_label
+{
+    color: #FFF!important
+}
+
+.ss_label: hover
+{
+    color: #FFF
+}
+
+.ss_track
+{
+    background-color: #555;
+    border-radius: 7px;
+    -moz-border-radius: 7px;
+    padding: 1px;
+    width: 150px;
+    height: 13px;
+    float: left;
+    position: relative;
+    clear: left;
+    -webkit-user-select: none;
+    -webkit-user-drag: none;
+    -moz-user-select: none;
+    -webkit-box-shadow: inset 0 1px 0 rgba(0,0,0,0.45),inset 0 -1px 0 rgba(255,255,255,0.1),inset 0 3px 5px rgba(0,0,0,0.3),0 5px 10px rgba(0,0,0,0.1),0 -3px 10px rgba(255,255,255,0.1);
+    -moz-box-shadow: inset 0 1px 0 rgba(0,0,0,0.45),inset 0 -1px 0 rgba(255,255,255,0.1),inset 0 3px 5px rgba(0,0,0,0.3),0 5px 10px rgba(0,0,0,0.1),0 -3px 10px rgba(255,255,255,0.1)
+}
+
+.ss_track: hover
+{
+	background-color:# 666
+}
+
+.ss_handle
+{
+    background-color: #999;
+    border-radius:6px;
+    -moz-border-radius:6px;
+    width:20px;
+    height:11px;
+    position:relative;
+    left:0;
+    top:1px;
+    cursor:pointer!important;
+    -webkit-user-select:none;
+    -webkit-user-drag:none;-moz-user-select:none;
+    -webkit-box-shadow:0 0 1px rgba(0,0,0,0.6),inset 0 1px 0 rgba(255,255,255,0.1),inset 0 2px 5px rgba(255,255,255,0.3);
+    -moz-box-shadow:0 0 1px rgba(0,0,0,0.6),inset 0 1px 0 rgba(255,255,255,0.1),inset 0 2px 5px rgba(255,255,255,0.3)
+}
+
+.ss_handle:active,.ss_handle:focus,.ss_handle:hover
+{
+	background-color: #FFF;
+    cursor: pointer!important
+}
+
+.ss_input
+{
+    font-family: helvetica,
+    arial,
+    sans - serif;
+    width: 3.5em;
+    font - size: 11px;
+    color: #999;
+    border:none;
+    background:transparent;
+    outline:none;
+    padding:0;
+    line-height:1;
+    vertical-align:top;
+    margin:0 0 0 10px;
+    float:left;
+    -webkit-user-select:text;
+    -moz-user-select:text
+}
+
+.ss_input:focus{
+	background-color:# 222;
+    color: #FFF;
+    -webkit-user-select: text!important;
+    -moz-user-select: text!important
+}*/

+ 836 - 0
examples/polygon_light_example/index.html

@@ -0,0 +1,836 @@
+<!DOCTYPE HTML>
+<html lang="en">
+
+<head>
+	<title>Shader Sandbox</title>
+	<meta charset="utf-8"></meta>
+</head>
+
+<body style="background: #191919">
+
+
+<link href="css/sandbox.css" rel="stylesheet" type="text/css"/>
+<link href="css/super_slider.css" media="screen" rel="stylesheet" type="text/css"/>
+<link href="css/super_picker.css" media="screen" rel="stylesheet" type="text/css"/>
+<link href="css/plugin.css" media="screen" rel="stylesheet" type="text/css"/>
+<link href="css/ace_theme_tweaks.css" rel="stylesheet" type="text/css"/>
+<link href="css/jquery.ui.resizable.css" rel="stylesheet">
+
+
+<div id="peekaboo" style="display: none; float: left; margin: 0px 8px 0px 0px; width: 750px; height: 600px; position: relative">
+	<pre id="editor"></pre>
+</div>
+
+
+<div id="stuff" style="display: none; overflow: auto; height: 100%">
+
+<div style="float: left; width: 512px; margin: 8px">
+	<div id="effect" style="width: 512px; height: 512px; float: left; position: relative;"></div>
+	<div id="status" style="min-width: 496px">
+		<p id="status_text"></p>
+	</div>
+</div>
+
+<div id="params"></div>
+
+</div>
+
+
+<script>
+	function Color(r, g, b) {
+		this.r = r;
+		this.g = g;
+		this.b = b;
+	}
+</script>
+
+
+<script src="js/color_picker.js" type="text/javascript"></script>
+<script src="js/jquery-1.6.1.min.js"></script>
+<script src="js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
+<script src="js/ace/theme-clouds_midnight.js" type="text/javascript" charset="utf-8"></script>
+<script src="js/ace/mode-glsl.js" type="text/javascript" charset="utf-8"></script>
+<script src="js/super_picker.js" type="text/javascript"></script>
+<script src="js/super_slider.js" type="text/javascript"></script>
+<script src="js/jquery.mousewheel.min.js" type="text/javascript"></script>
+
+<script src="js/macton/macton-utils.js" type="text/javascript"></script>
+<script src="js/macton/macton-gl-utils.js" type="text/javascript"></script>
+<script src="js/macton/webgl-utils.js" type="text/javascript"></script>
+<script src="js/macton/matrix4x4.js" type="text/javascript"></script>
+<script src="js/macton/cameracontroller.js" type="text/javascript"></script>
+
+<script src="js/jquery-ui-1.8.13.custom.min.js" type="text/javascript"></script>
+
+<script src="js/ltc_tables.js" type="text/javascript"></script>
+
+
+<script>
+var g_vshader = null;
+
+var g_param_types = {};
+var g_params = {};
+var g_param_edited = {};
+var g_param_default = {};
+
+var g_sample_count = 0;
+
+var bindSlider = function(name, label, default_value, value, state) {
+	var slider = new SuperSlider(name, {
+		label: label,
+		default_value: default_value,
+		value: value,
+		min:  state.min,
+		max:  state.max,
+		step: state.step });
+
+	slider.bind("change", function(event) {
+		g_params[name] = event.target.val;
+		g_param_edited[name] = true;
+		g_sample_count = 0;
+	});
+
+	slider.bind("reset", function(event) {
+		g_param_edited[name] = undefined;
+		g_sample_count = 0;
+	});
+
+	return slider;
+}
+
+var bindPicker = function(name, label, default_value, value) {
+	var picker = new SuperPicker(name, {
+		label: label,
+		default_value: default_value,
+		value: value,
+		callback: function(col) {
+			g_params[name] = col;
+			g_param_edited[name] = true;
+			g_sample_count = 0;
+		}
+	});
+
+	picker.bind("reset", function (event) {
+		g_param_edited[name] = undefined;
+		g_sample_count = 0;
+	});
+
+	return picker;
+}
+
+var bindCheckbox = function(name, label, value) {
+	var control = $("<input>")
+		.attr({ type: "checkbox", /*id: name,*/ checked: value })
+		.css("margin", "0 0 0 4px");
+
+	var cb_label = $("<label>")
+		.attr("for", name)
+		.text(label)
+		.addClass("checkbox_label");
+
+	cb_label.append(control);
+
+	control.bind("change", function (event) {
+		g_params[name] = event.target.checked;
+		g_param_edited[name] = true;
+		g_sample_count = 0;
+	});
+
+	cb_label.bind("click", function (event) {
+		control.prop("checked", g_param_default[name]);
+		control.trigger("change");
+		g_param_edited[name] = undefined;
+		g_sample_count = 0;
+	});
+
+	control.bind("click", function (event) {
+		event.stopPropagation();
+	});
+
+	return cb_label;
+}
+</script>
+
+
+<script type="text/javascript">
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ */
+if (!window.requestAnimationFrame) {
+	window.requestAnimationFrame = (function() {
+		return window.webkitRequestAnimationFrame ||
+		window.mozRequestAnimationFrame ||
+		window.oRequestAnimationFrame   ||
+		window.msRequestAnimationFrame  ||
+		function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+			window.setTimeout(callback, 1000/60);
+		};
+	})();
+}
+</script>
+
+
+<script type="text/javascript">
+var effectdiv,
+    sourcediv,
+    canvas,
+    gl,
+    buffer,
+    vertex_shader,
+    fragment_shader,
+    currentprogram,
+    vertexpositionlocation,
+    texturelocation,
+    parameters = {
+      start_time: new date().gettime(),
+      time: 0,
+      screenwidth: 0,
+      screenheight: 0
+    };
+
+var g_zoom = 0;
+
+var model      = new matrix4x4();
+var view       = new matrix4x4();
+var projection = new matrix4x4();
+var controller = null;
+
+var ltc_mat_texture = null;
+var ltc_mag_texture = null;
+
+var rttframebuffer = null;
+var rtttexture = null;
+
+var blit_vs = null;
+var blit_fs = null;
+var blitprogram = null;
+
+
+function fetchfile(url, cache)
+{
+  var text = $.ajax({
+    url:   url,
+    async: false,
+    datatype: "text",
+    mimetype: "text/plain",
+    cache: cache,
+  }).responsetext;
+
+  return text;
+}
+
+function setclampedtexturestate()
+{
+  gl.texparameteri(gl.texture_2d, gl.texture_min_filter, gl.nearest);
+  gl.texparameteri(gl.texture_2d, gl.texture_mag_filter, gl.linear);
+  gl.texparameteri(gl.texture_2d, gl.texture_wrap_s, gl.clamp_to_edge);
+  gl.texparameteri(gl.texture_2d, gl.texture_wrap_t, gl.clamp_to_edge);
+}
+
+function init() {
+  vertex_shader   = fetchfile("shaders/ltc/ltc.vs", false);
+  fragment_shader = fetchfile("shaders/ltc/ltc.fs", false);
+
+  g_vshader = vertex_shader;
+  $("#editor").text(fragment_shader);
+
+  canvas = document.createelement("canvas");
+  canvas.style.csstext = "border: 2px solid #404040; border-radius: 6px";
+
+  effectdiv = document.getelementbyid("effect");
+  effectdiv.appendchild(canvas);
+
+  // initialise webgl
+  try {
+    gl = canvas.getcontext("experimental-webgl");
+  } catch(error) { }
+
+  if (!gl) {
+    alert("webgl not supported");
+    throw "cannot create webgl context";
+  }
+
+  // check for float-rt support
+  if (!gl.getextension("oes_texture_float")) {
+    alert("oes_texture_float not supported");
+    throw "missing webgl extension";
+  }
+
+  if (!gl.getextension("oes_texture_float_linear")) {
+    alert("oes_texture_float_linear not supported");
+    throw "missing webgl extension";
+  }
+
+
+  // add enum strings to context
+  if (gl.enum_strings === undefined) {
+    gl.enum_strings = { };
+    for (var propertyname in gl) {
+      if (typeof gl[propertyname] == "number") {
+        gl.enum_strings[gl[propertyname]] = propertyname;
+      }
+    }
+  }
+
+  // create vertex buffer (2 triangles)
+  buffer = gl.createbuffer();
+  gl.bindbuffer(gl.array_buffer, buffer);
+  gl.bufferdata(gl.array_buffer, new float32array([
+    -1.0, -1.0,
+     1.0, -1.0,
+    -1.0,  1.0,
+     1.0, -1.0,
+     1.0,  1.0,
+    -1.0, 1.0
+  ]), gl.static_draw);
+
+  // create program
+  currentprogram = createprogram(vertex_shader, fragment_shader);
+
+  ////
+  rttframebuffer = gl.createframebuffer();
+  gl.bindframebuffer(gl.framebuffer, rttframebuffer);
+  rttframebuffer.width  = 512; // fixme
+  rttframebuffer.height = 512;
+
+  rtttexture = gl.createtexture();
+  gl.bindtexture(gl.texture_2d, rtttexture);
+  gl.teximage2d(gl.texture_2d, 0, gl.rgba,
+      rttframebuffer.width, rttframebuffer.height,
+      0, gl.rgba, gl.float, null);
+
+  gl.framebuffertexture2d(gl.framebuffer, gl.color_attachment0, gl.texture_2d, rtttexture, 0);
+  gl.bindframebuffer(gl.framebuffer, null);
+
+  gl.bindtexture(gl.texture_2d, null);
+
+
+  blit_vs = fetchfile("shaders/ltc/ltc_blit.vs", false);
+  blit_fs = fetchfile("shaders/ltc/ltc_blit.fs", false);
+
+  var header = "#ifdef gl_es\nprecision highp float;\n#endif\n#line 0\n";
+  blit_fs = header + blit_fs;
+
+  blitprogram = gl.createprogram();
+
+  var vs = createshader(blit_vs, gl.vertex_shader);
+  var fs = createshader(blit_fs, gl.fragment_shader);
+
+  gl.attachshader(blitprogram, vs);
+  gl.attachshader(blitprogram, fs);
+
+  gl.deleteshader(vs);
+  gl.deleteshader(fs);
+
+  gl.linkprogram(blitprogram);
+  ////
+
+  onwindowresize();
+  window.addeventlistener("resize", onwindowresize, false);
+
+  $("canvas").mousewheel(function(event, delta) {
+    g_zoom += delta*10.0;
+    g_sample_count = 0;
+    //		console.log("pagex: " + event.pagex + " pagey: " + event.pagey);
+    return false;
+  });
+
+  $("canvas").attr("tabindex", "0").keydown(function(event) {
+    //console.log(event);
+    return false;
+  });
+
+  // Load fitted brdf data as float texture
+  // 64 x 64 texels of 4 floats each
+  ltc_mat_texture = gl.createtexture();
+  gl.bindtexture(gl.texture_2d, ltc_mat_texture);
+  gl.teximage2d(gl.texture_2d, 0, gl.rgba, 64, 64, 0, gl.rgba, gl.float, new float32array(g_ltc_mat));
+  setclampedtexturestate();
+
+  // 64 x 64 texels of 1 float each
+  ltc_mag_texture = gl.createtexture();
+  gl.bindtexture(gl.texture_2d, ltc_mag_texture);
+  gl.teximage2d(gl.texture_2d, 0, gl.alpha, 64, 64, 0, gl.alpha, gl.float, new float32array(g_ltc_mag));
+  setclampedtexturestate();
+
+  // fetch media and kick off the main program once everything has loaded
+  $(function() {
+    $(document).ajaxstop(function() {
+      $(this).unbind("ajaxstop");
+      main_prog();
+    });
+
+    // load data here
+
+    main_prog();
+  });
+}
+
+
+function onwindowresize(event) {
+  $("#peekaboo").css("height", window.innerheight);
+  $("#stuff").css("height", window.innerheight);
+
+  canvas.width  = 512; // window.innerwidth;
+  canvas.height = 512; // window.innerheight;
+
+  parameters.screenwidth = canvas.width;
+  parameters.screenheight = canvas.height;
+
+  gl.viewport(0, 0, canvas.width, canvas.height);
+}
+
+
+function gluniformtypetostring(type) {
+  switch (type) {
+    case gl.float:        return "float";
+    case gl.float_vec2:   return "float_vec2";
+    case gl.float_vec3:   return "float_vec3";
+    case gl.float_vec4:   return "float_vec4";
+    case gl.int:          return "int";
+    case gl.int_vec2:     return "int_vec2";
+    case gl.int_vec3:     return "int_vec3";
+    case gl.int_vec4:     return "int_vec4";
+    case gl.bool:         return "bool";
+    case gl.bool_vec2:    return "bool_vec2";
+    case gl.bool_vec3:    return "bool_vec3";
+    case gl.bool_vec4:    return "bool_vec4";
+    case gl.float_mat2:   return "float_mat2";
+    case gl.float_mat3:   return "float_mat3";
+    case gl.float_mat4:   return "float_mat4";
+    case gl.sampler_2d:   return "sampler_2d";
+    case gl.sampler_cube: return "sampler_cube";
+  }
+
+  return "unknown";
+}
+
+
+function createprogram(vertex, fragment) {
+  var header = "#ifdef gl_es\nprecision highp float;\n#endif\n#line 0\n";
+  fragment = header + fragment;
+
+  var program = gl.createprogram();
+
+  var vs = createshader(vertex, gl.vertex_shader);
+  var fs = createshader(fragment, gl.fragment_shader);
+
+  if (vs == null || fs == null)
+    return null;
+
+  gl.attachshader(program, vs);
+  gl.attachshader(program, fs);
+
+  gl.deleteshader(vs);
+  gl.deleteshader(fs);
+
+  gl.linkprogram(program);
+
+
+  if (!gl.getprogramparameter(program, gl.link_status)) {
+    /*
+       alert( "error:\n" +
+       "validate_status: " + gl.getprogramparameter( program, gl.validate_status ) + "\n" +
+       "error: " + gl.geterror() + "\n\n" +
+       "- vertex shader -\n" + vertex + "\n\n" +
+       "- fragment shader -\n" + fragment );
+     */
+    return null;
+  }
+
+
+  var control_names = new array();
+  var controls = {};
+
+  var lines = fragment.split("\n");
+
+  var bindings = { }
+
+  // adds double quotes around labels
+  function preprocessjson(str) {
+    return str.replace(/("(\\.|[^"])*"|'(\\.|[^'])*')|(\w+)\s*:/g,
+        function(all, string, strdouble, strsingle, jsonlabel) {
+          if (jsonlabel) {
+            return '"' + jsonlabel + '": ';
+          }
+          return all;
+        });
+  }
+
+  for (var x in lines) {
+    var line = lines[x];
+    var re = /^\/\/ bind\s+(.*?)\s(.*)/;
+    var m = re.exec(line);
+    if (m) {
+      try {
+        var obj = $.parsejson(preprocessjson(m[2]));
+        bindings[m[1]] = obj;
+        control_names.push(m[1]);
+      }
+      catch (e) {
+        console.log("failed to parse shader binding: " + m[1]);
+      }
+    }
+  }
+
+  function copyproperties(src, dest) {
+    for (x in src) {
+      if (dest.hasownproperty(x) && typeof(dest[x]) === typeof(src[x]))
+        dest[x] = src[x];
+    }
+  }
+
+  // destroy existing children
+  var container = $("#params");
+
+  var children = container.children();
+  children.each(function() {
+    var v = $(this);
+    v.detach();
+  })
+  container.empty();
+
+  g_param_types = {};
+
+  var nb_uniforms = gl.getprogramparameter(program, gl.active_uniforms);
+  for (var i = 0; i < nb_uniforms; i++) {
+    var uni = gl.getactiveuniform(program, i);
+
+    var label = uni.name;
+    var bind = bindings[uni.name];
+    if (bind && typeof(bind.label) === "string")
+      label = bind.label;
+
+    var old_value  = g_params[uni.name];
+    var is_default = !g_param_edited[uni.name];
+    var new_value  = undefined;
+
+    var control = undefined;
+
+    if (uni.type === gl.float) {
+      var state = {
+        default : 1.0,
+                  min  : 0.0,
+                  max  : 1.0,
+                  step : 0.01,
+      };
+
+      copyproperties(bind, state);
+
+      var is_default = !g_param_edited[uni.name];
+
+      new_value = is_default ? state.default : old_value;
+      control = bindslider(uni.name, label, state.default, new_value, state);
+    }
+    else if (uni.type === gl.float_vec3) {
+      var def_col = new color(1, 1, 1)
+
+        copyproperties(bind, def_col);
+
+      new_value = is_default ? def_col : old_value;
+      control = bindpicker(uni.name, label, def_col, new_value);
+    }
+    else if (uni.type === gl.bool) {
+      var def_val = true;
+
+      if (bind && typeof(bind.default) === "boolean")
+        def_val = bind.default;
+
+      g_param_default[uni.name] = def_val;
+
+      new_value = is_default ? def_val : old_value;
+      control = bindcheckbox(uni.name, label, new_value);
+    }
+    else {
+      continue;
+    }
+
+    g_param_types[uni.name] = uni.type;
+    g_params[uni.name] = new_value;
+
+    if (bind === undefined)
+      control_names.push(uni.name);
+    controls[uni.name] = control;
+  }
+
+  // finally add the controls to the page
+  for (x in control_names) {
+    var control = controls[control_names[x]];
+
+    // skip any binds that don't reference active uniforms
+    if (control === undefined)
+      continue;
+
+    // pad for next element
+    control.css("margin", "0 0 6px 0");
+
+    container.append(control);
+  }
+
+  return program;
+}
+
+
+var gl_shader_error = null;
+
+function createshader(src, type) {
+  var shader = gl.createshader(type);
+
+  gl.shadersource(shader, src);
+  gl.compileshader(shader);
+
+  gl_shader_error = null;
+
+  if (!gl.getshaderparameter(shader, gl.compile_status)) {
+    gl_shader_error = gl.getshaderinfolog(shader);
+    //		console.log((type == gl.vertex_shader ? "vertex" : "fragment") + " shader:\n" + gl.getshaderinfolog(shader));
+    return null;
+  }
+
+  return shader;
+}
+
+
+function animate() {
+  requestanimationframe(animate);
+  draw();
+}
+
+function main_prog() {
+  controller = new cameracontroller(canvas);
+  // try the following (and uncomment the "pointer-events: none;" in
+  // the index.html) to try the more precise hit detection
+  //  controller = new cameracontroller(document.getelementbyid("body"), c, gl);
+  controller.onchange = function(xrot, yrot) {
+    draw();
+    g_sample_count = 0;
+  };
+
+  requestanimationframe(animate);
+
+  draw();
+}
+
+
+function checkglerror() {
+  var error = gl.geterror();
+  if (error != gl.no_error) {
+    var str = "gl error: " + error + " " + gl.enum_strings[error];
+    console.log(str);
+    throw str;
+  }
+}
+
+
+function draw() {
+  parameters.time = new date().gettime() - parameters.start_time;
+
+  gl.enable(gl.depth_test);
+  gl.clearcolor(0.0, 0.0, 0.0, 0.0);
+
+  // note: the viewport is automatically set up to cover the entire canvas.
+  gl.clear(gl.color_buffer_bit | gl.depth_buffer_bit);
+
+  checkglerror();
+
+  // load program into gpu
+  gl.useprogram(currentprogram);
+
+  checkglerror();
+
+  // add in camera controller's rotation
+  view.loadidentity();
+  view.translate(0, 6, 0.1*g_zoom - 0.5);
+  view.rotate(controller.xrot - 10.0, 1, 0, 0);
+  view.rotate(controller.yrot, 0, 1, 0);;
+
+  // get var locations
+  vertexpositionlocation = gl.getattriblocation(currentprogram, "position");
+
+  function location(u) {
+    return gl.getuniformlocation(currentprogram, u);
+  }
+
+  // set values to program variables
+  for (var x in g_params) {
+    var type  = g_param_types[x];
+    var value = g_params[x];
+    var loc   = location(x);
+
+    if (type === gl.float)      gl.uniform1f(loc, value);
+    if (type === gl.float_vec3) gl.uniform3f(loc, value.r, value.g, value.b);
+    if (type === gl.bool)       gl.uniform1i(loc, value);
+  }
+
+  gl.uniformmatrix4fv(location("view"), gl.false, new float32array(view.elements));
+  gl.uniform1f(location("time"), parameters.time/1000);
+  gl.uniform2f(location("resolution"), parameters.screenwidth, parameters.screenheight);
+
+  gl.activetexture(gl.texture0);
+  gl.bindtexture(gl.texture_2d, ltc_mat_texture);
+  gl.uniform1i(gl.getuniformlocation(currentprogram, "ltc_mat"), 0);
+
+  gl.activetexture(gl.texture1);
+  gl.bindtexture(gl.texture_2d, ltc_mag_texture);
+  gl.uniform1i(gl.getuniformlocation(currentprogram, "ltc_mag"), 1);
+
+  checkglerror();
+
+  gl.bindframebuffer(gl.framebuffer, rttframebuffer);
+
+  if (g_sample_count === 0) {
+    gl.clearcolor(0, 0, 0, 0);
+    gl.clear(gl.color_buffer_bit);
+  }
+
+  gl.uniform1i(location("samplecount"), g_sample_count);
+  g_sample_count += 8;
+
+  gl.enable(gl.blend);
+  gl.blendfunc(gl.one, gl.one);
+
+  // render geometry
+  gl.bindbuffer(gl.array_buffer, buffer);
+  gl.vertexattribpointer(vertexpositionlocation, 2, gl.float, false, 0, 0);
+  gl.enablevertexattribarray(vertexpositionlocation);
+  gl.drawarrays(gl.triangles, 0, 6);
+  gl.disablevertexattribarray(vertexpositionlocation);
+
+  gl.bindframebuffer(gl.framebuffer, null);
+  gl.useprogram(blitprogram);
+
+  gl.disable(gl.blend);
+
+  // set textures
+  gl.activetexture(gl.texture0);
+  gl.bindtexture(gl.texture_2d, rtttexture);
+  gl.uniform1i(gl.getuniformlocation(blitprogram, "tex"), 0);
+
+  gl.texparameteri(gl.texture_2d, gl.texture_min_filter, gl.nearest);
+  gl.texparameteri(gl.texture_2d, gl.texture_mag_filter, gl.nearest);
+  gl.texparameteri(gl.texture_2d, gl.texture_wrap_s,     gl.clamp_to_edge);
+  gl.texparameteri(gl.texture_2d, gl.texture_wrap_t,     gl.clamp_to_edge);
+
+  gl.uniform2f(gl.getuniformlocation(blitprogram, "resolution"), parameters.screenwidth, parameters.screenheight);
+
+  // blit pass
+  gl.enablevertexattribarray(vertexpositionlocation);
+  gl.drawarrays(gl.triangles, 0, 6);
+  gl.disablevertexattribarray(vertexpositionlocation);
+
+  gl.bindtexture(gl.texture_2d, null);
+}
+</script>
+
+
+<script>
+  window.onload = function() {
+    init();
+
+    $("#peekaboo").resizable({ minWidth: 500 });
+    $("#peekaboo").show();
+
+    var editor = ace.edit("editor");
+    editor.setTheme("ace/theme/clouds_midnight");
+
+    var Mode = require("ace/mode/glsl").Mode;
+    editor.getSession().setMode(new Mode());
+    editor.getSession().setUseWrapMode(true);
+    editor.getSession().setOption("firstLineNumber", 0);
+    editor.setShowPrintMargin(false);
+
+    editor.renderer.setHScrollBarAlwaysVisible(false);
+    editor.renderer.setVScrollBarAlwaysVisible(false);
+
+    var markers = [];
+
+    var nb_sliders = 0;
+
+    var timeout = null;
+
+    $("#peekaboo").bind("resize", function(event, ui) {
+      editor.resize();
+    });
+
+    $("#peekaboo").css("height", window.innerHeight);
+
+    $("#stuff").show();
+
+    var rebuild = function() {
+      var vertex_shader, fragment_shader;
+      var new_prog;
+      var text = editor.getSession().getValue();
+
+      vertex_shader   = g_vshader;
+      fragment_shader = text;
+
+      editor.getSession().clearAnnotations();
+      for (m in markers) {
+        editor.getSession().removeMarker(markers[m]);
+      }
+      markers = [];
+
+      new_prog = createProgram(vertex_shader, fragment_shader);
+
+      var status_color = new_prog ? "#404040" : "#7f0000"
+        $("#status").css("border-color", status_color);
+
+      marked_lines = [];
+
+      var text = "Compiled successfully!";
+
+      if (new_prog !== null) {
+        currentProgram = new_prog;
+      }
+      else if (gl_shader_error !== null) {
+        gl_shader_error = gl_shader_error.replace(/\0/g, "");
+        text = gl_shader_error.replace(/\n/g, "<br>");
+
+        var annos = [];
+
+        var re = /\d+:(\d+):(.*)\n/g;
+        var match;
+        while (match = re.exec(gl_shader_error)) {
+          var line = parseInt(match[1]);
+          var error = match[2];
+
+          if (marked_lines[line]) {
+            error = " **** " + error;
+          }
+
+          annos.push({
+            row: line,
+            column: 0,
+            text: error,
+            type: "error"
+          });
+
+          if (marked_lines[line] === undefined) {
+            marked_lines[line] = true;
+
+            var Range = require("ace/range").Range;
+            var line_range = new Range(line, 0, line + 1, 0);
+
+            var marker = editor.getSession().addMarker(line_range, "ace_error", "line", false);
+            markers.push(marker);
+          }
+        }
+
+        editor.getSession().setAnnotations(annos);
+      }
+
+      $("#status_text").html(text);
+
+      g_sample_count = 0;
+    }
+
+    $("#editor").keyup(function() {
+      clearTimeout(timeout);
+      timeout = setTimeout(rebuild, 500);
+    });
+  }
+</script>
+
+</body>
+</html>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
examples/polygon_light_example/js/ace/ace.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
examples/polygon_light_example/js/ace/mode-glsl.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
examples/polygon_light_example/js/ace/theme-clouds_midnight.js


+ 457 - 0
examples/polygon_light_example/js/color_picker.js

@@ -0,0 +1,457 @@
+
+if (window.ColorXXX == undefined) ColorXXX = {};
+
+ColorXXX.Picker = function (props) {
+
+	/// loading properties
+	if (typeof(props) == "undefined") props = {};
+	this.callback = props.callback; // bind custom function
+	this.hue = props.hue || 0; // 0-360
+	this.sat = props.sat || 0; // 0-100
+	this.val = props.val || 100; // 0-100
+	this.element = props.element || document.body;
+	this.size = 165; // size of colorpicker
+	this.margin = 10; // margins on colorpicker
+	this.offset = this.margin / 2;
+	this.hueWidth = 30;
+
+	/// creating colorpicker (header)
+	var plugin = document.createElement("div");
+	plugin.id = "colorjack_square";
+	plugin.style.cssText  = "height: " + (this.size + this.margin * 2) + "px";
+
+    this.visible = function() {
+		var current = plugin.style.display;
+        return current !== "none";
+    }
+
+	this.hide = function () {
+		plugin.style.display = "none";
+	}
+
+	this.show = function () {
+		plugin.style.display = "block";
+	}
+
+	this.toggle = function () {
+		if (this.visible())
+			$(plugin).fadeOut("fast", "linear");		
+		else
+			$(plugin).fadeIn("fast", "linear");
+
+		//plugin.style.display = (current === "none") ? "block" : "none";
+	}
+
+    if (props.fade) {
+	    plugin.style.cssText += "display: none";
+	    this.toggle();
+    }
+
+	var that = this;
+
+	/// creating media-resources
+	var arrows = document.createElement("canvas");
+	arrows.width = 40;
+	arrows.height = 5;
+	(function () { // creating arrows
+		var ctx = arrows.getContext("2d");
+		var width = 3;
+		var height = 5;
+		var size = 9;
+		var top = -size / 4;
+		var left = 1;
+		for (var n = 0; n < 20; n++) { // multiply anti-aliasing
+			ctx.beginPath();
+			ctx.fillStyle = "#FFF";
+			ctx.moveTo(left + size / 4, size / 2 + top);
+			ctx.lineTo(left, size / 4 + top);
+			ctx.lineTo(left, size / 4 * 3 + top);
+			ctx.fill();
+		}
+		ctx.translate(width, height);
+		ctx.rotate(180 * Math.PI / 180); // rotate arrows
+		ctx.drawImage(arrows, -29, 0);
+		ctx.translate(-width, -height);
+	})();
+
+	var circle = document.createElement("canvas");
+	circle.width = 10;
+	circle.height = 10;
+	(function () { // creating circle-selection
+		var ctx = circle.getContext("2d");
+		ctx.lineWidth = 1;
+		ctx.beginPath();
+		var x = circle.width / 2;
+		var y = circle.width / 2;
+		ctx.arc(x, y, 4.5, 0, Math.PI * 2, true);
+		ctx.strokeStyle = '#000';
+		ctx.stroke();
+		ctx.beginPath();
+		ctx.arc(x, y, 3.5, 0, Math.PI * 2, true);
+		ctx.strokeStyle = '#FFF';
+		ctx.stroke();
+	})();
+
+	/// creating colorpicker sliders
+	var canvas = document.createElement("canvas");
+	var ctx = canvas.getContext("2d");
+	canvas.style.cssText = "position: absolute; top: 5px; left: " + (this.offset) + "px;";
+	canvas.width = this.size + this.hueWidth + this.margin;
+	canvas.height = this.size + this.margin;
+	plugin.appendChild(canvas);
+
+	plugin.onclick = function(e) {
+	    e.stopPropagation();
+	}
+
+	plugin.onmousemove =
+	plugin.onmousedown = function (e) {
+		var down = (e.type == "mousedown");
+		var offset = that.margin / 2;
+
+	    var of = $(canvas).offset();
+	    var oX = of.left;
+	    var oY = of.top;
+
+		var x0 = (e.pageX - oX) - offset;
+		var y0 = (e.pageY - oY) - offset;
+		var x = clamp(x0, 0, canvas.width);
+		var y = clamp(y0, 0, that.size);
+
+		if (x != x0 || y != y0) { // move colorpicker
+			/*plugin.style.cursor = "move";
+			if (down) dragElement({
+				type: "difference",
+				event: e,
+				element: plugin,
+				callback: function (coords, state) {
+					plugin.style.left = coords.x + "px";
+					plugin.style.top = coords.y + "px";
+				}
+			});*/
+			return;
+		} else if (x <= that.size) { // saturation-value selection
+			plugin.style.cursor = "crosshair";
+			if (down) dragElement({
+				type: "relative",
+				event: e,
+				element: canvas,
+				callback: function (coords, state) {
+					var x = clamp(coords.x - that.offset, 0, that.size);
+					var y = clamp(coords.y - that.offset, 0, that.size);
+					that.sat = x / that.size * 100; // scale saturation
+					that.val = 100 - (y / that.size * 100); // scale value
+					that.drawSample();
+				}
+			});
+		} else if (x > that.size + that.margin && x <= that.size + that.hueWidth) { // hue selection
+			plugin.style.cursor = "crosshair";
+			if (down) dragElement({
+				type: "relative",
+				event: e,
+				element: canvas,
+				callback: function (coords, state) {
+					var y = clamp(coords.y - that.offset, 0, that.size);
+					that.hue = Math.min(1, y / that.size) * 360;
+					that.drawSample();
+				}
+			});
+		} else { // margin between hue/saturation-value
+			plugin.style.cursor = "default";
+		}
+		
+		return false; // prevent selection
+	};
+	// appending to elementc
+	this.element.appendChild(plugin);
+
+	/// helper functions
+	var that = this;
+	this.el = plugin;
+	this.drawSample = function () {
+		// clearing canvas
+		ctx.clearRect(0, 0, canvas.width, canvas.height)
+		that.drawSquare();
+		that.drawHue();
+
+		// arrow-selection
+		var y = (that.hue / 362) * that.size - 2;
+		ctx.drawImage(arrows, that.size + that.offset + 4, Math.round(y) + that.offset);
+
+		// circle-selection
+		var x = that.sat / 100 * that.size;
+		var y = (1 - (that.val / 100)) * that.size;
+		x = x - circle.width / 2;
+		y = y - circle.height / 2;
+ 		ctx.drawImage(circle, Math.round(x) + that.offset, Math.round(y) + that.offset);
+
+		// run custom code
+		if (that.callback) {
+            var rgb = ColorXXX.HSV_RGB({
+                H: that.hue,
+                S: that.sat,
+                V: that.val
+            });
+
+		    that.callback(rgb);
+		}
+	};
+
+	this.drawSquare = function () {
+		// retrieving hex-code
+		var hex = ColorXXX.HSV_HEX({
+			H: that.hue,
+			S: 100,
+			V: 100
+		});
+		var offset = that.offset;
+		var size = that.size;
+		// drawing color
+		ctx.fillStyle = "#" + hex;
+		ctx.fillRect(offset, offset, size, size);
+		// overlaying saturation
+		var gradient = ctx.createLinearGradient(offset, offset, size + offset, 0);
+		gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
+		gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
+		ctx.fillStyle = gradient;
+		ctx.fillRect(offset, offset, size, size);
+		// overlaying value
+		var gradient = ctx.createLinearGradient(offset, offset, 0, size + offset);
+		gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
+		gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
+		ctx.fillStyle = gradient;
+		ctx.fillRect(offset, offset, size, size);
+		// drawing outer bounds
+		ctx.strokeStyle = "rgba(255,255,255,0.15)";
+		ctx.strokeRect(offset+0.5, offset+0.5, size-1, size-1);
+	};
+
+	this.drawHue = function () {
+		// drawing hue selector
+		var left = that.size + that.margin + that.offset;
+		var gradient = ctx.createLinearGradient(0, 0, 0, that.size);
+		gradient.addColorStop(0.00, "rgba(255,   0,  0,  1)");
+		gradient.addColorStop(0.15, "rgba(255, 255,  0,  1)");
+		gradient.addColorStop(0.30, "rgba(0,   255,  0,  1)");
+		gradient.addColorStop(0.50, "rgba(0,   255, 255, 1)");
+		gradient.addColorStop(0.65, "rgba(0,     0, 255, 1)");
+		gradient.addColorStop(0.80, "rgba(255,   0, 255, 1)");
+		gradient.addColorStop(1.00, "rgba(255,   0,   0, 1)");
+		ctx.fillStyle = gradient;
+		ctx.fillRect(left, that.offset, 20, that.size);
+		// drawing outer bounds
+		ctx.strokeStyle = "rgba(255,255,255,0.2)";
+		ctx.strokeRect(left + 0.5, that.offset + 0.5, 19, that.size-1);
+	};
+
+	this.destroyMe = function () {
+	    $(plugin).remove();
+		for (var key in that) delete that[key];
+	};
+
+	// drawing color selection
+	this.drawSample();
+
+	return this;
+};
+
+
+var dragElement = function(props) {
+	function mouseMove(e, state) {
+		if (typeof(state) == "undefined") state = "move";
+		var coord = XY(e);
+		switch (props.type) {
+			case "difference": 
+				props.callback({
+					x: coord.x + oX - eX,
+					y: coord.y + oY - eY
+				}, state);
+				break;
+			case "relative":
+				props.callback({
+					x: coord.x - oX,
+					y: coord.y - oY
+				}, state);
+				break;
+			default: // "absolute"
+				props.callback({
+					x: coord.x,
+					y: coord.y
+				}, state);
+				break;
+		}
+	};
+	function mouseUp(e) {
+		window.removeEventListener("mousemove", mouseMove, false);
+		window.removeEventListener("mouseup", mouseUp, false);
+		mouseMove(e, "up");
+	};
+	function mouseClick(e) {
+	    console.log("hghghfghh");
+	    e.stopPropagation();
+	}
+
+	// current element position
+	var el = props.element;
+
+
+	var of = $(el).offset();
+	var oX = of.left;
+	var oY = of.top;
+
+//	var origin = abPos(el);
+//	var oX = origin.x;
+//	var oY = origin.y;
+
+	// current mouse position
+	var e = props.event;
+	var coord = XY(e);
+	var eX = coord.x;
+	var eY = coord.y;
+
+	// events
+	window.addEventListener("mousemove", mouseMove, false);
+	window.addEventListener("mouseup", mouseUp, false);
+	mouseMove(e, "down"); // run mouse-down
+};
+
+var clamp = function(n, min, max) {
+	return (n < min) ? min : ((n > max) ? max : n);
+};
+
+var XY = window.ActiveXObject ? // fix XY to work in various browsers
+	function(event) {
+		return {
+			x: event.clientX + document.documentElement.scrollLeft,
+			y: event.clientY + document.documentElement.scrollTop
+		};
+	} : function(event) {
+		return {
+			x: event.pageX,
+			y: event.pageY
+		};
+	};
+
+var abPos = function(o) { 
+	o = typeof(o) == 'object' ? o : $(o);
+	var offset = { x: 0, y: 0 };
+	while(o != null) { 
+		offset.x += o.offsetLeft; 
+		offset.y += o.offsetTop; 
+		o = o.offsetParent; 
+	};
+	return offset;
+};
+
+
+ColorXXX.HEX_STRING = function (o) {
+	var z = o.toString(16);
+	var n = z.length;
+	while (n < 6) {
+		z = '0' + z;
+		n ++;
+	}
+	return z;
+};
+
+ColorXXX.RGB_HEX = function (o) {
+	return o.R << 16 | o.G << 8 | o.B;
+};
+
+ColorXXX.HSV_RGB = function (o) {
+	var H = o.H / 360,
+		S = o.S / 100,
+		V = o.V / 100,
+		R, G, B;
+	var A, B, C, D;
+	if (S == 0) {
+		R = G = B = Math.round(V * 255);
+	} else {
+		if (H >= 1) H = 0;
+		H = 6 * H;
+		D = H - Math.floor(H);
+		A = Math.round(255 * V * (1 - S));
+		B = Math.round(255 * V * (1 - (S * D)));
+		C = Math.round(255 * V * (1 - (S * (1 - D))));
+		V = Math.round(255 * V);
+		switch (Math.floor(H)) {
+			case 0:
+				R = V;
+				G = C;
+				B = A;
+				break;
+			case 1:
+				R = B;
+				G = V;
+				B = A;
+				break;
+			case 2:
+				R = A;
+				G = V;
+				B = C;
+				break;
+			case 3:
+				R = A;
+				G = B;
+				B = V;
+				break;
+			case 4:
+				R = C;
+				G = A;
+				B = V;
+				break;
+			case 5:
+				R = V;
+				G = A;
+				B = B;
+				break;
+		}
+	}
+	return {
+		R: R,
+		G: G,
+		B: B
+	};
+};
+
+ColorXXX.HSV_HEX = function (o) {
+	return ColorXXX.HEX_STRING(ColorXXX.RGB_HEX(ColorXXX.HSV_RGB(o)));
+};
+
+ColorXXX.rgb2hsv = function (r, g, b) {
+	var color = typeof(r) === 'number' ? [r / 255, g / 255, b / 255] : [r.r / 255, r.g / 255, r.b / 255],
+		rgb_min = Math.min(color[0], Math.min(color[1], color[2])),
+		rgb_max = Math.max(color[0], Math.max(color[1], color[2])),
+		rgb_delta = rgb_max - rgb_min,
+		v = rgb_max,
+		h, s, r_delta, g_delta, b_delta;
+	
+    if (rgb_delta === 0.0) {
+        // Grey
+        h = 0.0;
+        s = 0.0;
+    } else {
+        // Colour
+        s = rgb_delta / rgb_max;
+        r_delta = (((rgb_max - color[0]) / 6.0) + (rgb_delta / 2.0)) / rgb_delta;
+        g_delta = (((rgb_max - color[1]) / 6.0) + (rgb_delta / 2.0)) / rgb_delta;
+        b_delta = (((rgb_max - color[2]) / 6.0) + (rgb_delta / 2.0)) / rgb_delta;
+
+        if (color[0] === rgb_max) {
+            h = b_delta - g_delta;
+        } else if (color[1] === rgb_max) {
+            h = 1.0 / 3.0 + r_delta - b_delta;
+        } else if (color[2] === rgb_max) {
+            h = 2.0 / 3.0 + g_delta - r_delta;
+        }
+
+        if (h < 0.0) {
+			h += 1.0;
+		}
+        if (h > 1.0) {
+			h -= 1.0;
+		}
+    }
+	
+    return [Math.round(h * 359), Math.round(s * 100), Math.round(v * 100)];
+};

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 15 - 0
examples/polygon_light_example/js/jquery-1.6.1.min.js


+ 98 - 0
examples/polygon_light_example/js/jquery-ui-1.8.13.custom.min.js

@@ -0,0 +1,98 @@
+/*!
+ * jQuery UI 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.13",
+keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();
+b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,
+"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",
+function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,
+outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b);
+return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=
+0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
+;/*!
+ * jQuery UI Widget 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
+a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h;
+e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,
+this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
+widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},
+enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
+;/*!
+ * jQuery UI Mouse 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Mouse
+ *
+ * Depends:
+ *	jquery.ui.widget.js
+ */
+(function(b){var d=false;b(document).mousedown(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
+this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"?b(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=
+this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&&
+!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
+false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
+;/*
+ * jQuery UI Resizable 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */
+(function(e){e.widget("ui.resizable",e.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var b=this,a=this.options;this.element.addClass("ui-resizable");e.extend(this,{_aspectRatio:!!a.aspectRatio,aspectRatio:a.aspectRatio,originalElement:this.element,
+_proportionallyResizeElements:[],_helper:a.helper||a.ghost||a.animate?a.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&e.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(e('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),
+top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=
+this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",
+nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d<c.length;d++){var f=e.trim(c[d]),g=e('<div class="ui-resizable-handle '+("ui-resizable-"+f)+'"></div>');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor==
+String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection();
+this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){if(!a.disabled){e(this).removeClass("ui-resizable-autohide");b._handles.show()}},function(){if(!a.disabled)if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();
+var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=
+false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});
+this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff=
+{width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio:this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];
+if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize",b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=
+false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height;f=f?0:c.sizeDiff.width;f={width:c.helper.width()-f,height:c.helper.height()-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);
+c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,
+d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top=a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidth<b.width,f=l(b.height)&&a.maxHeight&&a.maxHeight<b.height,g=l(b.width)&&a.minWidth&&a.minWidth>b.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=
+a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height,k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=
+this.helper||this.element,a=0;a<this._proportionallyResizeElements.length;a++){var c=this._proportionallyResizeElements[a];if(!this.borderDif){var d=[c.css("borderTopWidth"),c.css("borderRightWidth"),c.css("borderBottomWidth"),c.css("borderLeftWidth")],f=[c.css("paddingTop"),c.css("paddingRight"),c.css("paddingBottom"),c.css("paddingLeft")];this.borderDif=e.map(d,function(g,h){g=parseInt(g,10)||0;h=parseInt(f[h],10)||0;return g+h})}e.browser.msie&&(e(b).is(":hidden")||e(b).parents(":hidden").length)||
+c.css({height:b.height()-this.borderDif[0]-this.borderDif[2]||0,width:b.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var b=this.options;this.elementOffset=this.element.offset();if(this._helper){this.helper=this.helper||e('<div style="overflow:hidden;"></div>');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-
+c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,
+arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,
+element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable,{version:"1.8.13"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};
+if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize,function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),
+p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n=(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,
+c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition=false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),
+g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),
+10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize",b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,
+top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top","Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;
+g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset,f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:
+0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));
+if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&
+!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost==
+"string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative",height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;
+var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,
+10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery);
+;

+ 11 - 0
examples/polygon_light_example/js/jquery.mousewheel.min.js

@@ -0,0 +1,11 @@
+/* Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ *
+ * Version: 3.0.2
+ * 
+ * Requires: 1.2.2+
+ */
+(function(c){var a=["DOMMouseScroll","mousewheel"];c.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var d=a.length;d;){this.addEventListener(a[--d],b,false)}}else{this.onmousewheel=b}},teardown:function(){if(this.removeEventListener){for(var d=a.length;d;){this.removeEventListener(a[--d],b,false)}}else{this.onmousewheel=null}}};c.fn.extend({mousewheel:function(d){return d?this.bind("mousewheel",d):this.trigger("mousewheel")},unmousewheel:function(d){return this.unbind("mousewheel",d)}});function b(f){var d=[].slice.call(arguments,1),g=0,e=true;f=c.event.fix(f||window.event);f.type="mousewheel";if(f.wheelDelta){g=f.wheelDelta/120}if(f.detail){g=-f.detail/3}d.unshift(f,g);return c.event.handle.apply(this,d)}})(jQuery);

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
examples/polygon_light_example/js/ltc_tables.js


+ 134 - 0
examples/polygon_light_example/js/macton/cameracontroller.js

@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *    * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A simple camera controller which uses an HTML element as the event
+// source for constructing a view matrix. Assign an "onchange"
+// function to the controller as follows to receive the updated X and
+// Y angles for the camera:
+//
+//   var controller = new CameraController(canvas);
+//   controller.onchange = function(xRot, yRot) { ... };
+//
+// The view matrix is computed elsewhere.
+//
+// opt_canvas (an HTMLCanvasElement) and opt_context (a
+// WebGLRenderingContext) can be passed in to make the hit detection
+// more precise -- only opaque pixels will be considered as the start
+// of a drag action.
+function CameraController(element, opt_canvas, opt_context) {
+    var controller = this;
+    this.onchange = null;
+    this.xRot = 0;
+    this.yRot = 0;
+    this.scaleFactor = 3.0;
+    this.dragging = false;
+    this.curX = 0;
+    this.curY = 0;
+
+    if (opt_canvas)
+        this.canvas_ = opt_canvas;
+
+    if (opt_context)
+        this.context_ = opt_context;
+
+    // Assign a mouse down handler to the HTML element.
+    element.onmousedown = function(ev) {
+    	// $(element).css("cursor", "url(images/blank.png), default");
+        $(element).css("cursor", "none");
+
+        controller.curX = ev.clientX;
+        controller.curY = ev.clientY;
+        var dragging = false;
+        if (controller.canvas_ && controller.context_) {
+            var rect = controller.canvas_.getBoundingClientRect();
+            // Transform the event's x and y coordinates into the coordinate
+            // space of the canvas
+            var canvasRelativeX = ev.pageX - rect.left;
+            var canvasRelativeY = ev.pageY - rect.top;
+            var canvasWidth = controller.canvas_.width;
+            var canvasHeight = controller.canvas_.height;
+
+            // Read back a small portion of the frame buffer around this point
+            if (canvasRelativeX > 0 && canvasRelativeX < canvasWidth &&
+                canvasRelativeY > 0 && canvasRelativeY < canvasHeight) {
+                var pixels = new Uint8Array(1);
+                controller.context_.readPixels(canvasRelativeX,
+                                               canvasHeight - canvasRelativeY,
+                                               1,
+                                               1,
+                                               controller.context_.RGBA,
+                                               controller.context_.UNSIGNED_BYTE,
+                                               pixels);
+                // See whether this pixel has an alpha value of >= about 10%
+                if (pixels[3] > (255.0 / 10.0)) {
+                    dragging = true;
+                }
+            }
+        } else {
+            dragging = true;
+        }
+
+        controller.dragging = dragging;
+    };
+
+    // Assign a mouse up handler to the HTML element.
+    element.onmouseup = function(ev) {
+    	$(element).css("cursor", "default");
+        controller.dragging = false;
+    };
+
+    // Assign a mouse move handler to the HTML element.
+    element.onmousemove = function(ev) {
+        if (controller.dragging) {
+            // Determine how far we have moved since the last mouse move
+            // event.
+            var curX = ev.clientX;
+            var curY = ev.clientY;
+            var deltaX = (controller.curX - curX) / controller.scaleFactor;
+            var deltaY = (controller.curY - curY) / controller.scaleFactor;
+            controller.curX = curX;
+            controller.curY = curY;
+            // Update the X and Y rotation angles based on the mouse motion.
+            controller.yRot = (controller.yRot + deltaX) % 360;
+            controller.xRot = (controller.xRot + deltaY);
+            // Clamp the X rotation to prevent the camera from going upside
+            // down.
+            if (controller.xRot < -90) {
+                controller.xRot = -90;
+            } else if (controller.xRot > 90) {
+                controller.xRot = 90;
+            }
+            // Send the onchange event to any listener.
+            if (controller.onchange != null) {
+                controller.onchange(controller.xRot, controller.yRot);
+            }
+        }
+    };
+}

+ 663 - 0
examples/polygon_light_example/js/macton/macton-gl-utils.js

@@ -0,0 +1,663 @@
+jQuery.glCheckError = function( gl, error_log ) 
+{
+  var error = gl.getError();
+  if (error != gl.NO_ERROR) 
+  {
+    var error_str = "GL Error: " + error + " " + gl.enum_strings[error];
+
+    if ( typeof error_log === 'function' ) 
+    {
+      error_log( error_str );
+    }
+
+    throw error_str;
+  }
+}
+
+// jQuery.glProgram = function( gl, config, complete )
+//
+// Example config:
+//
+// var g_BumpReflectProgram = 
+// {
+//   VertexProgramURL:   '/shaders/bump_reflect.vs',
+//   FragmentProgramURL: '/shaders/bump_reflect.fs',
+//   ErrorLog:           error_log,
+// };
+
+jQuery.glProgram = function( gl, config, complete )
+{
+  this.Program           = null;
+  this.UniformLocations  = { };
+  this.AttributeIndices  = [];
+  this.Uniforms          = [];
+  this.UniformTypes      = { };
+  this.Attributes        = [];
+  this.AttributeTypes    = { };
+  this.ErrorLog          = config.error_log;
+  this.BoundTextureCount = 0;
+
+  this.Use = function()
+  {
+    gl.useProgram( this.Program );
+
+    this.BoundTextureCount = 0;
+  }
+
+  this.BindModel = function( model, bindings )
+  {
+    var attribute_ndx = this.AttributeIndices;
+
+    gl.bindBuffer(gl.ARRAY_BUFFER, model.VertexBuffer );
+
+    for ( var model_stream_name in bindings )
+    {
+      var program_stream_name = bindings[ model_stream_name ];
+      var program_attribute   = this.AttributeIndices[ program_stream_name ];
+      var model_stream_offset = model.VertexStreamBufferOffsets[ model_stream_name ];
+      var model_stream_stride = model.VertexStreamBufferStrides[ model_stream_name ];
+      var model_stream_type   = model.VertexStreamBufferGLTypes[ model_stream_name ];
+
+      gl.vertexAttribPointer( program_attribute, model_stream_stride, model_stream_type, false, 0, model_stream_offset );
+      gl.enableVertexAttribArray( program_attribute );
+    }
+
+    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, model.IndexBuffer );
+
+    $.glCheckError( gl, this.ErrorLog );
+
+    this.Model = model;
+  }
+
+  this.BindUniform = function( uniform_name, value )
+  { 
+    var uniform_type = this.UniformTypes[ uniform_name ];
+ 
+    switch (uniform_type)
+    {
+      case 'mat4':
+      {
+        gl.uniformMatrix4fv( this.UniformLocations[ uniform_name ], gl.FALSE, new Float32Array( value ) );
+      }
+      break;
+
+      case 'sampler2D':
+      {
+        gl.activeTexture( gl[ 'TEXTURE' + this.BoundTextureCount ] );
+ 
+        if ( value instanceof jQuery.glTexture )
+        {
+          gl.bindTexture( gl.TEXTURE_2D, value.Texture );
+        }
+        else
+        {
+          gl.bindTexture( gl.TEXTURE_2D, value );
+        }
+
+        gl.uniform1i( this.UniformLocations[ uniform_name ], this.BoundTextureCount );
+        this.BoundTextureCount++;
+      }
+      break;
+
+      case 'samplerCube':
+      {
+        gl.activeTexture( gl[ 'TEXTURE' + this.BoundTextureCount ] );
+        gl.bindTexture( gl.TEXTURE_CUBE_MAP, value );
+        gl.uniform1i( this.UniformLocations[ uniform_name ], this.BoundTextureCount );
+        this.BoundTextureCount++;
+      }
+      break;
+    }
+  }
+
+  this.DrawModel = function()
+  {
+    var model = this.Model; // Model must be bound with BindModel first.
+
+    gl.drawElements( gl.TRIANGLES, model.IndexCount, model.IndexStreamGLType, 0 );
+  }
+
+  this.CreateBestVertexBindings = function( model )
+  {
+    var bindings = new Object();
+
+    for (var i=0;i<model.VertexStreamNames.length;i++)
+    {
+      var vertex_stream_name = model.VertexStreamNames[i];
+      var best_dist          = 1000;
+
+      for (var j=0;j<this.Attributes.length;j++)
+      {
+        var attribute_name = this.Attributes[j];
+        var dist           = vertex_stream_name.LD( attribute_name );
+        if ( dist < best_dist )
+        { 
+          bindings[ vertex_stream_name ] = attribute_name;
+          best_dist                      = dist;
+        }
+      }
+    }
+
+    return (bindings);
+  }
+
+  var vertex_shader   = null;
+  var fragment_shader = null;
+
+  var CompileShader = function( type, source )
+  {
+    var shader = gl.createShader(type);
+
+    if (shader == null) 
+    {
+      return null;
+    }
+
+    gl.shaderSource(shader, source);
+    gl.compileShader(shader);
+
+    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) 
+    {
+      var infoLog = gl.getShaderInfoLog(shader);
+      output("Error compiling shader:\n" + infoLog);
+      gl.deleteShader(shader);
+      return null;
+    }
+
+    return shader;
+  }
+
+  var TryErrorLog = function( msg )
+  {
+    if ( typeof config.ErrorLog !== 'function' ) return;
+
+    config.ErrorLog( msg );
+  }
+
+  var TryBuildProgram = function()
+  {
+    if ( vertex_shader   == null ) return;
+    if ( fragment_shader == null ) return;
+
+    this.Program = gl.createProgram();
+    if (this.Program == null) 
+    {
+      TryErrorLog("Could not create GL program");
+      return;
+    }
+
+    gl.attachShader( this.Program, vertex_shader );
+    gl.attachShader( this.Program, fragment_shader );
+
+    for (var i=0;i<this.Attributes.length;i++)
+    {
+      var attribute_name = this.Attributes[i];
+
+      this.AttributeIndices[ attribute_name ] = i;
+
+      gl.bindAttribLocation( this.Program, i, attribute_name );
+    }
+
+    gl.linkProgram( this.Program );
+
+    var linked = gl.getProgramParameter( this.Program, gl.LINK_STATUS );
+    if (!linked) 
+    {
+      var infoLog = gl.getProgramInfoLog(this.Program);
+
+      TryErrorLog("GL program link failed: " + infoLog);
+
+      gl.deleteProgram( this.Program );
+      this.Program = null;
+
+      return;
+    }
+
+    for (var i=0;i<this.Uniforms.length;i++)
+    {
+      var uniform_name = this.Uniforms[i];
+
+      this.UniformLocations[ uniform_name ] = gl.getUniformLocation( this.Program, uniform_name );
+    }
+
+    $.glCheckError( gl, config.error_log );
+
+    if ( typeof complete === 'function' ) complete( this );
+  }
+
+  var AddUniforms = function( source )
+  {
+    var uniform_match = /uniform\s+(\w+)\s+(\w+)\s*;/g
+    var uniforms      = source.match( uniform_match );
+
+    for (var i=0;i<uniforms.length;i++)
+    {
+      var uniform       = uniforms[i].split( uniform_match );
+      var uniform_type  = uniform[1];
+      var uniform_name  = uniform[2];
+  
+      this.Uniforms.push( uniform_name );
+      this.UniformTypes[ uniform_name ] = uniform_type;
+    }
+  }
+
+  var AddAttributes = function( source )
+  {
+    var attribute_match = /attribute\s+(\w+)\s+(\w+)\s*;/g
+    var attributes      = source.match( attribute_match );
+
+    for (var i=0;i<attributes.length;i++)
+    {
+      var attribute       = attributes[i].split( attribute_match );
+      var attribute_type  = attribute[1];
+      var attribute_name  = attribute[2];
+  
+      this.Attributes.push( attribute_name );
+      this.AttributeTypes[ attribute_name ] = attribute_type;
+    }
+  }
+
+  var LoadVertexShader = function( source )
+  {
+    vertex_shader = CompileShader.apply( this, [ gl.VERTEX_SHADER, source ] );
+
+    AddUniforms.apply( this, [ source ] );
+    AddAttributes.apply( this, [ source ] );
+
+    TryBuildProgram.apply( this );
+  }
+
+  var LoadFragmentShader = function( source )
+  {
+    fragment_shader = CompileShader.apply( this, [ gl.FRAGMENT_SHADER, source ] );
+   
+    AddUniforms.apply( this, [ source ] );
+    TryBuildProgram.apply( this );
+  }
+
+  var that = this;
+
+  this.get = function( url, fn )
+  {
+	var request = new XMLHttpRequest();
+	request.open( "GET", url, true );
+	request.send( null );
+	fn( request.responseText );
+  }
+
+  this.get( config.VertexProgramURL,   function( source ) { LoadVertexShader.apply( that, arguments ); } );
+  this.get( config.FragmentProgramURL, function( source ) { LoadFragmentShader.apply( that, arguments ); } );
+
+//  $.get( config.VertexProgramURL,   function( source ) { LoadVertexShader.apply( that, arguments ); } );
+//  $.get( config.FragmentProgramURL, function( source ) { LoadFragmentShader.apply( that, arguments ); } );
+}
+
+// jQuery.glModel = function( gl, model_url, complete )
+//
+// Model JSON format:
+// {
+//   VertexStreams: [
+//     {
+//       Name: "position",
+//       Type: "float",
+//       Stride: 3,
+//       Stream: [ 1.0, 1.0, 1.0, ... ]
+//     }
+//     ,...
+//   ]
+//   Indices:  
+//   {
+//     Type: "uint16_t",
+//     Stream: [ 0, 1, 2, 2, 3, 4, ... ]
+//     }
+//   }
+// }
+
+jQuery.glModel = function( gl, model_url, complete )
+{
+  this.VertexStreamNames         = [];
+  this.VertexStreamBuffers       = [];
+  this.VertexStreamBufferOffsets = [];
+  this.VertexStreamBufferStrides = [];
+  this.VertexStreamBufferGLTypes = [];
+  this.VertexBuffer              = null;
+  this.IndexStreamBuffer         = null;
+  this.IndexStreamGLType         = null;
+  this.IndexBuffer               = null;
+  this.IndexCount                = 0;
+
+  var LoadModel = function( source )
+  {
+    var CreateArrayByStdType = function( type, source )
+    {
+      if (type == 'float')
+      {
+        return new Float32Array( source );
+      }
+      else if (type == 'uint16_t')
+      {
+        return new Uint16Array( source );
+      }
+      else if (type == 'uint32_t')
+      {
+        return new Uint32Array( source );
+      }
+      else if (type == 'uint8_t')
+      {
+        return new Uint8Array( source );
+      }
+    }
+
+    var GLTypeByStdType = function( type )
+    {
+      if (type == 'float')
+      {
+        return gl.FLOAT;
+      }
+      else if (type == 'uint16_t')
+      {
+        return gl.UNSIGNED_SHORT;
+      }
+      else if (type == 'uint32_t')
+      {
+        return gl.UNSIGNED_INT;
+      }
+      else if (type == 'uint8_t')
+      {
+        return gl.UNSIGNED_BYTE;
+      }
+    }
+
+    var vertex_stream_count = source.VertexStreams.length;
+    var vertex_buffer_size  = 0;
+
+    for (var i=0;i<vertex_stream_count;i++)
+    {
+      var stream_source  = source.VertexStreams[i];
+      var stream_name    = stream_source.Name;
+      var stream_buffer  = CreateArrayByStdType( stream_source.Type, stream_source.Stream );
+      var stream_stride  = stream_source.Stride;
+      var stream_type    = stream_source.Type;
+      var stream_gl_type = GLTypeByStdType( stream_type );
+
+      this.VertexStreamBuffers[i]                   = stream_buffer;
+      this.VertexStreamNames[i]                     = stream_name;
+      this.VertexStreamBufferOffsets[ stream_name ] = vertex_buffer_size;
+      this.VertexStreamBufferStrides[ stream_name ] = stream_stride;
+      this.VertexStreamBufferGLTypes[ stream_name ] = stream_gl_type;
+
+      if ( stream_buffer )
+      {
+        vertex_buffer_size += stream_buffer.byteLength;
+      }
+    }  
+
+    this.VertexBuffer = gl.createBuffer();
+
+    gl.bindBuffer( gl.ARRAY_BUFFER, this.VertexBuffer );
+    gl.bufferData( gl.ARRAY_BUFFER, vertex_buffer_size, gl.STATIC_DRAW );
+
+    for (var i=0;i<vertex_stream_count;i++)
+    {
+      var stream_source = source.VertexStreams[i];
+      var stream_name   = stream_source.Name;
+
+      gl.bufferSubData( gl.ARRAY_BUFFER, this.VertexStreamBufferOffsets[ stream_name ], this.VertexStreamBuffers[i] );
+    }
+
+    this.IndexBuffer       = gl.createBuffer();
+    this.IndexStreamBuffer = CreateArrayByStdType( source.Indices.Type, source.Indices.Stream );
+    this.IndexStreamGLType = GLTypeByStdType( source.Indices.Type );
+    this.IndexCount        = source.Indices.Stream.length;
+
+    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.IndexBuffer );
+    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, this.IndexStreamBuffer, gl.STATIC_DRAW );
+
+    if ( typeof complete === 'function' ) complete( this );
+  }
+
+  var that = this;
+
+	$.ajax({
+		url: model_url,
+		dataType: 'json',
+		mimeType: 'application/json',
+		success: function( source ) { LoadModel.apply( that, arguments ); }
+	});
+
+  //$.getJSON( model_url, function( source ) { LoadModel.apply( that, arguments ); } );
+}
+
+
+jQuery.glModel2 = function( gl, model, complete )
+{
+  this.VertexStreamNames         = [];
+  this.VertexStreamBuffers       = [];
+  this.VertexStreamBufferOffsets = [];
+  this.VertexStreamBufferStrides = [];
+  this.VertexStreamBufferGLTypes = [];
+  this.VertexBuffer              = null;
+  this.IndexStreamBuffer         = null;
+  this.IndexStreamGLType         = null;
+  this.IndexBuffer               = null;
+  this.IndexCount                = 0;
+
+  var LoadModel = function( source )
+  {
+    var CreateArrayByStdType = function( type, source )
+    {
+      if (type == 'float')
+      {
+        return new Float32Array( source );
+      }
+      else if (type == 'uint16_t')
+      {
+        return new Uint16Array( source );
+      }
+      else if (type == 'uint32_t')
+      {
+        return new Uint32Array( source );
+      }
+      else if (type == 'uint8_t')
+      {
+        return new Uint8Array( source );
+      }
+    }
+
+    var GLTypeByStdType = function( type )
+    {
+      if (type == 'float')
+      {
+        return gl.FLOAT;
+      }
+      else if (type == 'uint16_t')
+      {
+        return gl.UNSIGNED_SHORT;
+      }
+      else if (type == 'uint32_t')
+      {
+        return gl.UNSIGNED_INT;
+      }
+      else if (type == 'uint8_t')
+      {
+        return gl.UNSIGNED_BYTE;
+      }
+    }
+
+    var vertex_stream_count = source.VertexStreams.length;
+    var vertex_buffer_size  = 0;
+
+    for (var i=0;i<vertex_stream_count;i++)
+    {
+      var stream_source  = source.VertexStreams[i];
+      var stream_name    = stream_source.Name;
+      var stream_buffer  = CreateArrayByStdType( stream_source.Type, stream_source.Stream );
+      var stream_stride  = stream_source.Stride;
+      var stream_type    = stream_source.Type;
+      var stream_gl_type = GLTypeByStdType( stream_type );
+
+      this.VertexStreamBuffers[i]                   = stream_buffer;
+      this.VertexStreamNames[i]                     = stream_name;
+      this.VertexStreamBufferOffsets[ stream_name ] = vertex_buffer_size;
+      this.VertexStreamBufferStrides[ stream_name ] = stream_stride;
+      this.VertexStreamBufferGLTypes[ stream_name ] = stream_gl_type;
+
+      if ( stream_buffer )
+      {
+        vertex_buffer_size += stream_buffer.byteLength;
+      }
+    }  
+
+    this.VertexBuffer = gl.createBuffer();
+
+    gl.bindBuffer( gl.ARRAY_BUFFER, this.VertexBuffer );
+    gl.bufferData( gl.ARRAY_BUFFER, vertex_buffer_size, gl.STATIC_DRAW );
+
+    for (var i=0;i<vertex_stream_count;i++)
+    {
+      var stream_source = source.VertexStreams[i];
+      var stream_name   = stream_source.Name;
+
+      gl.bufferSubData( gl.ARRAY_BUFFER, this.VertexStreamBufferOffsets[ stream_name ], this.VertexStreamBuffers[i] );
+    }
+
+    this.IndexBuffer       = gl.createBuffer();
+    this.IndexStreamBuffer = CreateArrayByStdType( source.Indices.Type, source.Indices.Stream );
+    this.IndexStreamGLType = GLTypeByStdType( source.Indices.Type );
+    this.IndexCount        = source.Indices.Stream.length;
+
+    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.IndexBuffer );
+    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, this.IndexStreamBuffer, gl.STATIC_DRAW );
+
+    if ( typeof complete === 'function' ) complete( this );
+  }
+
+  var that = this;
+/*
+	$.ajax({
+		url: model_url,
+		dataType: 'json',
+		mimeType: 'application/json',
+		success: function( source ) { LoadModel.apply( that, arguments ); }
+	});
+*/
+  LoadModel.apply( this, [ model ] );
+}
+
+
+// jQuery.glTexture = function( gl, config, complete )
+// 
+// Example config:
+// 
+//   var g_BumpTextureConfig =
+//   {
+//     Type:      'TEXTURE_2D',
+//     ImageURL:  './images/bump.jpg',
+//     TexParameters: 
+//     {
+//       TEXTURE_MIN_FILTER: 'LINEAR',
+//       TEXTURE_MAG_FILTER: 'LINEAR',
+//       TEXTURE_WRAP_S:     'REPEAT',
+//       TEXTURE_WRAP_T:     'REPEAT'
+//     },
+//     PixelStoreParameters:
+//     {
+//       UNPACK_FLIP_Y_WEBGL: true
+//     }
+//   };
+
+jQuery.glTexture = function( gl, config, complete )
+{
+  this.Texture  = gl.createTexture();
+  this.ErrorLog = config.ErrorLog;
+
+  var texture_type = gl[ config.Type ];
+  var images_remaining;
+  var that = this;
+
+  if ( this.Texture == null )
+  {
+    return null;
+  }
+
+  gl.bindTexture( texture_type, this.Texture );
+
+  for ( var pname in config.TexParameters )
+  {
+    var pvalue          = config.TexParameters[ pname ];
+    var parameter_name  = gl[ pname ]; 
+    var parameter_value = gl[ pvalue ];
+
+    gl.texParameteri( texture_type, parameter_name, parameter_value );
+    $.glCheckError( gl, this.ErrorLog );
+  }
+
+  var ImageLoaded = function( image, image_type, mip )
+  {
+    gl.bindTexture( texture_type, this.Texture );
+    $.glCheckError( gl, this.ErrorLog );
+
+    for ( var pname in config.PixelStoreParameters )
+    {
+      var parameter_name  = gl[ pname ];
+      var parameter_value = config.PixelStoreParameters[ pname ];
+
+      gl.pixelStorei( parameter_name, parameter_value );
+      $.glCheckError( gl, this.ErrorLog );
+    }
+
+    gl.texImage2D( image_type, mip, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
+
+    $.glCheckError( gl, this.ErrorLog );
+
+	if ( texture_type === gl.TEXTURE_CUBE_MAP && image_type === gl.TEXTURE_CUBE_MAP_NEGATIVE_Z )
+		gl.generateMipmap( texture_type );
+
+	if ( texture_type === gl.TEXTURE_2D && config.MipURL == undefined )
+		gl.generateMipmap( texture_type );
+
+    $.glCheckError( gl, this.ErrorLog );
+
+    images_remaining--;
+
+    if ( images_remaining == 0 ) 
+    {
+    	if ( typeof complete === 'function' ) complete( this );
+    }
+  }
+
+  var LoadImage = function( src, image_type, mip )
+  {
+    var image        = new Image();
+    image.onload     = function() { ImageLoaded.apply( that, [ image, image_type, mip ] ); };
+    image.src        = src;
+  }
+
+  if ( typeof config.ImageURL == 'string' )
+  {
+    images_remaining = 1;
+    
+    if ( config.MipURL !== undefined )
+    {
+    	images_remaining += config.MipURL.length;
+    }
+    
+    LoadImage( config.ImageURL, gl[ config.Type ], 0 );
+
+	if ( config.MipURL !== undefined )
+	{
+		for (var i=0;i<config.MipURL.length;i++)
+		{
+		  LoadImage( config.MipURL[i], gl[ config.Type ], i + 1 );
+		}
+	}
+  }
+  else
+  {
+    images_remaining = config.ImageURL.length;
+    for (var i=0;i<config.ImageURL.length;i++)
+    {
+      LoadImage( config.ImageURL[i], gl[ config.ImageType[i] ], 0 );
+    }
+  }
+}

+ 142 - 0
examples/polygon_light_example/js/macton/macton-utils.js

@@ -0,0 +1,142 @@
+jQuery.getFiles = function( file_list, success )
+{
+  var results    = [];
+  var file_count = file_list.length;
+
+  var hold_file = function( ndx, data )
+  {
+    results[ndx] = data;
+    file_count--;
+    if ( file_count == 0 ) success( results );
+  }    
+
+  var get_file = function( ndx, url )
+  {
+    $.get( url, function( data ) { hold_file( ndx, data ); } );
+  }
+
+  for (var i=0;i<file_count;i++)
+  {
+    get_file( i, file_list[i] );
+  } 
+}
+
+jQuery.TriggerEvent = function( event_name, event_parameters_array )
+{
+  $(document).trigger( event_name, event_parameters_array );
+}
+
+jQuery.BindEvent = function( event_name, callback_function )
+{
+  $(document).bind( event_name, callback_function );
+}
+
+jQuery.UnbindEvent = function( event_name )
+{
+  $(document).unbind( event_name );
+}
+
+function fieldCount( obj )
+{
+  var field_count = 0;
+  for (k in obj) 
+  {
+    if (obj.hasOwnProperty(k))
+    {
+      field_count++;
+    }
+  }
+  return (field_count);
+}
+
+// LD - Lukasz Stilger (javascript version) http://www.mgilleland.com/ld/ldjavascript.htm
+// Levenshtein Distance http://www.merriampark.com/ld.htm 
+
+function stringLD(t) 
+{
+  var s  = this;
+  var dG = new Array();
+  
+  function Minimum(a, b, c) 
+  {
+    var mi;
+    mi = a;
+    if (b < mi)
+      mi = b;
+    if (c < mi)
+      mi = c;
+    return mi;
+  }
+ 
+  var d = new Array();
+  var n; // length of s
+  var m; // length of t
+  var i; // iterates through s
+  var j; // iterates through t
+  var s_i; // ith character of s
+  var t_j; // jth character of t
+  var cost; // cost
+ 
+ 
+  // Step 1
+  n = s.length;
+  m = t.length;
+  if (n == 0) {
+    return m;
+  }
+ 
+  if (m == 0) {
+    return n;
+  }
+  
+  //inicjacja tablicy dwu-wymiarowej w Javascript  
+  for(i=0; i<=n; i++)
+    d[i] = new Array();
+ 
+ 
+  // Step 2
+  for (i = 0; i <= n; i++) {
+    d[i][0] = i;
+  }
+ 
+  for (j = 0; j <= m; j++) {
+    d[0][j] = j;
+  }
+ 
+  // Step 3
+  for (i = 1; i <= n; i++) {
+ 
+    s_i = s.charAt(i - 1);
+ 
+    
+    // Step 4
+    for (j = 1; j <= m; j++) {
+ 
+      t_j = t.charAt(j - 1);
+ 
+      // Step 5
+      if (s_i == t_j) {
+        cost = 0;
+      }
+      else {
+        cost = 1;
+      }
+ 
+      // Step 6
+      d[i][j] = Minimum (d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost);
+    }
+ 
+  }
+  
+  //przepisanie do tablicy globalnej
+  for(i=1; i<=n; i++) {
+    dG[i] = new Array();
+    for(j=1; j<=m; j++)
+      dG[i][j] = d[i][j];
+  }
+ 
+  // Step 7
+  return d[n][m];
+}
+
+String.prototype.LD = stringLD;

+ 373 - 0
examples/polygon_light_example/js/macton/matrix4x4.js

@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2009, Mozilla Corp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Based on sample code from the OpenGL(R) ES 2.0 Programming Guide, which carriers
+ * the following header:
+ *
+ * Book:      OpenGL(R) ES 2.0 Programming Guide
+ * Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
+ * ISBN-10:   0321502795
+ * ISBN-13:   9780321502797
+ * Publisher: Addison-Wesley Professional
+ * URLs:      http://safari.informit.com/9780321563835
+ *            http://www.opengles-book.com
+ */
+
+//
+// A simple 4x4 Matrix utility class
+//
+
+function Matrix4x4() {
+  this.elements = Array(16);
+  this.loadIdentity();
+}
+
+Matrix4x4.prototype = {
+  scale: function (sx, sy, sz) {
+    this.elements[0*4+0] *= sx;
+    this.elements[0*4+1] *= sx;
+    this.elements[0*4+2] *= sx;
+    this.elements[0*4+3] *= sx;
+
+    this.elements[1*4+0] *= sy;
+    this.elements[1*4+1] *= sy;
+    this.elements[1*4+2] *= sy;
+    this.elements[1*4+3] *= sy;
+
+    this.elements[2*4+0] *= sz;
+    this.elements[2*4+1] *= sz;
+    this.elements[2*4+2] *= sz;
+    this.elements[2*4+3] *= sz;
+
+    return this;
+  },
+
+  translate: function (tx, ty, tz) {
+    this.elements[3*4+0] += this.elements[0*4+0] * tx + this.elements[1*4+0] * ty + this.elements[2*4+0] * tz;
+    this.elements[3*4+1] += this.elements[0*4+1] * tx + this.elements[1*4+1] * ty + this.elements[2*4+1] * tz;
+    this.elements[3*4+2] += this.elements[0*4+2] * tx + this.elements[1*4+2] * ty + this.elements[2*4+2] * tz;
+    this.elements[3*4+3] += this.elements[0*4+3] * tx + this.elements[1*4+3] * ty + this.elements[2*4+3] * tz;
+
+    return this;
+  },
+
+  rotate: function (angle, x, y, z) {
+    var mag = Math.sqrt(x*x + y*y + z*z);
+    var sinAngle = Math.sin(angle * Math.PI / 180.0);
+    var cosAngle = Math.cos(angle * Math.PI / 180.0);
+
+    if (mag > 0) {
+      var xx, yy, zz, xy, yz, zx, xs, ys, zs;
+      var oneMinusCos;
+      var rotMat;
+
+      x /= mag;
+      y /= mag;
+      z /= mag;
+
+      xx = x * x;
+      yy = y * y;
+      zz = z * z;
+      xy = x * y;
+      yz = y * z;
+      zx = z * x;
+      xs = x * sinAngle;
+      ys = y * sinAngle;
+      zs = z * sinAngle;
+      oneMinusCos = 1.0 - cosAngle;
+
+      rotMat = new Matrix4x4();
+
+      rotMat.elements[0*4+0] = (oneMinusCos * xx) + cosAngle;
+      rotMat.elements[0*4+1] = (oneMinusCos * xy) - zs;
+      rotMat.elements[0*4+2] = (oneMinusCos * zx) + ys;
+      rotMat.elements[0*4+3] = 0.0;
+
+      rotMat.elements[1*4+0] = (oneMinusCos * xy) + zs;
+      rotMat.elements[1*4+1] = (oneMinusCos * yy) + cosAngle;
+      rotMat.elements[1*4+2] = (oneMinusCos * yz) - xs;
+      rotMat.elements[1*4+3] = 0.0;
+
+      rotMat.elements[2*4+0] = (oneMinusCos * zx) - ys;
+      rotMat.elements[2*4+1] = (oneMinusCos * yz) + xs;
+      rotMat.elements[2*4+2] = (oneMinusCos * zz) + cosAngle;
+      rotMat.elements[2*4+3] = 0.0;
+
+      rotMat.elements[3*4+0] = 0.0;
+      rotMat.elements[3*4+1] = 0.0;
+      rotMat.elements[3*4+2] = 0.0;
+      rotMat.elements[3*4+3] = 1.0;
+
+      rotMat = rotMat.multiply(this);
+      this.elements = rotMat.elements;
+    }
+
+    return this;
+  },
+
+  frustum: function (left, right, bottom, top, nearZ, farZ) {
+    var deltaX = right - left;
+    var deltaY = top - bottom;
+    var deltaZ = farZ - nearZ;
+    var frust;
+
+    if ( (nearZ <= 0.0) || (farZ <= 0.0) ||
+         (deltaX <= 0.0) || (deltaY <= 0.0) || (deltaZ <= 0.0) )
+         return this;
+
+    frust = new Matrix4x4();
+
+    frust.elements[0*4+0] = 2.0 * nearZ / deltaX;
+    frust.elements[0*4+1] = frust.elements[0*4+2] = frust.elements[0*4+3] = 0.0;
+
+    frust.elements[1*4+1] = 2.0 * nearZ / deltaY;
+    frust.elements[1*4+0] = frust.elements[1*4+2] = frust.elements[1*4+3] = 0.0;
+
+    frust.elements[2*4+0] = (right + left) / deltaX;
+    frust.elements[2*4+1] = (top + bottom) / deltaY;
+    frust.elements[2*4+2] = -(nearZ + farZ) / deltaZ;
+    frust.elements[2*4+3] = -1.0;
+
+    frust.elements[3*4+2] = -2.0 * nearZ * farZ / deltaZ;
+    frust.elements[3*4+0] = frust.elements[3*4+1] = frust.elements[3*4+3] = 0.0;
+
+    frust = frust.multiply(this);
+    this.elements = frust.elements;
+
+    return this;
+  },
+
+  perspective: function (fovy, aspect, nearZ, farZ) {
+    var frustumH = Math.tan(fovy / 360.0 * Math.PI) * nearZ;
+    var frustumW = frustumH * aspect;
+
+    return this.frustum(-frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
+  },
+
+  ortho: function (left, right, bottom, top, nearZ, farZ) {
+    var deltaX = right - left;
+    var deltaY = top - bottom;
+    var deltaZ = farZ - nearZ;
+
+    var ortho = new Matrix4x4();
+
+    if ( (deltaX == 0.0) || (deltaY == 0.0) || (deltaZ == 0.0) )
+        return this;
+
+    ortho.elements[0*4+0] = 2.0 / deltaX;
+    ortho.elements[3*4+0] = -(right + left) / deltaX;
+    ortho.elements[1*4+1] = 2.0 / deltaY;
+    ortho.elements[3*4+1] = -(top + bottom) / deltaY;
+    ortho.elements[2*4+2] = -2.0 / deltaZ;
+    ortho.elements[3*4+2] = -(nearZ + farZ) / deltaZ;
+
+    ortho = ortho.multiply(this);
+    this.elements = ortho.elements;
+
+    return this;
+  },
+
+  multiply: function (right) {
+    var tmp = new Matrix4x4();
+
+    for (var i = 0; i < 4; i++) {
+      tmp.elements[i*4+0] =
+	(this.elements[i*4+0] * right.elements[0*4+0]) +
+	(this.elements[i*4+1] * right.elements[1*4+0]) +
+	(this.elements[i*4+2] * right.elements[2*4+0]) +
+	(this.elements[i*4+3] * right.elements[3*4+0]) ;
+
+      tmp.elements[i*4+1] =
+	(this.elements[i*4+0] * right.elements[0*4+1]) +
+	(this.elements[i*4+1] * right.elements[1*4+1]) +
+	(this.elements[i*4+2] * right.elements[2*4+1]) +
+	(this.elements[i*4+3] * right.elements[3*4+1]) ;
+
+      tmp.elements[i*4+2] =
+	(this.elements[i*4+0] * right.elements[0*4+2]) +
+	(this.elements[i*4+1] * right.elements[1*4+2]) +
+	(this.elements[i*4+2] * right.elements[2*4+2]) +
+	(this.elements[i*4+3] * right.elements[3*4+2]) ;
+
+      tmp.elements[i*4+3] =
+	(this.elements[i*4+0] * right.elements[0*4+3]) +
+	(this.elements[i*4+1] * right.elements[1*4+3]) +
+	(this.elements[i*4+2] * right.elements[2*4+3]) +
+	(this.elements[i*4+3] * right.elements[3*4+3]) ;
+    }
+
+    this.elements = tmp.elements;
+    return this;
+  },
+
+  copy: function () {
+    var tmp = new Matrix4x4();
+    for (var i = 0; i < 16; i++) {
+      tmp.elements[i] = this.elements[i];
+    }
+    return tmp;
+  },
+
+  get: function (row, col) {
+    return this.elements[4*row+col];
+  },
+
+  // In-place inversion
+  invert: function () {
+    var tmp_0 = this.get(2,2) * this.get(3,3);
+    var tmp_1 = this.get(3,2) * this.get(2,3);
+    var tmp_2 = this.get(1,2) * this.get(3,3);
+    var tmp_3 = this.get(3,2) * this.get(1,3);
+    var tmp_4 = this.get(1,2) * this.get(2,3);
+    var tmp_5 = this.get(2,2) * this.get(1,3);
+    var tmp_6 = this.get(0,2) * this.get(3,3);
+    var tmp_7 = this.get(3,2) * this.get(0,3);
+    var tmp_8 = this.get(0,2) * this.get(2,3);
+    var tmp_9 = this.get(2,2) * this.get(0,3);
+    var tmp_10 = this.get(0,2) * this.get(1,3);
+    var tmp_11 = this.get(1,2) * this.get(0,3);
+    var tmp_12 = this.get(2,0) * this.get(3,1);
+    var tmp_13 = this.get(3,0) * this.get(2,1);
+    var tmp_14 = this.get(1,0) * this.get(3,1);
+    var tmp_15 = this.get(3,0) * this.get(1,1);
+    var tmp_16 = this.get(1,0) * this.get(2,1);
+    var tmp_17 = this.get(2,0) * this.get(1,1);
+    var tmp_18 = this.get(0,0) * this.get(3,1);
+    var tmp_19 = this.get(3,0) * this.get(0,1);
+    var tmp_20 = this.get(0,0) * this.get(2,1);
+    var tmp_21 = this.get(2,0) * this.get(0,1);
+    var tmp_22 = this.get(0,0) * this.get(1,1);
+    var tmp_23 = this.get(1,0) * this.get(0,1);
+
+    var t0 = ((tmp_0 * this.get(1,1) + tmp_3 * this.get(2,1) + tmp_4 * this.get(3,1)) -
+              (tmp_1 * this.get(1,1) + tmp_2 * this.get(2,1) + tmp_5 * this.get(3,1)));
+    var t1 = ((tmp_1 * this.get(0,1) + tmp_6 * this.get(2,1) + tmp_9 * this.get(3,1)) -
+              (tmp_0 * this.get(0,1) + tmp_7 * this.get(2,1) + tmp_8 * this.get(3,1)));
+    var t2 = ((tmp_2 * this.get(0,1) + tmp_7 * this.get(1,1) + tmp_10 * this.get(3,1)) -
+              (tmp_3 * this.get(0,1) + tmp_6 * this.get(1,1) + tmp_11 * this.get(3,1)));
+    var t3 = ((tmp_5 * this.get(0,1) + tmp_8 * this.get(1,1) + tmp_11 * this.get(2,1)) -
+              (tmp_4 * this.get(0,1) + tmp_9 * this.get(1,1) + tmp_10 * this.get(2,1)));
+
+    var d = 1.0 / (this.get(0,0) * t0 + this.get(1,0) * t1 + this.get(2,0) * t2 + this.get(3,0) * t3);
+
+    var out_00 = d * t0;
+    var out_01 = d * t1;
+    var out_02 = d * t2;
+    var out_03 = d * t3;
+
+    var out_10 = d * ((tmp_1 * this.get(1,0) + tmp_2 * this.get(2,0) + tmp_5 * this.get(3,0)) -
+                      (tmp_0 * this.get(1,0) + tmp_3 * this.get(2,0) + tmp_4 * this.get(3,0)));
+    var out_11 = d * ((tmp_0 * this.get(0,0) + tmp_7 * this.get(2,0) + tmp_8 * this.get(3,0)) -
+                      (tmp_1 * this.get(0,0) + tmp_6 * this.get(2,0) + tmp_9 * this.get(3,0)));
+    var out_12 = d * ((tmp_3 * this.get(0,0) + tmp_6 * this.get(1,0) + tmp_11 * this.get(3,0)) -
+                      (tmp_2 * this.get(0,0) + tmp_7 * this.get(1,0) + tmp_10 * this.get(3,0)));
+    var out_13 = d * ((tmp_4 * this.get(0,0) + tmp_9 * this.get(1,0) + tmp_10 * this.get(2,0)) -
+                      (tmp_5 * this.get(0,0) + tmp_8 * this.get(1,0) + tmp_11 * this.get(2,0)));
+
+    var out_20 = d * ((tmp_12 * this.get(1,3) + tmp_15 * this.get(2,3) + tmp_16 * this.get(3,3)) -
+                      (tmp_13 * this.get(1,3) + tmp_14 * this.get(2,3) + tmp_17 * this.get(3,3)));
+    var out_21 = d * ((tmp_13 * this.get(0,3) + tmp_18 * this.get(2,3) + tmp_21 * this.get(3,3)) -
+                      (tmp_12 * this.get(0,3) + tmp_19 * this.get(2,3) + tmp_20 * this.get(3,3)));
+    var out_22 = d * ((tmp_14 * this.get(0,3) + tmp_19 * this.get(1,3) + tmp_22 * this.get(3,3)) -
+                      (tmp_15 * this.get(0,3) + tmp_18 * this.get(1,3) + tmp_23 * this.get(3,3)));
+    var out_23 = d * ((tmp_17 * this.get(0,3) + tmp_20 * this.get(1,3) + tmp_23 * this.get(2,3)) -
+                      (tmp_16 * this.get(0,3) + tmp_21 * this.get(1,3) + tmp_22 * this.get(2,3)));
+    
+    var out_30 = d * ((tmp_14 * this.get(2,2) + tmp_17 * this.get(3,2) + tmp_13 * this.get(1,2)) -
+                      (tmp_16 * this.get(3,2) + tmp_12 * this.get(1,2) + tmp_15 * this.get(2,2)));
+    var out_31 = d * ((tmp_20 * this.get(3,2) + tmp_12 * this.get(0,2) + tmp_19 * this.get(2,2)) -
+                      (tmp_18 * this.get(2,2) + tmp_21 * this.get(3,2) + tmp_13 * this.get(0,2)));
+    var out_32 = d * ((tmp_18 * this.get(1,2) + tmp_23 * this.get(3,2) + tmp_15 * this.get(0,2)) -
+                      (tmp_22 * this.get(3,2) + tmp_14 * this.get(0,2) + tmp_19 * this.get(1,2)));
+    var out_33 = d * ((tmp_22 * this.get(2,2) + tmp_16 * this.get(0,2) + tmp_21 * this.get(1,2)) -
+                      (tmp_20 * this.get(1,2) + tmp_23 * this.get(2,2) + tmp_17 * this.get(0,2)));
+
+    this.elements[0*4+0] = out_00;
+    this.elements[0*4+1] = out_01;
+    this.elements[0*4+2] = out_02;
+    this.elements[0*4+3] = out_03;
+    this.elements[1*4+0] = out_10;
+    this.elements[1*4+1] = out_11;
+    this.elements[1*4+2] = out_12;
+    this.elements[1*4+3] = out_13;
+    this.elements[2*4+0] = out_20;
+    this.elements[2*4+1] = out_21;
+    this.elements[2*4+2] = out_22;
+    this.elements[2*4+3] = out_23;
+    this.elements[3*4+0] = out_30;
+    this.elements[3*4+1] = out_31;
+    this.elements[3*4+2] = out_32;
+    this.elements[3*4+3] = out_33;
+    return this;
+  },
+
+  // Returns new matrix which is the inverse of this
+  inverse: function () {
+    var tmp = this.copy();
+    return tmp.invert();
+  },
+  
+  // In-place transpose
+  transpose: function () {
+    var tmp = this.elements[0*4+1];
+    this.elements[0*4+1] = this.elements[1*4+0];
+    this.elements[1*4+0] = tmp;
+
+    tmp = this.elements[0*4+2];
+    this.elements[0*4+2] = this.elements[2*4+0];
+    this.elements[2*4+0] = tmp;
+
+    tmp = this.elements[0*4+3];
+    this.elements[0*4+3] = this.elements[3*4+0];
+    this.elements[3*4+0] = tmp;
+
+    tmp = this.elements[1*4+2];
+    this.elements[1*4+2] = this.elements[2*4+1];
+    this.elements[2*4+1] = tmp;
+
+    tmp = this.elements[1*4+3];
+    this.elements[1*4+3] = this.elements[3*4+1];
+    this.elements[3*4+1] = tmp;
+
+    tmp = this.elements[2*4+3];
+    this.elements[2*4+3] = this.elements[3*4+2];
+    this.elements[3*4+2] = tmp;
+
+    return this;
+  },
+
+  loadIdentity: function () {
+    for (var i = 0; i < 16; i++)
+      this.elements[i] = 0;
+    this.elements[0*4+0] = 1.0;
+    this.elements[1*4+1] = 1.0;
+    this.elements[2*4+2] = 1.0;
+    this.elements[3*4+3] = 1.0;
+    return this;
+  }
+};

+ 164 - 0
examples/polygon_light_example/js/macton/webgl-utils.js

@@ -0,0 +1,164 @@
+/*
+ * Copyright 2010, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/**
+ * @fileoverview This file contains functions every webgl program will need
+ * a version of one way or another.
+ *
+ * Instead of setting up a context manually it is recommended to
+ * use. This will check for success or failure. On failure it
+ * will attempt to present an approriate message to the user.
+ *
+ *       gl = WebGLUtils.setupWebGL(canvas);
+ *
+ * For animated WebGL apps use of setTimeout or setInterval are
+ * discouraged. It is recommended you structure your rendering
+ * loop like this.
+ *
+ *       function render() {
+ *         window.requestAnimFrame(render, canvas);
+ *
+ *         // do rendering
+ *         ...
+ *       }
+ *       render();
+ *
+ * This will call your rendering function up to the refresh rate
+ * of your display but will stop rendering if your app is not
+ * visible.
+ */
+
+WebGLUtils = function() {
+
+/**
+ * Creates the HTLM for a failure message
+ * @param {string} canvasContainerId id of container of th
+ *        canvas.
+ * @return {string} The html.
+ */
+var makeFailHTML = function(msg) {
+  return '' +
+    '<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' +
+    '<td align="center">' +
+    '<div style="display: table-cell; vertical-align: middle;">' +
+    '<div style="">' + msg + '</div>' +
+    '</div>' +
+    '</td></tr></table>';
+};
+
+/**
+ * Mesasge for getting a webgl browser
+ * @type {string}
+ */
+var GET_A_WEBGL_BROWSER = '' +
+  'This page requires a browser that supports WebGL.<br/>' +
+  '<a href="http://get.webgl.org">Click here to upgrade your browser.</a>';
+
+/**
+ * Mesasge for need better hardware
+ * @type {string}
+ */
+var OTHER_PROBLEM = '' +
+  "It doesn't appear your computer can support WebGL.<br/>" +
+  '<a href="http://get.webgl.org/troubleshooting/">Click here for more information.</a>';
+
+/**
+ * Creates a webgl context. If creation fails it will
+ * change the contents of the container of the <canvas>
+ * tag to an error message with the correct links for WebGL.
+ * @param {Element} canvas. The canvas element to create a
+ *     context from.
+ * @param {WebGLContextCreationAttirbutes} opt_attribs Any
+ *     creation attributes you want to pass in.
+ * @return {WebGLRenderingContext} The created context.
+ */
+var setupWebGL = function(canvas, opt_attribs) {
+  function showLink(str) {
+    var container = canvas.parentNode;
+    if (container) {
+      container.innerHTML = makeFailHTML(str);
+    }
+  };
+
+  if (!window.WebGLRenderingContext) {
+    showLink(GET_A_WEBGL_BROWSER);
+    return null;
+  }
+
+  var context = create3DContext(canvas, opt_attribs);
+  if (!context) {
+    showLink(OTHER_PROBLEM);
+  }
+  return context;
+};
+
+/**
+ * Creates a webgl context.
+ * @param {!Canvas} canvas The canvas tag to get context
+ *     from. If one is not passed in one will be created.
+ * @return {!WebGLContext} The created context.
+ */
+var create3DContext = function(canvas, opt_attribs) {
+  var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
+  var context = null;
+  for (var ii = 0; ii < names.length; ++ii) {
+    try {
+      context = canvas.getContext(names[ii], opt_attribs);
+    } catch(e) {}
+    if (context) {
+      break;
+    }
+  }
+  return context;
+}
+
+return {
+  create3DContext: create3DContext,
+  setupWebGL: setupWebGL
+};
+}();
+
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ */
+window.requestAnimFrame = (function() {
+  return window.requestAnimationFrame ||
+         window.webkitRequestAnimationFrame ||
+         window.mozRequestAnimationFrame ||
+         window.oRequestAnimationFrame ||
+         window.msRequestAnimationFrame ||
+         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+           window.setTimeout(callback, 1000/60);
+         };
+})();
+
+

+ 163 - 0
examples/polygon_light_example/js/super_picker.js

@@ -0,0 +1,163 @@
+
+function SuperPicker(id, opts) {
+	this.id = id;
+	this.init(opts);
+	
+	return this.picker;
+}
+
+SuperPicker.prototype = {
+	
+	init: function (opts) {
+		var self = this,
+			opt,
+			style;
+	
+		this.pick = null;
+	
+		this.options =  { 
+			picker_class   : 'sp_picker', // picker class name
+			swatch_class   : 'sp_swatch', // swatch class name
+			label_class    : 'sp_label',  // label class name
+			default_value  : { r: 255, g: 255, b: 255 },
+			value          : { r: 255, g: 255, b: 255 },
+			label          : null,        // label
+			callback       : undefined
+		};
+	
+		for (opt in opts) {
+			if (opts.hasOwnProperty.call(opts, opt)) {
+				this.options[opt] = opts[opt];
+			}
+		}
+
+		// Build picker
+		this.picker = $("<div>")
+			.addClass(this.options.picker_class)
+			.css("webkitUserSelect", 'none');
+
+		if (this.options.label) {
+			this.label = $("<label>")
+				.text(this.options.label)
+				.addClass(this.options.label_class)
+				.attr("title", "Click to reset to default");
+			this.picker.append(this.label.get(0));
+		}
+		
+		var setBackgroundColor = function(element, r, g, b) {
+			self.r = r;
+			self.g = g;
+			self.b = b;		
+			element.css("background", "rgba(" + r + ", " + g + ", " + b + ", 1)");
+		}
+
+		this.swatch = $("<div>")
+			.addClass(this.options.swatch_class)
+			.css("webkitUserSelect", 'none');
+
+		var v = this.options.value;
+		var r = Math.round(v.r*255);
+		var g = Math.round(v.g*255);
+		var b = Math.round(v.b*255);
+		setBackgroundColor(this.swatch, r, g, b);
+
+		this.picker.append(this.swatch.get(0));
+
+		function newPicker(fade) {
+			var that = this;
+			var swatch = self.swatch;
+			var callback = self.options.callback;
+			var hsv = ColorXXX.rgb2hsv(self.r, self.g, self.b);
+
+			var picker = new ColorXXX.Picker({
+				hue: hsv[0],
+				sat: hsv[1],
+				val: hsv[2],
+				fade: fade,
+				callback: function(rgb) {
+					setBackgroundColor(self.swatch, rgb.R, rgb.G, rgb.B);
+					var c = new Color(rgb.R/255, rgb.G/255, rgb.B/255);
+					callback(c);
+				}
+			});
+
+			picker.el.style.top  = "0px";
+			picker.el.style.left = "40px";
+			
+			self.swatch.append(picker.el);
+
+			return picker;
+		}
+
+
+		// Define event listeners
+
+		this.labelClick = function (event) {
+			self.label.trigger("reset");
+
+			var v = self.options.default_value;
+			var r = Math.round(v.r*255);
+			var g = Math.round(v.g*255);
+			var b = Math.round(v.b*255);
+			setBackgroundColor(self.swatch, r, g, b);
+
+			if (self.pick !== null) {
+				var visible = self.pick.visible();
+				self.picker.destroy();
+				if (visible)
+					self.pick = newPicker(false);
+			}
+
+			return false;
+		};
+
+		this.swatchClick = function (event) {
+			if (self.pick === null)
+				self.pick = newPicker(true);
+			else
+				self.pick.toggle();
+
+			return false;
+		}
+
+		this.picker.bind("DOMNodeInserted", function (event) {
+			self.pickerInserted(event);
+			self.initialised = true;
+		});
+
+		this.picker.bind("DOMNodeRemoved", function (event) {
+			self.pickerRemoved(event);
+		});
+
+		this.picker.destroy = function() {
+			if (self.pick === null)
+				return;
+
+			self.pick.destroyMe();
+			self.pick = null;
+		}
+
+		this.infocus = false;
+	},
+	
+
+	// Picker inserted into the DOM and ready for action
+	pickerInserted: function (event) {
+		var self = this;
+		
+		if (self.initialised)
+			return;
+	
+		if (this.label) {
+			this.label.bind("click", this.labelClick);
+		}
+
+		this.swatch.bind("click", this.swatchClick);
+	},
+
+	pickerRemoved: function (event) {	
+		this.picker.destroy();
+	},
+};
+
+

+ 279 - 0
examples/polygon_light_example/js/super_slider.js

@@ -0,0 +1,279 @@
+/**
+ * Super Slider
+ * Last update: 12 Jan 2011
+ * 
+ * Changelog:
+ *   0.1   - Initial release
+ * 
+ * 
+ * Copyright (c) 2011 Tom Beddard
+ * http://www.subblue.com
+ * 
+ * Released under the MIT License: 
+ * http://www.opensource.org/licenses/mit-license.php
+ */ 
+
+/*global window, $, document*/
+
+function SuperSlider(id, opts) {
+	this.id = id;
+	this.init(opts);
+	
+	return this.slider;
+}
+
+SuperSlider.prototype = {
+	
+	init: function (opts) {
+		var self = this,
+			opt,
+			style;
+	
+		this.options =  { 
+			slider_class   : 'ss_slider', // slider class name
+			track_class    : 'ss_track',  // track class name
+			handle_class   : 'ss_handle', // handle class name
+			input_class    : 'ss_input',  // input class name
+			label_class    : 'ss_label',  // label class name
+			min            : 0,           // minimum value
+			max            : 1,           // maximum value
+			step           : 0.01,        // step increment
+			default_value  : 0.5,         // default value
+			value          : 0.5,         // current value
+			decimal_places : 3,           // decimal place rounding
+			label          : null,        // label
+			name           : null         // input name attribute
+		};
+	
+		for (opt in opts) {
+			if (opts.hasOwnProperty.call(opts, opt)) {
+				this.options[opt] = opts[opt];
+			}
+		}
+		
+		// Build slider
+		this.slider = $("<div>")
+			.addClass(this.options.slider_class)
+			.css("webkitUserSelect", 'none');
+	
+		this.track  = $("<div>").addClass(this.options.track_class);
+		this.handle = $("<div>").addClass(this.options.handle_class);
+	
+		this.input = $("<input>")
+			.attr("type", "input")
+			.addClass(this.options.input_class)
+			.attr("value", this.options.value);
+	
+		if (this.options.name) {
+			this.input.attr("name", this.options.name);
+		}
+	
+		if (this.options.label) {
+			this.label = $("<label>")
+				.text(this.options.label)
+				.addClass(this.options.label_class)
+				.attr("title", "Click to reset to default");
+			this.slider.append(this.label.get(0));
+		}
+	
+		this.track.append(this.handle.get(0));
+		this.slider.append(this.track.get(0));
+		this.slider.append(this.input.get(0));
+		
+		// Define event listeners
+		this.mouseDown = function (event) {
+			self.startDrag(event);
+		};
+
+		this.mouseMove = function (event) {
+			self.onDrag(event);
+		};
+
+		this.mouseUp = function (event) {
+			self.endDrag(event);
+		};
+	
+		this.mouseOver = function (event) {
+			self.infocus = true;
+			event.stopPropagation();
+		};
+	
+		this.mouseOut = function (event) {
+			self.infocus = false;
+			event.stopPropagation();
+		};
+	
+		this.keyDown = function (event) {
+			self.keyPress(event);
+		};
+	
+		this.startEdit = function (event) {
+			self.editing = true;
+		};
+	
+		this.endEdit = function (event) {
+			self.value(self.input.get(0).value);
+			self.editing = false;
+		};
+	
+		this.labelClick = function (event) {
+			self.updateSlider(self.options.default_value);
+			self.input.trigger("reset");
+			return false;
+		};
+
+		this.slider.bind("DOMNodeInserted", function (event) {
+			// We only want to call sliderInserted once, but Chrome
+			// won't attach bindings or update the slider until it's
+			// parented
+			var really_inserted = (self.slider.parent().length != 0);
+
+		    if (!self.initialised && really_inserted) {
+    			self.sliderInserted(event);
+	    		self.initialised = true;
+	    	}
+		});
+	
+		this.infocus = false;
+	},
+	
+	
+	// Slider inserted into the DOM and ready for action
+	sliderInserted: function (event) {
+		// Get computed style dimensions
+		this.handle_width = parseInt(window.getComputedStyle(this.handle.get(0), null).getPropertyValue("width"), 10);
+		this.track_width = parseInt(window.getComputedStyle(this.track.get(0), null).getPropertyValue("width"), 10) - this.handle_width;
+
+		// Setup current value
+		this.updateSlider(this.options.value);
+
+		if (this.label) {
+			this.label.bind("click", this.labelClick);
+		}
+
+		this.track.bind("mousedown", this.mouseDown);
+		this.slider.bind("mouseover", this.mouseOver);
+		this.slider.bind("mouseout", this.mouseOut);
+		this.input.bind("blur", this.endEdit);
+		document.addEventListener("keydown", this.keyDown, false);
+	},
+
+	value: function (v) {
+		if (typeof v !== "undefined") {
+			this.updateSlider(v);
+		}
+		return this.val;
+	},
+
+	
+	// Update the slider and fire the change event
+	updateSlider: function (value) {
+		var v = Math.min(Math.max(value, this.options.min), this.options.max),
+			pos = this.track_width * ((v - this.options.min) / (this.options.max - this.options.min)),
+			val = this.round(parseFloat(v), this.options.decimal_places);
+
+		if (isNaN(val) || value === '') {
+			// Invalid number, reset
+			this.input.attr("value", this.val);
+			this.input.get(0).value = this.val;
+		} else {
+			// Valid number
+			this.handle.css("left", pos + "px");
+			this.val = val;
+			this.input.attr("value", this.val);
+			this.input.get(0).value = this.val;		// This way too therwise the field doesn't always update
+			this.input.get(0).val = this.val;
+			
+			if (this.initialised && this.old_val !== this.val) {
+				this.input.trigger("change");
+				this.old_val = this.val;
+			}
+		}
+	},
+	
+	
+	startDrag: function (event) {
+		// event.preventDefault();
+		if (event.currentTarget === event.target) {
+			// Clicked track
+			this.ox = this.handle_width / 2;
+			this.onDrag(event);
+		} else {
+			// Clicked handle
+			this.ox = event.offsetX || event.layerX;
+		}
+	
+		this.track.get(0).addEventListener("mousemove", this.mouseMove, false);
+		$("body").bind("mouseup", this.mouseUp);
+	},
+
+
+	onDrag: function (event) {
+		var x, v;
+	
+		if (event.currentTarget === event.target) {
+			// track clicked
+			x = event.offsetX || event.layerX;
+		} else {
+			// handle clicked
+			x = event.target.offsetLeft + (event.offsetX || event.layerX);
+		}
+	
+		x -= this.ox;
+		v = (x / this.track_width) * (this.options.max - this.options.min);
+	
+		if (!event.altKey) {
+			// Snap to step increments
+			v = Math.floor(v / this.options.step) * this.options.step;
+		}
+	
+		this.updateSlider(this.options.min + v);
+	},
+
+
+	endDrag: function (event) {
+		this.track.get(0).removeEventListener("mousemove", this.mouseMove, false);
+		$("body").unbind("mouseup", this.mouseUp);
+	},
+	
+	
+	keyPress: function (event) {
+		var delta = 0,
+			mult = 1;
+	
+		if (this.infocus) {
+			// console.log("key", event);
+			if (event.shiftKey && event.altKey) {
+				mult = 0.01;
+			} else if (event.altKey) {
+				mult = 0.1;
+			} else if (event.shiftKey) {
+				mult = 10;
+			}
+		
+			switch (event.keyCode) {
+			case 38:
+				// Up arrow 38 - event.DOM_VK_UP
+				this.value(this.val += this.options.step * mult);
+				break;
+			case 40:
+				// Down arrow 40 - event.DOM_VK_DOWN
+				this.value(this.val -= this.options.step * mult);
+				break;
+			case 13:
+				// Enter pressed 13 - event.DOM_VK_RETURN
+				this.input.get(0).blur();
+				return false;
+			}
+		}
+	
+		return true;
+	},
+
+	round: function (v, n) {
+		var exp = Math.pow(10, n || 1);
+		return Math.round(v * exp) / exp;
+	}
+};
+
+

+ 541 - 0
examples/polygon_light_example/shaders/ltc/ltc.fs

@@ -0,0 +1,541 @@
+// bind roughness   {label:"Roughness", default:0.25, min:0.1, max:1, step:0.001}
+// bind dcolor      {label:"Diffuse Color",  r:1.0, g:1.0, b:1.0}
+// bind scolor      {label:"Specular Color", r:1.0, g:1.0, b:1.0}
+// bind intensity   {label:"Light Intensity", default:4, min:0, max:10}
+// bind width       {label:"Width",  default: 4, min:0.1, max:15, step:0.1}
+// bind height      {label:"Height", default: 4, min:0.1, max:15, step:0.1}
+
+uniform float roughness;
+uniform vec3  dcolor;
+uniform vec3  scolor;
+
+uniform float intensity;
+uniform float width;
+uniform float height;
+
+uniform sampler2D ltc_mat;
+uniform sampler2D ltc_mag;
+
+uniform mat4  view;
+uniform vec2  resolution;
+uniform int   sampleCount;
+
+const int   NUM_SAMPLES = 8;
+const float LUT_SIZE  = 64.0;
+const float LUT_SCALE = (LUT_SIZE - 1.0)/LUT_SIZE;
+const float LUT_BIAS  = 0.5/LUT_SIZE;
+const float pi = 3.14159265;
+
+// See "Building an orthonormal basis from a 3d unit vector without normalization"
+// Frisvad, Journal of Graphics Tools, 2012.
+mat3 CreateBasis(vec3 v)
+{
+    vec3 x, y;
+
+    if (v.z < -0.999999)
+    {
+        x = vec3( 0, -1, 0);
+        y = vec3(-1,  0, 0);
+    }
+    else
+    {
+        float a = 1.0 / (1.0 + v.z);
+        float b = -v.x*v.y*a;
+        x = vec3(1.0 - v.x*v.x*a, b, -v.x);
+        y = vec3(b, 1.0 - v.y*v.y*a, -v.y);
+    }
+
+    return mat3(x, y, v);
+}
+
+// Tracing and intersection
+///////////////////////////
+
+struct Ray
+{
+    vec3 origin;
+    vec3 dir;
+};
+
+struct Rect
+{
+    vec3  origin;
+    vec4  plane;
+    float sizex;
+    float sizey;
+};
+
+bool RayPlaneIntersect(Ray ray, vec4 plane, out float t)
+{
+    t = -dot(plane, vec4(ray.origin, 1.0))/dot(plane.xyz, ray.dir);
+    return t > 0.0;
+}
+
+bool RayRectIntersect(Ray ray, Rect rect, out float t)
+{
+    bool intersect = RayPlaneIntersect(ray, rect.plane, t);
+    if (intersect)
+    {
+        vec3 pos = ray.origin + ray.dir*t;
+        vec3 lpos = pos - rect.origin;
+        if (abs(lpos.x) > rect.sizex || abs(lpos.y) > rect.sizey)
+            intersect = false;
+    }
+
+    return intersect;
+}
+
+struct SphQuad
+{
+    vec3 o, x, y, z;
+    float z0, z0sq;
+    float x0, y0, y0sq;
+    float x1, y1, y1sq;
+    float b0, b1, b0sq, k;
+    float S;
+};
+
+SphQuad SphQuadInit(vec3 s, vec3 ex, vec3 ey, vec3 o)
+{
+    SphQuad squad;
+    
+    squad.o = o;
+    float exl = length(ex);
+    float eyl = length(ey);
+    
+    // compute local reference system ’R’
+    squad.x = ex / exl;
+    squad.y = ey / eyl;
+    squad.z = cross(squad.x, squad.y);
+    
+    // compute rectangle coords in local reference system
+    vec3 d = s - o;
+    squad.z0 = dot(d, squad.z);
+    
+    // flip ’z’ to make it point against ’Q’
+    if (squad.z0 > 0.0)
+    {
+        squad.z  *= -1.0;
+        squad.z0 *= -1.0;
+    }
+
+    squad.z0sq = squad.z0 * squad.z0;
+    squad.x0 = dot(d, squad.x);
+    squad.y0 = dot(d, squad.y);
+    squad.x1 = squad.x0 + exl;
+    squad.y1 = squad.y0 + eyl;
+    squad.y0sq = squad.y0 * squad.y0;
+    squad.y1sq = squad.y1 * squad.y1;
+    
+    // create vectors to four vertices
+    vec3 v00 = vec3(squad.x0, squad.y0, squad.z0);
+    vec3 v01 = vec3(squad.x0, squad.y1, squad.z0); 
+    vec3 v10 = vec3(squad.x1, squad.y0, squad.z0); 
+    vec3 v11 = vec3(squad.x1, squad.y1, squad.z0);
+
+    // compute normals to edges
+    vec3 n0 = normalize(cross(v00, v10));
+    vec3 n1 = normalize(cross(v10, v11));
+    vec3 n2 = normalize(cross(v11, v01));
+    vec3 n3 = normalize(cross(v01, v00));
+
+    // compute internal angles (gamma_i)
+    float g0 = acos(-dot(n0, n1));
+    float g1 = acos(-dot(n1, n2));
+    float g2 = acos(-dot(n2, n3));
+    float g3 = acos(-dot(n3, n0));
+    
+    // compute predefined constants
+    squad.b0 = n0.z;
+    squad.b1 = n2.z;
+    squad.b0sq = squad.b0 * squad.b0;
+    squad.k = 2.0*pi - g2 - g3;
+    
+    // compute solid angle from internal angles
+    squad.S = g0 + g1 - squad.k;
+
+    return squad;
+}
+
+vec3 SphQuadSample(SphQuad squad, float u, float v)
+{
+    // 1. compute 'cu'
+    float au = u * squad.S + squad.k;
+    float fu = (cos(au) * squad.b0 - squad.b1) / sin(au);
+    float cu = 1.0 / sqrt(fu*fu + squad.b0sq) * (fu > 0.0 ? 1.0 : -1.0);
+    cu = clamp(cu, -1.0, 1.0); // avoid NaNs
+    
+    // 2. compute 'xu'
+    float xu = -(cu * squad.z0) / sqrt(1.0 - cu * cu);
+    xu = clamp(xu, squad.x0, squad.x1); // avoid Infs
+    
+    // 3. compute 'yv'
+    float d = sqrt(xu * xu + squad.z0sq);
+    float h0 = squad.y0 / sqrt(d*d + squad.y0sq);
+    float h1 = squad.y1 / sqrt(d*d + squad.y1sq);
+    float hv = h0 + v * (h1 - h0), hv2 = hv * hv;
+    float yv = (hv2 < 1.0 - 1e-6) ? (hv * d) / sqrt(1.0 - hv2) : squad.y1;
+    
+    // 4. transform (xu, yv, z0) to world coords
+    return squad.o + xu*squad.x + yv*squad.y + squad.z0*squad.z;
+}
+
+// Sample generation
+////////////////////
+
+float Halton(int index, float base)
+{
+    float result = 0.0;
+    float f = 1.0/base;
+    float i = float(index);
+    for (int x = 0; x < 8; x++)
+    {
+        if (i <= 0.0) break;
+
+        result += f*mod(i, base);
+        i = floor(i/base);
+        f = f/base;
+    }
+
+    return result;
+}
+
+void Halton2D(out vec2 s[NUM_SAMPLES], int offset)
+{
+    for (int i = 0; i < NUM_SAMPLES; i++)
+    {
+        s[i].x = Halton(i + offset, 2.0);
+        s[i].y = Halton(i + offset, 3.0);
+    }
+}
+
+// Adapted from:
+// https://www.shadertoy.com/view/4djSRW
+float hash(float x, float y)
+{
+    vec2 p = vec2(x, y);
+    p  = fract(p * vec2(443.8975, 397.2973));
+    p += dot(p.xy, p.yx + 19.19);
+    return fract(p.x + p.y);
+}
+
+// Camera functions
+///////////////////
+
+Ray GenerateCameraRay(float u1, float u2)
+{
+    Ray ray;
+
+    // Random jitter within pixel for AA
+    vec2 xy = 2.0*(gl_FragCoord.xy)/resolution - vec2(1.0);
+
+    ray.dir = normalize(vec3(xy, 2.0));
+
+    float focalDistance = 2.0;
+    float ft = focalDistance/ray.dir.z;
+    vec3 pFocus = ray.dir*ft;
+
+    ray.origin = vec3(0);
+    ray.dir    = normalize(pFocus - ray.origin);
+
+    // Apply camera transform
+    ray.origin = (view*vec4(ray.origin, 1)).xyz;
+    ray.dir    = (view*vec4(ray.dir,    0)).xyz;
+
+    return ray;
+}
+
+vec3 mul(mat3 m, vec3 v)
+{
+    return m * v;
+}
+
+mat3 mul(mat3 m1, mat3 m2)
+{
+    return m1 * m2;
+}
+
+int modi(int x, int y)
+{
+    return int(mod(float(x), float(y)));
+}
+
+mat3 transpose(mat3 v)
+{
+    mat3 tmp;
+    tmp[0] = vec3(v[0].x, v[1].x, v[2].x);
+    tmp[1] = vec3(v[0].y, v[1].y, v[2].y);
+    tmp[2] = vec3(v[0].z, v[1].z, v[2].z);
+
+    return tmp;
+}
+
+// Linearly Transformed Cosines
+///////////////////////////////
+
+float IntegrateEdge(vec3 v1, vec3 v2)
+{
+    float cosTheta = dot(v1, v2);
+    cosTheta = clamp(cosTheta, -0.9999, 0.9999);
+
+    float theta = acos(cosTheta);    
+    float res = cross(v1, v2).z * theta / sin(theta);
+
+    return res;
+}
+
+void ClipQuadToHorizon(inout vec3 L[5], out int n)
+{
+    // detect clipping config
+    int config = 0;
+    if (L[0].z > 0.0) config += 1;
+    if (L[1].z > 0.0) config += 2;
+    if (L[2].z > 0.0) config += 4;
+    if (L[3].z > 0.0) config += 8;
+
+    // clip
+    n = 0;
+
+    if (config == 0)
+    {
+        // clip all
+    }
+    else if (config == 1) // V1 clip V2 V3 V4
+    {
+        n = 3;
+        L[1] = -L[1].z * L[0] + L[0].z * L[1];
+        L[2] = -L[3].z * L[0] + L[0].z * L[3];
+    }
+    else if (config == 2) // V2 clip V1 V3 V4
+    {
+        n = 3;
+        L[0] = -L[0].z * L[1] + L[1].z * L[0];
+        L[2] = -L[2].z * L[1] + L[1].z * L[2];
+    }
+    else if (config == 3) // V1 V2 clip V3 V4
+    {
+        n = 4;
+        L[2] = -L[2].z * L[1] + L[1].z * L[2];
+        L[3] = -L[3].z * L[0] + L[0].z * L[3];
+    }
+    else if (config == 4) // V3 clip V1 V2 V4
+    {
+        n = 3;
+        L[0] = -L[3].z * L[2] + L[2].z * L[3];
+        L[1] = -L[1].z * L[2] + L[2].z * L[1];
+    }
+    else if (config == 5) // V1 V3 clip V2 V4) impossible
+    {
+        n = 0;
+    }
+    else if (config == 6) // V2 V3 clip V1 V4
+    {
+        n = 4;
+        L[0] = -L[0].z * L[1] + L[1].z * L[0];
+        L[3] = -L[3].z * L[2] + L[2].z * L[3];
+    }
+    else if (config == 7) // V1 V2 V3 clip V4
+    {
+        n = 5;
+        L[4] = -L[3].z * L[0] + L[0].z * L[3];
+        L[3] = -L[3].z * L[2] + L[2].z * L[3];
+    }
+    else if (config == 8) // V4 clip V1 V2 V3
+    {
+        n = 3;
+        L[0] = -L[0].z * L[3] + L[3].z * L[0];
+        L[1] = -L[2].z * L[3] + L[3].z * L[2];
+        L[2] =  L[3];
+    }
+    else if (config == 9) // V1 V4 clip V2 V3
+    {
+        n = 4;
+        L[1] = -L[1].z * L[0] + L[0].z * L[1];
+        L[2] = -L[2].z * L[3] + L[3].z * L[2];
+    }
+    else if (config == 10) // V2 V4 clip V1 V3) impossible
+    {
+        n = 0;
+    }
+    else if (config == 11) // V1 V2 V4 clip V3
+    {
+        n = 5;
+        L[4] = L[3];
+        L[3] = -L[2].z * L[3] + L[3].z * L[2];
+        L[2] = -L[2].z * L[1] + L[1].z * L[2];
+    }
+    else if (config == 12) // V3 V4 clip V1 V2
+    {
+        n = 4;
+        L[1] = -L[1].z * L[2] + L[2].z * L[1];
+        L[0] = -L[0].z * L[3] + L[3].z * L[0];
+    }
+    else if (config == 13) // V1 V3 V4 clip V2
+    {
+        n = 5;
+        L[4] = L[3];
+        L[3] = L[2];
+        L[2] = -L[1].z * L[2] + L[2].z * L[1];
+        L[1] = -L[1].z * L[0] + L[0].z * L[1];
+    }
+    else if (config == 14) // V2 V3 V4 clip V1
+    {
+        n = 5;
+        L[4] = -L[0].z * L[3] + L[3].z * L[0];
+        L[0] = -L[0].z * L[1] + L[1].z * L[0];
+    }
+    else if (config == 15) // V1 V2 V3 V4
+    {
+        n = 4;
+    }
+    
+    if (n == 3)
+        L[3] = L[0];
+    if (n == 4)
+        L[4] = L[0];
+}
+
+
+vec3 LTC_Evaluate(
+    vec3 N, vec3 V, vec3 P, mat3 Minv, vec3 points[4], bool twoSided)
+{
+    // construct orthonormal basis around N
+    vec3 T1, T2;
+    T1 = normalize(V - N*dot(V, N));
+    T2 = cross(N, T1);
+
+    // rotate area light in (T1, T2, R) basis
+    Minv = mul(Minv, transpose(mat3(T1, T2, N)));
+
+    // polygon (allocate 5 vertices for clipping)
+    vec3 L[5];
+    L[0] = mul(Minv, points[0] - P);
+    L[1] = mul(Minv, points[1] - P);
+    L[2] = mul(Minv, points[2] - P);
+    L[3] = mul(Minv, points[3] - P);
+
+    int n;
+    ClipQuadToHorizon(L, n);
+    
+    if (n == 0)
+        return vec3(0, 0, 0);
+
+    // project onto sphere
+    L[0] = normalize(L[0]);
+    L[1] = normalize(L[1]);
+    L[2] = normalize(L[2]);
+    L[3] = normalize(L[3]);
+    L[4] = normalize(L[4]);
+
+    // integrate
+    float sum = 0.0;
+
+    sum += IntegrateEdge(L[0], L[1]);
+    sum += IntegrateEdge(L[1], L[2]);
+    sum += IntegrateEdge(L[2], L[3]);
+    if (n >= 4)
+        sum += IntegrateEdge(L[3], L[4]);
+    if (n == 5)
+        sum += IntegrateEdge(L[4], L[0]);
+
+    sum = twoSided ? abs(sum) : max(0.0, -sum);
+
+    vec3 Lo_i = vec3(sum, sum, sum);
+
+    return Lo_i;
+}
+
+
+// Misc. helpers
+////////////////
+
+float saturate(float v)
+{
+    return clamp(v, 0.0, 1.0);
+}
+
+vec3 PowVec3(vec3 v, float p)
+{
+    return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p));
+}
+
+const float gamma = 2.2;
+
+vec3 ToLinear(vec3 v) { return PowVec3(v,     gamma); }
+vec3 ToSRGB(vec3 v)   { return PowVec3(v, 1.0/gamma); }
+
+void main()
+{
+    vec2 seq[NUM_SAMPLES];
+    Halton2D(seq, sampleCount);
+
+    vec3 col = vec3(0);
+
+    // Scene info
+    Rect rect;
+    vec3 rectNormal = vec3(0, 0, -1);
+    rect.origin = vec3(0, 6, 32);
+    rect.plane  = vec4(rectNormal, -dot(rectNormal, rect.origin));
+    rect.sizex  = width;
+    rect.sizey  = height;
+
+    vec4 plane = vec4(0, 1, 0, 0);
+
+    vec3 lcol = vec3(intensity);
+    vec3 dcol = ToLinear(dcolor);
+    vec3 scol = ToLinear(scolor);
+
+    {
+        Ray ray = GenerateCameraRay(0.0, 0.0);
+
+        float distToFloor;
+        bool hitFloor = RayPlaneIntersect(ray, plane, distToFloor);
+        if (hitFloor)
+        {
+            vec3 pos = ray.origin + ray.dir*distToFloor;
+
+            vec3 N = plane.xyz;
+            vec3 V = -ray.dir;
+
+            vec3 ex = vec3(1, 0, 0)*rect.sizex;
+            vec3 ey = vec3(0, 1, 0)*rect.sizey;
+    
+            vec3 p1 = rect.origin - ex + ey;
+            vec3 p2 = rect.origin + ex + ey;
+            vec3 p3 = rect.origin + ex - ey;
+            vec3 p4 = rect.origin - ex - ey;
+            
+            vec3 points[4];
+            points[0] = p1;
+            points[1] = p2;
+            points[2] = p3;
+            points[3] = p4;
+            
+            float theta = acos(dot(N, V));
+            vec2 uv = vec2(roughness, theta/(0.5*pi));
+            uv = uv*LUT_SCALE + LUT_BIAS;
+            
+            vec4 t = texture2D(ltc_mat, uv);
+            mat3 Minv = mat3(
+                vec3(  1,   0, t.y),
+                vec3(  0, t.z,   0),
+                vec3(t.w,   0, t.x)
+            );
+            
+            vec3 spec = lcol*scol*LTC_Evaluate(N, V, pos, Minv, points, false);
+            spec *= texture2D(ltc_mag, uv).w;
+            
+            vec3 diff = lcol*dcol*LTC_Evaluate(N, V, pos, mat3(1), points, false); 
+            
+            col  = spec + diff;
+            col /= 2.0*pi;
+        }
+
+        float distToRect;
+        if (RayRectIntersect(ray, rect, distToRect))
+            if ((distToRect < distToFloor) || !hitFloor)
+                col = lcol;
+    }
+
+    gl_FragColor = vec4(col, 1.0);
+}

+ 6 - 0
examples/polygon_light_example/shaders/ltc/ltc.vs

@@ -0,0 +1,6 @@
+attribute vec3 position;
+
+void main()
+{
+    gl_Position = vec4(position, 1.0);
+}

+ 99 - 0
examples/polygon_light_example/shaders/ltc/ltc_blit.fs

@@ -0,0 +1,99 @@
+
+uniform vec2 resolution;
+
+uniform sampler2D tex;
+
+vec3 rrt_odt_fit(vec3 v)
+{
+    vec3 a = v*(         v + 0.0245786) - 0.000090537;
+    vec3 b = v*(0.983729*v + 0.4329510) + 0.238081;
+    return a/b;
+}
+
+mat3 transpose(mat3 v)
+{
+    mat3 tmp;
+    tmp[0] = vec3(v[0].x, v[1].x, v[2].x);
+    tmp[1] = vec3(v[0].y, v[1].y, v[2].y);
+    tmp[2] = vec3(v[0].z, v[1].z, v[2].z);
+
+    return tmp;
+}
+
+mat3 mat3_from_rows(vec3 c0, vec3 c1, vec3 c2)
+{
+    mat3 m = mat3(c0, c1, c2);
+    m = transpose(m);
+
+    return m;
+}
+
+vec3 mul(mat3 m, vec3 v)
+{
+    return m * v;
+}
+
+mat3 mul(mat3 m1, mat3 m2)
+{
+    return m1 * m2;
+}
+
+float saturate(float v)
+{
+    return clamp(v, 0.0, 1.0);
+}
+
+vec3 saturate(vec3 v)
+{
+    return vec3(saturate(v.x), saturate(v.y), saturate(v.z));
+}
+
+vec3 aces_fitted(vec3 color)
+{
+	mat3 ACES_INPUT_MAT = mat3_from_rows(
+	    vec3( 0.59719, 0.35458, 0.04823),
+	    vec3( 0.07600, 0.90834, 0.01566),
+	    vec3( 0.02840, 0.13383, 0.83777));
+
+	mat3 ACES_OUTPUT_MAT = mat3_from_rows(
+	    vec3( 1.60475,-0.53108,-0.07367),
+	    vec3(-0.10208, 1.10813,-0.00605),
+	    vec3(-0.00327,-0.07276, 1.07602));
+
+    color = mul(ACES_INPUT_MAT, color);
+
+    // Apply RRT and ODT
+    color = rrt_odt_fit(color);
+
+    color = mul(ACES_OUTPUT_MAT, color);
+
+    // Clamp to [0, 1]
+    color = saturate(color);
+
+    return color;
+}
+
+vec3 PowVec3(vec3 v, float p)
+{
+    return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p));
+}
+
+const float gamma = 2.2;
+
+vec3 ToLinear(vec3 v) { return PowVec3(v,     gamma); }
+vec3 ToSRGB(vec3 v)   { return PowVec3(v, 1.0/gamma); }
+
+void main()
+{
+    vec2 pos = gl_FragCoord.xy/resolution;
+
+	vec4 col = texture2D(tex, pos);
+
+	// Rescale by number of samples
+	col /= col.w;
+
+	col.rgb = aces_fitted(col.rgb);
+	col.rgb = ToSRGB(col.rgb);
+
+    gl_FragColor = vec4(col);
+}

+ 6 - 0
examples/polygon_light_example/shaders/ltc/ltc_blit.vs

@@ -0,0 +1,6 @@
+attribute vec3 position;
+
+void main()
+{
+    gl_Position = vec4(position, 1.0);
+}

+ 383 - 0
examples/webgl_lights_arealight.html

@@ -0,0 +1,383 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - lights - rect light</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
+			}
+
+			a {
+				color: #ff0080;
+				text-decoration: none;
+			}
+
+			a:hover {
+				color: #0080ff;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - Just to show the rect light and it's edge - by <a href="http://github.com/abelnation" target="_blank">abelnation</a><br />
+			Click and drag to move OrbitControls, center across the edge of the shadow.<br />
+			Click to set random color CTRL-Click for White.<br />
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/lights/RectAreaLightUniformsLib.js"></script>
+		<script src="../examples/js/libs/dat.gui.min.js"></script>
+		<script src="../examples/js/controls/OrbitControls.js"></script>
+		<script src="js/Detector.js"></script>
+
+		<script>
+
+			var container = document.getElementById( 'container' );
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var rnd = new THREE.WebGLRenderer();
+			var cam = new THREE.PerspectiveCamera( 34, window.innerWidth / window.innerHeight, 0.1, 20000 );
+			var orb = new THREE.OrbitControls( cam, rnd.domElement );
+
+			var scn = new THREE.Scene();
+
+			var matParams = {
+				specular: 0xFFFFFF,
+				shininess: 10000
+			}
+
+			var matFloor = new THREE.MeshPhongMaterial( matParams );
+			var matBox = new THREE.MeshPhongMaterial( matParams );
+			var geoFloor = new THREE.BoxGeometry( 2000, 0.1, 2000 );
+			var geoBox = new THREE.BoxGeometry( Math.PI, Math.sqrt( 2 ), Math.E );
+			var mshFloor = new THREE.Mesh( geoFloor, matFloor );
+			var mshBox = new THREE.Mesh( geoBox, matBox );
+
+			var amb = new THREE.AmbientLight( 0x080808 );
+
+			// TODO (abelnation): temp point light for debugging
+			var dir = new THREE.DirectionalLight( 0xFFFFFF );
+			var dirHelper = new THREE.DirectionalLightHelper( dir );
+
+			var shapes, shapeNames;
+			var rectLight;
+			var rectLightHelper;
+
+			var ray = new THREE.Raycaster();
+			var mouseDown = new THREE.Vector2();
+			var mouse = new THREE.Vector2();
+
+			var gui, guiElements;
+			var param = {};
+
+			function init() {
+
+				var gl = rnd.context;
+
+				// Check for float-RT support
+				// TODO (abelnation): figure out fall-back for float textures
+				if (!gl.getExtension("OES_texture_float")) {
+					alert("OES_texture_float not supported");
+					throw "missing webgl extension";
+				}
+
+				if (!gl.getExtension("OES_texture_float_linear")) {
+					alert("OES_texture_float_linear not supported");
+					throw "missing webgl extension";
+				}
+
+				rnd.shadowMap.enabled = true;
+				rnd.shadowMap.type = THREE.PCFSoftShadowMap;
+				rnd.gammaInput = true;
+				rnd.gammaOutput = true;
+				rnd.antialias = true;
+				rnd.domElement.addEventListener( 'mousedown', onDocumentClick );
+				rnd.domElement.addEventListener( 'mouseup', onDocumentClick );
+
+				// cam.position.set( 0, 100, 0 );
+				cam.position.set( 45, 20, 45 );
+
+				// shapes = {
+				// 	square: THREE.Polygon.makeSquare( 10.0 ),
+				// 	rectangle: THREE.Polygon.makeRectangle( 10.0, 5.0 ),
+				// 	circle: THREE.Polygon.makeCircle( 5.0, 20 ),
+				// 	star: THREE.Polygon.makeStar( 5, 5.0, 2.5 ),
+				// 	triangle: new THREE.Polygon( [
+				// 		new THREE.Vector3( -5.0, -5.0, 0 ),
+				// 		new THREE.Vector3(  0.0,  5.0, 0 ),
+				// 		new THREE.Vector3(  5.0, -5.0, 0 ),
+				// 	] )
+				// };
+				// shapeNames = Object.keys( shapes );
+
+				rectLight = new THREE.RectAreaLight( 0xFFFFFF, undefined, 2, 10 );
+				rectLight.matrixAutoUpdate = true;
+				rectLight.intensity = 70.0;
+				rectLight.position.set( 5, 5, 0 );
+				// rectLight.target = mshBox;
+				rectLightHelper = new THREE.RectAreaLightHelper( rectLight );
+				rectLight.add( rectLightHelper );
+
+				// TODO (abelnation): rect light shadow
+
+				matFloor.color.set( 0x808080 );
+				randomColor( matBox );
+
+				mshFloor.receiveShadow = true;
+				mshFloor.position.set( 0, 0, 0 );
+
+				mshBox.castShadow = true;
+				mshBox.receiveShadow = true;
+				mshBox.position.set( 0, 5, 0 );
+
+				scn.add( cam );
+
+				scn.add( mshFloor );
+				scn.add( mshBox );
+
+				// scn.add( amb );
+
+				// scn.add( dir );
+				// scn.add( dirHelper );
+
+				scn.add( rectLight );
+				// scn.add( rectLightHelper );
+
+				// scn.add( new THREE.AxisHelper( 10 ) );
+
+				document.body.appendChild( rnd.domElement );
+				onResize();
+				window.addEventListener( 'resize', onResize, false );
+
+				orb.addEventListener( 'change', render );
+				orb.update();
+
+			}
+
+			function onResize() {
+
+				rnd.setSize( window.innerWidth, window.innerHeight );
+				cam.aspect = ( window.innerWidth / window.innerHeight );
+				cam.updateProjectionMatrix();
+				orb.target = mshBox.position;
+
+			}
+
+			function tick() {
+
+				update();
+				render();
+
+				requestAnimationFrame( tick );
+
+			}
+
+			function update() {
+
+				var dt = 6000;
+				var qdt = dt / 4.0;
+				var dirSigns = [
+					[ 1,  1 ],
+					[ - 1,  1 ],
+					[ - 1,  1 ],
+					[ 1,  1 ]
+				];
+				var t = ( Date.now() / 1000 );
+
+				// move light in circle around center
+				// change light height with sine curve
+
+				var r = 10.0;
+
+				var lx = r * Math.cos( t );
+				var lz = r * Math.sin( t );
+
+				var ly = 5.0 + 5.0 * Math.sin( t / 3.0 );
+				// var ly = 7.0;
+
+				rectLight.position.set( lx, ly, lz );
+				rectLight.lookAt( mshBox.position );
+				rectLight.updateMatrixWorld();
+
+				// // move box in figure 8 path
+				// // xz is a figure 8
+				// // y is gently rising and falling
+				// 				var r = t * ( 4.0 * Math.PI );
+				// var s = Math.floor( t * dirSigns.length );
+                //
+				// var sign = dirSigns[ s ];
+				// var x = 5.0 * ( Math.cos( r ) + 1.0 ) * sign[ 0 ];
+				// var z = 5.0 * Math.sin( r ) * sign[ 1 ];
+				// var y = 2.5 * Math.cos( 0.5 * r ) + 5.0;
+                //
+				// mshBox.position.set( x, y, z );
+				// mshBox.updateMatrixWorld();
+
+			}
+
+			function render() {
+
+				rectLightHelper.update(); // required
+				rnd.render( scn, cam );
+
+			}
+
+			function clearGui() {
+
+				if ( gui ) gui.destroy();
+
+				gui = new dat.GUI();
+				gui.width = 190;
+				var gStyle = gui.domElement.style;
+				gStyle.position = "absolute";
+				gStyle.top = "48px";
+				gStyle.height = "220px";
+
+				gui.open();
+
+			}
+
+			function buildGui() {
+
+				clearGui();
+
+				param = {
+					'light color': rectLight.color.getHex(),
+					intensity: rectLight.intensity,
+					width: rectLight.width,
+					height: rectLight.height,
+					shininess: matFloor.shininess,
+					// shape: shapeNames[0]
+				};
+
+				gui.add( param, 'width', 0.1, 20).onChange( function ( val ) {
+
+					rectLight.width = val;
+
+				} );
+
+				gui.add( param, 'height', 0.1, 20).onChange( function ( val ) {
+
+					rectLight.height = val;
+
+				} );
+
+				// gui.add( param, 'shape', shapeNames ).onChange( function ( val ) {
+                //
+				// 	rectLight.polygon = shapes[ val ].clone();
+                //
+				// } );
+
+				gui.addColor( param, 'light color' ).onChange( function ( val ) {
+
+					rectLight.color.setHex( val );
+
+				} );
+
+				gui.add( param, 'intensity', 0, 100 ).onChange( function ( val ) {
+
+					rectLight.intensity = val;
+
+				} );
+
+				gui.add( param, 'shininess', 0, 100000 ).onChange( function ( val ) {
+
+					matBox.shininess = val;
+					matFloor.shininess = val;
+
+				} );
+
+				// TODO (abelnation): shadow controls
+				// addGui( 'distance', rectLight.distance, function( val ) {
+				// rectLight.distance = val;
+				// render();
+				// }, false, 0, 1000 );
+
+				// addGui( 'penumbra', rectLight.penumbra, function( val ) {
+				// rectLight.penumbra = val;
+				// render();
+				// }, false, 0, 1 );
+
+				// addGui( 'decay', rectLight.decay, function( val ) {
+				// rectLight.decay = val;
+				// render();
+				// }, false, 0, 100 );
+
+			}
+
+			function onDocumentClick( event ) {
+
+				event.preventDefault();
+
+				var rndDom = rnd.domElement;
+
+				if ( event.type === 'mousedown' ) {
+
+					mouseDown.x = ( event.clientX / rndDom.clientWidth ) * 2 - 1;
+					mouseDown.y = - ( event.clientY / rndDom.clientHeight ) * 2 + 1;
+
+				} else {
+
+					mouse.x = ( event.clientX / rndDom.clientWidth ) * 2 - 1;
+					mouse.y = - ( event.clientY / rndDom.clientHeight ) * 2 + 1;
+
+					if ( mouseDown.distanceTo( mouse ) < 0.0075 ) {
+
+						ray.setFromCamera( mouse, cam );
+						var found = ray.intersectObjects( [ mshBox, mshFloor ] );
+
+						if ( found.length > 0 ) {
+
+							if ( event.ctrlKey === false ) randomColor( found[ 0 ].object );
+							else found[ 0 ].object.material.color.set( 0xffffff );
+
+							render();
+
+						}
+
+					}
+
+				}
+
+			}
+
+			function randomColor( target ) {
+
+				if ( target !== undefined ) {
+
+					if ( target.material !== undefined ) target = target.material;
+
+					if ( target.color !== undefined ) {
+
+						target.color.setHex( 0xffffff * Math.random() );
+
+					}
+
+				}
+
+			}
+
+			init();
+			buildGui();
+			tick();
+
+		</script>
+	</body>
+</html>

+ 397 - 0
examples/webgl_lights_rectarealight.html

@@ -0,0 +1,397 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - lights - rect light</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
+			}
+
+			a {
+				color: #ff0080;
+				text-decoration: none;
+			}
+
+			a:hover {
+				color: #0080ff;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - Demo of RectAreaLight on Phong and Standard Material Meshes - by <a href="http://github.com/abelnation" target="_blank">abelnation</a><br />
+			Click and drag to move OrbitControls.<br />
+			<br />
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/lights/RectAreaLightUniformsLib.js"></script>
+
+		<script src="../examples/js/libs/dat.gui.min.js"></script>
+		<script src="../examples/js/controls/OrbitControls.js"></script>
+		<script src="js/Detector.js"></script>
+
+		<script>
+
+			var container = document.getElementById( 'container' );
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var rnd = new THREE.WebGLRenderer();
+			var cam = new THREE.PerspectiveCamera( 34, window.innerWidth / window.innerHeight, 0.1, 20000 );
+			var orb = new THREE.OrbitControls( cam, rnd.domElement );
+
+			var scn = new THREE.Scene();
+			var origin = new THREE.Object3D();
+
+			var matPhongParams = {
+				specular: 0xFFFFFF,
+				shininess: 1000
+			};
+			var matStdParams = {
+				roughness: 0.044676705160855, // calculated from shininess = 1000
+				metalness: 0.0
+			};
+
+			var matPhongFloor = new THREE.MeshPhongMaterial( matPhongParams );
+			var matPhongObjects = new THREE.MeshPhongMaterial( matPhongParams );
+
+			var matStdFloor = new THREE.MeshStandardMaterial( matStdParams );
+			var matStdObjects = new THREE.MeshStandardMaterial( matStdParams );
+
+			var geoFloor = new THREE.BoxGeometry( 2000, 0.1, 2000 );
+			var geoBox = new THREE.BoxGeometry( Math.PI, Math.sqrt( 2 ), Math.E );
+			var geoSphere = new THREE.SphereGeometry( 1.5, 32, 32 );
+			var geoKnot = new THREE.TorusKnotGeometry( 1.5, 0.5, 100, 16 );
+
+			var mshPhongFloor = new THREE.Mesh( geoFloor, matPhongFloor );
+			var mshPhongBox = new THREE.Mesh( geoBox, matPhongObjects );
+			var mshPhongSphere = new THREE.Mesh( geoSphere, matPhongObjects );
+			var mshPhongKnot = new THREE.Mesh( geoKnot, matPhongObjects );
+
+			var mshStdFloor = new THREE.Mesh( geoFloor, matStdFloor );
+			var mshStdBox = new THREE.Mesh( geoBox, matStdObjects );
+			var mshStdSphere = new THREE.Mesh( geoSphere, matStdObjects );
+			var mshStdKnot = new THREE.Mesh( geoKnot, matStdObjects );
+
+			var amb = new THREE.AmbientLight( 0x000000 );
+
+			var rectLight;
+			var rectLightHelper;
+
+			var ray = new THREE.Raycaster();
+			var mouseDown = new THREE.Vector2();
+			var mouse = new THREE.Vector2();
+
+			var gui, guiElements;
+			var param = {};
+
+			function init() {
+
+				var gl = rnd.context;
+
+				// Check for float-RT support
+				// TODO (abelnation): figure out fall-back for float textures
+				if (!gl.getExtension("OES_texture_float")) {
+					alert("OES_texture_float not supported");
+					throw "missing webgl extension";
+				}
+
+				if (!gl.getExtension("OES_texture_float_linear")) {
+					alert("OES_texture_float_linear not supported");
+					throw "missing webgl extension";
+				}
+
+				rnd.shadowMap.enabled = true;
+				rnd.shadowMap.type = THREE.PCFSoftShadowMap;
+				rnd.gammaInput = true;
+				rnd.gammaOutput = true;
+				rnd.antialias = true;
+
+				cam.position.set( 0, 20, 45 );
+
+				rectLight = new THREE.RectAreaLight( 0xFFFFFF, undefined, 2, 10 );
+				rectLight.matrixAutoUpdate = true;
+				rectLight.intensity = 80.0;
+				rectLight.position.set( 5, 5, 0 );
+
+				// TODO: ensure RectAreaLight handles target param correctly
+				// rectLight.target = mshPhongBox;
+
+				rectLightHelper = new THREE.RectAreaLightHelper( rectLight );
+				rectLight.add( rectLightHelper );
+
+				// TODO (abelnation): rect light shadow
+
+				scn.add( cam );
+				scn.add( origin );
+
+				matPhongFloor.color.set( 0x808080 );
+				matPhongObjects.color.set( 0xA00000 );
+				matStdFloor.color.set( 0x808080 );
+				matStdObjects.color.set( 0xA00000 );
+
+				mshPhongFloor.receiveShadow = true;
+				mshPhongFloor.position.set( 0, 0, 0 );
+
+				mshPhongBox.castShadow = true;
+				mshPhongBox.receiveShadow = true;
+				mshPhongBox.position.set( 0, 5, 5 );
+				mshPhongBox.rotation.set( 0, Math.PI / 2.0, 0 );
+
+				mshPhongSphere.castShadow = true;
+				mshPhongSphere.receiveShadow = true;
+				mshPhongSphere.position.set(-5, 5, 5 );
+
+				mshPhongKnot.position.set( 5, 5, 5 );
+				mshPhongKnot.castShadow = true;
+				mshPhongKnot.receiveShadow = true;
+
+				scn.add( mshPhongFloor );
+				scn.add( mshPhongBox );
+				scn.add( mshPhongSphere );
+				scn.add( mshPhongKnot );
+
+				mshStdBox.castShadow = true;
+				mshStdBox.receiveShadow = true;
+				mshStdBox.position.set( 0, 5, -5 );
+				mshStdBox.rotation.set( 0, Math.PI / 2.0, 0 );
+
+				mshStdSphere.castShadow = true;
+				mshStdSphere.receiveShadow = true;
+				mshStdSphere.position.set(-5, 5, -5 );
+
+				mshStdKnot.position.set( 5, 5, -5 );
+				mshStdKnot.castShadow = true;
+				mshStdKnot.receiveShadow = true;
+
+				scn.add( mshStdFloor );
+				scn.add( mshStdBox );
+				scn.add( mshStdSphere );
+				scn.add( mshStdKnot );
+
+				scn.add( amb );
+
+				scn.add( rectLight );
+
+				document.body.appendChild( rnd.domElement );
+				onResize();
+				window.addEventListener( 'resize', onResize, false );
+
+				orb.addEventListener( 'change', render );
+				orb.update();
+
+			}
+
+			function onResize() {
+
+				rnd.setSize( window.innerWidth, window.innerHeight );
+				cam.aspect = ( window.innerWidth / window.innerHeight );
+				cam.updateProjectionMatrix();
+				orb.target = mshPhongBox.position;
+
+			}
+
+			function tick() {
+
+				if ( param.motion )
+					update();
+
+				render();
+
+				requestAnimationFrame( tick );
+
+			}
+
+			function update() {
+
+				var dt = 6000;
+				var qdt = dt / 4.0;
+				var dirSigns = [
+					[ 1,  1 ],
+					[ - 1,  1 ],
+					[ - 1,  1 ],
+					[ 1,  1 ]
+				];
+				var t = ( Date.now() / 1000 );
+
+				// move light in circle around center
+				// change light height with sine curve
+
+				var r = 15.0;
+
+				var lx = r * Math.cos( t );
+				var lz = r * Math.sin( t );
+
+				var ly = 5.0 + 5.0 * Math.sin( t / 3.0 );
+
+				rectLight.position.set( lx, ly, lz );
+				rectLight.lookAt( origin.position );
+				rectLight.updateMatrixWorld();
+
+			}
+
+			function render() {
+
+				rectLightHelper.update(); // required
+				rnd.render( scn, cam );
+
+			}
+
+			function clearGui() {
+
+				if ( gui ) gui.destroy();
+
+				gui = new dat.GUI();
+				gui.width = 190;
+				var gStyle = gui.domElement.style;
+				gStyle.position = "absolute";
+				gStyle.top = "48px";
+				gStyle.height = "220px";
+
+				gui.open();
+
+			}
+
+			function blinnToGGX( blinnExp ) {
+				return Math.sqrt( 2.0 / ( blinnExp + 2.0 ) );
+			}
+
+			function GGXToBlinn( roughness ) {
+				return ( 2.0 / Math.pow( roughness + 0.0001, 2 ) - 2.0 );
+			}
+
+			function buildGui() {
+
+				clearGui();
+
+				param = {
+					motion: true,
+					width: rectLight.width,
+					height: rectLight.height,
+					color: rectLight.color.getHex(),
+					intensity: rectLight.intensity,
+					'ambient light color': amb.color.getHex(),
+					'floor color p': matPhongFloor.color.getHex(),
+					'object color p': matPhongObjects.color.getHex(),
+					'shininess p': matPhongFloor.shininess,
+					'floor color s': matStdFloor.color.getHex(),
+					'object color s': matStdObjects.color.getHex(),
+					'roughness s': matStdFloor.roughness,
+					'metalness s': matStdFloor.metalness,
+
+				};
+
+				gui.add( param, 'motion' );
+
+				var lightFolder = gui.addFolder( 'Light' );
+				lightFolder.add( param, 'width', 0.1, 20).onChange( function ( val ) {
+
+					rectLight.width = val;
+
+				} );
+
+				lightFolder.add( param, 'height', 0.1, 20).onChange( function ( val ) {
+
+					rectLight.height = val;
+
+				} );
+
+				lightFolder.addColor( param, 'color' ).onChange( function ( val ) {
+
+					rectLight.color.setHex( val );
+
+				} );
+
+				lightFolder.add( param, 'intensity', 0.0, 200.0 ).onChange( function ( val ) {
+
+					rectLight.intensity = val;
+
+				} );
+
+				lightFolder.addColor( param, 'ambient light color' ).onChange( function ( val ) {
+
+					amb.color.setHex( val );
+
+				} );
+
+				var phongFolder = gui.addFolder( 'Phong Material' );
+				phongFolder.addColor( param, 'floor color p' ).onChange( function ( val ) {
+
+						matPhongFloor.color.setHex( val );
+
+					} );
+
+				phongFolder.addColor( param, 'object color p' ).onChange( function ( val ) {
+
+					matPhongObjects.color.setHex( val );
+
+				} );
+
+				phongFolder.add( param, 'shininess p', 0.0, 1000.0 ).listen().onChange( function ( val ) {
+
+					matPhongObjects.shininess = val;
+					matPhongFloor.shininess = val;
+
+					param['roughness s'] = blinnToGGX( val );
+
+				} );
+
+				var standardFolder = gui.addFolder( 'Standard Material' );
+				standardFolder.addColor( param, 'floor color s' ).onChange( function ( val ) {
+
+					matStdFloor.color.setHex( val );
+
+				} );
+
+				standardFolder.addColor( param, 'object color s' ).onChange( function ( val ) {
+
+					matStdObjects.color.setHex( val );
+
+				} );
+
+				standardFolder.add( param, 'roughness s', 0.0, 1.0 ).listen().onChange( function ( val ) {
+
+					matStdObjects.roughness = val;
+					matStdFloor.roughness = val;
+
+					param['shininess p'] = GGXToBlinn( val );
+
+				} );
+
+				// TODO (abelnation): use env map to reflect metal property
+				standardFolder.add( param, 'metalness s', 0.0, 1.0 ).onChange( function ( val ) {
+
+					matStdObjects.metalness = val;
+					matStdFloor.metalness = val;
+
+				} );
+
+				// TODO: rect area light distance
+				// TODO: rect area light decay
+
+			}
+
+			init();
+			buildGui();
+			tick();
+
+		</script>
+	</body>
+</html>

+ 238 - 0
examples/webgl_math_spherical_distribution.html

@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - geometries</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+		</style>
+	</head>
+	<body>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer;
+
+			init();
+			animate();
+
+      function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
+        camera.position.y = 300;
+        camera.position.z = 300;
+
+				scene = new THREE.Scene();
+        camera.lookAt( scene.position );
+
+				var light, object;
+
+				scene.add( new THREE.AmbientLight( 0x808080 ) );
+
+				light = new THREE.DirectionalLight( 0xffffff );
+				light.position.set( 0, 1, 0 );
+				scene.add( light );
+
+				// var material = new THREE.MeshLambertMaterial( {
+				var material = new THREE.MeshPhongMaterial( {
+          vertexColors: THREE.VertexColors,
+          side: THREE.DoubleSide
+        } );
+
+				// create sphere colored with sphericalDist values
+
+        var numSphereSegments = 100;
+
+        var dist;
+        var geom;
+        var baseDist = new THREE.CosineSphericalDistribution();
+
+        dist = new THREE.LinearlyTransformedSphericalDistribution(baseDist)
+          .shear(-0.5, 0.0, 0.0);
+        geom = new THREE.SphereGeometry( 20, numSphereSegments, numSphereSegments );
+        colorSphereWithDistributionValues(geom, dist);
+        object = new THREE.Mesh( geom, material );
+				object.position.set( -100, 0, 0 );
+        object.rotation.set( -Math.PI / 2.0, 0.0, 0.0 );
+				scene.add( object );
+
+        dist = new THREE.LinearlyTransformedSphericalDistribution(baseDist)
+          .scale(0.5, 1.0, 1.0);
+        geom = new THREE.SphereGeometry( 20, numSphereSegments, numSphereSegments );
+        colorSphereWithDistributionValues(geom, dist);
+        object = new THREE.Mesh( geom, material );
+				object.position.set( -50, 0, 0 );
+        object.rotation.set( -Math.PI / 2.0, 0.0, 0.0 );
+				scene.add( object );
+
+        dist = new THREE.CosineSphericalDistribution();
+        geom = new THREE.SphereGeometry( 20, numSphereSegments, numSphereSegments );
+        colorSphereWithDistributionValues(geom, dist);
+        object = new THREE.Mesh( geom, material );
+				object.position.set( 0, 0, 0 );
+        object.rotation.set( -Math.PI / 2.0, 0.0, 0.0 );
+				scene.add( object );
+
+        dist = new THREE.LinearlyTransformedSphericalDistribution(baseDist)
+          .scale(1.0, 0.5, 1.0);
+        geom = new THREE.SphereGeometry( 20, numSphereSegments, numSphereSegments );
+        colorSphereWithDistributionValues(geom, dist);
+        object = new THREE.Mesh( geom, material );
+				object.position.set( 50, 0, 0 );
+        object.rotation.set( -Math.PI / 2.0, 0.0, 0.0 );
+				scene.add( object );
+
+        dist = new THREE.LinearlyTransformedSphericalDistribution(baseDist)
+          .shear(0.0, -0.5, 0.0);
+        geom = new THREE.SphereGeometry( 20, numSphereSegments, numSphereSegments );
+        colorSphereWithDistributionValues(geom, dist);
+        object = new THREE.Mesh( geom, material );
+				object.position.set( 100, 0, 0 );
+        object.rotation.set( -Math.PI / 2.0, 0.0, 0.0 );
+				scene.add( object );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+      function colorSphereWithDistributionValues(geom, dist) {
+
+        var maxVal = 0.65;
+        var sum = 0.0;
+
+        var values = [];
+
+        // calc dist values, and note max value to map colors
+        for ( var i = 0; i < geom.faces.length; i ++ ) {
+
+          values.push({});
+          var face = geom.faces[ i ];
+
+          for ( var j = 0; j < 3; j ++ ) {
+            var faceIdx = [ 'a', 'b', 'c' ][ j ];
+            var pos = geom.vertices[ face[ faceIdx ] ];
+
+            var distValue = dist.valueAtPosVec3( pos );
+
+            // sum += distValue;
+            // maxVal = Math.max(maxVal, distValue);
+
+            values[i][faceIdx] = distValue;
+          }
+
+        }
+
+        console.log('max: ' + maxVal);
+        console.log('sum: ' + sum);
+
+        // set colors using maxValue to interpolate properly
+        for ( var i = 0; i < geom.faces.length; i ++ ) {
+
+          var face = geom.faces[ i ];
+
+          for ( var j = 0; j < 3; j ++ ) {
+
+            var red = new THREE.Color( 'red' );
+            var blue = new THREE.Color( 'blue' );
+            var white = new THREE.Color( 'white' );
+
+            var faceIdx = [ 'a', 'b', 'c' ][ j ];
+            var distValue = values[i][faceIdx] / maxVal;
+
+            var color;
+            if (distValue > 0.5) {
+
+              // 1.0 -> 0.5: red -> white
+              color = red.lerp( white, 1.0 - ( ( distValue - 0.5 ) * 2.0 ) );
+
+            } else {
+
+              // 0.5 -> 0.0: white -> blue
+              color = blue.lerp( white, distValue * 2.0 );
+
+            }
+            face.vertexColors[ j ] = color;
+          }
+        }
+
+        geom.colorsNeedUpdate = true;
+
+      }
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.0001;
+
+				// camera.position.x = Math.cos( timer ) * 800;
+				// camera.position.z = Math.sin( timer ) * 800;
+
+				// camera.lookAt( scene.position );
+
+				// for ( var i = 0, l = scene.children.length; i < l; i ++ ) {
+
+					// var object = scene.children[ i ];
+
+					// object.rotation.x = timer * 5;
+					// object.rotation.y = timer * 2.5;
+
+				// }
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "three",
-  "version": "0.82.0",
+  "version": "0.82.1",
   "description": "JavaScript 3D library",
   "main": "build/three.js",
   "repository": "mrdoob/three.js",

+ 2 - 0
src/Three.js

@@ -49,6 +49,7 @@ export { AudioLoader } from './loaders/AudioLoader.js';
 export { SpotLightShadow } from './lights/SpotLightShadow.js';
 export { SpotLight } from './lights/SpotLight.js';
 export { PointLight } from './lights/PointLight.js';
+export { RectAreaLight } from './lights/RectAreaLight.js';
 export { HemisphereLight } from './lights/HemisphereLight.js';
 export { DirectionalLightShadow } from './lights/DirectionalLightShadow.js';
 export { DirectionalLight } from './lights/DirectionalLight.js';
@@ -135,6 +136,7 @@ export { VertexNormalsHelper } from './extras/helpers/VertexNormalsHelper.js';
 export { SpotLightHelper } from './extras/helpers/SpotLightHelper.js';
 export { SkeletonHelper } from './extras/helpers/SkeletonHelper.js';
 export { PointLightHelper } from './extras/helpers/PointLightHelper.js';
+export { RectAreaLightHelper } from './extras/helpers/RectAreaLightHelper.js';
 export { HemisphereLightHelper } from './extras/helpers/HemisphereLightHelper.js';
 export { GridHelper } from './extras/helpers/GridHelper.js';
 export { FaceNormalsHelper } from './extras/helpers/FaceNormalsHelper.js';

+ 109 - 0
src/extras/helpers/RectAreaLightHelper.js

@@ -0,0 +1,109 @@
+/**
+ * @author abelnation / http://github.com/abelnation
+ */
+
+import {Object3D} from '../../core/Object3D';
+import {Vector3} from '../../math/Vector3';
+import {Shape} from '../../extras/core/Shape';
+import {Mesh} from '../../objects/Mesh';
+import {MeshBasicMaterial} from '../../materials/MeshBasicMaterial';
+import {ShapeGeometry} from '../../geometries/ShapeGeometry';
+
+function RectAreaLightHelper(light) {
+
+    Object3D.call(this);
+
+    this.light = light;
+    this.light.updateMatrixWorld();
+
+    // this.matrix = light.matrixWorld;
+    // this.matrixAutoUpdate = false;
+
+    this.lightMat = new MeshBasicMaterial({
+        color: light.color,
+        fog: false
+    });
+
+    this.lightWireMat = new MeshBasicMaterial({
+        color: light.color,
+        fog: false,
+        wireframe: true
+    });
+
+    var hx = this.light.width / 2.0;
+    var hy = this.light.height / 2.0;
+    this.lightShape = new ShapeGeometry(new Shape([
+        new Vector3(-hx, hy, 0),
+        new Vector3(hx, hy, 0),
+        new Vector3(hx, -hy, 0),
+        new Vector3(-hx, -hy, 0)
+    ]));
+
+    // shows the "front" of the light, e.g. where light comes from
+    this.lightMesh = new Mesh(this.lightShape, this.lightMat);
+    // shows the "back" of the light, which does not emit light
+    this.lightWireMesh = new Mesh(this.lightShape, this.lightWireMat);
+
+    this.add(this.lightMesh);
+    this.add(this.lightWireMesh);
+
+    this.update();
+
+}
+
+RectAreaLightHelper.prototype = Object.create(Object3D.prototype);
+RectAreaLightHelper.prototype.constructor = RectAreaLightHelper;
+
+RectAreaLightHelper.prototype.dispose = function () {
+
+    this.lightMesh.geometry.dispose();
+    this.lightMesh.material.dispose();
+    this.lightWireMesh.geometry.dispose();
+    this.lightWireMesh.material.dispose();
+
+};
+
+RectAreaLightHelper.prototype.update = function () {
+
+    var vector = new Vector3();
+    var vector2 = new Vector3();
+
+    // TODO (abelnation) why not just make light helpers a child of the light object?
+    if (this.light.target) {
+
+        vector.setFromMatrixPosition(this.light.matrixWorld);
+        vector2.setFromMatrixPosition(this.light.target.matrixWorld);
+
+        var lookVec = vector2.clone().sub(vector);
+        this.lightMesh.lookAt(lookVec);
+        this.lightWireMesh.lookAt(lookVec);
+
+    }
+
+    this.lightMesh.material.color
+        .copy(this.light.color)
+        .multiplyScalar(this.light.intensity);
+
+    this.lightWireMesh.material.color
+        .copy(this.light.color)
+        .multiplyScalar(this.light.intensity);
+
+    var oldShape = this.lightShape;
+
+    var hx = this.light.width / 2.0;
+    var hy = this.light.height / 2.0;
+    this.lightShape = new ShapeGeometry(new Shape([
+        new Vector3(-hx, hy, 0),
+        new Vector3(hx, hy, 0),
+        new Vector3(hx, -hy, 0),
+        new Vector3(-hx, -hy, 0)
+    ]));
+
+    this.lightMesh.geometry = this.lightShape;
+    this.lightWireMesh.geometry = this.lightShape;
+
+    oldShape.dispose();
+
+};
+
+export {RectAreaLightHelper};

+ 50 - 0
src/lights/RectAreaLight.js

@@ -0,0 +1,50 @@
+import { Light } from './Light';
+
+/**
+ * @author abelnation / http://github.com/abelnation
+ */
+
+function RectAreaLight ( color, intensity, width, height ) {
+
+	Light.call( this, color, intensity );
+
+	this.type = 'RectAreaLight';
+
+	this.position.set( 0, 1, 0 );
+	this.updateMatrix();
+
+	this.width = ( width !== undefined ) ? width : 10;
+	this.height = ( height !== undefined ) ? height : 10;
+
+	// TODO (abelnation): distance/decay
+
+	// TODO (abelnation): update method for RectAreaLight to update transform to lookat target
+
+	// TODO (abelnation): shadows
+	// this.shadow = new THREE.RectAreaLightShadow( new THREE.PerspectiveCamera( 90, 1, 0.5, 500 ) );
+
+}
+
+// TODO (abelnation): RectAreaLight update when light shape is changed
+RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+	constructor: RectAreaLight,
+
+	isRectAreaLight: true,
+
+	copy: function ( source ) {
+
+		Light.prototype.copy.call( this, source );
+
+		this.width = source.width;
+		this.height = source.height;
+
+		// this.shadow = source.shadow.clone();
+
+		return this;
+
+	}
+
+} );
+
+export { RectAreaLight };

+ 18 - 0
src/lights/RectAreaLightShadow.js

@@ -0,0 +1,18 @@
+/**
+ * @author aallison / http://github.com/abelnation
+ */
+
+THREE.RectAreaLightShadow = function () {
+
+	THREE.LightShadow.call( this, new THREE.PerspectiveCamera( 50, 1, 0.5, 500 ) );
+
+};
+
+THREE.RectAreaLightShadow.prototype = Object.create( THREE.LightShadow.prototype );
+THREE.RectAreaLightShadow.prototype.constructor = THREE.RectAreaLightShadow;
+
+THREE.RectAreaLightShadow.prototype.update = function ( light ) {
+
+	// TODO (abelnation): implement
+
+};

+ 15 - 0
src/math/Matrix4.js

@@ -795,6 +795,21 @@ Matrix4.prototype = {
 
 	},
 
+	makeShear: function ( x, y, z ) {
+
+		this.set(
+
+			1, y, z, 0,
+			x, 1, z, 0,
+			x, y, 1, 0,
+			0, 0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
 	compose: function ( position, quaternion, scale ) {
 
 		this.makeRotationFromQuaternion( quaternion );

+ 180 - 0
src/math/Polygon.js

@@ -0,0 +1,180 @@
+/**
+ * @author abelnation / http://github.com/abelnation
+ */
+
+THREE.Polygon = function ( points ) {
+
+	this.points = points;
+
+};
+
+THREE.Polygon.prototype = {
+	constructor: THREE.Polygon,
+
+	// points: list of Vector3 objects
+	set: function ( points ) {
+
+		this.points = [];
+		for ( var i = 0; i < points.length; i++ ) {
+
+			this.points.push( points[ i ].copy() );
+
+		}
+		return this;
+
+	},
+
+	clone: function () {
+
+		var result = new this.constructor();
+		result.copy( this );
+		return result;
+
+	},
+
+	copy: function ( polygon ) {
+
+		this.points = Array.from( polygon.points );
+
+	},
+
+	empty: function () {
+
+		// TODO (abelnation): implement
+		return;
+
+	},
+
+	containsPoint: function ( point ) {
+
+		// TODO (abelnation): implement
+		return false;
+
+	},
+
+	distanceToPoint: function ( point ) {
+
+		// TODO (abelnation): implement
+		return 0;
+
+	},
+
+	intersectsSphere: function ( sphere ) {
+
+		// TODO (abelnation): implement
+		return false;
+
+	},
+
+	intersectsBox: function ( box ) {
+
+		// TODO (abelnation): implement
+		return false;
+
+	},
+
+	intersectsPlane: function ( plane ) {
+
+		// TODO (abelnation): implement
+		return false;
+
+	},
+
+	applyMatrix4: function ( matrix ) {
+
+		// TODO (abelnation): implement
+		return this;
+
+	},
+
+	translate: function ( offset ) {
+
+		// TODO (abelnation): implement
+		return this;
+
+	},
+
+	equals: function ( polygon ) {
+
+		// TODO (abelnation): implement
+		return false;
+
+	}
+};
+
+THREE.Polygon.makeSquare = function ( dim ) {
+
+	return THREE.Polygon.makeRectangle( dim, dim );
+
+};
+
+THREE.Polygon.makeRectangle = function ( width, height ) {
+
+	width = ( width !== undefined ) ? width : 10;
+	height = ( height !== undefined ) ? height : 10;
+
+	var halfWidth = width / 2.0;
+	var halfHeight = height / 2.0;
+
+	return new THREE.Polygon( [
+		new THREE.Vector3( - halfWidth,   halfHeight, 0 ),
+		new THREE.Vector3(   halfWidth,   halfHeight, 0 ),
+		new THREE.Vector3(   halfWidth, - halfHeight, 0 ),
+		new THREE.Vector3( - halfWidth, - halfHeight, 0 )
+	] );
+
+}
+
+THREE.Polygon.makeCircle = function ( radius, numPoints ) {
+
+	radius = ( radius !== undefined ) ? radius : 10.0;
+	numPoints = ( numPoints !== undefined ) ? numPoints : 5;
+
+	var twoPi = Math.PI * 2.0;
+	var theta = 0.0;
+	var dTheta = twoPi / numPoints;
+
+	var points = [];
+	for ( var i = 0; i < numPoints; i ++ ) {
+
+		theta = dTheta * i;
+		points.push( new THREE.Vector3(
+			Math.cos(theta) * radius,
+			Math.sin(theta) * radius,
+			0
+		) );
+
+	}
+
+	return new THREE.Polygon( points );
+
+}
+
+THREE.Polygon.makeStar = function ( numPoints, outerRadius, innerRadius ) {
+
+	numPoints = ( numPoints !== undefined ) ? numPoints : 5;
+	outerRadius = ( outerRadius !== undefined ) ? outerRadius : 10.0;
+	innerRadius = ( innerRadius !== undefined ) ? innerRadius : 5.0;
+
+	var theta;
+	var dTheta = ( Math.PI * 2.0 ) / numPoints / 2.0;
+	var points = [];
+
+	for ( var i = 0; i < numPoints; i ++ ) {
+
+		theta = dTheta * i * 2;
+		points.push( new THREE.Vector3(
+			outerRadius * Math.cos(theta),
+			outerRadius * Math.sin(theta),
+			0 ) );
+
+		theta += dTheta;
+		points.push( new THREE.Vector3(
+			innerRadius * Math.cos(theta),
+			innerRadius * Math.sin(theta),
+			0 ) );
+
+	}
+
+	return new THREE.Polygon( points );
+}

+ 19 - 0
src/math/Spherical.js

@@ -80,6 +80,25 @@ Spherical.prototype = {
 
 	},
 
+	getCartesian: function() {
+		if ( this.radius === 0 ) {
+
+			return new THREE.Vector3();
+
+		} else {
+
+			var sinPhi = Math.sin( this.phi );
+			var cosPhi = Math.cos( this.phi );
+			var sinTheta = Math.sin( this.theta );
+			var cosTheta = Math.cos( this.theta );
+
+			return new THREE.Vector3(
+				this.radius * sinPhi * cosTheta,
+				this.radius * sinPhi * sinTheta,
+				this.radius * cosPhi );
+
+		}
+	}
 };
 
 

+ 20 - 0
src/math/distributions/CosineSphericalDistribution.js

@@ -0,0 +1,20 @@
+/**
+ * @author abelnation / https://github.com/abelnation
+ */
+
+THREE.CosineSphericalDistribution = function () {
+
+	var oneOverPi = 1.0 / Math.PI;
+
+	THREE.SphericalDistribution.call( this, function( pos ) {
+
+		// see: Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
+		//      Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
+		//      https://eheitzresearch.wordpress.com/415-2/
+		return oneOverPi * Math.max( 0, pos.z );
+
+	} );
+}
+
+THREE.CosineSphericalDistribution.prototype = Object.create( THREE.SphericalDistribution.prototype );
+THREE.CosineSphericalDistribution.prototype.constructor = THREE.CosineSphericalDistribution;

+ 92 - 0
src/math/distributions/LinearlyTransformedSphericalDistribution.js

@@ -0,0 +1,92 @@
+/**
+ * @author abelnation / https://github.com/abelnation
+ */
+
+THREE.LinearlyTransformedSphericalDistribution = function( distributionFn, transformMat4 ) {
+
+	THREE.SphericalDistribution.call( this, distributionFn );
+
+	this.transformMat4 = transformMat4 || new THREE.Matrix4();
+	this.transformMat3 = new THREE.Matrix3();
+	this.updateTransform();
+}
+
+// inherit from SphericalDistribution
+THREE.LinearlyTransformedSphericalDistribution.prototype =
+	Object.create( THREE.SphericalDistribution.prototype );
+THREE.LinearlyTransformedSphericalDistribution.prototype.constructor =
+	THREE.LinearlyTransformedSphericalDistribution;
+
+// Overridden methods
+THREE.LinearlyTransformedSphericalDistribution.prototype.valueAtNormalizedPosVec3 = function ( pos ) {
+
+	var transformedPos = pos.clone().applyMatrix3( this.transformInverseMat3 );
+	var transformedPosMag = transformedPos.length();
+
+	// normalize with length (calling normalize would recalculate length)
+	transformedPos.divideScalar(transformedPosMag);
+
+	var distFnValue;
+	if (this.distributionFn instanceof THREE.SphericalDistribution) {
+
+		distFnValue = this.distributionFn.valueAtNormalizedPosVec3( transformedPos );
+
+	} else if (typeof this.distributionFn === 'function') {
+
+		distFnValue = this.distributionFn( transformedPos );
+
+	}
+
+	var angleScalarValue = ( this.transformInverseDet / Math.pow( transformedPosMag, 3 ) );
+	var result = distFnValue * angleScalarValue;
+
+	return result;
+
+}
+
+THREE.LinearlyTransformedSphericalDistribution.prototype.updateTransform = function() {
+
+	this.transformMat3.setFromMatrix4( this.transformMat4 );
+	this.transformInverseMat3 = this.transformMat3.getInverse(this.transformMat3, true);
+	this.transformInverseDet = this.transformInverseMat3.determinant();
+
+}
+
+THREE.LinearlyTransformedSphericalDistribution.prototype.scale = function ( x, y, z ) {
+
+	var scale = new THREE.Matrix4().makeScale( x, y, z );
+	this.transformMat4.multiply( scale );
+	this.updateTransform();
+
+	return this;
+
+}
+
+THREE.LinearlyTransformedSphericalDistribution.prototype.rotate = function ( rx, ry, rz ) {
+
+	var rot = new THREE.Matrix4().makeRotationFromEuler( new THREE.Euler( rx, ry, rz ) );
+	this.transformMat4.multiply( rot );
+	this.updateTransform();
+
+	return this;
+
+}
+
+THREE.LinearlyTransformedSphericalDistribution.prototype.shear = function ( x, y, z ) {
+
+	var shear = new THREE.Matrix4().makeShear( x, y, z );
+	this.transformMat4.multiply( shear );
+	this.updateTransform();
+
+	return this;
+
+}
+
+THREE.LinearlyTransformedSphericalDistribution.prototype.transformMat4 = function ( mat4 ) {
+
+	this.transformMat4 = mat4.clone();
+	this.updateTransform();
+
+	return this;
+
+}

+ 50 - 0
src/math/distributions/SphericalDistribution.js

@@ -0,0 +1,50 @@
+/**
+ * @author abelnation / https://github.com/abelnation
+ */
+
+THREE.SphericalDistribution = function ( distributionFn ) {
+
+	this.distributionFn = distributionFn;
+	this.radius = 1.0;
+
+	this.sphere = new THREE.Sphere( new THREE.Vector3(), this.radius );
+
+};
+
+THREE.SphericalDistribution.prototype = {
+	constructor: THREE.SphericalDistribution,
+
+	valueAtNormalizedPosVec3: function ( normalizedPos ) {
+
+		if (this.distributionFn instanceof THREE.SphericalDistribution) {
+
+			return this.distributionFn.valueAtNormalizedPosVec3( normalizedPos );
+
+		} else if (typeof this.distributionFn === 'function') {
+
+			return this.distributionFn( normalizedPos );
+
+		}
+
+	},
+
+	valueAtPosVec3: function ( pos ) {
+
+		var normalized = this.sphere.clampPoint( pos );
+		return this.valueAtNormalizedPosVec3( normalized );
+
+	},
+
+	valueAtPos: function ( x, y, z ) {
+
+		return this.valueAtPosVec3( new THREE.Vector3( x, y, z ) )
+
+	},
+
+	valueAtSphericalPos: function ( phi, theta ) {
+
+		var spherical = new THREE.Spherical( this.radius, phi, theta );
+		return this.valueAtPosVec3( spherical.getCartesian() );
+
+	}
+};

+ 130 - 90
src/renderers/WebGLRenderer.js

@@ -45,14 +45,14 @@ function WebGLRenderer( parameters ) {
 	parameters = parameters || {};
 
 	var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ),
-	_context = parameters.context !== undefined ? parameters.context : null,
+		_context = parameters.context !== undefined ? parameters.context : null,
 
-	_alpha = parameters.alpha !== undefined ? parameters.alpha : false,
-	_depth = parameters.depth !== undefined ? parameters.depth : true,
-	_stencil = parameters.stencil !== undefined ? parameters.stencil : true,
-	_antialias = parameters.antialias !== undefined ? parameters.antialias : false,
-	_premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
-	_preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false;
+		_alpha = parameters.alpha !== undefined ? parameters.alpha : false,
+		_depth = parameters.depth !== undefined ? parameters.depth : true,
+		_stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+		_antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+		_premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+		_preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false;
 
 	var lights = [];
 
@@ -112,62 +112,63 @@ function WebGLRenderer( parameters ) {
 
 	var _this = this,
 
-	// internal state cache
+		// internal state cache
 
-	_currentProgram = null,
-	_currentRenderTarget = null,
-	_currentFramebuffer = null,
-	_currentMaterialId = - 1,
-	_currentGeometryProgram = '',
-	_currentCamera = null,
+		_currentProgram = null,
+		_currentRenderTarget = null,
+		_currentFramebuffer = null,
+		_currentMaterialId = - 1,
+		_currentGeometryProgram = '',
+		_currentCamera = null,
 
-	_currentScissor = new Vector4(),
-	_currentScissorTest = null,
+		_currentScissor = new Vector4(),
+		_currentScissorTest = null,
 
-	_currentViewport = new Vector4(),
+		_currentViewport = new Vector4(),
 
-	//
+		//
 
-	_usedTextureUnits = 0,
+		_usedTextureUnits = 0,
 
-	//
+		//
 
-	_clearColor = new Color( 0x000000 ),
-	_clearAlpha = 0,
+		_clearColor = new Color( 0x000000 ),
+		_clearAlpha = 0,
 
-	_width = _canvas.width,
-	_height = _canvas.height,
+		_width = _canvas.width,
+		_height = _canvas.height,
 
-	_pixelRatio = 1,
+		_pixelRatio = 1,
 
-	_scissor = new Vector4( 0, 0, _width, _height ),
-	_scissorTest = false,
+		_scissor = new Vector4( 0, 0, _width, _height ),
+		_scissorTest = false,
 
-	_viewport = new Vector4( 0, 0, _width, _height ),
+		_viewport = new Vector4( 0, 0, _width, _height ),
 
-	// frustum
+		// frustum
 
-	_frustum = new Frustum(),
+		_frustum = new Frustum(),
 
-	// clipping
+		// clipping
 
-	_clipping = new WebGLClipping(),
-	_clippingEnabled = false,
-	_localClippingEnabled = false,
+		_clipping = new WebGLClipping(),
+		_clippingEnabled = false,
+		_localClippingEnabled = false,
 
-	_sphere = new Sphere(),
+		_sphere = new Sphere(),
 
-	// camera matrices cache
+		// camera matrices cache
 
-	_projScreenMatrix = new Matrix4(),
+		_projScreenMatrix = new Matrix4(),
 
-	_vector3 = new Vector3(),
+	_vector3 = new THREE.Vector3(),
+	_matrix4 = new THREE.Matrix4(), _matrix42 = new THREE.Matrix4(),
 
-	// light arrays cache
+		// light arrays cache
 
-	_lights = {
+		_lights = {
 
-		hash: '',
+			hash: '',
 
 		ambient: [ 0, 0, 0 ],
 		directional: [],
@@ -176,25 +177,26 @@ function WebGLRenderer( parameters ) {
 		spot: [],
 		spotShadowMap: [],
 		spotShadowMatrix: [],
+		rectArea: [],
 		point: [],
 		pointShadowMap: [],
 		pointShadowMatrix: [],
 		hemi: [],
 
-		shadows: []
+			shadows: []
 
-	},
+		},
 
-	// info
+		// info
 
-	_infoRender = {
+		_infoRender = {
 
-		calls: 0,
-		vertices: 0,
-		faces: 0,
-		points: 0
+			calls: 0,
+			vertices: 0,
+			faces: 0,
+			points: 0
 
-	};
+		};
 
 	this.info = {
 
@@ -620,8 +622,8 @@ function WebGLRenderer( parameters ) {
 			_gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal );
 
 			if ( ! material.isMeshPhongMaterial &&
-			     ! material.isMeshStandardMaterial &&
-			       material.shading === FlatShading ) {
+				! material.isMeshStandardMaterial &&
+				material.shading === FlatShading ) {
 
 				for ( var i = 0, l = object.count * 3; i < l; i += 9 ) {
 
@@ -754,7 +756,7 @@ function WebGLRenderer( parameters ) {
 			}
 
 			program.getUniforms().setValue(
-					_gl, 'morphTargetInfluences', morphInfluences );
+				_gl, 'morphTargetInfluences', morphInfluences );
 
 			updateBuffers = true;
 
@@ -1307,7 +1309,7 @@ function WebGLRenderer( parameters ) {
 			geometry.computeBoundingSphere();
 
 		_sphere.copy( geometry.boundingSphere ).
-			applyMatrix4( object.matrixWorld );
+		applyMatrix4( object.matrixWorld );
 
 		return isSphereViewable( _sphere );
 
@@ -1496,7 +1498,7 @@ function WebGLRenderer( parameters ) {
 		var materialProperties = properties.get( material );
 
 		var parameters = programCache.getParameters(
-				material, _lights, fog, _clipping.numPlanes, _clipping.numIntersection, object );
+			material, _lights, fog, _clipping.numPlanes, _clipping.numIntersection, object );
 
 		var code = programCache.getProgramCode( material, parameters );
 
@@ -1595,8 +1597,8 @@ function WebGLRenderer( parameters ) {
 		var uniforms = materialProperties.__webglShader.uniforms;
 
 		if ( ! material.isShaderMaterial &&
-		     ! material.isRawShaderMaterial ||
-		       material.clipping === true ) {
+			! material.isRawShaderMaterial ||
+			material.clipping === true ) {
 
 			materialProperties.numClippingPlanes = _clipping.numPlanes;
 			materialProperties.numIntersection = _clipping.numIntersection;
@@ -1617,6 +1619,7 @@ function WebGLRenderer( parameters ) {
 			uniforms.ambientLightColor.value = _lights.ambient;
 			uniforms.directionalLights.value = _lights.directional;
 			uniforms.spotLights.value = _lights.spot;
+			uniforms.rectAreaLights.value = _lights.rectArea;
 			uniforms.pointLights.value = _lights.point;
 			uniforms.hemisphereLights.value = _lights.hemi;
 
@@ -1626,12 +1629,13 @@ function WebGLRenderer( parameters ) {
 			uniforms.spotShadowMatrix.value = _lights.spotShadowMatrix;
 			uniforms.pointShadowMap.value = _lights.pointShadowMap;
 			uniforms.pointShadowMatrix.value = _lights.pointShadowMatrix;
+			// TODO (abelnation): add area lights shadow info to uniforms
 
 		}
 
 		var progUniforms = materialProperties.program.getUniforms(),
 			uniformsList =
-					WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
+				WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
 
 		materialProperties.uniformsList = uniformsList;
 
@@ -1668,15 +1672,15 @@ function WebGLRenderer( parameters ) {
 			if ( _localClippingEnabled || camera !== _currentCamera ) {
 
 				var useCache =
-						camera === _currentCamera &&
-						material.id === _currentMaterialId;
+					camera === _currentCamera &&
+					material.id === _currentMaterialId;
 
 				// we might want to call this function with some ClippingGroup
 				// object instead of the material, once it becomes feasible
 				// (#8465, #8379)
 				_clipping.setState(
-						material.clippingPlanes, material.clipIntersection, material.clipShadows,
-						camera, materialProperties, useCache );
+					material.clippingPlanes, material.clipIntersection, material.clipShadows,
+					camera, materialProperties, useCache );
 
 			}
 
@@ -1698,7 +1702,7 @@ function WebGLRenderer( parameters ) {
 
 			} else if ( materialProperties.numClippingPlanes !== undefined &&
 				( materialProperties.numClippingPlanes !== _clipping.numPlanes ||
- 				  materialProperties.numIntersection  !== _clipping.numIntersection ) ) {
+				materialProperties.numIntersection  !== _clipping.numIntersection ) ) {
 
 				material.needsUpdate = true;
 
@@ -1747,7 +1751,7 @@ function WebGLRenderer( parameters ) {
 			if ( capabilities.logarithmicDepthBuffer ) {
 
 				p_uniforms.setValue( _gl, 'logDepthBufFC',
-						2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
+					2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
 
 			}
 
@@ -1769,27 +1773,27 @@ function WebGLRenderer( parameters ) {
 			// (shader material also gets them for the sake of genericity)
 
 			if ( material.isShaderMaterial ||
-			     material.isMeshPhongMaterial ||
-			     material.isMeshStandardMaterial ||
-			     material.envMap ) {
+				material.isMeshPhongMaterial ||
+				material.isMeshStandardMaterial ||
+				material.envMap ) {
 
 				var uCamPos = p_uniforms.map.cameraPosition;
 
 				if ( uCamPos !== undefined ) {
 
 					uCamPos.setValue( _gl,
-							_vector3.setFromMatrixPosition( camera.matrixWorld ) );
+						_vector3.setFromMatrixPosition( camera.matrixWorld ) );
 
 				}
 
 			}
 
 			if ( material.isMeshPhongMaterial ||
-			     material.isMeshLambertMaterial ||
-			     material.isMeshBasicMaterial ||
-			     material.isMeshStandardMaterial ||
-			     material.isShaderMaterial ||
-			     material.skinning ) {
+				material.isMeshLambertMaterial ||
+				material.isMeshBasicMaterial ||
+				material.isMeshStandardMaterial ||
+				material.isShaderMaterial ||
+				material.skinning ) {
 
 				p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
 
@@ -1855,10 +1859,10 @@ function WebGLRenderer( parameters ) {
 			}
 
 			if ( material.isMeshBasicMaterial ||
-			     material.isMeshLambertMaterial ||
-			     material.isMeshPhongMaterial ||
-			     material.isMeshStandardMaterial ||
-			     material.isMeshDepthMaterial ) {
+				material.isMeshLambertMaterial ||
+				material.isMeshPhongMaterial ||
+				material.isMeshStandardMaterial ||
+				material.isMeshDepthMaterial ) {
 
 				refreshUniformsCommon( m_uniforms, material );
 
@@ -1912,7 +1916,7 @@ function WebGLRenderer( parameters ) {
 			}
 
 			WebGLUniforms.upload(
-					_gl, materialProperties.uniformsList, m_uniforms, _this );
+				_gl, materialProperties.uniformsList, m_uniforms, _this );
 
 		}
 
@@ -2205,6 +2209,7 @@ function WebGLRenderer( parameters ) {
 		uniforms.directionalLights.needsUpdate = value;
 		uniforms.pointLights.needsUpdate = value;
 		uniforms.spotLights.needsUpdate = value;
+		uniforms.rectAreaLights.needsUpdate = value;
 		uniforms.hemisphereLights.needsUpdate = value;
 
 	}
@@ -2234,17 +2239,18 @@ function WebGLRenderer( parameters ) {
 	function setupLights( lights, camera ) {
 
 		var l, ll, light,
-		r = 0, g = 0, b = 0,
-		color,
-		intensity,
-		distance,
-		shadowMap,
+			r = 0, g = 0, b = 0,
+			color,
+			intensity,
+			distance,
+			shadowMap,
 
-		viewMatrix = camera.matrixWorldInverse,
+			viewMatrix = camera.matrixWorldInverse,
 
 		directionalLength = 0,
 		pointLength = 0,
 		spotLength = 0,
+		rectAreaLength = 0,
 		hemiLength = 0;
 
 		for ( l = 0, ll = lights.length; l < ll; l ++ ) {
@@ -2320,6 +2326,38 @@ function WebGLRenderer( parameters ) {
 				_lights.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
 				_lights.spot[ spotLength ++ ] = uniforms;
 
+			} else if ( light.isRectAreaLight ) {
+
+				var uniforms = lightCache.get( light );
+
+				// (a) intensity controls irradiance of entire light
+				uniforms.color
+					.copy( color )
+					.multiplyScalar( intensity / ( light.width * light.height ) );
+
+				// (b) intensity controls the radiance per light area
+				// uniforms.color.copy( color ).multiplyScalar( intensity );
+
+				uniforms.position.setFromMatrixPosition( light.matrixWorld );
+				uniforms.position.applyMatrix4( viewMatrix );
+
+				// extract local rotation of light to derive width/height half vectors
+				_matrix42.identity();
+				_matrix4.copy( light.matrixWorld );
+				_matrix4.premultiply( viewMatrix );
+				_matrix42.extractRotation( _matrix4 );
+
+				uniforms.halfWidth.set( light.width * 0.5,                0.0, 0.0 );
+				uniforms.halfHeight.set(              0.0, light.height * 0.5, 0.0 );
+
+				uniforms.halfWidth.applyMatrix4( _matrix42 );
+				uniforms.halfHeight.applyMatrix4( _matrix42 );
+
+				// TODO (abelnation): RectAreaLight distance?
+				// uniforms.distance = distance;
+
+				_lights.rectArea[ rectAreaLength ++ ] = uniforms;
+
 			} else if ( light.isPointLight ) {
 
 				var uniforms = lightCache.get( light );
@@ -2379,10 +2417,12 @@ function WebGLRenderer( parameters ) {
 
 		_lights.directional.length = directionalLength;
 		_lights.spot.length = spotLength;
+		_lights.rectArea.length = rectAreaLength;
 		_lights.point.length = pointLength;
 		_lights.hemi.length = hemiLength;
 
-		_lights.hash = directionalLength + ',' + pointLength + ',' + spotLength + ',' + hemiLength + ',' + _lights.shadows.length;
+		// TODO (sam-g-steel) why aren't we using join
+		_lights.hash = directionalLength + ',' + pointLength + ',' + spotLength + ',' + rectAreaLength + ',' + hemiLength + ',' + _lights.shadows.length;
 
 	}
 
@@ -2484,7 +2524,7 @@ function WebGLRenderer( parameters ) {
 			// currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture
 			// TODO: unify these code paths
 			if ( ( texture && texture.isCubeTexture ) ||
-				 ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) {
+				( Array.isArray( texture.image ) && texture.image.length === 6 ) ) {
 
 				// CompressedTexture can have Array in image :/
 
@@ -2610,8 +2650,8 @@ function WebGLRenderer( parameters ) {
 				}
 
 				if ( textureType !== UnsignedByteType && paramThreeToGL( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // IE11, Edge and Chrome Mac < 52 (#9513)
-				     ! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox
-				     ! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) {
+					! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox
+					! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) {
 
 					console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
 					return;
@@ -2728,7 +2768,7 @@ function WebGLRenderer( parameters ) {
 		}
 
 		if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
-			 p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
+			p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
 
 			extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
 
@@ -2778,4 +2818,4 @@ function WebGLRenderer( parameters ) {
 
 }
 
-export { WebGLRenderer };
+export { WebGLRenderer };

+ 295 - 0
src/renderers/shaders/ShaderChunk/bsdfs.glsl

@@ -110,6 +110,301 @@ vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in Geometric
 
 } // validated
 
+//
+// Rect Area Light BRDF Approximations
+//
+
+// Area light computation code adapted from:
+// http://blog.selfshadow.com/sandbox/ltc.html
+//
+// Based on paper:
+// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
+// By: Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt
+// https://eheitzresearch.wordpress.com/415-2/
+
+vec2 ltcTextureCoords( const in GeometricContext geometry, const in float roughness ) {
+
+	const float LUT_SIZE  = 64.0;
+	const float LUT_SCALE = (LUT_SIZE - 1.0)/LUT_SIZE;
+	const float LUT_BIAS  = 0.5/LUT_SIZE;
+
+	vec3 N = geometry.normal;
+	vec3 V = geometry.viewDir;
+	vec3 P = geometry.position;
+
+	// view angle on surface determines which LTC BRDF values we use
+	float theta = acos( dot( N, V ) );
+
+	// Parameterization of texture:
+	// sqrt(roughness) -> [0,1]
+	// theta -> [0, PI/2]
+	vec2 uv = vec2(
+		sqrt( saturate( roughness ) ),
+		saturate( theta / ( 0.5 * PI ) ) );
+
+	// Ensure we don't have nonlinearities at the look-up table's edges
+	// see: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html
+	//      "Shader Analysis" section
+	uv = uv * LUT_SCALE + LUT_BIAS;
+
+	return uv;
+
+}
+
+void clipQuadToHorizon( inout vec3 L[5], out int n ) {
+
+	// detect clipping config
+	int config = 0;
+	if ( L[0].z > 0.0 ) config += 1;
+	if ( L[1].z > 0.0 ) config += 2;
+	if ( L[2].z > 0.0 ) config += 4;
+	if ( L[3].z > 0.0 ) config += 8;
+
+	// clip
+	n = 0;
+
+	if ( config == 0 ) {
+
+		// clip all
+
+	} else if ( config == 1 ) {
+
+		// V1 clip V2 V3 V4
+		n = 3;
+		L[1] = -L[1].z * L[0] + L[0].z * L[1];
+		L[2] = -L[3].z * L[0] + L[0].z * L[3];
+
+	} else if ( config == 2 ) {
+
+		// V2 clip V1 V3 V4
+		n = 3;
+		L[0] = -L[0].z * L[1] + L[1].z * L[0];
+		L[2] = -L[2].z * L[1] + L[1].z * L[2];
+
+	} else if ( config == 3 ) {
+
+		// V1 V2 clip V3 V4
+		n = 4;
+		L[2] = -L[2].z * L[1] + L[1].z * L[2];
+		L[3] = -L[3].z * L[0] + L[0].z * L[3];
+
+	} else if ( config == 4 ) {
+
+		// V3 clip V1 V2 V4
+		n = 3;
+		L[0] = -L[3].z * L[2] + L[2].z * L[3];
+		L[1] = -L[1].z * L[2] + L[2].z * L[1];
+
+	} else if ( config == 5 ) {
+
+		// V1 V3 clip V2 V4) impossible
+		n = 0;
+
+	} else if ( config == 6 ) {
+
+		// V2 V3 clip V1 V4
+		n = 4;
+		L[0] = -L[0].z * L[1] + L[1].z * L[0];
+		L[3] = -L[3].z * L[2] + L[2].z * L[3];
+
+	} else if ( config == 7 ) {
+
+		// V1 V2 V3 clip V4
+		n = 5;
+		L[4] = -L[3].z * L[0] + L[0].z * L[3];
+		L[3] = -L[3].z * L[2] + L[2].z * L[3];
+
+	} else if ( config == 8 ) {
+
+		// V4 clip V1 V2 V3
+		n = 3;
+		L[0] = -L[0].z * L[3] + L[3].z * L[0];
+		L[1] = -L[2].z * L[3] + L[3].z * L[2];
+		L[2] =  L[3];
+
+	} else if ( config == 9 ) {
+
+		// V1 V4 clip V2 V3
+		n = 4;
+		L[1] = -L[1].z * L[0] + L[0].z * L[1];
+		L[2] = -L[2].z * L[3] + L[3].z * L[2];
+
+	} else if ( config == 10 ) {
+
+		// V2 V4 clip V1 V3) impossible
+		n = 0;
+
+	} else if ( config == 11 ) {
+
+		// V1 V2 V4 clip V3
+		n = 5;
+		L[4] = L[3];
+		L[3] = -L[2].z * L[3] + L[3].z * L[2];
+		L[2] = -L[2].z * L[1] + L[1].z * L[2];
+
+	} else if ( config == 12 ) {
+
+		// V3 V4 clip V1 V2
+		n = 4;
+		L[1] = -L[1].z * L[2] + L[2].z * L[1];
+		L[0] = -L[0].z * L[3] + L[3].z * L[0];
+
+	} else if ( config == 13 ) {
+
+		// V1 V3 V4 clip V2
+		n = 5;
+		L[4] = L[3];
+		L[3] = L[2];
+		L[2] = -L[1].z * L[2] + L[2].z * L[1];
+		L[1] = -L[1].z * L[0] + L[0].z * L[1];
+
+	} else if ( config == 14 ) {
+
+		// V2 V3 V4 clip V1
+		n = 5;
+		L[4] = -L[0].z * L[3] + L[3].z * L[0];
+		L[0] = -L[0].z * L[1] + L[1].z * L[0];
+
+	} else if ( config == 15 ) {
+
+		// V1 V2 V3 V4
+		n = 4;
+
+	}
+
+	if ( n == 3 )
+		L[3] = L[0];
+	if ( n == 4 )
+		L[4] = L[0];
+
+}
+
+// Equation (11) of "Real-Time Polygonal-Light Shading with Linearly Transformed Cosines"
+float integrateLtcBrdfOverRectEdge( vec3 v1, vec3 v2 ) {
+
+	float cosTheta = dot( v1, v2 );
+	float theta = acos( cosTheta );
+	float res = cross( v1, v2 ).z * ( ( theta > 0.001 ) ? theta / sin( theta ) : 1.0 );
+
+	return res;
+
+}
+
+void initRectPoints( const in vec3 pos, const in vec3 halfWidth, const in vec3 halfHeight, out vec3 rectPoints[4] ) {
+
+	rectPoints[0] = pos - halfWidth - halfHeight;
+	rectPoints[1] = pos + halfWidth - halfHeight;
+	rectPoints[2] = pos + halfWidth + halfHeight;
+	rectPoints[3] = pos - halfWidth + halfHeight;
+
+}
+
+vec3 integrateLtcBrdfOverRect( const in GeometricContext geometry, const in mat3 brdfMat, const in vec3 rectPoints[4] ) {
+
+	vec3 N = geometry.normal;
+	vec3 V = geometry.viewDir;
+	vec3 P = geometry.position;
+
+	// construct orthonormal basis around N
+	vec3 T1, T2;
+	T1 = normalize(V - N * dot( V, N ));
+	// TODO (abelnation): I had to negate this cross product to get proper light.  Curious why sample code worked without negation
+	T2 = - cross( N, T1 );
+
+	// rotate area light in (T1, T2, N) basis
+	mat3 brdfWrtSurface = brdfMat * transpose( mat3( T1, T2, N ) );
+
+	// transformed rect relative to surface point
+	vec3 clippedRect[5];
+	clippedRect[0] = brdfWrtSurface * ( rectPoints[0] - P );
+	clippedRect[1] = brdfWrtSurface * ( rectPoints[1] - P );
+	clippedRect[2] = brdfWrtSurface * ( rectPoints[2] - P );
+	clippedRect[3] = brdfWrtSurface * ( rectPoints[3] - P );
+
+	// clip light rect to horizon, resulting in at most 5 points
+	// we do this because we are integrating the BRDF over the hemisphere centered on the surface points normal
+	int n;
+	clipQuadToHorizon(clippedRect, n);
+
+	// light is completely below horizon
+	if ( n == 0 )
+		return vec3( 0, 0, 0 );
+
+	// project clipped rect onto sphere
+	clippedRect[0] = normalize( clippedRect[0] );
+	clippedRect[1] = normalize( clippedRect[1] );
+	clippedRect[2] = normalize( clippedRect[2] );
+	clippedRect[3] = normalize( clippedRect[3] );
+	clippedRect[4] = normalize( clippedRect[4] );
+
+	// integrate
+	// simplified integration only needs to be evaluated for each edge in the polygon
+	float sum = 0.0;
+	sum += integrateLtcBrdfOverRectEdge( clippedRect[0], clippedRect[1] );
+	sum += integrateLtcBrdfOverRectEdge( clippedRect[1], clippedRect[2] );
+	sum += integrateLtcBrdfOverRectEdge( clippedRect[2], clippedRect[3] );
+	if (n >= 4)
+		sum += integrateLtcBrdfOverRectEdge( clippedRect[3], clippedRect[4] );
+	if (n == 5)
+		sum += integrateLtcBrdfOverRectEdge( clippedRect[4], clippedRect[0] );
+
+	// TODO (abelnation): two-sided area light
+	// sum = twoSided ? abs(sum) : max(0.0, sum);
+	sum = max( 0.0, sum );
+	// sum = abs( sum );
+
+	vec3 Lo_i = vec3( sum, sum, sum );
+
+	return Lo_i;
+
+}
+
+vec3 Rect_Area_Light_Specular_Reflectance(
+		const in GeometricContext geometry,
+		const in vec3 lightPos, const in vec3 lightHalfWidth, const in vec3 lightHalfHeight,
+		const in float roughness,
+		const in sampler2D ltcMat, const in sampler2D ltcMag ) {
+
+	vec3 rectPoints[4];
+	initRectPoints( lightPos, lightHalfWidth, lightHalfHeight, rectPoints );
+
+	vec2 uv = ltcTextureCoords( geometry, roughness );
+
+	vec4 brdfLtcApproxParams, t;
+
+	brdfLtcApproxParams = texture2D( ltcMat, uv );
+	t = texture2D( ltcMat, uv );
+
+	float brdfLtcScalar = texture2D( ltcMag, uv ).a;
+
+	// inv(M) matrix referenced by equation (6) in paper
+	mat3 brdfLtcApproxMat = mat3(
+		vec3(   1,   0, t.y ),
+		vec3(   0, t.z,   0 ),
+		vec3( t.w,   0, t.x )
+	);
+
+	vec3 specularReflectance = integrateLtcBrdfOverRect( geometry, brdfLtcApproxMat, rectPoints );
+	specularReflectance *= brdfLtcScalar;
+
+	return specularReflectance;
+
+}
+
+vec3 Rect_Area_Light_Diffuse_Reflectance(
+		const in GeometricContext geometry,
+		const in vec3 lightPos, const in vec3 lightHalfWidth, const in vec3 lightHalfHeight ) {
+
+	vec3 rectPoints[4];
+	initRectPoints( lightPos, lightHalfWidth, lightHalfHeight, rectPoints );
+
+	mat3 diffuseBrdfMat = mat3(1);
+	vec3 diffuseReflectance = integrateLtcBrdfOverRect( geometry, diffuseBrdfMat, rectPoints );
+
+	return diffuseReflectance;
+
+}
+// End RectArea BRDF
 
 // ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
 vec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {

+ 18 - 1
src/renderers/shaders/ShaderChunk/common.glsl

@@ -1,5 +1,6 @@
 #define PI 3.14159265359
 #define PI2 6.28318530718
+#define PI_HALF 1.5707963267949
 #define RECIPROCAL_PI 0.31830988618
 #define RECIPROCAL_PI2 0.15915494
 #define LOG2 1.442695
@@ -39,7 +40,6 @@ struct GeometricContext {
 	vec3 viewDir;
 };
 
-
 vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
 
 	return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
@@ -72,3 +72,20 @@ vec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 poi
 	return lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;
 
 }
+
+float gaussianPdf(in float x, in float sigma) {
+
+	return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;
+
+}
+
+mat3 transpose( const in mat3 v ) {
+
+    mat3 tmp;
+    tmp[0] = vec3(v[0].x, v[1].x, v[2].x);
+    tmp[1] = vec3(v[0].y, v[1].y, v[2].y);
+    tmp[2] = vec3(v[0].z, v[1].z, v[2].z);
+
+    return tmp;
+
+}

+ 10 - 0
src/renderers/shaders/ShaderChunk/lights_lambert_vertex.glsl

@@ -61,6 +61,16 @@ vec3 directLightColor_Diffuse;
 
 #endif
 
+#if NUM_RECT_AREA_LIGHTS > 0
+
+	for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
+
+		// TODO (abelnation): implement
+
+	}
+
+#endif
+
 #if NUM_DIR_LIGHTS > 0
 
 	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {

+ 19 - 1
src/renderers/shaders/ShaderChunk/lights_pars.glsl

@@ -114,12 +114,30 @@ vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {
 			directLight.visible = false;
 
 		}
-
 	}
 
 #endif
 
 
+#if NUM_RECT_AREA_LIGHTS > 0
+
+	struct RectAreaLight {
+		vec3 color;
+		vec3 position;
+		vec3 halfWidth;
+		vec3 halfHeight;
+	};
+
+	// Pre-computed values of LinearTransformedCosine approximation of BRDF
+	// BRDF approximation Texture is 64x64
+	uniform sampler2D ltcMat; // RGBA Float
+	uniform sampler2D ltcMag; // Alpha Float (only has w component)
+
+	uniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];
+
+#endif
+
+
 #if NUM_HEMI_LIGHTS > 0
 
 	struct HemisphereLight {

+ 27 - 0
src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl

@@ -16,6 +16,32 @@ struct BlinnPhongMaterial {
 
 };
 
+#if NUM_RECT_AREA_LIGHTS > 0
+    void RE_Direct_RectArea_BlinnPhong( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
+
+        vec3 matDiffColor = material.diffuseColor;
+        vec3 matSpecColor = material.specularColor;
+        vec3 lightColor   = rectAreaLight.color;
+
+        float roughness = BlinnExponentToGGXRoughness( material.specularShininess );
+
+        // Evaluate Lighting Equation
+        vec3 spec = Rect_Area_Light_Specular_Reflectance(
+                geometry,
+                rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight,
+                roughness,
+                ltcMat, ltcMag );
+        vec3 diff = Rect_Area_Light_Diffuse_Reflectance(
+                geometry,
+                rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight );
+
+        // TODO (abelnation): note why division by 2PI is necessary
+        reflectedLight.directSpecular += lightColor * matSpecColor * spec / PI2;
+        reflectedLight.directDiffuse  += lightColor * matDiffColor * diff / PI2;
+
+    }
+#endif
+
 void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
 
 	float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
@@ -40,6 +66,7 @@ void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in Geometric
 }
 
 #define RE_Direct				RE_Direct_BlinnPhong
+#define RE_Direct_RectArea		RE_Direct_RectArea_BlinnPhong
 #define RE_IndirectDiffuse		RE_IndirectDiffuse_BlinnPhong
 
 #define Material_LightProbeLOD( material )	(0)

+ 24 - 0
src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl

@@ -21,6 +21,29 @@ float clearCoatDHRApprox( const in float roughness, const in float dotNL ) {
 
 }
 
+#if NUM_RECT_AREA_LIGHTS > 0
+    void RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
+
+        vec3 matDiffColor = material.diffuseColor;
+        vec3 matSpecColor = material.specularColor;
+        vec3 lightColor   = rectAreaLight.color;
+        float roughness = material.specularRoughness;
+
+        vec3 spec = Rect_Area_Light_Specular_Reflectance(
+                geometry,
+                rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight,
+                roughness,
+                ltcMat, ltcMag );
+        vec3 diff = Rect_Area_Light_Diffuse_Reflectance(
+                geometry,
+                rectAreaLight.position, rectAreaLight.halfWidth, rectAreaLight.halfHeight );
+
+        reflectedLight.directSpecular += lightColor * matSpecColor * spec;
+        reflectedLight.directDiffuse  += lightColor * matDiffColor * diff;
+
+    }
+#endif
+
 void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
 
 	float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
@@ -77,6 +100,7 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCo
 }
 
 #define RE_Direct				RE_Direct_Physical
+#define RE_Direct_RectArea		RE_Direct_RectArea_Physical
 #define RE_IndirectDiffuse		RE_IndirectDiffuse_Physical
 #define RE_IndirectSpecular		RE_IndirectSpecular_Physical
 

+ 13 - 0
src/renderers/shaders/ShaderChunk/lights_template.glsl

@@ -81,6 +81,19 @@ IncidentLight directLight;
 
 #endif
 
+#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
+
+	RectAreaLight rectAreaLight;
+
+	for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
+
+		rectAreaLight = rectAreaLights[ i ];
+		RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );
+
+	}
+
+#endif
+
 #if defined( RE_IndirectDiffuse )
 
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );

+ 4 - 0
src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl

@@ -21,6 +21,10 @@
 
 	#endif
 
+    #if NUM_RECT_AREA_LIGHTS > 0
+        // TODO (abelnation): create uniforms for area light shadows
+    #endif
+
 	float texture2DCompare( sampler2D depths, vec2 uv, float compare ) {
 
 		return step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );

+ 6 - 0
src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl

@@ -21,4 +21,10 @@
 
 	#endif
 
+    #if NUM_RECT_AREA_LIGHTS > 0
+
+        // TODO (abelnation): uniforms for area light shadows
+
+    #endif
+
 #endif

+ 6 - 0
src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl

@@ -30,4 +30,10 @@
 
 	#endif
 
+    #if NUM_RECT_AREA_LIGHTS > 0
+
+    // TODO (abelnation): update vAreaShadowCoord with area light info
+
+    #endif
+
 #endif

+ 6 - 0
src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl

@@ -43,6 +43,12 @@ float getShadowMask() {
 
 	#endif
 
+	#if NUM_RECT_AREA_LIGHTS > 0
+
+	// TODO (abelnation): update shadow for Area light
+
+	#endif
+
 	#endif
 
 	return shadow;

+ 18 - 3
src/renderers/shaders/UniformsLib.js

@@ -1,6 +1,7 @@
 import { Vector4 } from '../../math/Vector4';
 import { Color } from '../../math/Color';
 import { Vector2 } from '../../math/Vector2';
+import { DataTexture } from '../../textures/DataTexture';
 
 /**
  * Uniforms library for shared webgl shaders
@@ -143,7 +144,22 @@ var UniformsLib = {
 			direction: {},
 			skyColor: {},
 			groundColor: {}
-		} }
+		} },
+
+        // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src
+        rectAreaLights: { type: "sa", value: [], properties: {
+            color: { type: "c" },
+            position: { type: "v3" },
+            width: { type: "v3" },
+            height: { type: "v3" },
+        } }
+        // rectAreaLights: { type: "sa", value: [], properties: {
+        //     color: { type: "c" },
+        //     position: { type: "v3" },
+        //     width: { type: "1f" },
+        //     height: { type: "1f" },
+        //     rotationMatrix: { type: "m4" }
+        // } },
 
 	},
 
@@ -160,5 +176,4 @@ var UniformsLib = {
 
 };
 
-
-export { UniformsLib };
+export { UniformsLib };

+ 13 - 1
src/renderers/webgl/WebGLLights.js

@@ -5,6 +5,7 @@
 import { Color } from '../../math/Color';
 import { Vector3 } from '../../math/Vector3';
 import { Vector2 } from '../../math/Vector2';
+import { Matrix4 } from '../../math/Matrix4';
 
 function WebGLLights() {
 
@@ -75,6 +76,17 @@ function WebGLLights() {
 					};
 					break;
 
+				case 'RectAreaLight':
+					uniforms = {
+						color: new THREE.Color(),
+						position: new THREE.Vector3(),
+						halfWidth: new THREE.Vector3(),
+						halfHeight: new THREE.Vector3(),
+
+						// TODO (abelnation): set RectAreaLight shadow uniforms
+					};
+					break;
+
 			}
 
 			lights[ light.id ] = uniforms;
@@ -88,4 +100,4 @@ function WebGLLights() {
 }
 
 
-export { WebGLLights };
+export { WebGLLights };

+ 3 - 2
src/renderers/webgl/WebGLProgram.js

@@ -144,6 +144,7 @@ function replaceLightNums( string, parameters ) {
 	return string
 		.replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )
 		.replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )
+		.replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )
 		.replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )
 		.replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights );
 
@@ -593,7 +594,7 @@ function WebGLProgram( renderer, code, material, parameters ) {
 		if ( cachedUniforms === undefined ) {
 
 			cachedUniforms =
-					new WebGLUniforms( gl, program, renderer );
+				new WebGLUniforms( gl, program, renderer );
 
 		}
 
@@ -664,4 +665,4 @@ function WebGLProgram( renderer, code, material, parameters ) {
 
 }
 
-export { WebGLProgram };
+export { WebGLProgram };

+ 2 - 1
src/renderers/webgl/WebGLPrograms.js

@@ -30,7 +30,7 @@ function WebGLPrograms( renderer, capabilities ) {
 		"flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning",
 		"maxBones", "useVertexTexture", "morphTargets", "morphNormals",
 		"maxMorphTargets", "maxMorphNormals", "premultipliedAlpha",
-		"numDirLights", "numPointLights", "numSpotLights", "numHemiLights",
+		"numDirLights", "numPointLights", "numSpotLights", "numHemiLights", "numRectAreaLights",
 		"shadowMapEnabled", "shadowMapType", "toneMapping", 'physicallyCorrectLights',
 		"alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking"
 	];
@@ -178,6 +178,7 @@ function WebGLPrograms( renderer, capabilities ) {
 			numDirLights: lights.directional.length,
 			numPointLights: lights.point.length,
 			numSpotLights: lights.spot.length,
+			numRectAreaLights: lights.rectArea.length,
 			numHemiLights: lights.hemi.length,
 
 			numClippingPlanes: nClipPlanes,

+ 7 - 0
src/renderers/webgl/WebGLShadowMap.js

@@ -200,6 +200,13 @@ function WebGLShadowMap( _renderer, _lights, _objects, capabilities ) {
 
 			}
 
+			// TODO (abelnation / sam-g-steel): is this needed?
+			if (shadow && shadow.isRectAreaLightShadow ) {
+
+				shadow.update( light );
+
+			}
+
 			var shadowMap = shadow.map;
 			var shadowMatrix = shadow.matrix;
 

+ 4 - 0
src/textures/Texture.js

@@ -102,6 +102,10 @@ Texture.prototype = {
 		this.unpackAlignment = source.unpackAlignment;
 		this.encoding = source.encoding;
 
+		// TODO (abelnation): this was added as something causes texture constants to be "copied" before they are properly uploaded to GPU
+		this.needsUpdate = source.needsUpdate;
+		this.version = source.version;
+
 		return this;
 
 	},

+ 38 - 0
test/unit/lights/RectAreaLight.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		color: 0xaaaaaa,
+		intensity: 0.5,
+	};
+
+	var lights;
+
+	// TODO (abelnation): verify this works
+
+	QUnit.module( "Lights - RectAreaLight", {
+
+		beforeEach: function() {
+
+			lights = [
+
+				new THREE.RectAreaLight( parameters.color ),
+				new THREE.RectAreaLight( parameters.color, parameters.intensity ),
+				new THREE.RectAreaLight( parameters.color, parameters.intensity, 5.0 ),
+				new THREE.RectAreaLight( parameters.color, parameters.intensity, 5.0, 20.0 ),
+				new THREE.RectAreaLight( parameters.color, parameters.intensity, undefined, 20.0 ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard light tests", function( assert ) {
+
+		runStdLightTests( assert, lights );
+
+	});
+
+})();

+ 2 - 0
utils/converters/fbx/convert_to_threejs.py

@@ -1686,6 +1686,8 @@ def generate_light_object(node):
 
         }
 
+    # TODO (abelnation): handle area lights
+
     return output
 
 def generate_ambient_light(scene):

+ 3 - 0
utils/exporters/blender/addons/io_three/constants.py

@@ -204,7 +204,10 @@ AMBIENT_LIGHT = 'AmbientLight'
 DIRECTIONAL_LIGHT = 'DirectionalLight'
 POINT_LIGHT = 'PointLight'
 SPOT_LIGHT = 'SpotLight'
+# TODO (abelnation): confirm this is correct area light string for exporter
+RECT_AREA_LIGHT = 'RectAreaLight'
 HEMISPHERE_LIGHT = 'HemisphereLight'
+# TODO: RectAreaLight support
 MESH = 'Mesh'
 EMPTY = 'Empty'
 SPRITE = 'Sprite'

+ 4 - 0
utils/exporters/blender/addons/io_three/exporter/api/object.py

@@ -9,6 +9,7 @@ from .constants import (
     EMPTY,
     ARMATURE,
     LAMP,
+    AREA,
     SPOT,
     SUN,
     POINT,
@@ -20,6 +21,7 @@ from .constants import (
     NO_SHADOW,
     ZYX
 )
+# TODO: RectAreaLight support
 
 
 # Blender doesn't seem to have a good way to link a mesh back to the
@@ -306,11 +308,13 @@ def node_type(obj):
     elif obj.type == EMPTY:
         return constants.OBJECT.title()
 
+    # TODO: RectAreaLight support
     dispatch = {
         LAMP: {
             POINT: constants.POINT_LIGHT,
             SUN: constants.DIRECTIONAL_LIGHT,
             SPOT: constants.SPOT_LIGHT,
+            AREA: constants.AREA_LIGHT,
             HEMI: constants.HEMISPHERE_LIGHT
         },
         CAMERA: {

+ 4 - 0
utils/exporters/blender/addons/io_three/exporter/object.py

@@ -52,6 +52,8 @@ class Object(base_classes.BaseNode):
         self[constants.DISTANCE] = 0;
 
         lightType = self[constants.TYPE]
+
+        # TODO (abelnation): handle Area lights
         if lightType == constants.SPOT_LIGHT:
             self[constants.ANGLE] = api.light.angle(self.data)
             self[constants.DECAY] = api.light.falloff(self.data)
@@ -96,6 +98,7 @@ class Object(base_classes.BaseNode):
             else:
                 logger.info("%s has no materials", self.node)
 
+        # TODO (abelnation): handle Area lights
         casts_shadow = (constants.MESH,
                         constants.DIRECTIONAL_LIGHT,
                         constants.SPOT_LIGHT)
@@ -113,6 +116,7 @@ class Object(base_classes.BaseNode):
         camera = (constants.PERSPECTIVE_CAMERA,
                   constants.ORTHOGRAPHIC_CAMERA)
 
+        # TODO (abelnation): handle Area lights
         lights = (constants.AMBIENT_LIGHT,
                   constants.DIRECTIONAL_LIGHT,
                   constants.POINT_LIGHT,

+ 2 - 1
utils/exporters/blender/tests/scripts/js/review.js

@@ -77,8 +77,9 @@ function loadObject( data ) {
 
     var hasLights = false;
 
+    // TODO: RectAreaLight support
     var lights = ['AmbientLight', 'DirectionalLight',
-        'PointLight', 'SpotLight', 'HemisphereLight'];
+        'PointLight', 'SpotLight', 'RectAreaLight', 'HemisphereLight'];
 
     var cameras = ['OrthographicCamera', 'PerspectiveCamera'];
 

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov