Remove crossorigin attributes
authorLanius Trolling <lanius@laniustrolling.dev>
Mon, 4 Dec 2023 00:19:50 +0000 (19:19 -0500)
committerLanius Trolling <lanius@laniustrolling.dev>
Mon, 4 Dec 2023 00:19:50 +0000 (19:19 -0500)
.idea/kotlinc.xml
.idea/misc.xml
build.gradle.kts
src/main/kotlin/info/mechyrdia/lore/view_tpl.kt
src/main/resources/static/obj-viewer/OrbitControls.js [deleted file]
src/main/resources/static/obj-viewer/three-examples.js

index f8467b458e43862c587a34f34c06af8fbbf30d0f..ae3f30ae18f16cef752ece873369cf78dbd9ca4e 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="KotlinJpsPluginSettings">
-    <option name="version" value="1.9.10" />
+    <option name="version" value="1.9.21" />
   </component>
 </project>
\ No newline at end of file
index 2e9b997f9958cfacb964a82260634c71283ecaf7..1b9246d1167700de7bd7190c806a97640f2ca25b 100644 (file)
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
   <component name="FrameworkDetectionExcludesConfiguration">
index dd2ac1aa28ff55e54ad0290a0512193224fb4590..7b26e4e958ce9d9f3966afeafff9d6431375032d 100644 (file)
@@ -24,8 +24,8 @@ buildscript {
 
 plugins {
        java
-       kotlin("jvm") version "1.9.10"
-       kotlin("plugin.serialization") version "1.9.10"
+       kotlin("jvm") version "1.9.21"
+       kotlin("plugin.serialization") version "1.9.21"
        id("com.github.johnrengelman.shadow") version "7.1.2"
        application
 }
index 29d1a4d0ed338f5daa78e442e58cf6eb30752c75..e79b50a4054e1790d2c734cc21998e7679765400 100644 (file)
@@ -58,7 +58,6 @@ fun ApplicationCall.page(pageTitle: String, navBar: List<NavItem>? = null, sideb
                        
                        script(src = "/static/obj-viewer/three.js") {}
                        script(src = "/static/obj-viewer/three-examples.js") {}
-                       script(src = "/static/obj-viewer/OrbitControls.js") {}
                        
                        link(rel = "icon", type = "image/svg+xml", href = "/static/images/icon.png")
                        
@@ -69,7 +68,6 @@ fun ApplicationCall.page(pageTitle: String, navBar: List<NavItem>? = null, sideb
                                        type = "font/woff"
                                ) {
                                        attributes["as"] = "font"
-                                       attributes["crossorigin"] = "anonymous"
                                }
                        
                        for (image in images)
@@ -79,7 +77,6 @@ fun ApplicationCall.page(pageTitle: String, navBar: List<NavItem>? = null, sideb
                                        type = "image/png"
                                ) {
                                        attributes["as"] = "image"
-                                       attributes["crossorigin"] = "anonymous"
                                }
                        
                        link(rel = "stylesheet", href = "/static/style.css")
diff --git a/src/main/resources/static/obj-viewer/OrbitControls.js b/src/main/resources/static/obj-viewer/OrbitControls.js
deleted file mode 100644 (file)
index 151f0c8..0000000
+++ /dev/null
@@ -1,1042 +0,0 @@
-/**
- * @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;
-
-               }
-
-       }
-
-} );
index 7892ef1ad008d4d1251d14b358b62aff8a4204d2..cd3d962084df1991b48c90abb01bcb58518693a2 100644 (file)
-(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]);
+} )();