Răsfoiți Sursa

Merge pull request #10 from mrdoob/dev

Update
Temdog007 6 ani în urmă
părinte
comite
8fc34512a1

Fișier diff suprimat deoarece este prea mare
+ 18 - 18
build/three.js


+ 21 - 2
docs/scenes/js/material.js

@@ -129,6 +129,20 @@ var diffuseMaps = ( function () {
 
 
 } )();
 } )();
 
 
+var roughnessMaps = ( function () {
+
+	var bricks = textureLoader.load( '../../examples/textures/brick_roughness.jpg' );
+	bricks.wrapT = THREE.RepeatWrapping;
+	bricks.wrapS = THREE.RepeatWrapping;
+	bricks.repeat.set( 9, 1 );
+
+	return {
+		none: null,
+		bricks: bricks
+	};
+
+} )();
+
 var matcaps = ( function () {
 var matcaps = ( function () {
 
 
 	return {
 	return {
@@ -154,6 +168,7 @@ var alphaMaps = ( function () {
 
 
 var envMapKeys = getObjectsKeys( envMaps );
 var envMapKeys = getObjectsKeys( envMaps );
 var diffuseMapKeys = getObjectsKeys( diffuseMaps );
 var diffuseMapKeys = getObjectsKeys( diffuseMaps );
+var roughnessMapKeys = getObjectsKeys( roughnessMaps );
 var matcapKeys = getObjectsKeys( matcaps );
 var matcapKeys = getObjectsKeys( matcaps );
 var alphaMapKeys = getObjectsKeys( alphaMaps );
 var alphaMapKeys = getObjectsKeys( alphaMaps );
 
 
@@ -444,6 +459,7 @@ function guiMeshStandardMaterial( gui, mesh, material, geometry ) {
 		emissive: material.emissive.getHex(),
 		emissive: material.emissive.getHex(),
 		envMaps: envMapKeys[ 0 ],
 		envMaps: envMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
+		roughnessMap: roughnessMapKeys[ 0 ],
 		alphaMap: alphaMapKeys[ 0 ]
 		alphaMap: alphaMapKeys[ 0 ]
 	};
 	};
 
 
@@ -461,9 +477,10 @@ function guiMeshStandardMaterial( gui, mesh, material, geometry ) {
 	folder.add( material, 'fog' );
 	folder.add( material, 'fog' );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
+	folder.add( data, 'roughnessMap', roughnessMapKeys ).onChange( updateTexture( material, 'roughnessMap', roughnessMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 
 
-	// TODO roughnessMap and metalnessMap
+	// TODO metalnessMap
 
 
 }
 }
 
 
@@ -474,6 +491,7 @@ function guiMeshPhysicalMaterial( gui, mesh, material, geometry ) {
 		emissive: material.emissive.getHex(),
 		emissive: material.emissive.getHex(),
 		envMaps: envMapKeys[ 0 ],
 		envMaps: envMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
+		roughnessMap: roughnessMapKeys[ 0 ],
 		alphaMap: alphaMapKeys[ 0 ]
 		alphaMap: alphaMapKeys[ 0 ]
 	};
 	};
 
 
@@ -494,9 +512,10 @@ function guiMeshPhysicalMaterial( gui, mesh, material, geometry ) {
 	folder.add( material, 'fog' );
 	folder.add( material, 'fog' );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
+	folder.add( data, 'roughnessMap', roughnessMapKeys ).onChange( updateTexture( material, 'roughnessMap', roughnessMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 
 
-	// TODO roughnessMap and metalnessMap
+	// TODO metalnessMap
 
 
 }
 }
 
 

+ 3 - 0
editor/index.html

@@ -126,8 +126,11 @@
 		<script src="js/Sidebar.Geometry.CircleGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CircleGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CylinderGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CylinderGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.IcosahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.IcosahedronGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.OctahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.PlaneGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.PlaneGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.RingGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.SphereGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.SphereGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.TetrahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TubeGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TubeGeometry.js"></script>

+ 2 - 2
editor/js/History.js

@@ -173,7 +173,7 @@ History.prototype = {
 
 
 		// Append Undos to History
 		// Append Undos to History
 
 
-		for ( var i = 0 ; i < this.undos.length; i ++ ) {
+		for ( var i = 0; i < this.undos.length; i ++ ) {
 
 
 			if ( this.undos[ i ].hasOwnProperty( "json" ) ) {
 			if ( this.undos[ i ].hasOwnProperty( "json" ) ) {
 
 
@@ -185,7 +185,7 @@ History.prototype = {
 
 
 		// Append Redos to History
 		// Append Redos to History
 
 
-		for ( var i = 0 ; i < this.redos.length; i ++ ) {
+		for ( var i = 0; i < this.redos.length; i ++ ) {
 
 
 			if ( this.redos[ i ].hasOwnProperty( "json" ) ) {
 			if ( this.redos[ i ].hasOwnProperty( "json" ) ) {
 
 

+ 63 - 0
editor/js/Menubar.Add.js

@@ -86,6 +86,22 @@ Menubar.Add = function ( editor ) {
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
+	// Ring
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/ring' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.RingBufferGeometry( 0.5, 1, 8, 1, 0, Math.PI * 2 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Ring';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
 	// Cylinder
 	// Cylinder
 
 
 	var option = new UI.Row();
 	var option = new UI.Row();
@@ -134,6 +150,38 @@ Menubar.Add = function ( editor ) {
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
+	// Octahedron
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/octahedron' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.OctahedronBufferGeometry( 1, 0 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Octahedron';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
+	// Tetrahedron
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/tetrahedron' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.TetrahedronBufferGeometry( 1, 0 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Tetrahedron';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
 	// Torus
 	// Torus
 
 
 	var option = new UI.Row();
 	var option = new UI.Row();
@@ -388,6 +436,21 @@ Menubar.Add = function ( editor ) {
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
+	// OrthographicCamera
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/orthographiccamera' ) );
+	option.onClick( function () {
+
+		var camera = new THREE.OrthographicCamera();
+		camera.name = 'OrthographicCamera';
+
+		editor.execute( new AddObjectCommand( camera ) );
+
+	} );
+	options.add( option );
+
 	return container;
 	return container;
 
 
 };
 };

+ 22 - 19
editor/js/Script.js

@@ -20,6 +20,7 @@ var Script = function ( editor ) {
 	header.add( title );
 	header.add( title );
 
 
 	var buttonSVG = ( function () {
 	var buttonSVG = ( function () {
+
 		var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
 		var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
 		svg.setAttribute( 'width', 32 );
 		svg.setAttribute( 'width', 32 );
 		svg.setAttribute( 'height', 32 );
 		svg.setAttribute( 'height', 32 );
@@ -28,6 +29,7 @@ var Script = function ( editor ) {
 		path.setAttribute( 'stroke', '#fff' );
 		path.setAttribute( 'stroke', '#fff' );
 		svg.appendChild( path );
 		svg.appendChild( path );
 		return svg;
 		return svg;
+
 	} )();
 	} )();
 
 
 	var close = new UI.Element( buttonSVG );
 	var close = new UI.Element( buttonSVG );
@@ -80,7 +82,7 @@ var Script = function ( editor ) {
 
 
 			if ( ! validate( value ) ) return;
 			if ( ! validate( value ) ) return;
 
 
-			if ( typeof( currentScript ) === 'object' ) {
+			if ( typeof ( currentScript ) === 'object' ) {
 
 
 				if ( value !== currentScript.source ) {
 				if ( value !== currentScript.source ) {
 
 
@@ -88,6 +90,7 @@ var Script = function ( editor ) {
 
 
 				}
 				}
 				return;
 				return;
+
 			}
 			}
 
 
 			if ( currentScript !== 'programInfo' ) return;
 			if ( currentScript !== 'programInfo' ) return;
@@ -118,7 +121,7 @@ var Script = function ( editor ) {
 
 
 		}, 300 );
 		}, 300 );
 
 
-	});
+	} );
 
 
 	// prevent backspace from deleting objects
 	// prevent backspace from deleting objects
 	var wrapper = codemirror.getWrapperElement();
 	var wrapper = codemirror.getWrapperElement();
@@ -177,7 +180,7 @@ var Script = function ( editor ) {
 					for ( var i = 0; i < errors.length; i ++ ) {
 					for ( var i = 0; i < errors.length; i ++ ) {
 
 
 						var error = errors[ i ];
 						var error = errors[ i ];
-						error.message = error.message.replace(/Line [0-9]+: /, '');
+						error.message = error.message.replace( /Line [0-9]+: /, '' );
 
 
 					}
 					}
 
 
@@ -189,7 +192,7 @@ var Script = function ( editor ) {
 
 
 					jsonlint.parseError = function ( message, info ) {
 					jsonlint.parseError = function ( message, info ) {
 
 
-						message = message.split('\n')[3];
+						message = message.split( '\n' )[ 3 ];
 
 
 						errors.push( {
 						errors.push( {
 
 
@@ -217,11 +220,11 @@ var Script = function ( editor ) {
 					try {
 					try {
 
 
 						var shaderType = currentScript === 'vertexShader' ?
 						var shaderType = currentScript === 'vertexShader' ?
-								glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
+							glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
 
 
 						glslprep.parseGlsl( string, shaderType );
 						glslprep.parseGlsl( string, shaderType );
 
 
-					} catch( error ) {
+					} catch ( error ) {
 
 
 						if ( error instanceof glslprep.SyntaxError ) {
 						if ( error instanceof glslprep.SyntaxError ) {
 
 
@@ -254,7 +257,7 @@ var Script = function ( editor ) {
 
 
 					for ( var i = 0, n = programs.length; i !== n; ++ i ) {
 					for ( var i = 0, n = programs.length; i !== n; ++ i ) {
 
 
-						var diagnostics = programs[i].diagnostics;
+						var diagnostics = programs[ i ].diagnostics;
 
 
 						if ( diagnostics === undefined ||
 						if ( diagnostics === undefined ||
 								diagnostics.material !== currentObject.material ) continue;
 								diagnostics.material !== currentObject.material ) continue;
@@ -262,7 +265,7 @@ var Script = function ( editor ) {
 						if ( ! diagnostics.runnable ) valid = false;
 						if ( ! diagnostics.runnable ) valid = false;
 
 
 						var shaderInfo = diagnostics[ currentScript ];
 						var shaderInfo = diagnostics[ currentScript ];
-						var lineOffset = shaderInfo.prefix.split(/\r\n|\r|\n/).length;
+						var lineOffset = shaderInfo.prefix.split( /\r\n|\r|\n/ ).length;
 
 
 						while ( true ) {
 						while ( true ) {
 
 
@@ -305,7 +308,7 @@ var Script = function ( editor ) {
 
 
 			return valid !== undefined ? valid : errors.length === 0;
 			return valid !== undefined ? valid : errors.length === 0;
 
 
-		});
+		} );
 
 
 	};
 	};
 
 
@@ -317,23 +320,23 @@ var Script = function ( editor ) {
 	} );
 	} );
 
 
 	codemirror.setOption( 'extraKeys', {
 	codemirror.setOption( 'extraKeys', {
-		'Ctrl-Space': function(cm) { server.complete(cm); },
-		'Ctrl-I': function(cm) { server.showType(cm); },
-		'Ctrl-O': function(cm) { server.showDocs(cm); },
-		'Alt-.': function(cm) { server.jumpToDef(cm); },
-		'Alt-,': function(cm) { server.jumpBack(cm); },
-		'Ctrl-Q': function(cm) { server.rename(cm); },
-		'Ctrl-.': function(cm) { server.selectName(cm); }
+		'Ctrl-Space': function ( cm ) { server.complete( cm ); },
+		'Ctrl-I': function ( cm ) { server.showType( cm ); },
+		'Ctrl-O': function ( cm ) { server.showDocs( cm ); },
+		'Alt-.': function ( cm ) { server.jumpToDef( cm ); },
+		'Alt-,': function ( cm ) { server.jumpBack( cm ); },
+		'Ctrl-Q': function ( cm ) { server.rename( cm ); },
+		'Ctrl-.': function ( cm ) { server.selectName( cm ); }
 	} );
 	} );
 
 
-	codemirror.on( 'cursorActivity', function( cm ) {
+	codemirror.on( 'cursorActivity', function ( cm ) {
 
 
 		if ( currentMode !== 'javascript' ) return;
 		if ( currentMode !== 'javascript' ) return;
 		server.updateArgHints( cm );
 		server.updateArgHints( cm );
 
 
 	} );
 	} );
 
 
-	codemirror.on( 'keypress', function( cm, kb ) {
+	codemirror.on( 'keypress', function ( cm, kb ) {
 
 
 		if ( currentMode !== 'javascript' ) return;
 		if ( currentMode !== 'javascript' ) return;
 		var typed = String.fromCharCode( kb.which || kb.keyCode );
 		var typed = String.fromCharCode( kb.which || kb.keyCode );
@@ -358,7 +361,7 @@ var Script = function ( editor ) {
 
 
 		var mode, name, source;
 		var mode, name, source;
 
 
-		if ( typeof( script ) === 'object' ) {
+		if ( typeof ( script ) === 'object' ) {
 
 
 			mode = 'javascript';
 			mode = 'javascript';
 			name = script.name;
 			name = script.name;

+ 54 - 0
editor/js/Sidebar.Geometry.OctahedronGeometry.js

@@ -0,0 +1,54 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.OctahedronGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// radius
+
+	var radiusRow = new UI.Row();
+	var radius = new UI.Number( parameters.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/octahedron_geometry/radius' ) ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// detail
+
+	var detailRow = new UI.Row();
+	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
+
+	detailRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/octahedron_geometry/detail' ) ).setWidth( '90px' ) );
+	detailRow.add( detail );
+
+	container.add( detailRow );
+
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			radius.getValue(),
+			detail.getValue()
+		) ) );
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.OctahedronBufferGeometry = Sidebar.Geometry.OctahedronGeometry;

+ 95 - 0
editor/js/Sidebar.Geometry.RingGeometry.js

@@ -0,0 +1,95 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.RingGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// innerRadius
+
+	var innerRadiusRow = new UI.Row();
+	var innerRadius = new UI.Number( parameters.innerRadius ).onChange( update );
+
+	innerRadiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/innerRadius' ) ).setWidth( '90px' ) );
+	innerRadiusRow.add( innerRadius );
+
+	container.add( innerRadiusRow );
+
+	// outerRadius
+
+	var outerRadiusRow = new UI.Row();
+	var outerRadius = new UI.Number( parameters.outerRadius ).onChange( update );
+
+	outerRadiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/outerRadius' ) ).setWidth( '90px' ) );
+	outerRadiusRow.add( outerRadius );
+
+	container.add( outerRadiusRow );
+
+	// thetaSegments
+
+	var thetaSegmentsRow = new UI.Row();
+	var thetaSegments = new UI.Integer( parameters.thetaSegments ).setRange( 3, Infinity ).onChange( update );
+
+	thetaSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetaSegments' ) ).setWidth( '90px' ) );
+	thetaSegmentsRow.add( thetaSegments );
+
+	container.add( thetaSegmentsRow );
+
+	// phiSegments
+
+	var phiSegmentsRow = new UI.Row();
+	var phiSegments = new UI.Integer( parameters.phiSegments ).setRange( 3, Infinity ).onChange( update );
+
+	phiSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/phiSegments' ) ).setWidth( '90px' ) );
+	phiSegmentsRow.add( phiSegments );
+
+	container.add( phiSegmentsRow );
+
+	// thetaStart
+
+	var thetaStartRow = new UI.Row();
+	var thetaStart = new UI.Number( parameters.thetaStart * THREE.Math.RAD2DEG ).setStep( 10 ).onChange( update );
+
+	thetaStartRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetastart' ) ).setWidth( '90px' ) );
+	thetaStartRow.add( thetaStart );
+
+	container.add( thetaStartRow );
+
+	// thetaLength
+
+	var thetaLengthRow = new UI.Row();
+	var thetaLength = new UI.Number( parameters.thetaLength * THREE.Math.RAD2DEG ).setStep( 10 ).onChange( update );
+
+	thetaLengthRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetalength' ) ).setWidth( '90px' ) );
+	thetaLengthRow.add( thetaLength );
+
+	container.add( thetaLengthRow );
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			innerRadius.getValue(),
+			outerRadius.getValue(),
+			thetaSegments.getValue(),
+			phiSegments.getValue(),
+			thetaStart.getValue() * THREE.Math.DEG2RAD,
+			thetaLength.getValue() * THREE.Math.DEG2RAD
+		) ) );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.RingBufferGeometry = Sidebar.Geometry.RingGeometry;

+ 55 - 0
editor/js/Sidebar.Geometry.TetrahedronGeometry.js

@@ -0,0 +1,55 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+
+Sidebar.Geometry.TetrahedronGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// radius
+
+	var radiusRow = new UI.Row();
+	var radius = new UI.Number( parameters.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/tetrahedron_geometry/radius' ) ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// detail
+
+	var detailRow = new UI.Row();
+	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
+
+	detailRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/tetrahedron_geometry/detail' ) ).setWidth( '90px' ) );
+	detailRow.add( detail );
+
+	container.add( detailRow );
+
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			radius.getValue(),
+			detail.getValue()
+		) ) );
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.TetrahedronBufferGeometry = Sidebar.Geometry.TetrahedronGeometry;

+ 112 - 6
editor/js/Sidebar.Object.js

@@ -146,6 +146,46 @@ Sidebar.Object = function ( editor ) {
 
 
 	container.add( objectFovRow );
 	container.add( objectFovRow );
 
 
+	// left
+
+	var objectLeftRow = new UI.Row();
+	var objectLeft = new UI.Number().onChange( update );
+
+	objectLeftRow.add( new UI.Text( strings.getKey( 'sidebar/object/left' ) ).setWidth( '90px' ) );
+	objectLeftRow.add( objectLeft );
+
+	container.add( objectLeftRow );
+
+	// right
+
+	var objectRightRow = new UI.Row();
+	var objectRight = new UI.Number().onChange( update );
+
+	objectRightRow.add( new UI.Text( strings.getKey( 'sidebar/object/right' ) ).setWidth( '90px' ) );
+	objectRightRow.add( objectRight );
+
+	container.add( objectRightRow );
+
+	// top
+
+	var objectTopRow = new UI.Row();
+	var objectTop = new UI.Number().onChange( update );
+
+	objectTopRow.add( new UI.Text( strings.getKey( 'sidebar/object/top' ) ).setWidth( '90px' ) );
+	objectTopRow.add( objectTop );
+
+	container.add( objectTopRow );
+
+	// bottom
+
+	var objectBottomRow = new UI.Row();
+	var objectBottom = new UI.Number().onChange( update );
+
+	objectBottomRow.add( new UI.Text( strings.getKey( 'sidebar/object/bottom' ) ).setWidth( '90px' ) );
+	objectBottomRow.add( objectBottom );
+
+	container.add( objectBottomRow );
+
 	// near
 	// near
 
 
 	var objectNearRow = new UI.Row();
 	var objectNearRow = new UI.Row();
@@ -400,15 +440,53 @@ Sidebar.Object = function ( editor ) {
 
 
 			}
 			}
 
 
+			if ( object.left !== undefined && Math.abs( object.left - objectLeft.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'left', objectLeft.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.right !== undefined && Math.abs( object.right - objectRight.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'right', objectRight.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.top !== undefined && Math.abs( object.top - objectTop.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'top', objectTop.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.bottom !== undefined && Math.abs( object.bottom - objectBottom.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'bottom', objectBottom.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
 			if ( object.near !== undefined && Math.abs( object.near - objectNear.getValue() ) >= 0.01 ) {
 			if ( object.near !== undefined && Math.abs( object.near - objectNear.getValue() ) >= 0.01 ) {
 
 
 				editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
 				editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
+				if ( object.isOrthographicCamera ) {
+
+					object.updateProjectionMatrix();
+
+				}
 
 
 			}
 			}
 
 
 			if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
 			if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
 
 
 				editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
 				editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
+				if ( object.isOrthographicCamera ) {
+
+					object.updateProjectionMatrix();
+
+				}
 
 
 			}
 			}
 
 
@@ -518,17 +596,21 @@ Sidebar.Object = function ( editor ) {
 
 
 		var properties = {
 		var properties = {
 			'fov': objectFovRow,
 			'fov': objectFovRow,
+			'left': objectLeftRow,
+			'right': objectRightRow,
+			'top': objectTopRow,
+			'bottom': objectBottomRow,
 			'near': objectNearRow,
 			'near': objectNearRow,
 			'far': objectFarRow,
 			'far': objectFarRow,
 			'intensity': objectIntensityRow,
 			'intensity': objectIntensityRow,
 			'color': objectColorRow,
 			'color': objectColorRow,
 			'groundColor': objectGroundColorRow,
 			'groundColor': objectGroundColorRow,
-			'distance' : objectDistanceRow,
-			'angle' : objectAngleRow,
-			'penumbra' : objectPenumbraRow,
-			'decay' : objectDecayRow,
-			'castShadow' : objectShadowRow,
-			'receiveShadow' : objectReceiveShadow,
+			'distance': objectDistanceRow,
+			'angle': objectAngleRow,
+			'penumbra': objectPenumbraRow,
+			'decay': objectDecayRow,
+			'castShadow': objectShadowRow,
+			'receiveShadow': objectReceiveShadow,
 			'shadow': objectShadowRadius
 			'shadow': objectShadowRadius
 		};
 		};
 
 
@@ -617,6 +699,30 @@ Sidebar.Object = function ( editor ) {
 
 
 		}
 		}
 
 
+		if ( object.left !== undefined ) {
+
+			objectLeft.setValue( object.left );
+
+		}
+
+		if ( object.right !== undefined ) {
+
+			objectRight.setValue( object.right );
+
+		}
+
+		if ( object.top !== undefined ) {
+
+			objectTop.setValue( object.top );
+
+		}
+
+		if ( object.bottom !== undefined ) {
+
+			objectBottom.setValue( object.bottom );
+
+		}
+
 		if ( object.near !== undefined ) {
 		if ( object.near !== undefined ) {
 
 
 			objectNear.setValue( object.near );
 			objectNear.setValue( object.near );

+ 21 - 0
editor/js/Strings.js

@@ -38,8 +38,11 @@ var Strings = function ( config ) {
 			'menubar/add/box': 'Box',
 			'menubar/add/box': 'Box',
 			'menubar/add/circle': 'Circle',
 			'menubar/add/circle': 'Circle',
 			'menubar/add/cylinder': 'Cylinder',
 			'menubar/add/cylinder': 'Cylinder',
+			'menubar/add/ring': 'Ring',
 			'menubar/add/sphere': 'Sphere',
 			'menubar/add/sphere': 'Sphere',
 			'menubar/add/icosahedron': 'Icosahedron',
 			'menubar/add/icosahedron': 'Icosahedron',
+			'menubar/add/octahedron': 'Octahedron',
+			'menubar/add/tetrahedron': 'Tetrahedron',
 			'menubar/add/torus': 'Torus',
 			'menubar/add/torus': 'Torus',
 			'menubar/add/tube': 'Tube',
 			'menubar/add/tube': 'Tube',
 			'menubar/add/torusknot': 'TorusKnot',
 			'menubar/add/torusknot': 'TorusKnot',
@@ -51,6 +54,7 @@ var Strings = function ( config ) {
 			'menubar/add/hemispherelight': 'HemisphereLight',
 			'menubar/add/hemispherelight': 'HemisphereLight',
 			'menubar/add/ambientlight': 'AmbientLight',
 			'menubar/add/ambientlight': 'AmbientLight',
 			'menubar/add/perspectivecamera': 'PerspectiveCamera',
 			'menubar/add/perspectivecamera': 'PerspectiveCamera',
+			'menubar/add/orthographiccamera': 'OrthographicCamera',
 
 
 			'menubar/status/autosave': 'autosave',
 			'menubar/status/autosave': 'autosave',
 
 
@@ -81,6 +85,10 @@ var Strings = function ( config ) {
 			'sidebar/object/rotation': 'Rotation',
 			'sidebar/object/rotation': 'Rotation',
 			'sidebar/object/scale': 'Scale',
 			'sidebar/object/scale': 'Scale',
 			'sidebar/object/fov': 'Fov',
 			'sidebar/object/fov': 'Fov',
+			'sidebar/object/left': 'Left',
+			'sidebar/object/right': 'Right',
+			'sidebar/object/top': 'Top',
+			'sidebar/object/bottom': 'Bottom',
 			'sidebar/object/near': 'Near',
 			'sidebar/object/near': 'Near',
 			'sidebar/object/far': 'Far',
 			'sidebar/object/far': 'Far',
 			'sidebar/object/intensity': 'Intensity',
 			'sidebar/object/intensity': 'Intensity',
@@ -132,6 +140,12 @@ var Strings = function ( config ) {
 			'sidebar/geometry/icosahedron_geometry/radius': 'Radius',
 			'sidebar/geometry/icosahedron_geometry/radius': 'Radius',
 			'sidebar/geometry/icosahedron_geometry/detail': 'Detail',
 			'sidebar/geometry/icosahedron_geometry/detail': 'Detail',
 
 
+			'sidebar/geometry/octahedron_geometry/radius': 'Radius',
+			'sidebar/geometry/octahedron_geometry/detail': 'Detail',
+
+      'sidebar/geometry/tetrahedron_geometry/radius': 'Radius',
+			'sidebar/geometry/tetrahedron_geometry/detail': 'Detail',
+
 			'sidebar/geometry/lathe_geometry/segments': 'Segments',
 			'sidebar/geometry/lathe_geometry/segments': 'Segments',
 			'sidebar/geometry/lathe_geometry/phistart': 'Phi start (°)',
 			'sidebar/geometry/lathe_geometry/phistart': 'Phi start (°)',
 			'sidebar/geometry/lathe_geometry/philength': 'Phi length (°)',
 			'sidebar/geometry/lathe_geometry/philength': 'Phi length (°)',
@@ -142,6 +156,13 @@ var Strings = function ( config ) {
 			'sidebar/geometry/plane_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/plane_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/plane_geometry/heightsegments': 'Height segments',
 			'sidebar/geometry/plane_geometry/heightsegments': 'Height segments',
 
 
+			'sidebar/geometry/ring_geometry/innerRadius': 'Inner Radius',
+			'sidebar/geometry/ring_geometry/outerRadius': 'Outer Radius',
+			'sidebar/geometry/ring_geometry/thetaSegments': 'Theta Segments',
+			'sidebar/geometry/ring_geometry/phiSegments': 'Phi Segments',
+			'sidebar/geometry/ring_geometry/thetastart': 'Theta start',
+			'sidebar/geometry/ring_geometry/thetalength': 'Theta length',
+
 			'sidebar/geometry/sphere_geometry/radius': 'Radius',
 			'sidebar/geometry/sphere_geometry/radius': 'Radius',
 			'sidebar/geometry/sphere_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/sphere_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/sphere_geometry/heightsegments': 'Height segments',
 			'sidebar/geometry/sphere_geometry/heightsegments': 'Height segments',

+ 11 - 3
editor/js/Viewport.js

@@ -503,10 +503,18 @@ var Viewport = function ( editor ) {
 
 
 	signals.viewportCameraChanged.add( function ( viewportCamera ) {
 	signals.viewportCameraChanged.add( function ( viewportCamera ) {
 
 
-		camera = viewportCamera;
+		if ( viewportCamera.isPerspectiveCamera ) {
+
+			viewportCamera.aspect = editor.camera.aspect;
+			viewportCamera.projectionMatrix.copy( editor.camera.projectionMatrix );
+
+		} else if ( ! viewportCamera.isOrthographicCamera ) {
+
+			throw "Invalid camera set as viewport";
 
 
-		camera.aspect = editor.camera.aspect;
-		camera.projectionMatrix.copy( editor.camera.projectionMatrix );
+		}
+
+		camera = viewportCamera;
 
 
 		render();
 		render();
 
 

+ 169 - 15
examples/js/loaders/LDrawLoader.js

@@ -271,7 +271,19 @@ THREE.LDrawLoader = ( function () {
 
 
 			}, onProgress, onError );
 			}, onProgress, onError );
 
 
-			function processObject( text, onProcessed ) {
+			function subobjectLoad( url, onLoad, onProgress, onError, subobject ) {
+
+				var fileLoader = new THREE.FileLoader( scope.manager );
+				fileLoader.setPath( scope.path );
+				fileLoader.load( url, function ( text ) {
+
+					processObject( text, onLoad, subobject );
+
+				}, onProgress, onError );
+
+			}
+
+			function processObject( text, onProcessed, subobject ) {
 
 
 				var parseScope = scope.newParseScopeLevel();
 				var parseScope = scope.newParseScopeLevel();
 				parseScope.url = url;
 				parseScope.url = url;
@@ -284,9 +296,10 @@ THREE.LDrawLoader = ( function () {
 
 
 					scope.subobjectCache[ currentFileName ] = text;
 					scope.subobjectCache[ currentFileName ] = text;
 
 
-
 				}
 				}
 
 
+				parseScope.inverted = subobject !== undefined ? subobject.inverted : false;
+
 				// Parse the object (returns a THREE.Group)
 				// Parse the object (returns a THREE.Group)
 				var objGroup = scope.parse( text );
 				var objGroup = scope.parse( text );
 
 
@@ -372,7 +385,7 @@ THREE.LDrawLoader = ( function () {
 					var cached = scope.subobjectCache[ subobject.originalFileName ];
 					var cached = scope.subobjectCache[ subobject.originalFileName ];
 					if ( cached ) {
 					if ( cached ) {
 
 
-						var subobjectGroup = processObject( cached, sync ? undefined : onSubobjectLoaded );
+						var subobjectGroup = processObject( cached, sync ? undefined : onSubobjectLoaded, subobject );
 						if ( sync ) {
 						if ( sync ) {
 
 
 							addSubobject( subobject, subobjectGroup );
 							addSubobject( subobject, subobjectGroup );
@@ -462,7 +475,15 @@ THREE.LDrawLoader = ( function () {
 					subobject.url = subobjectURL;
 					subobject.url = subobjectURL;
 
 
 					// Load the subobject
 					// Load the subobject
-					scope.load( subobjectURL, onSubobjectLoaded, undefined, onSubobjectError );
+					// Use another file loader here so we can keep track of the subobject information
+					// and use it when processing the next model.
+					var fileLoader = new THREE.FileLoader( scope.manager );
+					fileLoader.setPath( scope.path );
+					fileLoader.load( subobjectURL, function ( text ) {
+
+						processObject( text, onSubobjectLoaded, subobject );
+
+					}, undefined, onSubobjectError );
 
 
 				}
 				}
 
 
@@ -587,6 +608,7 @@ THREE.LDrawLoader = ( function () {
 				subobjects: null,
 				subobjects: null,
 				numSubobjects: 0,
 				numSubobjects: 0,
 				subobjectIndex: 0,
 				subobjectIndex: 0,
+				inverted: false,
 
 
 				// Current subobject
 				// Current subobject
 				currentFileName: null,
 				currentFileName: null,
@@ -897,9 +919,6 @@ THREE.LDrawLoader = ( function () {
 
 
 			}
 			}
 
 
-			// BFC (Back Face Culling) LDraw language meta extension is not implemented, so set all materials double-sided:
-			material.side = THREE.DoubleSide;
-
 			material.transparent = isTransparent;
 			material.transparent = isTransparent;
 			material.opacity = alpha;
 			material.opacity = alpha;
 
 
@@ -1035,6 +1054,11 @@ THREE.LDrawLoader = ( function () {
 
 
 			}
 			}
 
 
+			var bfcCertified = false;
+			var bfcCCW = true;
+			var bfcInverted = false;
+			var bfcCull = true;
+
 			// Parse all line commands
 			// Parse all line commands
 			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 
 
@@ -1137,6 +1161,58 @@ THREE.LDrawLoader = ( function () {
 										currentEmbeddedFileName = lp.getRemainingString();
 										currentEmbeddedFileName = lp.getRemainingString();
 										currentEmbeddedText = '';
 										currentEmbeddedText = '';
 
 
+										bfcCertified = false;
+										bfcCCW = true;
+
+									}
+
+									break;
+
+								case 'BFC':
+
+									// Changes to the backface culling state
+									while ( ! lp.isAtTheEnd() ) {
+
+										var token = lp.getToken();
+
+										switch ( token ) {
+
+											case 'CERTIFY':
+											case 'NOCERTIFY':
+
+												bfcCertified = token === 'CERTIFY';
+												bfcCCW = true;
+
+												break;
+
+											case 'CW':
+											case 'CCW':
+
+												bfcCCW = token === 'CCW';
+
+												break;
+
+											case 'INVERTNEXT':
+
+												bfcInverted = true;
+
+												break;
+
+											case 'CLIP':
+											case 'NOCLIP':
+
+											  bfcCull = token === 'CLIP';
+
+												break;
+
+											default:
+
+												console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
+
+												break;
+
+										}
+
 									}
 									}
 
 
 									break;
 									break;
@@ -1198,6 +1274,14 @@ THREE.LDrawLoader = ( function () {
 
 
 						}
 						}
 
 
+						// If the scale of the object is negated then the triangle winding order
+						// needs to be flipped.
+						if ( scope.separateObjects === false && matrix.determinant() < 0 ) {
+
+							bfcInverted = ! bfcInverted;
+
+						}
+
 						subobjects.push( {
 						subobjects.push( {
 							material: material,
 							material: material,
 							matrix: matrix,
 							matrix: matrix,
@@ -1205,9 +1289,12 @@ THREE.LDrawLoader = ( function () {
 							originalFileName: fileName,
 							originalFileName: fileName,
 							locationState: LDrawLoader.FILE_LOCATION_AS_IS,
 							locationState: LDrawLoader.FILE_LOCATION_AS_IS,
 							url: null,
 							url: null,
-							triedLowerCase: false
+							triedLowerCase: false,
+							inverted: bfcInverted !== currentParseScope.inverted
 						} );
 						} );
 
 
+						bfcInverted = false;
+
 						break;
 						break;
 
 
 					// Line type 2: Line segment
 					// Line type 2: Line segment
@@ -1229,14 +1316,45 @@ THREE.LDrawLoader = ( function () {
 
 
 						var material = parseColourCode( lp );
 						var material = parseColourCode( lp );
 
 
+						var inverted = currentParseScope.inverted;
+						var ccw = bfcCCW !== inverted;
+						var doubleSided = ! bfcCertified || ! bfcCull;
+						var v0, v1, v2;
+
+						if ( ccw === true ) {
+
+							v0 = parseVector( lp );
+							v1 = parseVector( lp );
+							v2 = parseVector( lp );
+
+						} else {
+
+							v2 = parseVector( lp );
+							v1 = parseVector( lp );
+							v0 = parseVector( lp );
+
+						}
+
 						triangles.push( {
 						triangles.push( {
 							material: material,
 							material: material,
 							colourCode: material.userData.code,
 							colourCode: material.userData.code,
-							v0: parseVector( lp ),
-							v1: parseVector( lp ),
-							v2: parseVector( lp )
+							v0: v0,
+							v1: v1,
+							v2: v2
 						} );
 						} );
 
 
+						if ( doubleSided === true ) {
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v2,
+								v2: v1
+							} );
+
+						}
+
 						break;
 						break;
 
 
 					// Line type 4: Quadrilateral
 					// Line type 4: Quadrilateral
@@ -1244,10 +1362,26 @@ THREE.LDrawLoader = ( function () {
 
 
 						var material = parseColourCode( lp );
 						var material = parseColourCode( lp );
 
 
-						var v0 = parseVector( lp );
-						var v1 = parseVector( lp );
-						var v2 = parseVector( lp );
-						var v3 = parseVector( lp );
+						var inverted = currentParseScope.inverted;
+						var ccw = bfcCCW !== inverted;
+						var doubleSided = ! bfcCertified || ! bfcCull;
+						var v0, v1, v2, v3;
+
+						if ( ccw === true ) {
+
+							v0 = parseVector( lp );
+							v1 = parseVector( lp );
+							v2 = parseVector( lp );
+							v3 = parseVector( lp );
+
+						} else {
+
+							v3 = parseVector( lp );
+							v2 = parseVector( lp );
+							v1 = parseVector( lp );
+							v0 = parseVector( lp );
+
+						}
 
 
 						triangles.push( {
 						triangles.push( {
 							material: material,
 							material: material,
@@ -1265,6 +1399,26 @@ THREE.LDrawLoader = ( function () {
 							v2: v3
 							v2: v3
 						} );
 						} );
 
 
+						if ( doubleSided === true ) {
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v2,
+								v2: v1
+							} );
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v3,
+								v2: v2
+							} );
+
+						}
+
 						break;
 						break;
 
 
 					// Line type 5: Optional line
 					// Line type 5: Optional line

+ 21 - 5
examples/js/loaders/VRMLLoader.js

@@ -512,7 +512,9 @@ THREE.VRMLLoader.prototype = {
 
 
 					}
 					}
 
 
-					node[ fieldName ] = property;
+					// VRMLLoader does not support text so it can't process the "string" property yet
+
+					if ( fieldName !== 'string' ) node[ fieldName ] = property;
 
 
 				}
 				}
 
 
@@ -825,6 +827,16 @@ THREE.VRMLLoader.prototype = {
 
 
 						parent.geometry = new THREE.SphereBufferGeometry( data.radius );
 						parent.geometry = new THREE.SphereBufferGeometry( data.radius );
 
 
+					} else if ( data.nodeType === 'IndexedLineSet' ) {
+
+						console.warn( 'THREE.VRMLLoader: IndexedLineSet not supported yet.' );
+						parent.parent.remove( parent ); // since the loader is not able to parse the geometry, remove the respective 3D object
+
+					} else if ( data.nodeType === 'Text' ) {
+
+						console.warn( 'THREE.VRMLLoader: Text not supported yet.' );
+						parent.parent.remove( parent );
+
 					} else if ( data.nodeType === 'IndexedFaceSet' ) {
 					} else if ( data.nodeType === 'IndexedFaceSet' ) {
 
 
 						var geometry = new THREE.BufferGeometry();
 						var geometry = new THREE.BufferGeometry();
@@ -1244,13 +1256,13 @@ THREE.VRMLLoader.prototype = {
 
 
 		// some lines do not have breaks
 		// some lines do not have breaks
 
 
-		for ( var i = lines.length - 1; i > - 1; i -- ) {
-
-			var line = lines[ i ];
+		for ( var i = lines.length - 1; i > 0; i -- ) {
 
 
 			// The # symbol indicates that all subsequent text, until the end of the line is a comment,
 			// The # symbol indicates that all subsequent text, until the end of the line is a comment,
 			// and should be ignored. (see http://gun.teipir.gr/VRML-amgem/spec/part1/grammar.html)
 			// and should be ignored. (see http://gun.teipir.gr/VRML-amgem/spec/part1/grammar.html)
-			line = line.replace( /(#.*)/, '' );
+			lines[ i ] = lines[ i ].replace( /(#.*)/, '' );
+
+			var line = lines[ i ];
 
 
 			// split lines with {..{ or {..[ - some have both
 			// split lines with {..{ or {..[ - some have both
 			if ( /{.*[{\[]/.test( line ) ) {
 			if ( /{.*[{\[]/.test( line ) ) {
@@ -1270,6 +1282,8 @@ THREE.VRMLLoader.prototype = {
 
 
 			}
 			}
 
 
+			line = lines[ i ];
+
 			if ( /}.*}/.test( line ) ) {
 			if ( /}.*}/.test( line ) ) {
 
 
 				// split lines with }..}
 				// split lines with }..}
@@ -1280,6 +1294,8 @@ THREE.VRMLLoader.prototype = {
 
 
 			}
 			}
 
 
+			line = lines[ i ];
+
 			if ( /^\b[^\s]+\b$/.test( line.trim() ) ) {
 			if ( /^\b[^\s]+\b$/.test( line.trim() ) ) {
 
 
 				// prevent lines with single words like "coord" or "geometry", see #12209
 				// prevent lines with single words like "coord" or "geometry", see #12209

+ 12 - 3
examples/js/nodes/accessors/NormalNode.js

@@ -43,7 +43,8 @@ NormalNode.prototype.generate = function ( builder, output ) {
 
 
 		case NormalNode.LOCAL:
 		case NormalNode.LOCAL:
 
 
-			builder.requires.normal = true;
+			// to use vObjectNormal as vertex normal
+			//builder.requires.normal = true;
 
 
 			result = 'normal';
 			result = 'normal';
 
 
@@ -51,9 +52,17 @@ NormalNode.prototype.generate = function ( builder, output ) {
 
 
 		case NormalNode.WORLD:
 		case NormalNode.WORLD:
 
 
-			builder.requires.worldNormal = true;
+			if ( builder.isShader( 'vertex' ) ) {
 
 
-			result = builder.isShader( 'vertex' ) ? '( modelMatrix * vec4( objectNormal, 0.0 ) ).xyz' : 'vWNormal';
+				return '( modelMatrix * vec4( objectNormal, 0.0 ) ).xyz';
+
+			} else {
+
+				builder.requires.worldNormal = true;
+
+				result = 'vWNormal';
+
+			}
 
 
 			break;
 			break;
 
 

+ 20 - 4
examples/js/nodes/accessors/PositionNode.js

@@ -59,17 +59,33 @@ PositionNode.prototype.generate = function ( builder, output ) {
 
 
 		case PositionNode.LOCAL:
 		case PositionNode.LOCAL:
 
 
-			builder.requires.position = true;
+			if ( builder.isShader( 'vertex' ) ) {
 
 
-			result = builder.isShader( 'vertex' ) ? 'transformed' : 'vPosition';
+				result = 'transformed';
+
+			} else {
+
+				builder.requires.position = true;
+
+				result = 'vPosition';
+
+			}
 
 
 			break;
 			break;
 
 
 		case PositionNode.WORLD:
 		case PositionNode.WORLD:
 
 
-			builder.requires.worldPosition = true;
+			if ( builder.isShader( 'vertex' ) ) {
+
+				return '( modelMatrix * vec4( transformed, 1.0 ) ).xyz';
+
+			} else {
+
+				builder.requires.worldPosition = true;
+
+				result = 'vWPosition';
 
 
-			result = 'vWPosition';
+			}
 
 
 			break;
 			break;
 
 

+ 7 - 4
examples/js/nodes/accessors/ResolutionNode.js

@@ -8,6 +8,8 @@ function ResolutionNode() {
 
 
 	Vector2Node.call( this );
 	Vector2Node.call( this );
 
 
+	this.size = new THREE.Vector2();
+
 }
 }
 
 
 ResolutionNode.prototype = Object.create( Vector2Node.prototype );
 ResolutionNode.prototype = Object.create( Vector2Node.prototype );
@@ -18,11 +20,12 @@ ResolutionNode.prototype.updateFrame = function ( frame ) {
 
 
 	if ( frame.renderer ) {
 	if ( frame.renderer ) {
 
 
-		var size = frame.renderer.getSize(),
-			pixelRatio = frame.renderer.getPixelRatio();
+		frame.renderer.getSize( this.size );
+
+		var pixelRatio = frame.renderer.getPixelRatio();
 
 
-		this.x = size.width * pixelRatio;
-		this.y = size.height * pixelRatio;
+		this.x = this.size.width * pixelRatio;
+		this.y = this.size.height * pixelRatio;
 
 
 	} else {
 	} else {
 
 

BIN
examples/models/sea3d/morph.sea


BIN
examples/models/sea3d/morph.tjs.sea


+ 207 - 0
examples/webgl_lights_lightprobe.html

@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - lights - light probe</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;
+				color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			a {
+				color: #ffa;
+				font-weight: bold;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 0; /* to not conflict with dat.gui */
+				display:block;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl lights - light probe
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/WebGL.js"></script>
+
+		<script>
+
+			if ( WEBGL.isWebGLAvailable() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGLErrorMessage() );
+
+			}
+
+			var mesh, renderer, scene, camera;
+
+			var gui;
+
+			var ambientLight;
+			var lightProbe;
+			var directionalLight;
+
+			// linear color space
+			var API = {
+				ambientLightIntensity: 0.0,
+				lightProbeIntensity: 0.3,
+				directionalLightIntensity: 0.2,
+				envMapIntensity: 1
+			};
+
+			init();
+
+			function init() {
+
+				// renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				// tone mapping
+				//renderer.toneMapping = THREE.LinearToneMapping;
+			 	//renderer.toneMappingExposure = API.exposure;
+
+				// gamma
+				renderer.gammaOutput = true;
+				renderer.gammaFactor = 2.2; // approximate sRGB
+
+				// scene
+				scene = new THREE.Scene();
+
+				// camera
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( - 15, 0, 20 );
+
+				// controls
+				var controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
+				controls.minDistance = 10;
+				controls.maxDistance = 50;
+				controls.enablePan = false;
+
+				// ambient
+				ambientLight = new THREE.AmbientLight( 0xffffff, API.ambientLightIntensity );
+				scene.add( ambientLight );
+
+				// probe
+				lightProbe = new THREE.LightProbe( 0xffffff, API.lightProbeIntensity );
+				scene.add( lightProbe );
+
+				// light
+				directionalLight = new THREE.DirectionalLight( 0xffffff, API.directionalLightIntensity );
+				directionalLight.position.set( 10, 10, 10 );
+				scene.add( directionalLight );
+
+				// envmap
+				var genCubeUrls = function ( prefix, postfix ) {
+
+					return [
+						prefix + 'px' + postfix, prefix + 'nx' + postfix,
+						prefix + 'py' + postfix, prefix + 'ny' + postfix,
+						prefix + 'pz' + postfix, prefix + 'nz' + postfix
+					];
+
+				};
+
+				var urls = genCubeUrls( 'textures/cube/pisa/', '.png' );
+
+				new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) {
+
+					cubeTexture.encoding = THREE.sRGBEncoding;
+
+					scene.background = cubeTexture;
+
+					lightProbe.setFromCubeTexture( cubeTexture );
+
+					var geometry = new THREE.SphereBufferGeometry( 5, 64, 32 );
+					//var geometry = new THREE.TorusKnotBufferGeometry( 4, 1.5, 256, 32, 2, 3 );
+
+					var material = new THREE.MeshStandardMaterial( {
+						color: 0xffffff, 
+						metalness: 0,
+						roughness: 0,
+						envMap: cubeTexture,
+						envMapIntensity: API.envMapIntensity,
+					} );
+
+					// mesh
+					mesh = new THREE.Mesh( geometry, material );
+					scene.add( mesh );
+
+					render();
+
+				} );
+
+
+				// gui
+				gui = new dat.GUI();
+
+				gui.width = 300;
+
+				gui.domElement.style.userSelect = 'none';
+
+				var fl = gui.addFolder( 'Intensity' );
+				fl.add( API, 'ambientLightIntensity', 0, 1, 0.02 )
+					.name( 'ambient light')
+					.onChange( function() { ambientLight.intensity = API.ambientLightIntensity; render(); } );
+
+				fl.add( API, 'lightProbeIntensity', 0, 1, 0.02 )
+					.name( 'light probe')
+					.onChange( function() { lightProbe.intensity = API.lightProbeIntensity; render(); } );
+
+				fl.add( API, 'directionalLightIntensity', 0, 1, 0.02 )
+					.name( 'directional light')
+					.onChange( function() { directionalLight.intensity = API.directionalLightIntensity; render(); } );
+
+				fl.add( API, 'envMapIntensity', 0, 1, 0.02 )
+					.name( 'envMap')
+					.onChange( function() { mesh.material.envMapIntensity = API.envMapIntensity; render(); } );
+
+				fl.open();
+
+				// listener
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				render();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 10 - 2
examples/webgl_loader_ldraw.html

@@ -97,7 +97,7 @@
 
 
 				//
 				//
 
 
-				renderer = new THREE.WebGLRenderer();
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				container.appendChild( renderer.domElement );
 				container.appendChild( renderer.domElement );
@@ -108,7 +108,8 @@
 
 
 				guiData = {
 				guiData = {
 					modelFileName: modelFileList[ 'Car' ],
 					modelFileName: modelFileList[ 'Car' ],
-					envMapActivated: false
+					envMapActivated: false,
+					separateObjects: false
 				};
 				};
 
 
 				gui = new dat.GUI();
 				gui = new dat.GUI();
@@ -127,6 +128,12 @@
 
 
 				} );
 				} );
 
 
+				gui.add( guiData, 'separateObjects' ).name( 'Separate Objects' ).onChange( function ( value ) {
+
+					reloadObject( false );
+
+				} );
+
 				window.addEventListener( 'resize', onWindowResize, false );
 				window.addEventListener( 'resize', onWindowResize, false );
 
 
 				progressBarDiv = document.createElement( 'div' );
 				progressBarDiv = document.createElement( 'div' );
@@ -160,6 +167,7 @@
 				showProgressBar();
 				showProgressBar();
 
 
 				var lDrawLoader = new THREE.LDrawLoader();
 				var lDrawLoader = new THREE.LDrawLoader();
+				lDrawLoader.separateObjects = guiData.separateObjects;
 				lDrawLoader
 				lDrawLoader
 					.setPath( ldrawPath )
 					.setPath( ldrawPath )
 					.load( guiData.modelFileName, function ( group2 ) {
 					.load( guiData.modelFileName, function ( group2 ) {

+ 5 - 3
examples/webgl_materials_nodes.html

@@ -455,7 +455,7 @@
 
 
 							// apply material
 							// apply material
 
 
-							mtl.side = THREE.DoubleSide;
+							mtl.side = defaultSide;
 							mtl.needsUpdate = true;
 							mtl.needsUpdate = true;
 
 
 							mesh.material = mtl;
 							mesh.material = mtl;
@@ -766,6 +766,8 @@
 
 
 						mtl = new THREE.PhongNodeMaterial();
 						mtl = new THREE.PhongNodeMaterial();
 
 
+						defaultSide = THREE.FrontSide;
+
 						var intensity = 1.3;
 						var intensity = 1.3;
 						var power = new THREE.FloatNode( 3 );
 						var power = new THREE.FloatNode( 3 );
 						var color = new THREE.ColorNode( 0xFFFFFF );
 						var color = new THREE.ColorNode( 0xFFFFFF );
@@ -1538,6 +1540,8 @@
 
 
 						mtl = new THREE.PhongNodeMaterial();
 						mtl = new THREE.PhongNodeMaterial();
 
 
+						defaultSide = THREE.FrontSide;
+
 						var time = new THREE.TimerNode();
 						var time = new THREE.TimerNode();
 						var uv = new THREE.UVNode();
 						var uv = new THREE.UVNode();
 
 
@@ -1577,8 +1581,6 @@
 						mtl.environment = new THREE.ColorNode( 0xFFFFFF );
 						mtl.environment = new THREE.ColorNode( 0xFFFFFF );
 						mtl.alpha = clouds;
 						mtl.alpha = clouds;
 
 
-						defaultSide = THREE.FrontSide;
-
 						// GUI
 						// GUI
 
 
 						addGui( 'color', mtl.environment.value.getHex(), function ( val ) {
 						addGui( 'color', mtl.environment.value.getHex(), function ( val ) {

+ 2 - 0
src/Three.js

@@ -60,6 +60,7 @@ export { DirectionalLight } from './lights/DirectionalLight.js';
 export { AmbientLight } from './lights/AmbientLight.js';
 export { AmbientLight } from './lights/AmbientLight.js';
 export { LightShadow } from './lights/LightShadow.js';
 export { LightShadow } from './lights/LightShadow.js';
 export { Light } from './lights/Light.js';
 export { Light } from './lights/Light.js';
+export { LightProbe } from './lights/LightProbe.js';
 export { StereoCamera } from './cameras/StereoCamera.js';
 export { StereoCamera } from './cameras/StereoCamera.js';
 export { PerspectiveCamera } from './cameras/PerspectiveCamera.js';
 export { PerspectiveCamera } from './cameras/PerspectiveCamera.js';
 export { OrthographicCamera } from './cameras/OrthographicCamera.js';
 export { OrthographicCamera } from './cameras/OrthographicCamera.js';
@@ -123,6 +124,7 @@ export { Vector3 } from './math/Vector3.js';
 export { Vector2 } from './math/Vector2.js';
 export { Vector2 } from './math/Vector2.js';
 export { Quaternion } from './math/Quaternion.js';
 export { Quaternion } from './math/Quaternion.js';
 export { Color } from './math/Color.js';
 export { Color } from './math/Color.js';
+export { SphericalHarmonics3 } from './math/SphericalHarmonics3.js';
 export { ImmediateRenderObject } from './extras/objects/ImmediateRenderObject.js';
 export { ImmediateRenderObject } from './extras/objects/ImmediateRenderObject.js';
 export { VertexNormalsHelper } from './helpers/VertexNormalsHelper.js';
 export { VertexNormalsHelper } from './helpers/VertexNormalsHelper.js';
 export { SpotLightHelper } from './helpers/SpotLightHelper.js';
 export { SpotLightHelper } from './helpers/SpotLightHelper.js';

+ 1 - 1
src/core/BufferGeometry.js

@@ -716,7 +716,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 
 						for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
 						for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
 
 
-							vector.fromBufferAttribute( morphAttribute, i );
+							vector.fromBufferAttribute( morphAttribute, j );
 
 
 							maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 							maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 
 

+ 164 - 0
src/lights/LightProbe.js

@@ -0,0 +1,164 @@
+import { _Math } from '../math/Math.js';
+import { Vector3 } from '../math/Vector3.js';
+import { Color } from '../math/Color.js';
+import { SphericalHarmonics3 } from '../math/SphericalHarmonics3.js';
+import { Light } from './Light.js';
+
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+// A LightProbe is a source of indirect-diffuse light
+
+function LightProbe( color, intensity ) {
+
+	Light.call( this, color, intensity );
+
+	this.sh = new SphericalHarmonics3();
+
+	this.sh.coefficients[ 0 ].set( this.color.r, this.color.g, this.color.b );
+
+	this.type = 'LightProbe';
+
+}
+
+LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
+
+	constructor: LightProbe,
+
+	isLightProbe: true,
+
+	// https://www.ppsloan.org/publications/StupidSH36.pdf
+	setFromCubeTexture: function ( cubeTexture ) {
+
+		var norm, lengthSq, weight, totalWeight = 0;
+
+		var coord = new Vector3();
+
+		var dir = new Vector3();
+
+		var color = new Color();
+
+		var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+
+		var shCoefficients = this.sh.coefficients;
+
+		for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
+
+			var image = cubeTexture.image[ faceIndex ];
+
+			var width = image.width;
+			var height = image.height;
+
+			var canvas = document.createElement( 'canvas' );
+
+			canvas.width = width;
+			canvas.height = height;
+
+			var context = canvas.getContext( '2d' );
+
+			context.drawImage( image, 0, 0, width, height );
+
+			var imageData = context.getImageData( 0, 0, width, height );
+
+			var data = imageData.data;
+
+			var imageWidth = imageData.width; // assumed to be square
+
+			var pixelSize = 2 / imageWidth;
+
+			for ( var i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
+
+				// pixel color
+				color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 );
+
+				// convert to linear color space
+				color.copySRGBToLinear( color );
+
+				// pixel coordinate on unit cube
+
+				var pixelIndex = i / 4;
+
+				var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
+
+				var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
+
+				switch ( faceIndex ) {
+
+					case 0: coord.set( - 1, row, - col ); break;
+
+					case 1: coord.set( 1, row, col ); break;
+
+					case 2: coord.set( - col, 1, - row ); break;
+
+					case 3: coord.set( - col, - 1, row ); break;
+
+					case 4: coord.set( - col, row, 1 ); break;
+
+					case 5: coord.set( col, row, - 1 ); break;
+
+				}
+
+				// weight assigned to this pixel
+
+				lengthSq = coord.lengthSq();
+
+				weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
+
+				totalWeight += weight;
+
+				// direction vector to this pixel
+				dir.copy( coord ).normalize();
+
+				// evaluate SH basis functions in direction dir
+				SphericalHarmonics3.getBasisAt( dir, shBasis );
+
+				// accummuulate
+				for ( var j = 0; j < 9; j ++ ) {
+
+					shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
+					shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
+					shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
+
+				}
+
+			}
+
+		}
+
+		// normalize
+		norm = ( 4 * Math.PI ) / totalWeight;
+
+		for ( var j = 0; j < 9; j ++ ) {
+
+			shCoefficients[ j ].x *= norm;
+			shCoefficients[ j ].y *= norm;
+			shCoefficients[ j ].z *= norm;
+
+		}
+
+	},
+
+	copy: function ( source ) {
+
+		Light.prototype.copy.call( this, source );
+
+		this.sh.copy( source.sh );
+
+		return this;
+
+	},
+
+	toJSON: function ( meta ) {
+
+		var data = Light.prototype.toJSON.call( this, meta );
+
+		//data.sh = this.sh.toArray(); // todo
+
+		return data;
+
+	}
+
+} );
+
+export { LightProbe };

+ 1 - 1
src/materials/ShaderMaterial.d.ts

@@ -17,7 +17,7 @@ export interface ShaderMaterialParameters extends MaterialParameters {
   uniforms?: any;
   uniforms?: any;
   vertexShader?: string;
   vertexShader?: string;
   fragmentShader?: string;
   fragmentShader?: string;
-  lineWidth?: number;
+  linewidth?: number;
   wireframe?: boolean;
   wireframe?: boolean;
   wireframeLinewidth?: number;
   wireframeLinewidth?: number;
   lights?: boolean;
   lights?: boolean;

+ 211 - 0
src/math/SphericalHarmonics3.js

@@ -0,0 +1,211 @@
+import { Vector3 } from './Vector3.js';
+
+/**
+ * @author bhouston / http://clara.io
+ * @author WestLangley / http://github.com/WestLangley
+ *
+ * Primary reference:
+ *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
+ *
+ * Secondary reference:
+ *   https://www.ppsloan.org/publications/StupidSH36.pdf
+ */
+
+// 3-band SH defined by 9 coefficients
+
+function SphericalHarmonics3() {
+
+	this.coefficients = [];
+
+	for ( var i = 0; i < 9; i ++ ) {
+
+		this.coefficients.push( new Vector3() );
+
+	}
+
+}
+
+Object.assign( SphericalHarmonics3.prototype, {
+
+	isSphericalHarmonics3: true,
+
+	set: function ( coefficients ) {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			this.coefficients[ i ].copy( coefficients[ i ] );
+
+		}
+
+		return this;
+
+	},
+
+	zero: function () {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			this.coefficients[ i ].set( 0, 0, 0 );
+
+		}
+
+		return this;
+
+	},
+
+	// get the radiance in the direction of the normal
+	// target is a Vector3
+	getAt: function ( normal, target ) {
+
+		// normal is assumed to be unit length
+
+		var x = normal.x, y = normal.y, z = normal.z;
+
+		var coeff = this.coefficients;
+
+		// band 0
+		target = coeff[ 0 ] * 0.282095;
+
+		// band 1
+		target += coeff[ 1 ] * 0.488603 * y;
+		target += coeff[ 2 ] * 0.488603 * z;
+		target += coeff[ 3 ] * 0.488603 * x;
+
+		// band 2
+		target += coeff[ 4 ] * 1.092548 * ( x * y );
+		target += coeff[ 5 ] * 1.092548 * ( y * z );
+		target += coeff[ 6 ] * 0.315392 * ( 3.0 * z * z - 1.0 );
+		target += coeff[ 7 ] * 1.092548 * ( x * z );
+		target += coeff[ 8 ] * 0.546274 * ( x * x - y * y );
+
+		return target;
+
+	},
+
+	// get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
+	// target is a Vector3
+	// https://graphics.stanford.edu/papers/envmap/envmap.pdf
+	getIrradianceAt: function ( normal, target ) {
+
+		// normal is assumed to be unit length
+
+		var x = normal.x, y = normal.y, z = normal.z;
+
+		var coeff = this.coefficients;
+
+		// band 0
+		target = coeff[ 0 ] * 0.886227; // π * 0.282095
+
+		// band 1
+		target += coeff[ 1 ] * 2.0 * 0.511664 * y; // ( 2 * π / 3 ) * 0.488603
+		target += coeff[ 2 ] * 2.0 * 0.511664 * z;
+		target += coeff[ 3 ] * 2.0 * 0.511664 * x;
+
+		// band 2
+		target += coeff[ 4 ] * 2.0 * 0.429043 * x * y; // ( π / 4 ) * 1.092548
+		target += coeff[ 5 ] * 2.0 * 0.429043 * y * z;
+		target += coeff[ 6 ] * ( 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3
+		target += coeff[ 7 ] * 2.0 * 0.429043 * x * z;
+		target += coeff[ 8 ] * 0.429043 * ( x * x - y * y ); // ( π / 4 ) * 0.546274
+
+		return target;
+
+	},
+
+	add: function ( sh ) {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			this.coefficients[ i ].add( sh.coefficients[ i ] );
+
+		}
+
+		return this;
+
+	},
+
+
+	scale: function ( s ) {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			this.coefficients[ i ].multiplyScalar( s );
+
+		}
+
+		return this;
+
+	},
+
+	lerp: function ( sh, alpha ) {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
+
+		}
+
+		return this;
+
+	},
+
+	equals: function ( sh ) {
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
+
+				return false;
+
+			}
+
+		}
+
+		return true;
+
+	},
+
+	copy: function ( sh ) {
+
+		return this.set( sh.coefficients );
+
+	},
+
+	clone: function () {
+
+		return new this.constructor().copy( this );
+
+	}
+
+} );
+
+Object.assign( SphericalHarmonics3, {
+
+	// evaluate the basis functions
+	// shBasis is an Array[ 9 ]
+	getBasisAt: function ( normal, shBasis ) {
+
+		// normal is assumed to be unit length
+
+		var x = normal.x, y = normal.y, z = normal.z;
+
+		// band 0
+		shBasis[ 0 ] = 0.282095;
+
+		// band 1
+		shBasis[ 1 ] = 0.488603 * y;
+		shBasis[ 2 ] = 0.488603 * z;
+		shBasis[ 3 ] = 0.488603 * x;
+
+		// band 2
+		shBasis[ 4 ] = 1.092548 * x * y;
+		shBasis[ 5 ] = 1.092548 * y * z;
+		shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 );
+		shBasis[ 7 ] = 1.092548 * x * z;
+		shBasis[ 8 ] = 0.546274 * ( x * x - y * y );
+
+	}
+
+} );
+
+export { SphericalHarmonics3 };

+ 2 - 0
src/renderers/WebGLRenderer.js

@@ -1599,6 +1599,7 @@ function WebGLRenderer( parameters ) {
 			// wire up the material to this renderer's lighting state
 			// wire up the material to this renderer's lighting state
 
 
 			uniforms.ambientLightColor.value = lights.state.ambient;
 			uniforms.ambientLightColor.value = lights.state.ambient;
+			uniforms.lightProbe.value = lights.state.probe;
 			uniforms.directionalLights.value = lights.state.directional;
 			uniforms.directionalLights.value = lights.state.directional;
 			uniforms.spotLights.value = lights.state.spot;
 			uniforms.spotLights.value = lights.state.spot;
 			uniforms.rectAreaLights.value = lights.state.rectArea;
 			uniforms.rectAreaLights.value = lights.state.rectArea;
@@ -2402,6 +2403,7 @@ function WebGLRenderer( parameters ) {
 	function markUniformsLightsNeedsUpdate( uniforms, value ) {
 	function markUniformsLightsNeedsUpdate( uniforms, value ) {
 
 
 		uniforms.ambientLightColor.needsUpdate = value;
 		uniforms.ambientLightColor.needsUpdate = value;
+		uniforms.lightProbe.needsUpdate = value;
 
 
 		uniforms.directionalLights.needsUpdate = value;
 		uniforms.directionalLights.needsUpdate = value;
 		uniforms.pointLights.needsUpdate = value;
 		uniforms.pointLights.needsUpdate = value;

+ 2 - 0
src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js

@@ -103,6 +103,8 @@ IncidentLight directLight;
 
 
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
 
 
+	irradiance += getLightProbeIrradiance( lightProbe, geometry );
+
 	#if ( NUM_HEMI_LIGHTS > 0 )
 	#if ( NUM_HEMI_LIGHTS > 0 )
 
 
 		#pragma unroll_loop
 		#pragma unroll_loop

+ 38 - 0
src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js

@@ -1,5 +1,43 @@
 export default /* glsl */`
 export default /* glsl */`
 uniform vec3 ambientLightColor;
 uniform vec3 ambientLightColor;
+uniform vec3 lightProbe[ 9 ];
+
+// get the irradiance (radiance convolved with cosine lobe) at the point 'normal' on the unit sphere
+// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf
+vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {
+
+	// normal is assumed to have unit length
+
+	float x = normal.x, y = normal.y, z = normal.z;
+
+	// band 0
+	vec3 result = shCoefficients[ 0 ] * 0.886227;
+
+	// band 1
+	result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;
+	result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;
+	result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;
+
+	// band 2
+	result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;
+	result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;
+	result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );
+	result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;
+	result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );
+
+	return result;
+
+}
+
+vec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {
+
+	vec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );
+
+	vec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );
+
+	return irradiance;
+
+}
 
 
 vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {
 vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {
 
 

+ 2 - 0
src/renderers/shaders/UniformsLib.js

@@ -109,6 +109,8 @@ var UniformsLib = {
 
 
 		ambientLightColor: { value: [] },
 		ambientLightColor: { value: [] },
 
 
+		lightProbe: { value: [] },
+
 		directionalLights: { value: [], properties: {
 		directionalLights: { value: [], properties: {
 			direction: {},
 			direction: {},
 			color: {},
 			color: {},

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

@@ -121,6 +121,7 @@ function WebGLLights() {
 		},
 		},
 
 
 		ambient: [ 0, 0, 0 ],
 		ambient: [ 0, 0, 0 ],
+		probe: [],
 		directional: [],
 		directional: [],
 		directionalShadowMap: [],
 		directionalShadowMap: [],
 		directionalShadowMatrix: [],
 		directionalShadowMatrix: [],
@@ -135,6 +136,8 @@ function WebGLLights() {
 
 
 	};
 	};
 
 
+	for ( var i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+
 	var vector3 = new Vector3();
 	var vector3 = new Vector3();
 	var matrix4 = new Matrix4();
 	var matrix4 = new Matrix4();
 	var matrix42 = new Matrix4();
 	var matrix42 = new Matrix4();
@@ -143,6 +146,8 @@ function WebGLLights() {
 
 
 		var r = 0, g = 0, b = 0;
 		var r = 0, g = 0, b = 0;
 
 
+		for ( var i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
+
 		var directionalLength = 0;
 		var directionalLength = 0;
 		var pointLength = 0;
 		var pointLength = 0;
 		var spotLength = 0;
 		var spotLength = 0;
@@ -167,6 +172,14 @@ function WebGLLights() {
 				g += color.g * intensity;
 				g += color.g * intensity;
 				b += color.b * intensity;
 				b += color.b * intensity;
 
 
+			} else if ( light.isLightProbe ) {
+
+				for ( var j = 0; j < 9; j ++ ) {
+
+					state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
+
+				}
+
 			} else if ( light.isDirectionalLight ) {
 			} else if ( light.isDirectionalLight ) {
 
 
 				var uniforms = cache.get( light );
 				var uniforms = cache.get( light );

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff