diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..498174e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# original source
+# https://github.com/mrdoob/three.js/blob/dev/.editorconfig
+
+# http://editorconfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.{js,ts,html}]
+charset = utf-8
+indent_style = tab
+
+[*.{js,ts}]
+trim_trailing_whitespace = true
diff --git a/js/OrbitControls.js b/js/OrbitControls.js
index fbe44d0..9024dd9 100644
--- a/js/OrbitControls.js
+++ b/js/OrbitControls.js
@@ -1,587 +1,1252 @@
-/**
- * @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
- * @author mrflix / http://felixniklas.de
- *
- * released under MIT License (MIT)
- */
-/*global THREE, console */
-
-// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
-// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
-// supported.
+import {
+ EventDispatcher,
+ MOUSE,
+ Quaternion,
+ Spherical,
+ TOUCH,
+ Vector2,
+ Vector3
+} from 'three';
+
+// 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 finter swipe
-//
-// This is a drop-in replacement for (most) TrackballControls used in examples.
-// That is, include this js file and wherever you see:
-// controls = new THREE.TrackballControls( camera );
-// controls.target.z = 150;
-// Simple substitute "OrbitControls" and the control should work as-is.
+// 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
-THREE.OrbitControls = function ( object, domElement, localElement ) {
+const _changeEvent = { type: 'change' };
+const _startEvent = { type: 'start' };
+const _endEvent = { type: 'end' };
- this.object = object;
- this.domElement = ( domElement !== undefined ) ? domElement : document;
- this.localElement = ( localElement !== undefined ) ? localElement : document;
+class OrbitControls extends EventDispatcher {
- // API
+ constructor( object, domElement ) {
- // Set to false to disable this control
- this.enabled = true;
+ super();
- // "target" sets the location of focus, where the control orbits around
- // and where it pans with respect to.
- this.target = new THREE.Vector3();
- // center is old, deprecated; use "target" instead
- this.center = this.target;
+ 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 option actually enables dollying in and out; left as "zoom" for
- // backwards compatibility
- this.noZoom = false;
- this.zoomSpeed = 1.0;
- // Limits to how far you can dolly in and out
- this.minDistance = 0;
- this.maxDistance = Infinity;
+ this.object = object;
+ this.domElement = domElement;
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
- // Set to true to disable this control
- this.noRotate = false;
- this.rotateSpeed = 1.0;
+ // Set to false to disable this control
+ this.enabled = true;
- // Set to true to disable this control
- this.noPan = false;
- this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new Vector3();
- // Set to true to automatically rotate around the target
- this.autoRotate = false;
- this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = 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 zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
- // Set to true to disable use of the keys
- this.noKeys = false;
- // The four arrow keys
- this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+ // 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
- ////////////
- // internals
+ // 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 )
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
- var scope = this;
+ // 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.05;
- var EPS = 0.000001;
+ // 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;
- var rotateStart = new THREE.Vector2();
- var rotateEnd = new THREE.Vector2();
- var rotateDelta = new THREE.Vector2();
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
- var panStart = new THREE.Vector2();
- var panEnd = new THREE.Vector2();
- var panDelta = new THREE.Vector2();
+ // 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
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
- var dollyStart = new THREE.Vector2();
- var dollyEnd = new THREE.Vector2();
- var dollyDelta = new THREE.Vector2();
+ // 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 orbit when fps is 60
- var phiDelta = 0;
- var thetaDelta = 0;
- var scale = 1;
- var pan = new THREE.Vector3();
+ // The four arrow keys
+ this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
- var lastPosition = new THREE.Vector3();
+ // Mouse buttons
+ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
- var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
- var state = STATE.NONE;
+ // Touch fingers
+ this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
- // events
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
- var changeEvent = { type: 'change' };
+ // the target DOM element for key events
+ this._domElementKeyEvents = null;
+ //
+ // public methods
+ //
- this.rotateLeft = function ( angle ) {
+ this.getPolarAngle = function () {
- if ( angle === undefined ) {
+ return spherical.phi;
- angle = getAutoRotationAngle();
+ };
- }
+ this.getAzimuthalAngle = function () {
- thetaDelta -= angle;
+ return spherical.theta;
- };
+ };
- this.rotateUp = function ( angle ) {
+ this.getDistance = function () {
- if ( angle === undefined ) {
+ return this.object.position.distanceTo( this.target );
- angle = getAutoRotationAngle();
+ };
- }
+ this.listenToKeyEvents = function ( domElement ) {
+
+ domElement.addEventListener( 'keydown', onKeyDown );
+ this._domElementKeyEvents = domElement;
- phiDelta -= angle;
+ };
- };
+ this.saveState = function () {
- // pass in distance in world space to move left
- this.panLeft = function ( distance ) {
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
- var panOffset = new THREE.Vector3();
- var te = this.object.matrix.elements;
- // get X column of matrix
- panOffset.set( te[0], te[1], te[2] );
- panOffset.multiplyScalar(-distance);
-
- pan.add( panOffset );
+ };
- };
+ this.reset = function () {
- // pass in distance in world space to move up
- this.panUp = function ( distance ) {
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
- var panOffset = new THREE.Vector3();
- var te = this.object.matrix.elements;
- // get Y column of matrix
- panOffset.set( te[4], te[5], te[6] );
- panOffset.multiplyScalar(distance);
-
- pan.add( panOffset );
- };
-
- // main entry point; pass in Vector2 of change desired in pixel space,
- // right and down are positive
- this.pan = function ( delta ) {
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( _changeEvent );
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+ scope.update();
- if ( scope.object.fov !== undefined ) {
+ state = STATE.NONE;
- // perspective
- var position = scope.object.position;
- var offset = position.clone().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
- scope.panLeft( 2 * delta.x * targetDistance / element.clientHeight );
- scope.panUp( 2 * delta.y * targetDistance / element.clientHeight );
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
- } else if ( scope.object.top !== undefined ) {
+ const offset = new Vector3();
- // orthographic
- scope.panLeft( delta.x * (scope.object.right - scope.object.left) / element.clientWidth );
- scope.panUp( delta.y * (scope.object.top - scope.object.bottom) / element.clientHeight );
+ // so camera.up is the orbit axis
+ const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
+ const quatInverse = quat.clone().invert();
- } else {
+ const lastPosition = new Vector3();
+ const lastQuaternion = new Quaternion();
- // camera neither orthographic or perspective - warn user
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ const twoPI = 2 * Math.PI;
- }
+ return function update() {
+
+ const 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() );
+
+ }
+
+ if ( scope.enableDamping ) {
+
+ spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+ spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+
+ } else {
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ }
+
+ // restrict theta to be between desired limits
+
+ let min = scope.minAzimuthAngle;
+ let max = scope.maxAzimuthAngle;
+
+ 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;
+
+ 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 );
- this.dollyIn = function ( dollyScale ) {
+ }
+
+ }
+
+ // 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
+
+ if ( scope.enableDamping === true ) {
+
+ scope.target.addScaledVector( panOffset, scope.dampingFactor );
+
+ } else {
+
+ 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 );
+
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+ } else {
+
+ sphericalDelta.set( 0, 0, 0 );
+
+ panOffset.set( 0, 0, 0 );
+
+ }
- if ( dollyScale === undefined ) {
+ scale = 1;
- dollyScale = getZoomScale();
+ // 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 );
+
+ 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 ) {
+
+ scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
+
+ }
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ 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 Spherical();
+ const sphericalDelta = new Spherical();
+
+ let scale = 1;
+ const panOffset = new Vector3();
+ let zoomChanged = false;
+
+ const rotateStart = new Vector2();
+ const rotateEnd = new Vector2();
+ const rotateDelta = new Vector2();
+
+ const panStart = new Vector2();
+ const panEnd = new Vector2();
+ const panDelta = new Vector2();
+
+ const dollyStart = new Vector2();
+ const dollyEnd = new Vector2();
+ const dollyDelta = new Vector2();
+
+ const pointers = [];
+ const pointerPositions = {};
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
- scale /= dollyScale;
+ function getZoomScale() {
- };
+ return Math.pow( 0.95, scope.zoomSpeed );
- this.dollyOut = function ( dollyScale ) {
+ }
- if ( dollyScale === undefined ) {
+ function rotateLeft( angle ) {
- dollyScale = getZoomScale();
+ sphericalDelta.theta -= angle;
}
- scale *= dollyScale;
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
- };
+ }
- this.update = function () {
+ const panLeft = function () {
- var position = this.object.position;
- var offset = position.clone().sub( this.target );
+ const v = new Vector3();
- // angle from z-axis around y-axis
+ return function panLeft( distance, objectMatrix ) {
- var theta = Math.atan2( offset.x, offset.z );
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
- // angle from y-axis
+ panOffset.add( v );
- var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
+ };
- if ( this.autoRotate ) {
+ }();
- this.rotateLeft( getAutoRotationAngle() );
+ const panUp = function () {
- }
+ const v = new Vector3();
- theta += thetaDelta;
- phi += phiDelta;
+ return function panUp( distance, objectMatrix ) {
- // restrict phi to be between desired limits
- phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
+ if ( scope.screenSpacePanning === true ) {
- // restrict phi to be betwee EPS and PI-EPS
- phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
+ v.setFromMatrixColumn( objectMatrix, 1 );
- var radius = offset.length() * scale;
+ } else {
- // restrict radius to be between desired limits
- radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
-
- // move target to panned location
- this.target.add( pan );
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
- offset.x = radius * Math.sin( phi ) * Math.sin( theta );
- offset.y = radius * Math.cos( phi );
- offset.z = radius * Math.sin( phi ) * Math.cos( theta );
+ }
- position.copy( this.target ).add( offset );
+ v.multiplyScalar( distance );
- this.object.lookAt( this.target );
+ panOffset.add( v );
- thetaDelta = 0;
- phiDelta = 0;
- scale = 1;
- pan.set(0,0,0);
+ };
- if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
+ }();
- this.dispatchEvent( changeEvent );
+ // deltaX and deltaY are in pixels; right and down are positive
+ const pan = function () {
- lastPosition.copy( this.object.position );
+ const offset = new Vector3();
- }
+ return function pan( deltaX, deltaY ) {
- };
+ const element = scope.domElement;
+ if ( scope.object.isPerspectiveCamera ) {
- function getAutoRotationAngle() {
+ // perspective
+ const position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ let targetDistance = offset.length();
- return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
- }
+ // we use only clientHeight here so aspect ratio does not distort speed
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
- function getZoomScale() {
+ } else if ( scope.object.isOrthographicCamera ) {
- return Math.pow( 0.95, scope.zoomSpeed );
+ // 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 {
- function onMouseDown( event ) {
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
- if ( scope.enabled === false ) { return; }
- event.preventDefault();
+ }
- if ( event.button === 0 ) {
- if ( scope.noRotate === true ) { return; }
+ };
- state = STATE.ROTATE;
+ }();
- rotateStart.set( event.clientX, event.clientY );
+ function dollyOut( dollyScale ) {
- } else if ( event.button === 1 ) {
- if ( scope.noZoom === true ) { return; }
+ if ( scope.object.isPerspectiveCamera ) {
- state = STATE.DOLLY;
+ scale /= dollyScale;
- dollyStart.set( event.clientX, event.clientY );
+ } else if ( scope.object.isOrthographicCamera ) {
- } else if ( event.button === 2 ) {
- if ( scope.noPan === true ) { return; }
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
- state = STATE.PAN;
+ } else {
- panStart.set( event.clientX, event.clientY );
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
}
- // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
- scope.domElement.addEventListener( 'mousemove', onMouseMove, false );
- scope.domElement.addEventListener( 'mouseup', onMouseUp, false );
+ function dollyIn( dollyScale ) {
- }
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
- function onMouseMove( event ) {
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
- if ( scope.enabled === false ) return;
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
- event.preventDefault();
+ function handleMouseDownRotate( event ) {
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
- if ( state === STATE.ROTATE ) {
+ function handleMouseDownDolly( event ) {
- if ( scope.noRotate === true ) return;
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
rotateEnd.set( event.clientX, event.clientY );
- rotateDelta.subVectors( rotateEnd, rotateStart );
- // rotating across whole screen goes 360 degrees around
- scope.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
- scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ const element = scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
rotateStart.copy( rotateEnd );
- } else if ( state === STATE.DOLLY ) {
+ scope.update();
+
+ }
- if ( scope.noZoom === true ) return;
+ function handleMouseMoveDolly( event ) {
dollyEnd.set( event.clientX, event.clientY );
+
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
- scope.dollyIn();
+ dollyOut( getZoomScale() );
- } else {
+ } else if ( dollyDelta.y < 0 ) {
- scope.dollyOut();
+ dollyIn( getZoomScale() );
}
dollyStart.copy( dollyEnd );
- } else if ( state === STATE.PAN ) {
+ scope.update();
- if ( scope.noPan === true ) return;
+ }
+
+ function handleMouseMovePan( event ) {
panEnd.set( event.clientX, event.clientY );
- panDelta.subVectors( panEnd, panStart );
-
- scope.pan( panDelta );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
+ scope.update();
+
}
- // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
- scope.update();
+ function handleMouseWheel( event ) {
- }
+ if ( event.deltaY < 0 ) {
- function onMouseUp( /* event */ ) {
+ dollyIn( getZoomScale() );
- if ( scope.enabled === false ) return;
+ } else if ( event.deltaY > 0 ) {
- // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
- scope.domElement.removeEventListener( 'mousemove', onMouseMove, false );
- scope.domElement.removeEventListener( 'mouseup', onMouseUp, false );
+ dollyOut( getZoomScale() );
- state = STATE.NONE;
+ }
- }
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ let needsUpdate = false;
+
+ switch ( event.code ) {
+
+ 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;
- function onMouseWheel( event ) {
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
- if ( scope.enabled === false || scope.noZoom === true ) return;
+ }
- var delta = 0;
+ if ( needsUpdate ) {
- if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
+ // prevent the browser from scrolling on cursor keys
+ event.preventDefault();
- delta = event.wheelDelta;
+ scope.update();
- } else if ( event.detail ) { // Firefox
+ }
- delta = - event.detail;
}
- if ( delta > 0 ) {
+ function handleTouchStartRotate() {
- scope.dollyOut();
+ if ( pointers.length === 1 ) {
- } else {
+ rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+ } else {
- scope.dollyIn();
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+ rotateStart.set( x, y );
+
+ }
}
- }
+ function handleTouchStartPan() {
- function onKeyDown( event ) {
-
- if ( scope.enabled === false ) { return; }
- if ( scope.noKeys === true ) { return; }
- if ( scope.noPan === true ) { return; }
-
- // pan a pixel - I guess for precise positioning?
- // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
- var needUpdate = false;
-
- switch ( event.keyCode ) {
-
- case scope.keys.UP:
- scope.pan( new THREE.Vector2( 0, scope.keyPanSpeed ) );
- needUpdate = true;
- break;
- case scope.keys.BOTTOM:
- scope.pan( new THREE.Vector2( 0, -scope.keyPanSpeed ) );
- needUpdate = true;
- break;
- case scope.keys.LEFT:
- scope.pan( new THREE.Vector2( scope.keyPanSpeed, 0 ) );
- needUpdate = true;
- break;
- case scope.keys.RIGHT:
- scope.pan( new THREE.Vector2( -scope.keyPanSpeed, 0 ) );
- needUpdate = true;
- break;
- }
-
- // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
- if ( needUpdate ) {
+ if ( pointers.length === 1 ) {
- scope.update();
+ panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
+
+ } else {
+
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
+
+ panStart.set( x, y );
+
+ }
}
- }
-
- function touchstart( event ) {
+ function handleTouchStartDolly() {
+
+ 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 );
+
+ }
+
+ function handleTouchStartDollyPan() {
+
+ if ( scope.enableZoom ) handleTouchStartDolly();
+
+ if ( scope.enablePan ) handleTouchStartPan();
+
+ }
+
+ function handleTouchStartDollyRotate() {
+
+ if ( scope.enableZoom ) handleTouchStartDolly();
- if ( scope.enabled === false ) { return; }
+ if ( scope.enableRotate ) handleTouchStartRotate();
- switch ( event.touches.length ) {
+ }
+
+ function handleTouchMoveRotate( event ) {
- case 1: // one-fingered touch: rotate
- if ( scope.noRotate === true ) { return; }
+ if ( pointers.length == 1 ) {
- state = STATE.TOUCH_ROTATE;
+ rotateEnd.set( event.pageX, event.pageY );
- rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- break;
+ } else {
- case 2: // two-fingered touch: dolly
- if ( scope.noZoom === true ) { return; }
+ const position = getSecondPointerPosition( event );
- state = STATE.TOUCH_DOLLY;
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
- 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 );
- break;
+ rotateEnd.set( x, y );
- case 3: // three-fingered touch: pan
- if ( scope.noPan === true ) { return; }
+ }
- state = STATE.TOUCH_PAN;
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
- panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- break;
+ const element = scope.domElement;
- default:
- state = STATE.NONE;
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
}
- }
- function touchmove( event ) {
+ function handleTouchMovePan( event ) {
- if ( scope.enabled === false ) { return; }
+ if ( pointers.length === 1 ) {
- event.preventDefault();
- event.stopPropagation();
+ panEnd.set( event.pageX, event.pageY );
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+ } else {
- switch ( event.touches.length ) {
+ const position = getSecondPointerPosition( event );
- case 1: // one-fingered touch: rotate
- if ( scope.noRotate === true ) { return; }
- if ( state !== STATE.TOUCH_ROTATE ) { return; }
+ const x = 0.5 * ( event.pageX + position.x );
+ const y = 0.5 * ( event.pageY + position.y );
- rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- rotateDelta.subVectors( rotateEnd, rotateStart );
+ panEnd.set( x, y );
- // rotating across whole screen goes 360 degrees around
- scope.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
- scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+ }
- rotateStart.copy( rotateEnd );
- break;
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
- case 2: // two-fingered touch: dolly
- if ( scope.noZoom === true ) { return; }
- if ( state !== STATE.TOUCH_DOLLY ) { return; }
+ pan( panDelta.x, panDelta.y );
- 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 );
+ panStart.copy( panEnd );
- dollyEnd.set( 0, distance );
- dollyDelta.subVectors( dollyEnd, dollyStart );
+ }
- if ( dollyDelta.y > 0 ) {
+ function handleTouchMoveDolly( event ) {
- scope.dollyOut();
+ const position = getSecondPointerPosition( event );
- } else {
+ 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 );
+
+ }
+
+ function handleTouchMoveDollyPan( event ) {
+
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+ if ( scope.enablePan ) handleTouchMovePan( event );
+
+ }
+
+ function handleTouchMoveDollyRotate( event ) {
+
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
+
+ if ( scope.enableRotate ) handleTouchMoveRotate( event );
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onPointerDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.setPointerCapture( event.pointerId );
+
+ scope.domElement.addEventListener( 'pointermove', onPointerMove );
+ scope.domElement.addEventListener( 'pointerup', onPointerUp );
+
+ }
+
+ //
+
+ addPointer( event );
+
+ if ( event.pointerType === 'touch' ) {
- scope.dollyIn();
+ onTouchStart( event );
+
+ } else {
+
+ onMouseDown( event );
+
+ }
+
+ }
+
+ function onPointerMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ if ( event.pointerType === 'touch' ) {
+
+ onTouchMove( event );
+
+ } else {
+
+ onMouseMove( event );
+
+ }
+
+ }
+
+ function onPointerUp( event ) {
+
+ removePointer( event );
+
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.releasePointerCapture( event.pointerId );
+
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+ }
+
+ scope.dispatchEvent( _endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onPointerCancel( event ) {
+
+ removePointer( event );
+
+ }
+
+ function onMouseDown( event ) {
+
+ let mouseAction;
+
+ switch ( event.button ) {
+
+ case 0:
+
+ mouseAction = scope.mouseButtons.LEFT;
+ break;
+
+ case 1:
+
+ mouseAction = scope.mouseButtons.MIDDLE;
+ break;
+
+ case 2:
+
+ mouseAction = scope.mouseButtons.RIGHT;
+ break;
+
+ default:
+
+ mouseAction = - 1;
+
+ }
+
+ switch ( mouseAction ) {
+
+ case MOUSE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case MOUSE.ROTATE:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ } else {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ }
+
+ break;
+
+ case MOUSE.PAN:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ } else {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ }
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( _startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ 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 onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
+
+ event.preventDefault();
+
+ scope.dispatchEvent( _startEvent );
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( _endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ trackPointer( event );
+
+ switch ( pointers.length ) {
+
+ case 1:
+
+ switch ( scope.touches.ONE ) {
+
+ case TOUCH.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate();
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case TOUCH.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleTouchStartPan();
+
+ state = STATE.TOUCH_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ break;
+
+ case 2:
+
+ switch ( scope.touches.TWO ) {
+
+ case TOUCH.DOLLY_PAN:
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan();
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ case TOUCH.DOLLY_ROTATE:
+
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+
+ handleTouchStartDollyRotate();
+
+ state = STATE.TOUCH_DOLLY_ROTATE;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( _startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ trackPointer( event );
+
+ switch ( state ) {
+
+ case STATE.TOUCH_ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchMoveRotate( event );
+
+ scope.update();
+
+ break;
+
+ case STATE.TOUCH_PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleTouchMovePan( event );
+
+ scope.update();
+
+ break;
+
+ 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;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onContextMenu( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ }
+
+ function addPointer( event ) {
+
+ pointers.push( event );
+
+ }
+
+ function removePointer( event ) {
+
+ delete pointerPositions[ event.pointerId ];
+
+ for ( let i = 0; i < pointers.length; i ++ ) {
+
+ if ( pointers[ i ].pointerId == event.pointerId ) {
+
+ pointers.splice( i, 1 );
+ return;
}
- dollyStart.copy( dollyEnd );
- break;
+ }
+
+ }
+
+ function trackPointer( event ) {
- case 3: // three-fingered touch: pan
- if ( scope.noPan === true ) { return; }
- if ( state !== STATE.TOUCH_PAN ) { return; }
+ let position = pointerPositions[ event.pointerId ];
- panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
- panDelta.subVectors( panEnd, panStart );
-
- scope.pan( panDelta );
+ if ( position === undefined ) {
- panStart.copy( panEnd );
- break;
+ position = new Vector2();
+ pointerPositions[ event.pointerId ] = position;
+
+ }
- default:
- state = STATE.NONE;
+ position.set( event.pageX, event.pageY );
}
- }
+ function getSecondPointerPosition( event ) {
+
+ const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ];
+
+ return pointerPositions[ pointer.pointerId ];
- function touchend( /* event */ ) {
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+
+ scope.domElement.addEventListener( 'pointerdown', onPointerDown );
+ scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
- if ( scope.enabled === false ) { return; }
+ // force an update at start
+
+ this.update();
- state = STATE.NONE;
}
- this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
- this.localElement.addEventListener( 'mousedown', onMouseDown, false );
- this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
- this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
+}
+
+
+// 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
+
+class MapControls extends OrbitControls {
+
+ constructor( object, domElement ) {
+
+ super( object, domElement );
- this.domElement.addEventListener( 'keydown', onKeyDown, false );
+ this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
- this.localElement.addEventListener( 'touchstart', touchstart, false );
- this.domElement.addEventListener( 'touchend', touchend, false );
- this.domElement.addEventListener( 'touchmove', touchmove, false );
+ this.mouseButtons.LEFT = MOUSE.PAN;
+ this.mouseButtons.RIGHT = MOUSE.ROTATE;
+
+ this.touches.ONE = TOUCH.PAN;
+ this.touches.TWO = TOUCH.DOLLY_ROTATE;
+
+ }
-};
+}
-THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
\ No newline at end of file
+export { OrbitControls, MapControls };
diff --git a/js/PointerLockControls.js b/js/PointerLockControls.js
index d9586af..884a69c 100644
--- a/js/PointerLockControls.js
+++ b/js/PointerLockControls.js
@@ -1,183 +1,164 @@
+import {
+ Euler,
+ EventDispatcher,
+ Vector3
+} from 'three';
-THREE.PointerLockControls = function ( camera, domElement ) {
+const _euler = new Euler( 0, 0, 0, 'YXZ' );
+const _vector = new Vector3();
- if ( domElement === undefined ) {
+const _changeEvent = { type: 'change' };
+const _lockEvent = { type: 'lock' };
+const _unlockEvent = { type: 'unlock' };
- console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
- domElement = document.body;
+const _PI_2 = Math.PI / 2;
- }
+class PointerLockControls extends EventDispatcher {
- this.domElement = domElement;
- this.isLocked = false;
+ constructor( camera, domElement ) {
- // Set to constrain the pitch of the camera
- // Range is 0 to Math.PI radians
- this.minPolarAngle = 0; // radians
- this.maxPolarAngle = Math.PI; // radians
+ super();
- //
- // internals
- //
+ if ( domElement === undefined ) {
- var scope = this;
+ console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
+ domElement = document.body;
- var changeEvent = { type: 'change' };
- var lockEvent = { type: 'lock' };
- var unlockEvent = { type: 'unlock' };
+ }
- var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
+ this.domElement = domElement;
+ this.isLocked = false;
- var PI_2 = Math.PI / 2;
+ // Set to constrain the pitch of the camera
+ // Range is 0 to Math.PI radians
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
- var vec = new THREE.Vector3();
- var touchVec = new THREE.Vector3();
- //var scope = window;
- function onMouseMove( event ) {
+ this.pointerSpeed = 1.0;
- if(scope.isLocked === false && !isDesktop()) {
- const ts = event.changedTouches;
- if(ts.length == 1) {
- touchVec.x = ts[0].screenX;
- touchVec.y = ts[0].screenY;
- }
- }
+ const scope = this;
- if ( scope.isLocked === false ) return;
+ function onMouseMove( event ) {
- var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
- var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+ if ( scope.isLocked === false ) return;
+
+ const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
+ const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+
+ _euler.setFromQuaternion( camera.quaternion );
+
+ _euler.y -= movementX * 0.002 * scope.pointerSpeed;
+ _euler.x -= movementY * 0.002 * scope.pointerSpeed;
+
+ _euler.x = Math.max( _PI_2 - scope.maxPolarAngle, Math.min( _PI_2 - scope.minPolarAngle, _euler.x ) );
+
+ camera.quaternion.setFromEuler( _euler );
+
+ scope.dispatchEvent( _changeEvent );
- if(!isDesktop()) {
- const ts = event.changedTouches;
- if(ts.length == 1) {
- movementX = ts[0].screenX - touchVec.x;
- movementY = ts[0].screenY - touchVec.y;
- touchVec.x = ts[0].screenX;
- touchVec.y = ts[0].screenY;
- }
}
- euler.setFromQuaternion( camera.quaternion );
+ function onPointerlockChange() {
- euler.y -= movementX * 0.002;
- euler.x -= movementY * 0.002;
+ if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
- euler.x = Math.max( PI_2 - scope.maxPolarAngle, Math.min( PI_2 - scope.minPolarAngle, euler.x ) );
+ scope.dispatchEvent( _lockEvent );
- camera.quaternion.setFromEuler( euler );
+ scope.isLocked = true;
- scope.dispatchEvent( changeEvent );
+ } else {
- }
+ scope.dispatchEvent( _unlockEvent );
- function onPointerlockChange() {
- if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
- scope.dispatchEvent( lockEvent );
- scope.isLocked = true;
- } else {
- scope.dispatchEvent( unlockEvent );
- scope.isLocked = false;
- }
- }
+ scope.isLocked = false;
- function onPointerlockError() {
- console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
- }
+ }
- function onTouchDown(event) {
- const ts = event.changedTouches;
- if(ts.length == 1) {
- touchVec.x = ts[0].screenX;
- touchVec.y = ts[0].screenY;
}
- }
- function isDesktop() {
- const ua = navigator.userAgent.toLowerCase();
- return !((('ontouchend' in document) && ua.indexOf('macintosh') > 0) || ua.indexOf('iphone') > 0 || ua.indexOf('ipod') > 0 || ua.indexOf('ipad') > 0 || ua.indexOf('android') > 0 && ua.indexOf('mobile') > 0 || ua.indexOf('android') > 0);
- }
+ function onPointerlockError() {
+
+ console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
- this.connect = function () {
- if(isDesktop()) {
- scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove, false );
- scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange, false );
- scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError, false );
- } else {
- scope.domElement.addEventListener( 'touchmove', onMouseMove, false );
- scope.domElement.addEventListener( 'touchstart', onTouchDown, false );
- }
- };
-
- this.disconnect = function () {
- if(isDesktop()) {
- scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove, false );
- scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange, false );
- scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError, false );
- } else {
- scope.domElement.removeEventListener( 'touchstart', onTouchDown, false );
- scope.domElement.removeEventListener( 'touchmove', onMouseMove, false );
}
- };
- this.dispose = function () {
- this.disconnect();
- };
+ this.connect = function () {
+
+ scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove );
+ scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange );
+ scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError );
+
+ };
+
+ this.disconnect = function () {
+
+ scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove );
+ scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange );
+ scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError );
+
+ };
+
+ this.dispose = function () {
- this.getObject = function () { // retaining this method for backward compatibility
- return camera;
- };
+ this.disconnect();
- this.getDirection = function () {
- var direction = new THREE.Vector3( 0, 0, - 1 );
- return function ( v ) {
- return v.copy( direction ).applyQuaternion( camera.quaternion );
};
- }();
- this.moveForward = function ( distance ) {
+ this.getObject = function () { // retaining this method for backward compatibility
+
+ return camera;
+
+ };
+
+ this.getDirection = function () {
+
+ const direction = new Vector3( 0, 0, - 1 );
+
+ return function ( v ) {
- // move forward parallel to the xz-plane
- // assumes camera.up is y-up
+ return v.copy( direction ).applyQuaternion( camera.quaternion );
- vec.setFromMatrixColumn( camera.matrix, 0 );
- vec.crossVectors( camera.up, vec );
- camera.position.addScaledVector( vec, distance );
- };
+ };
- this.moveRight = function ( distance ) {
- vec.setFromMatrixColumn( camera.matrix, 0 );
- camera.position.addScaledVector( vec, distance );
- };
+ }();
+
+ this.moveForward = function ( distance ) {
+
+ // move forward parallel to the xz-plane
+ // assumes camera.up is y-up
+
+ _vector.setFromMatrixColumn( camera.matrix, 0 );
+
+ _vector.crossVectors( camera.up, _vector );
+
+ camera.position.addScaledVector( _vector, distance );
+
+ };
+
+ this.moveRight = function ( distance ) {
+
+ _vector.setFromMatrixColumn( camera.matrix, 0 );
+
+ camera.position.addScaledVector( _vector, distance );
+
+ };
+
+ this.lock = function () {
- this.lock = function () {
- if(isDesktop()) {
this.domElement.requestPointerLock();
- } else {
- scope.dispatchEvent( lockEvent );
- scope.isLocked = true;
- }
- euler.x = 0;
- euler.y = Math.PI / 2;
- camera.quaternion.setFromEuler( euler );
- };
- this.unlock = function () {
- if(isDesktop()) {
+ };
+
+ this.unlock = function () {
+
scope.domElement.ownerDocument.exitPointerLock();
- } else {
- scope.dispatchEvent( unlockEvent );
- scope.isLocked = false;
- }
- };
- this.desktopMode = isDesktop();
+ };
- this.connect();
+ this.connect();
-};
+ }
-THREE.PointerLockControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.PointerLockControls.prototype.constructor = THREE.PointerLockControls;
+}
-//export { PointerLockControls };
\ No newline at end of file
+export { PointerLockControls };
diff --git a/js/main.js b/js/main.js
index 62eca83..9a4e777 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,7 +1,10 @@
+import * as THREE from '../build/three.module.js';
import { GLTFLoader } from './GLTFLoader.js';
import { RGBELoader } from './RGBELoader.js';
import { VRButton } from './WebVR.js';
import { VirtualPad } from './virtualpad.js';
+import { OrbitControls } from './OrbitControls.js';
+import { PointerLockControls } from './PointerLockControls.js';
const debugMode = false;
const domtip = $("#tip");
@@ -160,7 +163,7 @@ var settings = {
settings._halfFramerate = (savedata[1] == 1);
settings._enableShadow = (savedata[2] == 1);
settings._cycleSpeed = 6000;
- settings._cycleSun = (savedata[3] == 1);
+ settings._cycleSun = (savedata[3] == 1);
}
if (localStorage.hasOwnProperty("settings")) {
try {
@@ -254,7 +257,7 @@ class VirtualCampusApp {
// カメラを作成
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.1, 1000);
- this.controls = new THREE.OrbitControls(this.camera);
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.maxDistance = 600;
this.controls.noKeys = true;
this.controls.maxPolarAngle = Math.PI * 0.495;
@@ -286,7 +289,7 @@ class VirtualCampusApp {
setupFirstPersonMode() {
// 移動関連のコンポーネント初期化
- this.walkthrough = new THREE.PointerLockControls(this.camera, document.getElementById('canvas') );
+ this.walkthrough = new PointerLockControls(this.camera, document.getElementById('canvas'));
this.walkthrough.addEventListener('lock', () => {
player.birdPos = new THREE.Vector3(this.camera.position.x, this.camera.position.y, this.camera.position.z);
if(this.currentScene) {
@@ -340,7 +343,7 @@ class VirtualCampusApp {
this.currentScene.update();
}
- if(!vcConfig._halfFramerate || vcConfig._halfFramerate && this.frameCounter == 0) {
+ if(!vcConfig._halfFramerate || vcConfig._halfFramerate && this.frameCounter == 0) {
this.renderer.render(this.scene, this.camera);
}
this.frameCounter = (this.frameCounter + 1) % 2;
@@ -391,7 +394,7 @@ class VirtualCampusApp {
this.walkthrough.moveRight(dir.x * player.speed * delta);
this.walkthrough.moveForward(dir.z * player.speed * delta);
}
-
+
}
resetRenderer() {
@@ -445,7 +448,7 @@ class VirtualCampusApp {
newRenderer.shadowMap.enabled = this.renderer.shadowMap.enabled;
if(debugMode)
console.log(newRenderer);
- this.renderer.dispose();
+ this.renderer.dispose();
this.renderer = null;
this.renderer = newRenderer;
this.resetRenderer();
@@ -501,7 +504,7 @@ class CampusScene {
/**
* マウスが移動したときに呼ばれます
- * @param {Object} evt MouseMoveイベントのオブジェクト
+ * @param {Object} evt MouseMoveイベントのオブジェクト
*/
onMouseMove(evt) {}
@@ -648,7 +651,7 @@ class CampusSceneMain extends CampusScene {
} else {
UIKit.setDialogContent('');
}
-
+
doneFade = false;
dialog.visible = true;
});
@@ -675,7 +678,7 @@ class CampusSceneMain extends CampusScene {
}
doneFade = true;
}
-
+
}
@@ -687,7 +690,7 @@ class CampusSceneMain extends CampusScene {
this.ambientLight.intensity = 0.1 * now;
if(this.nightWindows.length > 0)
this.nightWindows[0].emissive.setHex(now < 0.25 || now > 0.75 ? 0xf4ef9b : 0x0);
-
+
if(this.skyDome) {
const overlay = 1.0 - (Math.max(0, Math.sin(6.2 * now + 1.6)) * 1.0);
this.skyDome.material.color.setRGB(overlay, overlay, overlay);
@@ -705,11 +708,11 @@ class CampusSceneMain extends CampusScene {
}
onMouseMove(ev) {
-
+
if(!dialog.visible) {
if(!(this.scene && this.app.controls) || this.app.isFirstPersonMode())
return;
-
+
if(ev.target && ev.target.nodeName == "IMG") {
tooltip.visible = false;
return;
@@ -724,7 +727,7 @@ class CampusSceneMain extends CampusScene {
let mouse = new THREE.Vector2();
mouse.x = ( x / size.x ) * 2 - 1;
mouse.y = -( y / size.y ) * 2 + 1;
-
+
// 取得したX、Y座標でrayの位置を更新
this.toolTipRaycaster.setFromCamera( mouse, this.app.camera );
// オブジェクトの取得
@@ -740,12 +743,12 @@ class CampusSceneMain extends CampusScene {
break;
}
}
- if (!hit) {
+ if (!hit) {
tooltip.visible = false;
}
}
-
+
// ツールチップ処理
if(!this.app.isFirstPersonMode()) {
if(tooltip.prevFrameVisible != tooltip.visible) {
@@ -755,7 +758,7 @@ class CampusSceneMain extends CampusScene {
if (tooltip.visible && !UIKit.isMenuOpen()) {
domtip.show();
}
-
+
if (tooltip.targetId && tooltip.targetId.length > 0) {
domtipText.html("" + tooltip.label + "");
domtipExpl.css("opacity", toolTips[tooltip.targetId].doc ? 1 : 0.3);
@@ -764,13 +767,13 @@ class CampusSceneMain extends CampusScene {
domtip.css("width", domtipCopy.css("width")).css("height", domtipCopy.css("height"));
}
}
-
+
if (dialog.visible && dialog.height != domDialog.height()) {
dialog.height = domDialog.height();
domDialogMain.height(dialog.height - 105);
}
-
-
+
+
}
tooltip.prevFrameVisible = tooltip.visible;
}
@@ -883,7 +886,7 @@ class UIController {
$("#VRButton").show(500);
}
-
+
openSettings() {
this.closeMobileMenu();
this.app.controls.enabled = false;
@@ -1069,7 +1072,7 @@ class UIKit {
/**
* プログレスバーの値を設定します
- * @param {number} p 進捗度 0.0 ~ 1.0
+ * @param {number} p 進捗度 0.0 ~ 1.0
*/
static setProgress(p) {
UIKit.getInstance().domProgressBar.css('transform', `scaleX(${p})`);
@@ -1078,7 +1081,7 @@ class UIKit {
/**
* Pathの内容を読み込んでダイアログを表示します
* @param {string} title タイトル
- * @param {string} path 読み込むファイルパス
+ * @param {string} path 読み込むファイルパス
*/
static showDialogFromPath(title, path, callback) {
$("#dialog_title").text(title);
@@ -1121,7 +1124,7 @@ class UIKit {
clickPos = {x:evt.screenX, y:evt.screenY};
cancel = false;
}, false);
-
+
dom.addEventListener('touchstart', evt => {
clickPos = {x:evt.screenX, y:evt.screenY};
cancel = false;
@@ -1129,7 +1132,7 @@ class UIKit {
function upHandler(evt) {
if(cancel)
return;
-
+
const d = (evt.screenX - clickPos.x) * (evt.screenX - clickPos.x) + (evt.screenY - clickPos.y) * (evt.screenY - clickPos.y);
if(d <= 16) {
callback();
@@ -1137,7 +1140,7 @@ class UIKit {
}
dom.addEventListener('touchend', evt => upHandler(evt), false);
dom.addEventListener('mouseup', evt => upHandler(evt), false);
-
+
// for iOS
function preventScroll(evt) {
if(evt.touches.length >= 2) {
@@ -1154,7 +1157,7 @@ const uikitInstance = new UIKit();
const app = new VirtualCampusApp();
-window.addEventListener('DOMContentLoaded', () => {
+document.addEventListener('DOMContentLoaded', () => {
app.init();
});
app.main();
diff --git a/vhc.html b/vhc.html
index 78e23f4..dc2dc69 100644
--- a/vhc.html
+++ b/vhc.html
@@ -6,12 +6,15 @@
-
-
-
-
-
+
+
@@ -105,4 +108,4 @@