import mitt from 'mitt' import libTransform from 'coordtransform'; import axios from 'axios' //{ axios } from '@/api' const Id_noIntersect = -100 //path绘制在地图上的点,modelId传这个值,勿更改 let requestLoadCount = 0 let maxLoadingCount = 2; //正在加载模型的最大数目 //0看看,1看见,2深时,3用户上传三维模型,4深时mesh,5深光点云,6深光mesh const ModelTypes = { 0 : {name:'看看(八目)', panos4dkk:true}, 1 : {name:'看见(双目转台)', panos4dkk:true, rot90:true}, 2 : {name:'深时', }, 3 : {name:'用户上传三维模型'}, 4 : {name:'深时mesh(激光转台)',panos4dkk:true, rot90:true},//3dtiles 5 : {name:'深光点云' }, 6 : {name:'深光mesh',panos4dkk:true, rot90:true},//3dtiles 7 : {name:'圆周率相机' },//圆周率相机场景 } let cesAspect , cesImageryProvider let isValidPoint = (modelId)=>{//所存的modelId没被删或者它本身不在模型上 return modelId == Id_noIntersect || viewer.objs.children.concat(viewer.scene.pointclouds).some(e=>e.dataset_id == modelId ) } { // 84坐标转高德 (国外地区用84,所以地理注册时填的是84,我这需要转成高德) const wgs84ToAMap = (pos ) => { const latlng = libTransform.wgs84togcj02(pos.x, pos.y) return { x: latlng[0], y: latlng[1] } } // 高德坐标转84 const aMapToWgs84 = (pos ) => { const latlng = libTransform.gcj02towgs84(pos.x, pos.y) return { x: latlng[0], y: latlng[1] } } window.AMapWith84 = { aMapToWgs84, wgs84ToAMap } } //江门本地版本 export const enter = ({ dom, mapDom, isLocal, lonlat, scenes, laserRoot, laserOSSRoot, panoOSSRoot,ossRoot }) => { console.warn('新的页面') Potree.settings.isOfficial = true //标记为正式、非测试版本 //Potree.fileServer = axios Potree.settings.libsUrl = './lib/' let loadStartTime = Date.now() //正式环境(本地调试会打不开) 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' } if(window.offline){//离线版 Potree.fileStorage = { get(url){ return new Promise(function(resolve,reject){ let data = window.offlineData[url] if(data){ resolve(data) }else{ console.error('没找到',url) reject() } }) } } } if(laserRoot != void 0){ laserRoot.slice(-1) == '/' && (laserRoot = laserRoot.slice(0,-1)) //去掉最后一个'/' Potree.settings.urls.prefix = laserRoot } if(laserOSSRoot != void 0){ Potree.settings.urls.prefix1 = laserOSSRoot } if(panoOSSRoot != void 0){ Potree.settings.urls.prefix3 = panoOSSRoot //tile } if(ossRoot){ Potree.settings.urls.panoPrefix = ossRoot //vision.txt } const mapBus = mitt(), sceneBus = mitt() let isLocal2 = true if(isLocal2){//本地配置 Potree.settings.isLocal = Potree.settings.tileOriginUrl = isLocal2 } Potree.settings.showCompass = true Potree.settings.compassDom = dom.querySelector('#direction') Potree.settings.mergeType2 = true //标识新版 Potree.settings.modelSkybox = true //是否将全景图贴在模型上(会导致卡顿)。若不显示模型将不显示Reticule Potree.settings.tiles3DMaxMemory = 300 //稍微增加点 Potree.settings.mergeTransCtlOnClick = true Potree.settings.canWalkThroughModel = true window.cesErrorWords = '由于GPU占用过大, 将关闭地图,请更换更好的显卡!' window.cesErrorCallback = ()=>{ sdk.setBackdrop('none') } let { THREE } = Potree.mergeEditStart(dom, mapDom) let MergeEditor = viewer.modules.MergeEditor Potree.settings.unableNavigate = true Potree.setLonlat(lonlat[0], lonlat[1]) if(window.offline){//离线版 改目录 viewer.images360.tileDownloader.getTiles = function(d, sceneNum, useV4url, model){ let kankan = !model.isPointcloud //ModelTypes[model.props.fromType].panos4dkk if(kankan){ return `/swkk/${sceneNum}/wwwroot/scene_view_data/${sceneNum}/images/${d}` }else{ return `/swss/${sceneNum}/www/${sceneNum}/scene_view_data/${sceneNum}/images/${d}` } } } /* Potree.loadControlPoint = async function(callback,sceneCode,onError,prefix){//点云绑定地图 let path = `/laser/jm/${sceneCode}/getDataSetAndControlPoint` return Potree.loadFile(path, { fetchMethod: 'post' }, callback,onError) } */ //因为getPose里用的是target,俯视的yaw不准,所以限制一下不要完全俯视 viewer.mainViewport.view.maxPitch-=0.001 viewer.mainViewport.view.minPitch+=0.001 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 }) } updateMap() Potree.Common.intervalTool.isWaiting('updateCamNear', ()=>{ updateCamNear() }, 1000) updateCamFar() }) //------------------------------------- let modelAinB = (A,B)=>{ //B的expand(5m) bound完全包含A let boundB = B.boundingBox.clone().expandByVector(new THREE.Vector3(5,5,5)).applyMatrix4(B.matrixWorld) let boundA = A.boundingBox.clone().applyMatrix4(A.matrixWorld) return boundB.containsBox(boundA) } let changeMeshVisi = (object, show) => { if(show == void 0) show = Potree.settings.displayMode == 'showPointCloud' || object == viewer.images360.currentPano.pointcloud && Potree.settings.modelSkybox || object.showInPano //showInPano: 装饰物,一直显示 || !object.panos && modelAinB(object, viewer.images360.currentPano.pointcloud) //装饰物 Potree.Utils.updateVisible(object, 'showPanos', show) } if(Potree.settings.canWalkThroughModel){ let lastModel viewer.images360.addEventListener('flyToPano',(e)=>{//开始漫游 漫游到另一个模型就要选中这个模型? let model = e.toPano.pano.pointcloud if(lastModel != model){ changeMeshVisi(model, true) //MergeEditor.selectModel(model) //model.result_.flyInPano(e.toPano.pano, {dontFly:true}) //切换模型显示,因为flyInPano有事件怕乱所以统一用这个函数 updateCamNear() } }) viewer.images360.addEventListener('flyToPanoDone',(e)=>{ if(!e.makeIt)return let model = viewer.images360.currentPano.pointcloud if(lastModel != model){ lastModel?.isModel && changeMeshVisi(lastModel, false) sceneBus.emit('panoModelChange', model.result_ ) } lastModel = model }) } viewer.images360.addEventListener('endChangeMode',(e)=>{ sceneBus.emit('modeChange', {mode: e.mode == 'showPanos' ? 'pano' : 'fuse', model : e.mode == 'showPanos' && viewer.images360.currentPano.pointcloud.result_} ) Potree.Utils.updateVisible(MergeEditor.transformControls, 'showPanos', e.mode == 'showPointCloud') Potree.Utils.updateVisible(MergeEditor.boxHelper, 'showPanos', e.mode == 'showPointCloud') if(e.mode == 'showPanos'){ viewer.setControls( viewer.fpControls ) viewer.removeEventListener('camera_changed', camera_changed) }else{ viewer.addEventListener('camera_changed', camera_changed) } viewer.objs.children.forEach((e)=>{changeMeshVisi(e)}) Potree.settings.canWalkThroughModel || viewer.images360.panos.forEach(pano => { pano.setEnable(e.mode == 'showPanos' ? pano.pointcloud == viewer.images360.currentPano.model : true) }) Potree.settings.unableNavigate = e.mode == 'showPointCloud' updateCamNear() }) let camera_changed = (e) => { if (e.viewport.name == 'MainView' && e.changeInfo.positionChanged) { //viewer.mainViewport.camera.position viewer.mainViewport.view.radius = 0.1 //使pivot在面前一丢丢距离 viewer.setControls(viewer.orbitControls) viewer.removeEventListener('camera_changed', camera_changed) } } let requestInPano = false //------------------------------------- /* viewer.inputHandler.addEventListener('keydown', (e)=>{ if(e.event.key == "e" ){ MergeEditor.transformControls.mode = 'rotate' }else if(e.event.key == "w"){ MergeEditor.transformControls.mode = 'translate' }else if(e.event.key == "s"){ MergeEditor.transformControls.mode = 'scale' } }) */ viewer.addEventListener('webglError', e => { console.error('viewer webglError: ' + e) let memory = '. \n jsHeapSizeLimit:'+ performance.memory.jsHeapSizeLimit/ 1e6 + ', usedJSHeapSize: '+performance.memory.usedJSHeapSize/ 1e6 + '(M)' sceneBus.emit('webglError', { msg: e.msg + memory }) }) viewer.compass.setAutoDisplay(true) /* mapBus.on('visible', v => { //console.log('mapBus visible', v) viewer.mapViewer.visible = v if (v) { viewer.mapViewer.mapLayer.needUpdate = true } viewer.mapViewer.dispatchEvent({type:'forceVisible',visible:v}) }) */ { 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 = /* window.autoLoads = */ [] let readyToAddModel 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) => { if(measure.type == 'Path'){ bus.emit(e.state ? 'enter' : 'leave') }else{ bus.emit('highlight', e.state) } }) let update = (e)=>{ //拖拽结束后发送changeCallBack if (measure.parent) { //未被删除 console.warn('changePoints', measure.dataset_points.length ) if(measure.type == 'Path'){ bus.emit('changePoints', measure.dataset_points.map((p,i)=>{return { position: (p || measure.points[i]).clone(), modelId: measure.points_datasets[i] == void 0 ? Id_noIntersect : measure.points_datasets[i], name: measure.markerLabels[i].originText }})) }else{ bus.emit('update', [ measure.dataset_points.map(p => p.clone()), measure.points_datasets ]) } } } measure.addEventListener('marker_dropped', update) measure.addEventListener('changed', update) measure.addEventListener('createDone', update) measure.addEventListener('changeByHistory', update); 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, {dontLookUp:measure.type == 'Path'}) 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, mapBus, canTurnToPanoMode(pos) { pos = pos ? new THREE.Vector3().copy(pos) : viewer.images360.position let pano = viewer.images360.findNearestPano(pos) if (pano && pano.position.distanceTo(pos) < Potree.config.panoFieldRadius * pano.pointcloud.scale.x) { return {model:pano.pointcloud.result_} } //poschange后会调用这个,如果返回false会变为点云模式,且不会自动变回原先的模式 }, getPositionByScreen(pos2d, hopeModelId) {//通过屏幕坐标获取真实坐标 . hopeModelId: 如果指定了模型,优先返回hopeModelId上的intersect //console.log('getPositionByScreen',hopeModelId) hopeModelId = null let worldPos, localPos, modelId, intersect, normal, localNormal 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 }) normal = intersect.normal localNormal = intersect.localNormal } else return null return { worldPos, modelId, normal, localPos, localNormal } }, 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) }, setCameraFov(fov) { viewer.setFOV(fov) }, screenshot: (width, height/* , bgOpacity=0 */ ) => {// //截图 let bgOpacity = Potree.settings.showCesium ? 0 : 1 /* viewer.background == 'skybox' */ //因为要画map底图所以上层只能透明。之后需要的话再改 console.log('bgOpacity', bgOpacity) Potree.Utils.updateVisible(MergeEditor.boxHelper, 'screenshot', false) Potree.Utils.updateVisible(viewer.scene.overlayScene, 'screenshot', false) //hide all var { getImagePromise, finishPromise } = viewer.startScreenshot({ type: 'default', /* useRenderTarget:true, */bgOpacity }, width, height) var deferred = $.Deferred(); finishPromise.done(({ dataUrl }) => { if(Potree.settings.displayMode != 'showPanos' && Potree.settings.showCesium){//need map background Potree.cesScreenshot(width, height).done((mapBGurl)=>{ let img = new Image(); img.src = dataUrl let imgBG = new Image(); imgBG.src = mapBGurl let loadCount = 0 img.onload = imgBG.onload = ()=>{ loadCount++; if(loadCount == 2){ let url = Potree.Common.imgAddLabel(imgBG,img,{leftRatioToImg:0,topRatioToImg:0}) deferred.resolve(url) } } }) }else{ deferred.resolve(dataUrl) } Potree.Utils.updateVisible(MergeEditor.boxHelper, 'screenshot', true) Potree.Utils.updateVisible(viewer.scene.overlayScene, 'screenshot', true) }) return deferred.promise() }, getPose() {//获取当前点位和朝向 const camera = viewer.scene.getActiveCamera() const target = viewer.scene.view.getPivot().clone() const position = viewer.scene.view.position.clone() const pose = { position, target, displayMode:Potree.settings.displayMode } if(Potree.settings.displayMode == 'showPanos'){ let model = viewer.images360.currentPano.pointcloud pose.panoId = viewer.images360.currentPano.originID pose.model = model.result_ pose.posInModel = Potree.Utils.datasetPosTransform({ toDataset: true, position: camera.position.clone(), object:model }) pose.rotInModel = Potree.Utils.datasetRotTransform({ toDataset: true, quaternion: camera.quaternion.clone(), getQuaternion: true, pointcloud:model }).toArray() //拿第一个数据集 } //console.log('getPose',position, target) return pose }, comeTo(o = {}) { //console.log('comeTo',o.position, o.target) //飞到某个点 let deferred = $.Deferred() if(o.panoId != void 0){ let model = o.model.model let pano = model.panos.find(a=>a.originID == o.panoId) if(pano){ o.rotInModel = new THREE.Quaternion().fromArray(o.rotInModel) let quaternion = Potree.Utils.datasetRotTransform({ fromDataset: true, quaternion: o.rotInModel, getQuaternion: true, object:model}) o.model.flyInPano(pano, {quaternion, duration:0, callback(){ o.callback && o.callback() deferred.resolve(true) }}) return deferred.promise() }else{ console.warn('没有找到漫游点',o) } }else if(requestInPano){ requestInPano.result_.flyOutPano() }else{ if (o.modelId != void 0) { ['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 } viewer.scene.view.setView($.extend({}, o, { duration: o.dur, callback: () => { o.callback && o.callback() deferred.resolve(true) } })) return deferred.promise() }, setBackdrop(sky, type, { scale, rotate }) {//天空盒背景 //console.log('天空盒背景', sky,type) 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) } viewer.dispatchEvent('content_changed') if(type == 'map'){ MergeEditor.setGroundPlaneImg(null) viewer.setBackground(mainBackground) Potree.settings.showCesium = true buildMap() viewer.backgroundOpacity = 0 return }else{ Potree.settings.showCesium = false } if (type == 'bimg') {//地面图 MergeEditor.setGroundPlaneImg(sky, scale, rotate) setGroundAndText('#e0e0e0') viewer.setBackground(mainBackground) } else { MergeEditor.setGroundPlaneImg(null) 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 (type == 'image-map' || type == 'vector-map') {//影像|矢量 地图 } else {//环境 viewer.setBackground('skybox', sky) setGroundAndText('#e0e0e0') } } }, /* switchMapType(type) { let map = viewer.mapViewer.mapLayer.maps.find(e => e.name == 'map') map.switchStyle(type ) }, */ switchMapType(type){//切换成江门的卫星或标准 return; console.log('switchMapType',type) let maximumLevel, url if(type == 'satellite'){ maximumLevel = 18 url = "https://a.map.jms.gd/tile/weixing/{z}/{x}/{y}.png" }else{ maximumLevel = 19 url = "https://a.map.jms.gd/tile/gd_xiangtu/{z}/{x}/{y}.png" } cesImageryProvider = new Cesium.UrlTemplateImageryProvider({ //直接用84坐标,不用转高德 url, crossOrigin: 'anonymous', minimumLevel: 0, maximumLevel }) if(Potree.settings.showCesium){ cesiumViewer.imageryLayers.removeAll(); cesiumViewer.imageryLayers.addImageryProvider(cesImageryProvider); } }, enableMap(mapArea, latlng) { if (!viewer.mapViewer) { //-------------------------------- viewer.mapViewer = new Potree.MapViewer(mapArea) viewer.mapViewer.initProjection() //focus let boundSize = new THREE.Vector3(200, 150, 1).max(viewer.bound.boundSize) viewer.mapViewer.addEventListener('viewerResize', () => { viewer.mapViewer.moveTo(viewer.bound.center, boundSize, 0) }, { once: true }) } }, enterSceneGuide(pathArr) {//导览 (不需要修改参数) let editor = viewer.modules.CamAniEditor console.log('pathArr', pathArr) //console.log('enterSceneGuide',pathArr) pathArr.forEach(e=>{ if(e.panoId != void 0){ e.model = e.model.model } }) let data = { //duration: pathArr.slice(0, pathArr.length - 1).reduce(function (total, currentValue) { return total + currentValue.time }, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义) points: pathArr, useDurSlice: true } let ani = editor.createMulAnimation(data) //注:最多只存在一条导览 let bus = mitt() //播放完成 ani.event_.addEventListener('playDone', () => { bus.emit('playComplete') viewer.images360.panos.forEach(e=>e.marker && Potree.Utils.updateVisible(e, 'playAni', true)) }) //切换点 ani.event_.addEventListener('updateCurrentIndex', e => { bus.emit('changePoint', e.currentIndex + 1) }) return { bus, play() { MergeEditor.selectModel(null) viewer.images360.panos.forEach(e=>e.marker && Potree.Utils.updateVisible(e, 'playAni', false)) ani.play() }, pause() { viewer.images360.panos.forEach(e=>e.marker && Potree.Utils.updateVisible(e, 'playAni', true)) ani.stop() }, clear() { ani.remove() }, } }, //[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 }, addModel(props) { let bus = props.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) } } let getDefaultRotation = () => { if(ModelTypes[props.fromType]?.rot90 && props.type != 'glb'){ return new THREE.Euler(Math.PI / 2, 0, 0) } else return new THREE.Euler(0, 0, 0) } 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 //准备开始加载 loadNext()//startLoad(autoLoads[0]) }, 30) } autoLoads.push(props) readyToAddModel = false } else { readyToAddModel = true props.rotation = getDefaultRotation() } let model let done = (model_) => { model = model_ model.result_ = result model.props = props result.model = model model.fromType = ModelTypes[props.fromType].name if (!props.isFirstLoad) { model.visible = false//先不显示,防止卡顿 } model.showInPano = props.raw.showInPano 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, props, true) model.addEventListener('changeSelect', (e) => { MergeEditor.transformControls.visible && e.selected && MergeEditor.transformControls.attach(model, e.clickPos) //: MergeEditor.transformControls.detach() }) MergeEditor.modelAdded(model) if (props.mode == 'single') {//模型查看页 MergeEditor.noNeedSelection = true setTimeout(() => { MergeEditor.focusOn([model], 1000, true, true) }, 1) } if(ModelTypes[props.fromType].panos4dkk){ Potree.load4dkkPanos(props.raw.num, model, getDefaultRotation(), () => { bus.emit('loadDone') }, props.fromType == 0 ? '2k' : '4k' ) //看看场景是2k } else { 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, props, 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 < maxLoadingCount) { startLoad(props) } } let scaleMeasure let result = { bus, model, getDefaultRotation, supportPano() { //是否支持全景图 return model?.panos?.length > 0 }, flyInPano(pano, {dontFly, quaternion, duration}={}) {// 飞入全景图 requestInPano = model pano = pano || viewer.images360.findNearestPano(null, model.panos) if (pano) { dontFly || viewer.images360.flyToPano({ pano, canCancelLast: true, quaternion, duration}) Potree.settings.displayMode = 'showPanos' } }, flyOutPano() {// 飞出全景图(就是切换到正常融合视角) requestInPano = false Potree.settings.displayMode = 'showPointCloud' /* setTimeout(() => {//在下一帧再变,因为3dtiles需要更新一下才会显示tiles if (!requestInPano) { Potree.settings.displayMode = 'showPointCloud' Potree.Utils.updateVisible(MergeEditor.boxHelper, 'showPanos', true) } }, 50) */ }, changeShow(show) { props.show = show //for autoLoads show model if (model) { Potree.Utils.updateVisible(model, 'datasetSelection', show) if (model.panos) { model.panos.forEach(e => e.setEnable(show)) } viewer.dispatchEvent('content_changed') } }, changeSelect(state) { //console.error('select', state) if (model) { let fly = viewer.images360.latestRequestMode != 'showPanos' MergeEditor.selectModel(model, state, fly, true) updateCamNear() //console.log('changeSelect', props.id, state) } }, changeScale(s) { if (model) { s /= 100 if (model.scale.x == s) return //MergeEditor.history.beforeChange(model)//但不知道什么时候结束拖拽 model.scale.set(s, s, s) model.isPointcloud && model.changePointSize(/* Potree.config.material.realPointSize * s */) model.dispatchEvent("scale_changed") } }, changeOpacity(opacity) { //见笔记:透明物体的材质设置 if (opacity == void 0) opacity = 100 opacity /= 100 MergeEditor.changeOpacity(model, opacity) }, changeBottom(z) { /* model && MergeEditor.setModelBtmHeight(model,z) model.dispatchEvent('transformChanged') //改了position */ }, changePosition(pos) {//校准取消时执行 //console.log('changePosition', pos.x, pos.y, pos.z) model && model.position.copy(pos) model.dispatchEvent({ type: 'position_changed' }) }, changeRotation(rot) {//校准取消时执行 //console.log('changeRotation', rot.x, rot.y, rot.z) model && model.rotation.setFromVector3(rot) model.dispatchEvent({ type: 'rotation_changed' }) }, enterRotateMode() { if (model) { if (MergeEditor.split) {//分屏校准 MergeEditor.setTransformState('rotate') MergeEditor.transformControls2.attach(model) MergeEditor.transformControls2.mode = 'rotate' } MergeEditor.transformControls.attach(model) MergeEditor.transformControls.mode = 'rotate' } }, enterMoveMode() { console.log('enterMoveMode') if (model) { if (MergeEditor.split) {//分屏校准 MergeEditor.setTransformState('translate') MergeEditor.transformControls2.attach(model) MergeEditor.transformControls2.mode = 'translate' } MergeEditor.transformControls.attach(model) MergeEditor.transformControls.mode = 'translate' } }, leaveTransform() { console.log('leaveTransform') if (MergeEditor.split) {//分屏校准 MergeEditor.setTransformState(null) } else { MergeEditor.transformControls.detach() MergeEditor.transformControls2.detach() } MergeEditor.history.clear() }, enterAlignment() {//开始校准 result.leaveTransform() MergeEditor.enterSplit() if(Potree.settings.showCesium){ cesiumViewer.scene.canvas.style.width = '50%' //cesiumViewer.resize() } let bus = new mitt() return { bus } }, leaveAlignment() { //console.log('leaveAlignment',model.position, model.rotation) MergeEditor.leaveSplit() MergeEditor.transformControls.detach() MergeEditor.transformControls2.detach() if(Potree.settings.showCesium){ cesiumViewer.scene.canvas.style.width = '' updateMap() } }, enterScaleSet() {//设置比例 let bus = new mitt() let length, measureBuilded; //viewer.outlinePass.selectedObjects = [] if (!Potree.Utils.isInsideFrustum(model.boundingBox.clone().applyMatrix4(model.matrixWorld), viewer.scene.getActiveCamera())) { MergeEditor.focusOn(model, 600) } MergeEditor.getAllObjects().forEach(m => {//隐藏其他的模型 if (m != model) Potree.Utils.updateVisible(m, 'enterScaleSet', false) }) result.oldFar = Potree.settings.cameraFar model.enterScaleOldState_ = { //view: viewer.mainViewport.view.clone(), scale: model.scale.x, far: Potree.settings.cameraFar } let setScale = () => { if (length == void 0 || !measureBuilded) return let vec = new THREE.Vector3().subVectors(viewer.mainViewport.camera.position, scaleMeasure.points[1]) let dis = scaleMeasure.points[0].distanceTo(scaleMeasure.points[1]) let s = length / Math.max(dis,0.00001) 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中心转,坏处是每次更改都会变一下画面 //Potree.settings.cameraFar = Math.max(model.enterScaleOldState_.far, viewer.scene.view.position.distanceTo(model.boundCenter) + model.boundingBox.clone().applyMatrix4(model.matrixWorld).getSize(new THREE.Vector3).length()) //use updateCamFar() } }) } 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.forbitRepeatPoint = true //两个点不能相同,否则长度是0 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) }) setTimeout(()=>{//可能还原了 相机位置移动回去 if(model.scale.x == model.enterScaleOldState_.scale){ MergeEditor.focusOn(model, 0) //reset orbitcontrol's minRadius //viewer.mainViewport.view.copy(model.enterScaleOldState_.view) //Potree.settings.cameraFar = model.enterScaleOldState_.far } },10) }, destroy() { model && MergeEditor.removeModel(model) result.changeSelect(false) 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') }, () => { //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 }, /* export type PathProps = { // 线段名称 name: string, // 是否显示名称, showName: boolean, // 文字大小 fontSize: number, // 是否显示方向, showDirection: boolean, // 方向是否反向 reverseDirection: boolean, line: { width: number, color: string, altitudeAboveGround: number position: SceneLocalPos, normal: SceneLocalPos, modelId: string }, points: { // 点位名称 name: string, position: SceneLocalPos, modelId: string, }[] } bus: Emitter<{ // 标注点击事件 click: void; // 鼠标移入标注事件 enter: void; // 鼠标移出标注事件 leave: void; // 线段坐标更改事件 linePositionChange: { pos: SceneLocalPos, normal: SceneLocalPos, modelId: string } // 路径点位置变更 changePoints: PathProps['points'] // 距离相机位置变更 toCameraDistanceChange: number }>; */ createPath(props){//路线 //console.log('createPath', props) let bus = mitt() let path let info = {type : 'Path', minMarkers : 2, title:props.name} if(props.points.length == 0){ path = viewer.measuringTool.startInsertion( info, () => { bus.emit("drawed" ); //完成 }) viewer.dispatchEvent({ type: 'cancel_insertions', dontRemove: true, measure:path }) //要等进入编辑才能继续编辑 }else{ let originPointCount = props.points.length props.points = props.points.filter(e=> isValidPoint(e.modelId)) info.points_datasets = props.points.map(e=> e.modelId == Id_noIntersect ? null : e.modelId) info.dataset_points = info.points = props.points.map(e=>e.position)//当该点不在任何模型上时,记录的是世界坐标,所以两个都赋值,过后根据有无datasetID选择 path = viewer.measuringTool.createMeasureFromData(info); if(props.line.position) { if(isValidPoint(props.line.modelId)){ let pos = props.line.modelId == Id_noIntersect ? new THREE.Vector3().copy(props.line.position) : Potree.Utils.datasetPosTransform({fromDataset:true, position: props.line.position, datasetId: props.line.modelId }) path.updateTitlePos(pos) }else{ console.log('path label pos 因模型被删而去除', info.title ) } } if(props.points.length < originPointCount ) { path.dispatchEvent('createDone') console.log('path点因模型被删减少', info.title, originPointCount,'->',props.points.length) } } { let curSelectMarker path.addEventListener('markerSelect',(e)=>{ let msg if(e.cancel){ curSelectMarker == e.marker && (msg = -1) //是当前选中的marker就取消 }else{ curSelectMarker = e.marker msg = path.markers.indexOf(e.marker) } //msg != void 0 && console.log('msg',msg) msg != void 0 && bus.emit('activePoint', msg ) }) path.addEventListener('titlePosChanged',(e)=>{ //console.log('titlePosChanged',path.title, e.position.clone()) bus.emit('linePositionChange', { modelId: e.root ? e.root.dataset_id : Id_noIntersect, pos: e.root ? Potree.Utils.datasetPosTransform({toDataset:true, position: e.position.clone(), datasetId: e.root.dataset_id }) : e.position.clone() }) }) path.addEventListener('chose',(e)=>{ bus.emit('focus', e.state) }) } let funs = getMeasureFunction(path, bus) //let fadeFar = -1 let functions = Object.assign(funs,{ bus, changeEditMode(state){//进入编辑 if(!state){ viewer.dispatchEvent({ type: 'cancel_insertions', dontRemove: true, measure:path }) } path.setEditEnable(state) //functions.changeVisibilityRange(fadeFar) }, changeCanEdit(state){//是否点击pen图标以加点和删点 if(state){ if(path.points.length < 2){//继续绘制 info.resume = true, info.measure = path path = viewer.measuringTool.startInsertion( info, () => { bus.emit("drawed" ); //完成 }) } }else{ viewer.dispatchEvent({ type: 'cancel_insertions', dontRemove: true, measure:path }) } console.log('changeCanEdit',state) path.setAddOrRemPoint(state) }, visibility(v){ //console.log('visibility', path.title, v) Potree.Utils.updateVisible(path,'user', v) }, visibilityName(v){ path.setTitleVisi(path.titleLabel.parent, v, 'user') }, changeName(name){ path.setTitle(name) }, changePointName(index,name){ path.setMarkerTitle(index, name) }, changePathPoints(points){ console.log('changePathPoints??????????',points) }, deletePoint(index){ path.removePoint(index) }, changeFontSize(fontsize){ path.setFontSize(fontsize) }, changeLine({width,color,altitudeAboveGround}){ path.setPathWidth(width) path.setPathColor(color) }, changeVisibilityRange(far){//设置消失距离 //fadeFar = far //path.setFadeFar(( far== -1 || path.editEnable) ? null : far) //注意:编辑时显示全部 path.setFadeFar(far== -1 ? 0 : far) }, highlight(state){ path.setSelected(state?'hover':'unhover', true) }, focus(state){ path.setSelected(state?'click':'unclick', true) }, changeDirection(show,reverse){ path.setArrowDisplay(show) if(path.reverse != reverse){ path.reverse = reverse path.constructor.updateArrows(true) } }, createAni(tension){ let distance = path.getTotalDistance() let pathPoints = path.points.map(e=>e.clone().add(new THREE.Vector3(0,0,2))) //在地面之上一定高度 if(path.reverse) pathPoints.reverse() const speed = 3, //m/s turnDisPerRad = 1.5, maxTurnDis = 3,//拐弯最大距离 maxRoadTurnRatio = 0.8 //每段路单次转弯最大比例,防止拐弯占据一整条路直到下一个点 let roadLens = [] let vecs = pathPoints.map((p,i)=>{ if(i==0)return let last = pathPoints[i-1] roadLens.push(p.distanceTo(last)) return new THREE.Vector3().subVectors(p,last).normalize() }) let turnDis = vecs.map((vec,i)=>{//在每个转折点拐弯前后需要的米数 if(i==0 || i==vecs.length-1)return 0 let next = vecs[i+1] let angle = next.angleTo(vec) return Math.min(turnDisPerRad * angle / 2, maxTurnDis ) }) let points = [] let len = pathPoints.length for(let i=0;i roadLens[i]){//如果超过了路长度, 该条路就只有一个拐点 turnDis1 = turnDis1 / turnDisSum * roadLens[i] let p = thisPoint.clone().add(vecs[i+1].clone().multiplyScalar(turnDis1)) points.push({position: p, target: nextPoint.clone()}) }else{ if(turnDis1>0){ //i==0时为0 let turnPoint1 = thisPoint.clone().add(vecs[i+1].clone().multiplyScalar(turnDis1)) points.push({position: turnPoint1, target: nextPoint.clone()}) } if(turnDis2>0){//i==len-2时为0 let turnPoint2 = nextPoint.clone().sub(vecs[i+1].clone().multiplyScalar(turnDis2)) points.push({position:turnPoint2, target: nextPoint.clone()}) } } } } //加后缀&test以看路线 let data = { name : 'path_guideTour', duration : distance / speed, points, tension } path.animation_ = viewer.modules.CamAniEditor.createAnimation(data) }, play(playDone){ let oldStates = { editEnable: path.editEnable, addOrRemovePoint: path.addOrRemovePoint } path.editEnable && functions.changeEditMode(false) path.addOrRemovePoint && path.setAddOrRemPoint(false) functions.createAni();//不传参数时路径最圆润缓和,但会脱离原路径。传参后除了拐弯都按路径,参数越大越圆润,但容易有折回的bug。 如果没有严格要求就不传参效果最佳。 path.animation_.play() path.animation_.addEventListener('playDone', () => { oldStates.editEnable && functions.changeEditMode(true) oldStates.addOrRemovePoint && path.setAddOrRemPoint(true) playDone && playDone() },{once:true}) }, pause(){ path.animation_.pause() viewer.modules.CamAniEditor.removeAnimation(path.animation_) } }) /* for(let i in functions){ if(functions[i] instanceof Function){ let oldFun = functions[i] functions[i] = function(){ console.warn('path', i, path.title, ...arguments) oldFun.apply(this, arguments) } } } */ path.functions = functions props.line && functions.changeLine(props.line) return functions }, startAddSth(){//开始添加热点 Potree.settings.disableClick = true //禁止点击事件,尤其是全景模式下,否则会走到下一个点 viewer.dispatchEvent('start_inserting_tag') }, endAddSth(){ Potree.settings.disableClick = false viewer.dispatchEvent('endTagMove') }, createTagging(props){ let bus = mitt() //console.warn('createTagging', props) let root = viewer.scene.pointclouds.concat(viewer.objs.children).find(e=>e.dataset_id == props.modelId) if(!root){ return console.error('热点没有找到该modelId,模型是否已经删除?') } let info = { position: new THREE.Vector3().copy(props.position), //局部坐标 normal: new THREE.Vector3().copy(props.normal), root, lineLength: props.altitudeAboveGround, title: props.title, fontsize: props.fontSize } let tag = viewer.tagTool.createTagFromData(info) tag.addEventListener('mouseover',()=>{ bus.emit('enter') }) tag.addEventListener('mouseleave',()=>{ bus.emit('leave') }) tag.addEventListener('click',()=>{ bus.emit('click') }) tag.addEventListener('posChanged',(e)=>{ bus.emit('changePosition', { modelId: tag.root.dataset_id, normal: tag.normal.clone(), pos: tag.position.clone() }) }) tag.functions = { bus, changeType(type){ //console.log('changeType', tag.title, type) let onMesh = type == '3d' if(tag.onMesh != onMesh){ tag.changeOnMesh(onMesh) } }, visibility(v){// 标注可见性 //console.log('visibility', tag.title, v) Potree.Utils.updateVisible(tag,'user', v) viewer.dispatchEvent('content_changed') }, visibilityTitle(v){ tag.setTitleVisi(v, 'user') }, changePosition({modelId,position,normal}){ let root = viewer.scene.pointclouds.concat(viewer.objs.children).find(e=>e.dataset_id == props.modelId) tag.changePos({root,position,normal}) }, changeImage(url){ tag.changeMap(url) }, changeTitle(title){ tag.setTitle(title) }, changeMat({scale,rotation}){//大小旋转 贴墙时 tag.setFaceAngle(rotation) tag.changeSpotScale(scale) }, changeFontSize(fontsize){ tag.setFontSize(fontsize) }, // 更改离地高度 changeLineHeight(height){//线长 tag.changeLineLen(height) }, changeCanMove(canMove){ //console.log('changeCanMove', tag.title, canMove) tag.dragEnable = canMove }, getImageCenter(){ //热点在模型的本地坐标 return tag.onMesh ? tag.position : new THREE.Vector3().addVectors(tag.position, tag.titleLabel.parent.position) }, getCameraDisSquared(){//距离intersect的位置 return viewer.mainViewport.camera.position.distanceToSquared(tag.getWorldPosition(new THREE.Vector3)) /* < tag.farSquared */ }, destory(){ tag.dispose() }, } tag.functions.changeImage(props.image) /* tag.functions.changeType(props.type) */ return tag.functions }, 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') } } function spliceFromArr(model, props, 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]) */ if (!loadNext()) { 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) let loadTimeCost = Date.now() - loadStartTime console.log('所有模型加载完毕, 耗时', parseInt(loadTimeCost) ) autoLoads.filter(e => e.loaded && e.show).forEach(e => e.model.visible = true) MergeEditor.focusOn(autoLoadsDone, 1000, true, true) autoLoads.length = 0 } } } function loadNext(){ let haventLoad = autoLoads.filter(e => !e.loading && !e.loadFinish); let loading = autoLoads.filter(e => e.loading); let needLoad = haventLoad.slice(0, maxLoadingCount - loading.length) needLoad.forEach(e => startLoad(e)) return haventLoad.length > 0 } function startLoad(prop){ /* if(prop.raw.visible !== 1){//用于临时隐藏 setTimeout(()=>{ spliceFromArr(null, prop, false) prop.bus.emit('loadError' ) },1) return } */ if(prop.loading || prop.loadFinish)return Potree.Log(`--开始加载--`, { font: { color: '#f68' } }); console.log('id:', prop.id, ', title:', prop.title, ', filename:', Potree.Common.getNameFromURL(prop.url), ', type:', prop.type, prop) prop.unlit = prop.renderType != 'normal' prop.maximumScreenSpaceError = 70 prop.prefix = prop.raw.prefix /* laserRoot != void 0 && (prop.prefix = laserRoot) //prefix for getdataset //Potree.settings.urls.prefix = prop.prefix = '' */ Potree.addModel(prop, prop.done, prop.progressFun, prop.onError) prop.loading = true } function buildMap(){ if (Potree.settings.showCesium && !window.cesiumViewer) { viewer.backgroundOpacity = 0 //密钥 Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2ZGM2YzY0ZC1kNWE0LTRiYTgtYTkwNS1kYmJiODRjMWUwMmQiLCJpZCI6MjMzMTQ1LCJpYXQiOjE3MjI5OTUwNTB9.niqpkl6xOkQ2KeJjelyDDDydmSGqKXKb5cX2NyxSNAw' window.cesiumViewer = new Cesium.Viewer('app', { useDefaultRenderLoop: true, requestRenderMode: true, //add 只有需要render时才会render,如tile加载完后、镜头移动后 animation: false, baseLayerPicker: false, fullscreenButton: false, geocoder: false, homeButton: false, infoBox: false, sceneModePicker: false, selectionIndicator: false, timeline: false, navigationHelpButton: false, //高德秘钥版 imageryProvider: new Cesium.AmapImageryProvider({key, mapStyle: 'normal'}) //报错 401 (Unauthorized) 的方法 https://blog.csdn.net/LBY_XK/article/details/121992641 //terrainShadows: Cesium.ShadowMode.DISABLED, //terrain地形 }); let satellite = true let imageryProvider = cesImageryProvider || new Cesium.UrlTemplateImageryProvider({ //直接用84坐标,不用转高德 url: `//wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=${satellite?6:7}&x={x}&y={y}&z={z}&token=YOUR_API_KEY`, //style=6是卫星,7是标准 minimumLevel: 0, maximumLevel: satellite?18:19, crossOrigin: 'anonymous', }) cesiumViewer.imageryLayers.removeAll(); cesiumViewer.imageryLayers.addImageryProvider(imageryProvider); Potree.cesScreenshot = (w,h)=>{ console.log('cesScreenshot',w,h) cesiumViewer.scene.canvas.style.width = w+'px' cesiumViewer.scene.canvas.style.height = h+'px' cesiumViewer.scene.canvas.style.visibility = 'hidden' cesiumViewer.resize() cesAspect = w/h let deferred = $.Deferred(); updateMap(w/h)//hfov可能改变了需要update。 setTimeout(()=>{ //延迟是似乎还要做别的处理,否则立即截图的话可能得到绿色底图(俯视状态容易触发) let oldMode = window.cesiumViewer._cesiumWidget._scene.requestRenderMode window.cesiumViewer._cesiumWidget._scene.requestRenderMode = 0 //强制render,否则会黑屏 cesiumViewer.render(); let dataUrl = window.cesiumViewer.scene.canvas.toDataURL('image/png') window.cesiumViewer._cesiumWidget._scene.requestRenderMode = oldMode //Potree.Common.downloadFile(dataUrl, 'screenshot.png') cesAspect = null cesiumViewer.scene.canvas.style.width = '' cesiumViewer.scene.canvas.style.height = '' cesiumViewer.scene.canvas.style.visibility = '' deferred.resolve(dataUrl) },200) //时间短了容易黑屏 return deferred.promise() } } updateMap() } function updateCamNear(type){// 有的漫游场景模型缩放的很小(0.1%),需要缩小near才能看见, 但会造成z-fighting, 离远了看大模型会闪烁 const min = 0.0001, max = 0.1 let near , bigScale = 0.2 if(Potree.settings.displayMode == 'showPanos'/* && type != 'cameraMove' */){ let currentModel = viewer.images360.currentPano.pointcloud near = Potree.math.linearClamp(currentModel.scale.x, [0, 1], [min, max]) }else/* if(type == 'cameraMove') */{ //没有完美的解决方式,优先考虑选中的模型。否则优先考虑离得近的。 另外尽量用大的,因为缩很小的情况很少。 //虽然案例说应该看model.bound.size,但如果非场景模型,缩很小的话也不需要凑近看。 let allModels = viewer.objs.children.concat(viewer.scene.pointclouds) if(allModels.length == 0)return allModels.sort((a,b)=>{return a.scale.x - b.scale.x}) let minS = allModels[0].scale.x, maxS = allModels.pop().scale.x let considerModel if(minS>bigScale) near = max else{ if(MergeEditor.selected){ considerModel = MergeEditor.selected near = Potree.math.linearClamp(considerModel.scale.x, [0, bigScale], [min, max]) }else{ //allModels = allModels.filter() //写不下去了好难,就算了吧, 折中 near = Potree.math.linearClamp(minS, [0, bigScale], [max/4, max]) } } } if(near != viewer.mainViewport.camera.near){ console.log('updateNear',near) viewer.mainViewport.camera.near = near viewer.mainViewport.camera.updateProjectionMatrix() viewer.dispatchEvent('content_changed') } } function updateCamFar(){ let expand = 1.1 //for label Potree.settings.cameraFar = THREE.Math.clamp((viewer.bound.boundingBox.distanceToPoint(viewer.mainViewport.camera.position)+viewer.bound.boundSize.length() ) * expand , 10000, 100000000000) } function updateMap(){ if (Potree.settings.showCesium && Potree.settings.displayMode == 'showPointCloud') { let camera = MergeEditor.split ? viewer.viewports.find(e=>e.name == 'top').camera : viewer.mainViewport.camera let pPos = new THREE.Vector3(0, 0, 0).applyMatrix4(camera.matrixWorld); let orientation let toCes = (pos) => { let xy = [pos.x, pos.y]; let height = pos.z; let deg = viewer.transform.lonlatToLocal.inverse(xy) // toMap.forward(xy); let cPos = Cesium.Cartesian3.fromDegrees(...deg, height); return cPos; }; let cPos = toCes(pPos); if(MergeEditor.split){ orientation = { heading: Cesium.Math.toRadians(0.0), // 方向角 pitch: Cesium.Math.toRadians(-90.0), // 俯仰角 roll: 0.0 // 翻滚角 } if(!cesiumViewer.camera.perpFrustum_){ cesiumViewer.camera.perpFrustum_ = cesiumViewer.camera.frustum cesiumViewer.camera.frustum = new Cesium.OrthographicOffCenterFrustum({//OrthographicFrustum OrthographicOffCenterFrustum left: -10000, // 左边界 right: 10000, // 右边界 bottom: -10000, // 下边界 top: 10000, // 上边界 near: 1.0, // 近裁剪面距离 far: 100000000.0, // 远裁剪面距离 }) } cesiumViewer.camera.frustum.left = camera.left / camera.zoom cesiumViewer.camera.frustum.right = camera.right / camera.zoom cesiumViewer.camera.frustum.top = camera.top / camera.zoom cesiumViewer.camera.frustum.bottom = camera.bottom / camera.zoom }else{ cesiumViewer.camera.perpFrustum_ && (cesiumViewer.camera.frustum = cesiumViewer.camera.perpFrustum_, cesiumViewer.camera.perpFrustum_ = null) //恢复 //let pRight = new THREE.Vector3(600, 0, 0).applyMatrix4(camera.matrixWorld); let pUp = new THREE.Vector3(0, 600, 0).applyMatrix4(camera.matrixWorld); let pTarget = viewer.scene.view.getPivot(); let cUpTarget = toCes(pUp); let cTarget = toCes(pTarget); let cDir = Cesium.Cartesian3.subtract(cTarget, cPos, new Cesium.Cartesian3()); let cUp = Cesium.Cartesian3.subtract(cUpTarget, cPos, new Cesium.Cartesian3()); cDir = Cesium.Cartesian3.normalize(cDir, new Cesium.Cartesian3()); cUp = Cesium.Cartesian3.normalize(cUp, new Cesium.Cartesian3()); //console.log('ces', 'cPos', cPos, 'cDir',cDir, 'cUp', cUp) orientation = { direction: cDir, up: cUp } let aspect = cesAspect || camera.aspect; //console.log('updateMap', aspect) if (aspect < 1) { let fovy = Math.PI * (viewer.scene.getActiveCamera().fov / 180); cesiumViewer.camera.frustum.fov = fovy; } else { let fovy = Math.PI * (viewer.scene.getActiveCamera().fov / 180); let fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 cesiumViewer.camera.frustum.fov = fovx; } } cesiumViewer.camera.setView({ destination: cPos, orientation }); cesiumViewer.scene.globe.show = camera.position.z > 0 //在地面之下地球会闪烁,故隐藏 cesiumViewer.render(); //立即render,否则会和点云render不同步而错位 }//cesium测试沙盒 https://sandcastle.cesium.com/ } return sdk } /* 暂定不同场景间的漫游点不能互通。虽然它们可能是摆放正确的,如果是组成一整个场景的话还是要打通…… 不互通的方法是设置pano.enable 现在需要互通了。但是还需要设置neibgbours, 有点麻烦,暂时没写。 */ export default enter