import * as THREE from "../../../../libs/three.js/build/three.module.js"; import cameraLight from '../../utils/cameraLight.js' import math from "../../utils/math.js" import Common from '../../utils/Common.js' import {LineDraw, MeshDraw} from "../../utils/DrawUtil.js"; import {transitions, easing, lerp} from '../../utils/transitions.js' import SplitScreen from "../../utils/SplitScreen.js"; import InfiniteGridHelper from '../../objects/InfiniteGridHelper.js' import Compass from "../../objects/tool/Compass.js"; import {TransformControls} from "../../objects/tool/TransformControls.js"; import History from "../../utils/History.js" import {Box3Helper} from "../../../utils/Box3Helper.js"; import {TextSprite} from '../../objects/TextSprite.js' const texLoader = new THREE.TextureLoader() texLoader.crossOrigin = "anonymous" const edgeStrengths = { pointcloud: 4, glb: 100 } const viewportProps = [{ left:0, bottom:0, width: 0.5,height:1, name : 'top', axis:["x","y"], direction : new THREE.Vector3(0,0,-1), //镜头朝向 active: true, //相机位置在z轴正向 limitBound: new THREE.Box3(new THREE.Vector3(-Infinity,-Infinity, 1),new THREE.Vector3(Infinity,Infinity,5000)), //在地面以上 margin:{x:50, y:150} , }, { left:0.5, bottom:0, width: 0.5,height:1, name : 'right', axis:["y","z"], direction : new THREE.Vector3(1,0,0), active: true, //相机位置在x轴负向 右下角屏 viewContainsPoints:[new THREE.Vector3(0,0,0)], margin:{x:300, y:250} , } ] let cylinderSkyGeo, oldSkyGeo let MergeEditor = { bus:new THREE.EventDispatcher(), SplitScreen : new SplitScreen(), init(){ this.boxHelper = new Box3Helper(new THREE.Box3(new THREE.Vector3(-0.5,-0.5,-0.5), new THREE.Vector3(0.5,0.5,0.5))); viewer.scene.scene.add(this.boxHelper) Potree.Utils.updateVisible(this.boxHelper,'unselect',false) this.lastMemoryState = {} this.history = new History({ applyData: (data)=>{ if(data.object.parent /* && data.object == this.selected */){ data = Potree.Common.CloneObject(data) //避免使用后更改数据又被使用 data.matrix.decompose( data.object.position, data.object.quaternion, data.object.scale ); data.object.boundCenter.copy(data.boundCenter) data.object.dispatchEvent('changeByHistory') data.object.dispatchEvent('transformChanged') viewer.dispatchEvent('content_changed') return true } }, getData:(object)=>{ return { object, matrix: object.matrixWorld.clone(), boundCenter: object.boundCenter.clone() } } }) { Potree.settings.notAdditiveBlending = true let ground = this.ground = new InfiniteGridHelper(1, 10000, new THREE.Color('#eee'), 10000, 0.2, 0.3) viewer.scene.scene.add(ground) //再加两条线否则在正侧边看不到 let line1 = LineDraw.createLine([new THREE.Vector3(-10000, 0, 0),new THREE.Vector3(10000, 0, 0) ], {color:'#aaa', }) let line2 = LineDraw.createLine([new THREE.Vector3(0, -10000, 0),new THREE.Vector3(0, 10000, 0) ], {mat:line1.material}) ground.renderOrder = Potree.config.renderOrders.model + 1//line1.renderOrder + 1 //要比模型低,否则模型透明时效果不对 ground.add(line1) ground.add(line2) ground.material.opacity = 0.9 //为了滞后渲染,否则被rt遮住 ground.material.polygonOffset = true //多边形偏移(视觉上没有移动模型位置),防止闪烁 ground.material.polygonOffsetFactor = 100 //多边形偏移因子 ground.material.polygonOffsetUnits = 10 //多边形偏移单位 ground.material.depthWrite = false //ground.material.depthTest = false line1.material.polygonOffset = true line1.material.polygonOffsetFactor = 130 line1.material.polygonOffsetUnits = 10 line1.material.depthWrite = false //见笔记:透明物体的材质设置 } let oriEdgeStrength = viewer.outlinePass.edgeStrength { this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{ //dontHideWhenFaceCamera: true, }); //this.transformControls.space = 'local'//为了在当前方向上平移 this.transformControls.setSize(1.5) viewer.scene.scene.add(this.transformControls) this.transformControls._gizmo.hideAxis = {rotate:['e'] ,scale:['x','y','z' ] } this.transformControls._gizmo.showAxis = {scale:['XYZY']} //仅显示 等于而非包含 this.transformControls.setRotateMethod(2) //右屏 this.transformControls2 = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{ //dontHideWhenFaceCamera: true, }); this.transformControls2.setSize(1.5) viewer.scene.scene.add(this.transformControls2) Potree.Utils.setObjectLayers(this.transformControls2, 'layer2' ) let mouseDown = (e)=>{ viewer.outlinePass.edgeStrength = 0//暂时消失线 } let mouseUp = (e)=>{ //this.updateEdgeStrength() viewer.outlinePass.edgeStrength = oriEdgeStrength } this.transformControls.addEventListener('mouseDown',mouseDown) this.transformControls2.addEventListener('mouseDown',mouseDown) this.transformControls.addEventListener('mouseUp',mouseUp) this.transformControls2.addEventListener('mouseUp',mouseUp) this.transformControls.addEventListener('mouseDown', ()=>{ //dragstart this.history.beforeChange(this.selected) }) this.transformControls.addEventListener('mouseUp',()=>{ this.history.afterChange(this.selected) }) } { this.secondCompass = new Compass(null) } viewer.setControls(viewer.orbitControls) //viewer.mainViewport.view.fixZWhenPan = true viewer.orbitControls.constantlyForward = true viewer.addEventListener('global_single_click',(e)=>{ if( e.button != THREE.MOUSE.LEFT || this.noNeedSelection //如模型查看页 || viewer.scene.cameraAnimations.some(c=>c.onUpdate) //正在播放 || e.drag && e.drag.notPressMouse //在加测量线 || viewer.mainViewport.view.isFlying() //有其他校准 || this.split //分屏中 || e.clickElement //触发别的点击事件,如测量时click marker /* && e.clickElement != e.intersect.object */ ){ return } if(e.intersect){ let object = e.intersect.object || e.intersect.pointcloud let objects = this.getAllObjects() let posInModel = Potree.Utils.datasetPosTransform({ toDataset: true, position: e.intersect.location.clone(), object }) if(objects.includes(object) && this.selected != object){ this.selectModel(object, posInModel) }else{ //if(!viewer.inputHandler.selection[0]){//正在平移和旋转,不允许取消 if(this.selected == object && this.transformControls.mode == 'translate'){ this.selectModel(object, posInModel) //update click pos }else{ this.selectModel(null) } //} } }else{ //if(!viewer.inputHandler.selection[0]){ this.selectModel(null) //} } }) viewer.inputHandler.addEventListener('keydown', (e)=>{ if((e.event.key).toLowerCase() == "h" ){ this.fadeOutlineAuto = !this.fadeOutlineAuto this.showModelOutline(this.selected,!!this.selected) } }) //viewer.fxaaPass.enabled = false//viewer.ssaaRenderPass.enabled = false viewer.outlinePass.enabled = true //Potree.settings.intersectWhenHover = false //Potree.Utils.updateVisible(viewer.reticule, 'force', false) viewer.composer.scaleRatio = 1 viewer.composer.readTarget = false viewer.addEventListener('updateModelBound', (e)=>{ if(this.split){ this.SplitScreen.updateCameraOutOfModel(/* this.selected && [this.selected] */) } }) {//校准页面拖拽 //左右屏都可以拖拽模型,旋转只能左屏 let dragInfo let drag = (e)=>{ if(this.split && this.selected && this.transformState && (e.dragViewport.name == 'top' || this.transformState == 'translate') ){ if(e.type == 'global_mousedown' ){ //开始 //if((e.intersect.object || e.intersect.pointcloud) == this.selected){ if(e.intersect.pointclouds.includes(this.selected) || e.intersect.allElements.some(e=>e.object == this.selected)){ dragInfo = {} //if(this.selected.isPointcloud){ viewer.outlinePass.edgeStrength = 0//暂时消失线 //} } } if(e.type == 'global_drag' && dragInfo ){ if(this.transformState == 'translate'){ let moveVec = Potree.Utils.getOrthoCameraMoveVec(e.drag.pointerDelta, e.dragViewport.camera )//最近一次移动向量 this.selected.position.add(moveVec) this.selected.dispatchEvent("position_changed") }else if(this.transformState == 'rotate'){ let vec = new THREE.Vector3().subVectors(e.intersect.orthoIntersect || e.intersect.location, this.selected.boundCenter).setZ(0) if(dragInfo.lastVec == void 0){//global_mousedown dragInfo.lastVec = vec return } let angle = math.getAngle(dragInfo.lastVec, vec, 'z') dragInfo.lastVec = vec //this.selected.rotation.z += angle //局部 /* object.quaternion.copy( .setFromAxisAngle( new THREE.Vector3(0,0,1), angle ) ); object.quaternion.multiply( quaternionStart ).normalize(); */ let diffQua = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3(0,0,1), angle ) this.selected.quaternion.premultiply(diffQua) //世界 this.selected.dispatchEvent("rotation_changed") } return {stopContinue:true} } } } viewer.addEventListener('global_mousedown', drag) viewer.addEventListener('global_drag', drag, {importance:10}) viewer.addEventListener('global_mousemove', (e)=>{ if(this.split && this.transformState && !e.drag && (e.hoverViewport.name == 'top' || this.transformState == 'translate')){ /* if(this.lastHoverViewport != e.hoverViewport){ this.lastHoverViewport = e.hoverViewport this.transformControls.view = e.hoverViewport.view this.transformControls.camera = e.hoverViewport.camera this.transformControls.hideAxis( this.transformState, e.hoverViewport.name == 'top' ? [z] : [x,y]); } */ let mouseover = e.intersect.pointclouds.includes(this.selected) || e.intersect.allElements.some(e=>e.object == this.selected) //let mouseover = (e.intersect.object || e.intersect.pointcloud) == this.selected if(mouseover){ if(this.transformState == 'translate'){ viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"movePointcloud" }) }else{ viewer.dispatchEvent({ type : "CursorChange", action : "add", name:"rotatePointcloud" }) } }else{ this.clearTranCursor() } } }) viewer.addEventListener('global_drop', (e)=>{ dragInfo = null this.clearTranCursor() //this.updateEdgeStrength() viewer.outlinePass.edgeStrength = oriEdgeStrength }) } /* viewer.addEventListener('background_changed',()=>{ }) */ viewer.addEventListener('camera_changed',()=>{//其实静止时内存也会变,因为在加载 this.lastMemoryState.history = [] //clear Common.intervalTool.isWaiting('updateMemoryUsage', ()=>{ return this.updateMemoryUsage() }, 1000) }) viewer.addEventListener('setDisplay',this.updateMemoryUsage.bind(this)) }, clearTranCursor(){ viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"movePointcloud" }) viewer.dispatchEvent({ type : "CursorChange", action : "remove", name:"rotatePointcloud" }) }, enterSplit(){ this.split = true if(this.selected) this.SplitScreen.focusCenter = this.selected.boundCenter //旋转中心。注意 boundCenter不能直接赋值,否则改变后focusCenter也要改 else this.SplitScreen.focusCenter = null this.SplitScreen.splitStart(viewportProps) this.beforeSplit = { pointDensity: Potree.settings.pointDensity, } Potree.settings.pointDensity = 'fourViewports' //强制降低点云质量 viewer.setControls(viewer.fpControls) let rightViewport = viewer.viewports.find(e=>e.name == 'right') let topViewport = viewer.viewports.find(e=>e.name == 'top') topViewport.alignment = true rightViewport.rotateSide = true rightViewport.skyboxFixPos = true rightViewport.skyboxMinZoom = 10 rightViewport.skyboxRenderFun = ()=>{// 使cube的一面永远正向镜头。 因侧视图的camera是ortho类型,需要平视mesh才不会拉伸 viewer.skybox.scene.children[0].rotation.copy(rightViewport.camera.rotation) } topViewport.skyboxRenderFun = ()=>{ viewer.skybox.scene.children[0].rotation.set(0,0,0) } viewer.viewports[1].layersAdd('layer2') viewer.viewports[0].layersAdd('layer1') Potree.Utils.setObjectLayers(this.transformControls, 'layer1' ) this.transformControls.view = viewer.viewports[0].view this.transformControls.camera = viewer.viewports[0].camera this.transformControls._gizmo.hideAxis = {translate:['z'], rotate:['x','y','z'] } this.transformControls2.view = viewer.viewports[1].view this.transformControls2.camera = viewer.viewports[1].camera this.transformControls2._gizmo.hideAxis = {translate:['x','y'], rotate:['x','y','z'] } this.secondCompass.changeViewport(viewer.viewports[0]) this.secondCompass.setDomPos() this.secondCompass.setDisplay(true) viewer.compass.changeViewport(viewer.viewports[1]) viewer.compass.setDomPos() //this.changeSkyboxGeo(true) }, leaveSplit(){ this.split = false this.SplitScreen.unSplit() viewer.setControls(viewer.orbitControls) Potree.settings.pointDensity = this.beforeSplit.pointDensity /* if(this.selected && this.selected.isPointcloud){ this.showModelOutline(this.selected, true) this.selected.material.activeAttributeName = "rgba" } */ this.transformControls.camera = viewer.viewports[0].camera this.transformControls.view = viewer.viewports[0].view this.transformControls._gizmo.hideAxis = {rotate:['e']} Potree.Utils.setObjectLayers(this.transformControls, 'sceneObjects' ) //恢复 viewer.compass.changeViewport(viewer.viewports[0]) //恢复 viewer.compass.setDomPos() this.secondCompass.setDisplay(false) }, rotateSideCamera(angle){ this.SplitScreen.rotateSideCamera(viewer.viewports.find(e=>e.name == 'right'), angle) }, setTransformState(state){//校准时 this.transformState = state this.clearTranCursor() }, //--------------------------- getAllObjects(){ return viewer.objs.children.concat(viewer.scene.pointclouds) }, getModel(id){ let models = this.getAllObjects() return models.find(e=>e.dataset_id == id) }, changeModelPointCount(object, type){ if(object.fileType == '3dTiles' || object.isPointcloud) return let posCount , texArea if(type == 'add'){ let o = viewer.getObjectPointCount(object) posCount = o.posCount, texArea = o.texArea object.posCount = posCount, object.texArea = texArea }else{ posCount = -object.posCount, texArea = -object.texArea } viewer.memoryModelCountInfo.otherPosCount += posCount viewer.memoryModelCountInfo.otherTexArea += texArea }, modelAdded(model){ /* model.addEventListener('isVisible',(e)=>{ if(e.reason == "overlinePass")return //console.log(e) viewer.addEventListener('update',()=>{ //下一次更新结束后 this.updateMemoryUsage() },{once:true}) }) */ let weightUpdate = ()=>{ this.changeModelPointCount(model,'add') this.updateMemoryUsage() } if(model.fileType == 'obj'){//要等待贴图都加载完 if(viewer.fileManager.loading){ viewer.addEventListener('managerOnLoad',(e)=>{ weightUpdate() },{once:true}) //如果onError了咋办,暂时无法定位manager加载的哪个模型的 } }else{ weightUpdate() } }, removeModel(model){ if(this.selected == model) this.selectModel(null) let dispose = (e)=>{ e.geometry && e.geometry.dispose() e.material && e.material.dispose() } if(model.isPointcloud){ dispose(model) viewer.scene.removePointCloud(model) }else{ model.traverse(e=>{ dispose(e) }) viewer.objs.remove(model) } model.panos?.slice().forEach(e=>{ e.dispose() }) viewer.images360.tileDownloader.setPanoData(viewer.images360.panos, [] ); if(model.fileType == '3dTiles'){ model.traverse(child=>{ if(child.runtime){ child.runtime.getTileset().destroy() return {stopContinue:true} } }) } this.changeModelPointCount(model,'sub') this.updateMemoryUsage() }, selectModel(model, state=true, fitBound, by2d){ if(!model) { model = this.selected state = false } if(!by2d && model){ model.dispatchEvent({type:'changeSelect', selected : state, clickPos:state}) } if(state){ if(this.selected){ if(this.selected == model) return else{ let transToolAttached = !!this.transformControls.object this.selectModel(this.selected, false, fitBound, by2d) transToolAttached && this.transformControls.attach(model) } } this.selected = model MergeEditor.focusOn(model, 500, !!fitBound) //通过在场景里点击模型的话,不focus this.showModelOutline(model) //this.updateEdgeStrength() //console.log('selectModel', model) }else{ if(this.selected != model)return //model本来就没选中,不需要处理(防止2d先选中新的再取消旧的) this.showModelOutline(model, false) this.selected = null this.transformControls.detach() //viewer.transformObject(null); //console.log('selectModel', null) } }, updateBoxHelper(model){ let size = new THREE.Vector3 model.boundingBox.getSize(size) size.multiply(model.scale) this.boxHelper.scale.copy(size) let center = new THREE.Vector3 model.boundingBox.getCenter(center) center.applyMatrix4(model.matrixWorld) //center.add(model.position) this.boxHelper.position.copy(center) this.boxHelper.quaternion.copy(model.quaternion) viewer.dispatchEvent('content_changed') }, showModelOutline(model, state){ if(Potree.settings.selectShowBox || (model ? model.fileType == '3dgs' : this.boxHelper.visible) ){//高斯很卡 if(state !== false ){ this.updateBoxHelper(model) Potree.Utils.updateVisible(this.boxHelper,'unselect',true) }else{ Potree.Utils.updateVisible(this.boxHelper,'unselect',false) } return } if(this.fadeOutlineAuto){ if(state === false){ viewer.outlinePass.selectedObjects = [] clearTimeout(this.timer) return } viewer.outlinePass.selectedObjects = [model] if(this.timer){ clearTimeout(this.timer) } this.timer = setTimeout(()=>{ viewer.outlinePass.selectedObjects = [] viewer.dispatchEvent('content_changed') }, 1000) }else{ if(state === false){ viewer.outlinePass.selectedObjects = [] }else{ viewer.outlinePass.selectedObjects = [model] } } viewer.dispatchEvent('content_changed') }, /*updateEdgeStrength(){ if(!this.selected)return if(this.selected.isPointcloud){ viewer.outlinePass.edgeStrength = edgeStrengths.pointcloud// / this.selected.material.opacity }else{ viewer.outlinePass.edgeStrength = edgeStrengths.glb } },*/ focusOn(objects, duration = 400, fitBound=true, dontLookUp, dir){ if(!(objects instanceof Array)){ objects = [objects] } let boundingBox = new THREE.Box3 objects.forEach(object=>{ boundingBox.union(object.boundingBox.clone().applyMatrix4(object.matrixWorld)) }) let len = boundingBox.getSize(new THREE.Vector3).length() Potree.settings.cameraFar = Math.max( Potree.settings.cameraFar, len*3 ) if(fitBound){ viewer.focusOnObject({boundingBox}, 'boundingBox', duration, {dontLookUp, dontChangeCamDir:dir?false:true, dir}) }else{ /* let position = viewer.inputHandler.intersect ? viewer.inputHandler.intersect.location : boundingBox.getCenter(new THREE.Vector3) position && viewer.focusOnObject({position}, 'point', duration, {dontChangePos: true}) */ let position = viewer.inputHandler.intersect ? viewer.inputHandler.intersect.location : boundingBox.getCenter(new THREE.Vector3) if(!position)return /* let targetOld = viewer.mainViewport.view.getPivot() let projected1 = targetOld.clone().project(viewer.mainViewport.camera); let projected2 = position.clone().project(viewer.mainViewport.camera); //使用其z let targetNew = projected1.clone().setZ(projected2.z).unproject(viewer.mainViewport.camera); viewer.mainViewport.view.lookAt(targetNew) */ viewer.mainViewport.view.radius = viewer.mainViewport.camera.position.distanceTo(position) //为了不改画面,不调节方向了,只能调调radius,一定程度将target靠近model } }, moveBoundCenterTo(model,pos){ //使boundCenter在所要的位置 let diff = new THREE.Vector3().subVectors(pos, model.boundCenter) model.position.add(diff); }, getBoundCenter(model){ if(!model.boundCenter) { model.boundCenter = new THREE.Vector3 model.boundSize = new THREE.Vector3 } let bound = model.boundingBox.clone().applyMatrix4(model.matrixWorld) bound.getCenter(model.boundCenter) bound.getSize(model.boundSize) //model.boundingBox.getCenter(model.boundCenter).applyMatrix4(model.matrixWorld) }, setModelBtmHeight(model, z ){ //无论模型怎么缩放、旋转,都使最低点为z if(z == void 0) z = model.btmHeight; //维持离地高度 else model.btmHeight = z; if(model.btmHeight == void 0)return model.updateMatrixWorld() this.getBoundCenter(model) /* let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld) let size = boundingBox2.getSize(new THREE.Vector3); let center = boundingBox2.getCenter(new THREE.Vector3); */ let hopeZ = z + model.boundSize.z / 2 //model.position.z = z + size.z / 2 - center.z model.position.z += (hopeZ - model.boundCenter.z) }, computeBtmHeight(model){ //位移之后重新计算btmHeight model.updateMatrixWorld() /* let boundingBox2 = model.boundingBox.clone().applyMatrix4(model.matrixWorld) let size = boundingBox2.getSize(new THREE.Vector3); let center = boundingBox2.getCenter(new THREE.Vector3); */ this.getBoundCenter(model) model.btmHeight = model.boundCenter.z - model.boundSize.z / 2 }, maintainBoundXY(model){ //在旋转和缩放后,立即执行这个函数,使boundCenter保持原位 model.updateMatrixWorld() let center1 = model.boundCenter.clone();//还未更新的 this.getBoundCenter(model)//更新 let center2 = model.boundCenter.clone(); let diff = new THREE.Vector2().subVectors(center1,center2); model.position.x += diff.x; model.position.y += diff.y; model.boundCenter.copy(center1) }, maintainBoundCenter(model){ model.updateMatrixWorld() let center1 = model.boundCenter.clone();//还未更新的 this.getBoundCenter(model)//更新 let center2 = model.boundCenter.clone(); let diff = new THREE.Vector3().subVectors(center1,center2); model.position.add(diff) model.boundCenter.copy(center1) }, modelTransformCallback(model,force){ model.updateMatrixWorld() if(!force && model.matrixWorld.equals(model.lastMatrixWorld))return viewer.scene.measurements.forEach(measure=>{ let changed measure.points_datasets.forEach((dataset_id,i)=>{ if(dataset_id == model.dataset_id){ changed = true measure.points[i] = Potree.Utils.datasetPosTransform({fromDataset:true,datasetId:dataset_id, position:measure.dataset_points[i].clone()}) measure.updateMarker(measure.markers[i], measure.points[i]) } }) if(changed){//仿transformByPointcloud measure.getPoint2dInfo(measure.points) measure.update() measure.setSelected(false)//隐藏edgelabel } }) viewer.tags.children.forEach(tag=>{ if(tag.root == model){ tag.titleLabel.updatePose() } }) //反向求transformMatrix 参考Alignment.js 移动漫游点 if(model.isPointcloud && model.transformMatrix){ model.transformMatrix.multiplyMatrices(model.matrix, model.pos1MatrixInvert) model.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(model.rotation); model.panos.forEach(e=>e.transformByPointcloud()) }else if(model.panos){ model.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(model.rotation).multiply(model.rot1MatrixInvert); model.transformMatrix.multiplyMatrices(model.matrix, model.posRot1MatrixInvert) model.panos.forEach(e=>e.transformByPointcloud()) model.bound = model.boundingBox.clone().applyMatrix4(model.matrixWorld) } if(model.panos){ model.transformInvMatrix.copy(model.transformMatrix).invert() model.rotateInvMatrix.copy(model.rotateMatrix).invert() model.panos.forEach(e=>{ e.marker.scale.copy(model.scale) }) } model.lastMatrixWorld = model.matrixWorld.clone() viewer.dispatchEvent('content_changed') viewer.mapViewer && Potree.settings.showObjectsOnMap && viewer.mapViewer.dispatchEvent('content_changed') //-------- this.selected == model && this.updateBoxHelper(model) }, changeOpacity(model, opacity){ let isRoot = model.dataset_id != void 0 //是否是最外层 if(model.isPointcloud){ model.changePointOpacity(opacity) //MergeEditor.updateEdgeStrength() }else{ //model.traverse(e=>e.material && setOp(e, opacity)) model.traverse(mesh=>{ if(mesh.material){ let mats = (mesh.material instanceof Array) ? mesh.material : [mesh.material] mats.forEach(mat=>{ if(mat.originOpacity == void 0 ){ mat.originOpacity = mesh.material.opacity } mat.opacity = mat.originOpacity * opacity if(mat.opacity<1){ mat.transparent = true /* if(model.isPointcloud){ mesh.changePointOpacity(realOpacity) }else{ mesh.material.opacity = realOpacity } */ mesh.renderOrder = Potree.config.renderOrders.model+1 //如果是一个mesh多个material咋整? obj的。 暂时默认全部opacity一样吧 //mesh.material.depthWrite = false }else{ mat.transparent = false mesh.renderOrder = Potree.config.renderOrders.model //mesh.material.depthWrite = true } mat.depthWrite = mat.opacity > 0.5 //防止的mesh之间完全遮挡,去掉write。write为true会完全遮挡后排的物体。没有write之后需要对渲染排序(three会排序,但有的角度会错) }) } }) } isRoot && (model.opacity = opacity)//记录在最外层 viewer.dispatchEvent('content_changed') }, addTitleForModel(model){ var titleLabel = new TextSprite({ startClipDis : 0.5, clipDistance : 1,//消失距离 startOcclusDis: 0.5, occlusionDistance: 0.9,//变为backColor距离 maxOcclusionFactor:0.7, maxClipFactor:1, text:model.name, sizeInfo:{width2d:150}, rectBorderThick:1, borderColor:{r:200,g:200,b:200,a:0.5}, textColor:{r:255 ,g:255,b:255,a:1.0}, textshadowColor:'black', backgroundColor:{r: 100,g:100,b:100,a:0.3}, borderRadius: 6, fontsize: 20, fontWeight:'',//thick renderOrder : Potree.config.renderOrders.tag.label, pickOrder: Potree.config.renderOrders.tag.label, useDepth : true , maxLineWidth: 300, transform2Dpercent:{x:0,y:1.3}, //向上移动 textAlign: Potree.settings.isOfficial && 'left' }) model.titleLabel = titleLabel viewer.scene.scene.add(titleLabel) let updatePos = ()=>{ titleLabel.position.copy(model.boundCenter) titleLabel.position.z += model.boundSize.z / 2 //暂时加载模型顶部,但也可能如果旋转了要在头顶 titleLabel.updatePose() } let setVisible = ()=>{ Potree.Utils.updateVisible(titleLabel,'followModel', model.visible) viewer.dispatchEvent('content_changed') } model.addEventListener('transformChanged',updatePos) model.addEventListener('isVisible',setVisible) titleLabel.addEventListener('isVisible',()=>{ titleLabel.visible && updatePos() }) setVisible() updatePos() }, updateMemoryUsage1(){ //obj暂时不管其贴图大小, 因为顶点造成的不仅是内存还有卡顿所以先只看顶点 const maxMemory = Potree.config.tiles3DMaxMemory + 100 //M 实际估计是这个的10倍 const eachObjPosWeight = 100/1000/1000 //M 每个顶点pos是3*4个字节?法线3*4和uv2*4 其实还有贴图 const eachCloudPointWeight = 12/1000/1000 //M 每个点 pos + 颜色 + 法线 大概 const eachVisiCPointWeight = eachCloudPointWeight * 5 // 或 maxMemory / (6*1000*1000) 大概值接近 (再除以一个数是因为显示的要比内存中的耗更多资源 const eachGltfPosWeight = 100/1000/1000 //M 每个顶点pos是3*4个字节?法线3*4和uv2*4 其实还有贴图 let posCount=0 let eachTexPxWeight = 1 / 10 / 1000 / 1000 //不知道“图片缓存”会不会引起崩溃, 它不在“内存占用空间”中, 所以单位weight值设置小一点 let panosWeight = viewer.images360.panos.filter(e=>e.depthTex).length * 0.7 + viewer.images360.tileDownloader.tilesCount * 512*512 * eachTexPxWeight //深度图和全景图 let texArea = 0 viewer.objs.children.forEach(e=>{ if(!e.visible)return if(e.fileType == 'glb' || e.fileType == 'obj'){ e.traverse((mesh)=>{ if(mesh.geometry){ posCount += mesh.geometry.attributes.position.count } if(mesh.material?.map){ texArea += mesh.material?.map.image.width * mesh.material?.map.image.height } }) }else if(e.fileType == '3dTiles'){ } }) //获取点云的内存限制 let objWeight = posCount*eachObjPosWeight + texArea * eachTexPxWeight let laserWeight = Potree.numVisiblePoints * eachCloudPointWeight //点云实际显示所占大小 let laserMemoryWeight = Potree.lru.numPoints * eachCloudPointWeight //点云所使用内存大小 let tiles3DWeight = (viewer.visiVertexCount || 0) * eachGltfPosWeight //M 3dTiles所占内存大小 let tiles3DMemoryWeight = viewer.tiles3dMemoryUsage / 1000 / 1000 //M 3dTiles显示的所占内存大小 /* let min = 0.1, max = 6, minP = 100, maxP = 1000000; let ratio = Math.round(math.linearClamp(score, minP, maxP, max, min )); */ let rest = maxMemory - objWeight - tiles3DWeight - panosWeight Potree.pointBudget = THREE.Math.clamp(Math.round(rest/eachVisiCPointWeight), Potree.config.pointDensity.low.pointBudget, 1.5*Potree.config.pointDensity.high.pointBudget) //Potree.settings.maxLRUPoints //获取3dTiles的内存限制 let tiles3DMaxMemory = maxMemory - Math.round(objWeight + laserWeight + panosWeight) window.cesiumViewer && (tiles3DMaxMemory-=30) Potree.settings.tiles3DMaxMemory = THREE.Math.clamp(tiles3DMaxMemory , 30, Potree.config.tiles3DMaxMemory ) //还存在的问题:仍然有隐患,因为没用到真实缓存的大小: tiles3DMemoryWeight laserMemoryWeight, 它们比真实可见的要多。不使用是因为它们无法反应出实际需要的内存量,缓存是只增不减 //obj等普通mesh限制不了 //console.log('objWeight',objWeight.toFixed(1), 'laserMemoryWeight',laserMemoryWeight.toFixed(1), 'tiles3DWeight',tiles3DWeight.toFixed(1), 'pointBudget',Potree.pointBudget, 'tiles3DMaxMemory',tiles3DMaxMemory) //总内存 = 内存占用空间+图片缓存 , obj的缓存比较多在图片中 //尽量使任务管理器里的内存占用空间不超过2G /* 崩溃历史: cesium容易先崩溃,并弹窗报An error occurred while rendering. Rendering has stopped. 体育中心:http://192.168.0.140/index.html?caseId=1&app=1&token=1#/fuseEdit/path 罗敏电脑崩溃 ,报了ces的崩溃错误。内存占用空间2196M, memory.usedJSheapSize:1812M 崩溃时正在外部浏览模型(导览崩溃过),可能是3dtiles */ }, updateMemoryUsage(){//新 注意 模型即使隐藏也不会降低内存占用,只是会降低卡顿。 未支持3dgs //疑问:1 在不超过最大内存的前提下会因为显示太多模型崩溃吗? 2如何获知每个浏览器最大内存支持?看了一圈,jsHeapSizeLimit都一样怎么办? //3dtiles(或模型)的大小是直接用文件大小还是根据点和贴图尺寸计算呢,感觉它会分成两部分,加载的和显示的,都需要考虑。 //obj暂时不管其贴图大小, 因为顶点造成的不仅是内存还有卡顿所以先只看顶点 if(window.stopUpdateM) return //let start = performance.now(); let old = { pointBudget : Potree.pointBudget, tiles3DMaxMemory: Potree.settings.tiles3DMaxMemory, maxLRUPoints : Potree.settings.maxLRUPoints } const maxMemory = 1500 //1500 //M 整体占用内存限制 (不考虑峰值,要等静止后退下来的值) //什么都不加载可能就占了300M const eachObjPosWeight = 100/1000/1000 //M 每个顶点pos是3*4个字节?法线3*4和uv2*4 + faceIndex比较难说 const eachCloudPointWeight = 70/1000/1000 //M 每个点 pos + 颜色 + 法线 大概 根据测试算出来也是这个值 0.00007 //const eachVisiCPointWeight = eachCloudPointWeight * 5 // 或 maxMemory / (6*1000*1000) 大概值接近 (再除以一个数是因为显示的要比内存中的耗更多资源 const eachTexPxWeight = 1 / 10 / 1000 / 1000 //每个模型都不一样只能取个大概 const eachTexPxWeightTiles = eachTexPxWeight * 1.5 //感觉3dtiles的比glb和obj都高很多,是因为文件还在缓存里吗 let posCount=0, posCount3dTiles = 0, visiPosCount = 0 let panosWeight = 0.1 * (viewer.images360.panos.filter(e=>e.depthTex).length * 0.7 + viewer.images360.tileDownloader.tilesCount * 512*512 * eachTexPxWeight) //深度图和全景图(但似乎是额外放图片缓存里?因为没有增加tex, panoExit后就dispose了, 所以乘以一个小系数。担心图片缓存也可能导致崩溃) //let texArea = 0, texArea3dTiles = 0, visiTexArea = 0 //texArea = viewer.memoryModelCountInfo.otherTexArea + viewer.memoryModelCountInfo.tileTexArea //posCount = viewer.memoryModelCountInfo.otherPosCount + viewer.memoryModelCountInfo.tilePosCount //posCount3dTiles = viewer.memoryModelCountInfo.tilePosCount //texArea3dTiles = viewer.memoryModelCountInfo.tileTexArea let otherModelWeight = viewer.memoryModelCountInfo.otherPosCount*eachObjPosWeight + viewer.memoryModelCountInfo.otherTexArea * eachTexPxWeight let tilesWeight = (viewer.memoryModelCountInfo.tilePosCount*eachObjPosWeight + viewer.memoryModelCountInfo.tileTexArea * eachTexPxWeightTiles) * 1.5 //3dtiles太占内存了 let modelWeight = tilesWeight + otherModelWeight //posCount*eachObjPosWeight + texArea * eachTexPxWeight //为何obj、glb过后会降很多,而3dtiles降的很少? 不过考虑到glb更卡,且加载瞬间确实占用高,还是不把otherModelWeight改少了吧 Potree.tilesWeight = tilesWeight Potree.modelWeight = modelWeight//所有模型 //Potree.modelVisiWeight = visiPosCount*eachObjPosWeight + visiTexArea * eachTexPxWeight //经测试,发现模型并不会因为不可见而降低内存,3dtiles加载满了之后拉远,可见weight下降了但内存没降。 //so,需要测试崩溃是否只与内存有关,如果内存爆了,但都不可见,非常流畅,还会崩溃吗 let laserVisiWeight = Potree.numVisiblePoints * eachCloudPointWeight //点云实际显示所占大小 let laserWeight = Potree.lru.numPoints * eachCloudPointWeight //点云所使用内存大小 Potree.laserWeight = laserWeight let fixedPart = modelWeight - tilesWeight + panosWeight + (window.cesiumViewer ? 30:0) //ces:刚加载时60,静止一段时间后20 Potree.allWeight = Potree.laserWeight + fixedPart + tilesWeight let tiles3dMemoryUsage = viewer.tiles3dMemoryUsage/1024/1024 let overRatio = Potree.allWeight / maxMemory let visiRatio1 = posCount3dTiles == 0 ? Infinity : viewer.visiVertexCount / posCount3dTiles let visiRatio2 = Potree.lru.numPoints == 0 ? Infinity : Potree.numVisiblePoints / Potree.lru.numPoints let resolved let restMemory = maxMemory - fixedPart //去除固定的part后 if(overRatio > 1){ const MinRestMemory = 400 if(restMemory < MinRestMemory){ Common.intervalTool.isWaiting('updateMemoryUsage', ()=>{ let warnText if(this.lastMemoryState.restMemory < 0){ warnText = '固定内存已经超额!没有剩余空间给点云和3dtiles。' }else if(this.lastMemoryState.restMemory < MinRestMemory){ warnText = '固定内存过多!' } if(warnText){ warnText += '请减少glb类模型, 当前固定内存占用大小:' + fixedPart console.error(warnText) } }, 10000) } if(visiRatio1 < visiRatio2 || Potree.lru.numPoints <= Potree.config.pointDensity.low.pointBudget / 10 * 1.1 ){//占比小的需要缩减缓存容器 if(Potree.settings.tiles3DMaxMemory < tiles3dMemoryUsage / 2 && this.lastMemoryState.tiles3dMemoryUsage == tiles3dMemoryUsage //已经很难减少了,这时候tiles3DMaxMemory很可能降到个位数 || Potree.settings.tiles3DMaxMemory < 2){ resolved = false }else{ Potree.settings.tiles3DMaxMemory = Math.min(tiles3dMemoryUsage, Potree.settings.tiles3DMaxMemory)//先降到已使用的数据量 Potree.settings.tiles3DMaxMemory > 1 && (Potree.settings.tiles3DMaxMemory *= 0.8 ) //注意,降低该值不一定会降低 tiles3dMemoryUsage,只有后续改变sse才行 viewer.tiles3dMemoryUsage / tiles3DMaxMemory 可能大于1 resolved = true } } if(!resolved){// visiRatio1 > visiRatio2 if( visiRatio2 > 0.9){//压缩太小了,或者说没什么缓存就超额了,只能继续减少显示个数 if(Potree.pointBudget < Potree.config.pointDensity.low.pointBudget){ //console.error('Potree.pointBudget已经很小了!', Potree.pointBudget) }else{ Potree.pointBudget *= 0.85 resolved = true } }else{ Potree.pointBudget = THREE.Math.clamp(Potree.numVisiblePoints, Potree.config.pointDensity.low.pointBudget / 10, Potree.pointBudget*0.8 )//先往降到可见数量方向降 (当画面中可见点确实很少时允许降低到low之下) Potree.settings.maxLRUPoints = Math.min(Potree.lru.numPoints, Potree.settings.maxLRUPoints);//先降到已使用的数量 Potree.settings.maxLRUPoints *= 0.8 resolved = true } } }else{//恢复部分好难写 //优先恢复实际使用(可见)内存,然后是缓存 //额外缓存少的,实际使用内存被压缩的概率也大 //visiRatio1 == Infinity && (visiRatio1 = 0) //因这里大的才会被改所以改一下 visiRatio1 *= tiles3dMemoryUsage / Potree.settings.tiles3DMaxMemory //用point数量有时候不准,明明内存不够了不可见的tile还存在,所以×memory的占比 visiRatio2 = Potree.pointBudget == 0 ? 0 : Potree.numVisiblePoints / Potree.pointBudget //使用率越高,说明越需要内存. 因为恢复时先提高使用内存所以改一下 let r = Math.min(1, visiRatio1 , visiRatio2 ) //如果另一个visiRatio 很小,s就可以再小一些,使内存超过一点,然后朝另一方倾斜,另一方去缩减 let s = 1 - Math.max(0.8, overRatio * r ) //太难判断是哪个更需要内存了,所以两个都涨吧 if(visiRatio2 > visiRatio1){ let s_ = visiRatio2 / (visiRatio1 + visiRatio2) * s + 1 const addCount = 500000 * s_ if(Potree.pointBudget < Potree.config.pointDensity.high.pointBudget*0.9){ Potree.pointBudget += addCount }else{ Potree.settings.maxLRUPoints += addCount } } if(Potree.settings.tiles3DMaxMemory < Potree.config.tiles3DMaxMemory ){ let s_ = visiRatio1 / (visiRatio1 + visiRatio2) * s + 1 const addCount = 20 * s_ Potree.settings.tiles3DMaxMemory += addCount Potree.settings.tiles3DMaxMemory = Math.min(Potree.settings.tiles3DMaxMemory, Potree.config.tiles3DMaxMemory) } resolved = true } let min = overRatio > 1 ? 1.1 : 1.5, max = 5; Potree.settings.maxLRUPoints = THREE.Math.clamp(Potree.settings.maxLRUPoints , Potree.pointBudget * min, Potree.pointBudget * max) //压缩时先压缩缓存,然后是使用内存; 恢复时相反。 //3dtiles不像点云那样可以在加载时拦截,使不高于tiles3DMaxMemory,只能自动动态调节maxSSE,所以有可能调节完还是降低不了 //一次只更新一点,一点点调节,因为可能不是一个原因造成 let changed if(old.pointBudget != Potree.pointBudget || old.tiles3DMaxMemory != Potree.settings.tiles3DMaxMemory || old.maxLRUPoints != Potree.settings.maxLRUPoints ){ if(old.tiles3DMaxMemory != Potree.settings.tiles3DMaxMemory){ viewer.setAllTilesets(model=>{ model.runtime.getTileset().nextForceUpdate = true model.runtime.getTileset().needRenderNext = true }) }else{ viewer.dispatchEvent('content_changed') } changed = true } { let mSSE = math.linearClamp(Potree.fpsRendered2, [2, 52], [500, 70]) //有效降低卡顿 和贴图变白概率。越卡越提高msse let r = tiles3dMemoryUsage / Potree.settings.tiles3DMaxMemory viewer.setAllTilesets(model=>{ let tileset = model.runtime.getTileset() tileset.options.initialMaxSSE = mSSE let e = tileset.options.maximumScreenSpaceError * THREE.Math.clamp(r, 0.8, 1.2) e = THREE.Math.clamp(e, mSSE, mSSE+400) //如果有很卡的模型可能需要1000,但太高了降回来很慢,就假设模型都正常的吧 tileset.options.maximumScreenSpaceError = e }) } //针对很卡的设备:(卡的也容易崩溃) Potree.config.pointDensity.high.pointBudget = math.linearClamp(Potree.fpsRendered2, [2, 50], [1e6, 6e6]) let history = this.lastMemoryState.history || [] history.push(overRatio) this.lastMemoryState = { tiles3dMemoryUsage, history, restMemory } //console.log('cost', performance.now() - start) return changed && ((overRatio > 1.1 || overRatio < 0.9) || history.slice(-7).length < 7 || history.slice(-7).some(overRatio=>overRatio > 1.1 || overRatio < 0.9) //后十个都在最大内存范围左右抖动,则不继续更新,直到相机变化 ) //难点:要防止抖动,否则3dtiles的sse变大变小,相机静止时还在清晰和模糊间变来变去。好在点云是可以较精细调节,tile是间接控制的 //模型删除后还会占用一定内存,尤其是3dtile几乎降不了,怎么回事?是否删除的模型还要算进来 }, setGroundPlaneImg(src,scale,angle){//设置地面图 this.goundScale = scale || 1, this.goundAngle = angle || 0 let oldSrc = this.curGroundImgSrc this.curGroundImgSrc = src const ratio = 0.03 if(src){ if(oldSrc == src && this.groundPlane.material.map.image){ //仅修改大小 const s = ratio * this.goundScale let {width, height} = this.groundPlane.material.map.image this.groundPlane.scale.set(width*s, height*s) viewer.dispatchEvent('content_changed') this.groundPlane.rotation.z = THREE.Math.degToRad(this.goundAngle) return } let map = texLoader.load(src,(tex)=>{ if(this.curGroundImgSrc == src){ const s = ratio * this.goundScale this.groundPlane.scale.set(tex.image.width*s, tex.image.height*s) this.groundPlane.rotation.z = THREE.Math.degToRad(this.goundAngle) viewer.dispatchEvent('content_changed') } }) Potree.Utils.makeTexDontResize(map) if(!this.groundPlane){ this.groundPlane = new THREE.Mesh(new THREE.PlaneBufferGeometry(1,1,1), new THREE.MeshBasicMaterial({ map, side : 2, })) viewer.scene.scene.add(this.groundPlane) this.groundPlane.position.z = 0.1 }else{ this.groundPlane.material.map = map } Potree.Utils.updateVisible(this.groundPlane,'show',true ) }else{ this.groundPlane && Potree.Utils.updateVisible(this.groundPlane,'show',false ) } } } /* watch: performance.memory.usedJSHeapSize / 1e6 Potree.settings.tiles3DMaxMemory viewer.tiles3dMemoryUsage Potree.tilesWeight Potree.modelWeight Potree.modelVisiWeight Potree.allWeight Potree.lru.numPoints Potree.pointBudget Potree.numVisiblePoints Potree.settings.maxLRUPoints */ export default MergeEditor /* class titleLabel extends THREE.Shim.FollowRootObject{ constructor(o){ super(o.root) this.l } } */ /* note: 要注意getHoveredElements只在getIntersect时才使interactables包含加载的model, 也就是model上不能有使之成为interactables的事件,否则在鼠标hover到模型上开始转动的一瞬间很卡。 控制台断开完全重合的漫游点的方法 window.pano1 = viewer.modules.PanoEditor.selectedPano //选中第一个点后输入这行,得到第一个漫游点 window.pano2 = viewer.modules.PanoEditor.selectedPano //选中第二个点后输入这行,得到第二个漫游点 viewer.modules.PanoEditor.linkChange(window.pano1, window.pano2, 'remove') //断开链接 如果直接刷新,内存管理器中的内存会有之前的残留。不知道是不是真实的加入这次的内存了。 并且,如果第一次加载的是几乎不占内存的模型,如glb,内存也会一下子涨30,不过等一会儿会降。4M的3dtiles会涨60. 每次加载模型后都会内存突增,但过后会降。 为何obj、glb过后会降很多,而3dtiles降的很少。? 国产系统8192贴图直接出错,4096的贴图过多也出错。texImage: Driver ran out of memory during upload. */