import mitt from 'mitt' import axios from 'axios' //{ axios } from '@/api' let requestLoadCount = 0 export const enter = (dom, mapDom, isLocal, lonlat) => { console.warn('新的页面') Potree.settings.isOfficial = true //标记为正式、非测试版本 //Potree.fileServer = axios Potree.settings.libsUrl = './lib/' //正式环境(本地调试会打不开) if(location.host === 'mix3d.4dkankan.com' ){ Potree.settings.urls.prefix = Potree.settings.urls.prefix6 Potree.settings.webSite = 'datav1' }else if(location.host === 'xfhd.4dkankan.com'){ Potree.settings.urls.prefix = Potree.settings.urls.prefix7 Potree.settings.webSite = 'datav1' } const tagLimitDis = 8; Potree.settings.showCompass = true Potree.settings.compassDom = dom.querySelector('#direction') Potree.settings.showObjectsOnMap = true let {THREE} = Potree.mergeEditStart(dom, mapDom) let MergeEditor = viewer.modules.MergeEditor let sceneBus = mitt() viewer.addEventListener('camera_changed', e => { var camera = e.viewport.camera var pos = camera.position if (e.viewport.name == 'MainView') { sceneBus.emit('cameraChange', { x: pos.x, y: pos.y, z: pos.z, rotate: camera.rotation }) } }) viewer.addEventListener('webglError', e => { console.error('viewer webglError: ' + e) sceneBus.emit('webglError', { msg: e.msg }) }) { let index = 1; //let setDisplay() if(!Potree.isIframeChild){ /* viewer.addEventListener('createIframe',(e)=>{//创建了子页面 }) */ window.winIndex = 0; window.iframeCreated = function(iframe){ let child = iframe.contentWindow child.winIndex = index++ //案件里视图提取页面子页面覆盖了父级页面,父级的模型可以隐藏以释放内存 console.error('createdIframe', child.winIndex, child.location.href) viewer.setDisplay(false ) child.beforeDestroy = function(){ //注:在前端仍会找不到beforeDestroy,可能contentWindow变更??所以手动调用setDisplay console.warn('beforeDestroy', child.winIndex) child.viewer && child.viewer.setDisplay(false) //如果是四维看看的场景,先不管了,页面被销毁应该就没了吧 viewer.setDisplay(true )//恢复主页的模型显示 if(!child.viewer){ try{ let player = child.__sdk.core.get('Player') /* let runtime = player.model._3dTilesRuntime let tileset = runtime.getTileset() tileset._cache.trim(); //使下一次update时dispose所有不可见的tiles let sceneRenderer = child.__sdk.core.get('SceneRenderer') player.model.visible = false runtime.update(16, sceneRenderer.renderer, sceneRenderer.camera, true) //没用,为何_trimTiles的while无法进入 */ player.model.traverse(e=>{ e.geometry && e.geometry.dispose() if(e.material){ e.material.map && e.material.map.dispose() if(e.material.uniforms && e.material.uniforms.map && e.material.uniforms.map.value){ e.material.uniforms.map.value.dispose() } } }) //效果甚微 /* let sceneRenderer = child.__sdk.core.get('SceneRenderer') sceneRenderer.renderer.render(sceneRenderer.scene, sceneRenderer.camera) */ }catch(e){ console.log(e) } } } } //不知道删除iframe时是否那些模型还在内存里,需要释放吗? 如果要需要加一个事件 }else{ } } window.THREE = THREE //isLocal = false let autoLoads = [] let readyToAddModel let maxLoadingCount = /* isLocal ? 1 : */2; //正在加载模型的最大数目 let mainBackground = viewer.background const units = { 1: 'metric', 2: 'imperial' } let getMeasureType = function (type, unit=1) { let info switch (type) { case 'free': info = { measureType: 'Distance' } break case 'area': info = { measureType: 'Area' } break case 'vertical': info = { measureType: 'Ver Distance' } break default: console.error('无此 measure type') } info.unit = units[unit] return info } let getMeasureFunction = function (measure, bus) { measure.addEventListener('highlight',(e)=>{ //console.log('3d->2d highlight',e.state) bus.emit('highlight', e.state) }) measure.addEventListener('marker_dropped',(e)=>{//拖拽结束后发送changeCallBack if (measure.parent) { //未被删除 bus.emit('update',[ measure.dataset_points.map(p=>p.clone()) , measure.points_datasets ]) } }) return { /* quit: () => { Potree.Log('quit结束且删除: ' + measure.id, '#00c7b2') viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) }, //触发结束。退出测量模式,清除之前操作 */ destroy: () => { viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) viewer.scene.removeMeasurement(measure) }, /* getPoints: () => { return measure.points }, getDatasetLocations: () => { return measure.dataset_points }, getDatasets: () => { return measure.points_datasets }, getDatasetId: () => { return measure.datasetId }, */ getArea: () => { return measure.area //{value:area, string:..} }, getDistance: () => { if (measure.points.length < 2) return 0 var value = measure.points[0].distanceTo(measure.points[1]) return { value, //米 string: measure.getConvertString(value, 'distance') } }, //手动开启或关闭: show: () => { Potree.Utils.updateVisible(measure, 'inListByUser', true) }, hide: () => { Potree.Utils.updateVisible(measure, 'inListByUser', false) }, fly(){ let result = viewer.focusOnObject(measure , 'measure', 1200 ) return result.msg ? result.msg : result.promise //返回值 1 deferred 表示即将位移 2 'posNoChange' 表示已在最佳位置 3 'tooFar' 表示距离最佳位置太远 }, changeSelect(isHight){ console.log('2d->3d isHight ',isHight) measure.setSelected(isHight, 'byList') }, } } let sdk = { sceneBus, getPositionByScreen(pos2d, hopeModelId ){//通过屏幕坐标获取真实坐标 . hopeModelId: 如果指定了模型,优先返回hopeModelId上的intersect //console.log('getPositionByScreen',hopeModelId) hopeModelId = null let worldPos, localPos, modelId, intersect let Handler = viewer.inputHandler let reGet = ()=>{//不使用当前鼠标所在位置的intersect,单独算 pos2d.clientX = pos2d.x pos2d.clientY = pos2d.y pos2d.onlyGetIntersect = true pos2d.whichPointcloud = true if(hopeModelId != void 0){//隐藏其他的模型 let models = MergeEditor.getAllObjects() models.forEach(model=>{ Potree.Utils.updateVisible(model, 'forPick', model.dataset_id == hopeModelId) }) } let intersect2 = Handler.onMouseMove(pos2d) if(hopeModelId != void 0){//恢复 let models = MergeEditor.getAllObjects() models.forEach(model=>{ Potree.Utils.updateVisible(model, 'forPick', true) }) } if(intersect2 && intersect2.location){ intersect = intersect2 } } if (pos2d && pos2d.inDrag) { reGet() } else { intersect = Handler.intersect if(intersect){ modelId = intersect.pointcloud ? intersect.pointcloud.dataset_id : intersect.object.dataset_id if(hopeModelId != void 0 && modelId != hopeModelId){ reGet() } } } if (intersect && intersect.location) { modelId = intersect.pointcloud ? intersect.pointcloud.dataset_id : intersect.object.dataset_id /* if(hopeModelId != void 0 && modelId != hopeModelId){ return null } */ worldPos = intersect.location.clone() localPos = Potree.Utils.datasetPosTransform({ toDataset: true, datasetId:modelId, position:worldPos }) } else return null return { worldPos, modelId, localPos } }, getScreenByPosition(pos3d, modelId, canShelter/* , disToCameraLimit */){//通过模型局部坐标获取屏幕坐标 //console.log('getScreenByPoint ') let isLocal = modelId != void 0 pos3d = new THREE.Vector3().copy(pos3d) let worldPos = isLocal ? Potree.Utils.datasetPosTransform({ fromDataset: true, datasetId: modelId, position:pos3d}) : pos3d if(!worldPos)return if(canShelter){ if(viewer.inputHandler.ifBlockedByIntersect(worldPos, 0.1, true)) return {trueSide:false}; } var viewport = viewer.mainViewport var camera = viewport.camera var dom = viewer.renderArea if(tagLimitDis != void 0){ if(camera.position.distanceToSquared(worldPos) > Math.pow(tagLimitDis,2))return false } //console.log('getScreenByPoint ' + pos3d.toArray()) return Potree.Utils.getPos2d(worldPos, viewport, dom ) }, screenshot: (width, height, bgOpacity=1) => { //截图 var {getImagePromise, finishPromise} = viewer.startScreenshot({ type: 'default', /* useRenderTarget:true, */bgOpacity }, width, height) var deferred = $.Deferred(); finishPromise.done(({dataUrl}) => { deferred.resolve(dataUrl) }) return deferred.promise() }, getPose() {//获取当前点位和朝向 const camera = viewer.scene.getActiveCamera() const target = viewer.scene.view.getPivot().clone() const position = viewer.scene.view.position.clone() //console.log('getPose',position, target) return { position, target } }, comeTo(o = {}) { //console.log('comeTo',o.position, o.target) //飞到某个点 if(o.modelId){ ['position','target'].forEach(e=>{ if(o[e]){ o[e] = Potree.Utils.datasetPosTransform({ fromDataset: true, datasetId: o.modelId, position:o[e]}) } }) } if(o.distance){ let position = o.target || o.position return viewer.focusOnObject({ position}, 'tag', null,{distance:o.distance} ).promise } let deferred = $.Deferred() viewer.scene.view.setView($.extend({},o, { duration: o.dur, callback:()=>{ o.callback && o.callback() deferred.resolve(true) } })) return deferred.promise() }, setBackdrop(sky){//天空盒背景 console.log('天空盒背景', sky) let setGroundAndText = (color)=>{ MergeEditor.secondCompass.dom.find(".dirText").css({'color':color}) viewer.compass.dom.find(".dirText").css({'color':color}) MergeEditor.ground.material.uniforms.uColor.value.set(color) //MergeEditor.ground.children[0].material.color.set(color) } if(sky == 'none'){ viewer.setBackground(mainBackground) setGroundAndText('#eee') }else if(sky[0] == '#'){ viewer.setBackground(new THREE.Color(sky)) let color = sky == '#fff' ? '#666' : sky == '#333' ? '#eee' : '#bbb' //反相 setGroundAndText(color) }else if(sky == 'image-map' || sky == 'vector-map' ){//影像|矢量 地图 }else{ viewer.setBackground('skybox', sky) setGroundAndText('#e0e0e0') } viewer.dispatchEvent('content_changed') }, switchMapType(type){ let map = viewer.mapViewer.mapLayer.maps.find(e=>e.name == 'map') map.switchStyle(type/* map.style == 'satellite' ? 'standard' : 'satellite' */) }, enableMap(mapArea, latlng){ if(!viewer.mapViewer){ proj4.defs("LOCAL", "+proj=tmerc +ellps=WGS84 +lon_0=" + latlng[1].toPrecision(15) + " +lat_0=" + latlng[0].toPrecision(15)); //高德坐标系 proj4.defs("LOCAL_MAP", "+proj=tmerc +ellps=WGS84 +lon_0=" + latlng[1].toPrecision(15) + " +lat_0=" + latlng[0].toPrecision(15)); //地图和本地一样 proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"); let transform1 = proj4("WGS84", "LOCAL"); //这个ok 是展开的平面投影 LOCAL即NAVVIS: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;"); //注:转入后再转出,和原来的有偏差。如果输入是local坐标,数字越大偏差越大,当百万时就明显了。如果是lonlat,很奇怪经度小于50时就乱了。 viewer.transform = { lonlatToLocal : transform1, lonlatTo4550 : transform2 // 转大地坐标EPSG:4550 } //-------------------------------- viewer.mapViewer = new Potree.MapViewer(mapArea) viewer.mapViewer.initProjection() //focus let boundSize = new THREE.Vector3(300,150,1).max(viewer.bound.boundSize) setTimeout(()=>{ viewer.mapViewer.moveTo(viewer.bound.center, boundSize, 0) },100) } }, enterSceneGuide(pathArr){//导览 (不需要修改参数) let editor = viewer.modules.CamAniEditor console.log('pathArr',pathArr) /* type SceneGuidec = { position: {x,y,z} target: {x,y,z} time: number speed: number //没用到 } */ //console.log('enterSceneGuide',pathArr) let data = { duration: pathArr.slice(0,pathArr.length-1).reduce(function(total, currentValue ){return total+currentValue.time}, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义) points: pathArr, useDurSlice:true } let animation = editor.createAnimation(data) //注:最多只存在一条导览 let bus = mitt() //播放完成 animation.addEventListener('playDone', () => { bus.emit('playComplete') }) //切换点 animation.addEventListener('updateCurrentIndex', e => { bus.emit('changePoint', e.currentIndex + 1) }) return { bus, play() { MergeEditor.selectModel(null) animation.play() }, pause() { animation.pause() }, clear() { //删除 editor.removeAnimation(animation) }, } }, //[path1, paht2], { time, speed } calcPathInfo(paths, info){ //传入的time, speed仅有一个。返回完整的 time, speed //这一版的control似乎无法在某个位置上改变角度,位置和角度一般都是一起变的,所以先不增加单位更换功能。 let pos1 = new THREE.Vector3().copy(paths[0].position) let pos2 = new THREE.Vector3().copy(paths[1].position) let dis = pos1.distanceTo(pos2) if(info.time != void 0){ info.speed = dis / info.time }else{ info.time = dis / info.speed } return info }, //scaleRange: { min, max }, opacityRange: { min, max }, bottomRange: { min, max } }) addModel(props){ /* requestLoadCount || (props.url = Potree.resourcePath+'/models/Block1.glb') requestLoadCount++ */ //console.log(props.isDynamicAdded, props.mode) let bus = mitt() //console.log('addModel',props) props.isFirstLoad = isLocal ? props.bottom == void 0 : (props.isDynamicAdded || props.mode == 'single') // 在编辑时用户添加的 或 展示单个模型 (props.mode='single'模型展示页, props.mode='many'融合页) if(props.opacity == void 0) props.opacity = 1 if(props.type == 'obj') props.type = 'glb' props.scale /= 100 if(props.rotation){ if(props.rotation._x == void 0 && props.rotation.x != void 0){ props.rotation = new THREE.Euler().setFromVector3(props.rotation) } } if(!props.isFirstLoad){ if(autoLoads.length == 0){ //首次加载 setTimeout(()=>{ let sizes = autoLoads.map(e=>e.size|| 0 ) console.log('需要请求加载的模型大小为', sizes, '总大小', sizes.reduce(function(total, currentValue ){ let current = parseFloat(currentValue) return total + ((typeof currentValue == 'number' || currentValue.includes('M')) ? current : current / 1024) }, 0)) readyToAddModel = true //准备开始加载 startLoad(autoLoads[0]) },30) } autoLoads.push(props) readyToAddModel = false }else{ readyToAddModel = true } let startLoad = (prop)=>{ //if(autoLoads.filter(e=>e.loaded).length>1)return console.log('取消加载', prop), prop.onError() //return prop.onError() Potree.addModel(prop, prop.done , prop.progressFun, prop.onError) prop.loading = true console.log('-------开始加载 id:', prop.id, 'title:', prop.title, ', filename:', Potree.Common.getNameFromURL(prop.url), prop ) } let spliceFromArr = (model,loaded)=>{ //let autoLoads.find() props.loadFinish = true props.loading = false if(loaded){ props.loaded = true props.model = model }else{ props.error = true } let haventLoad = autoLoads.filter(e=>!e.loading && !e.loadFinish); if( haventLoad[0]){ startLoad(haventLoad[0]) //this.addModel(autoLoads[0]) }else if(autoLoads.filter(e=>!e.loadFinish).length == 0 && autoLoads.filter(e=>e.loaded).length>0 && !props.isFirstLoad){//设置相机位置:当自动开始加载第一个模型时(其余的也跟着自动加载),等这批加载完后; let autoLoadsDone = autoLoads.filter(e=>e.loaded).map(e=>e.model) console.log('所有模型加载完毕') autoLoads.filter(e=>e.loaded && e.show).forEach(e=>e.model.visible = true) MergeEditor.focusOn(autoLoadsDone, 1000, true, true) autoLoads.length = 0 } } let model let done = (model_)=>{ model = model_ if(!props.isFirstLoad){ model.visible = false//先不显示,防止卡顿 } props.opacity < 100 && result.changeOpacity(props.opacity) model.addEventListener('changeSelect',(e)=>{ bus.emit('changeSelect',e.selected) }) let lastState={ } model.addEventListener('transformChanged',(e)=>{ let msg = {} if(!lastState.position || !model.position.equals(lastState.position)){ lastState.position = msg.position = model.position.clone() } if(!lastState.rotation || !model.rotation.equals(lastState.rotation)){ lastState.rotation = msg.rotation = model.rotation.clone() } if(lastState.scale == void 0 || model.scale.x * 100 != lastState.scale){ lastState.scale = msg.scale = model.scale.x * 100 } msg = Potree.Common.CloneObject(msg) //console.log(msg) bus.emit('transformChanged', msg ) }) spliceFromArr(model,true) if(props.mode == 'single'){//模型查看页 MergeEditor.noNeedSelection = true setTimeout(()=>{ MergeEditor.focusOn([model], 1000, true, true) },1) } bus.emit('loadDone') //console.log('loadDone' ) } let progressFun = (progress)=>{ bus.emit('loadProgress',progress) } let onError = function ( xhr ) { bus.emit('loadError', xhr) console.log('loadError!!!!!!!!!', Potree.Common.getNameFromURL(props.url), props.size, xhr) spliceFromArr(model,false) } try{ props.url = JSON.parse(props.url) //去掉 '\' }catch(e){} props.done = done; props.progressFun = progressFun; props.onError = onError if(readyToAddModel){ if(autoLoads.filter(e=>e.loading).length{//隐藏其他的模型 if(m!=model) Potree.Utils.updateVisible(m, 'enterScaleSet', false) }) let setScale = ()=>{ if(length == void 0 || !measureBuilded )return let vec = new THREE.Vector3().subVectors(viewer.mainViewport.camera.position, scaleMeasure.points[1]) let s = length / (scaleMeasure.points[0].distanceTo(scaleMeasure.points[1])) result.changeScale(model.scale.x * s*100) /* setTimeout(()=>{ viewer.focusOnObject(scaleMeasure , 'measure', 500) },1) */ let newCamPos = new THREE.Vector3().addVectors(scaleMeasure.points[1], vec.multiplyScalar(s)) viewer.scene.view.setView({position:newCamPos, target:scaleMeasure.getCenter(), duration:0, callback:()=>{ //更改target到measure中心的好处就是可以让相机绕measure中心转,坏处是每次更改都会变一下画面 } }) } return { bus, setLength(v){ if(!v)return length = v setScale() }, startMeasure(){ if(scaleMeasure){ viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure:scaleMeasure }) viewer.scene.removeMeasurement(scaleMeasure) } measureBuilded = false scaleMeasure = viewer.measuringTool.startInsertion( {measureType: "Distance", unit: "metric"}, () => { //done: //bus.emit('end' ) //完成 measureBuilded = true setScale() }, () => { //cancel //bus.emit('quit') //删除 } ) scaleMeasure.addEventListener('marker_dropped',(e)=>{//拖拽结束后发送changeCallBack if (scaleMeasure.parent) { //未被删除 measureBuilded && setScale() } }) } } }, leaveScaleSet(){ if(scaleMeasure){ viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure:scaleMeasure }) viewer.scene.removeMeasurement(scaleMeasure) scaleMeasure = null } viewer.outlinePass.selectedObjects = [model]; MergeEditor.getAllObjects().forEach(m=>{//恢复其他的模型 if(m!=model) Potree.Utils.updateVisible(m, 'enterScaleSet', true) }) }, destroy(){ model && MergeEditor.removeModel(model) viewer.dispatchEvent('content_changed') } } return result }, //测量线的点都附着于各个模型,当模型变化时,点跟着变化。 // 新的测量创建方法,传入type 返回新测量对象 startMeasure(type){ // 寻创建的测量对象有上面绘画测量对象的所有方法 const bus = mitt() let info = getMeasureType(type) let measure = viewer.measuringTool.startInsertion( info, () => { //done: /* bus.emit('submit', { dataset_points: measure.dataset_points.map(p=>p.clone()) , points_datasets: measure.points_datasets } ) //完成 */ bus.emit('submit') bus.emit('update',[ measure.dataset_points.map(p=>p.clone()) , measure.points_datasets ]) }, () => { //cancel bus.emit('cancel'/* , ret */) //删除 } ) Potree.Log('startMeasure: ' + measure.id, '#00c7b2') /* let cancel = ()=>{ Potree.Log('clear删除: ' + measure.id, '#00c7b2') viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) viewer.scene.removeMeasurement(measure) } */ let result = { bus, ...getMeasureFunction(measure, bus), } /* StartMeasure = Measure & { // 多了cancel 取消测量的事件,没有参数 // 多了invalidPoint 当用户测量了无效点时的事件,抛出无效原因 bus: Emitter<{ cancel: void; invalidPoint: string }> } */ return result }, // 绘画测量线(非新增使用) // type = 'free' (自由) || 'vertical' (垂直) || 'area' (面积) // positions 点数组 构成如下 [{ point: {x,y,z}, modelId: 1 }] drawMeasure(type, dataset_points, points_datasets){ // 返回测量对象有如下 const bus = mitt() let info = getMeasureType(type /* , unit */) //info.points = positions info.dataset_points = dataset_points info.points_datasets = points_datasets //info.sid = sid info.bus = bus let measure = viewer.measuringTool.createMeasureFromData(info) if(!measure)return {bus} Potree.Log('drawMeasure由数据新建: ' + measure.id, '#00c7b2') let result = { bus, setPositions(dataset_points, points_datasets){//用于恢复measure的点,不会修改点的个数 measure.dataset_points = dataset_points.map(e=>{ return e && new THREE.Vector3().copy(e) }) measure.points_datasets = points_datasets measure.points = measure.dataset_points.map((p,i)=>{ return Potree.Utils.datasetPosTransform({fromDataset:true, datasetId:measure.points_datasets[i], position: p}) }) measure.getPoint2dInfo(measure.points) measure.update({ifUpdateMarkers:true}) measure.setSelected(false)//隐藏edgelabel }, ...getMeasureFunction(measure, bus), } return result }, addTag(info){//加热点 let bus = mitt() let tag let done = ()=>{ bus.emit('added') bus.emit('update', {position: tag.position.clone(), normal:o.normal.clone(), modelId:tag.root.dataset_id } ) tag = tag_ tag.spot.addEventListener('mouseover',()=>{ bus.emit('hoverState',true) }) tag.spot.addEventListener('mouseout',()=>{ bus.emit('hoverState',false) }) } if(!info.position){ viewer.tagTool.startInsertion().done(tag_=>{ done() }) }else{ info.root = MergeEditor.getAllObjects().find(e=>e.dataset_id == info.modelId) if(!info.root){ console.error('没有找到该modelId') } tag = viewer.tagTool.createTagFromData(info) done() } let result = { bus, getScreenPos(){ let pos3d = new THREE.Vector3().setFromMatrixPosition( tag.matrixWorld ) return sdk.getScreenByPosition(pos3d) }, show(){ Potree.Utils.updateVisible(tag, 'byList', true) }, hide(){ Potree.Utils.updateVisible(tag, 'byList', false) }, destroy(){ if(tag){ tag.dispose() } viewer.dispatchEvent({ type: 'cancel_insertions', remove: true }) }, changeTitle(title){ tag.changeTitle(title) } } return result }, showGrid(){ Potree.Utils.updateVisible(viewer.modules.MergeEditor.ground, 'hideGrid', true) viewer.dispatchEvent('content_changed') }, hideGrid(){ Potree.Utils.updateVisible(viewer.modules.MergeEditor.ground, 'hideGrid', false) viewer.dispatchEvent('content_changed') } } //sdk.hideGrid() return sdk } export default enter