+++ /dev/null
-/**
- * @author qiao / https://github.com/qiao
- * @author mrdoob / http://mrdoob.com
- * @author alteredq / http://alteredqualia.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author erich666 / http://erichaines.com
- */
-
-// This set of controls performs orbiting, dollying (zooming), and panning.
-// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
-//
-// Orbit - left mouse / touch: one finger move
-// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
-// Pan - right mouse, or arrow keys / touch: three finger swipe
-
-THREE.OrbitControls = function ( object, domElement ) {
-
- this.object = object;
-
- this.domElement = ( domElement !== undefined ) ? domElement : document;
-
- // Set to false to disable this control
- this.enabled = true;
-
- // "target" sets the location of focus, where the object orbits around
- this.target = new THREE.Vector3();
-
- // How far you can dolly in and out ( PerspectiveCamera only )
- this.minDistance = 0;
- this.maxDistance = Infinity;
-
- // How far you can zoom in and out ( OrthographicCamera only )
- this.minZoom = 0;
- this.maxZoom = Infinity;
-
- // How far you can orbit vertically, upper and lower limits.
- // Range is 0 to Math.PI radians.
- this.minPolarAngle = 0; // radians
- this.maxPolarAngle = Math.PI; // radians
-
- // How far you can orbit horizontally, upper and lower limits.
- // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
- this.minAzimuthAngle = - Infinity; // radians
- this.maxAzimuthAngle = Infinity; // radians
-
- // Set to true to enable damping (inertia)
- // If damping is enabled, you must call controls.update() in your animation loop
- this.enableDamping = false;
- this.dampingFactor = 0.25;
-
- // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
- // Set to false to disable zooming
- this.enableZoom = true;
- this.zoomSpeed = 1.0;
-
- // Set to false to disable rotating
- this.enableRotate = true;
- this.rotateSpeed = 1.0;
-
- // Set to false to disable panning
- this.enablePan = true;
- this.keyPanSpeed = 7.0; // pixels moved per arrow key push
-
- // Set to true to automatically rotate around the target
- // If auto-rotate is enabled, you must call controls.update() in your animation loop
- this.autoRotate = false;
- this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
-
- // Set to false to disable use of the keys
- this.enableKeys = true;
-
- // The four arrow keys
- this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
-
- // Mouse buttons
- this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
-
- // for reset
- this.target0 = this.target.clone();
- this.position0 = this.object.position.clone();
- this.zoom0 = this.object.zoom;
-
- //
- // public methods
- //
-
- this.getPolarAngle = function () {
-
- return spherical.phi;
-
- };
-
- this.getAzimuthalAngle = function () {
-
- return spherical.theta;
-
- };
-
- this.saveState = function () {
-
- scope.target0.copy( scope.target );
- scope.position0.copy( scope.object.position );
- scope.zoom0 = scope.object.zoom;
-
- };
-
- this.reset = function () {
-
- scope.target.copy( scope.target0 );
- scope.object.position.copy( scope.position0 );
- scope.object.zoom = scope.zoom0;
-
- scope.object.updateProjectionMatrix();
- scope.dispatchEvent( changeEvent );
-
- scope.update();
-
- state = STATE.NONE;
-
- };
-
- // this method is exposed, but perhaps it would be better if we can make it private...
- this.update = function () {
-
- var offset = new THREE.Vector3();
-
- // so camera.up is the orbit axis
- var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
- var quatInverse = quat.clone().inverse();
-
- var lastPosition = new THREE.Vector3();
- var lastQuaternion = new THREE.Quaternion();
-
- return function update() {
-
- var position = scope.object.position;
-
- offset.copy( position ).sub( scope.target );
-
- // rotate offset to "y-axis-is-up" space
- offset.applyQuaternion( quat );
-
- // angle from z-axis around y-axis
- spherical.setFromVector3( offset );
-
- if ( scope.autoRotate && state === STATE.NONE ) {
-
- rotateLeft( getAutoRotationAngle() );
-
- }
-
- spherical.theta += sphericalDelta.theta;
- spherical.phi += sphericalDelta.phi;
-
- // restrict theta to be between desired limits
- spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
-
- // restrict phi to be between desired limits
- spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
-
- spherical.makeSafe();
-
-
- spherical.radius *= scale;
-
- // restrict radius to be between desired limits
- spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
-
- // move target to panned location
- scope.target.add( panOffset );
-
- offset.setFromSpherical( spherical );
-
- // rotate offset back to "camera-up-vector-is-up" space
- offset.applyQuaternion( quatInverse );
-
- position.copy( scope.target ).add( offset );
-
- scope.object.lookAt( scope.target );
-
- if ( scope.enableDamping === true ) {
-
- sphericalDelta.theta *= ( 1 - scope.dampingFactor );
- sphericalDelta.phi *= ( 1 - scope.dampingFactor );
-
- } else {
-
- sphericalDelta.set( 0, 0, 0 );
-
- }
-
- scale = 1;
- panOffset.set( 0, 0, 0 );
-
- // update condition is:
- // min(camera displacement, camera rotation in radians)^2 > EPS
- // using small-angle approximation cos(x/2) = 1 - x^2 / 8
-
- if ( zoomChanged ||
- lastPosition.distanceToSquared( scope.object.position ) > EPS ||
- 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
-
- scope.dispatchEvent( changeEvent );
-
- lastPosition.copy( scope.object.position );
- lastQuaternion.copy( scope.object.quaternion );
- zoomChanged = false;
-
- return true;
-
- }
-
- return false;
-
- };
-
- }();
-
- this.dispose = function () {
-
- scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
- scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
- scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
-
- scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
- scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
- scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
-
- document.removeEventListener( 'mousemove', onMouseMove, false );
- document.removeEventListener( 'mouseup', onMouseUp, false );
-
- window.removeEventListener( 'keydown', onKeyDown, false );
-
- //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
-
- };
-
- //
- // internals
- //
-
- var scope = this;
-
- var changeEvent = { type: 'change' };
- var startEvent = { type: 'start' };
- var endEvent = { type: 'end' };
-
- var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
-
- var state = STATE.NONE;
-
- var EPS = 0.000001;
-
- // current position in spherical coordinates
- var spherical = new THREE.Spherical();
- var sphericalDelta = new THREE.Spherical();
-
- var scale = 1;
- var panOffset = new THREE.Vector3();
- var zoomChanged = false;
-
- var rotateStart = new THREE.Vector2();
- var rotateEnd = new THREE.Vector2();
- var rotateDelta = new THREE.Vector2();
-
- var panStart = new THREE.Vector2();
- var panEnd = new THREE.Vector2();
- var panDelta = new THREE.Vector2();
-
- var dollyStart = new THREE.Vector2();
- var dollyEnd = new THREE.Vector2();
- var dollyDelta = new THREE.Vector2();
-
- function getAutoRotationAngle() {
-
- return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
-
- }
-
- function getZoomScale() {
-
- return Math.pow( 0.95, scope.zoomSpeed );
-
- }
-
- function rotateLeft( angle ) {
-
- sphericalDelta.theta -= angle;
-
- }
-
- function rotateUp( angle ) {
-
- sphericalDelta.phi -= angle;
-
- }
-
- var panLeft = function () {
-
- var v = new THREE.Vector3();
-
- return function panLeft( distance, objectMatrix ) {
-
- v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
- v.multiplyScalar( - distance );
-
- panOffset.add( v );
-
- };
-
- }();
-
- var panUp = function () {
-
- var v = new THREE.Vector3();
-
- return function panUp( distance, objectMatrix ) {
-
- v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
- v.multiplyScalar( distance );
-
- panOffset.add( v );
-
- };
-
- }();
-
- // deltaX and deltaY are in pixels; right and down are positive
- var pan = function () {
-
- var offset = new THREE.Vector3();
-
- return function pan( deltaX, deltaY ) {
-
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
-
- if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
- // perspective
- var position = scope.object.position;
- offset.copy( position ).sub( scope.target );
- var targetDistance = offset.length();
-
- // half of the fov is center to top of screen
- targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
-
- // we actually don't use screenWidth, since perspective camera is fixed to screen height
- panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
- panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
-
- } else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
- // orthographic
- panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
- panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
-
- } else {
-
- // camera neither orthographic nor perspective
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
- scope.enablePan = false;
-
- }
-
- };
-
- }();
-
- function dollyIn( dollyScale ) {
-
- if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
- scale /= dollyScale;
-
- } else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
- scope.object.updateProjectionMatrix();
- zoomChanged = true;
-
- } else {
-
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
- scope.enableZoom = false;
-
- }
-
- }
-
- function dollyOut( dollyScale ) {
-
- if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
- scale *= dollyScale;
-
- } else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
- scope.object.updateProjectionMatrix();
- zoomChanged = true;
-
- } else {
-
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
- scope.enableZoom = false;
-
- }
-
- }
-
- //
- // event callbacks - update the object state
- //
-
- function handleMouseDownRotate( event ) {
-
- //console.log( 'handleMouseDownRotate' );
-
- rotateStart.set( event.clientX, event.clientY );
-
- }
-
- function handleMouseDownDolly( event ) {
-
- //console.log( 'handleMouseDownDolly' );
-
- dollyStart.set( event.clientX, event.clientY );
-
- }
-
- function handleMouseDownPan( event ) {
-
- //console.log( 'handleMouseDownPan' );
-
- panStart.set( event.clientX, event.clientY );
-
- }
-
- function handleMouseMoveRotate( event ) {
-
- //console.log( 'handleMouseMoveRotate' );
-
- rotateEnd.set( event.clientX, event.clientY );
- rotateDelta.subVectors( rotateEnd, rotateStart );
-
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
-
- // rotating across whole screen goes 360 degrees around
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
-
- // rotating up and down along whole screen attempts to go 360, but limited to 180
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
-
- rotateStart.copy( rotateEnd );
-
- scope.update();
-
- }
-
- function handleMouseMoveDolly( event ) {
-
- //console.log( 'handleMouseMoveDolly' );
-
- dollyEnd.set( event.clientX, event.clientY );
-
- dollyDelta.subVectors( dollyEnd, dollyStart );
-
- if ( dollyDelta.y > 0 ) {
-
- dollyIn( getZoomScale() );
-
- } else if ( dollyDelta.y < 0 ) {
-
- dollyOut( getZoomScale() );
-
- }
-
- dollyStart.copy( dollyEnd );
-
- scope.update();
-
- }
-
- function handleMouseMovePan( event ) {
-
- //console.log( 'handleMouseMovePan' );
-
- panEnd.set( event.clientX, event.clientY );
-
- panDelta.subVectors( panEnd, panStart );
-
- pan( panDelta.x, panDelta.y );
-
- panStart.copy( panEnd );
-
- scope.update();
-
- }
-
- function handleMouseUp( event ) {
-
- // console.log( 'handleMouseUp' );
-
- }
-
- function handleMouseWheel( event ) {
-
- // console.log( 'handleMouseWheel' );
-
- if ( event.deltaY < 0 ) {
-
- dollyOut( getZoomScale() );
-
- } else if ( event.deltaY > 0 ) {
-
- dollyIn( getZoomScale() );
-
- }
-
- scope.update();
-
- }
-
- function handleKeyDown( event ) {
-
- //console.log( 'handleKeyDown' );
-
- switch ( event.keyCode ) {
-
- case scope.keys.UP:
- pan( 0, scope.keyPanSpeed );
- scope.update();
- break;
-
- case scope.keys.BOTTOM:
- pan( 0, - scope.keyPanSpeed );
- scope.update();
- break;
-
- case scope.keys.LEFT:
- pan( scope.keyPanSpeed, 0 );
- scope.update();
- break;
-
- case scope.keys.RIGHT:
- pan( - scope.keyPanSpeed, 0 );
- scope.update();
- break;
-
- }
-
- }
-
- function handleTouchStartRotate( event ) {
-
- //console.log( 'handleTouchStartRotate' );
-
- rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
- }
-
- function handleTouchStartDolly( event ) {
-
- //console.log( 'handleTouchStartDolly' );
-
- var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
- var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
- var distance = Math.sqrt( dx * dx + dy * dy );
-
- dollyStart.set( 0, distance );
-
- }
-
- function handleTouchStartPan( event ) {
-
- //console.log( 'handleTouchStartPan' );
-
- panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
- }
-
- function handleTouchMoveRotate( event ) {
-
- //console.log( 'handleTouchMoveRotate' );
-
- rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- rotateDelta.subVectors( rotateEnd, rotateStart );
-
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
-
- // rotating across whole screen goes 360 degrees around
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
-
- // rotating up and down along whole screen attempts to go 360, but limited to 180
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
-
- rotateStart.copy( rotateEnd );
-
- scope.update();
-
- }
-
- function handleTouchMoveDolly( event ) {
-
- //console.log( 'handleTouchMoveDolly' );
-
- var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
- var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
- var distance = Math.sqrt( dx * dx + dy * dy );
-
- dollyEnd.set( 0, distance );
-
- dollyDelta.subVectors( dollyEnd, dollyStart );
-
- if ( dollyDelta.y > 0 ) {
-
- dollyOut( getZoomScale() );
-
- } else if ( dollyDelta.y < 0 ) {
-
- dollyIn( getZoomScale() );
-
- }
-
- dollyStart.copy( dollyEnd );
-
- scope.update();
-
- }
-
- function handleTouchMovePan( event ) {
-
- //console.log( 'handleTouchMovePan' );
-
- panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
- panDelta.subVectors( panEnd, panStart );
-
- pan( panDelta.x, panDelta.y );
-
- panStart.copy( panEnd );
-
- scope.update();
-
- }
-
- function handleTouchEnd( event ) {
-
- //console.log( 'handleTouchEnd' );
-
- }
-
- //
- // event handlers - FSM: listen for events and reset state
- //
-
- function onMouseDown( event ) {
-
- if ( scope.enabled === false ) return;
-
- event.preventDefault();
-
- switch ( event.button ) {
-
- case scope.mouseButtons.ORBIT:
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseDownRotate( event );
-
- state = STATE.ROTATE;
-
- break;
-
- case scope.mouseButtons.ZOOM:
-
- if ( scope.enableZoom === false ) return;
-
- handleMouseDownDolly( event );
-
- state = STATE.DOLLY;
-
- break;
-
- case scope.mouseButtons.PAN:
-
- if ( scope.enablePan === false ) return;
-
- handleMouseDownPan( event );
-
- state = STATE.PAN;
-
- break;
-
- }
-
- if ( state !== STATE.NONE ) {
-
- document.addEventListener( 'mousemove', onMouseMove, false );
- document.addEventListener( 'mouseup', onMouseUp, false );
-
- scope.dispatchEvent( startEvent );
-
- }
-
- }
-
- function onMouseMove( event ) {
-
- if ( scope.enabled === false ) return;
-
- event.preventDefault();
-
- switch ( state ) {
-
- case STATE.ROTATE:
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseMoveRotate( event );
-
- break;
-
- case STATE.DOLLY:
-
- if ( scope.enableZoom === false ) return;
-
- handleMouseMoveDolly( event );
-
- break;
-
- case STATE.PAN:
-
- if ( scope.enablePan === false ) return;
-
- handleMouseMovePan( event );
-
- break;
-
- }
-
- }
-
- function onMouseUp( event ) {
-
- if ( scope.enabled === false ) return;
-
- handleMouseUp( event );
-
- document.removeEventListener( 'mousemove', onMouseMove, false );
- document.removeEventListener( 'mouseup', onMouseUp, false );
-
- scope.dispatchEvent( endEvent );
-
- state = STATE.NONE;
-
- }
-
- function onMouseWheel( event ) {
-
- if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
-
- event.preventDefault();
- event.stopPropagation();
-
- handleMouseWheel( event );
-
- scope.dispatchEvent( startEvent ); // not sure why these are here...
- scope.dispatchEvent( endEvent );
-
- }
-
- function onKeyDown( event ) {
-
- if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
-
- handleKeyDown( event );
-
- }
-
- function onTouchStart( event ) {
-
- if ( scope.enabled === false ) return;
-
- switch ( event.touches.length ) {
-
- case 1: // one-fingered touch: rotate
-
- if ( scope.enableRotate === false ) return;
-
- handleTouchStartRotate( event );
-
- state = STATE.TOUCH_ROTATE;
-
- break;
-
- case 2: // two-fingered touch: dolly
-
- if ( scope.enableZoom === false ) return;
-
- handleTouchStartDolly( event );
-
- state = STATE.TOUCH_DOLLY;
-
- break;
-
- case 3: // three-fingered touch: pan
-
- if ( scope.enablePan === false ) return;
-
- handleTouchStartPan( event );
-
- state = STATE.TOUCH_PAN;
-
- break;
-
- default:
-
- state = STATE.NONE;
-
- }
-
- if ( state !== STATE.NONE ) {
-
- scope.dispatchEvent( startEvent );
-
- }
-
- }
-
- function onTouchMove( event ) {
-
- if ( scope.enabled === false ) return;
-
- event.preventDefault();
- event.stopPropagation();
-
- switch ( event.touches.length ) {
-
- case 1: // one-fingered touch: rotate
-
- if ( scope.enableRotate === false ) return;
- if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
-
- handleTouchMoveRotate( event );
-
- break;
-
- case 2: // two-fingered touch: dolly
-
- if ( scope.enableZoom === false ) return;
- if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
-
- handleTouchMoveDolly( event );
-
- break;
-
- case 3: // three-fingered touch: pan
-
- if ( scope.enablePan === false ) return;
- if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
-
- handleTouchMovePan( event );
-
- break;
-
- default:
-
- state = STATE.NONE;
-
- }
-
- }
-
- function onTouchEnd( event ) {
-
- if ( scope.enabled === false ) return;
-
- handleTouchEnd( event );
-
- scope.dispatchEvent( endEvent );
-
- state = STATE.NONE;
-
- }
-
- function onContextMenu( event ) {
-
- if ( scope.enabled === false ) return;
-
- event.preventDefault();
-
- }
-
- //
-
- scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
-
- scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
- scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
-
- scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
- scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
- scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
-
- window.addEventListener( 'keydown', onKeyDown, false );
-
- // force an update at start
-
- this.update();
-
-};
-
-THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
-
-Object.defineProperties( THREE.OrbitControls.prototype, {
-
- center: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
- return this.target;
-
- }
-
- },
-
- // backward compatibility
-
- noZoom: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
- return ! this.enableZoom;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
- this.enableZoom = ! value;
-
- }
-
- },
-
- noRotate: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
- return ! this.enableRotate;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
- this.enableRotate = ! value;
-
- }
-
- },
-
- noPan: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
- return ! this.enablePan;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
- this.enablePan = ! value;
-
- }
-
- },
-
- noKeys: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
- return ! this.enableKeys;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
- this.enableKeys = ! value;
-
- }
-
- },
-
- staticMoving: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
- return ! this.enableDamping;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
- this.enableDamping = ! value;
-
- }
-
- },
-
- dynamicDampingFactor: {
-
- get: function () {
-
- console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
- return this.dampingFactor;
-
- },
-
- set: function ( value ) {
-
- console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
- this.dampingFactor = value;
-
- }
-
- }
-
-} );
-(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-window.THREE.MTLLoader = require("./MTLLoader").MTLLoader;
+( function () {
-},{"./MTLLoader":2}],2:[function(require,module,exports){
-/**
- * Loads a Wavefront .mtl file specifying materials
- */
+ /**
+ * Loads a Wavefront .mtl file specifying materials
+ */
-class MTLLoader extends THREE.Loader {
+ class MTLLoader extends THREE.Loader {
- constructor(manager) {
+ constructor( manager ) {
- super(manager);
+ super( manager );
- }
+ }
+ /**
+ * Loads and parses a MTL asset from a URL.
+ *
+ * @param {String} url - URL to the MTL file.
+ * @param {Function} [onLoad] - Callback invoked with the loaded object.
+ * @param {Function} [onProgress] - Callback for download progress.
+ * @param {Function} [onError] - Callback for download errors.
+ *
+ * @see setPath setResourcePath
+ *
+ * @note In order for relative texture references to resolve correctly
+ * you must call setResourcePath() explicitly prior to load.
+ */
+
+
+ load( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+ const path = this.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;
+ const loader = new THREE.FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setWithCredentials( this.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( text, path ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ }
+
+ setMaterialOptions( value ) {
+
+ this.materialOptions = value;
+ return this;
+
+ }
+ /**
+ * Parses a MTL file.
+ *
+ * @param {String} text - Content of MTL file
+ * @return {MaterialCreator}
+ *
+ * @see setPath setResourcePath
+ *
+ * @note In order for relative texture references to resolve correctly
+ * you must call setResourcePath() explicitly prior to parse.
+ */
+
+
+ parse( text, path ) {
+
+ const lines = text.split( '\n' );
+ let info = {};
+ const delimiter_pattern = /\s+/;
+ const materialsInfo = {};
+ for ( let i = 0; i < lines.length; i ++ ) {
+
+ let line = lines[ i ];
+ line = line.trim();
+
+ if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+ // Blank line or comment ignore
+ continue;
+
+ }
+
+ const pos = line.indexOf( ' ' );
+ let key = pos >= 0 ? line.substring( 0, pos ) : line;
+ key = key.toLowerCase();
+ let value = pos >= 0 ? line.substring( pos + 1 ) : '';
+ value = value.trim();
+
+ if ( key === 'newmtl' ) {
+
+ // New material
+ info = {
+ name: value
+ };
+ materialsInfo[ value ] = info;
+
+ } else {
+
+ if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
+
+ const ss = value.split( delimiter_pattern, 3 );
+ info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+ } else {
+
+ info[ key ] = value;
+
+ }
+
+ }
+
+ }
+
+ const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions );
+ materialCreator.setCrossOrigin( this.crossOrigin );
+ materialCreator.setManager( this.manager );
+ materialCreator.setMaterials( materialsInfo );
+ return materialCreator;
+
+ }
+
+ }
/**
- * Loads and parses a MTL asset from a URL.
- *
- * @param {String} url - URL to the MTL file.
- * @param {Function} [onLoad] - Callback invoked with the loaded object.
- * @param {Function} [onProgress] - Callback for download progress.
- * @param {Function} [onError] - Callback for download errors.
- *
- * @see setPath setResourcePath
- *
- * @note In order for relative texture references to resolve correctly
- * you must call setResourcePath() explicitly prior to load.
+ * Create a new MTLLoader.MaterialCreator
+ * @param baseUrl - Url relative to which textures are loaded
+ * @param options - Set of options on how to construct the materials
+ * side: Which side to apply the material
+ * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
+ * wrap: What type of wrapping to apply for textures
+ * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+ * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ * Default: false, assumed to be already normalized
+ * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ * Default: false
+ * @constructor
*/
- load(url, onLoad, onProgress, onError) {
- const scope = this;
- const path = (this.path === '') ? THREE.LoaderUtils.extractUrlBase(url) : this.path;
+ class MaterialCreator {
- const loader = new THREE.FileLoader(this.manager);
- loader.setPath(this.path);
- loader.setRequestHeader(this.requestHeader);
- loader.setWithCredentials(this.withCredentials);
- loader.load(url, function (text) {
+ constructor( baseUrl = '', options = {} ) {
- try {
+ this.baseUrl = baseUrl;
+ this.options = options;
+ this.materialsInfo = {};
+ this.materials = {};
+ this.materialsArray = [];
+ this.nameLookup = {};
+ this.crossOrigin = 'anonymous';
+ this.side = this.options.side !== undefined ? this.options.side : THREE.FrontSide;
+ this.wrap = this.options.wrap !== undefined ? this.options.wrap : THREE.RepeatWrapping;
- onLoad(scope.parse(text, path));
+ }
- } catch (e) {
+ setCrossOrigin( value ) {
- if (onError) {
+ this.crossOrigin = value;
+ return this;
- onError(e);
+ }
- } else {
+ setManager( value ) {
- console.error(e);
+ this.manager = value;
+
+ }
+
+ setMaterials( materialsInfo ) {
+
+ this.materialsInfo = this.convert( materialsInfo );
+ this.materials = {};
+ this.materialsArray = [];
+ this.nameLookup = {};
+
+ }
+
+ convert( materialsInfo ) {
+
+ if ( ! this.options ) return materialsInfo;
+ const converted = {};
+
+ for ( const mn in materialsInfo ) {
+
+ // Convert materials info into normalized form based on options
+ const mat = materialsInfo[ mn ];
+ const covmat = {};
+ converted[ mn ] = covmat;
+
+ for ( const prop in mat ) {
+
+ let save = true;
+ let value = mat[ prop ];
+ const lprop = prop.toLowerCase();
+
+ switch ( lprop ) {
+
+ case 'kd':
+ case 'ka':
+ case 'ks':
+ // Diffuse color (color under white light) using RGB values
+ if ( this.options && this.options.normalizeRGB ) {
+
+ value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+ }
+
+ if ( this.options && this.options.ignoreZeroRGBs ) {
+
+ if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+ // ignore
+ save = false;
+
+ }
+
+ }
+
+ break;
+
+ default:
+ break;
+
+ }
+
+ if ( save ) {
+
+ covmat[ lprop ] = value;
+
+ }
}
- scope.manager.itemError(url);
+ }
+
+ return converted;
+
+ }
+
+ preload() {
+
+ for ( const mn in this.materialsInfo ) {
+
+ this.create( mn );
}
- }, onProgress, onError);
+ }
- }
+ getIndex( materialName ) {
- setMaterialOptions(value) {
+ return this.nameLookup[ materialName ];
- this.materialOptions = value;
- return this;
+ }
- }
+ getAsArray() {
- /**
- * Parses a MTL file.
- *
- * @param {String} text - Content of MTL file
- * @return {MaterialCreator}
- *
- * @see setPath setResourcePath
- *
- * @note In order for relative texture references to resolve correctly
- * you must call setResourcePath() explicitly prior to parse.
- */
- parse(text, path) {
+ let index = 0;
- const lines = text.split('\n');
- let info = {};
- const delimiter_pattern = /\s+/;
- const materialsInfo = {};
+ for ( const mn in this.materialsInfo ) {
- for (let i = 0; i < lines.length; i++) {
+ this.materialsArray[ index ] = this.create( mn );
+ this.nameLookup[ mn ] = index;
+ index ++;
- let line = lines[i];
- line = line.trim();
+ }
- if (line.length === 0 || line.charAt(0) === '#') {
+ return this.materialsArray;
- // Blank line or comment ignore
- continue;
+ }
+
+ create( materialName ) {
+
+ if ( this.materials[ materialName ] === undefined ) {
+
+ this.createMaterial_( materialName );
}
- const pos = line.indexOf(' ');
+ return this.materials[ materialName ];
- let key = (pos >= 0) ? line.substring(0, pos) : line;
- key = key.toLowerCase();
+ }
- let value = (pos >= 0) ? line.substring(pos + 1) : '';
- value = value.trim();
+ createMaterial_( materialName ) {
- if (key === 'newmtl') {
+ // Create material
+ const scope = this;
+ const mat = this.materialsInfo[ materialName ];
+ const params = {
+ name: materialName,
+ side: this.side
+ };
- // New material
+ function resolveURL( baseUrl, url ) {
- info = {name: value};
- materialsInfo[value] = info;
+ if ( typeof url !== 'string' || url === '' ) return ''; // Absolute URL
- } else {
+ if ( /^https?:\/\//i.test( url ) ) return url;
+ return baseUrl + url;
- if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {
+ }
- const ss = value.split(delimiter_pattern, 3);
- info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
+ function setMapForType( mapType, value ) {
- } else {
+ if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+ const texParams = scope.getTextureParams( value, params );
+ const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+ map.repeat.copy( texParams.scale );
+ map.offset.copy( texParams.offset );
+ map.wrapS = scope.wrap;
+ map.wrapT = scope.wrap;
+
+ params[ mapType ] = map;
+
+ }
+
+ for ( const prop in mat ) {
- info[key] = value;
+ const value = mat[ prop ];
+ let n;
+ if ( value === '' ) continue;
+
+ switch ( prop.toLowerCase() ) {
+
+ // Ns is material specular exponent
+ case 'kd':
+ // Diffuse color (color under white light) using RGB values
+ params.color = new THREE.Color().fromArray( value );
+ break;
+
+ case 'ks':
+ // Specular color (color when light is reflected from shiny surface) using RGB values
+ params.specular = new THREE.Color().fromArray( value );
+ break;
+
+ case 'ke':
+ // Emissive using RGB values
+ params.emissive = new THREE.Color().fromArray( value );
+ break;
+
+ case 'map_kd':
+ // Diffuse texture map
+ setMapForType( 'map', value );
+ break;
+
+ case 'map_ks':
+ // Specular map
+ setMapForType( 'specularMap', value );
+ break;
+
+ case 'map_ke':
+ // Emissive map
+ setMapForType( 'emissiveMap', value );
+ break;
+
+ case 'norm':
+ setMapForType( 'normalMap', value );
+ break;
+
+ case 'map_bump':
+ case 'bump':
+ // Bump texture map
+ setMapForType( 'bumpMap', value );
+ break;
+
+ case 'map_d':
+ // Alpha map
+ setMapForType( 'alphaMap', value );
+ params.transparent = true;
+ break;
+
+ case 'ns':
+ // The specular exponent (defines the focus of the specular highlight)
+ // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+ params.shininess = parseFloat( value );
+ break;
+
+ case 'd':
+ n = parseFloat( value );
+
+ if ( n < 1 ) {
+
+ params.opacity = n;
+ params.transparent = true;
+
+ }
+
+ break;
+
+ case 'tr':
+ n = parseFloat( value );
+ if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+ if ( n > 0 ) {
+
+ params.opacity = 1 - n;
+ params.transparent = true;
+
+ }
+
+ break;
+
+ default:
+ break;
}
}
+ this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
+ return this.materials[ materialName ];
+
}
- const materialCreator = new MaterialCreator(this.resourcePath || path, this.materialOptions);
- materialCreator.setCrossOrigin(this.crossOrigin);
- materialCreator.setManager(this.manager);
- materialCreator.setMaterials(materialsInfo);
- return materialCreator;
+ getTextureParams( value, matParams ) {
- }
+ const texParams = {
+ scale: new THREE.Vector2( 1, 1 ),
+ offset: new THREE.Vector2( 0, 0 )
+ };
+ const items = value.split( /\s+/ );
+ let pos;
+ pos = items.indexOf( '-bm' );
-}
+ if ( pos >= 0 ) {
-/**
- * Create a new MTLLoader.MaterialCreator
- * @param baseUrl - Url relative to which textures are loaded
- * @param options - Set of options on how to construct the materials
- * side: Which side to apply the material
- * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
- * wrap: What type of wrapping to apply for textures
- * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
- * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
- * Default: false, assumed to be already normalized
- * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
- * Default: false
- * @constructor
- */
+ matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+ items.splice( pos, 2 );
-class MaterialCreator {
+ }
+
+ pos = items.indexOf( '-s' );
- constructor(baseUrl = '', options = {}) {
+ if ( pos >= 0 ) {
- this.baseUrl = baseUrl;
- this.options = options;
- this.materialsInfo = {};
- this.materials = {};
- this.materialsArray = [];
- this.nameLookup = {};
+ texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+ items.splice( pos, 4 ); // we expect 3 parameters here!
- this.crossOrigin = 'anonymous';
+ }
- this.side = (this.options.side !== undefined) ? this.options.side : THREE.FrontSide;
- this.wrap = (this.options.wrap !== undefined) ? this.options.wrap : THREE.RepeatWrapping;
+ pos = items.indexOf( '-o' );
- }
+ if ( pos >= 0 ) {
+
+ texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+ items.splice( pos, 4 ); // we expect 3 parameters here!
+
+ }
+
+ texParams.url = items.join( ' ' ).trim();
+ return texParams;
- setCrossOrigin(value) {
+ }
+
+ loadTexture( url, mapping, onLoad, onProgress, onError ) {
+
+ const manager = this.manager !== undefined ? this.manager : THREE.DefaultLoadingManager;
+ let loader = manager.getHandler( url );
+
+ if ( loader === null ) {
+
+ loader = new THREE.TextureLoader( manager );
+
+ }
- this.crossOrigin = value;
- return this;
+ if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+ const texture = loader.load( url, onLoad, onProgress, onError );
+ if ( mapping !== undefined ) texture.mapping = mapping;
+ return texture;
+
+ }
}
- setManager(value) {
+ THREE.MTLLoader = MTLLoader;
+
+} )();
+( function () {
+
+ const _object_pattern = /^[og]\s*(.+)?/; // mtllib file_reference
+
+ const _material_library_pattern = /^mtllib /; // usemtl material_name
+
+ const _material_use_pattern = /^usemtl /; // usemap map_name
+
+ const _map_use_pattern = /^usemap /;
+
+ const _vA = new THREE.Vector3();
+
+ const _vB = new THREE.Vector3();
+
+ const _vC = new THREE.Vector3();
+
+ const _ab = new THREE.Vector3();
+
+ const _cb = new THREE.Vector3();
+
+ function ParserState() {
+
+ const state = {
+ objects: [],
+ object: {},
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+ materials: {},
+ materialLibraries: [],
+ startObject: function ( name, fromDeclaration ) {
+
+ // If the current object (initial from reset) is not from a g/o declaration in the parsed
+ // file. We need to use it for the first parsed g/o to keep things in sync.
+ if ( this.object && this.object.fromDeclaration === false ) {
+
+ this.object.name = name;
+ this.object.fromDeclaration = fromDeclaration !== false;
+ return;
+
+ }
+
+ const previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined;
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ this.object = {
+ name: name || '',
+ fromDeclaration: fromDeclaration !== false,
+ geometry: {
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+ hasUVIndices: false
+ },
+ materials: [],
+ smooth: true,
+ startMaterial: function ( name, libraries ) {
+
+ const previous = this._finalize( false ); // New usemtl declaration overwrites an inherited material, except if faces were declared
+ // after the material, then it must be preserved for proper MultiMaterial continuation.
+
+
+ if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+ this.materials.splice( previous.index, 1 );
+
+ }
+
+ const material = {
+ index: this.materials.length,
+ name: name || '',
+ mtllib: Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '',
+ smooth: previous !== undefined ? previous.smooth : this.smooth,
+ groupStart: previous !== undefined ? previous.groupEnd : 0,
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false,
+ clone: function ( index ) {
+
+ const cloned = {
+ index: typeof index === 'number' ? index : this.index,
+ name: this.name,
+ mtllib: this.mtllib,
+ smooth: this.smooth,
+ groupStart: 0,
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false
+ };
+ cloned.clone = this.clone.bind( cloned );
+ return cloned;
+
+ }
+ };
+ this.materials.push( material );
+ return material;
+
+ },
+ currentMaterial: function () {
+
+ if ( this.materials.length > 0 ) {
+
+ return this.materials[ this.materials.length - 1 ];
+
+ }
+
+ return undefined;
+
+ },
+ _finalize: function ( end ) {
+
+ const lastMultiMaterial = this.currentMaterial();
+
+ if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+ lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+ lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+ lastMultiMaterial.inherited = false;
+
+ } // Ignore objects tail materials if no face declarations followed them before a new o/g started.
+
+
+ if ( end && this.materials.length > 1 ) {
+
+ for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+ if ( this.materials[ mi ].groupCount <= 0 ) {
+
+ this.materials.splice( mi, 1 );
+
+ }
+
+ }
+
+ } // Guarantee at least one empty material, this makes the creation later more straight forward.
+
+
+ if ( end && this.materials.length === 0 ) {
+
+ this.materials.push( {
+ name: '',
+ smooth: this.smooth
+ } );
+
+ }
+
+ return lastMultiMaterial;
+
+ }
+ }; // Inherit previous objects material.
+ // Spec tells us that a declared material must be set to all objects until a new material is declared.
+ // If a usemtl declaration is encountered while this new object is being parsed, it will
+ // overwrite the inherited material. Exception being that there was already face declarations
+ // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+ if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+ const declared = previousMaterial.clone( 0 );
+ declared.inherited = true;
+ this.object.materials.push( declared );
+
+ }
+
+ this.objects.push( this.object );
+
+ },
+ finalize: function () {
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ },
+ parseVertexIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+ parseNormalIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+ parseUVIndex: function ( value, len ) {
+
+ const index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+ },
+ addVertex: function ( a, b, c ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+ addVertexPoint: function ( a ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+ addVertexLine: function ( a ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.vertices;
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+ addNormal: function ( a, b, c ) {
+
+ const src = this.normals;
+ const dst = this.object.geometry.normals;
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+ addFaceNormal: function ( a, b, c ) {
+
+ const src = this.vertices;
+ const dst = this.object.geometry.normals;
+
+ _vA.fromArray( src, a );
+
+ _vB.fromArray( src, b );
+
+ _vC.fromArray( src, c );
+
+ _cb.subVectors( _vC, _vB );
+
+ _ab.subVectors( _vA, _vB );
+
+ _cb.cross( _ab );
+
+ _cb.normalize();
+
+ dst.push( _cb.x, _cb.y, _cb.z );
+ dst.push( _cb.x, _cb.y, _cb.z );
+ dst.push( _cb.x, _cb.y, _cb.z );
+
+ },
+ addColor: function ( a, b, c ) {
+
+ const src = this.colors;
+ const dst = this.object.geometry.colors;
+ if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+ addUV: function ( a, b, c ) {
+
+ const src = this.uvs;
+ const dst = this.object.geometry.uvs;
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+ },
+ addDefaultUV: function () {
+
+ const dst = this.object.geometry.uvs;
+ dst.push( 0, 0 );
+ dst.push( 0, 0 );
+ dst.push( 0, 0 );
+
+ },
+ addUVLine: function ( a ) {
+
+ const src = this.uvs;
+ const dst = this.object.geometry.uvs;
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+ },
+ addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+ const vLen = this.vertices.length;
+ let ia = this.parseVertexIndex( a, vLen );
+ let ib = this.parseVertexIndex( b, vLen );
+ let ic = this.parseVertexIndex( c, vLen );
+ this.addVertex( ia, ib, ic );
+ this.addColor( ia, ib, ic ); // normals
+
+ if ( na !== undefined && na !== '' ) {
+
+ const nLen = this.normals.length;
+ ia = this.parseNormalIndex( na, nLen );
+ ib = this.parseNormalIndex( nb, nLen );
+ ic = this.parseNormalIndex( nc, nLen );
+ this.addNormal( ia, ib, ic );
+
+ } else {
+
+ this.addFaceNormal( ia, ib, ic );
+
+ } // uvs
+
+
+ if ( ua !== undefined && ua !== '' ) {
+
+ const uvLen = this.uvs.length;
+ ia = this.parseUVIndex( ua, uvLen );
+ ib = this.parseUVIndex( ub, uvLen );
+ ic = this.parseUVIndex( uc, uvLen );
+ this.addUV( ia, ib, ic );
+ this.object.geometry.hasUVIndices = true;
+
+ } else {
+
+ // add placeholder values (for inconsistent face definitions)
+ this.addDefaultUV();
+
+ }
+
+ },
+ addPointGeometry: function ( vertices ) {
+
+ this.object.geometry.type = 'Points';
+ const vLen = this.vertices.length;
+
+ for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ const index = this.parseVertexIndex( vertices[ vi ], vLen );
+ this.addVertexPoint( index );
+ this.addColor( index );
+
+ }
+
+ },
+ addLineGeometry: function ( vertices, uvs ) {
+
+ this.object.geometry.type = 'Line';
+ const vLen = this.vertices.length;
+ const uvLen = this.uvs.length;
+
+ for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+ this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+ }
+
+ }
+ };
+ state.startObject( '', false );
+ return state;
+
+ } //
+
+
+ class OBJLoader extends THREE.Loader {
+
+ constructor( manager ) {
+
+ super( manager );
+ this.materials = null;
+
+ }
+
+ load( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+ const loader = new THREE.FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setWithCredentials( this.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( text ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ }
+
+ setMaterials( materials ) {
+
+ this.materials = materials;
+ return this;
+
+ }
+
+ parse( text ) {
+
+ const state = new ParserState();
+
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+ // This is faster than String.split with regex that splits on both
+ text = text.replace( /\r\n/g, '\n' );
+
+ }
+
+ if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+ // join lines separated by a line continuation character (\)
+ text = text.replace( /\\\n/g, '' );
+
+ }
+
+ const lines = text.split( '\n' );
+ let line = '',
+ lineFirstChar = '';
+ let lineLength = 0;
+ let result = []; // Faster to just trim left side of the line. Use if available.
+
+ const trimLeft = typeof ''.trimLeft === 'function';
+
+ for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+ line = lines[ i ];
+ line = trimLeft ? line.trimLeft() : line.trim();
+ lineLength = line.length;
+ if ( lineLength === 0 ) continue;
+ lineFirstChar = line.charAt( 0 ); // @todo invoke passed in handler if any
+
+ if ( lineFirstChar === '#' ) continue;
+
+ if ( lineFirstChar === 'v' ) {
+
+ const data = line.split( /\s+/ );
+
+ switch ( data[ 0 ] ) {
+
+ case 'v':
+ state.vertices.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) );
+
+ if ( data.length >= 7 ) {
+
+ state.colors.push( parseFloat( data[ 4 ] ), parseFloat( data[ 5 ] ), parseFloat( data[ 6 ] ) );
+
+ } else {
+
+ // if no colors are defined, add placeholders so color and vertex indices match
+ state.colors.push( undefined, undefined, undefined );
+
+ }
+
+ break;
+
+ case 'vn':
+ state.normals.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) );
+ break;
+
+ case 'vt':
+ state.uvs.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ) );
+ break;
+
+ }
+
+ } else if ( lineFirstChar === 'f' ) {
+
+ const lineData = line.substr( 1 ).trim();
+ const vertexData = lineData.split( /\s+/ );
+ const faceVertices = []; // Parse the face vertex data into an easy to work with format
+
+ for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+ const vertex = vertexData[ j ];
+
+ if ( vertex.length > 0 ) {
+
+ const vertexParts = vertex.split( '/' );
+ faceVertices.push( vertexParts );
+
+ }
+
+ } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+
+ const v1 = faceVertices[ 0 ];
+
+ for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+ const v2 = faceVertices[ j ];
+ const v3 = faceVertices[ j + 1 ];
+ state.addFace( v1[ 0 ], v2[ 0 ], v3[ 0 ], v1[ 1 ], v2[ 1 ], v3[ 1 ], v1[ 2 ], v2[ 2 ], v3[ 2 ] );
+
+ }
+
+ } else if ( lineFirstChar === 'l' ) {
+
+ const lineParts = line.substring( 1 ).trim().split( ' ' );
+ let lineVertices = [];
+ const lineUVs = [];
+
+ if ( line.indexOf( '/' ) === - 1 ) {
+
+ lineVertices = lineParts;
+
+ } else {
+
+ for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+ const parts = lineParts[ li ].split( '/' );
+ if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
+ if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
+
+ }
+
+ }
+
+ state.addLineGeometry( lineVertices, lineUVs );
+
+ } else if ( lineFirstChar === 'p' ) {
+
+ const lineData = line.substr( 1 ).trim();
+ const pointData = lineData.split( ' ' );
+ state.addPointGeometry( pointData );
+
+ } else if ( ( result = _object_pattern.exec( line ) ) !== null ) {
+
+ // o object_name
+ // or
+ // g group_name
+ // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+ // let name = result[ 0 ].substr( 1 ).trim();
+ const name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
+ state.startObject( name );
+
+ } else if ( _material_use_pattern.test( line ) ) {
+
+ // material
+ state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+ } else if ( _material_library_pattern.test( line ) ) {
+
+ // mtl file
+ state.materialLibraries.push( line.substring( 7 ).trim() );
+
+ } else if ( _map_use_pattern.test( line ) ) {
+
+ // the line is parsed but ignored since the loader assumes textures are defined MTL files
+ // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+ console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
+
+ } else if ( lineFirstChar === 's' ) {
+
+ result = line.split( ' ' ); // smooth shading
+ // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+ // but does not define a usemtl for each face set.
+ // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+ // This requires some care to not create extra material on each smooth value for "normal" obj files.
+ // where explicit usemtl defines geometry groups.
+ // Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+ /*
+ * http://paulbourke.net/dataformats/obj/
+ *
+ * From chapter "Grouping" Syntax explanation "s group_number":
+ * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+ * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+ * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+ * than 0."
+ */
+
+ if ( result.length > 1 ) {
+
+ const value = result[ 1 ].trim().toLowerCase();
+ state.object.smooth = value !== '0' && value !== 'off';
+
+ } else {
+
+ // ZBrush can produce "s" lines #11707
+ state.object.smooth = true;
+
+ }
+
+ const material = state.object.currentMaterial();
+ if ( material ) material.smooth = state.object.smooth;
+
+ } else {
+
+ // Handle null terminated files without exception
+ if ( line === '\0' ) continue;
+ console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+ }
+
+ }
+
+ state.finalize();
+ const container = new THREE.Group();
+ container.materialLibraries = [].concat( state.materialLibraries );
+ const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
+
+ if ( hasPrimitives === true ) {
+
+ for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
+
+ const object = state.objects[ i ];
+ const geometry = object.geometry;
+ const materials = object.materials;
+ const isLine = geometry.type === 'Line';
+ const isPoints = geometry.type === 'Points';
+ let hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces
+
+ if ( geometry.vertices.length === 0 ) continue;
+ const buffergeometry = new THREE.BufferGeometry();
+ buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
+
+ if ( geometry.normals.length > 0 ) {
+
+ buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
+
+ }
+
+ if ( geometry.colors.length > 0 ) {
+
+ hasVertexColors = true;
+ buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );
+
+ }
+
+ if ( geometry.hasUVIndices === true ) {
+
+ buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
+
+ } // Create materials
+
+
+ const createdMaterials = [];
+
+ for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ const sourceMaterial = materials[ mi ];
+ const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
+ let material = state.materials[ materialHash ];
+
+ if ( this.materials !== null ) {
+
+ material = this.materials.create( sourceMaterial.name ); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+
+ if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
+
+ const materialLine = new THREE.LineBasicMaterial();
+ THREE.Material.prototype.copy.call( materialLine, material );
+ materialLine.color.copy( material.color );
+ material = materialLine;
+
+ } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {
+
+ const materialPoints = new THREE.PointsMaterial( {
+ size: 10,
+ sizeAttenuation: false
+ } );
+ THREE.Material.prototype.copy.call( materialPoints, material );
+ materialPoints.color.copy( material.color );
+ materialPoints.map = material.map;
+ material = materialPoints;
+
+ }
+
+ }
+
+ if ( material === undefined ) {
+
+ if ( isLine ) {
+
+ material = new THREE.LineBasicMaterial();
+
+ } else if ( isPoints ) {
+
+ material = new THREE.PointsMaterial( {
+ size: 1,
+ sizeAttenuation: false
+ } );
+
+ } else {
+
+ material = new THREE.MeshPhongMaterial();
+
+ }
+
+ material.name = sourceMaterial.name;
+ material.flatShading = sourceMaterial.smooth ? false : true;
+ material.vertexColors = hasVertexColors;
+ state.materials[ materialHash ] = material;
+
+ }
- this.manager = value;
+ createdMaterials.push( material );
- }
+ } // Create mesh
- setMaterials(materialsInfo) {
- this.materialsInfo = this.convert(materialsInfo);
- this.materials = {};
- this.materialsArray = [];
- this.nameLookup = {};
+ let mesh;
- }
+ if ( createdMaterials.length > 1 ) {
- convert(materialsInfo) {
+ for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
- if (!this.options) return materialsInfo;
+ const sourceMaterial = materials[ mi ];
+ buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
- const converted = {};
+ }
- for (const mn in materialsInfo) {
+ if ( isLine ) {
- // Convert materials info into normalized form based on options
+ mesh = new THREE.LineSegments( buffergeometry, createdMaterials );
- const mat = materialsInfo[mn];
+ } else if ( isPoints ) {
- const covmat = {};
+ mesh = new THREE.Points( buffergeometry, createdMaterials );
- converted[mn] = covmat;
+ } else {
- for (const prop in mat) {
+ mesh = new THREE.Mesh( buffergeometry, createdMaterials );
- let save = true;
- let value = mat[prop];
- const lprop = prop.toLowerCase();
+ }
- switch (lprop) {
+ } else {
- case 'kd':
- case 'ka':
- case 'ks':
+ if ( isLine ) {
- // Diffuse color (color under white light) using RGB values
+ mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] );
- if (this.options && this.options.normalizeRGB) {
+ } else if ( isPoints ) {
- value = [value[0] / 255, value[1] / 255, value[2] / 255];
+ mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] );
- }
+ } else {
- if (this.options && this.options.ignoreZeroRGBs) {
+ mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] );
- if (value[0] === 0 && value[1] === 0 && value[2] === 0) {
+ }
- // ignore
+ }
- save = false;
+ mesh.name = object.name;
+ container.add( mesh );
- }
+ }
- }
+ } else {
- break;
+ // if there is only the default parser state object with no geometry data, interpret data as point cloud
+ if ( state.vertices.length > 0 ) {
- default:
+ const material = new THREE.PointsMaterial( {
+ size: 1,
+ sizeAttenuation: false
+ } );
+ const buffergeometry = new THREE.BufferGeometry();
+ buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( state.vertices, 3 ) );
- break;
+ if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
- }
+ buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( state.colors, 3 ) );
+ material.vertexColors = true;
- if (save) {
+ }
- covmat[lprop] = value;
+ const points = new THREE.Points( buffergeometry, material );
+ container.add( points );
}
}
- }
+ return container;
- return converted;
+ }
}
- preload() {
+ THREE.OBJLoader = OBJLoader;
- for (const mn in this.materialsInfo) {
+} )();
+(function () {
- this.create(mn);
+ /**
+ * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
+ */
- }
+ const _position = new THREE.Vector3();
- }
+ const _quaternion = new THREE.Quaternion();
- getIndex(materialName) {
+ const _scale = new THREE.Vector3();
- return this.nameLookup[materialName];
+ class CSS3DObject extends THREE.Object3D {
- }
+ constructor(element = document.createElement('div')) {
- getAsArray() {
+ super();
+ this.element = element;
+ this.element.style.position = 'absolute';
+ this.element.style.pointerEvents = 'auto';
+ this.element.style.userSelect = 'none';
+ this.element.setAttribute('draggable', false);
+ this.addEventListener('removed', function () {
- let index = 0;
+ this.traverse(function (object) {
- for (const mn in this.materialsInfo) {
+ if (object.element instanceof Element && object.element.parentNode !== null) {
- this.materialsArray[index] = this.create(mn);
- this.nameLookup[mn] = index;
- index++;
+ object.element.parentNode.removeChild(object.element);
- }
+ }
- return this.materialsArray;
+ });
- }
+ });
- create(materialName) {
+ }
- if (this.materials[materialName] === undefined) {
+ copy(source, recursive) {
- this.createMaterial_(materialName);
+ super.copy(source, recursive);
+ this.element = source.element.cloneNode(true);
+ return this;
}
- return this.materials[materialName];
-
}
- createMaterial_(materialName) {
+ CSS3DObject.prototype.isCSS3DObject = true;
- // Create material
+ class CSS3DSprite extends CSS3DObject {
- const scope = this;
- const mat = this.materialsInfo[materialName];
- const params = {
+ constructor(element) {
- name: materialName,
- side: this.side
+ super(element);
+ this.rotation2D = 0;
- };
+ }
- function resolveURL(baseUrl, url) {
+ copy(source, recursive) {
- if (typeof url !== 'string' || url === '')
- return '';
+ super.copy(source, recursive);
+ this.rotation2D = source.rotation2D;
+ return this;
- // Absolute URL
- if (/^https?:\/\//i.test(url)) return url;
+ }
- return baseUrl + url;
+ }
- }
+ CSS3DSprite.prototype.isCSS3DSprite = true; //
- function setMapForType(mapType, value) {
+ const _matrix = new THREE.Matrix4();
- if (params[mapType]) return; // Keep the first encountered texture
+ const _matrix2 = new THREE.Matrix4();
- const texParams = scope.getTextureParams(value, params);
- const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url));
+ class CSS3DRenderer {
- map.repeat.copy(texParams.scale);
- map.offset.copy(texParams.offset);
+ constructor(parameters = {}) {
- map.wrapS = scope.wrap;
- map.wrapT = scope.wrap;
+ const _this = this;
- params[mapType] = map;
+ let _width, _height;
- }
+ let _widthHalf, _heightHalf;
- for (const prop in mat) {
+ const cache = {
+ camera: {
+ fov: 0,
+ style: ''
+ },
+ objects: new WeakMap()
+ };
+ const domElement = parameters.element !== undefined ? parameters.element : document.createElement('div');
+ domElement.style.overflow = 'hidden';
+ this.domElement = domElement;
+ const cameraElement = document.createElement('div');
+ cameraElement.style.transformStyle = 'preserve-3d';
+ cameraElement.style.pointerEvents = 'none';
+ domElement.appendChild(cameraElement);
+
+ this.getSize = function () {
+
+ return {
+ width: _width,
+ height: _height
+ };
- const value = mat[prop];
- let n;
+ };
+
+ this.render = function (scene, camera) {
- if (value === '') continue;
+ const fov = camera.projectionMatrix.elements[5] * _heightHalf;
- switch (prop.toLowerCase()) {
+ if (cache.camera.fov !== fov) {
- // Ns is material specular exponent
+ domElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : '';
+ cache.camera.fov = fov;
- case 'kd':
+ }
- // Diffuse color (color under white light) using RGB values
+ if (scene.autoUpdate === true) scene.updateMatrixWorld();
+ if (camera.parent === null) camera.updateMatrixWorld();
+ let tx, ty;
- params.color = new THREE.Color().fromArray(value);
+ if (camera.isOrthographicCamera) {
- break;
+ tx = -(camera.right + camera.left) / 2;
+ ty = (camera.top + camera.bottom) / 2;
- case 'ks':
+ }
- // Specular color (color when light is reflected from shiny surface) using RGB values
- params.specular = new THREE.Color().fromArray(value);
+ const cameraCSSMatrix = camera.isOrthographicCamera ? 'scale(' + fov + ')' + 'translate(' + epsilon(tx) + 'px,' + epsilon(ty) + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse) : 'translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse);
+ const style = cameraCSSMatrix + 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';
- break;
+ if (cache.camera.style !== style) {
- case 'ke':
+ cameraElement.style.transform = style;
+ cache.camera.style = style;
- // Emissive using RGB values
- params.emissive = new THREE.Color().fromArray(value);
+ }
- break;
+ renderObject(scene, scene, camera, cameraCSSMatrix);
- case 'map_kd':
+ };
- // Diffuse texture map
+ this.setSize = function (width, height) {
- setMapForType('map', value);
+ _width = width;
+ _height = height;
+ _widthHalf = _width / 2;
+ _heightHalf = _height / 2;
+ domElement.style.width = width + 'px';
+ domElement.style.height = height + 'px';
+ cameraElement.style.width = width + 'px';
+ cameraElement.style.height = height + 'px';
- break;
+ };
- case 'map_ks':
+ function epsilon(value) {
- // Specular map
+ return Math.abs(value) < 1e-10 ? 0 : value;
- setMapForType('specularMap', value);
+ }
- break;
+ function getCameraCSSMatrix(matrix) {
- case 'map_ke':
+ const elements = matrix.elements;
+ return 'matrix3d(' + epsilon(elements[0]) + ',' + epsilon(-elements[1]) + ',' + epsilon(elements[2]) + ',' + epsilon(elements[3]) + ',' + epsilon(elements[4]) + ',' + epsilon(-elements[5]) + ',' + epsilon(elements[6]) + ',' + epsilon(elements[7]) + ',' + epsilon(elements[8]) + ',' + epsilon(-elements[9]) + ',' + epsilon(elements[10]) + ',' + epsilon(elements[11]) + ',' + epsilon(elements[12]) + ',' + epsilon(-elements[13]) + ',' + epsilon(elements[14]) + ',' + epsilon(elements[15]) + ')';
- // Emissive map
+ }
- setMapForType('emissiveMap', value);
+ function getObjectCSSMatrix(matrix) {
- break;
+ const elements = matrix.elements;
+ const matrix3d = 'matrix3d(' + epsilon(elements[0]) + ',' + epsilon(elements[1]) + ',' + epsilon(elements[2]) + ',' + epsilon(elements[3]) + ',' + epsilon(-elements[4]) + ',' + epsilon(-elements[5]) + ',' + epsilon(-elements[6]) + ',' + epsilon(-elements[7]) + ',' + epsilon(elements[8]) + ',' + epsilon(elements[9]) + ',' + epsilon(elements[10]) + ',' + epsilon(elements[11]) + ',' + epsilon(elements[12]) + ',' + epsilon(elements[13]) + ',' + epsilon(elements[14]) + ',' + epsilon(elements[15]) + ')';
+ return 'translate(-50%,-50%)' + matrix3d;
- case 'norm':
+ }
- setMapForType('normalMap', value);
+ function renderObject(object, scene, camera, cameraCSSMatrix) {
- break;
+ if (object.isCSS3DObject) {
- case 'map_bump':
- case 'bump':
+ const visible = object.visible && object.layers.test(camera.layers);
+ object.element.style.display = visible ? '' : 'none'; // only getObjectCSSMatrix when object.visible
- // Bump texture map
+ if (visible) {
- setMapForType('bumpMap', value);
+ object.onBeforeRender(_this, scene, camera);
+ let style;
- break;
+ if (object.isCSS3DSprite) {
- case 'map_d':
+ // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
+ _matrix.copy(camera.matrixWorldInverse);
- // Alpha map
+ _matrix.transpose();
- setMapForType('alphaMap', value);
- params.transparent = true;
+ if (object.rotation2D !== 0) _matrix.multiply(_matrix2.makeRotationZ(object.rotation2D));
+ object.matrixWorld.decompose(_position, _quaternion, _scale);
- break;
+ _matrix.setPosition(_position);
- case 'ns':
+ _matrix.scale(_scale);
- // The specular exponent (defines the focus of the specular highlight)
- // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+ _matrix.elements[3] = 0;
+ _matrix.elements[7] = 0;
+ _matrix.elements[11] = 0;
+ _matrix.elements[15] = 1;
+ style = getObjectCSSMatrix(_matrix);
- params.shininess = parseFloat(value);
+ } else {
- break;
+ style = getObjectCSSMatrix(object.matrixWorld);
- case 'd':
- n = parseFloat(value);
+ }
- if (n < 1) {
+ const element = object.element;
+ const cachedObject = cache.objects.get(object);
- params.opacity = n;
- params.transparent = true;
+ if (cachedObject === undefined || cachedObject.style !== style) {
- }
+ element.style.transform = style;
+ const objectData = {
+ style: style
+ };
+ cache.objects.set(object, objectData);
- break;
+ }
- case 'tr':
- n = parseFloat(value);
+ if (element.parentNode !== cameraElement) {
- if (this.options && this.options.invertTrProperty) n = 1 - n;
+ cameraElement.appendChild(element);
- if (n > 0) {
+ }
- params.opacity = 1 - n;
- params.transparent = true;
+ object.onAfterRender(_this, scene, camera);
}
- break;
+ }
+
+ for (let i = 0, l = object.children.length; i < l; i++) {
- default:
- break;
+ renderObject(object.children[i], scene, camera, cameraCSSMatrix);
+
+ }
}
}
- this.materials[materialName] = new THREE.MeshPhongMaterial(params);
- return this.materials[materialName];
-
}
- getTextureParams(value, matParams) {
+ THREE.CSS3DObject = CSS3DObject;
+ THREE.CSS3DRenderer = CSS3DRenderer;
+ THREE.CSS3DSprite = CSS3DSprite;
- const texParams = {
+})();
+( function () {
- scale: new THREE.Vector2(1, 1),
- offset: new THREE.Vector2(0, 0)
+ // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+ //
+ // Orbit - left mouse / touch: one-finger move
+ // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+ // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
- };
+ const _changeEvent = {
+ type: 'change'
+ };
+ const _startEvent = {
+ type: 'start'
+ };
+ const _endEvent = {
+ type: 'end'
+ };
- const items = value.split(/\s+/);
- let pos;
+ class OrbitControls extends THREE.EventDispatcher {
- pos = items.indexOf('-bm');
+ constructor( object, domElement ) {
- if (pos >= 0) {
+ super();
+ if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
+ if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
+ this.object = object;
+ this.domElement = domElement;
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
+ // Set to false to disable this control
- matParams.bumpScale = parseFloat(items[pos + 1]);
- items.splice(pos, 2);
+ this.enabled = true; // "target" sets the location of focus, where the object orbits around
- }
+ this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
- pos = items.indexOf('-s');
+ this.minDistance = 0;
+ this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
- if (pos >= 0) {
+ this.minZoom = 0;
+ this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
- texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
- items.splice(pos, 4); // we expect 3 parameters here!
+ this.minPolarAngle = 0; // radians
- }
+ this.maxPolarAngle = Math.PI; // radians
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
- pos = items.indexOf('-o');
+ this.minAzimuthAngle = - Infinity; // radians
- if (pos >= 0) {
+ this.maxAzimuthAngle = Infinity; // radians
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
- texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
- items.splice(pos, 4); // we expect 3 parameters here!
+ this.enableDamping = false;
+ this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
- }
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0; // Set to false to disable rotating
- texParams.url = items.join(' ').trim();
- return texParams;
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0; // Set to false to disable panning
- }
+ this.enablePan = true;
+ this.panSpeed = 1.0;
+ this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
- loadTexture(url, mapping, onLoad, onProgress, onError) {
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
- const manager = (this.manager !== undefined) ? this.manager : THREE.DefaultLoadingManager;
- let loader = manager.getHandler(url);
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+ // The four arrow keys
- if (loader === null) {
+ this.keys = {
+ LEFT: 'ArrowLeft',
+ UP: 'ArrowUp',
+ RIGHT: 'ArrowRight',
+ BOTTOM: 'ArrowDown'
+ }; // Mouse buttons
- loader = new THREE.TextureLoader(manager);
+ this.mouseButtons = {
+ LEFT: THREE.MOUSE.ROTATE,
+ MIDDLE: THREE.MOUSE.DOLLY,
+ RIGHT: THREE.MOUSE.PAN
+ }; // Touch fingers
- }
+ this.touches = {
+ ONE: THREE.TOUCH.ROTATE,
+ TWO: THREE.TOUCH.DOLLY_PAN
+ }; // for reset
- if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom; // the target DOM element for key events
- const texture = loader.load(url, onLoad, onProgress, onError);
+ this._domElementKeyEvents = null; //
+ // public methods
+ //
- if (mapping !== undefined) texture.mapping = mapping;
+ this.getPolarAngle = function () {
- return texture;
+ return spherical.phi;
- }
+ };
-}
+ this.getAzimuthalAngle = function () {
-module.exports = {MTLLoader: MTLLoader};
+ return spherical.theta;
-},{}],3:[function(require,module,exports){
-window.THREE.OBJLoader = require("./OBJLoader").OBJLoader;
+ };
-},{"./OBJLoader":4}],4:[function(require,module,exports){
-// o object_name | g group_name
-const _object_pattern = /^[og]\s*(.+)?/;
-// mtllib file_reference
-const _material_library_pattern = /^mtllib /;
-// usemtl material_name
-const _material_use_pattern = /^usemtl /;
-// usemap map_name
-const _map_use_pattern = /^usemap /;
+ this.getDistance = function () {
-const _vA = new THREE.Vector3();
-const _vB = new THREE.Vector3();
-const _vC = new THREE.Vector3();
+ return this.object.position.distanceTo( this.target );
-const _ab = new THREE.Vector3();
-const _cb = new THREE.Vector3();
+ };
-function ParserState() {
+ this.listenToKeyEvents = function ( domElement ) {
- const state = {
- objects: [],
- object: {},
+ domElement.addEventListener( 'keydown', onKeyDown );
+ this._domElementKeyEvents = domElement;
- vertices: [],
- normals: [],
- colors: [],
- uvs: [],
+ };
- materials: {},
- materialLibraries: [],
+ this.saveState = function () {
- startObject: function (name, fromDeclaration) {
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
- // If the current object (initial from reset) is not from a g/o declaration in the parsed
- // file. We need to use it for the first parsed g/o to keep things in sync.
- if (this.object && this.object.fromDeclaration === false) {
+ };
- this.object.name = name;
- this.object.fromDeclaration = (fromDeclaration !== false);
- return;
+ this.reset = function () {
- }
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( _changeEvent );
+ scope.update();
+ state = STATE.NONE;
- const previousMaterial = (this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined);
+ }; // this method is exposed, but perhaps it would be better if we can make it private...
- if (this.object && typeof this.object._finalize === 'function') {
- this.object._finalize(true);
+ this.update = function () {
- }
+ const offset = new THREE.Vector3(); // so camera.up is the orbit axis
- this.object = {
- name: name || '',
- fromDeclaration: (fromDeclaration !== false),
+ const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+ const quatInverse = quat.clone().invert();
+ const lastPosition = new THREE.Vector3();
+ const lastQuaternion = new THREE.Quaternion();
+ const twoPI = 2 * Math.PI;
+ return function update() {
- geometry: {
- vertices: [],
- normals: [],
- colors: [],
- uvs: [],
- hasUVIndices: false
- },
- materials: [],
- smooth: true,
+ const position = scope.object.position;
+ offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
- startMaterial: function (name, libraries) {
+ offset.applyQuaternion( quat ); // angle from z-axis around y-axis
- const previous = this._finalize(false);
+ spherical.setFromVector3( offset );
- // New usemtl declaration overwrites an inherited material, except if faces were declared
- // after the material, then it must be preserved for proper MultiMaterial continuation.
- if (previous && (previous.inherited || previous.groupCount <= 0)) {
+ if ( scope.autoRotate && state === STATE.NONE ) {
- this.materials.splice(previous.index, 1);
+ rotateLeft( getAutoRotationAngle() );
}
- const material = {
- index: this.materials.length,
- name: name || '',
- mtllib: (Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : ''),
- smooth: (previous !== undefined ? previous.smooth : this.smooth),
- groupStart: (previous !== undefined ? previous.groupEnd : 0),
- groupEnd: -1,
- groupCount: -1,
- inherited: false,
-
- clone: function (index) {
-
- const cloned = {
- index: (typeof index === 'number' ? index : this.index),
- name: this.name,
- mtllib: this.mtllib,
- smooth: this.smooth,
- groupStart: 0,
- groupEnd: -1,
- groupCount: -1,
- inherited: false
- };
- cloned.clone = this.clone.bind(cloned);
- return cloned;
+ if ( scope.enableDamping ) {
- }
- };
+ spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+ spherical.phi += sphericalDelta.phi * scope.dampingFactor;
- this.materials.push(material);
+ } else {
- return material;
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
- },
+ } // restrict theta to be between desired limits
- currentMaterial: function () {
- if (this.materials.length > 0) {
+ let min = scope.minAzimuthAngle;
+ let max = scope.maxAzimuthAngle;
- return this.materials[this.materials.length - 1];
+ if ( isFinite( min ) && isFinite( max ) ) {
- }
+ if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
+ if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
- return undefined;
+ if ( min <= max ) {
- },
+ spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
+
+ } else {
+
+ spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
+
+ }
+
+ } // restrict phi to be between desired limits
+
+
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+ spherical.makeSafe();
+ spherical.radius *= scale; // restrict radius to be between desired limits
- _finalize: function (end) {
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
- const lastMultiMaterial = this.currentMaterial();
- if (lastMultiMaterial && lastMultiMaterial.groupEnd === -1) {
+ if ( scope.enableDamping === true ) {
- lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
- lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
- lastMultiMaterial.inherited = false;
+ scope.target.addScaledVector( panOffset, scope.dampingFactor );
+
+ } else {
+
+ scope.target.add( panOffset );
}
- // Ignore objects tail materials if no face declarations followed them before a new o/g started.
- if (end && this.materials.length > 1) {
+ offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
- for (let mi = this.materials.length - 1; mi >= 0; mi--) {
+ offset.applyQuaternion( quatInverse );
+ position.copy( scope.target ).add( offset );
+ scope.object.lookAt( scope.target );
- if (this.materials[mi].groupCount <= 0) {
+ if ( scope.enableDamping === true ) {
- this.materials.splice(mi, 1);
+ sphericalDelta.theta *= 1 - scope.dampingFactor;
+ sphericalDelta.phi *= 1 - scope.dampingFactor;
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
- }
+ } else {
- }
+ sphericalDelta.set( 0, 0, 0 );
+ panOffset.set( 0, 0, 0 );
}
- // Guarantee at least one empty material, this makes the creation later more straight forward.
- if (end && this.materials.length === 0) {
+ scale = 1; // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
- this.materials.push({
- name: '',
- smooth: this.smooth
- });
+ if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+ scope.dispatchEvent( _changeEvent );
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+ return true;
}
- return lastMultiMaterial;
+ return false;
- }
- };
+ };
- // Inherit previous objects material.
- // Spec tells us that a declared material must be set to all objects until a new material is declared.
- // If a usemtl declaration is encountered while this new object is being parsed, it will
- // overwrite the inherited material. Exception being that there was already face declarations
- // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+ }();
- if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') {
+ this.dispose = function () {
- const declared = previousMaterial.clone(0);
- declared.inherited = true;
- this.object.materials.push(declared);
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
+ scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
+ scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel );
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
- }
+ if ( scope._domElementKeyEvents !== null ) {
- this.objects.push(this.object);
+ scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
- },
+ } //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
- finalize: function () {
+ }; //
+ // internals
+ //
- if (this.object && typeof this.object._finalize === 'function') {
- this.object._finalize(true);
+ const scope = this;
+ const STATE = {
+ NONE: - 1,
+ ROTATE: 0,
+ DOLLY: 1,
+ PAN: 2,
+ TOUCH_ROTATE: 3,
+ TOUCH_PAN: 4,
+ TOUCH_DOLLY_PAN: 5,
+ TOUCH_DOLLY_ROTATE: 6
+ };
+ let state = STATE.NONE;
+ const EPS = 0.000001; // current position in spherical coordinates
+
+ const spherical = new THREE.Spherical();
+ const sphericalDelta = new THREE.Spherical();
+ let scale = 1;
+ const panOffset = new THREE.Vector3();
+ let zoomChanged = false;
+ const rotateStart = new THREE.Vector2();
+ const rotateEnd = new THREE.Vector2();
+ const rotateDelta = new THREE.Vector2();
+ const panStart = new THREE.Vector2();
+ const panEnd = new THREE.Vector2();
+ const panDelta = new THREE.Vector2();
+ const dollyStart = new THREE.Vector2();
+ const dollyEnd = new THREE.Vector2();
+ const dollyDelta = new THREE.Vector2();
+ const pointers = [];
+ const pointerPositions = {};
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
- },
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
- parseVertexIndex: function (value, len) {
+ }
- const index = parseInt(value, 10);
- return (index >= 0 ? index - 1 : index + len / 3) * 3;
+ function rotateLeft( angle ) {
- },
+ sphericalDelta.theta -= angle;
- parseNormalIndex: function (value, len) {
+ }
- const index = parseInt(value, 10);
- return (index >= 0 ? index - 1 : index + len / 3) * 3;
+ function rotateUp( angle ) {
- },
+ sphericalDelta.phi -= angle;
- parseUVIndex: function (value, len) {
+ }
- const index = parseInt(value, 10);
- return (index >= 0 ? index - 1 : index + len / 2) * 2;
+ const panLeft = function () {
- },
+ const v = new THREE.Vector3();
+ return function panLeft( distance, objectMatrix ) {
- addVertex: function (a, b, c) {
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
- const src = this.vertices;
- const dst = this.object.geometry.vertices;
+ v.multiplyScalar( - distance );
+ panOffset.add( v );
- dst.push(src[a + 0], src[a + 1], src[a + 2]);
- dst.push(src[b + 0], src[b + 1], src[b + 2]);
- dst.push(src[c + 0], src[c + 1], src[c + 2]);
+ };
- },
+ }();
- addVertexPoint: function (a) {
+ const panUp = function () {
- const src = this.vertices;
- const dst = this.object.geometry.vertices;
+ const v = new THREE.Vector3();
+ return function panUp( distance, objectMatrix ) {
- dst.push(src[a + 0], src[a + 1], src[a + 2]);
+ if ( scope.screenSpacePanning === true ) {
- },
+ v.setFromMatrixColumn( objectMatrix, 1 );
- addVertexLine: function (a) {
+ } else {
- const src = this.vertices;
- const dst = this.object.geometry.vertices;
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
- dst.push(src[a + 0], src[a + 1], src[a + 2]);
+ }
- },
+ v.multiplyScalar( distance );
+ panOffset.add( v );
- addNormal: function (a, b, c) {
+ };
- const src = this.normals;
- const dst = this.object.geometry.normals;
+ }(); // deltaX and deltaY are in pixels; right and down are positive
- dst.push(src[a + 0], src[a + 1], src[a + 2]);
- dst.push(src[b + 0], src[b + 1], src[b + 2]);
- dst.push(src[c + 0], src[c + 1], src[c + 2]);
- },
+ const pan = function () {
- addFaceNormal: function (a, b, c) {
+ const offset = new THREE.Vector3();
+ return function pan( deltaX, deltaY ) {
- const src = this.vertices;
- const dst = this.object.geometry.normals;
+ const element = scope.domElement;
- _vA.fromArray(src, a);
- _vB.fromArray(src, b);
- _vC.fromArray(src, c);
+ if ( scope.object.isPerspectiveCamera ) {
- _cb.subVectors(_vC, _vB);
- _ab.subVectors(_vA, _vB);
- _cb.cross(_ab);
+ // perspective
+ const position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ let targetDistance = offset.length(); // half of the fov is center to top of screen
- _cb.normalize();
+ targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
- dst.push(_cb.x, _cb.y, _cb.z);
- dst.push(_cb.x, _cb.y, _cb.z);
- dst.push(_cb.x, _cb.y, _cb.z);
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
- },
+ } else if ( scope.object.isOrthographicCamera ) {
- addColor: function (a, b, c) {
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
- const src = this.colors;
- const dst = this.object.geometry.colors;
+ } else {
- if (src[a] !== undefined) dst.push(src[a + 0], src[a + 1], src[a + 2]);
- if (src[b] !== undefined) dst.push(src[b + 0], src[b + 1], src[b + 2]);
- if (src[c] !== undefined) dst.push(src[c + 0], src[c + 1], src[c + 2]);
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
- },
+ }
- addUV: function (a, b, c) {
+ };
- const src = this.uvs;
- const dst = this.object.geometry.uvs;
+ }();
- dst.push(src[a + 0], src[a + 1]);
- dst.push(src[b + 0], src[b + 1]);
- dst.push(src[c + 0], src[c + 1]);
+ function dollyOut( dollyScale ) {
- },
+ if ( scope.object.isPerspectiveCamera ) {
- addDefaultUV: function () {
+ scale /= dollyScale;
- const dst = this.object.geometry.uvs;
+ } else if ( scope.object.isOrthographicCamera ) {
- dst.push(0, 0);
- dst.push(0, 0);
- dst.push(0, 0);
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
- },
+ } else {
- addUVLine: function (a) {
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
- const src = this.uvs;
- const dst = this.object.geometry.uvs;
+ }
- dst.push(src[a + 0], src[a + 1]);
+ }
- },
+ function dollyIn( dollyScale ) {
- addFace: function (a, b, c, ua, ub, uc, na, nb, nc) {
+ if ( scope.object.isPerspectiveCamera ) {
- const vLen = this.vertices.length;
+ scale *= dollyScale;
- let ia = this.parseVertexIndex(a, vLen);
- let ib = this.parseVertexIndex(b, vLen);
- let ic = this.parseVertexIndex(c, vLen);
+ } else if ( scope.object.isOrthographicCamera ) {
- this.addVertex(ia, ib, ic);
- this.addColor(ia, ib, ic);
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
- // normals
+ } else {
- if (na !== undefined && na !== '') {
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
- const nLen = this.normals.length;
+ }
- ia = this.parseNormalIndex(na, nLen);
- ib = this.parseNormalIndex(nb, nLen);
- ic = this.parseNormalIndex(nc, nLen);
+ } //
+ // event callbacks - update the object state
+ //
- this.addNormal(ia, ib, ic);
- } else {
+ function handleMouseDownRotate( event ) {
- this.addFaceNormal(ia, ib, ic);
+ rotateStart.set( event.clientX, event.clientY );
}
- // uvs
+ function handleMouseDownDolly( event ) {
- if (ua !== undefined && ua !== '') {
+ dollyStart.set( event.clientX, event.clientY );
- const uvLen = this.uvs.length;
+ }
- ia = this.parseUVIndex(ua, uvLen);
- ib = this.parseUVIndex(ub, uvLen);
- ic = this.parseUVIndex(uc, uvLen);
+ function handleMouseDownPan( event ) {
- this.addUV(ia, ib, ic);
+ panStart.set( event.clientX, event.clientY );
- this.object.geometry.hasUVIndices = true;
+ }
- } else {
+ function handleMouseMoveRotate( event ) {
- // add placeholder values (for inconsistent face definitions)
+ rotateEnd.set( event.clientX, event.clientY );
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+ const element = scope.domElement;
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
- this.addDefaultUV();
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+ rotateStart.copy( rotateEnd );
+ scope.update();
}
- },
+ function handleMouseMoveDolly( event ) {
+
+ dollyEnd.set( event.clientX, event.clientY );
+ dollyDelta.subVectors( dollyEnd, dollyStart );
- addPointGeometry: function (vertices) {
+ if ( dollyDelta.y > 0 ) {
- this.object.geometry.type = 'THREE.Points';
+ dollyOut( getZoomScale() );
- const vLen = this.vertices.length;
+ } else if ( dollyDelta.y < 0 ) {
- for (let vi = 0, l = vertices.length; vi < l; vi++) {
+ dollyIn( getZoomScale() );
- const index = this.parseVertexIndex(vertices[vi], vLen);
+ }
- this.addVertexPoint(index);
- this.addColor(index);
+ dollyStart.copy( dollyEnd );
+ scope.update();
}
- },
+ function handleMouseMovePan( event ) {
+
+ panEnd.set( event.clientX, event.clientY );
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+ pan( panDelta.x, panDelta.y );
+ panStart.copy( panEnd );
+ scope.update();
- addLineGeometry: function (vertices, uvs) {
+ }
- this.object.geometry.type = 'Line';
+ function handleMouseWheel( event ) {
- const vLen = this.vertices.length;
- const uvLen = this.uvs.length;
+ if ( event.deltaY < 0 ) {
- for (let vi = 0, l = vertices.length; vi < l; vi++) {
+ dollyIn( getZoomScale() );
- this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen));
+ } else if ( event.deltaY > 0 ) {
- }
+ dollyOut( getZoomScale() );
- for (let uvi = 0, l = uvs.length; uvi < l; uvi++) {
+ }
- this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen));
+ scope.update();
}
- }
+ function handleKeyDown( event ) {
- };
+ let needsUpdate = false;
- state.startObject('', false);
+ switch ( event.code ) {
- return state;
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
-}
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
-//
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
-class OBJLoader extends THREE.Loader {
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
- constructor(manager) {
+ }
- super(manager);
+ if ( needsUpdate ) {
- this.materials = null;
+ // prevent the browser from scrolling on cursor keys
+ event.preventDefault();
+ scope.update();
- }
+ }
- load(url, onLoad, onProgress, onError) {
+ }
+
+ function handleTouchStartRotate() {
+
+ if ( pointers.length === 1 ) {
+
+ rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
- const scope = this;
+ } else {
- const loader = new THREE.FileLoader(this.manager);
- loader.setPath(this.path);
- loader.setRequestHeader(this.requestHeader);
- loader.setWithCredentials(this.withCredentials);
- loader.load(url, function (text) {
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+ rotateStart.set( x, y );
- try {
+ }
- onLoad(scope.parse(text));
+ }
- } catch (e) {
+ function handleTouchStartPan() {
- if (onError) {
+ if ( pointers.length === 1 ) {
- onError(e);
+ panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
} else {
- console.error(e);
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+ panStart.set( x, y );
}
- scope.manager.itemError(url);
-
}
- }, onProgress, onError);
+ function handleTouchStartDolly() {
- }
-
- setMaterials(materials) {
+ const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
+ const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
+ const distance = Math.sqrt( dx * dx + dy * dy );
+ dollyStart.set( 0, distance );
- this.materials = materials;
+ }
- return this;
+ function handleTouchStartDollyPan() {
- }
+ if ( scope.enableZoom ) handleTouchStartDolly();
+ if ( scope.enablePan ) handleTouchStartPan();
- parse(text) {
+ }
- const state = new ParserState();
+ function handleTouchStartDollyRotate() {
- if (text.indexOf('\r\n') !== -1) {
+ if ( scope.enableZoom ) handleTouchStartDolly();
+ if ( scope.enableRotate ) handleTouchStartRotate();
- // This is faster than String.split with regex that splits on both
- text = text.replace(/\r\n/g, '\n');
+ }
- }
+ function handleTouchMoveRotate( event ) {
- if (text.indexOf('\\\n') !== -1) {
+ if ( pointers.length == 1 ) {
- // join lines separated by a line continuation character (\)
- text = text.replace(/\\\n/g, '');
+ rotateEnd.set( event.pageX, event.pageY );
- }
+ } else {
- const lines = text.split('\n');
- let line = '', lineFirstChar = '';
- let lineLength = 0;
- let result = [];
+ const position = getSecondPointerPosition( event );
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
+ rotateEnd.set( x, y );
- // Faster to just trim left side of the line. Use if available.
- const trimLeft = (typeof ''.trimLeft === 'function');
+ }
- for (let i = 0, l = lines.length; i < l; i++) {
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+ const element = scope.domElement;
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
- line = lines[i];
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+ rotateStart.copy( rotateEnd );
- line = trimLeft ? line.trimLeft() : line.trim();
+ }
- lineLength = line.length;
+ function handleTouchMovePan( event ) {
- if (lineLength === 0) continue;
+ if ( pointers.length === 1 ) {
- lineFirstChar = line.charAt(0);
+ panEnd.set( event.pageX, event.pageY );
- // @todo invoke passed in handler if any
- if (lineFirstChar === '#') continue;
+ } else {
- if (lineFirstChar === 'v') {
+ const position = getSecondPointerPosition( event );
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
+ panEnd.set( x, y );
- const data = line.split(/\s+/);
+ }
- switch (data[0]) {
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+ pan( panDelta.x, panDelta.y );
+ panStart.copy( panEnd );
- case 'v':
- state.vertices.push(
- parseFloat(data[1]),
- parseFloat(data[2]),
- parseFloat(data[3])
- );
- if (data.length >= 7) {
+ }
- state.colors.push(
- parseFloat(data[4]),
- parseFloat(data[5]),
- parseFloat(data[6])
- );
+ function handleTouchMoveDolly( event ) {
- } else {
+ const position = getSecondPointerPosition( event );
+ const dx = event.pageX - position.x;
+ const dy = event.pageY - position.y;
+ const distance = Math.sqrt( dx * dx + dy * dy );
+ dollyEnd.set( 0, distance );
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+ dollyOut( dollyDelta.y );
+ dollyStart.copy( dollyEnd );
- // if no colors are defined, add placeholders so color and vertex indices match
+ }
- state.colors.push(undefined, undefined, undefined);
+ function handleTouchMoveDollyPan( event ) {
- }
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+ if ( scope.enablePan ) handleTouchMovePan( event );
- break;
- case 'vn':
- state.normals.push(
- parseFloat(data[1]),
- parseFloat(data[2]),
- parseFloat(data[3])
- );
- break;
- case 'vt':
- state.uvs.push(
- parseFloat(data[1]),
- parseFloat(data[2])
- );
- break;
+ }
- }
+ function handleTouchMoveDollyRotate( event ) {
- } else if (lineFirstChar === 'f') {
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+ if ( scope.enableRotate ) handleTouchMoveRotate( event );
- const lineData = line.substr(1).trim();
- const vertexData = lineData.split(/\s+/);
- const faceVertices = [];
+ } //
+ // event handlers - FSM: listen for events and reset state
+ //
- // Parse the face vertex data into an easy to work with format
- for (let j = 0, jl = vertexData.length; j < jl; j++) {
+ function onPointerDown( event ) {
- const vertex = vertexData[j];
+ if ( scope.enabled === false ) return;
- if (vertex.length > 0) {
+ if ( pointers.length === 0 ) {
- const vertexParts = vertex.split('/');
- faceVertices.push(vertexParts);
+ scope.domElement.setPointerCapture( event.pointerId );
+ scope.domElement.addEventListener( 'pointermove', onPointerMove );
+ scope.domElement.addEventListener( 'pointerup', onPointerUp );
- }
+ } //
- }
- // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+ addPointer( event );
- const v1 = faceVertices[0];
+ if ( event.pointerType === 'touch' ) {
- for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) {
+ onTouchStart( event );
- const v2 = faceVertices[j];
- const v3 = faceVertices[j + 1];
+ } else {
- state.addFace(
- v1[0], v2[0], v3[0],
- v1[1], v2[1], v3[1],
- v1[2], v2[2], v3[2]
- );
+ onMouseDown( event );
}
- } else if (lineFirstChar === 'l') {
+ }
+
+ function onPointerMove( event ) {
- const lineParts = line.substring(1).trim().split(' ');
- let lineVertices = [];
- const lineUVs = [];
+ if ( scope.enabled === false ) return;
- if (line.indexOf('/') === -1) {
+ if ( event.pointerType === 'touch' ) {
- lineVertices = lineParts;
+ onTouchMove( event );
} else {
- for (let li = 0, llen = lineParts.length; li < llen; li++) {
+ onMouseMove( event );
+
+ }
+
+ }
- const parts = lineParts[li].split('/');
+ function onPointerUp( event ) {
- if (parts[0] !== '') lineVertices.push(parts[0]);
- if (parts[1] !== '') lineUVs.push(parts[1]);
+ removePointer( event );
- }
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.releasePointerCapture( event.pointerId );
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
}
- state.addLineGeometry(lineVertices, lineUVs);
+ scope.dispatchEvent( _endEvent );
+ state = STATE.NONE;
+
+ }
+
+ function onPointerCancel( event ) {
- } else if (lineFirstChar === 'p') {
+ removePointer( event );
- const lineData = line.substr(1).trim();
- const pointData = lineData.split(' ');
+ }
+
+ function onMouseDown( event ) {
- state.addPointGeometry(pointData);
+ let mouseAction;
- } else if ((result = _object_pattern.exec(line)) !== null) {
+ switch ( event.button ) {
- // o object_name
- // or
- // g group_name
+ case 0:
+ mouseAction = scope.mouseButtons.LEFT;
+ break;
- // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
- // let name = result[ 0 ].substr( 1 ).trim();
- const name = (' ' + result[0].substr(1).trim()).substr(1);
+ case 1:
+ mouseAction = scope.mouseButtons.MIDDLE;
+ break;
- state.startObject(name);
+ case 2:
+ mouseAction = scope.mouseButtons.RIGHT;
+ break;
- } else if (_material_use_pattern.test(line)) {
+ default:
+ mouseAction = - 1;
- // material
+ }
- state.object.startMaterial(line.substring(7).trim(), state.materialLibraries);
+ switch ( mouseAction ) {
- } else if (_material_library_pattern.test(line)) {
+ case THREE.MOUSE.DOLLY:
+ if ( scope.enableZoom === false ) return;
+ handleMouseDownDolly( event );
+ state = STATE.DOLLY;
+ break;
- // mtl file
+ case THREE.MOUSE.ROTATE:
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- state.materialLibraries.push(line.substring(7).trim());
+ if ( scope.enablePan === false ) return;
+ handleMouseDownPan( event );
+ state = STATE.PAN;
- } else if (_map_use_pattern.test(line)) {
+ } else {
- // the line is parsed but ignored since the loader assumes textures are defined MTL files
- // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+ if ( scope.enableRotate === false ) return;
+ handleMouseDownRotate( event );
+ state = STATE.ROTATE;
- console.warn('THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.');
+ }
- } else if (lineFirstChar === 's') {
+ break;
- result = line.split(' ');
+ case THREE.MOUSE.PAN:
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
- // smooth shading
+ if ( scope.enableRotate === false ) return;
+ handleMouseDownRotate( event );
+ state = STATE.ROTATE;
- // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
- // but does not define a usemtl for each face set.
- // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
- // This requires some care to not create extra material on each smooth value for "normal" obj files.
- // where explicit usemtl defines geometry groups.
- // Example asset: examples/models/obj/cerberus/Cerberus.obj
+ } else {
- /*
- * http://paulbourke.net/dataformats/obj/
- * or
- * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
- *
- * From chapter "Grouping" Syntax explanation "s group_number":
- * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
- * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
- * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
- * than 0."
- */
- if (result.length > 1) {
+ if ( scope.enablePan === false ) return;
+ handleMouseDownPan( event );
+ state = STATE.PAN;
- const value = result[1].trim().toLowerCase();
- state.object.smooth = (value !== '0' && value !== 'off');
+ }
- } else {
+ break;
- // ZBrush can produce "s" lines #11707
- state.object.smooth = true;
+ default:
+ state = STATE.NONE;
}
- const material = state.object.currentMaterial();
- if (material) material.smooth = state.object.smooth;
+ if ( state !== STATE.NONE ) {
- } else {
-
- // Handle null terminated files without exception
- if (line === '\0') continue;
+ scope.dispatchEvent( _startEvent );
- console.warn('THREE.OBJLoader: Unexpected line: "' + line + '"');
+ }
}
- }
+ function onMouseMove( event ) {
- state.finalize();
+ if ( scope.enabled === false ) return;
- const container = new THREE.Group();
- container.materialLibraries = [].concat(state.materialLibraries);
+ switch ( state ) {
- const hasPrimitives = !(state.objects.length === 1 && state.objects[0].geometry.vertices.length === 0);
+ case STATE.ROTATE:
+ if ( scope.enableRotate === false ) return;
+ handleMouseMoveRotate( event );
+ break;
- if (hasPrimitives === true) {
+ case STATE.DOLLY:
+ if ( scope.enableZoom === false ) return;
+ handleMouseMoveDolly( event );
+ break;
- for (let i = 0, l = state.objects.length; i < l; i++) {
+ case STATE.PAN:
+ if ( scope.enablePan === false ) return;
+ handleMouseMovePan( event );
+ break;
- const object = state.objects[i];
- const geometry = object.geometry;
- const materials = object.materials;
- const isLine = (geometry.type === 'Line');
- const isPoints = (geometry.type === 'THREE.Points');
- let hasVertexColors = false;
+ }
- // Skip o/g line declarations that did not follow with any faces
- if (geometry.vertices.length === 0) continue;
+ }
- const buffergeometry = new THREE.BufferGeometry();
+ function onMouseWheel( event ) {
- buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(geometry.vertices, 3));
+ if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
+ event.preventDefault();
+ scope.dispatchEvent( _startEvent );
+ handleMouseWheel( event );
+ scope.dispatchEvent( _endEvent );
- if (geometry.normals.length > 0) {
+ }
- buffergeometry.setAttribute('normal', new THREE.Float32BufferAttribute(geometry.normals, 3));
+ function onKeyDown( event ) {
- }
+ if ( scope.enabled === false || scope.enablePan === false ) return;
+ handleKeyDown( event );
- if (geometry.colors.length > 0) {
+ }
- hasVertexColors = true;
- buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(geometry.colors, 3));
+ function onTouchStart( event ) {
- }
+ trackPointer( event );
- if (geometry.hasUVIndices === true) {
+ switch ( pointers.length ) {
- buffergeometry.setAttribute('uv', new THREE.Float32BufferAttribute(geometry.uvs, 2));
+ case 1:
+ switch ( scope.touches.ONE ) {
- }
+ case THREE.TOUCH.ROTATE:
+ if ( scope.enableRotate === false ) return;
+ handleTouchStartRotate();
+ state = STATE.TOUCH_ROTATE;
+ break;
- // Create materials
+ case THREE.TOUCH.PAN:
+ if ( scope.enablePan === false ) return;
+ handleTouchStartPan();
+ state = STATE.TOUCH_PAN;
+ break;
- const createdMaterials = [];
+ default:
+ state = STATE.NONE;
- for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {
+ }
- const sourceMaterial = materials[mi];
- const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
- let material = state.materials[materialHash];
+ break;
- if (this.materials !== null) {
+ case 2:
+ switch ( scope.touches.TWO ) {
- material = this.materials.create(sourceMaterial.name);
+ case THREE.TOUCH.DOLLY_PAN:
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+ handleTouchStartDollyPan();
+ state = STATE.TOUCH_DOLLY_PAN;
+ break;
- // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
- if (isLine && material && !(material instanceof THREE.LineBasicMaterial)) {
+ case THREE.TOUCH.DOLLY_ROTATE:
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+ handleTouchStartDollyRotate();
+ state = STATE.TOUCH_DOLLY_ROTATE;
+ break;
- const materialLine = new THREE.LineBasicMaterial();
- THREE.Material.prototype.copy.call(materialLine, material);
- materialLine.color.copy(material.color);
- material = materialLine;
+ default:
+ state = STATE.NONE;
- } else if (isPoints && material && !(material instanceof THREE.PointsMaterial)) {
+ }
- const materialPoints = new THREE.PointsMaterial({size: 10, sizeAttenuation: false});
- THREE.Material.prototype.copy.call(materialPoints, material);
- materialPoints.color.copy(material.color);
- materialPoints.map = material.map;
- material = materialPoints;
+ break;
- }
+ default:
+ state = STATE.NONE;
- }
+ }
- if (material === undefined) {
+ if ( state !== STATE.NONE ) {
- if (isLine) {
+ scope.dispatchEvent( _startEvent );
- material = new THREE.LineBasicMaterial();
+ }
- } else if (isPoints) {
+ }
- material = new THREE.PointsMaterial({size: 1, sizeAttenuation: false});
+ function onTouchMove( event ) {
- } else {
+ trackPointer( event );
- material = new THREE.MeshPhongMaterial();
+ switch ( state ) {
- }
+ case STATE.TOUCH_ROTATE:
+ if ( scope.enableRotate === false ) return;
+ handleTouchMoveRotate( event );
+ scope.update();
+ break;
- material.name = sourceMaterial.name;
- material.flatShading = sourceMaterial.smooth ? false : true;
- material.vertexColors = hasVertexColors;
+ case STATE.TOUCH_PAN:
+ if ( scope.enablePan === false ) return;
+ handleTouchMovePan( event );
+ scope.update();
+ break;
- state.materials[materialHash] = material;
+ case STATE.TOUCH_DOLLY_PAN:
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+ handleTouchMoveDollyPan( event );
+ scope.update();
+ break;
- }
+ case STATE.TOUCH_DOLLY_ROTATE:
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+ handleTouchMoveDollyRotate( event );
+ scope.update();
+ break;
- createdMaterials.push(material);
+ default:
+ state = STATE.NONE;
}
- // Create mesh
+ }
- let mesh;
+ function onContextMenu( event ) {
- if (createdMaterials.length > 1) {
+ if ( scope.enabled === false ) return;
+ event.preventDefault();
- for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {
+ }
- const sourceMaterial = materials[mi];
- buffergeometry.addGroup(sourceMaterial.groupStart, sourceMaterial.groupCount, mi);
+ function addPointer( event ) {
- }
+ pointers.push( event );
- if (isLine) {
+ }
- mesh = new THREE.LineSegments(buffergeometry, createdMaterials);
+ function removePointer( event ) {
- } else if (isPoints) {
+ delete pointerPositions[ event.pointerId ];
- mesh = new THREE.Points(buffergeometry, createdMaterials);
+ for ( let i = 0; i < pointers.length; i ++ ) {
- } else {
+ if ( pointers[ i ].pointerId == event.pointerId ) {
- mesh = new THREE.Mesh(buffergeometry, createdMaterials);
+ pointers.splice( i, 1 );
+ return;
}
- } else {
-
- if (isLine) {
-
- mesh = new THREE.LineSegments(buffergeometry, createdMaterials[0]);
+ }
- } else if (isPoints) {
+ }
- mesh = new THREE.Points(buffergeometry, createdMaterials[0]);
+ function trackPointer( event ) {
- } else {
+ let position = pointerPositions[ event.pointerId ];
- mesh = new THREE.Mesh(buffergeometry, createdMaterials[0]);
+ if ( position === undefined ) {
- }
+ position = new THREE.Vector2();
+ pointerPositions[ event.pointerId ] = position;
}
- mesh.name = object.name;
-
- container.add(mesh);
+ position.set( event.pageX, event.pageY );
}
- } else {
+ function getSecondPointerPosition( event ) {
- // if there is only the default parser state object with no geometry data, interpret data as point cloud
+ const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
+ return pointerPositions[ pointer.pointerId ];
- if (state.vertices.length > 0) {
+ } //
- const material = new THREE.PointsMaterial({size: 1, sizeAttenuation: false});
- const buffergeometry = new THREE.BufferGeometry();
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+ scope.domElement.addEventListener( 'pointerdown', onPointerDown );
+ scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, {
+ passive: false
+ } ); // force an update at start
- buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(state.vertices, 3));
+ this.update();
- if (state.colors.length > 0 && state.colors[0] !== undefined) {
+ }
- buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(state.colors, 3));
- material.vertexColors = true;
+ } // This set of controls performs orbiting, dollying (zooming), and panning.
+ // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+ // This is very similar to OrbitControls, another set of touch behavior
+ //
+ // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+ // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+ // Pan - left mouse, or arrow keys / touch: one-finger move
- }
- const points = new THREE.Points(buffergeometry, material);
- container.add(points);
+ class MapControls extends OrbitControls {
- }
+ constructor( object, domElement ) {
- }
+ super( object, domElement );
+ this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
- return container;
+ this.mouseButtons.LEFT = THREE.MOUSE.PAN;
+ this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
+ this.touches.ONE = THREE.TOUCH.PAN;
+ this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
- }
+ }
-}
+ }
-module.exports = {OBJLoader: OBJLoader};
+ THREE.MapControls = MapControls;
+ THREE.OrbitControls = OrbitControls;
-},{}]},{},[1,3]);
+} )();