function PanoramaControls(camera, domElement) { // fyz 相机放大缩小 this.activationThreshold = 1.1; this.scrollZoomSpeed = 0.001; this.scrollZoomSta = true; this.zoomMin = 0.7; this.zoomMax = 1.5; this.baseFov = 70; this.camera = camera; this.camera.fov = this.baseFov; this.domElement = domElement; this.camera.controls = this; this.enabled = true; this.target = new THREE.Vector3(0, 0, 0); this.lookVector = new THREE.Vector3; this.aimFrom = this.camera.position; this.lat = 0; this.latMin = -60//-40; this.latMax = 60//40; this.lon = 0; this.phi = 0; this.theta = 0; this.lookSpeed = 0.05; this.rotationAcc = new THREE.Vector2; this.rotationSpeed = new THREE.Vector2; this.rotationHistory = []; this.rotationDifference = new THREE.Vector2; this.pointerDragOn = !1; this.pointer = new THREE.Vector3(0, 0, -1); this.pointerDragStart = new THREE.Vector3(0, 0, -1); this._wheel = 0; this.zoomLevel = 1; this.translationWorldDelta = new THREE.Vector3 this.bindEvents() } PanoramaControls.prototype.bindEvents = function() { window.addEventListener("mousemove", this.onMouseMove.bind(this)); this.domElement.addEventListener("mousedown", this.onMouseDown.bind(this)); window.addEventListener("mouseup", this.onMouseUp.bind(this)); this.domElement.addEventListener("mouseover", (event) => this.pointerDragOn && 0 === event.which && this.onMouseUp(event)); this.domElement.addEventListener("touchstart", this.onTouchStart.bind(this)); this.domElement.addEventListener("touchmove", this.onTouchMove.bind(this)); this.domElement.addEventListener("touchend", this.onTouchEnd.bind(this)); this.domElement.addEventListener("wheel", this.onMouseWheel.bind(this)); // fyz wheel事件代替mousewheel事件 this.domElement.addEventListener("DOMMouseScroll", this.onMouseWheel.bind(this)); this.domElement.addEventListener("contextmenu", (event) => event.preventDefault()); //document.addEventListener("keydown", this.onKeyDown.bind(this)); //document.addEventListener("keyup", this.onKeyUp.bind(this)); } PanoramaControls.prototype.lookAt = function(point) { var directionNegative = this.camera.position.clone().sub(point), theta = Math.atan(directionNegative.z / directionNegative.x); theta += directionNegative.x < 0 ? Math.PI : 0; theta += directionNegative.x > 0 && directionNegative.z < 0 ? 2 * Math.PI : 0; this.lon = THREE.Math.radToDeg(theta) + 180; let projectorR = Math.sqrt(directionNegative.x * directionNegative.x + directionNegative.z * directionNegative.z), phi = Math.atan(directionNegative.y / projectorR); this.lat = -THREE.Math.radToDeg(phi) } PanoramaControls.prototype.startRotationFrom = function(screenX, screenY) { this.updatePointer(screenX, screenY); this.pointerDragOn = true; this.pointerDragStart.copy(this.pointer); //TODO //this.pointerDragStartIntersect = this.player.getMouseIntersect(this.pointer.clone(), [this.scene.skybox]).point; this.rotationHistory = []; this.rotationSpeed.set(0, 0); } PanoramaControls.prototype.onTouchStart = function(event) { if (this.enabled) { event.preventDefault(); event.stopPropagation(); this.startRotationFrom(event.changedTouches[0].clientX, event.changedTouches[0].clientY); } } PanoramaControls.prototype.onMouseDown = function(event) { if (this.enabled) { event.preventDefault(); event.stopPropagation() switch (event.button) { case 0: this.startRotationFrom(event.clientX, event.clientY); break; case 2: } } } PanoramaControls.prototype.updateRotation = function( ) { if (this.enabled && this.pointerDragOn) { var pointerDragStart3D = new THREE.Vector3(this.pointerDragStart.x, this.pointerDragStart.y, 1).unproject(this.camera); var pointer3D = new THREE.Vector3(this.pointer.x, this.pointer.y, 1).unproject(this.camera); //两交互点分别到原点的长度 var pointerDragStart3DLength = Math.sqrt(pointerDragStart3D.x * pointerDragStart3D.x + pointerDragStart3D.z * pointerDragStart3D.z); var pointer3DLength = Math.sqrt(pointer3D.x * pointer3D.x + pointer3D.z * pointer3D.z); //通过Math.atan2计算在XY面上与X轴的夹角弧度。 //注:因为 z = -1,所以两者到原点的长度近似为x分量(数值的大小也不需要绝对对应) var anglePointerDragStart3DToX = Math.atan2(pointerDragStart3D.y, pointerDragStart3DLength); //近似为 anglePointerDragStart3DToX = Math.atan2( pointerDragStart3D.y, pointerDragStart3D.x ) var anglePointer3DToX = Math.atan2(pointer3D.y, pointer3DLength); //近似为 anglePointer3DToX = Math.atan2( pointer3D.y, pointer3D.x ) //算出两者角度差,作为竖直方向角度差值(rotationDifference.y) this.rotationDifference.y = THREE.Math.radToDeg(anglePointerDragStart3DToX - anglePointer3DToX); //y分量清零,原向量等价于在XZ轴上的投影向量 pointerDragStart3D.y = 0; pointer3D.y = 0; //归一化(/length),求两者夹角作为 //判断方向,最后记为水平方向角度差值(rotationDifference.x) var anglePointerDragStart3DToPointer3D = Math.acos(pointerDragStart3D.dot(pointer3D) / pointerDragStart3D.length() / pointer3D.length()); if (!isNaN(anglePointerDragStart3DToPointer3D)) { this.rotationDifference.x = THREE.Math.radToDeg(anglePointerDragStart3DToPointer3D); if (this.pointerDragStart.x < this.pointer.x) { this.rotationDifference.x *= -1; } } //更新pointerDragStart记录当前帧坐标,用于下一帧求帧差值 this.pointerDragStart.copy(this.pointer); } } PanoramaControls.prototype.onMouseMove = function(event) { this.updatePointer(event.clientX, event.clientY); } PanoramaControls.prototype.onTouchMove = function(event) { this.updatePointer(event.changedTouches[0].clientX, event.changedTouches[0].clientY) } PanoramaControls.prototype.updatePointer = function(screenX, screenY) { this.pointer.x = screenX / this.domElement.clientWidth * 2 - 1; // 屏幕坐标换算相对于canvas的父级 this.pointer.y = 2 * -(screenY / this.domElement.clientHeight) + 1; } PanoramaControls.prototype.endRotation = function() { this.pointerDragOn = false; try{ var rotationHistoryAverage = averageVectors(this.rotationHistory); }catch(e){ console.error(e) } this.rotationSpeed.set(rotationHistoryAverage.x * 30, rotationHistoryAverage.y * 30); } PanoramaControls.prototype.onTouchEnd = function(event) { if (this.enabled) { event.preventDefault(); event.stopPropagation(); this.endRotation() } } PanoramaControls.prototype.onMouseUp = function(event) { if (this.enabled) { event.preventDefault(); event.stopPropagation(); this.endRotation() } } PanoramaControls.prototype.update = function(deltaTime) { if (this.enabled) { this.updateRotation(); for (this.rotationHistory.push(this.rotationDifference.clone()); this.rotationHistory.length > 5;) { this.rotationHistory.shift(); } this.lon += this.rotationDifference.x; this.lat += this.rotationDifference.y; this.rotationDifference.set(0, 0); this.rotationSpeed.x = this.rotationSpeed.x * (1 - 0.05) + this.rotationAcc.x * 4.5; this.rotationSpeed.y = this.rotationSpeed.y * (1 - 0.05) + this.rotationAcc.y * 4.5; this.lon += this.rotationSpeed.x * deltaTime; this.lat += this.rotationSpeed.y * deltaTime; this.lat = Math.max(this.latMin, Math.min(this.latMax, this.lat)); this.phi = THREE.Math.degToRad(90 - this.lat); this.theta = THREE.Math.degToRad(this.lon); this.lookVector.x = Math.sin(this.phi) * Math.cos(this.theta); this.lookVector.y = Math.cos(this.phi); this.lookVector.z = Math.sin(this.phi) * Math.sin(this.theta); this.camera.position.add(this.translationWorldDelta) this.translationWorldDelta.multiplyScalar(0.9); this.target.copy(this.lookVector).add(this.aimFrom); this.camera.lookAt(this.target) } } PanoramaControls.prototype.onMouseWheel = function(event) { /* if (this.enabled) { // let z = void 0 !== event['wheelDelta'] ? event['wheelDelta'] : 0 !== event.detail && -event.detail; // this.flyDirection(new THREE.Vector3(0, 0, -z).normalize()); this._wheel = Math.floor(event['wheelDeltaY'] / 120); this._wheel = Math.abs(this._wheel) > 0.1 ? Math.sign(this._wheel) : 0; if (this._wheel !== 0 && this.scrollZoomSta) { this._wheel > 0 ? this._wheel = 1 + this.scrollZoomSpeed : this._wheel = 1 - this.scrollZoomSpeed; let curZoomLevel = this._wheel * this.zoomLevel; this.zoomTo(curZoomLevel); } } */ let delta if (event.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9 delta = event.wheelDelta; } else if (event.detail !== undefined) { // Firefox delta = -event.detail; } if(delta != void 0){//滚轮缩放 if(delta == 0)return //mac let direction = new THREE.Vector3(0,0,-1).applyQuaternion(this.camera.quaternion) let moveSpeed = 0.03 if(delta < 0) moveSpeed *=-1 this.translationWorldDelta.add(direction.multiplyScalar(moveSpeed)) } } PanoramaControls.prototype.zoomTo = function(curZoomLevel) { curZoomLevel < this.zoomMin && (curZoomLevel = this.zoomMin); curZoomLevel > this.zoomMax && (curZoomLevel = this.zoomMax); this.zoomLevel = curZoomLevel; this.camera.fov = this.baseFov*(1/this.zoomLevel); this.camera.updateProjectionMatrix(); } /* reset() { this.stop() } stop() { this.rotationAcc.set(0, 0); this.rotationSpeed.set(0, 0); } */ //-------------copyFromPlayer PanoramaControls.prototype.handleControlScroll = function(e) { e > 0 ? e = 1 + this.scrollZoomSpeed : e < 0 && (e = 1 - this.scrollZoomSpeed); 0 !== e && this.zoomBy(e) } PanoramaControls.prototype.zoomBy = function(e) { this.zoomTo(this.zoomLevel * e); } function averageVectors(e, t) { var i = new THREE.Vector3(); if (0 === e.length) return i; for (var r = 0, o = 0; o < e.length; o++) { var a = t ? e[o][t] : e[o]; i.add(a), r++; } return i.divideScalar(r); }