import mitt from 'mitt' import libTransform from 'coordtransform'; import axios from 'axios' //{ axios } from '@/api' 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 or obj 5 : {name:'深光点云' }, 6 : {name:'深光mesh',panos4dkk:true, rot90:true},//3dtiles 7 : {name:'圆周率相机' },//圆周率相机场景 8 : {name:'动画模型'} } let satellite = true let defaultMapProps = [{url: `//wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=${satellite?6:7}&x={x}&y={y}&z={z}&layer=6&token=YOUR_API_KEY`, //style=6是卫星,7是标准 maximumLevel: satellite?18:19, name:'高德baseLayer' },{ url: `//wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}&layer=6&token=YOUR_API_KEY`, //style=6是卫星,7是标准 maximumLevel: 18, name:'高德textLayer' }] let cesAspect , cesImageryProvider, mapProps = defaultMapProps const Id_noIntersect = -100 //path绘制在地图上的点,modelId传这个值,勿更改 let isValidPoint = (modelId)=>{//所存的modelId没被删或者它本身不在模型上 return modelId == Id_noIntersect || viewer.objs.children.concat(viewer.scene.pointclouds).some(e=>e.dataset_id == modelId ) } let curSelectPath { // 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__ = { //在Potree里setLonlat时不管转不转效果都一样 很奇怪,所以这里改名。只在ces 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.settings.urls.templates = { depthTex : 'swss/{sceneCode}/www/{sceneCode}/wwwroot/{sceneCode}/data/{sceneCode}/depthmap', vision : '/swkk/{sceneCode}/wwwroot/scene_view_data/{sceneCode}/images/vision.txt' } Potree.fileStorage = { get(url){ return new Promise(async function(resolve,reject){ let data = await window.offlineData[url] if(data){ resolve(data) }else{ console.error('没找到',url) reject() } }) } } } if(laserRoot != void 0){ Potree.settings.urls.prefix = Potree.settings.urls.handlePrefix(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 Potree.settings.urls.setPrefix(8, 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 = '内存占用过高,建议关闭部分场景或升级显卡。' window.cesErrorCallback = ()=>{ //sdk.setBackdrop('none') } let { THREE } = Potree.mergeEditStart(dom, mapDom) let {MergeEditor, AnimationEditor} = viewer.modules 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 }) Potree.Common.intervalTool.isWaiting('updateCamNear', ()=>{ updateCamNear() }, 1000) updateCamFar() } if (e.viewport.name == 'MainView' || e.viewport.name == 'top' ) { updateMap() } }) //------------------------------------- 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) viewer.objs.children.forEach(model=>{ model.result_.updateVisiByRange && model.result_.updateVisiByRange() }) } } 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) viewer.addEventListener('watchMonitor',()=>{ sceneBus.emit('watchMonitor') }) /* 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() { if(measure.type == 'Path') Potree.settings.displayMode = 'showPointCloud' let result = viewer.focusOnObject(measure, 'measure', 1200, {dontLookUp:measure.type == 'Path', maxDis: measure.fadeFar && measure.fadeFar*1.5}) 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, far=Potree.config.panoFieldRadius) { pos = pos ? new THREE.Vector3().copy(pos) : viewer.images360.position let pano = viewer.images360.findNearestPano(pos) if (pano && pano.position.distanceTo(pos) < far * 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 || o.maxDis) { //o.isFlyToTag = true let requestShowPano let position = o.target || o.position if(o.isFlyToTag){ let r = sdk.canTurnToPanoMode(position, 5)//热点新需求:如果附近有漫游点就飞到全景模式 if(r){ requestShowPano = true }else{ Potree.settings.displayMode = 'showPointCloud' } } let rusult = viewer.focusOnObject({ position }, 'tag', null, { distance: o.distance || 1, maxDis : o.maxDis, requestShowPano /* , checkIntersect:true */ }) rusult.promise.then(()=>{ if(o.isFlyToTag){ Potree.settings.displayMode = requestShowPano ? 'showPanos' : 'showPointCloud' } }) return rusult.promise } viewer.scene.view.setView($.extend({}, o, { duration: o.dur, callback: () => { o.callback && o.callback() deferred.resolve(true) } })) return deferred.promise() }, comeToByLatLng(lonlat){//飞到指定经纬度 let pos = viewer.transform.lonlatToLocal.forward(lonlat) let location = viewer.mainViewport.view.position.clone().setX(pos[0]).setY(pos[1]) viewer.scene.view.setView({position:location, duration:500}); }, 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){//切换成江门的卫星或标准 if(window.location.href.includes('localhost:7173')/* || Potree.browser.urlHasValue('testMap') */ ) return console.log('switchMapType',type) mapProps = [{ name:'江门', maximumLevel : type == 'satellite' ? 18 : 19, url: type == 'satellite' ? "//a.map.jms.gd/tile/weixing/{z}/{x}/{y}.png" : "//a.map.jms.gd/tile/gd_xiangtu/{z}/{x}/{y}.png" }] if(Potree.settings.showCesium){ buildMapFromProp() } }, changeMapTile(urls){ //'http:/a.xxx.com/{x}/{y}/{z}.png' console.log('changeMapTile',urls) mapProps = urls.map(e=>{ return { maximumLevel : e.maximumLevel, url: e.tempUrl, name: e.name || '' } }) if(Potree.settings.showCesium){ buildMapFromProp() } }, 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 model 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 = 100 //if (props.type == 'obj') props.type = 'glb' props.scale && (props.scale /= 100) let getBaseRotation = () => { if(ModelTypes[props.fromType]?.rot90 && props.type != 'obj'){ return new THREE.Euler(Math.PI / 2, 0, 0) } else return new THREE.Euler(0, 0, 0) } let getDefaultRotation = () => { if(model.lonLatRot){ return model.lonLatRot }else{ return getBaseRotation() } } if (props.rotation) { if (props.rotation._x == void 0 && props.rotation.x != void 0) { props.rotation = new THREE.Euler().setFromVector3(props.rotation) } } props.baseRotation = getBaseRotation() props.is4dkkModel = ModelTypes[props.fromType].panos4dkk if (!props.isFirstLoad) { if (autoLoads.length == 0) { //首次加载 setTimeout(() => { let sizes = autoLoads.map(e => e.size || 0) console.log('需要请求加载的模型为', autoLoads.map(e=>e.title) , '总大小', 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 } 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 = /* !model.is4dkkModel// */props.raw.showInPano //现在不用这个,所有模型都可见,非is4dkkModel的还显示原本的贴图 props.opacity < 100 && result.changeOpacity(props.opacity) model.addEventListener('changeSelect', (e) => { bus.emit('changeSelect', e.selected) }) let lastState = {} model.addEventListener('transformChanged', (e) => { let msg = {byControl:!!e.byControl} //byControl代表是手动用控制轴修改 if (!lastState.position || !model.position.equals(lastState.position)) { lastState.position = msg.position = model.position.clone() //console.log('change pos', model.name, model.position.toArray()) } if (!lastState.rotation || !model.rotation.equals(lastState.rotation)) { lastState.rotation = model.rotation.clone() msg.rotation = model.rotation.toObject() } 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(model.name, 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) load4dkkMedias(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, getBaseRotation(), () => { bus.emit('loadDone',model) }, props.fromType == 0 ? '2k' : '4k' ) //看看场景是2k } else { bus.emit('loadDone',model) } //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) */ }, moveModelTo(mouse, pos3d){//'移动到这里' console.log('moveModelTo', mouse) let viewport = viewer.mainViewport if(!pos3d){ let width = viewport.width * viewer.renderArea.clientWidth let height = viewport.height * viewer.renderArea.clientHeight let pointer = Potree.Utils.convertScreenPositionToNDC(null, mouse, width, height); let {x,y} = Potree.Utils.getPointerPosAtHeight(model.boundCenter.z, pointer) pos3d = new THREE.Vector3(x,y, model.boundCenter.z) } MergeEditor.moveBoundCenterTo(model, pos3d) //使模型中心的xy在鼠标所在位置 model.dispatchEvent("position_changed") }, 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)) } } }, changeSelect(state) { //console.error('select', state) if (model) { let focus = true //viewer.images360.latestRequestMode != 'showPanos' MergeEditor.selectModel(model, state, focus, 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', model.name, pos.x, pos.y, pos.z) if(pos.x == 0 && pos.y == 0 && pos.z == 0 && model.lonLatPos ){ model && model.position.copy(model.lonLatPos) console.log('changePosition 使用经纬度坐标', model.name ) }else{ model && model.position.copy(pos) } model.dispatchEvent({ type: 'position_changed' }) }, changeRotation(rot) {//校准取消时执行 //console.log('changeRotation', model.name, rot.x, rot.y, rot.z) /* if(rot.x == 0 && rot.y == 0 && rot.z == 0 && model.lonLatRot ){ model && model.rotation.copy(model.lonLatRot) console.log('changePosition 使用经纬度坐标', model.name ) }else{ */ model && model.rotation.setFromVector3(rot) //} model.dispatchEvent({ type: 'rotation_changed' , by2d:true}) }, getModelPose(){ return { position: model.position.clone(), rotation: model.rotation.toObject(), quaternion: model.quaternion.toObject(), scale: model.scale.clone() } }, 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' } }, enterScaleMode(){ if (model) { MergeEditor.transformControls.attach(model) MergeEditor.transformControls.mode = 'scale' } }, 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 }, createAnimationGroup(){//创建动画模块 let bus = mitt() AnimationEditor.addEventListener('atTime',e=>{//发送当前动画时间,同步字幕 //console.log('currentTime',e.time ) bus.emit('currentTime',e.time ) }) let funs = { bus, play(){ console.log('play ani, time:', AnimationEditor.cursorTime) AnimationEditor.play({time:AnimationEditor.cursorTime }) }, pause(){ console.log('pause ani') AnimationEditor.pause() }, setCurrentTime(s){// 设置当前时间, 单位为秒 (!AnimationEditor.playing || s == 0) && AnimationEditor.at(s) //除非s==0否则一定要先pause后才能设置时间 //console.log('setCurrentTime',s, AnimationEditor.playing, AnimationEditor.cursorTime) }, addAnimationModel(data){// 添加动画模型 /* title: string; 模型名称 url: string; 模型路径 showTitle: boolean; 是否显示名称 fontSize: number; 名称字体大小 globalVisibility: boolean; 是否全局可视 visibilityRange: number; */ console.log('添加动画文件',data) let update = ()=>{ AnimationEditor.at(AnimationEditor.cursorTime || 0,null,true) } let model let prop = { url: data.url, title: data.title, fromType: 8, raw:{}, //renderType : 'normal', //色彩好些,否则MeshBasic颜色偏深 type:'obj', id:data.id, dontFocus:true } let modelFuns = sdk.addModel(prop) let visibleRange let updateVisiByRange = ()=>{//可见范围 let visi = true if(visibleRange){ let cameraPos = viewer.mainViewport.view.position let dis = model.boundCenter.distanceTo(cameraPos) visi = dis < visibleRange } Potree.Utils.updateVisible(model,'outOfVisiRange', visi) } modelFuns.bus.on('loadDone',(model_)=>{ model = model_ MergeEditor.addTitleForModel(model) model.addEventListener('transformChanged',updateVisiByRange) model.result_ = result setTimeout(e=>{ update()//计算下默认姿态 },1) }) let result = Object.assign(modelFuns, { updateVisiByRange, visibilityTitle(v){ Potree.Utils.updateVisible(model.titleLabel,'user', v) }, // 更改动画可见范围 不传为全局可见 changeVisibilityRange(range){ visibleRange = range updateVisiByRange() }, // 更改模型名称 changeTitle(name){ model.name = name if(model.titleLabel){ model.titleLabel.setText(name) viewer.dispatchEvent('content_changed') } }, changeFontSize(fontsize){ model.titleLabel.fontsize = fontsize model.titleLabel.updateTexture(); viewer.dispatchEvent('content_changed') }, // 添加模型帧 addFrame(frame){//pose 至少会有一个关键帧作为默认姿态 //console.log('addFrame',frame) let key = {time: frame.time } let getData = (data)=>{ let info = {} info.pos = new THREE.Vector3().copy(data.position) info.scale = (typeof data.scale == 'number') ? new THREE.Vector3(data.scale/100,data.scale/100,data.scale/100) : new THREE.Vector3().copy(data.scale) info.qua = new THREE.Quaternion().copy(data.quaternion) return info } Object.assign(key, getData(frame.mat)) AnimationEditor.addKey(model, 'pose', key ) return { destroy(){ AnimationEditor.removeKey(model,'pose', key) update() }, changeTime(time){ if(time==key.time)return key.time = time AnimationEditor.reOrderKey(model, 'pose', key) update() }, setMat(data){//设置帧 //console.log('设置帧',data.scale) Object.assign(key,getData(data)) //update() //更新的话会使transformControls打滑,因camFollowObject跟随该物体 } } }, getSupportActions(){ return model.actions?.map(e=>e._clip.name) }, addAction(frame){// 添加模型动作 //console.log('addAction',frame) let key = Object.assign({},frame) key.weight = key.amplitude || 1 //幅度 key.speed = key.speed || 1 key.action = model.actions.find(e=>e._clip.name == key.key) if(!key.action){ //return console.error('cannot find action', key.key) let random = Math.floor(Math.random() * (model.actions.length)) random = Math.min(model.actions.length-1, random) console.log('没找到动作,暂时先用第'+ random +'个') key.action = model.actions[random] } delete key.amplitude delete key.key delete key.duration AnimationEditor.addKey(model, 'clip', key) return { destroy(){ AnimationEditor.removeKey(model,'clip', key) update() }, changeTime(time){ if(time==key.time)return key.time = time AnimationEditor.reOrderKey(model, 'clip', key) update() }, changeDuration(dur){ key.dur = dur AnimationEditor.updateTimeRange() update() }, changeAmplitude(weight){//修改动作幅度 key.weight = weight update() }, changeSpeed(speed){ key.speed = speed update() } } }, addPath(frame){//走路路径 //console.log('addPath',frame) let key = Object.assign({},frame) key.path = key.path.path key.dur = key.duration delete key.duration AnimationEditor.addKey(model, 'path', key) return { destroy(){ AnimationEditor.removeKey(model, 'path', key) update() }, changeTime(time){ if(time==key.time)return key.time = time AnimationEditor.reOrderKey(model, 'path', key) update() }, changeDuration(dur){ key.dur = dur AnimationEditor.updateTimeRange() update() }, changeReverse(reverse){ key.reverse = reverse update() }, changePath(path){ key.path = path.path update() } } }, /* getCurrentMat(){// 获取当前时间改模型的姿态 return { quaternion: model.quaternion.clone(), scale: model.scale.clone(), position: model.position.clone() } }, */ getCurrentSubtitlePixel({width , height}){// 获取当前模型旁白出现的适合位置,传入旁边dom的宽高,返回像素位置 // let startTime = performance.now() let bound = model.boundingBox let boundPoints = [ new THREE.Vector3(bound.min.x, bound.min.y, bound.min.z ), new THREE.Vector3(bound.min.x, bound.min.y, bound.max.z ), new THREE.Vector3(bound.min.x, bound.max.y, bound.min.z ), new THREE.Vector3(bound.max.x, bound.min.y, bound.min.z ), new THREE.Vector3(bound.max.x, bound.max.y, bound.min.z ), new THREE.Vector3(bound.max.x, bound.min.y, bound.max.z ), new THREE.Vector3(bound.min.x, bound.max.y, bound.max.z ), new THREE.Vector3(bound.max.x, bound.max.y, bound.max.z ), ]; let pos2ds = boundPoints.map((point)=>{ return Potree.Utils.getPos2d(point.applyMatrix4(model.matrixWorld), viewer.mainViewport , viewer.renderArea, viewer.renderer ) }).filter(e=>e.inSight && e.trueSide ) if(pos2ds.length == 0)return pos2ds.sort((a,b)=>{return a.vector.y - b.vector.y}) let top = pos2ds[0], btm = pos2ds[pos2ds.length - 1] let centerY = (top.pos.y + btm.pos.y) / 2 pos2ds.sort((a,b)=>{return a.vector.x - b.vector.x}) let left = pos2ds[0], right = pos2ds[pos2ds.length - 1] let leftPad = left.vector.x - (-1) //模型左侧空出的宽度 let rightPad = 1 - right.vector.y const margin = 10; let x let y = centerY - height/2 //返回字幕左上角位置 if(leftPad > rightPad){//向左侧扩展 x = left.pos.x - margin - width }else{ x = right.pos.x + margin } //console.log('获取字幕位置', performance.now() - startTime ) return {x,y} }, delayEndTime(){ return Potree.settings.maxClipFadeTime / 2 } }) return result }, } return funs }, //测量线的点都附着于各个模型,当模型变化时,点跟着变化。 // 新的测量创建方法,传入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 }, createPath(props){//路线 //console.log('createPath', props) let bus = mitt() let path let info = {type : 'Path', minMarkers : 2, title:props.name, lineHeight : props.line.altitudeAboveGround } 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)=>{ switchSelect(e.state) bus.emit('focus', e.state) }) path.addEventListener('dragLineLen',(e)=>{ bus.emit('changeLineHeight', path.lineHeight) }) } let funs = getMeasureFunction(path, bus) let switchSelect = (state)=>{//切换选择,最多一个选中 if(state){ curSelectPath && curSelectPath.setSelected('unclick' ) //取消上一个选中的 curSelectPath = path }else{ curSelectPath == path && (curSelectPath = null) } } let functions = Object.assign(funs,{ bus, path, changeEditMode(state){//进入编辑 if(!state){ viewer.dispatchEvent({ type: 'cancel_insertions', dontRemove: true, measure:path }) } path.setEditEnable(state) }, 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.setColor(color) path.setLineHeight(altitudeAboveGround) }, changeVisibilityRange(far){//设置消失距离 path.setFadeFar(far== -1 ? 0 : far) }, highlight(state){ path.setSelected(state?'hover':'unhover', true) }, focus(state){ switchSelect(state) path.setSelected(state?'click':'unclick', true) }, changeDirection(show,reverse){ path.setArrowDisplay(show) path.setReverse(reverse) }, createAni(tension){ let distance = path.totalLength 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){ if(path.points.length < 2)return playDone && playDone() //no points Potree.settings.displayMode = 'showPointCloud' let oldStates = { editEnable: path.editEnable, addOrRemovePoint: path.addOrRemovePoint } path.editEnable && functions.changeEditMode(false) path.addOrRemovePoint && path.setAddOrRemPoint(false) if(Potree.settings.pathSmooth){ let curve = path.curve.clone(); curve.points.forEach(e=>e.z += 2) if(path.reverse) curve.points.reverse() //let geoPoints = path.geoPoints.map(e=> e.clone().add(new THREE.Vector3(0,0,2)) )//height let duration = path.totalLength / 3 //let tangentDt = path.totalLength * 0.0001 path.animation_ = viewer.modules.CamAniEditor.createCurveAni(curve, duration ) }else{ 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() path.animation_ && viewer.modules.CamAniEditor.removeAnimation(path.animation_) path.animation_ = null } }) /* 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: props.normal ? new THREE.Vector3().copy(props.normal) : new THREE.Vector3(0,0,1), 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.addEventListener('dragLineLen',(e)=>{ bus.emit('changeLineHeight', tag.lineLength) }) 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) }, 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(){ //热点在模型的本地坐标 if(!tag.parent)return new THREE.Vector3 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 */ }, destroy(){ 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) }, hideGrid() { Potree.Utils.updateVisible(viewer.modules.MergeEditor.ground, 'hideGrid', false) }, exitWatchMonitor(){ viewer.scene.monitors.find(e=>e.isWatching).leave() } } 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) autoLoads.filter(e => e.loaded && e.show).forEach(e => e.model.visible = true) if(autoLoads.length > 1){ let loadTimeCost = Date.now() - loadStartTime console.log('所有模型加载完毕, 耗时', parseInt(loadTimeCost) ) } loadStartTime = Date.now() if(!props.dontFocus){ 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 buildMapFromProp(){ cesiumViewer.imageryLayers.removeAll(); mapProps.forEach(e=>{ let gaoDeImageryProvider = new Cesium.UrlTemplateImageryProvider({ url:e.url, minimumLevel: 0, maximumLevel: e.maximumLevel, credit: new Cesium.Credit(e.name), tilingScheme: new AmapMercatorTilingScheme(), //修改投影,从84->高德 crossOrigin: 'anonymous', }) cesiumViewer.imageryLayers.addImageryProvider(gaoDeImageryProvider); }) } 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地形 //自带的地图直接用84坐标 }); buildMapFromProp() 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); //console.log('toCes',cPos,height) //数字过大如e35会崩溃 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/ } class AmapMercatorTilingScheme extends Cesium.WebMercatorTilingScheme { constructor(options) { super(options) let projection = new Cesium.WebMercatorProjection() this._projection.project = function(cartographic, result) { //WGS84转GCJ02坐标 /* result = gcoord.transform([ Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude) ], gcoord.WGS84, gcoord.GCJ02) */ result = AMapWith84__.wgs84ToAMap({ x: Cesium.Math.toDegrees(cartographic.longitude), y: Cesium.Math.toDegrees(cartographic.latitude) }) result = projection.project(new Cesium.Cartographic(Cesium.Math.toRadians(result.x),Cesium.Math.toRadians(result.y))) return new Cesium.Cartesian2(result.x,result.y) } this._projection.unproject = function(cartesian, result) { let cartographic = projection.unproject(cartesian) //GCJ02转WGS84坐标 /* result = gcoord.transform([ Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude) ], gcoord.GCJ02, gcoord.WGS84) */ result = AMapWith84__.aMapToWgs84({ x: Cesium.Math.toDegrees(cartographic.longitude), y: Cesium.Math.toDegrees(cartographic.latitude) }) return new Cesium.Cartographic(Cesium.Math.toRadians(result.x),Cesium.Math.toRadians(result.y)) } } }//see : https://blog.csdn.net/hongxianqiang/article/details/140541555 cesium加载高德地图并纠偏 return sdk } function load4dkkMedias(model){//加载四维看看的一些媒体物品 if(model.isPointcloud)return let {sceneJsonPath,surveillancePath} = model.props.raw if(sceneJsonPath){//box图片视频 Potree.loadFile(sceneJsonPath,null,(json)=>{ console.log(model, 'sceneJson', json) //json.surveillances //监控开关? 'video' if(json.boxPhotos || json.boxVideos){ let boxPhotos = json.boxPhotos ? JSON.parse(json.boxPhotos) : [] let boxVideos = json.boxVideos ? JSON.parse(json.boxVideos) : [] let medias = boxPhotos.concat(boxVideos) medias.forEach(prop=>{ //Potree.addModel(prop, prop.done, prop.progressFun, prop.onError) prop.type = 'media' prop.model = model prop.position = Potree.Utils.tran4dkkVecInModel(new THREE.Vector3().fromArray(prop.pos), model) , prop.rotation = new THREE.Euler().setFromQuaternion(new THREE.Quaternion().fromArray(prop.qua)), prop.url = 'oss/scene_view_data/'+ model.props.raw.num + '/user/'+prop.url, prop.id = prop.sid //Potree.settings.urls.getPrefix(8,model) //isNew:true, //是否新创建而非加载 Potree.addModel(prop,(overlay)=>{ //overlay.scale.set(10,10,10) }) }) } }) } if(surveillancePath){//监控 Potree.loadFile(surveillancePath,null,(json)=>{ console.log(model, 'surveillance', json) json.forEach((e)=>{ viewer.addMonitor(e,model) }) }) } //cutModelPath裁剪模型路径 } /* 暂定不同场景间的漫游点不能互通。虽然它们可能是摆放正确的,如果是组成一整个场景的话还是要打通…… 不互通的方法是设置pano.enable 现在需要互通了。但是还需要设置neibgbours, 有点麻烦,暂时没写。 */ export default enter