Răsfoiți Sursa

Merge remote-tracking branch 'mrdoob/dev' into add-optional-lines-shader

Garrett Johnson 6 ani în urmă
părinte
comite
d911645042

+ 3 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -81,8 +81,11 @@
 						<li>DeviceOrientationControls</li>
 						<li>DragControls</li>
 						<li>EditorControls</li>
+						<li>FirstPersonControls</li>
+						<li>FlyControls</li>
 						<li>MapControls</li>
 						<li>OrbitControls</li>
+						<li>OrthographicTrackballControls</li>
 						<li>PointerLockControls</li>
 						<li>TrackballControls</li>
 					</ul>

+ 1 - 0
examples/files.js

@@ -132,6 +132,7 @@ var files = {
 		"webgl_loader_texture_hdr",
 		"webgl_loader_texture_ktx",
 		"webgl_loader_texture_pvrtc",
+		"webgl_loader_texture_rgbm",
 		"webgl_loader_texture_tga",
 		"webgl_loader_ttf",
 		"webgl_loader_vrm",

+ 0 - 5
examples/index.html

@@ -55,11 +55,6 @@
 				<input placeholder="Search" type="text" id="filter" autocorrect="off" autocapitalize="off" spellcheck="false" />
 				<div id="exitSearchButton"></div>
 
-				<select id="language">
-					<option value="en">en</option>
-					<option value="zh">zh</option>
-				</select>
-
 				<div id="content"></div>
 			</div>
 

+ 48 - 0
examples/js/loaders/ColladaLoader.js

@@ -3814,6 +3814,32 @@ THREE.ColladaLoader.prototype = {
 
 		}
 
+		// convert the parser error element into text with each child elements text
+		// separated by new lines.
+		function parserErrorToText( parserError ) {
+
+			var result = '';
+			var stack = [ parserError ];
+			while ( stack.length ) {
+
+				var node = stack.shift();
+				if ( node.nodeType === Node.TEXT_NODE ) {
+
+					result += node.textContent;
+
+				} else {
+
+					result += '\n';
+					stack.push.apply( stack, node.childNodes );
+
+				}
+
+			}
+
+			return result.trim();
+
+		}
+
 		if ( text.length === 0 ) {
 
 			return { scene: new THREE.Scene() };
@@ -3824,6 +3850,28 @@ THREE.ColladaLoader.prototype = {
 
 		var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
 
+		var parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ];
+		if ( parserError !== undefined ) {
+
+			// Chrome will return parser error with a div in it
+			var errorElement = getElementsByTagName( parserError, 'div' )[ 0 ];
+			var errorText;
+			if ( errorElement ) {
+
+				errorText = errorElement.textContent;
+
+			} else {
+
+				errorText = parserErrorToText( parserError );
+
+			}
+
+			console.error( 'ColladaLoader: Failed to parse collada file.\n', errorText );
+
+			return null;
+
+		}
+
 		// metadata
 
 		var version = collada.getAttribute( 'version' );

+ 23 - 5
examples/js/loaders/LDrawLoader.js

@@ -144,6 +144,13 @@ THREE.LDrawLoader = ( function () {
 
 		}
 
+		// NOTE: Some of the normals wind up being skewed in an unexpected way because
+		// quads provide more "influence" to some vertex normals than a triangle due to
+		// the fact that a quad is made up of two triangles and all triangles are weighted
+		// equally. To fix this quads could be tracked separately so their vertex normals
+		// are weighted appropriately or we could try only adding a normal direction
+		// once per normal.
+
 		// Iterate until we've tried to connect all triangles to share normals
 		while ( true ) {
 
@@ -228,11 +235,22 @@ THREE.LDrawLoader = ( function () {
 							var otherHash = hashEdge( otherV0, otherV1 );
 							if ( otherHash === reverseHash ) {
 
-								otherTri[ `n${ otherIndex }` ] = tri[ `n${ next }` ];
-								otherTri[ `n${ otherNext }` ] = tri[ `n${ index }` ];
+								if ( otherTri[ `n${ otherIndex }` ] === null ) {
+
+									var norm = tri[ `n${ next }` ];
+									otherTri[ `n${ otherIndex }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
+
+								if ( otherTri[ `n${ otherNext }` ] === null ) {
+
+									var norm = tri[ `n${ index }` ];
+									otherTri[ `n${ otherNext }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
 
-								tri[ `n${ next }` ].add( otherTri.faceNormal );
-								tri[ `n${ index }` ].add( otherTri.faceNormal );
 								break;
 
 							}
@@ -1245,7 +1263,7 @@ THREE.LDrawLoader = ( function () {
 
 				case LDrawLoader.FINISH_TYPE_RUBBER:
 
-					// Rubber is best simulated with Lambert
+					// Rubber finish
 					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.9, metalness: 0 } );
 					canHaveEnvMap = false;
 					break;

+ 2 - 0
examples/js/loaders/MTLLoader.js

@@ -14,6 +14,8 @@ THREE.MTLLoader.prototype = {
 
 	constructor: THREE.MTLLoader,
 
+	crossOrigin: 'anonymous',
+
 	/**
 	 * Loads and parses a MTL asset from a URL.
 	 *

+ 32 - 0
examples/jsm/controls/FirstPersonControls.d.ts

@@ -0,0 +1,32 @@
+import {
+  Camera,
+  Vector3
+} from '../../../src/Three';
+
+export class FirstPersonControls {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  enabled: boolean;
+  movementSpeed: number;
+  lookSpeed: number;
+  lookVertical: boolean;
+  autoForward: boolean;
+  activeLook: boolean;
+  heightSpeed: boolean;
+  heightCoef: number;
+  heightMin: number;
+  heightMax: number;
+  constrainVertical: boolean;
+  verticalMin: number;
+  verticalMax: number;
+  autoSpeedFactor: number;
+
+  handleResize(): void;
+  lookAt(x: number | Vector3, y: number, z: number): this;
+  update(delta: number): this;
+  dispose(): void;
+
+}

+ 350 - 0
examples/jsm/controls/FirstPersonControls.js

@@ -0,0 +1,350 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author paulirish / http://paulirish.com/
+ */
+
+import {
+	Math as _Math,
+	Spherical,
+	Vector3
+} from "../../../build/three.module.js";
+
+var FirstPersonControls = function ( object, domElement ) {
+
+	this.object = object;
+
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	this.enabled = true;
+
+	this.movementSpeed = 1.0;
+	this.lookSpeed = 0.005;
+
+	this.lookVertical = true;
+	this.autoForward = false;
+
+	this.activeLook = true;
+
+	this.heightSpeed = false;
+	this.heightCoef = 1.0;
+	this.heightMin = 0.0;
+	this.heightMax = 1.0;
+
+	this.constrainVertical = false;
+	this.verticalMin = 0;
+	this.verticalMax = Math.PI;
+
+	this.autoSpeedFactor = 0.0;
+
+	this.mouseX = 0;
+	this.mouseY = 0;
+
+	this.moveForward = false;
+	this.moveBackward = false;
+	this.moveLeft = false;
+	this.moveRight = false;
+
+	this.mouseDragOn = false;
+
+	this.viewHalfX = 0;
+	this.viewHalfY = 0;
+
+	// private variables
+
+	var lat = 0;
+	var lon = 0;
+
+	var lookDirection = new Vector3();
+	var spherical = new Spherical();
+	var target = new Vector3();
+
+	//
+
+	if ( this.domElement !== document ) {
+
+		this.domElement.setAttribute( 'tabindex', - 1 );
+
+	}
+
+	//
+
+	this.handleResize = function () {
+
+		if ( this.domElement === document ) {
+
+			this.viewHalfX = window.innerWidth / 2;
+			this.viewHalfY = window.innerHeight / 2;
+
+		} else {
+
+			this.viewHalfX = this.domElement.offsetWidth / 2;
+			this.viewHalfY = this.domElement.offsetHeight / 2;
+
+		}
+
+	};
+
+	this.onMouseDown = function ( event ) {
+
+		if ( this.domElement !== document ) {
+
+			this.domElement.focus();
+
+		}
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.activeLook ) {
+
+			switch ( event.button ) {
+
+				case 0: this.moveForward = true; break;
+				case 2: this.moveBackward = true; break;
+
+			}
+
+		}
+
+		this.mouseDragOn = true;
+
+	};
+
+	this.onMouseUp = function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.activeLook ) {
+
+			switch ( event.button ) {
+
+				case 0: this.moveForward = false; break;
+				case 2: this.moveBackward = false; break;
+
+			}
+
+		}
+
+		this.mouseDragOn = false;
+
+	};
+
+	this.onMouseMove = function ( event ) {
+
+		if ( this.domElement === document ) {
+
+			this.mouseX = event.pageX - this.viewHalfX;
+			this.mouseY = event.pageY - this.viewHalfY;
+
+		} else {
+
+			this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
+			this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
+
+		}
+
+	};
+
+	this.onKeyDown = function ( event ) {
+
+		//event.preventDefault();
+
+		switch ( event.keyCode ) {
+
+			case 38: /*up*/
+			case 87: /*W*/ this.moveForward = true; break;
+
+			case 37: /*left*/
+			case 65: /*A*/ this.moveLeft = true; break;
+
+			case 40: /*down*/
+			case 83: /*S*/ this.moveBackward = true; break;
+
+			case 39: /*right*/
+			case 68: /*D*/ this.moveRight = true; break;
+
+			case 82: /*R*/ this.moveUp = true; break;
+			case 70: /*F*/ this.moveDown = true; break;
+
+		}
+
+	};
+
+	this.onKeyUp = function ( event ) {
+
+		switch ( event.keyCode ) {
+
+			case 38: /*up*/
+			case 87: /*W*/ this.moveForward = false; break;
+
+			case 37: /*left*/
+			case 65: /*A*/ this.moveLeft = false; break;
+
+			case 40: /*down*/
+			case 83: /*S*/ this.moveBackward = false; break;
+
+			case 39: /*right*/
+			case 68: /*D*/ this.moveRight = false; break;
+
+			case 82: /*R*/ this.moveUp = false; break;
+			case 70: /*F*/ this.moveDown = false; break;
+
+		}
+
+	};
+
+	this.lookAt = function ( x, y, z ) {
+
+		if ( x.isVector3 ) {
+
+			target.copy( x );
+
+		} else {
+
+			target.set( x, y, z );
+
+		}
+
+		this.object.lookAt( target );
+
+		setOrientation( this );
+
+		return this;
+
+	};
+
+	this.update = function () {
+
+		var targetPosition = new Vector3();
+
+		return function update( delta ) {
+
+			if ( this.enabled === false ) return;
+
+			if ( this.heightSpeed ) {
+
+				var y = _Math.clamp( this.object.position.y, this.heightMin, this.heightMax );
+				var heightDelta = y - this.heightMin;
+
+				this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
+
+			} else {
+
+				this.autoSpeedFactor = 0.0;
+
+			}
+
+			var actualMoveSpeed = delta * this.movementSpeed;
+
+			if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
+			if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
+
+			if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
+			if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
+
+			if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
+			if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
+
+			var actualLookSpeed = delta * this.lookSpeed;
+
+			if ( ! this.activeLook ) {
+
+				actualLookSpeed = 0;
+
+			}
+
+			var verticalLookRatio = 1;
+
+			if ( this.constrainVertical ) {
+
+				verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
+
+			}
+
+			lon -= this.mouseX * actualLookSpeed;
+			if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio;
+
+			lat = Math.max( - 85, Math.min( 85, lat ) );
+
+			var phi = _Math.degToRad( 90 - lat );
+			var theta = _Math.degToRad( lon );
+
+			if ( this.constrainVertical ) {
+
+				phi = _Math.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
+
+			}
+
+			var position = this.object.position;
+
+			targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
+
+			this.object.lookAt( targetPosition );
+
+		};
+
+	}();
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _onMouseDown, false );
+		this.domElement.removeEventListener( 'mousemove', _onMouseMove, false );
+		this.domElement.removeEventListener( 'mouseup', _onMouseUp, false );
+
+		window.removeEventListener( 'keydown', _onKeyDown, false );
+		window.removeEventListener( 'keyup', _onKeyUp, false );
+
+	};
+
+	var _onMouseMove = bind( this, this.onMouseMove );
+	var _onMouseDown = bind( this, this.onMouseDown );
+	var _onMouseUp = bind( this, this.onMouseUp );
+	var _onKeyDown = bind( this, this.onKeyDown );
+	var _onKeyUp = bind( this, this.onKeyUp );
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousemove', _onMouseMove, false );
+	this.domElement.addEventListener( 'mousedown', _onMouseDown, false );
+	this.domElement.addEventListener( 'mouseup', _onMouseUp, false );
+
+	window.addEventListener( 'keydown', _onKeyDown, false );
+	window.addEventListener( 'keyup', _onKeyUp, false );
+
+	function bind( scope, fn ) {
+
+		return function () {
+
+			fn.apply( scope, arguments );
+
+		};
+
+	}
+
+	function setOrientation( controls ) {
+
+		var quaternion = controls.object.quaternion;
+
+		lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
+		spherical.setFromVector3( lookDirection );
+
+		lat = 90 - _Math.radToDeg( spherical.phi );
+		lon = _Math.radToDeg( spherical.theta );
+
+	}
+
+	this.handleResize();
+
+	setOrientation( this );
+
+};
+
+export { FirstPersonControls };

+ 19 - 0
examples/jsm/controls/FlyControls.d.ts

@@ -0,0 +1,19 @@
+import {
+  Camera
+} from '../../../src/Three';
+
+export class FlyControls {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  movementSpeed: number;
+  rollSpeed: number;
+  dragToLook: boolean;
+  autoForward: boolean;
+
+  update(delta: number): void;
+  dispose(): void;
+
+}

+ 290 - 0
examples/jsm/controls/FlyControls.js

@@ -0,0 +1,290 @@
+/**
+ * @author James Baicoianu / http://www.baicoianu.com/
+ */
+
+import {
+	Quaternion,
+	Vector3
+} from "../../../build/three.module.js";
+
+var FlyControls = function ( object, domElement ) {
+
+	this.object = object;
+
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+	if ( domElement ) this.domElement.setAttribute( 'tabindex', - 1 );
+
+	// API
+
+	this.movementSpeed = 1.0;
+	this.rollSpeed = 0.005;
+
+	this.dragToLook = false;
+	this.autoForward = false;
+
+	// disable default target object behavior
+
+	// internals
+
+	this.tmpQuaternion = new Quaternion();
+
+	this.mouseStatus = 0;
+
+	this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
+	this.moveVector = new Vector3( 0, 0, 0 );
+	this.rotationVector = new Vector3( 0, 0, 0 );
+
+	this.keydown = function ( event ) {
+
+		if ( event.altKey ) {
+
+			return;
+
+		}
+
+		//event.preventDefault();
+
+		switch ( event.keyCode ) {
+
+			case 16: /* shift */ this.movementSpeedMultiplier = .1; break;
+
+			case 87: /*W*/ this.moveState.forward = 1; break;
+			case 83: /*S*/ this.moveState.back = 1; break;
+
+			case 65: /*A*/ this.moveState.left = 1; break;
+			case 68: /*D*/ this.moveState.right = 1; break;
+
+			case 82: /*R*/ this.moveState.up = 1; break;
+			case 70: /*F*/ this.moveState.down = 1; break;
+
+			case 38: /*up*/ this.moveState.pitchUp = 1; break;
+			case 40: /*down*/ this.moveState.pitchDown = 1; break;
+
+			case 37: /*left*/ this.moveState.yawLeft = 1; break;
+			case 39: /*right*/ this.moveState.yawRight = 1; break;
+
+			case 81: /*Q*/ this.moveState.rollLeft = 1; break;
+			case 69: /*E*/ this.moveState.rollRight = 1; break;
+
+		}
+
+		this.updateMovementVector();
+		this.updateRotationVector();
+
+	};
+
+	this.keyup = function ( event ) {
+
+		switch ( event.keyCode ) {
+
+			case 16: /* shift */ this.movementSpeedMultiplier = 1; break;
+
+			case 87: /*W*/ this.moveState.forward = 0; break;
+			case 83: /*S*/ this.moveState.back = 0; break;
+
+			case 65: /*A*/ this.moveState.left = 0; break;
+			case 68: /*D*/ this.moveState.right = 0; break;
+
+			case 82: /*R*/ this.moveState.up = 0; break;
+			case 70: /*F*/ this.moveState.down = 0; break;
+
+			case 38: /*up*/ this.moveState.pitchUp = 0; break;
+			case 40: /*down*/ this.moveState.pitchDown = 0; break;
+
+			case 37: /*left*/ this.moveState.yawLeft = 0; break;
+			case 39: /*right*/ this.moveState.yawRight = 0; break;
+
+			case 81: /*Q*/ this.moveState.rollLeft = 0; break;
+			case 69: /*E*/ this.moveState.rollRight = 0; break;
+
+		}
+
+		this.updateMovementVector();
+		this.updateRotationVector();
+
+	};
+
+	this.mousedown = function ( event ) {
+
+		if ( this.domElement !== document ) {
+
+			this.domElement.focus();
+
+		}
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.dragToLook ) {
+
+			this.mouseStatus ++;
+
+		} else {
+
+			switch ( event.button ) {
+
+				case 0: this.moveState.forward = 1; break;
+				case 2: this.moveState.back = 1; break;
+
+			}
+
+			this.updateMovementVector();
+
+		}
+
+	};
+
+	this.mousemove = function ( event ) {
+
+		if ( ! this.dragToLook || this.mouseStatus > 0 ) {
+
+			var container = this.getContainerDimensions();
+			var halfWidth = container.size[ 0 ] / 2;
+			var halfHeight = container.size[ 1 ] / 2;
+
+			this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
+			this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
+
+			this.updateRotationVector();
+
+		}
+
+	};
+
+	this.mouseup = function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.dragToLook ) {
+
+			this.mouseStatus --;
+
+			this.moveState.yawLeft = this.moveState.pitchDown = 0;
+
+		} else {
+
+			switch ( event.button ) {
+
+				case 0: this.moveState.forward = 0; break;
+				case 2: this.moveState.back = 0; break;
+
+			}
+
+			this.updateMovementVector();
+
+		}
+
+		this.updateRotationVector();
+
+	};
+
+	this.update = function ( delta ) {
+
+		var moveMult = delta * this.movementSpeed;
+		var rotMult = delta * this.rollSpeed;
+
+		this.object.translateX( this.moveVector.x * moveMult );
+		this.object.translateY( this.moveVector.y * moveMult );
+		this.object.translateZ( this.moveVector.z * moveMult );
+
+		this.tmpQuaternion.set( this.rotationVector.x * rotMult, this.rotationVector.y * rotMult, this.rotationVector.z * rotMult, 1 ).normalize();
+		this.object.quaternion.multiply( this.tmpQuaternion );
+
+		// expose the rotation vector for convenience
+		this.object.rotation.setFromQuaternion( this.object.quaternion, this.object.rotation.order );
+
+
+	};
+
+	this.updateMovementVector = function () {
+
+		var forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
+
+		this.moveVector.x = ( - this.moveState.left + this.moveState.right );
+		this.moveVector.y = ( - this.moveState.down + this.moveState.up );
+		this.moveVector.z = ( - forward + this.moveState.back );
+
+		//console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
+
+	};
+
+	this.updateRotationVector = function () {
+
+		this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp );
+		this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft );
+		this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft );
+
+		//console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
+
+	};
+
+	this.getContainerDimensions = function () {
+
+		if ( this.domElement != document ) {
+
+			return {
+				size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
+				offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
+			};
+
+		} else {
+
+			return {
+				size: [ window.innerWidth, window.innerHeight ],
+				offset: [ 0, 0 ]
+			};
+
+		}
+
+	};
+
+	function bind( scope, fn ) {
+
+		return function () {
+
+			fn.apply( scope, arguments );
+
+		};
+
+	}
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _mousedown, false );
+		this.domElement.removeEventListener( 'mousemove', _mousemove, false );
+		this.domElement.removeEventListener( 'mouseup', _mouseup, false );
+
+		window.removeEventListener( 'keydown', _keydown, false );
+		window.removeEventListener( 'keyup', _keyup, false );
+
+	};
+
+	var _mousemove = bind( this, this.mousemove );
+	var _mousedown = bind( this, this.mousedown );
+	var _mouseup = bind( this, this.mouseup );
+	var _keydown = bind( this, this.keydown );
+	var _keyup = bind( this, this.keyup );
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+
+	this.domElement.addEventListener( 'mousemove', _mousemove, false );
+	this.domElement.addEventListener( 'mousedown', _mousedown, false );
+	this.domElement.addEventListener( 'mouseup', _mouseup, false );
+
+	window.addEventListener( 'keydown', _keydown, false );
+	window.addEventListener( 'keyup', _keyup, false );
+
+	this.updateMovementVector();
+	this.updateRotationVector();
+
+};
+
+export { FlyControls };

+ 30 - 0
examples/jsm/controls/OrthographicTrackballControls.d.ts

@@ -0,0 +1,30 @@
+import { Camera, EventDispatcher } from '../../../src/Three';
+
+export class OrthographicTrackballControls extends EventDispatcher {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement;
+
+  enabled: boolean;
+  screen: {left: number; top: number; width: number; height: number};
+  radius: number;
+  rotateSpeed: number;
+  zoomSpeed: number;
+  noRotate: boolean;
+  noZoom: boolean;
+  noPan: boolean;
+  noRoll: boolean;
+  staticMoving: boolean;
+  dynamicDampingFactor: number;
+  keys: number[];
+
+  handleResize(): void;
+  rotateCamera(): void;
+  zoomCamera(): void;
+  panCamera(): void;
+  update(): void;
+  reset(): void;
+  dispose(): void;
+
+}

+ 635 - 0
examples/jsm/controls/OrthographicTrackballControls.js

@@ -0,0 +1,635 @@
+/**
+ * @author Eberhard Graether / http://egraether.com/
+ * @author Mark Lundin 	/ http://mark-lundin.com
+ * @author Patrick Fuller / http://patrick-fuller.com
+ * @author Max Smolens / https://github.com/msmolens
+ */
+
+import {
+	EventDispatcher,
+	Quaternion,
+	Vector2,
+	Vector3
+} from "../../../build/three.module.js";
+
+var OrthographicTrackballControls = function ( object, domElement ) {
+
+	var _this = this;
+	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
+
+	this.object = object;
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	// API
+
+	this.enabled = true;
+
+	this.screen = { left: 0, top: 0, width: 0, height: 0 };
+
+	this.radius = 0;
+
+	this.rotateSpeed = 1.0;
+	this.zoomSpeed = 1.2;
+
+	this.noRotate = false;
+	this.noZoom = false;
+	this.noPan = false;
+	this.noRoll = false;
+
+	this.staticMoving = false;
+	this.dynamicDampingFactor = 0.2;
+
+	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
+
+	// internals
+
+	this.target = new Vector3();
+
+	var EPS = 0.000001;
+
+	var _changed = true;
+
+	var _state = STATE.NONE,
+		_prevState = STATE.NONE,
+
+		_eye = new Vector3(),
+
+		_rotateStart = new Vector3(),
+		_rotateEnd = new Vector3(),
+
+		_zoomStart = new Vector2(),
+		_zoomEnd = new Vector2(),
+
+		_touchZoomDistanceStart = 0,
+		_touchZoomDistanceEnd = 0,
+
+		_panStart = new Vector2(),
+		_panEnd = new Vector2();
+
+	// for reset
+
+	this.target0 = this.target.clone();
+	this.position0 = this.object.position.clone();
+	this.up0 = this.object.up.clone();
+
+	this.left0 = this.object.left;
+	this.right0 = this.object.right;
+	this.top0 = this.object.top;
+	this.bottom0 = this.object.bottom;
+
+	// events
+
+	var changeEvent = { type: 'change' };
+	var startEvent = { type: 'start' };
+	var endEvent = { type: 'end' };
+
+
+	// methods
+
+	this.handleResize = function () {
+
+		if ( this.domElement === document ) {
+
+			this.screen.left = 0;
+			this.screen.top = 0;
+			this.screen.width = window.innerWidth;
+			this.screen.height = window.innerHeight;
+
+		} else {
+
+			var box = this.domElement.getBoundingClientRect();
+			// adjustments come from similar code in the jquery offset() function
+			var d = this.domElement.ownerDocument.documentElement;
+			this.screen.left = box.left + window.pageXOffset - d.clientLeft;
+			this.screen.top = box.top + window.pageYOffset - d.clientTop;
+			this.screen.width = box.width;
+			this.screen.height = box.height;
+
+		}
+
+		this.radius = 0.5 * Math.min( this.screen.width, this.screen.height );
+
+		this.left0 = this.object.left;
+		this.right0 = this.object.right;
+		this.top0 = this.object.top;
+		this.bottom0 = this.object.bottom;
+
+	};
+
+	var getMouseOnScreen = ( function () {
+
+		var vector = new Vector2();
+
+		return function getMouseOnScreen( pageX, pageY ) {
+
+			vector.set(
+				( pageX - _this.screen.left ) / _this.screen.width,
+				( pageY - _this.screen.top ) / _this.screen.height
+			);
+
+			return vector;
+
+		};
+
+	}() );
+
+	var getMouseProjectionOnBall = ( function () {
+
+		var vector = new Vector3();
+		var objectUp = new Vector3();
+		var mouseOnBall = new Vector3();
+
+		return function getMouseProjectionOnBall( pageX, pageY ) {
+
+			mouseOnBall.set(
+				( pageX - _this.screen.width * 0.5 - _this.screen.left ) / _this.radius,
+				( _this.screen.height * 0.5 + _this.screen.top - pageY ) / _this.radius,
+				0.0
+			);
+
+			var length = mouseOnBall.length();
+
+			if ( _this.noRoll ) {
+
+				if ( length < Math.SQRT1_2 ) {
+
+					mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+				} else {
+
+					mouseOnBall.z = .5 / length;
+
+				}
+
+			} else if ( length > 1.0 ) {
+
+				mouseOnBall.normalize();
+
+			} else {
+
+				mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+			}
+
+			_eye.copy( _this.object.position ).sub( _this.target );
+
+			vector.copy( _this.object.up ).setLength( mouseOnBall.y );
+			vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
+			vector.add( _eye.setLength( mouseOnBall.z ) );
+
+			return vector;
+
+		};
+
+	}() );
+
+	this.rotateCamera = ( function () {
+
+		var axis = new Vector3(),
+			quaternion = new Quaternion();
+
+
+		return function rotateCamera() {
+
+			var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
+
+			if ( angle ) {
+
+				axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
+
+				angle *= _this.rotateSpeed;
+
+				quaternion.setFromAxisAngle( axis, - angle );
+
+				_eye.applyQuaternion( quaternion );
+				_this.object.up.applyQuaternion( quaternion );
+
+				_rotateEnd.applyQuaternion( quaternion );
+
+				if ( _this.staticMoving ) {
+
+					_rotateStart.copy( _rotateEnd );
+
+				} else {
+
+					quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
+					_rotateStart.applyQuaternion( quaternion );
+
+				}
+
+				_changed = true;
+
+			}
+
+		};
+
+	}() );
+
+	this.zoomCamera = function () {
+
+		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
+
+			var factor = _touchZoomDistanceEnd / _touchZoomDistanceStart;
+			_touchZoomDistanceStart = _touchZoomDistanceEnd;
+
+			_this.object.zoom *= factor;
+
+			_changed = true;
+
+		} else {
+
+			var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
+
+			if ( Math.abs( factor - 1.0 ) > EPS && factor > 0.0 ) {
+
+				_this.object.zoom /= factor;
+
+				if ( _this.staticMoving ) {
+
+					_zoomStart.copy( _zoomEnd );
+
+				} else {
+
+					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+
+				}
+
+				_changed = true;
+
+			}
+
+		}
+
+	};
+
+	this.panCamera = ( function () {
+
+		var mouseChange = new Vector2(),
+			objectUp = new Vector3(),
+			pan = new Vector3();
+
+		return function panCamera() {
+
+			mouseChange.copy( _panEnd ).sub( _panStart );
+
+			if ( mouseChange.lengthSq() ) {
+
+				// Scale movement to keep clicked/dragged position under cursor
+				var scale_x = ( _this.object.right - _this.object.left ) / _this.object.zoom;
+				var scale_y = ( _this.object.top - _this.object.bottom ) / _this.object.zoom;
+				mouseChange.x *= scale_x;
+				mouseChange.y *= scale_y;
+
+				pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
+				pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
+
+				_this.object.position.add( pan );
+				_this.target.add( pan );
+
+				if ( _this.staticMoving ) {
+
+					_panStart.copy( _panEnd );
+
+				} else {
+
+					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
+
+				}
+
+				_changed = true;
+
+			}
+
+		};
+
+	}() );
+
+	this.update = function () {
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		if ( ! _this.noRotate ) {
+
+			_this.rotateCamera();
+
+		}
+
+		if ( ! _this.noZoom ) {
+
+			_this.zoomCamera();
+
+			if ( _changed ) {
+
+				_this.object.updateProjectionMatrix();
+
+			}
+
+		}
+
+		if ( ! _this.noPan ) {
+
+			_this.panCamera();
+
+		}
+
+		_this.object.position.addVectors( _this.target, _eye );
+
+		_this.object.lookAt( _this.target );
+
+		if ( _changed ) {
+
+			_this.dispatchEvent( changeEvent );
+
+			_changed = false;
+
+		}
+
+	};
+
+	this.reset = function () {
+
+		_state = STATE.NONE;
+		_prevState = STATE.NONE;
+
+		_this.target.copy( _this.target0 );
+		_this.object.position.copy( _this.position0 );
+		_this.object.up.copy( _this.up0 );
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		_this.object.left = _this.left0;
+		_this.object.right = _this.right0;
+		_this.object.top = _this.top0;
+		_this.object.bottom = _this.bottom0;
+
+		_this.object.lookAt( _this.target );
+
+		_this.dispatchEvent( changeEvent );
+
+		_changed = false;
+
+	};
+
+	// listeners
+
+	function keydown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		window.removeEventListener( 'keydown', keydown );
+
+		_prevState = _state;
+
+		if ( _state !== STATE.NONE ) {
+
+			return;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
+
+			_state = STATE.ROTATE;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
+
+			_state = STATE.ZOOM;
+
+		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
+
+			_state = STATE.PAN;
+
+		}
+
+	}
+
+	function keyup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		_state = _prevState;
+
+		window.addEventListener( 'keydown', keydown, false );
+
+	}
+
+	function mousedown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.NONE ) {
+
+			_state = event.button;
+
+		}
+
+		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+			_rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
+			_rotateEnd.copy( _rotateStart );
+
+		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+			_zoomEnd.copy( _zoomStart );
+
+		} else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+			_panEnd.copy( _panStart );
+
+		}
+
+		document.addEventListener( 'mousemove', mousemove, false );
+		document.addEventListener( 'mouseup', mouseup, false );
+
+		_this.dispatchEvent( startEvent );
+
+	}
+
+	function mousemove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+			_rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
+
+		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+
+		} else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+
+		}
+
+	}
+
+	function mouseup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		_state = STATE.NONE;
+
+		document.removeEventListener( 'mousemove', mousemove );
+		document.removeEventListener( 'mouseup', mouseup );
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function mousewheel( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		_zoomStart.y += event.deltaY * 0.01;
+		_this.dispatchEvent( startEvent );
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function touchstart( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_state = STATE.TOUCH_ROTATE;
+				_rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				_rotateEnd.copy( _rotateStart );
+				break;
+
+			case 2:
+				_state = STATE.TOUCH_ZOOM_PAN;
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+
+				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
+				_panStart.copy( getMouseOnScreen( x, y ) );
+				_panEnd.copy( _panStart );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+		_this.dispatchEvent( startEvent );
+
+	}
+
+	function touchmove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				break;
+
+			case 2:
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
+
+				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
+				_panEnd.copy( getMouseOnScreen( x, y ) );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+
+	}
+
+	function touchend( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				_rotateStart.copy( _rotateEnd );
+				break;
+
+			case 2:
+				_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
+
+				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
+				_panEnd.copy( getMouseOnScreen( x, y ) );
+				_panStart.copy( _panEnd );
+				break;
+
+		}
+
+		_state = STATE.NONE;
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', mousedown, false );
+		this.domElement.removeEventListener( 'wheel', mousewheel, false );
+
+		this.domElement.removeEventListener( 'touchstart', touchstart, false );
+		this.domElement.removeEventListener( 'touchend', touchend, false );
+		this.domElement.removeEventListener( 'touchmove', touchmove, false );
+
+		document.removeEventListener( 'mousemove', mousemove, false );
+		document.removeEventListener( 'mouseup', mouseup, false );
+
+		window.removeEventListener( 'keydown', keydown, false );
+		window.removeEventListener( 'keyup', keyup, false );
+
+	};
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousedown', mousedown, false );
+	this.domElement.addEventListener( 'wheel', mousewheel, false );
+
+	this.domElement.addEventListener( 'touchstart', touchstart, false );
+	this.domElement.addEventListener( 'touchend', touchend, false );
+	this.domElement.addEventListener( 'touchmove', touchmove, false );
+
+	window.addEventListener( 'keydown', keydown, false );
+	window.addEventListener( 'keyup', keyup, false );
+
+	this.handleResize();
+
+	// force an update at start
+	this.update();
+
+};
+
+OrthographicTrackballControls.prototype = Object.create( EventDispatcher.prototype );
+OrthographicTrackballControls.prototype.constructor = OrthographicTrackballControls;
+
+export { OrthographicTrackballControls };

BIN
examples/textures/memorial.png


+ 140 - 0
examples/webgl_loader_texture_rgbm.html

@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - RGBM texture loader</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 {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color:#fff;
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+
+			}
+
+			a { color: red; }
+
+		</style>
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl RGBM texture loader example
+		</div>
+
+		<script src="../build/three.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 params = {
+				exposure: 2.0
+			};
+
+			var renderer, scene, camera;
+
+			init();
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				renderer.toneMapping = THREE.ReinhardToneMapping;
+				renderer.toneMappingExposure = params.exposure;
+
+				renderer.gammaOutput = true;
+
+				scene = new THREE.Scene();
+
+				var aspect = window.innerWidth / window.innerHeight;
+
+				camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 1 );
+
+				new THREE.TextureLoader().load( 'textures/memorial.png', function ( texture ) {
+
+					texture.encoding = THREE.RGBM16Encoding;
+
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.flipY = true;
+
+					var material = new THREE.MeshBasicMaterial( { map: texture } );
+
+					var quad = new THREE.PlaneBufferGeometry( 1.5 * texture.width / texture.height, 1.5 );
+
+					var mesh = new THREE.Mesh( quad, material );
+
+					scene.add( mesh );
+
+					render();
+
+				} );
+
+				//
+
+				var gui = new dat.GUI();
+
+				gui.add( params, 'exposure', 0, 4, 0.01 ).onChange( render );
+				gui.open();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				var aspect = window.innerWidth / window.innerHeight;
+
+				var frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
+
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.toneMappingExposure = params.exposure;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 1
src/core/Geometry.js

@@ -228,7 +228,7 @@ Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 		if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = [];
 
-		for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {
+		for ( var i = 0; i < positions.length; i += 3 ) {
 
 			scope.vertices.push( new Vector3().fromArray( positions, i ) );
 

+ 1 - 1
src/renderers/webgl/WebGLAttributes.d.ts

@@ -5,5 +5,5 @@ export class WebGLAttributes {
 
 	remove(attribute: any): void;
 
-	update(attribute: any, bufferType: Array): void;
+	update(attribute: any, bufferType: Array<any>): void;
 }

+ 3 - 0
utils/modularize.js

@@ -11,8 +11,11 @@ var files = [
 	{ path: 'controls/DragControls.js', ignoreList: [] },
 	{ path: 'controls/DeviceOrientationControls.js', ignoreList: [] },
 	{ path: 'controls/EditorControls.js', ignoreList: [] },
+	{ path: 'controls/FirstPersonControls.js', ignoreList: [] },
+	{ path: 'controls/FlyControls.js', ignoreList: [] },
 	{ path: 'controls/OrbitControls.js', ignoreList: [] },
 	{ path: 'controls/MapControls.js', ignoreList: [] },
+	{ path: 'controls/OrthographicTrackballControls.js', ignoreList: [] },
 	{ path: 'controls/PointerLockControls.js', ignoreList: [] },
 	{ path: 'controls/TrackballControls.js', ignoreList: [] },
 	// { path: 'controls/TransformControls.js', ignoreList: [] },