From b8b56dfcb3198d085cb5f725ea2958e0d17be4ee Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 00:57:49 +0900 Subject: [PATCH 1/7] remove trailing whitespace --- js/main.js | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/js/main.js b/js/main.js index 62eca83..b66db0e 100644 --- a/js/main.js +++ b/js/main.js @@ -160,7 +160,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 { @@ -340,7 +340,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 +391,7 @@ class VirtualCampusApp { this.walkthrough.moveRight(dir.x * player.speed * delta); this.walkthrough.moveForward(dir.z * player.speed * delta); } - + } resetRenderer() { @@ -445,7 +445,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 +501,7 @@ class CampusScene { /** * マウスが移動したときに呼ばれます - * @param {Object} evt MouseMoveイベントのオブジェクト + * @param {Object} evt MouseMoveイベントのオブジェクト */ onMouseMove(evt) {} @@ -648,7 +648,7 @@ class CampusSceneMain extends CampusScene { } else { UIKit.setDialogContent(''); } - + doneFade = false; dialog.visible = true; }); @@ -675,7 +675,7 @@ class CampusSceneMain extends CampusScene { } doneFade = true; } - + } @@ -687,7 +687,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 +705,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 +724,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 +740,12 @@ class CampusSceneMain extends CampusScene { break; } } - if (!hit) { + if (!hit) { tooltip.visible = false; } } - + // ツールチップ処理 if(!this.app.isFirstPersonMode()) { if(tooltip.prevFrameVisible != tooltip.visible) { @@ -755,7 +755,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 +764,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 +883,7 @@ class UIController { $("#VRButton").show(500); } - + openSettings() { this.closeMobileMenu(); this.app.controls.enabled = false; @@ -1069,7 +1069,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 +1078,7 @@ class UIKit { /** * Pathの内容を読み込んでダイアログを表示します * @param {string} title タイトル - * @param {string} path 読み込むファイルパス + * @param {string} path 読み込むファイルパス */ static showDialogFromPath(title, path, callback) { $("#dialog_title").text(title); @@ -1121,7 +1121,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 +1129,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 +1137,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) { From 7214f3a60c6962203740e5d62b4daa2a5cf33073 Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 00:58:15 +0900 Subject: [PATCH 2/7] add .editorconfig --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig 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 From ef35db495f1a25d1aa44067e4294ac171f41d121 Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:27:49 +0900 Subject: [PATCH 3/7] replace THREE.*Controls with jsm version original source (tag: r142) - [OrbitControls.js](https://github.com/mrdoob/three.js/blob/r142/examples/jsm/controls/OrbitControls.js) - [PointerLockControls.js](https://github.com/mrdoob/three.js/blob/r142/examples/jsm/controls/PointerLockControls.js) --- js/OrbitControls.js | 1399 +++++++++++++++++++++++++++---------- js/PointerLockControls.js | 253 ++++--- js/main.js | 7 +- vhc.html | 7 +- 4 files changed, 1155 insertions(+), 511 deletions(-) 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 b66db0e..007791c 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"); @@ -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.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) { diff --git a/vhc.html b/vhc.html index 78e23f4..beed22b 100644 --- a/vhc.html +++ b/vhc.html @@ -6,12 +6,7 @@ - - - - - @@ -105,4 +100,4 @@ - \ No newline at end of file + From 6d0c0a8cbceb5afa54a6a6afb83aa0910ed4c74f Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:53:07 +0900 Subject: [PATCH 4/7] add importmap to fix `Uncaught TypeError: Failed to resolve module specifier "three". Relative references must start with either "/", "./", or "../".` --- vhc.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vhc.html b/vhc.html index beed22b..ed764f9 100644 --- a/vhc.html +++ b/vhc.html @@ -7,6 +7,13 @@ + From 6fb8d0b0286df4ca94b533143eac14a4bf3a8938 Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:44:01 +0900 Subject: [PATCH 5/7] fix missing domElement see https://github.com/mrdoob/three.js/pull/17612 --- js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/main.js b/js/main.js index 007791c..95d6f04 100644 --- a/js/main.js +++ b/js/main.js @@ -257,7 +257,7 @@ class VirtualCampusApp { // カメラを作成 this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.1, 1000); - this.controls = new 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; From 74a80169e768cf87ed3622206653458ae83175bf Mon Sep 17 00:00:00 2001 From: h-takeyeah <61489178+h-takeyeah@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:51:19 +0900 Subject: [PATCH 6/7] add polyfill to import maps support importmap reference: https://github.com/WICG/import-maps importmap does not work in Firefox --- vhc.html | 1 + 1 file changed, 1 insertion(+) diff --git a/vhc.html b/vhc.html index ed764f9..dc2dc69 100644 --- a/vhc.html +++ b/vhc.html @@ -7,6 +7,7 @@ +