Threejs OrbitControl源码解析
import {Vector3} from 'three'; import {Vector2} from 'three'; import {MOUSE} from 'three'; import {Quaternion} from 'three'; import {Spherical} from 'three'; import {PerspectiveCamera} from 'three'; import {OrthographicCamera} from 'three'; import {EventDispatcher} from 'three'; import {FOV} from './CameraUtils'; import {zoomRange} from '../utils/zoomUtils'; /** * 实现手势控制: * 移动端: * 单指移动->平移 * 双指转动->绕y轴旋转 * 双指pinch->缩放 * 双指上下滑动->绕x轴旋转(2/3D切换) * pc端: * 鼠标左键移动->平移 * 鼠标滚轮->缩放 * 鼠标右键移动->旋转 * @type {[type]} */ const DEG2RAD = Math.PI / 180; const INTOUCHDEVICE = 'ontouchstart' in document; // const zoomRange = [2.3886571, 1.1943286, 0.5971643, 0.2985821, 0.1492911, 0.0746455, 0.0373227]; export let OrbitControls = function ( object, domElement ) { this.object = object; // Object代表camera this.domElement = ( domElement !== undefined ) ? domElement : document; // Set to false to disable this control this.enabled = true; // "target" sets the location of focus, where the object orbits around this.target = new Vector3(); // How far you can dolly in and out ( PerspectiveCamera only ) // 我理解是相机距离target的距离 this.minDistance = 0; this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only ) this.minZoom = 0; this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits. // Range is 0 to Math.PI radians. // 我理解是在一个包围y轴的平面内能旋转的角度(与y轴平行线的夹角); // 可以理解为绕垂直y轴和相机的target->position向量形成的平面的向量来转动(四元数) this.minPolarAngle = 0; // radians this.maxPolarAngle = Math.PI; // radians // How far you can orbit horizontally, upper and lower limits. // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. // 在一个包围x轴的平面内能够旋转的角度(与x轴平行线的夹角);可以理解为绕y轴的旋转 this.minAzimuthAngle = - Infinity; // radians this.maxAzimuthAngle = Infinity; // radians // Set to true to enable damping (inertia) // If damping is enabled, you must call controls.update() in your animation loop this.enableDamping = false;// 阻尼 this.dampingFactor = 0.25; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. // Set to false to disable zooming this.enableZoom = true;// 缩放控制(透视相机下dolly距离或正射投影下的zoom) this.zoomSpeed = 1.0; // 这个zoomScale是指的地图数据的级别 this.zoomScale = zoomRange[zoomRange.length - 1];// 设置zoom级别1-6 // Set to false to disable rotating this.enableRotate = true; this.rotateSpeed = 1.0; // Set to false to disable panning this.enablePan = true; this.keyPanSpeed = 7.0; // pixels moved per arrow key push // Set to true to automatically rotate around the target // If auto-rotate is enabled, you must call controls.update() in your animation loop this.autoRotate = false; this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 // Set to false to disable use of the keys this.enableKeys = true; // The four arrow keys this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; // Mouse buttons this.mouseButtons = { ORBIT: MOUSE.RIGHT, ZOOM: MOUSE.MIDDLE, PAN: MOUSE.LEFT }; // for reset this.target0 = this.target.clone(); this.position0 = this.object.position.clone(); this.zoom0 = this.object.zoom; // // public methods // this.getPolarAngle = function () { return spherical.phi; }; this.getAzimuthalAngle = function () { return spherical.theta; }; // 保存当前control的target、position和相机的zoom this.saveState = function () { scope.target0.copy( scope.target ); scope.position0.copy( scope.object.position ); scope.zoom0 = scope.object.zoom; }; // 恢复上次状态 this.reset = function () { scope.target.copy( scope.target0 ); scope.object.position.copy( scope.position0 ); scope.object.zoom = scope.zoom0; scope.object.updateProjectionMatrix(); scope.dispatchEvent( changeEvent ); scope.update(); state = STATE.NONE; }; // this method is exposed, but perhaps it would be better if we can make it private... this.update = function () { var offset = new Vector3(); // so camera.up is the orbit axis // 根据相机的up轴指向和y轴来求出一个表示绕垂直这两向量形成的平面的向量的转动夹角 // 这个四元数表示初始时相机up与y轴的夹角 var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); var quatInverse = quat.clone().inverse();// 求共轭四元数 var lastPosition = new Vector3(); var lastQuaternion = new Quaternion(); /** * [update description] * @param {[type]} updateZoomScale [zoomScale相同时仍然触发zoom事件] * @param {[type]} noUpdateZoom [是否触发zoom事件] * @return {[type]} [description] */ return function update(updateZoomScale, noUpdateZoom) { var position = scope.object.position; // 这个时候position跟target应该都还未变化 offset.copy( position ).sub( scope.target );// 形成一个从target->position的向量 // rotate offset to "y-axis-is-up" space // 相机可以做各种旋转up也会有各种变化,但我们希望将y轴作为相机的up轴,这样方便计算 offset.applyQuaternion( quat );// 用一个四元素来旋转向量, v‘ = qvq-1 // angle from z-axis around y-axis spherical.setFromVector3( offset );// 将相机位置转化成球面坐标 if ( scope.autoRotate && state === STATE.NONE ) { rotateLeft( getAutoRotationAngle() ); } // 在我看来每次手势事件转动物体实际都是相当于在转动一个球, // 以屏幕中心为原点转动轴就是世界坐标系的三个轴, // 而我们观察到的物体就是将相机帮到球上转动后的结果, // 所以我们先把相机的up轴设置为与y轴重合, // 这样相机就相当于在一个标准单位球下来转动、缩放 // 最后再将相机放回原先的up轴所在空间 // 这样的好处就是通过改变相机来改变视域中的所有物体,而不是要改变物体的模型矩阵 spherical.theta += sphericalDelta.theta; // console.log('theta: ' + spherical.theta * 180 / Math.PI, 'phi: ' + spherical.phi * 180 / Math.PI); // restrict theta to be between desired limits spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); // if (scope.object instanceof PerspectiveCamera) { // console.log('spherical.phi: ', spherical.phi, 'sphericalDelta.phi: ', sphericalDelta.phi); spherical.phi += sphericalDelta.phi; // restrict phi to be between desired limits spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); // } spherical.makeSafe(); spherical.radius *= scale; // 缩放,dollyIn或dollyOut // console.log('scale: ' + scale); // restrict radius to be between desired limits spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // console.log('spherical.radius: ' + spherical.radius, scope.minDistance, scope.maxDistance); // move target to panned location scope.target.add( panOffset ); // 利用向量加法将观察点向平移方向移动 // console.log('target: ', scope.target); offset.setFromSpherical( spherical ); // 利用y轴为up轴的球面坐标坐标系来设置target-position向量 // rotate offset back to "camera-up-vector-is-up" space offset.applyQuaternion( quatInverse ); // 然后将相机的target-position旋转到以相机真正up轴为相机正上方的空间中 // 利用向量加法将新观察点的向量与target-position相加,得到相机新的位置 // 这样就通过移动相机来实现移动物体;而不是要修改每个物体的模型矩阵 position.copy( scope.target ).add( offset ); scope.object.lookAt( scope.target ); // if (sphericalDelta.theta !== 0 || sphericalDelta.phi !== 0) { scope.dispatchEvent({ type: 'rotation-change', sphericalDelta: sphericalDelta, spherical: spherical }); // } if ( scope.enableDamping === true ) { sphericalDelta.theta *= ( 1 - scope.dampingFactor ); sphericalDelta.phi *= ( 1 - scope.dampingFactor ); } else { sphericalDelta.set( 0, 0, 0 ); } if (!noUpdateZoom) { if (scale !== 1 || updateZoomScale) { var zoomLevel = getZoomRange(spherical.radius); if (scope.zoomScale !== zoomLevel || updateZoomScale) { scope.dispatchEvent({ type: 'zoom-change', zoom: zoomLevel }); scope.zoomScale = zoomLevel; } scope.dispatchEvent({ type: 'scale-change', scale: scale }); // console.log('zoomLevel: ' + zoomLevel); } } // 重置变量 scale = 1; panOffset.set( 0, 0, 0 ); // update condition is: // min(camera displacement, camera rotation in radians)^2 > EPS // using small-angle approximation cos(x/2) = 1 - x^2 / 8 // 满足更新条件时才会记录 var ds = lastPosition.distanceToSquared( scope.object.position ); if ( zoomChanged || ds > 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.getPixelMeterRatio = function(target) { // target = target ? target : scope.target; // var element = scope.domElement === document ? // scope.domElement.body : scope.domElement; // var distance = scope.object.position.distanceTo(target); // var h = scope.object instanceof PerspectiveCamera ? // (Math.tan(scope.object.fov / 2 * DEG2RAD) * distance) * 2 : // (scope.object.top - scope.object.bottom) / scope.object.zoom; // var meterPerPixel = h / element.clientHeight; // return meterPerPixel; // } this.goTop = function() { sphericalDelta.phi = -spherical.phi; } this.getScaleByDistance = function(distance) { return distance / spherical.radius; } this.getScaleByZoom = function(zoom) { if (scope.object instanceof OrthographicCamera) { return 1; } var element = scope.domElement === document ? scope.domElement.body : scope.domElement; var ratio = zoomRange[zoom - 1]; var h = ratio * element.clientHeight; var distance = h / 2 / Math.tan(scope.object.fov / 2 * DEG2RAD); var eyeDis = scope.object.position.distanceTo(scope.target); return distance / eyeDis; } this.setScale = function(s, noUpdateZoom, updateZoomScale) { scale = s; scope.update(updateZoomScale, noUpdateZoom); } this.unprojectPan = function(deltaVector, moveDown, target) { // var getProjectLength() var element = scope.domElement === document ? scope.domElement.body : scope.domElement; var cxv = new Vector3(0, 0, 0).setFromMatrixColumn(scope.object.matrix, 0);// 相机x轴 var cyv = new Vector3(0, 0, 0).setFromMatrixColumn(scope.object.matrix, 1);// 相机y轴 // 相机轴都是单位向量 var pxl = deltaVector.dot(cxv)/* / cxv.length()*/; // 向量在相机x轴的投影 var pyl = deltaVector.dot(cyv)/* / cyv.length()*/; // 向量在相机y轴的投影 // offset=dx * vector(cx) + dy * vector(cy.project(xoz).normalize) // offset由相机x轴方向向量+相机y轴向量在xoz平面的投影组成 var dv = deltaVector.clone(); dv.y = 0; // 目前算法不会改变相机target的y分量 dv.sub(cxv.multiplyScalar(pxl)); pyl = dv.length(); if ( scope.object instanceof PerspectiveCamera ) { // perspective var position = scope.object.position; var offset = new Vector3(0, 0, 0); offset.copy(position).sub(target || scope.target); var distance = offset.length(); distance *= Math.tan(scope.object.fov / 2 * Math.PI / 180); // console.log('unprojectPan distance: ',distance); // var xd = 2 * distance * deltaX / element.clientHeight; // var yd = 2 * distance * deltaY / element.clientHeight; // panLeft( xd, scope.object.matrix ); // panUp( yd, scope.object.matrix ); var deltaX = pxl * element.clientHeight / (2 * distance); var deltaY = pyl * element.clientHeight / (2 * distance) * (moveDown ? -1 : 1); return [deltaX, deltaY]; } else if ( scope.object instanceof OrthographicCamera ) { // orthographic // panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); // panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); var deltaX = pxl * element.clientWidth * scope.object.zoom / (scope.object.right - scope.object.left); var deltaY = pyl * element.clientHeight * scope.object.zoom / (scope.object.top - scope.object.bottom) * (moveDown ? -1 : 1); return [deltaX, deltaY]; } else { // camera neither orthographic nor perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); } } this.projectPan = function(deltaX, deltaY) { var element = scope.domElement === document ? scope.domElement.body : scope.domElement; var deltaVector = new Vector3(0, 0, 0); if ( scope.object instanceof PerspectiveCamera ) { var position = scope.object.position; var offset = new Vector3(0, 0, 0); offset.copy(position).sub(scope.target); var distance = offset.length(); distance *= Math.tan(scope.object.fov / 2 * Math.PI / 180); var xd = 2 * distance * deltaX / element.clientHeight; var yd = 2 * distance * deltaY / element.clientHeight; panLeft( xd, scope.object.matrix ); panUp( yd, scope.object.matrix ); deltaVector.set(panOffset.x, panOffset.y, panOffset.z); panOffset.set(0, 0, 0); return deltaVector; } else if ( scope.object instanceof OrthographicCamera ) { // orthographic panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); deltaVector.set(panOffset.x, panOffset.y, panOffset.z); panOffset.set(0, 0, 0); return deltaVector; } else { // camera neither orthographic nor perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); } } this.dispose = function () { scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); document.removeEventListener( 'mousemove', onMouseMove, false ); document.removeEventListener( 'mouseup', onMouseUp, false ); window.removeEventListener( 'keydown', onKeyDown, false ); //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? }; this.getPixelMeterRatio = function(target) { target = target ? target : scope.target; var distance = scope.object.position.distanceTo(target); return getPixelMeterRatioByDistance(distance); } function getPixelMeterRatioByDistance(distance) { var element = scope.domElement === document ? scope.domElement.body : scope.domElement; // var distance = scope.object.position.distanceTo(target); var h = scope.object instanceof PerspectiveCamera ? (Math.tan(scope.object.fov / 2 * DEG2RAD) * distance) * 2 : (scope.object.top - scope.object.bottom) / scope.object.zoom; var meterPerPixel = h / element.clientHeight; return meterPerPixel; } function getDistanceByPixelMeterRatio(ratio) { var element = scope.domElement === document ? scope.domElement.body : scope.domElement; let cameraScreenHeight = ratio * element.clientHeight; let distance = cameraScreenHeight / 2 / Math.tan((scope.object.fov || FOV) / 2 * DEG2RAD); return distance; } function getZoomRange(ds) { // return 5; let meterPerPixel = scope.getPixelMeterRatio(); // var maxIdx = zoomRange.length - 1; // if (meterPerPixel <= zoomRange[maxIdx]) { // return maxIdx + 1; // } else if(meterPerPixel >= zoomRange[0]) { // return 0 + 1; // } // for (var i = 1, len = zoomRange.length; i < len; i++) { // if (meterPerPixel <= zoomRange[i] && meterPerPixel > zoomRange[i + 1]) { // return i + 1; // } // } return getZoomRangeByRatio(meterPerPixel); } function getZoomRangeByRatio(meterPerPixel) { // let meterPerPixel = scope.getPixelMeterRatio(); var maxIdx = zoomRange.length - 1; if (meterPerPixel <= zoomRange[maxIdx]) { return maxIdx + 1; } else if(meterPerPixel >= zoomRange[0]) { return 0 + 1; } for (var i = 0, len = zoomRange.length; i < len; i++) { if (meterPerPixel <= zoomRange[i] && meterPerPixel > zoomRange[i + 1]) { return i + 1; } } } // // internals // var scope = this; var changeEvent = { type: 'change' }; var startEvent = { type: 'start' }; var endEvent = { type: 'end' }; var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5, TOUCH_ROTATE_THETA: 6, TOUCH_ROTATE_PHI: 7 }; var state = STATE.NONE; var EPS = 0.000001; // current position in spherical coordinates // 当前的球面坐标系;所有在屏幕的旋转最终都映射为球的转动; // 也就是说你在2d屏幕的转动实际上相当于在转一个球 var spherical = new Spherical(); var sphericalDelta = new Spherical(); var scale = 1; var panOffset = new Vector3(); var zoomChanged = false; // 记录touchstart开始的时间,主要用于处理双指事件时判断是旋转还是缩放 var touchStartTime = null; var rotateStart = new Vector2(); var rotateEnd = new Vector2(); var rotateDelta = new Vector2(); var touchThetaRotateStart = new Vector2(); var touchThetaRotateEnd = new Vector2(); var touchPhiFingerStart1 = new Vector2(); var touchPhiFingerEnd1 = new Vector2(); var touchPhiFingerStart2 = new Vector2(); var touchPhiFingerEnd2 = new Vector2(); var panStart = new Vector2(); var panEnd = new Vector2(); var panDelta = new Vector2(); var dollyStart = new Vector2(); var dollyEnd = new Vector2(); var dollyDelta = new Vector2(); function getAutoRotationAngle() { return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; } function getZoomScale() { return Math.pow( 0.95, scope.zoomSpeed ); } function rotateLeft( angle ) {// 绕y轴向左转 // console.log('rotateLeft: ' + sphericalDelta.theta * 180 / Math.PI + ' ' + angle * 180 / Math.PI); sphericalDelta.theta -= angle; } function rotateUp( angle ) {// 极坐标向上转动 // console.log('rotateUp: ' + sphericalDelta.phi * 180 / Math.PI + ' ' + angle * 180 / Math.PI); sphericalDelta.phi -= angle; } // x轴控制世界的左右、y轴控制世界的上下,z轴控制物体离屏幕的远近 var panLeft = function () { var v = new Vector3(); return function panLeft( distance, objectMatrix ) {// panLeft只求出沿着x轴向左平移的距离 v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix v.multiplyScalar( - distance ); panOffset.add( v ); }; }(); var panUp = function () { var v = new Vector3(); return function panUp( distance, objectMatrix ) {// panUp求出沿着y轴向上平移的距离 v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix var h = v.dot(new Vector3(0, 1, 0)); v.sub(new Vector3(0, h, 0)); v.normalize(); v.multiplyScalar( distance ); panOffset.add( v ); }; }(); // deltaX and deltaY are in pixels; right and down are positive var pan = function () { var offset = new Vector3(); return function pan( deltaX, deltaY ) { var element = scope.domElement === document ? scope.domElement.body : scope.domElement; if ( scope.object instanceof PerspectiveCamera ) { // perspective var position = scope.object.position; offset.copy( position ).sub( scope.target ); var targetDistance = offset.length(); // half of the fov is center to top of screen targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );// 计算出透视屏幕的高度 // console.log('pan targetDistance: ',targetDistance); // we actually don't use screenWidth, since perspective camera is fixed to screen height // 不使用屏幕宽度,因为透视相机的屏幕高度是固定的 // 平移的要点是沿着相机投影屏幕的的x轴、y轴移动,也就是沿着相机的right轴和up轴移动 panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); } else if ( scope.object instanceof OrthographicCamera ) { // orthographic panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); } else { // camera neither orthographic nor perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); scope.enablePan = false; } }; }(); function dollyIn( dollyScale ) { // if ( scope.object instanceof PerspectiveCamera ) { if (INTOUCHDEVICE) { scale = dollyScale; } else { scale /= dollyScale; } // } else if ( scope.object instanceof OrthographicCamera ) { console.log('dollyIn: ', dollyScale); if (INTOUCHDEVICE) { scale = (2 - dollyScale / scope.object.zoom); } let zoom = scope.object.zoom; if (INTOUCHDEVICE) { zoom = dollyScale; } else { zoom = scope.object.zoom * dollyScale; } scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, zoom ) ); scope.object.updateProjectionMatrix(); zoomChanged = true; } // else { // console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); // scope.enableZoom = false; // } } function dollyOut( dollyScale ) { // console.log() // if ( scope.object instanceof PerspectiveCamera ) { if (INTOUCHDEVICE) { scale = dollyScale; } else { scale *= dollyScale; } // } else if ( scope.object instanceof OrthographicCamera ) { console.log('dollyOut: ', dollyScale); if (INTOUCHDEVICE) { scale = (2 - dollyScale / scope.object.zoom); } let zoom = scope.object.zoom; if (INTOUCHDEVICE) { zoom = dollyScale; } else { zoom = scope.object.zoom / dollyScale; } scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, zoom) ); scope.object.updateProjectionMatrix(); zoomChanged = true; } // else { // console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); // scope.enableZoom = false; // } } // // event callbacks - update the object state // function handleMouseDownRotate( event ) { //console.log( 'handleMouseDownRotate' ); rotateStart.set( event.clientX, event.clientY ); } function handleMouseDownDolly( event ) { //console.log( 'handleMouseDownDolly' ); dollyStart.set( event.clientX, event.clientY ); } function handleMouseDownPan( event ) { //console.log( 'handleMouseDownPan' ); panStart.set( event.clientX, event.clientY ); } function handleMouseMoveRotate( event ) { //console.log( 'handleMouseMoveRotate' ); rotateEnd.set( event.clientX, event.clientY ); rotateDelta.subVectors( rotateEnd, rotateStart ); var element = scope.domElement === document ? scope.domElement.body : scope.domElement; // rotating across whole screen goes 360 degrees around // 这是在转一个椭圆,将deltax映射成为弧度,类似于用周长代表角度 rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); // rotating up and down along whole screen attempts to go 360, but limited to 180 rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); rotateStart.copy( rotateEnd ); scope.update(); } function handleMouseMoveDolly( event ) { //console.log( 'handleMouseMoveDolly' ); dollyEnd.set( event.clientX, event.clientY ); dollyDelta.subVectors( dollyEnd, dollyStart ); if ( dollyDelta.y > 0 ) { // 竟然只是简单的用y来控制每次的缩放差 dollyIn( getZoomScale() ); } else if ( dollyDelta.y < 0 ) { dollyOut( getZoomScale() ); } dollyStart.copy( dollyEnd ); scope.update(); } function handleMouseMovePan( event ) { //console.log( 'handleMouseMovePan' ); panEnd.set( event.clientX, event.clientY ); panDelta.subVectors( panEnd, panStart ); pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); scope.update(); } function handleMouseUp( event ) { // console.log( 'handleMouseUp' ); } function handleMouseWheel( event ) { // console.log( 'handleMouseWheel' ); if ( event.deltaY < 0 ) { dollyOut( getZoomScale() ); } else if ( event.deltaY > 0 ) { dollyIn( getZoomScale() ); } scope.update(); } function handleKeyDown( event ) { //console.log( 'handleKeyDown' ); switch ( event.keyCode ) { case scope.keys.UP: pan( 0, scope.keyPanSpeed ); scope.update(); break; case scope.keys.BOTTOM: pan( 0, - scope.keyPanSpeed ); scope.update(); break; case scope.keys.LEFT: pan( scope.keyPanSpeed, 0 ); scope.update(); break; case scope.keys.RIGHT: pan( - scope.keyPanSpeed, 0 ); scope.update(); break; } } function handleTouchStartRotate( event ) { //console.log( 'handleTouchStartRotate' ); // rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; touchThetaRotateStart.set(dx, dy); touchPhiFingerStart1.set(event.touches[0].pageX, event.touches[0].pageY); touchPhiFingerStart2.set(event.touches[1].pageX, event.touches[1].pageY); } function handleTouchStartDolly( event ) { //console.log( 'handleTouchStartDolly' ); var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; var distance = Math.sqrt( dx * dx + dy * dy ); dollyStart.set( 0, distance );// touch情况下只是用两指之间距离来控制delta scale } function handleTouchStartPan( event ) { //console.log( 'handleTouchStartPan' ); panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); } function handleTouchMoveRotate( event ) { var dx = event.touches[0].pageX - event.touches[1].pageX; var dy = event.touches[0].pageY - event.touches[1].pageY; touchThetaRotateEnd.set(dx, dy); var element = scope.domElement === document ? scope.domElement.body : scope.domElement; touchPhiFingerEnd1.set(event.touches[0].pageX, event.touches[0].pageY); touchPhiFingerEnd2.set(event.touches[1].pageX, event.touches[1].pageY); let deltaPhi = Math.PI * (touchPhiFingerEnd1.y - touchPhiFingerStart1.y + touchPhiFingerEnd2.y - touchPhiFingerStart2.y) / 2 / element.clientHeight; // console.log(deltaPhi); if (Math.abs(deltaPhi) > 0.01) { rotateUp(deltaPhi); } var costheta = touchThetaRotateEnd.dot(touchThetaRotateStart) / (touchThetaRotateStart.length() * touchThetaRotateEnd.length()); // console.log('costheta', costheta); var cross = touchThetaRotateStart.x * touchThetaRotateEnd.y - touchThetaRotateEnd.x * touchThetaRotateStart.y; // console.log('cross', cross); var theta = Math.acos(costheta); // console.log('theta', theta); // 防止浮点数精度问题引发问题,比如Math.acos(1.000000000000002); theta = typeof theta === 'number' && isFinite(theta) ? theta : 0; if (Math.abs(theta) > 0.01) { sphericalDelta.theta = cross >= 0 ? theta : -theta; } touchThetaRotateStart.copy(touchThetaRotateEnd); touchPhiFingerStart1.copy(touchPhiFingerEnd1); touchPhiFingerStart2.copy(touchPhiFingerEnd2); scope.update(); } function handleTouchMoveDolly( event ) { // console.log( 'handleTouchMoveDolly' ); var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; var distance = Math.sqrt( dx * dx + dy * dy ); dollyEnd.set( 0, distance ); dollyDelta.subVectors( dollyEnd, dollyStart ); // console.log(dollyDelta, dollyStart); // console.log('handleTouchMoveDolly: ', (1 - dollyDelta.y / dollyStart.y)); if (scope.object instanceof PerspectiveCamera) { if ( dollyDelta.y > 0 ) { dollyOut( 1 - dollyDelta.y / dollyStart.y ); } else if ( dollyDelta.y < 0 ) { dollyIn( 1 - dollyDelta.y / dollyStart.y ); } } else if (scope.object instanceof OrthographicCamera) { if ( dollyDelta.y > 0 ) { dollyOut(scope.object.zoom * (1 + dollyDelta.y / dollyStart.y)); } else if ( dollyDelta.y < 0 ) { dollyIn(scope.object.zoom * ( 1 + dollyDelta.y / dollyStart.y )); } } dollyStart.copy( dollyEnd ); scope.update(); } function handleTouchMovePan( event ) { // console.log( 'handleTouchMovePan' ); panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); panDelta.subVectors( panEnd, panStart ); pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); scope.update(); } function handleTouchEnd( event ) { //console.log( 'handleTouchEnd' ); } // // event handlers - FSM: listen for events and reset state // function onMouseDown( event ) { if ( scope.enabled === false ) return; event.preventDefault(); switch ( event.button ) { case scope.mouseButtons.ORBIT: if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; break; case scope.mouseButtons.ZOOM: if ( scope.enableZoom === false ) return; handleMouseDownDolly( event ); state = STATE.DOLLY; break; case scope.mouseButtons.PAN: if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; break; } if ( state !== STATE.NONE ) { document.addEventListener( 'mousemove', onMouseMove, false ); document.addEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( startEvent ); } } function onMouseMove( event ) { if ( scope.enabled === false ) return; event.preventDefault(); switch ( state ) { case STATE.ROTATE: if ( scope.enableRotate === false ) return; handleMouseMoveRotate( event ); break; case STATE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseMoveDolly( event ); break; case STATE.PAN: if ( scope.enablePan === false ) return; handleMouseMovePan( event ); break; } } function onMouseUp( event ) { if ( scope.enabled === false ) return; handleMouseUp( event ); document.removeEventListener( 'mousemove', onMouseMove, false ); document.removeEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( endEvent ); state = STATE.NONE; } function onMouseWheel( event ) { if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; event.preventDefault(); event.stopPropagation(); handleMouseWheel( event ); scope.dispatchEvent( startEvent ); // not sure why these are here... scope.dispatchEvent( endEvent ); } function onKeyDown( event ) { if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; handleKeyDown( event ); } function onTouchStart( event ) { if ( scope.enabled === false ) return; console.log('OrbitControls+_start'); switch ( event.touches.length ) { // case 1: // one-fingered touch: rotate // if ( scope.enableRotate === false ) return; // handleTouchStartRotate( event ); // state = STATE.TOUCH_ROTATE; // break; case 2: // two-fingered touch: dolly or rotate // if ( scope.enableZoom === false ) return; // handleTouchStartDolly( event ); // state = STATE.TOUCH_DOLLY; touchStartTime = Date.now(); if (scope.enableZoom) { handleTouchStartDolly( event ); } if (scope.enableRotate) { handleTouchStartRotate( event ); } state = STATE.NONE; break; case 1: // one-fingered touch: pan if ( scope.enablePan === false ) return; handleTouchStartPan( event ); state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } if ( state !== STATE.NONE ) { scope.dispatchEvent( startEvent ); } } function onTouchMove( event ) { // console.log('OrbitControls+_move'); if ( scope.enabled === false ) return; event.preventDefault(); event.stopPropagation(); switch ( event.touches.length ) { // case 1: // one-fingered touch: rotate // if ( scope.enableRotate === false ) return; // if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... // handleTouchMoveRotate( event ); // break; case 2: // two-fingered touch: dolly // if ( scope.enableZoom === false ) return; // if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... handleTouchMoveDolly( event ); handleTouchMoveRotate(event); // if (state === STATE.NONE) { // var now = Date.now(); // // 如果双指在50ms内进行了缩放操作则记录state为dolly // // 否则为Pan,在移动时无论是Pan还是dolly都会同时处理移动和缩放 // if (now - touchStartTime >= 200) { // // 记录移动距离 // var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; // var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; // var distance = Math.sqrt( dx * dx + dy * dy ); // dollyEnd.set( 0, distance ); // dollyDelta.subVectors( dollyEnd, dollyStart ); // let dollyLength = dollyDelta.length(); // touchThetaRotateEnd.set(dx, dy); // var costheta = touchThetaRotateEnd.dot(touchThetaRotateStart) / (touchThetaRotateStart.length() * touchThetaRotateEnd.length()); // var theta = Math.acos(costheta); // touchPhiFingerEnd1.set(event.touches[0].pageX, event.touches[0].pageY); // touchPhiFingerEnd2.set(event.touches[1].pageX, event.touches[1].pageY); // if (Math.abs(dollyLength) >= 10) { // state = STATE.TOUCH_DOLLY; // 既可以缩放又可以theta旋转 // } else if (theta > 5 * Math.PI / 180) { // state = STATE.TOUCH_ROTATE_THETA; // } else if (Math.abs(touchPhiFingerEnd1.x - touchPhiFingerStart1.x) < 5 && // Math.abs(touchPhiFingerEnd2.x - touchPhiFingerStart2.x) < 5 && // (Math.abs(touchPhiFingerEnd1.y - touchPhiFingerStart1.y) > 5) && // (Math.abs(touchPhiFingerEnd2.y - touchPhiFingerStart2.y) > 5)) { // state = STATE.TOUCH_ROTATE_PHI; // } // dollyStart.copy(dollyEnd); // dollyDelta.subVectors(dollyEnd, dollyStart); // touchThetaRotateStart.copy(touchThetaRotateEnd); // touchPhiFingerStart1.copy(touchPhiFingerEnd1); // touchPhiFingerStart2.copy(touchPhiFingerEnd2); // } else if (now - touchStartTime >= 400) { // state = STATE.TOUCH_DOLLY; // } else { // state = STATE.NONE; // } // } else if (state === STATE.TOUCH_DOLLY && scope.enableZoom) { // handleTouchMoveDolly(event); // handleTouchMoveRotate(event); // } else if ((state === STATE.TOUCH_ROTATE || // state === STATE.TOUCH_ROTATE_THETA || state === STATE.TOUCH_ROTATE_PHI) && // scope.enableRotate) { // if (state === STATE.TOUCH_ROTATE_THETA) { // handleTouchMoveDolly(event); // } // handleTouchMoveRotate(event); // } break; case 1: // one-fingered touch: pan if ( scope.enablePan === false ) return; // if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... if (state !== STATE.TOUCH_PAN) {// 处理突然由两指变成单指情况 handleTouchStartPan( event ); } else { handleTouchMovePan( event ); } state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } } function onTouchEnd( event ) { if ( scope.enabled === false ) return; handleTouchEnd( event ); scope.dispatchEvent( endEvent ); state = STATE.NONE; } function onContextMenu( event ) { event.preventDefault(); } if (INTOUCHDEVICE) { scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); scope.domElement.addEventListener( 'touchmove', onTouchMove , false ); } else { scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); window.addEventListener( 'keydown', onKeyDown, false ); } // force an update at start this.update(); this.pan = pan; this.rotate = function(theta, phi) { if (typeof theta === 'number' && isFinite(theta)) { sphericalDelta.theta -= theta; } if (typeof phi === 'number' && isFinite(phi)) { sphericalDelta.phi -= phi; } scope.update(); }; this.getMinDistanceByZoom = function() { return getDistanceByPixelMeterRatio(zoomRange[zoomRange.length - 1]); }; this.getMaxDistanceByZoom = function() { return getDistanceByPixelMeterRatio(zoomRange[0]); }; this.getDistanceByZoom = function(zoom) { // zoom代表数据侧zoom:1-7 return getDistanceByPixelMeterRatio(zoomRange[zoom - 1]); }; this.getZoomByDistance = function(distance) { var meterPerPixel = getPixelMeterRatioByDistance(distance); var mapZoom = getZoomRangeByRatio(meterPerPixel); return mapZoom; }; }; OrbitControls = OrbitControls; OrbitControls.prototype = Object.create( EventDispatcher.prototype ); OrbitControls.prototype.constructor = OrbitControls; Object.defineProperties( OrbitControls.prototype, { center: { get: function () { console.warn( 'OrbitControls: .center has been renamed to .target' ); return this.target; } }, // backward compatibility noZoom: { get: function () { console.warn( 'OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); return ! this.enableZoom; }, set: function ( value ) { console.warn( 'OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); this.enableZoom = ! value; } }, noRotate: { get: function () { console.warn( 'OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); return ! this.enableRotate; }, set: function ( value ) { console.warn( 'OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); this.enableRotate = ! value; } }, noPan: { get: function () { console.warn( 'OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); return ! this.enablePan; }, set: function ( value ) { console.warn( 'OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); this.enablePan = ! value; } }, noKeys: { get: function () { console.warn( 'OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); return ! this.enableKeys; }, set: function ( value ) { console.warn( 'OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); this.enableKeys = ! value; } }, staticMoving: { get: function () { console.warn( 'OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); return ! this.enableDamping; }, set: function ( value ) { console.warn( 'OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); this.enableDamping = ! value; } }, dynamicDampingFactor: { get: function () { console.warn( 'OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); return this.dampingFactor; }, set: function ( value ) { console.warn( 'OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); this.dampingFactor = value; } } } ); } function handleTouchMoveRotate( event ) { var dx = event.touches[0].pageX - event.touches[1].pageX; var dy = event.touches[0].pageY - event.touches[1].pageY; touchThetaRotateEnd.set(dx, dy); var element = scope.domElement === document ? scope.domElement.body : scope.domElement; touchPhiFingerEnd1.set(event.touches[0].pageX, event.touches[0].pageY); touchPhiFingerEnd2.set(event.touches[1].pageX, event.touches[1].pageY); let deltaPhi = Math.PI * (touchPhiFingerEnd1.y - touchPhiFingerStart1.y + touchPhiFingerEnd2.y - touchPhiFingerStart2.y) / 2 / element.clientHeight; // console.log(deltaPhi); if (Math.abs(deltaPhi) > 0.01) { rotateUp(deltaPhi); } var costheta = touchThetaRotateEnd.dot(touchThetaRotateStart) / (touchThetaRotateStart.length() * touchThetaRotateEnd.length()); // console.log('costheta', costheta); var cross = touchThetaRotateStart.x * touchThetaRotateEnd.y - touchThetaRotateEnd.x * touchThetaRotateStart.y; // console.log('cross', cross); var theta = Math.acos(costheta); // console.log('theta', theta); // 防止浮点数精度问题引发问题,比如Math.acos(1.000000000000002); theta = typeof theta === 'number' && isFinite(theta) ? theta : 0; if (Math.abs(theta) > 0.01) { sphericalDelta.theta = cross >= 0 ? theta : -theta; } touchThetaRotateStart.copy(touchThetaRotateEnd); touchPhiFingerStart1.copy(touchPhiFingerEnd1); touchPhiFingerStart2.copy(touchPhiFingerEnd2); scope.update(); } function handleTouchMoveDolly( event ) { // console.log( 'handleTouchMoveDolly' ); var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; var distance = Math.sqrt( dx * dx + dy * dy ); dollyEnd.set( 0, distance ); dollyDelta.subVectors( dollyEnd, dollyStart ); // console.log(dollyDelta, dollyStart); // console.log('handleTouchMoveDolly: ', (1 - dollyDelta.y / dollyStart.y)); if (scope.object instanceof PerspectiveCamera) { if ( dollyDelta.y > 0 ) { dollyOut( 1 - dollyDelta.y / dollyStart.y ); } else if ( dollyDelta.y < 0 ) { dollyIn( 1 - dollyDelta.y / dollyStart.y ); } } else if (scope.object instanceof OrthographicCamera) { if ( dollyDelta.y > 0 ) { dollyOut(scope.object.zoom * (1 + dollyDelta.y / dollyStart.y)); } else if ( dollyDelta.y < 0 ) { dollyIn(scope.object.zoom * ( 1 + dollyDelta.y / dollyStart.y )); } } dollyStart.copy( dollyEnd ); scope.update(); } function handleTouchMovePan( event ) { // console.log( 'handleTouchMovePan' ); panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); panDelta.subVectors( panEnd, panStart ); pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); scope.update(); } function handleTouchEnd( event ) { //console.log( 'handleTouchEnd' ); } // // event handlers - FSM: listen for events and reset state // function onMouseDown( event ) { if ( scope.enabled === false ) return; event.preventDefault(); switch ( event.button ) { case scope.mouseButtons.ORBIT: if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; break; case scope.mouseButtons.ZOOM: if ( scope.enableZoom === false ) return; handleMouseDownDolly( event ); state = STATE.DOLLY; break; case scope.mouseButtons.PAN: if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; break; } if ( state !== STATE.NONE ) { document.addEventListener( 'mousemove', onMouseMove, false ); document.addEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( startEvent ); } } function onMouseMove( event ) { if ( scope.enabled === false ) return; event.preventDefault(); switch ( state ) { case STATE.ROTATE: if ( scope.enableRotate === false ) return; handleMouseMoveRotate( event ); break; case STATE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseMoveDolly( event ); break; case STATE.PAN: if ( scope.enablePan === false ) return; handleMouseMovePan( event ); break; } } function onMouseUp( event ) { if ( scope.enabled === false ) return; handleMouseUp( event ); document.removeEventListener( 'mousemove', onMouseMove, false ); document.removeEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( endEvent ); state = STATE.NONE; } function onMouseWheel( event ) { if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; event.preventDefault(); event.stopPropagation(); handleMouseWheel( event ); scope.dispatchEvent( startEvent ); // not sure why these are here... scope.dispatchEvent( endEvent ); } function onKeyDown( event ) { if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; handleKeyDown( event ); } function onTouchStart( event ) { if ( scope.enabled === false ) return; console.log('OrbitControls+_start'); switch ( event.touches.length ) { // case 1: // one-fingered touch: rotate // if ( scope.enableRotate === false ) return; // handleTouchStartRotate( event ); // state = STATE.TOUCH_ROTATE; // break; case 2: // two-fingered touch: dolly or rotate // if ( scope.enableZoom === false ) return; // handleTouchStartDolly( event ); // state = STATE.TOUCH_DOLLY; touchStartTime = Date.now(); if (scope.enableZoom) { handleTouchStartDolly( event ); } if (scope.enableRotate) { handleTouchStartRotate( event ); } state = STATE.NONE; break; case 1: // one-fingered touch: pan if ( scope.enablePan === false ) return; handleTouchStartPan( event ); state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } if ( state !== STATE.NONE ) { scope.dispatchEvent( startEvent ); } } function onTouchMove( event ) { // console.log('OrbitControls+_move'); if ( scope.enabled === false ) return; event.preventDefault(); event.stopPropagation(); switch ( event.touches.length ) { // case 1: // one-fingered touch: rotate // if ( scope.enableRotate === false ) return; // if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... // handleTouchMoveRotate( event ); // break; case 2: // two-fingered touch: dolly // if ( scope.enableZoom === false ) return; // if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... handleTouchMoveDolly( event ); handleTouchMoveRotate(event); // if (state === STATE.NONE) { // var now = Date.now(); // // 如果双指在50ms内进行了缩放操作则记录state为dolly // // 否则为Pan,在移动时无论是Pan还是dolly都会同时处理移动和缩放 // if (now - touchStartTime >= 200) { // // 记录移动距离 // var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; // var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; // var distance = Math.sqrt( dx * dx + dy * dy ); // dollyEnd.set( 0, distance ); // dollyDelta.subVectors( dollyEnd, dollyStart ); // let dollyLength = dollyDelta.length(); // touchThetaRotateEnd.set(dx, dy); // var costheta = touchThetaRotateEnd.dot(touchThetaRotateStart) / (touchThetaRotateStart.length() * touchThetaRotateEnd.length()); // var theta = Math.acos(costheta); // touchPhiFingerEnd1.set(event.touches[0].pageX, event.touches[0].pageY); // touchPhiFingerEnd2.set(event.touches[1].pageX, event.touches[1].pageY); // if (Math.abs(dollyLength) >= 10) { // state = STATE.TOUCH_DOLLY; // 既可以缩放又可以theta旋转 // } else if (theta > 5 * Math.PI / 180) { // state = STATE.TOUCH_ROTATE_THETA; // } else if (Math.abs(touchPhiFingerEnd1.x - touchPhiFingerStart1.x) < 5 && // Math.abs(touchPhiFingerEnd2.x - touchPhiFingerStart2.x) < 5 && // (Math.abs(touchPhiFingerEnd1.y - touchPhiFingerStart1.y) > 5) && // (Math.abs(touchPhiFingerEnd2.y - touchPhiFingerStart2.y) > 5)) { // state = STATE.TOUCH_ROTATE_PHI; // } // dollyStart.copy(dollyEnd); // dollyDelta.subVectors(dollyEnd, dollyStart); // touchThetaRotateStart.copy(touchThetaRotateEnd); // touchPhiFingerStart1.copy(touchPhiFingerEnd1); // touchPhiFingerStart2.copy(touchPhiFingerEnd2); // } else if (now - touchStartTime >= 400) { // state = STATE.TOUCH_DOLLY; // } else { // state = STATE.NONE; // } // } else if (state === STATE.TOUCH_DOLLY && scope.enableZoom) { // handleTouchMoveDolly(event); // handleTouchMoveRotate(event); // } else if ((state === STATE.TOUCH_ROTATE || // state === STATE.TOUCH_ROTATE_THETA || state === STATE.TOUCH_ROTATE_PHI) && // scope.enableRotate) { // if (state === STATE.TOUCH_ROTATE_THETA) { // handleTouchMoveDolly(event); // } // handleTouchMoveRotate(event); // } break; case 1: // one-fingered touch: pan if ( scope.enablePan === false ) return; // if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... if (state !== STATE.TOUCH_PAN) {// 处理突然由两指变成单指情况 handleTouchStartPan( event ); } else { handleTouchMovePan( event ); } state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } } function onTouchEnd( event ) { if ( scope.enabled === false ) return; handleTouchEnd( event ); scope.dispatchEvent( endEvent ); state = STATE.NONE; } function onContextMenu( event ) { event.preventDefault(); } if (INTOUCHDEVICE) { scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); scope.domElement.addEventListener( 'touchmove', onTouchMove , false ); } else { scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); window.addEventListener( 'keydown', onKeyDown, false ); } // force an update at start this.update(); this.pan = pan; this.rotate = function(theta, phi) { if (typeof theta === 'number' && isFinite(theta)) { sphericalDelta.theta -= theta; } if (typeof phi === 'number' && isFinite(phi)) { sphericalDelta.phi -= phi; } scope.update(); }; this.getMinDistanceByZoom = function() { return getDistanceByPixelMeterRatio(zoomRange[zoomRange.length - 1]); }; this.getMaxDistanceByZoom = function() { return getDistanceByPixelMeterRatio(zoomRange[0]); } }; OrbitControls = OrbitControls; OrbitControls.prototype = Object.create( EventDispatcher.prototype ); OrbitControls.prototype.constructor = OrbitControls; Object.defineProperties( OrbitControls.prototype, { center: { get: function () { console.warn( 'OrbitControls: .center has been renamed to .target' ); return this.target; } }, // backward compatibility noZoom: { get: function () { console.warn( 'OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); return ! this.enableZoom; }, set: function ( value ) { console.warn( 'OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); this.enableZoom = ! value; } }, noRotate: { get: function () { console.warn( 'OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); return ! this.enableRotate; }, set: function ( value ) { console.warn( 'OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); this.enableRotate = ! value; } }, noPan: { get: function () { console.warn( 'OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); return ! this.enablePan; }, set: function ( value ) { console.warn( 'OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); this.enablePan = ! value; } }, noKeys: { get: function () { console.warn( 'OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); return ! this.enableKeys; }, set: function ( value ) { console.warn( 'OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); this.enableKeys = ! value; } }, staticMoving: { get: function () { console.warn( 'OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); return ! this.enableDamping; }, set: function ( value ) { console.warn( 'OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); this.enableDamping = ! value; } }, dynamicDampingFactor: { get: function () { console.warn( 'OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); return this.dampingFactor; }, set: function ( value ) { console.warn( 'OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); this.dampingFactor = value; } } } );
您可以考虑给树发个小额微信红包以资鼓励