import * as THREE from "../../../libs/three.js/build/three.module.js"; import {transitions, easing, lerp} from '../../utils/transitions.js' import TileUtils from './tile/TileUtils' import { PanoRendererEvents, PanoramaEvents, PanoSizeClass} from '../../defines' import math from '../../utils/math' import {TextSprite} from '../../objects/TextSprite' var texLoader = new THREE.TextureLoader() const labelProp = { sizeInfo: {minSize : 200 , maxSize : 250, nearBound : 0.8, farBound : 10}, backgroundColor:{r: 255, g: 255, b: 255, a: 0.4 }, textColor:{r: 0, g: 0, b: 0, a: 1 }, borderRadius: 15, renderOrder:10 } let standardMarkerMat let getMarerMat = function(){ if(!standardMarkerMat) { let map = texLoader.load( Potree.resourcePath+'/textures/marker.png' ) map.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊 standardMarkerMat = new THREE.MeshBasicMaterial({opacity:0.7, side: THREE.DoubleSide , map ,transparent:true, depthTest:false})//总是被点云遮住,所以depthTest:false } return standardMarkerMat.clone() } //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial 或者直接把skybox的深度修改(拿到深度贴图后更如此) let planeGeo = new THREE.PlaneBufferGeometry(0.2,0.2); let sg = new THREE.SphereGeometry(0.1, 8, 8); let smHovered = new THREE.MeshBasicMaterial({/* side: THREE.BackSide, */color: 0xff0000}); let sm = new THREE.MeshBasicMaterial({/* side: THREE.BackSide */}); var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI/2 ); //使用的是刚好适合全景图的,给cube贴图需要转90° //var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), -Math.PI/2 ); //4dkk->navvis //var rot901 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), -Math.PI/2 ); //整张球幕图要旋转下 //rot90 = new THREE.Quaternion().multiplyQuaternions( rot901, rot90) var old = null; /* 转成四维看看的axis: var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)) 因为四维的要绕y转90 这里的quaternion.multiply(a); 先乘再换顺序 w : q.w, x:q.x , y:-q.z, z:q.y */ //暂时直接用4dkkconsole输出的数据 class Panorama extends THREE.EventDispatcher{ constructor(o, transform, images360){//file, time, longitude, latitude, altitude, course, pitch, roll super() this.id = o.id; this.images360 = images360 this.transform = transform this.visible = true //for viewer updateVisible this.enabled = true//是否可以走 this.originPosition = new THREE.Vector3().fromArray(o.dataset_location) this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location) this.originID = parseInt(o.file_id)//"file_id":"00022"对应是原本的4dkk的id --来自vision.txt this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0] this.pointcloud.panos.push(this) /* this.pointcloud.addEventListener('isVisible',(e)=>{ var visible = viewer.getObjVisiByReason(this.pointcloud, 'datasetSelection') console.log('datasetVisi', visible) viewer.updateVisible(this, 'pointcloudVisi', visible) //e.reason == 'datasetSelection' && viewer.updateVisible(this, 'pointcloudVisi', e.visible) }) */ this.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走) this.marker.visible = e.visible Potree.settings.showPanoMesh && (this.mesh.visible = e.visible) if(e.reason == 'screenshot' || e.visible){ this.label && (this.label.visible = e.visible)//截图时隐藏下 } }) //全景图和Cube的水平采样起始坐标相差90度 /* if(from4dkk){ var qua = o.dataset_orientation var quaternion = new THREE.Quaternion().fromArray(qua) quaternion = new THREE.Quaternion().multiplyQuaternions(quaternion, rot901);//整张球幕图要旋转下 因为在4dkk里转过,还原。如果是tiles的不用 this.quaternion = new THREE.Quaternion(quaternion.x, -quaternion.z, quaternion.y, quaternion.w) //转化坐标 }else{ */ var qua = o.dataset_orientation qua = [qua[1], qua[2], qua[3], qua[0]] this.quaternion = new THREE.Quaternion().fromArray(qua) this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度 this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk) this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion) //} //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation) //同quaternion //let xy = this.transform.forward([this.longitude, this.latitude]); this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg` this.build() this.transformByPointcloud() //初始化位移 {//tile this.minimumTiledPanoLoaded = !1; this.highestPartialTileRenderOpCompleted = 0; this.highestFullTileRenderOpCompleted = 0; this.shouldRedrawOnBaseLoaded = !1; this.resolutionPromise = {} this.tiledPanoRenderTarget = null; this.zoomed = !1; images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this)); images360.panoRenderer.addEventListener(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this)); images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this)); images360.panoRenderer.addEventListener(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this)); } this.addEventListener('hoverOn', (e)=>{//from Map if(!e.byMainView){ this.hoverOn(e) } }) this.addEventListener('hoverOff', (e)=>{ if(!e.byMainView){ this.hoverOff(e) } }) } setEnable(enable){//是否可以走 viewer.updateVisible(this, 'isEnabled', enable) //令所有marker不可见 this.enabled = enable //如果当前在全景模式且在这个点,需要切换显示吗? 目前用不到 } build(){ let mesh = new THREE.Mesh(sg, sm); mesh.scale.set(1, 1, 1); mesh.material.transparent = true; mesh.material.opacity = 0.75; mesh.pano = this; mesh.name = 'panoSphere' mesh.addEventListener('mouseover',(e)=>{ mesh.material = smHovered }) mesh.addEventListener('mouseleave',(e)=>{ mesh.material = sm }) mesh.addEventListener('click',(e)=>{ this.images360.focusPano(this) }) { // orientation //var {course, pitch, roll} = this; //mesh.quaternion.copy(this.quaternion) //add //var quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot901);//改 为球目全 //quaternion.premultiply(rot90) this.panoMatrix = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion) this.oriPanoMatrix = this.panoMatrix.clone() //console.log(this.quaternion) //this.quaternion = quaternion } this.mesh = mesh; if(!Potree.settings.showPanoMesh) mesh.visible = false let marker = new THREE.Mesh(planeGeo, getMarerMat() ) marker.up.set(0,0,1) marker.lookAt(marker.up) marker.scale.set(2,2,2) this.marker = marker this.images360.node.add(mesh) this.images360.node.add(marker) Potree.settings.isTest && this.createTextLabel() } transformByPointcloud(){ let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);//也可以用datasetPosTransform算 let floorPosition = this.originFloorPosition.clone().applyMatrix4(this.pointcloud.transformMatrix); this.setPosition(position, floorPosition) this.panoMatrix = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix ) //this.panoMatrix2 = Potree.Utils.datasetRotTransform({fromDataset:true, pointcloud:this.pointcloud, matrix:this.oriPanoMatrix, getMatrix:true}) //和上一行结果一样 //quaternion也变下 this.dispatchEvent('rePos') } setPosition(position, floorPosition){ this.position = position this.floorPosition = floorPosition this.mesh.position.copy(this.position) this.marker.position.copy(this.floorPosition) this.marker.position.z+=0.1//会被点云遮住 if(this.label){ this.label.position.copy(this.floorPosition), this.label.position.z+=0.2 this.label.update() } } hoverOn(e={}) { //console.log("hoverOn " + this.id ) transitions.start(lerp.property(this.marker.material, "opacity", 1), 250) if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true}) } hoverOff(e={}){ //console.log("hoverOff " + this.id ) transitions.start(lerp.property(this.marker.material, "opacity", 0.5), 250) if(!e.byMap) this.dispatchEvent({type:'hoverOff', byMainView:true}) } setZoomed(zoomed){ this.zoomed = zoomed; Potree.settings.displayMode == 'showPanos' && this.updateSkyboxForZoomLevel(); //放大后换成zoomTarget贴图 viewer.dispatchEvent({type:'panoSetZoom', zoomed}) } enter(){ this.setZoomed(!1), viewer.dispatchEvent({type:PanoramaEvents.Enter, oldPano:old, newPano:this } ) old = this //console.log("enter pano "+ this.id) } exit(){ /* if(this.tiled) { */ this.clearWaitDeferreds(); this.minimumTiledPanoLoaded = !1; this.tiledPanoRenderTarget = null; this.setZoomed(!1); this.images360.panoRenderer.deactivateTiledPano(this); this.highestPartialTileRenderOpCompleted = 0; this.highestFullTileRenderOpCompleted = 0; /*} else { this.solidSkybox.dispose(); this.solidSkybox.loaded = !1; this.solidSkybox.version = 0; } */ //console.log("exit pano "+ this.id) viewer.dispatchEvent({type:PanoramaEvents.Exit, pano:this}); } updateSkyboxForZoomLevel(){ if(this.minimumTiledPanoLoaded){ this.images360.updateProjectedPanos(); } } getSkyboxTexture(){ if(this.minimumTiledPanoLoaded) { if(this.zoomed && this.images360.qualityManager.maxRenderTargetSize > this.images360.qualityManager.maxNavPanoSize)//change 如果放大后和不放大都是2k就不用这个 { return this.images360.panoRenderer.zoomRenderTarget.texture; } else { this.tiledPanoRenderTarget.texture.mapping = THREE.UVMapping//add return this.tiledPanoRenderTarget.texture; } } else { return null; } } isLoaded(e){ if (e && "string" == typeof e) console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass"); return !!this.minimumTiledPanoLoaded && (!e || this.highestFullTileRenderOpCompleted >= e)//改:原本是:this.highestPartialTileRenderOpCompleted >= e, 希望这代表全部加载完 } getWaitDeferred(size){//获取不同size的tile贴图的promiss var t = this.resolutionPromise[this.id]; t || (t = {}, this.resolutionPromise[this.id] = t); var i = t[size]; return i || (i = { deferred: $.Deferred(), active: !1 }, t[size] = i), i } clearWaitDeferreds(){ var e = this.resolutionPromise[this.id]; e || (e = {}, this.resolutionPromise[this.id] = e); for (var t in e) if (e.hasOwnProperty(t)) { var i = e[t]; i.active = !1, i.deferred = $.Deferred() } } resetWaitDeferred(e){ var t = this.getWaitDeferred(e); t.active = !1; t.deferred = $.Deferred(); } onTileRendered(ev){ ev.id === this.id && this.dispatchEvent({ type:PanoramaEvents.TileLoaded, size:ev.panoSize, index:ev.tileIndex, count:ev.totalTiles }); } onPanoRendered(ev) { if(ev.id === this.id) { this.minimumTiledPanoLoaded = !0; this.updateSkyboxForZoomLevel();//更新贴图 setProjected ev.panoSize > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = ev.panoSize);//应该是更新最高获取到的Partial size ev.updateFullComplete && ev.panoSize > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = ev.panoSize); //应该是更新最高获取到的Full size //this.dispatchEvent("load", ev.panoSize); viewer.ifAllLoaded( this); this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:ev.panoSize, count:ev.totalTiles}); } } onTileRenderFail(ev) { ev.id === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed }); } onUploadAttemptedForAllTiles(ev) { if (ev.id === this.id) { var n = this.images360.qualityManager.getPanoSize(PanoSizeClass.BASE); if(ev.panoSize === n && this.shouldRedrawOnBaseLoaded) //shouldRedrawOnBaseLoaded一直是false。在4dkk里只有初始点在quickstart后变为true。 { this.shouldRedrawOnBaseLoaded = !1; this.panoRenderer.resetRenderStatus(this.id, !0, !1); this.panoRenderer.renderPanoTiles(this.id, null, !0, !0); } } } createTextLabel(){ this.removeTextLabel() this.label = new TextSprite($.extend( labelProp, {text: this.id }) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`} ); this.images360.node.add(this.label); this.floorPosition && this.label.position.copy(this.floorPosition) } removeTextLabel(){ if(this.label){ this.label.parent.remove(this.label); } } }; Panorama.prototype.loadTiledPano = function() { //var downloads = [] , t = []; var downloaded = {} , eventAdded = {}, latestPartialRequest = {}; //每个pano对应一组这些 return function(size, dirs, fov, o, a, download) { var dir = dirs.datasetsLocal.find(e=>e.datasetId == this.pointcloud.dataset_id).direction; //var dir = dirs null !== o && void 0 !== o || (o = !0), null !== a && void 0 !== a || (a = !0); var l = this.getWaitDeferred(size) , c = l.deferred , h = null , u = null; fov && ("number" == typeof fov ? h = fov : (h = fov.hFov, u = fov.vFov)) if (!this.isLoaded(size)) { //console.log('loadTiledPano', this.id, size, fov) if (!l.active) { l.active = !0 let name = this.id + ":" + size downloaded[name] = downloaded[name] || [] /* this.downloaded = downloaded this.latestPartialRequest = latestPartialRequest */ latestPartialRequest[name] = null if (fov) { let tileArr = []//add var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u, tileArr); latestPartialRequest[name] = tileArr downloaded[name].forEach((e)=>{ let item = latestPartialRequest[name].find(a=>e.faceTileIndex == a.faceTileIndex && e.face == a.face) if(item){ item.loaded = true } }) if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的全部加载成功 //let total = TileUtils.getTileCountForSize(size) //this.onPanoRendered(this.id, size, total, !0); c.resolve(size/* , total */); this.resetWaitDeferred(size) //console.log('该部分早已经加载好了'+size, this.id) latestPartialRequest[name] = null } //console.log("Loading partial pano: " + this.id + " with " + d + " tiles") } if(!eventAdded[this.id]) { eventAdded[this.id] = !0 this.addEventListener(PanoramaEvents.LoadComplete, function(ev/* e, t */) {//本次任务全部加载完毕 //console.warn('点位(可能部分)下载完成 ', 'id:'+this.id, 'size:'+ev.size ) var i = this.getWaitDeferred(ev.size).deferred;//"pending"为还未完成 i && "pending" === i.state() && this.highestPartialTileRenderOpCompleted >= ev.size && (i.resolve(ev.size, ev.count), this.resetWaitDeferred(ev.size))//恢复active为false }.bind(this)) this.addEventListener(PanoramaEvents.LoadFailed, function(ev) { var t = this.getWaitDeferred(e).deferred; t && "pending" === t.state() && this.highestPartialTileRenderOpCompleted >= ev.t && (t.reject(ev.t), this.resetWaitDeferred(ev.t))//恢复active为false }.bind(this)) this.addEventListener(PanoramaEvents.TileLoaded, function(ev/* t, i, n */) {//每张加载完时 //console.log('tileLoaded', 'id:'+this.id, 'size:'+ev.size, 'tileIndex:'+ev.index ) let tileIndex = ev.index let total = ev.count let size = ev.size let name = this.id + ":" + size downloaded[name] = downloaded[name] || [] //不是所有的加载都是从loadTiledPano获取的所以会有未定义的情况 let {faceTileIndex,face} = TileUtils.getTileLocation(size, tileIndex, {}) downloaded[name].push({faceTileIndex,face}) var r = this.getWaitDeferred(size).deferred; if (r && "pending" === r.state()) { r.notify(size, tileIndex, total); if(latestPartialRequest[name]){ let item = latestPartialRequest[name].find(e=>e.faceTileIndex == faceTileIndex && e.face == face) item && (item.loaded = true ) if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的局部tiles全部加载成功 this.onPanoRendered(this.id, size, total, !0); //onPanoRendered还会触发 PanoramaEvents.LoadComplete r.resolve(size, total); this.resetWaitDeferred(size) //console.log('该部分加载好了'+size, this.id) latestPartialRequest[name] = null } } } /* var r = this.getWaitDeferred(ev.size).deferred; if (r && "pending" === r.state()) { r.notify(ev.size, ev.index, ev.count); var o = downloads[this.id + ":" + ev.size]; if(o){//如果有规定下载哪些tile,只需要下载这些tile则LoadComplete o.tileCount++ if(o.tileCount === o.targetTileCount){//达到下载目标数 this.onPanoRendered(this.id, ev.size, ev.count, !0); r.resolve(ev.size, ev.count); this.resetWaitDeferred(ev.size) } } } */ }.bind(this)) } } this.images360.tileDownloader.clearForceQueue(), this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download) this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o) this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a) }else{ //console.log('早已经全加载好了' +size, this.id) c.resolve(size) } return c.promise() } }() /* 经观察发现,navvis的也存在的问题是点云和全景有微小的偏差,导致远处的热点在全景和点云上看位置差别感大,比如一个在路上一个在天空上。 */ export default Panorama