UNPKG

react-xrplayer

Version:

An excellent xr player for react

421 lines (378 loc) 14.9 kB
/** * 在球体中的全景视角控制 * Three.js自带的Controls控制效果不理想,所以自己实现控制器 */ import * as THREE from 'three'; import DeviceOrientationControls from './DeviceOrientationControls'; class InnerViewControls { constructor(camera) { this.camera = camera; this.isConnected = true; this.isUserInteracting = false; // 标记用户是否正在交互中 this.isPointerInteracting = false; // 鼠标完全控制模式 this.onMouseDownMouseX = 0; // 鼠标点击的初始坐标x this.onMouseDownMouseY = 0; // 鼠标点击的初始坐标y this.lon = 0; // 经度 this.onMouseDownLon = 0; this.lat = 0; // 纬度 this.onMouseDownLat = 0; this.phi = 0; this.theta = 0; this.distance = 10; this.onPointerDownPointerX = 0; this.onPointerDownPointerY = 0; this.onPointerDownLon = 0; this.onPointerDownLat = 0; // 视野自动旋转 this.enableAutoRotate = false; // 是否自动旋转 this.autoRotateSpeed = 1.0; // 自动旋转速度,对外 this.autoRotateAngle = // 内部速度 this.getRotateAngle(); this.autoRotateDirection = 'left'; // 自动旋转方向,left、right、up、down // 视野范围 this.fovTopEdge = 90; this.fovDownEdge = -90; this.fovLeftEdge = -90; this.fovRightEdge = 270; this.enableFovChange = true; // 是否允许用户调整fov大小 //键盘交互控件 this.onKeyLeft = false; this.onKeyRight = false; this.onKeyUp = false; this.onKeyDown = false; this.onKeyShift = false; this.initControlsListener(); //重力交互控件 this.orientationControls = new DeviceOrientationControls(this.camera); this.orientationEnable = false; } /******************************对外接口************************* */ // 相机控制器开关 connect = () => { this.isConnected = true; this.initSphericalData(); }; disConnect = () => { this.isConnected = false; }; // 方向传感器开关 getEnableOrientationControls = () => { return this.orientationEnable; } enableOrientationControls = () => { if (this.orientationEnable === false) { this.orientationControls.connect(THREE.MathUtils.degToRad(this.lon - 90)); this.orientationEnable = true; } } disableOrientationControls = () => { if (this.orientationEnable === true) { this.orientationControls.disConnect(); this.orientationEnable = false; this.initSphericalData(); } } // 自动旋转接口 getEnableAutoRotate = () => { return this.enableAutoRotate; } setEnableAutoRotate = (enable) => { this.enableAutoRotate = enable; } setAutoRotateSpeed = (speed) => { this.autoRotateSpeed = speed; this.getRotateAngle(); } setAutoRotateDirection = (direction) => { this.autoRotateDirection = direction; } // FOV范围接口 setFovVerticalScope = (bottom, top) => { this.fovTopEdge = top; this.fovDownEdge = bottom; } getFovVerticalScope = () => { return { top: this.fovTopEdge, bottom: this.fovDownEdge } } setFovHorizontalScope = (left, right) => { this.fovLeftEdge = left; this.fovRightEdge = right; } getFovHorizontalScope = () => { return { left: this.fovLeftEdge, right: this.fovRightEdge } } // 相机当前位置接口 getCameraPosition = () => { return this.camera.position; } setCameraPosition = (x, y, z) => { this.camera.position.set(x, y, z); this.initSphericalData(); } // 相机FOV接口 getCameraFov = () => { return this.camera.fov; } setCameraFov = (fov) => { this.camera.fov = fov; this.camera.updateProjectionMatrix(); } enableChangeFov = (enable) => { this.enableFovChange = enable; } /*******************************内部方法实现******************************** */ // 将初始化的直角坐标转化为控制所需要的球体坐标数据 initSphericalData = () => { const spherical = new THREE.Spherical(); const position = this.camera.position; spherical.setFromCartesianCoords(position.x, position.y, position.z); this.phi = spherical.phi; this.theta = spherical.theta; this.distance = spherical.radius; this.lon = 90 - THREE.Math.radToDeg(this.theta); this.lat = 90 - THREE.Math.radToDeg(this.phi); return { lat: this.lat, lon: this.lon }; }; initControlsListener = () => { this.browser = window.navigator.userAgent.toLowerCase(); const container = document.getElementById('xr-container') if (this.browser.indexOf('mobile') > 0) { container.addEventListener('touchstart', this.onTouchstart, false); container.addEventListener('touchmove', this.onTouchmove, false); container.addEventListener('touchend', this.onTouchend, false); container.addEventListener('wheel', this.onDocumentMouseWheel, false); } else { container.addEventListener('mousedown', this.onDocumentMouseDown, false); container.addEventListener('mousemove', this.onDocumentMouseMove, false); container.addEventListener('mouseup', this.onDocumentMouseUp, false); container.addEventListener('wheel', this.onDocumentMouseWheel, false); //添加键盘监听 document.addEventListener('keydown', this.onDocumentKeyDown, false); document.addEventListener('keyup', this.onDocumentKeyUp, false); } }; update = () => { if (!this.isConnected) { this.camera.lookAt(this.camera.target); return; } this.updateCamera(); }; updateCamera = () => { if (this.orientationEnable === true) { this.camera.lookAt(this.camera.target); // 需要在updateposition之前,否则传感器效果异常 this.orientationControls.update(this.distance); return; } if (this.isUserInteracting) { var dLon = 2; var dLat = 2; if (this.onKeyShift) { dLon = 10; dLat = 10; } if (this.onKeyLeft) { this.lon -= dLon; } if (this.onKeyRight) { this.lon += dLon; } if (this.onKeyUp) { this.lat -= dLat; } if (this.onKeyDown) { this.lat += dLat; } this.updateCameraPosition(); } else if (this.enableAutoRotate) { this.autoRotate(); } this.camera.lookAt(this.camera.target); }; updateCameraPosition = () => { if (this.fovLeftEdge !== -90 || this.fovRightEdge !== 270) { // 对水平fov做了限制 this.lon = Math.max(this.fovLeftEdge, Math.min(this.fovRightEdge, this.lon)); } if (this.fovDownEdge !== -90 || this.fovTopEdge !== 90) {// 对垂直fov做了限制 this.lat = Math.max(this.fovDownEdge, Math.min(this.fovTopEdge, this.lat)); } this.phi = THREE.Math.degToRad(90 - this.lat); this.theta = THREE.Math.degToRad(this.lon); console.log('lat:', this.lat, ',lon:', this.lon, "phi:", this.phi, ",theta:", this.theta); console.log('fov', this.camera.fov); // 球坐标系与直角坐标系的转换 this.camera.position.x = this.distance * Math.sin(this.phi) * Math.cos(this.theta); this.camera.position.y = this.distance * Math.cos(this.phi); this.camera.position.z = this.distance * Math.sin(this.phi) * Math.sin(this.theta); } autoRotate = () => { this.updateCameraPosition(); // 旋转更新,等下次渲染 switch (this.autoRotateDirection) { case 'left': this.theta += this.autoRotateAngle; this.lon = THREE.Math.radToDeg(this.theta); break; case 'right': this.theta -= this.autoRotateAngle; this.lon = THREE.Math.radToDeg(this.theta); break; case 'up': this.phi += this.autoRotateAngle; this.lat = 90 - THREE.Math.radToDeg(this.phi); break; case 'down': this.phi -= this.autoRotateAngle; this.lat = 90 - THREE.Math.radToDeg(this.phi); break; default: break; } } getRotateAngle = () => { this.autoRotateAngle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; return this.autoRotateAngle; } onDocumentMouseDown = (event) => { if (!!document.pointerLockElement) { return; } event.preventDefault(); this.isUserInteracting = true; // 记录鼠标点击屏幕坐标 this.onPointerDownPointerX = event.clientX; this.onPointerDownPointerY = event.clientY; // 记录点击时候经纬度 this.onPointerDownLon = this.lon; // 经度 this.onPointerDownLat = this.lat; // 纬度 } onDocumentMouseMove = (event) => { if (this.isUserInteracting === true) { if (this.isPointerInteracting) { this.lon = event.movementX * 0.1 + this.lon; this.lat = event.movementY * 0.1 + this.lat; } else { // 在鼠标Down位置叠加偏移量 this.lon = (this.onPointerDownPointerX - event.clientX) * 0.1 + this.onPointerDownLon; this.lat = (this.onPointerDownPointerY - event.clientY) * 0.1 + this.onPointerDownLat; } // 用于立体场景音效 // mouseActionLocal([lon, lat]); } } onDocumentMouseUp = (event) => { this.isUserInteracting = false; } onTouchstart = (event) => { if (event.targetTouches.length === 1) { this.isUserInteracting = true; // 记录滑动开始的坐标 var touch = event.targetTouches[0]; this.onPointerDownPointerX = touch.pageX; // 把元素放在手指所在的位置 this.onPointerDownPointerY = touch.pageY; // 记录滑动开始时候的经纬度 this.onPointerDownLon = this.lon; // 经度 this.onPointerDownLat = this.lat; // 纬度 } } onTouchmove = (event) => { if (this.isUserInteracting === true) { var touch = event.targetTouches[0]; this.lon = (parseFloat(this.onPointerDownPointerX) - touch.pageX) * 0.2 + this.onPointerDownLon; this.lat = (parseFloat(this.onPointerDownPointerY - touch.pageY)) * 0.2 + this.onPointerDownLat; // 用于立体场景音效 // mouseActionLocal([lon, lat]); } } onTouchend = (event) => { this.isUserInteracting = false; } onDocumentMouseWheel = (event) => { //this.distance += event.deltaY * 0.5; if (!this.enableFovChange) return; let fov = this.camera.fov; fov += event.deltaY * 0.03; // 0.03 is a suitable value if (fov >= 160) { fov = 160; } else if (fov <= 10) { fov = 10 } this.camera.fov = fov; this.camera.updateProjectionMatrix(); return; } onDocumentKeyDown = (event) => { event.preventDefault(); var keyCode = event.keyCode || event.which || event.charCode; this.setInteractingIfKeys(keyCode, true); switch (keyCode) { case 65: /*a*/ case 37: /*left*/ this.onKeyLeft = true; this.onKeyRight = false; break; case 68: /*d*/ case 39: /*right*/ this.onKeyRight = true; this.onKeyLeft = false; break; case 87: /*w*/ case 38: /*up*/ this.onKeyUp = true; this.onKeyDown = false; break; case 83: /*s*/ case 40: /*down*/ this.onKeyDown = true; this.onKeyUp = false; break; case 16: /*Shift*/ this.onKeyShift = true; break; case 81: /*q*/ if (!!document.pointerLockElement) { document.exitPointerLock(); this.isUserInteracting = false; this.isPointerInteracting = false; } else { document.body.requestPointerLock(); this.isUserInteracting = true; this.isPointerInteracting = true; } break; default: break; } } setInteractingIfKeys = (keyCode, interacting) => { if (this.isPointerInteracting) { return } switch (keyCode) { case 65: /*a*/ case 37: /*left*/ case 68: /*d*/ case 39: /*right*/ case 87: /*w*/ case 38: /*up*/ case 83: /*s*/ case 40: /*down*/ case 16: /*Shift*/ this.isUserInteracting = interacting; break; default: break; } } onDocumentKeyUp = (event) => { var keyCode = event.keyCode || event.which || event.charCode; this.setInteractingIfKeys(keyCode, false) switch (keyCode) { case 65: /*a*/ case 37: /*left*/ this.onKeyLeft = false; break; case 68: /*d*/ case 39: /*right*/ this.onKeyRight = false; break; case 87: /*w*/ case 38: /*up*/ this.onKeyUp = false; break; case 83: /*s*/ case 40: /*down*/ this.onKeyDown = false; break; case 16: /*L_Shift*/ this.onKeyShift = false; break; default: break; } }; } export default InnerViewControls;