import mitt from 'mitt' 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 5 : {name:'深光点云' }, 6 : {name:'深光mesh',panos4dkk:true, rot90:true},//3dtiles } let cesAspect export const enter = ({ dom, mapDom, isLocal, lonlat, scenes }) => { 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' } const mapBus = mitt(), sceneBus = mitt() const tagLimitDis = 8; Potree.settings.showCompass = true Potree.settings.compassDom = dom.querySelector('#direction') Potree.settings.showObjectsOnMap = true Potree.settings.mergeType2 = true //标识新版 Potree.settings.modelSkybox = true //是否将全景图贴在模型上(会导致卡顿)。若不显示模型将不显示Reticule Potree.settings.tiles3DMaxMemory = 300 //稍微增加点 Potree.settings.mergeTransCtlOnClick = true Potree.settings.canWalkThroughModel = true Potree.config.view.near = 0.0001 //以防有的漫游场景模型缩放的很小(0.1%) 但似乎会造成z-fighting,不记得之前是否有过bug let { THREE } = Potree.mergeEditStart(dom, mapDom) let MergeEditor = viewer.modules.MergeEditor Potree.settings.unableNavigate = true /* Potree.settings.showCesium = !!lonlat Potree.settings.showCesium && buildMap() */ //因为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() } }) //------------------------------------- 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有事件怕乱所以统一用这个函数 } }) 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' }) 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) sceneBus.emit('webglError', { msg: e.msg }) }) 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) => { //console.log('3d->2d highlight',e.state) bus.emit('highlight', e.state) }) measure.addEventListener('marker_dropped', (e) => {//拖拽结束后发送changeCallBack if (measure.parent) { //未被删除 bus.emit('update', [ measure.dataset_points.map(p => p.clone()), measure.points_datasets ]) } }) return { /* quit: () => { Potree.Log('quit结束且删除: ' + measure.id, '#00c7b2') viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) }, //触发结束。退出测量模式,清除之前操作 */ destroy: () => { viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) viewer.scene.removeMeasurement(measure) }, /* getPoints: () => { return measure.points }, getDatasetLocations: () => { return measure.dataset_points }, getDatasets: () => { return measure.points_datasets }, getDatasetId: () => { return measure.datasetId }, */ getArea: () => { return measure.area //{value:area, string:..} }, getDistance: () => { if (measure.points.length < 2) return 0 var value = measure.points[0].distanceTo(measure.points[1]) return { value, //米 string: measure.getConvertString(value, 'distance') } }, //手动开启或关闭: show: () => { Potree.Utils.updateVisible(measure, 'inListByUser', true) }, hide: () => { Potree.Utils.updateVisible(measure, 'inListByUser', false) }, fly() { let result = viewer.focusOnObject(measure, 'measure', 1200) return result.msg ? result.msg : result.promise //返回值 1 deferred 表示即将位移 2 'posNoChange' 表示已在最佳位置 3 'tooFar' 表示距离最佳位置太远 }, changeSelect(isHight) { console.log('2d->3d isHight ', isHight) measure.setSelected(isHight, 'byList') }, } } let sdk = { sceneBus, 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 let Handler = viewer.inputHandler let reGet = () => {//不使用当前鼠标所在位置的intersect,单独算 pos2d.clientX = pos2d.x pos2d.clientY = pos2d.y pos2d.onlyGetIntersect = true pos2d.whichPointcloud = true if (hopeModelId != void 0) {//隐藏其他的模型 let models = MergeEditor.getAllObjects() models.forEach(model => { Potree.Utils.updateVisible(model, 'forPick', model.dataset_id == hopeModelId) }) } let intersect2 = Handler.onMouseMove(pos2d) if (hopeModelId != void 0) {//恢复 let models = MergeEditor.getAllObjects() models.forEach(model => { Potree.Utils.updateVisible(model, 'forPick', true) }) } if (intersect2 && intersect2.location) { intersect = intersect2 } } if (pos2d && pos2d.inDrag) { reGet() } else { intersect = Handler.intersect if (intersect) { modelId = intersect.pointcloud ? intersect.pointcloud.dataset_id : intersect.object.dataset_id if (hopeModelId != void 0 && modelId != hopeModelId) { reGet() } } } if (intersect && intersect.location) { modelId = intersect.pointcloud ? intersect.pointcloud.dataset_id : intersect.object.dataset_id /* if(hopeModelId != void 0 && modelId != hopeModelId){ return null } */ worldPos = intersect.location.clone() localPos = Potree.Utils.datasetPosTransform({ toDataset: true, datasetId: modelId, position: worldPos }) } else return null return { worldPos, modelId, localPos } }, getScreenByPosition(pos3d, modelId, canShelter/* , disToCameraLimit */) {//通过模型局部坐标获取屏幕坐标 //console.log('getScreenByPoint ') let isLocal = modelId != void 0 pos3d = new THREE.Vector3().copy(pos3d) let worldPos = isLocal ? Potree.Utils.datasetPosTransform({ fromDataset: true, datasetId: modelId, position: pos3d }) : pos3d if (!worldPos) return if (canShelter) { if (viewer.inputHandler.ifBlockedByIntersect(worldPos, 0.1, true)) return { trueSide: false }; } var viewport = viewer.mainViewport var camera = viewport.camera var dom = viewer.renderArea if (tagLimitDis != void 0) { if (camera.position.distanceToSquared(worldPos) > Math.pow(tagLimitDis, 2)) return false } //console.log('getScreenByPoint ' + pos3d.toArray()) return Potree.Utils.getPos2d(worldPos, viewport, dom) }, 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) 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) }) 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/* map.style == 'satellite' ? 'standard' : 'satellite' */) }, 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() }) 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) //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() //console.log('enterAlignment',model.position, model.rotation) let bus = new mitt() /* MergeEditor.transformControls.attach(model) MergeEditor.transformControls.mode = 'translate' */ return { bus } }, leaveAlignment() { //console.log('leaveAlignment',model.position, model.rotation) MergeEditor.leaveSplit() MergeEditor.transformControls.detach() MergeEditor.transformControls2.detach() }, 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) }) let setScale = () => { if (length == void 0 || !measureBuilded) return let vec = new THREE.Vector3().subVectors(viewer.mainViewport.camera.position, scaleMeasure.points[1]) let s = length / (scaleMeasure.points[0].distanceTo(scaleMeasure.points[1])) result.changeScale(model.scale.x * s * 100) /* setTimeout(()=>{ viewer.focusOnObject(scaleMeasure , 'measure', 500) },1) */ let newCamPos = new THREE.Vector3().addVectors(scaleMeasure.points[1], vec.multiplyScalar(s)) viewer.scene.view.setView({ position: newCamPos, target: scaleMeasure.getCenter(), duration: 0, callback: () => { //更改target到measure中心的好处就是可以让相机绕measure中心转,坏处是每次更改都会变一下画面 } }) } return { bus, setLength(v) { if (!v) return length = v setScale() }, startMeasure() { if (scaleMeasure) { viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure: scaleMeasure }) viewer.scene.removeMeasurement(scaleMeasure) } measureBuilded = false scaleMeasure = viewer.measuringTool.startInsertion( { measureType: "Distance", unit: "metric" }, () => { //done: //bus.emit('end' ) //完成 measureBuilded = true setScale() }, () => { //cancel //bus.emit('quit') //删除 } ) scaleMeasure.addEventListener('marker_dropped', (e) => {//拖拽结束后发送changeCallBack if (scaleMeasure.parent) { //未被删除 measureBuilded && setScale() } }) } } }, leaveScaleSet() { if (scaleMeasure) { viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure: scaleMeasure }) viewer.scene.removeMeasurement(scaleMeasure) scaleMeasure = null } //viewer.outlinePass.selectedObjects = [model]; MergeEditor.getAllObjects().forEach(m => {//恢复其他的模型 if (m != model) Potree.Utils.updateVisible(m, 'enterScaleSet', true) }) }, destroy() { model && MergeEditor.removeModel(model) 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', { dataset_points: measure.dataset_points.map(p=>p.clone()) , points_datasets: measure.points_datasets } ) //完成 */ bus.emit('submit') bus.emit('update', [ measure.dataset_points.map(p => p.clone()), measure.points_datasets ]) }, () => { //cancel bus.emit('cancel'/* , ret */) //删除 } ) Potree.Log('startMeasure: ' + measure.id, '#00c7b2') /* let cancel = ()=>{ Potree.Log('clear删除: ' + measure.id, '#00c7b2') viewer.dispatchEvent({ type: 'cancel_insertions', remove: true, measure }) viewer.scene.removeMeasurement(measure) } */ let result = { bus, ...getMeasureFunction(measure, bus), } /* StartMeasure = Measure & { // 多了cancel 取消测量的事件,没有参数 // 多了invalidPoint 当用户测量了无效点时的事件,抛出无效原因 bus: Emitter<{ cancel: void; invalidPoint: string }> } */ return result }, // 绘画测量线(非新增使用) // type = 'free' (自由) || 'vertical' (垂直) || 'area' (面积) // positions 点数组 构成如下 [{ point: {x,y,z}, modelId: 1 }] drawMeasure(type, dataset_points, points_datasets) { // 返回测量对象有如下 const bus = mitt() let info = getMeasureType(type /* , unit */) //info.points = positions info.dataset_points = dataset_points info.points_datasets = points_datasets //info.sid = sid info.bus = bus let measure = viewer.measuringTool.createMeasureFromData(info) if (!measure) return { bus } Potree.Log('drawMeasure由数据新建: ' + measure.id, '#00c7b2') let result = { bus, setPositions(dataset_points, points_datasets) {//用于恢复measure的点,不会修改点的个数 measure.dataset_points = dataset_points.map(e => { return e && new THREE.Vector3().copy(e) }) measure.points_datasets = points_datasets measure.points = measure.dataset_points.map((p, i) => { return Potree.Utils.datasetPosTransform({ fromDataset: true, datasetId: measure.points_datasets[i], position: p }) }) measure.getPoint2dInfo(measure.points) measure.update({ ifUpdateMarkers: true }) measure.setSelected(false)//隐藏edgelabel }, ...getMeasureFunction(measure, bus), } return result }, addTag(info) {//加标签 let bus = mitt() let tag let done = () => { bus.emit('added') bus.emit('update', { position: tag.position.clone(), normal: o.normal.clone(), modelId: tag.root.dataset_id }) tag = tag_ tag.spot.addEventListener('mouseover', () => { bus.emit('hoverState', true) }) tag.spot.addEventListener('mouseout', () => { bus.emit('hoverState', false) }) } if (!info.position) { viewer.tagTool.startInsertion().done(tag_ => { done() }) } else { info.root = MergeEditor.getAllObjects().find(e => e.dataset_id == info.modelId) if (!info.root) { console.error('没有找到该modelId') } tag = viewer.tagTool.createTagFromData(info) done() } let result = { bus, getScreenPos() { let pos3d = new THREE.Vector3().setFromMatrixPosition(tag.matrixWorld) return sdk.getScreenByPosition(pos3d) }, show() { Potree.Utils.updateVisible(tag, 'byList', true) }, hide() { Potree.Utils.updateVisible(tag, 'byList', false) }, destroy() { if (tag) { tag.dispose() } viewer.dispatchEvent({ type: 'cancel_insertions', remove: true }) }, changeTitle(title) { tag.changeTitle(title) } } return result }, showGrid() { Potree.Utils.updateVisible(viewer.modules.MergeEditor.ground, 'hideGrid', true) viewer.dispatchEvent('content_changed') }, hideGrid() { Potree.Utils.updateVisible(viewer.modules.MergeEditor.ground, 'hideGrid', false) viewer.dispatchEvent('content_changed') } } 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 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 : Cesium.createOpenStreetMapImageryProvider({url : 'https://a.tile.openstreetmap.org/'}), imageryProvider: Cesium.UrlTemplateImageryProvider({ //直接用84坐标,不用转高德 //"https://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&style=7&yrs=m&x=${x}&y=${y}&z=${z}" // //url : 'https://webst0{0-7}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}&token=YOUR_API_KEY', url: 'https://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}&token=YOUR_API_KEY', minimumLevel: 0, maximumLevel: 19 }), //高德秘钥版 imageryProvider: new Cesium.AmapImageryProvider({key, mapStyle: 'normal'}) //报错 401 (Unauthorized) 的方法 https://blog.csdn.net/LBY_XK/article/details/121992641 terrainShadows: Cesium.ShadowMode.DISABLED, //terrain地形 }); //lonlat = [113.595236803415,22.3665168584444]//[113.600356,22.364093] Potree.setLonlat(lonlat[0], lonlat[1]) 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 updateMap( ){ if (Potree.settings.showCesium && Potree.settings.displayMode == 'showPointCloud') { let camera = viewer.mainViewport.camera let pPos = new THREE.Vector3(0, 0, 0).applyMatrix4(camera.matrixWorld); 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 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); 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()); cesiumViewer.camera.setView({ destination: cPos, orientation: { direction: cDir, up: cUp } }); let aspect = cesAspect || viewer.scene.getActiveCamera().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.render(); //立即render,否则会和点云render不同步而错位 } } return sdk } /* 暂定不同场景间的漫游点不能互通。虽然它们可能是摆放正确的,如果是组成一整个场景的话还是要打通…… 不互通的方法是设置pano.enable 现在需要互通了。但是还需要设置neibgbours, 有点麻烦,暂时没写。 */ export default enter