import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {transitions, easing, lerp} from '../../utils/transitions.js' import TileUtils from './tile/TileUtils.js' import math from '../../utils/math.js' import {TextSprite} from '../../objects/TextSprite.js' import Sprite from '../../objects/Sprite.js' import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js"; let { PanoRendererEvents, PanoramaEvents, PanoSizeClass} = Potree.defines var texLoader = new THREE.TextureLoader() const markerOpacitys ={ default : 0.4, hovered : 1, } 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, useDepth:true, clipDistance: 30, maxClipFactor:0.3, occlusionDistance:3, } const labelProp2 = { //sizeInfo: {minSize : 200 , maxSize : 250, nearBound : 0.8, farBound : 10}, backgroundColor:{r: 255, g: 255, b: 255, a: 0 }, textColor:{r:255 , g: 255, b: 255, a: 1 }, textBorderColor:{r:30 , g:30, b: 30, a: 1 }, textBorderThick:3, dontFixOrient:true, renderOrder:10, fontsize:30, } let markerTex //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial 或者直接把skybox的深度修改(拿到深度贴图后更如此) let planeGeo = new THREE.PlaneBufferGeometry(0.4,0.4); 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) const rotQua2 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI ) 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, images360){ super() this.id = o.id; //唯一标识 this.images360 = images360 this.visible = true //for updateVisible this.enabled = true//是否可以走 this.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走) //console.log('pano isVisible', this.id, e.visible) Potree.Utils.updateVisible(this.marker, 'panoVisi', e.visible) Potree.settings.showPanoMesh && (this.mesh.visible = e.visible) if(e.reason == 'screenshot' || e.visible){ this.label && (this.label.visible = e.visible)//截图时隐藏下 } this.label2 && Potree.Utils.updateVisible(this.label2, 'panoVisi', e.visible) }) /* 漫游点可见性:新 level reason 类型 2(最高)buildingChange(不在此楼层) unvisible 1 ifShowMarker(marker显示开关) unvisible 0 pointcloudVisi(隐藏了数据集) unvisible */ if(Potree.settings.editType == 'pano'){//漫游点拼合编辑 this.uuid = this.originID = o.uuid //对应4dkk中的id,可能不连续 this.index = o.index //下标, 用于visibles this.pointcloud = viewer.scene.pointclouds.find(e=>e.panoUuid == o.uuid) this.pointcloud.panos.push(this) this.sid = this.pointcloud.dataset_id + '|' + this.uuid //不会更改的标记 用于entity.panos里的标记 delete o.pointcloud this.panoData = o /* //数据中原本的位置朝向 this.dataPosition = new THREE.Vector3().copy(o.pose.translation) this.dataQuaternion = new THREE.Quaternion().copy(o.pose.rotation) this.dataRotation = new THREE.Euler().setFromQuaternion(this.dataQuaternion) */ //因为位置朝向随着点云位置改变,所以直接改变点云,这里清零 this.originPosition = new THREE.Vector3() this.quaternion = new THREE.Quaternion().copy(rotQua2); this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion this.quaternion2 = this.quaternion.clone() this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度 const height = 1.4; //相机高度 this.originFloorPosition = this.originPosition.clone() this.originFloorPosition.z -= height }else{ this.originPosition = new THREE.Vector3().fromArray(o.dataset_location) //完全对应vision.txt的translation 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 = o.pointcloud //viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0] this.pointcloud.panos.push(this) //this.sid = this.pointcloud.sceneCode + '|' + this.originID //不会更改的标记 this.sid = this.pointcloud.dataset_id + '|' + this.originID //不会更改的标记 //全景图和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 //完全对应vision.txt的rotation 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.quaternion2 = this.quaternion.clone() this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度 this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk) } this.neighbours = []; this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion) 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) } }) } get noNeighbour(){//是否绝对到不到的孤立点 for(let i=0,j=this.images360.panos.length; i{ this.depthTex = texture this.dispatchEvent({type:'loadedDepthImg', loaded:true}) this.images360.dispatchEvent({type:'loadedDepthImg', pano:this}) this.depthTexLoading = false this.images360.updateDepthTex(this) },null,(e)=>{//error console.error('loadDepthImg失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id ) this.pointcloud.hasDepthTex = false this.dispatchEvent({type:'loadedDepthImg' }) }); texture.wrapS = THREE.RepeatWrapping; texture.flipY = false texture.magFilter = THREE.LinearFilter texture.minFilter = THREE.LinearFilter texture.generateMipmaps = false //平均一张0.75M。2048*1024。 和tile一样加载后永不删除,是否会造成崩溃? } /* loadTypeImg(type){ if(!this['has_'+type] || this[type+'Tex'] || this[type+'Loading'])return let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_${type}.png` //let url = `${Potree.settings.urls.prefix1}/testdata/${Potree.settings.number}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_temp.png` let texture = texLoader.load( url, ()=>{ this[type+'Tex'] = texture this.dispatchEvent({type:'loaded_'+type, loaded:true}) this.images360.dispatchEvent({type:'loaded_'+type, pano:this}) this[type+'Loading'] = false },null,(e)=>{//error console.error('load '+type+'Img失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id ) this['has_'+type] = false this['loadFailed_'+type] = false this.dispatchEvent({type:'loaded_'+type }) }); texture.wrapS = THREE.RepeatWrapping; texture.flipY = false texture.magFilter = THREE.LinearFilter texture.minFilter = THREE.LinearFilter texture.generateMipmaps = false } */ loadTypeImg(type){ if(!this['has_'+type] || this[type+'Tex'] || this[type+'Loading'])return let url = this.pointcloud.typesUrl + `${this.originID}_${type}.png` //let url = `${Potree.settings.urls.prefix1}/testdata/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/imagemap/${this.originID}_temp.png` let range = {min:Infinity, max:-Infinity, minPixel:null, maxPixel:null} let pixels let getRangeFun = (type == 'ir' || type == 'temp') && function(uint16Value, index, pixelCount){ if(type == 'ir'){ pixels || (pixels = new Uint16Array(pixelCount)) pixels[index] = uint16Value } if(uint16Value != 0 && uint16Value < range.min){ range.min = uint16Value range.minPixel_ = index } if(uint16Value != 0 && uint16Value > range.max){ range.max = uint16Value range.maxPixel_ = index } } let texture = Potree.Common.load16bitPngTex(url,()=>{ this[type+'Tex'] = texture if(getRangeFun){ if(type == 'ir'){ let p = [range.minPixel_, range.maxPixel_].map(index=>{ let row = Math.floor(index / texture.image.width) let col = index % texture.image.width return {x:col,y:row} }) range.minPixel = p[0] range.maxPixel = p[1] let pixelCount = texture.image.width * texture.image.height const stopMemberCount = 50; (['min','max']).forEach(name =>{ let value = range[name] let groups = [] let isNeigh = (A,B)=>{ return (Math.abs(A.x - B.x)<=1 || Math.abs(A.x - B.x) == texture.image.width-1) && Math.abs(A.y - B.y)<=1 } for(let i=0;ie.length > stopMemberCount && e.some(e=>e==range[name]))){ break } } } let groups2 = groups.filter(e=>e.length>1) if(groups2.length < 3) groups2 = groups groups2.forEach(group=>{//x的因边界不好写,就只判断y尽量接近中间且范围不大不小即可 group.sort((a,b)=>{return a.y-b.y}) let minY = group[0].y let maxY = group[group.length-1].y let centerY = (minY + maxY) / 2 let rectRatio = (maxY - minY) * 1//除非竖的一列,否则一般y范围越大越好 Math.abs(2 - Potree.math.getBaseLog(maxY - minY, group.length)) //范围圆润度, 最好个数是y范围的平方 group.score = group.length * 0.2 + rectRatio - Math.abs(texture.image.height/2 - centerY) * 0.5 //y尽量接近中间 group.center = group[Math.floor(group.length/2)] //忽略是否横向的中间 }) groups2.sort((b,a)=>{return a.score - b.score}) range[name+'Pixel'] = groups2[0]?.center //range[name+'PixelGroup'] = groups2 //console.log('groups2', this.id, name,groups2) }) } range.min /= 10, range.max /= 10 texture.image.range = range viewer.gatherTempRange(type,range) } this.images360.waitDelTexDataList.push(texture) this.dispatchEvent({type:'loaded_'+type, loaded:true}) this.images360.dispatchEvent({type:'loaded_'+type, pano:this}) this[type+'Loading'] = false },(e)=>{ console.error('load '+type+'Img失败, 数据集sceneCode'+ this.pointcloud.sceneCode, e, this.id ) this['has_'+type] = false this['loadFailed_'+type] = false this.dispatchEvent({type:'loaded_'+type }) }, getRangeFun) texture.wrapS = THREE.RepeatWrapping; texture.flipY = false texture.magFilter = THREE.LinearFilter texture.minFilter = THREE.LinearFilter texture.generateMipmaps = false } build(){ { // orientation //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() if(this.quaternion2)this.oriPanoMatrix2 = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion2) //console.log(this.quaternion) //this.quaternion = quaternion } let marker = new THREE.Mesh(planeGeo, this.getMarkerMat() ) //new Sprite({mat:this.getMarkerMat(), dontFixOrient:true }) marker.name = 'marker_'+this.id marker.up.set(0,0,1) this.addEventListener('changeMarkerTex',(e)=>{ marker.material.map = markerTex[e.name] }) this.marker = marker marker.pano = this; this.images360.node.add(marker) Potree.settings.isTest && this.addLabel() marker.addEventListener('mouseover', this.hoverOn.bind(this)); marker.addEventListener('mouseleave', this.hoverOff.bind(this)); } transformByPointcloud(){ this.ceilZ = null //need reset 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也变下 if(this.oriPanoMatrix2){ this.panoMatrix2 = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix2 )//供DepthImageSampler使用 this.panoMatrix2Inverse = this.panoMatrix2.clone().invert(); } 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.lookAt(position) //融合页面marker可能跟随模型倾斜 let upVec = new THREE.Vector3().subVectors(position, floorPosition).normalize().multiplyScalar(0.04*this.pointcloud.scale.x) this.marker.position.add(upVec) //this.marker.position.z+=0.04//会被点云遮住 if(this.label){ if(Potree.settings.editType == 'pano'){ this.label.position.copy(this.position) }else{ this.label.position.copy(this.floorPosition) } this.label.position.z+=0.14 this.label.updatePose() } } /* getRealPos(){//当整体移动以后 return this.position.clone().applyMatrix4(viewer.scene.scene.matrix) } */ getMarkerMat(){ if(!markerTex) { markerTex = { default:texLoader.load( Potree.resourcePath+'/textures/marker.png' ), ring:texLoader.load( Potree.resourcePath+'/textures/marker2.png' ) } markerTex.default.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊 markerTex.ring.anisotropy = 4 //有可能被点云遮住吗。 } let mat = new DepthBasicMaterial({opacity: markerOpacitys.default, side: THREE.DoubleSide , map:markerTex.default ,transparent:true, clipDistance: 2, occlusionDistance:1, //不能设置太短,因为过渡时深度不准确 useDepth: !!( Potree.settings.useDepthTex && this.pointcloud.hasDepthTex || Potree.settings.modelSkybox && this.pointcloud.is4dkkModel ), autoDepthTest: true, //改为DepthBasicMaterial是因为原Basic的材质在有深度图时过渡会先隐藏后出现。 注:没有深度图时全景模式的marker无法遮挡 }) mat.mapTransparent = true return mat } hoverOn(e={}) { //console.log("hoverOn " + this.id ) transitions.start(lerp.property(this.marker.material, "opacity", markerOpacitys.hovered,()=>{ viewer.dispatchEvent('content_changed') }), this.marker.visible ? 250 : 0) if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true}) if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:true, pano:this}) } hoverOff(e={}){ //console.log("hoverOff " + this.id ) transitions.start(lerp.property(this.marker.material, "opacity", markerOpacitys.default,()=>{ viewer.dispatchEvent('content_changed') }), this.marker.visible ? 250 : 0) if(!e.byMap) this.dispatchEvent({type:'hoverOff', byMainView:true}) if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:false, pano:this}) } setZoomed(zoomed){ this.zoomed = zoomed; Potree.settings.displayMode == 'showPanos' && this.updateSkyboxForZoomLevel(); //放大后换成zoomTarget贴图 viewer.dispatchEvent({type:'panoSetZoom', zoomed}) } enter(){ this.entered = true this.setZoomed(!1) viewer.dispatchEvent({type:PanoramaEvents.Enter, oldPano:old, newPano:this } ) old = this //console.log("enter pano "+ this.id) } exit(){ this.clearWaitDeferreds(); this.minimumTiledPanoLoaded = !1; this.tiledPanoRenderTarget = null; this.setZoomed(!1); this.images360.panoRenderer.deactivateTiledPano(this); this.highestPartialTileRenderOpCompleted = 0; this.highestFullTileRenderOpCompleted = 0; this.depthTex && this.depthTex.dispose() //贴图不使用后先dispose,下次到该点时会自动还原 this.entered = false //add //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); } } } addLabel(){ this.removeTextLabel() this.label = new TextSprite(Object.assign({}, labelProp, {text: this.id + "("+this.originID+")"}) //{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) } addLabel2(){ if(this.label2)return this.label2 = new TextSprite(Object.assign({}, labelProp2, {text: /* this.originID */ parseInt(this.id)+1 }) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`} ); //this.images360.node.add(this.label2); this.marker.add(this.label2) //this.floorPosition && this.label2.position.copy(this.floorPosition) let s = 0.2 this.label2.scale.set(s,s,s) Potree.Utils.updateVisible(this.label2, 'notDisplay', false) Potree.Utils.updateVisible(this.label2, 'panoVisi', this.visible) //Potree.Utils.setObjectLayers(this.label2, 'bothMapAndScene') } removeTextLabel(){ if(this.label){ this.label.parent.remove(this.label); } } dispose(){ let i = viewer.images360.panos.indexOf(this); if(i==-1)return viewer.images360.panos.splice(i,1); i = this.pointcloud.panos.indexOf(this) this.pointcloud.panos.splice(i,1); this.marker.parent.remove(this.marker) this.removeTextLabel() if(this.depthTex) this.depthTex.dispose() this.dispatchEvent('dispose') //删除tile贴图、depthTex等以后再写 } getCeilHeight(){//天花板高度值 (假设不存在depth为0的点,所有为0的要么是在盲区,要么是无穷远。) if(this.ceilZ == void 0){ //const depthTiming = Potree.timeCollect.depthSampler.median //pc firefox达到4. chrome为0.01 //用三个间隔120度散开,和中心垂直线成一定夹角的三个向量去求 最高高度 (不求平均的原因:万一是0不好算) let rotMat = new THREE.Matrix4().makeRotationX((Potree.config.depthTexUVyLimit+0.01)*Math.PI)// 角度不能小于天花板中空的半径 let dir0 = new THREE.Vector3(0,0,1).applyMatrix4(rotMat) let dirs = [ dir0, dir0.clone().applyMatrix4(new THREE.Matrix4().makeRotationZ(Math.PI*2 / 3)), dir0.clone().applyMatrix4(new THREE.Matrix4().makeRotationZ(-Math.PI*2 / 3)) ]; /* if(depthTiming < 1){ let rotMat1 = new THREE.Matrix4().makeRotationZ(Math.PI*2 / 3); dirs.push(dirs[0].clone().applyMatrix4(rotMat1)) } if(depthTiming < 0.3){ let rotMat2 = new THREE.Matrix4().makeRotationZ(-Math.PI*2 / 3); dirs.push(dirs[0].clone().applyMatrix4(rotMat2)); } */ let zs = dirs.map(dir_=>{ let dir = dir_.clone().applyMatrix4(this.panoMatrix2) //pano不一定是垂直的, 需要把之前的dirInPano先转成真实的dir,防止超出角度限制 let intersect = viewer.images360.getIntersect(this, dir) let z = intersect ? intersect.location.z : Infinity/* this.position.z+skyHeight */ //没有intersect代表可能是天空 return z }) zs.sort((a,b)=>{return b-a});//得最大值 (不用中位数的原因:在屋檐处,如果仅有一个intersect是天空,因到了室外所以也用天空高度) this.ceilZ = zs[0] let min = this.position.z + 1 // 防止意外太低 this.ceilZ = Math.max(min, this.ceilZ) //console.log(this.id, 'ceilZ:', this.ceilZ ) } return this.ceilZ } }; 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 } } } viewer.dispatchEvent('content_changed') /* 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) //将512的先贴上 }else{ //console.log('早已经全加载好了' +size, this.id) c.resolve(size) } return c.promise() } }() /* 经观察发现,navvis的也存在的问题是点云和全景有微小的偏差,导致远处的热点在全景和点云上看位置差别感大,比如一个在路上一个在天空上。 */ export default Panorama