import * as THREE from "../../libs/three.js/build/three.module.js"; import {settings, config} from './settings.js' import math from './utils/math.js' import browser from './utils/browser.js' import {Utils} from "./../utils.js" import cameraLight from './utils/cameraLight.js' import './three.shim.js' import "./potree.shim.js" //多元融合模块 var start = function(dom, mapDom, number, fileServer, webSite){ //t-Zvd3w0m //设置: Potree.settings.editType = 'merge' Potree.settings.sidebar = 'sidebar2.html' Potree.settings.intersectOnObjs = true Potree.settings.boundAddObjs = true Potree.settings.showCompass = true Potree.settings.number = number || 't-o5YMR13'// 't-iksBApb'// 写在viewer前 Potree.fileServer = fileServer webSite && (Potree.settings.webSite = webSite) let viewer = new Potree.Viewer(dom , mapDom); let Alignment = viewer.modules.Alignment viewer.setEDLEnabled(false); viewer.setFOV(config.view.fov); viewer.loadSettingsFromURL(); { viewer.mainViewport.view.position.set(100,100,200) viewer.mainViewport.view.lookAt(0,0,0) viewer.updateModelBound()//init //this.bound = new THREE.Box3(new THREE.Vector3(-1,-1,-1),new THREE.Vector3(1,1,1)) viewer.transformationTool.setModeEnable('scale',false) viewer.ssaaRenderPass.sampleLevel = 0 //奇怪好像没啥锯齿? sampleLevel为1 的话,ground就不会 } if(!Potree.settings.isOfficial){ viewer.loadGUI(() => { viewer.setLanguage('en'); //$("#menu_appearance").next().show(); //$("#menu_tools").next().show(); //$("#menu_scene").next().hide(); $("#mergeModel").show(); viewer.toggleSidebar(); }); Potree.settings.sizeFitToLevel = true//当type为衰减模式时自动根据level调节大小。每长一级,大小就除以2 } Potree.loadDatasetsCallback = function(data, ifReload){ if(!data || data.length == 0)return console.error('getDataSet加载的数据为空') Potree.datasetData = data viewer.transform = null var datasetLength = data.length var pointcloudLoaded = 0 var panosLoaded = 0 var pointcloudLoadDone = function(){//点云cloud.js加载完毕后 viewer.updateModelBound() let {boundSize, center} = viewer.bound Potree.Log(`中心点: ${math.toPrecision(center.toArray(),2)}, boundSize: ${math.toPrecision(boundSize.toArray(),2)} ` , null, 12) if(!Potree.settings.isOfficial){ Potree.loadMapEntity('all') //加载floorplan } if(!ifReload){ /* viewer.scene.view.setView({ position: center.clone().add(new THREE.Vector3(10,5,10)), target: center }) */ viewer.dispatchEvent({type:'loadPointCloudDone'}) if(!Potree.settings.UserPointDensity){ Potree.settings.UserPointDensity = 'high'//'middle' } Potree.Log('loadPointCloudDone 点云加载完毕', null, 10) } } var panosLoadDone = function(){ viewer.images360.loadDone() viewer.scene.add360Images(viewer.images360); {//初始位置 var urlFirstView = false var panoId = browser.urlHasValue('pano',true); if(panoId !== ''){ var pos var pano = viewer.images360.panos.find(e=>e.id==panoId); if(pano){ viewer.images360.focusPano({ pano, duration:0, callback:()=>{/* Potree.settings.displayMode = 'showPanos' */} }) } }else{//考虑到多数据集距离很远,或者像隧道那种场景,要使视野范围内一定能看到点云,最好初始点设置在漫游点上 let {boundSize, center} = viewer.bound //let pano = viewer.images360.findNearestPano(center) } } console.log('allLoaded') viewer.dispatchEvent('allLoaded') } var transformPointcloud = (pointcloud, dataset)=>{ var locationLonLat = dataset.location.slice(0,2) //当只有一个dataset时,无论如何transform 点云和漫游点都能对应上。 var location = viewer.transform.lonlatToLocal.forward(locationLonLat) //transform.inverse() //初始化位置 viewer.sidebar && viewer.sidebar.addAlignmentButton(pointcloud) Alignment.rotate(pointcloud, null, dataset.orientation) Alignment.translate(pointcloud, new THREE.Vector3(location[0], location[1], dataset.location[2])) pointcloud.updateMatrixWorld() Potree.Log(`点云${pointcloud.dataset_id}旋转值:${pointcloud.orientationUser}, 位置${math.toPrecision(pointcloud.translateUser.toArray(),3)}, 经纬度 ${locationLonLat}, spacing ${pointcloud.material.spacing}`, null, 17 ) } if(!Potree.settings.originDatasetId)Potree.settings.originDatasetId = data[0].id var originDataset = data.find(e=>e.id == Potree.settings.originDatasetId) {//拿初始数据集作为基准。它的位置是000 var locationLonLat = originDataset.location.slice(0,2) proj4.defs("NAVVIS:TMERC", "+proj=tmerc +ellps=WGS84 +lon_0=" + locationLonLat[0].toPrecision(15) + " +lat_0=" + locationLonLat[1].toPrecision(15)); proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"); let transform1 = proj4("WGS84", "NAVVIS:TMERC"); //这个ok TMERC是展开的平面投影 let transform2 = proj4("+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs;"); viewer.transform = { lonlatToLocal : transform1, lonlatTo4550 : transform2 // 转大地坐标EPSG:4550 } viewer.mapViewer && viewer.mapViewer.mapLayer.maps[0].updateProjection() } data.forEach((dataset,index)=>{ if(!ifReload){ var datasetCode = dataset.sceneCode || dataset.name //对应4dkk的场景码 var cloudPath = `${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${datasetCode}/data/${datasetCode}/webcloud/cloud.js` var timeStamp = dataset.createTime ? dataset.createTime.replace(/[^0-9]/ig,'') : ''; //每重算一次后缀随createTime更新一次 //console.warn(dataset.name, 'timeStamp', timeStamp) Potree.loadPointCloud(cloudPath, dataset.name ,datasetCode, timeStamp, e => { let scene = viewer.scene; let pointcloud = e.pointcloud; let config = Potree.config.material let material = pointcloud.material; material.minSize = config.minSize material.maxSize = config.maxSize material.pointSizeType = config.pointSizeType //Potree.PointSizeType[config.pointSizeType]//Potree.PointSizeType.ADAPTIVE;//FIXED pointcloud.changePointSize(config.realPointSize) //material.size = config.pointSize; pointcloud.changePointOpacity(1) material.shape = Potree.PointShape.SQUARE; pointcloud.color = pointcloud.material.color = dataset.color pointcloud.dataset_id = dataset.id;//供漫游点找到属于的dataset点云 pointcloud.timeStamp = timeStamp transformPointcloud(pointcloud,dataset) scene.addPointCloud(pointcloud); pointcloudLoaded ++; if(pointcloudLoaded == datasetLength)pointcloudLoadDone() Potree.loadPanos(dataset.id, (data) => { //console.log('loadPanos',dataset.sceneCode, dataset.id, data) viewer.images360.addPanoData(data, dataset.id ) panosLoaded ++; if(panosLoaded == datasetLength){ panosLoadDone() } }) }) }else{ let pointcloud = viewer.scene.pointclouds.find(p => p.dataset_id == dataset.id) if(!pointcloud){ Potree.Log('数据集id变了,自动使用第一个','#500') pointcloud = viewer.scene.pointclouds[0] } //先归零 Alignment.translate(pointcloud, pointcloud.translateUser.clone().negate()) Alignment.rotate(pointcloud, null, - pointcloud.orientationUser) transformPointcloud(pointcloud, dataset) } }) } let setMatrix = (pointcloud)=>{//为了漫游点变换,要算一下 类似setMatrix pointcloud.updateMatrixWorld() /* pointcloud.transformMatrix = new THREE.Matrix4().multiplyMatrices(pointcloud.matrix, pointcloud.pos1MatrixInvert)//还原一点位移 pointcloud.transformInvMatrix.copy(pointcloud.transformMatrix).invert() pointcloud.rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(pointcloud.rotation); pointcloud.rotateInvMatrix.copy(pointcloud.rotateMatrix).invert() pointcloud.panos.forEach(e=>e.transformByPointcloud()) */ pointcloud.updateBound() pointcloud.getPanosBound() viewer.updateModelBound() } let moveModel = (e)=>{//根据鼠标移动的位置改变位置 let camera = viewer.mainViewport.camera var origin = new THREE.Vector3(e.pointer.x, e.pointer.y, -1).unproject(camera), end = new THREE.Vector3(e.pointer.x, e.pointer.y, 1).unproject(camera) var dir = end.sub(origin) let planeZ = 0; let r = (planeZ - origin.z)/dir.z let x = r * dir.x + origin.x let y = r * dir.y + origin.y /* if(modelType == 'laser'){ // modelEditing.translateUser.copy(pos) //Alignment.setMatrix(modelEditing) let pos = new THREE.Vector3(x,y, planeZ ) modelEditing.position.copy(modelEditing.initialPosition).add(pos) }else{ let pos = new THREE.Vector3(x,y, modelEditing.position.z ) modelEditing.position.copy(pos) } */ MergeEditor.moveBoundCenterTo(modelEditing,new THREE.Vector3(x,y, modelEditing.boundCenter.z)) //使模型中心的xy在鼠标所在位置 modelEditing.dispatchEvent("position_changed") } let cancelMove = ()=>{ modelEditing = null viewer.removeEventListener('global_mousemove', moveModel); viewer.removeEventListener('global_click', confirmPos); } let confirmPos = ()=>{ focusOnSelect(modelEditing) cancelMove() return {stopContinue:true} } let focusOnSelect = (object, duration = 400)=>{ let boundingBox = object.boundingBox.clone().applyMatrix4(object.matrixWorld) let center = boundingBox.getCenter(new THREE.Vector3) let size = boundingBox.getSize(new THREE.Vector3) let maxSize = size.length() //对角线长度 if(object.isPointcloud){ maxSize /= 2 } let hfov = cameraLight.getHFOVForCamera(viewer.mainViewport.camera,true) let minRadius = maxSize / Math.tan(hfov/2) //viewer.mainViewport.view.lookAt(center) viewer.mainViewport.view.setView({ position: center.clone().sub(viewer.mainViewport.view.direction.clone().multiplyScalar(minRadius)), target: center, duration }) //setView can cancel bump } viewer.setControls(viewer.orbitControls) let tilesetUrls = [ 'https://4dkk.4dage.com/fusion/test/b3dm/tileset.json', //高层小区 'https://testgis.4dage.com/LVBADUI_qp/tileset.json', //村庄 'https://4dkk.4dage.com/fusion/testb3dm/modelId_613/tileset.json',//"952.16MB" 港一 'https://4dkk.4dage.com/fusion/testb3dm/modelId_609/tileset.json',//618.37MB 田野 'https://4dkk.4dage.com/fusion/test/b3dmtest001/tileset.json', 'https://4dkk.4dage.com/fusion/test/model/modelId_614/tileset.json',//172.97MB 国家电网 //'https://4dkk.4dage.com/fusion/test/model/modelId_602/tileset.json', 'https://4dkk.4dage.com/fusion/test/model/modelId_602/Tile_016_011/tileset.json', //modelId_614的一部分 //'https://4dkk.4dage.com/fusion/test/model/modelId_570/3dt/3dtiles.json', //only has boundingVolume.sphere 拉远了特别模糊,凑近了模型不太对 ], tileIndex = 0 let modelType, modelEditing, MergeEditor = viewer.modules.MergeEditor Potree.addModel = function(name, done){ let isFirstLoad = true cancelMove() modelType = name let loadDone = (model)=>{ if(isFirstLoad){ modelEditing = model; MergeEditor.setModelBtmHeight(model, 0) //默认离地高度为0 /* if(name == '3dTiles'){ setTimeout(()=>{ moveModel({pointer:{x:0,y:0}}) //3dTiles的移动会错乱,先默认放在当前视图中间吧 confirmPos() },1) }else{ */ viewer.addEventListener('global_mousemove', moveModel); viewer.addEventListener('global_click', confirmPos, 3); //} }else{ modelEditing = null } {//transform let updateBound = ()=>{ model.updateMatrixWorld() viewer.updateModelBound() } let maintainBtmZAndCenter = ()=>{ MergeEditor.maintainBoundXY(model) MergeEditor.setModelBtmHeight(model) updateBound() model.dispatchEvent('transformChanged') } model.addEventListener('position_changed', ()=>{ updateBound() MergeEditor.getBoundCenter(model);//更新boundcenter MergeEditor.computeBtmHeight(model) model.dispatchEvent('transformChanged') }) model.addEventListener("rotation_changed", maintainBtmZAndCenter ) model.addEventListener("scale_changed", maintainBtmZAndCenter ) model.addEventListener('transformChanged', ()=>{ MergeEditor.modelTransformCallback(model) }) } model.updateMatrixWorld() viewer.updateModelBound() model.lastMatrixWorld = model.matrixWorld.clone(); MergeEditor.getBoundCenter(model) //初始化 model.addEventListener('changeSelect',(e)=>{ e.selected ? MergeEditor.transformControls.attach(model) : MergeEditor.transformControls.detach() //viewer.transformObject(e.selected ? model : null); }) done(model) } if(name == 'laser'){ Potree.loadDatasets(Potree.loadDatasetsCallback) viewer.addEventListener('allLoaded',()=>{ let pointcloud = modelEditing = viewer.scene.pointclouds[0]; pointcloud.matrixAutoUpdate = true pointcloud.initialPosition = pointcloud.position.clone() /* pointcloud.addEventListener('select',(e)=>{ if(Potree.settings.displayMode == 'showPanos')return console.log('select',e) //viewer.setControls(viewer.orbitControls) focusOnSelect(pointcloud) viewer.outlinePass.selectedObjects = [pointcloud] return {stopContinue:true} },1) pointcloud.addEventListener('deselect',(e)=>{ console.log('deselect',e) //viewer.setControls(viewer.fpControls) viewer.outlinePass.selectedObjects = [] }) */ loadDone(pointcloud) }) }else{ let callback = (object)=>{ //focusOnSelect(object, 1000) object.traverse(e=>e.material && (e.material.transparent = true)) object.isModel = true object.dataset_id = Date.now() //暂时 /* object.addEventListener('select',(e)=>{ if(Potree.settings.displayMode == 'showPanos')return console.log('select',e) viewer.setControls(viewer.orbitControls) focusOnSelect(object) viewer.outlinePass.selectedObjects = [object] return {stopContinue:true} },1) object.addEventListener('deselect',(e)=>{ console.log('deselect',e) viewer.setControls(viewer.fpControls) viewer.outlinePass.selectedObjects = [] }) */ /* object.addEventListener('click',(e)=>{ //只是为了能得到hoverElement识别才加这个侦听 }) */ $('#log').text('') loadDone(object) } let onprogress = (num)=>{ $('#log').text("已加载 " + Math.round(num) +" %") } if(name == '4dkk'){ let num = 't-yAWONOn' var path = `${Potree.scriptPath}/data/4dkk/${num}/` viewer.loadModel({ name, objurl: path+'mesh.obj', //0.6s mtlurl: path+'mesh.mtl', unlit:true, transform :{ rotation : [0, 0, 0], position : [0,0,0] } },callback,onprogress) }else if(name == 'obj'){ /* var path = `${Potree.resourcePath}/models/13/` viewer.loadModel({ name, objurl: path+'w13.OBJ', mtlurl: path+'w13.mtl', transform : { rotation : [Math.PI/2, 0, 0], position : [0,0,0] } },callback,onprogress) */ var path = `${Potree.resourcePath}/models/obj/28M/` viewer.loadModel({ name, objurl: path+'GW1H.obj', //解析时间4.392s mtlurl: path+'GW1H.mtl', transform : { rotation : [0, 0, 0], position : [0,0,0] } },callback,onprogress) /* var path = `${Potree.resourcePath}/models/obj/75M/` viewer.loadModel({ name, objurl: path+'Tile_+070_+051.obj', //解析时间4.945s mtlurl: path+'Tile_+070_+051.mtl', transform : { rotation : [0, 0, 0], position : [0,0,0] } },callback,onprogress) */ /* var path = `${Potree.resourcePath}/models/obj/496M/` viewer.loadModel({ name, objurl: path+'Model.obj', //解析时间25.629 s 期间弹出崩溃提示。很卡 mtlurl: path+'Model.mtl', transform : { rotation : [0, 0, 0], position : [0,0,0] } },callback,onprogress) */ }else if(name == 'glb'){ let angle = 0 let fileName = '87b3a367bc3e4273832cb4fa398782e5.glb' /* let angle = Math.PI/2 let fileName = 'coffeemat.glb', //0.3s */ /* let angle = Math.PI/2 let fileName = 'ModernJPHouseSofa44216499.glb' // 21M */ /* let angle = 0 let fileName = 'cloud_glb_47_loadErrored.glb' //176M */ var path = `${Potree.resourcePath}/models/glb/` viewer.loadModel({ fileType:'glb', name, url: path+fileName, transform : { rotation : [angle, 0, 0], position : [0,0,0] } },callback,onprogress) }else if(name == '3dTiles'){ viewer.loadModel({ fileType:'3dTiles', url: tilesetUrls[tileIndex++], transform : { rotation : [Math.PI/2, 0, 0], position : [0,0,0] } },callback,onprogress) } } } let getModelByName = (name)=>{ if(name == 'laser'){ return viewer.scene.pointclouds[0] }else{ return viewer.objs.children.find(e=>e.name == name) } } Potree.removeModel = function(name){ let model = getModelByName(name) if(name == 'laser'){ viewer.scene.removePointCloud(model); viewer.updateModelBound() }else{ viewer.removeObj(model) } cancelMove() } /* Potree.selectModel = function(name){ let model = getModelByName(name) model && viewer.transformObject(model) } */ //Potree.loadDatasets(Potree.loadDatasetsCallback) viewer.inputHandler.addEventListener('keydown', (e)=>{ if(e.event.key == "e" ){ MergeEditor.transformControls.mode = 'rotate' }else if(e.event.key == "w"){ MergeEditor.transformControls.mode = 'translate' } }) window.THREE = THREE window.buttonFunction = function(){ viewer.scene.pointclouds.forEach(e=>e.predictNodeMaxLevel()) } } /* var changeLog = ()=>{ //如果移动端加了test反而出不来bug的话,用这个 var textarea = document.createElement('textarea'); textarea.id = "consoleLog"; textarea.style.width = '160px'; textarea.style.height = '200px' textarea.style.position = 'fixed' textarea.style.right = 0 textarea.style.bottom = '0' textarea.style['z-index'] = 9999; textarea.style.color = 'black'; textarea.style.opacity = 0.9; textarea.style['font-size'] = '12px'; textarea.style['backgroundColor'] = '#ffffff' document.getElementsByTagName("body")[0].appendChild(textarea); var list = ["log", "error", "warn", "debug", "info", "time", "timeEnd"] var exchange = function (o) { console["old" + o] = console[o]; console[o] = function () { var args = Array.from(arguments) console["old" + o].apply(this, arguments) var t = document.getElementById("consoleLog").innerHTML; var str = '' args.forEach(a=>{ str += a + ' ' }) document.getElementById("consoleLog").innerHTML = str + "\n\n" + t; } } for (var i = 0; i < list.length; i++) { exchange(list[i]) } } changeLog() */ //可以直接用edlShader来渲染obj的outline,但不能渲染被遮挡的部分 export {start}