import mitt from 'mitt' import axios from 'axios' //{ axios } from '@/api' export const enter = (dom, isLocal) => { Potree.settings.isOfficial = true //标记为正式、非测试版本 //Potree.fileServer = axios Potree.settings.libsUrl = './lib/' const tagLimitDis = 8; Potree.settings.showCompass = true Potree.settings.compassDom = dom.querySelector('#direction') let {THREE} = Potree.mergeEditStart(dom) let MergeEditor = viewer.modules.MergeEditor let sceneBus = mitt() viewer.addEventListener('camera_changed', e => { var camera = e.viewport.camera var pos = camera.position if (e.viewport.name == 'MainView') { sceneBus.emit('cameraChange', { x: pos.x, y: pos.y, z: pos.z, rotate: camera.rotation }) } }) viewer.addEventListener('webglError', e => { console.error('viewer webglError: ' + e) sceneBus.emit('webglError', { msg: e.msg }) }) window.THREE = THREE //isLocal = false let autoLoads = [] let readyToAddModel let maxLoadingCount = /* isLocal ? 1 : */2; //正在加载模型的最大数目 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: viewer.unitConvert.convert(value, 'distance', void 0, measure.unitSystem, 0.1, true), } }, /* changeUnit: unit => { //公制|英制 , 1 | 2 单位 measure.setUnitSystem(units[unit]) }, toDataURL: (width, height) => { //截图 isScreenshoting = true var promise = viewer.startScreenshot({ type: 'measure', measurement: measure, hideMarkers: true }, width, height) promise.done(() => { isScreenshoting = false }) return promise }, */ //手动开启或关闭: show: () => { viewer.updateVisible(measure, 'inListByUser', true) }, hide: () => { viewer.updateVisible(measure, 'inListByUser', false) }, fly(){ let result = viewer.focusOnObject(measure , 'measure', 1200 ) return result.msg ? result.msg : result.promise //返回值 1 deferred 表示即将位移 2 'posNoChange' 表示已在最佳位置 3 'tooFar' 表示距离最佳位置太远 }, changeSelect(isHight){ console.log('2d->3d isHight ',isHight) measure.setSelected(isHight, 'byList') }, } } let sdk = { sceneBus, getPositionByScreen(pos2d, hopeModelId ){//通过屏幕坐标获取真实坐标 . hopeModelId: 如果指定了模型,优先返回hopeModelId上的intersect console.log('getPositionByScreen',hopeModelId) hopeModelId = null let worldPos, localPos, modelId, intersect let Handler = viewer.inputHandler let reGet = ()=>{//不使用当前鼠标所在位置的intersect,单独算 pos2d.clientX = pos2d.x pos2d.clientY = pos2d.y pos2d.onlyGetIntersect = true pos2d.whichPointcloud = true if(hopeModelId != void 0){//隐藏其他的模型 let models = MergeEditor.getAllObjects() models.forEach(model=>{ viewer.updateVisible(model, 'forPick', model.dataset_id == hopeModelId) }) } let intersect2 = Handler.onMouseMove(pos2d) if(hopeModelId != void 0){//恢复 let models = MergeEditor.getAllObjects() models.forEach(model=>{ viewer.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 */){//通过模型局部坐标获取屏幕坐标 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.distanceTo(worldPos) > tagLimitDis)return false } //console.log('getScreenByPoint ' + pos3d.toArray()) return Potree.Utils.getPos2d(worldPos, camera, dom, viewport) }, screenshot: (width, height) => { //截图 var promise = viewer.startScreenshot({ type: 'default' }, width, height) promise.done(() => { }) return promise }, getPose() {//获取当前点位和朝向 const camera = viewer.scene.getActiveCamera() const target = viewer.scene.view.getPivot().clone() const position = viewer.scene.view.position.clone() console.log('getPose',position, target) return { position, target } }, comeTo(o = {}) { console.log('comeTo',o.position, o.target) //飞到某个点 if(o.modelId){ ['position','target'].forEach(e=>{ if(o[e]){ o[e] = Potree.Utils.datasetPosTransform({ fromDataset: true, datasetId: o.modelId, position:o[e]}) } }) } if(o.distance){ let position = o.target || o.position return viewer.focusOnObject({ position}, 'tag', null,{distance:o.distance} ).promise } let deferred = $.Deferred() viewer.scene.view.setView($.extend({},o, { duration: o.dur, callback:()=>{ o.callback && o.callback() deferred.resolve(true) } })) return deferred.promise() }, /* getPose(o={}) { //获取相对于第一个数据集的初始画面。(当数据集校准后,如果初始画面设置在被修改的数据集上,且该数据集非初始数据集的话,还是会偏移的) var deferred = o.deferred || $.Deferred(); console.log('getPose') if(viewer.mainViewport.view.isFlying()){ let f = ()=>{ this.getPose(o) viewer.mainViewport.view.removeEventListener('flyingDone', f) } viewer.mainViewport.view.addEventListener('flyingDone', f) //once o.deferred = deferred return deferred.promise() } var camera = viewer.scene.getActiveCamera() var rotation = camera.rotation var pos_In_dataset = Potree.Utils.datasetPosTransform({ toDataset: true, position: camera.position.clone(), datasetId: Potree.settings.originDatasetId }) var rot_In_dataset = Potree.Utils.datasetRotTransform({ toDataset: true, rotation, getRotation: true, datasetId: Potree.settings.originDatasetId }) //拿第一个数据集 var view = viewer.scene.view.clone() view.rotation = rot_In_dataset //获取yaw pitch var pose = { //displayMode: Potree.settings.displayMode, position: pos_In_dataset, yaw: view.yaw, pitch: view.pitch, displayMode : Potree.settings.displayMode, panoSid: viewer.images360.currentPano.sid } //return pose setTimeout(()=>{ deferred.resolve(pose) console.log('getPose resolve',pose) },1) return deferred.promise() }, setPose(o = {}, duration=0) { //设置相机位置和朝向 var deferred = o.deferred || $.Deferred(); console.warn('setPose 初始画面', o) var quaternion let view = viewer.scene.view.clone() if(viewer.mainViewport.view.isFlying()){ let f = ()=>{ this.setPose(o, duration) viewer.mainViewport.view.removeEventListener('flyingDone', f) } viewer.mainViewport.view.addEventListener('flyingDone', f) //once o.deferred = deferred return deferred.promise() } var getQuaternion = ()=>{ view.pitch = o.pitch view.yaw = o.yaw quaternion = Potree.Utils.datasetRotTransform({ fromDataset: true, rotation: view.rotation, getQuaternion: true, datasetId: Potree.settings.originDatasetId }) //拿第一个数据集 } viewer.images360.cancelFlyToPano()//防止旧的在之后继续执行 let pano if(o.panoSid != void 0){//好像都不存这个 pano = viewer.images360.panos.find(e=>e.sid == o.panoSid) if(pano == void 0)return deferred.reject('没有找到该panoSid').promise() getQuaternion() viewer.images360.flyToPano({pano, duration, quaternion},()=>{ deferred.resolve() }) }else{ if(Potree.settings.displayMode == 'showPanos'){ return deferred.reject('全景模式下不允许设置位置').promise() } let position = Potree.Utils.datasetPosTransform({ fromDataset: true, position: o.position, datasetId: Potree.settings.originDatasetId }) //view.position.copy(position) getQuaternion() pano = viewer.images360.panos.find(e=>Potree.math.closeTo(e.position, position)) if(pano){//如果原来在某pano上最好也使currentPano为此pano,否则isAtPano会返回false viewer.images360.flyToPano({pano, duration, quaternion},()=>{ deferred.resolve() }) }else{ viewer.scene.view.setView({position,quaternion,duration, callback:()=>{ //setTimeout(()=>{ deferred.resolve() console.log('setPose resolve') //},1) } }) viewer.mapViewer.moveTo(position, null, duration) //初始位置在地图居中 } } return deferred.promise() }, */ enterSceneGuide(pathArr){//导览 (不需要修改参数) let editor = viewer.modules.CamAniEditor console.log('pathArr',pathArr) /* type SceneGuidec = { position: {x,y,z} target: {x,y,z} time: number speed: number //没用到 } */ console.log('enterSceneGuide',pathArr) let data = { duration: pathArr.slice(0,pathArr.length-1).reduce(function(total, currentValue ){return total+currentValue.time}, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义) points: pathArr, useDurSlice:true } let animation = editor.createAnimation(data) //注:最多只存在一条导览 let bus = mitt() //播放完成 animation.addEventListener('playDone', () => { bus.emit('playComplete') }) //切换点 animation.addEventListener('updateCurrentIndex', e => { bus.emit('changePoint', e.currentIndex + 1) }) return { bus, play() { MergeEditor.selectModel(null) animation.play() }, pause() { animation.pause() }, clear() { //删除 editor.removeAnimation(animation) }, } }, //[path1, paht2], { time, speed } calcPathInfo(paths, info){ //传入的time, speed仅有一个。返回完整的 time, speed //这一版的control似乎无法在某个位置上改变角度,位置和角度一般都是一起变的,所以先不增加单位更换功能。 let pos1 = new THREE.Vector3().copy(paths[0].position) let pos2 = new THREE.Vector3().copy(paths[1].position) let dis = pos1.distanceTo(pos2) if(info.time != void 0){ info.speed = dis / info.time }else{ info.time = dis / info.speed } return info }, //scaleRange: { min, max }, opacityRange: { min, max }, bottomRange: { min, max } }) addModel(props){ let bus = mitt() //console.log('addModel',props) props.isFirstLoad = isLocal && props.bottom == void 0 //离地高度去掉了这怎么办 // //在编辑时用户添加的 if(props.opacity == void 0) props.opacity = 1 if(props.type == 'obj') props.type = 'glb' props.scale /= 100 if(!props.isFirstLoad){ if(autoLoads.length == 0){ //首次加载 setTimeout(()=>{ let sizes = autoLoads.map(e=>e.size|| 0 ) console.log('需要请求加载的模型大小为', sizes, '总大小', sizes.reduce(function(total, currentValue ){ let current = parseFloat(currentValue) return total + ((typeof currentValue == 'number' || currentValue.includes('M')) ? current : current / 1024) }, 0)) readyToAddModel = true //准备开始加载 startLoad(autoLoads[0]) },30) } autoLoads.push(props) readyToAddModel = false }else{ readyToAddModel = true } let startLoad = (prop)=>{ //if(autoLoads.filter(e=>e.loaded).length>1)return console.log('取消加载', prop), prop.onError() //return prop.onError() Potree.addModel(prop, prop.done , prop.progressFun, prop.onError) prop.loading = true console.log('-------开始加载 id:', prop.id, 'title:', prop.title, ', filename:',getName(prop.url), prop ) } let spliceFromArr = (model,loaded)=>{ //let autoLoads.find() props.loadFinish = true props.loading = false if(loaded){ props.loaded = true props.model = model }else{ props.error = true } let haventLoad = autoLoads.filter(e=>!e.loading && !e.loadFinish); if( haventLoad[0]){ startLoad(haventLoad[0]) //this.addModel(autoLoads[0]) }else if(autoLoads.filter(e=>!e.loadFinish).length == 0 && autoLoads.filter(e=>e.loaded).length>0 && !props.isFirstLoad){//设置相机位置:当自动开始加载第一个模型时(其余的也跟着自动加载),等这批加载完后; let autoLoadsDone = autoLoads.filter(e=>e.loaded).map(e=>e.model) console.log('所有模型加载完毕') autoLoads.filter(e=>e.loaded && e.show).forEach(e=>e.model.visible = true) MergeEditor.focusOn(autoLoadsDone, 1000, true, true) autoLoads.length = 0 } } let model let done = (model_)=>{ model = model_ if(!props.isFirstLoad){ model.visible = false//先不显示,防止卡顿 } props.opacity < 100 && result.changeOpacity(props.opacity) model.addEventListener('changeSelect',(e)=>{ bus.emit('changeSelect',e.selected) }) model.addEventListener('transformChanged',(e)=>{ bus.emit('transformChanged', { position : model.position.clone(), scale: model.scale.x * 100, rotation: model.rotation.clone(), //bottom: model.btmHeight }) }) spliceFromArr(model,true) bus.emit('loadDone') //console.log('loadDone' ) } let progressFun = (progress)=>{ bus.emit('loadProgress',progress) } let onError = function ( xhr ) { bus.emit('loadError', xhr) console.log('loadError!!!!!!!!!', getName(props.url), props.size, xhr) spliceFromArr(model,false) } if(props.type == "glb"){////////////////////////////test if(props.url.includes('coffeemat')){ props.url = '/lib/potree/resources/models/glb/coffeemat.glb' } //props.url += '5' //props.url = 'http://localhost:5173/api/profile/datav1/1537680519838306304/data/glb/cloud_glb_24.glb' } props.done = done; props.progressFun = progressFun; props.onError = onError if(readyToAddModel){ if(autoLoads.filter(e=>e.loading).length{//见笔记:透明物体的材质设置 if(model.isPointcloud){ mesh.changePointOpacity(opacity) }else{ mesh.material.opacity = opacity } if(opacity<1){ mesh.material.transparent = true mesh.renderOrder = Potree.config.renderOrders.model+1 mesh.material.depthWrite = false }else{ mesh.material.transparent = false mesh.renderOrder = Potree.config.renderOrders.model mesh.material.depthWrite = true } } if(model){ if(model.isPointcloud){ setOp(model) }else{ model.traverse(e=>e.material && setOp(e, opacity)) } model.opacity = opacity//记录在最外层 } }, changeBottom(z){ /* model && MergeEditor.setModelBtmHeight(model,z) model.dispatchEvent('transformChanged') //改了position */ }, changePosition(pos){//校准取消时执行 //if(MergeEditor.selected == model){ //console.log('pos',pos.x, pos.y, pos.z) //} model && model.position.copy(pos) model.dispatchEvent({type:'position_changed'}) }, changeRotation(rot){//校准取消时执行 //if(MergeEditor.selected == model){ //console.log('rot', 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(){ 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(){ if(MergeEditor.split){//分屏校准 MergeEditor.setTransformState(null) }else{ MergeEditor.transformControls.detach() MergeEditor.transformControls2.detach() } }, enterAlignment(){//开始校准 MergeEditor.enterSplit() result.leaveTransform() //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() }, 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) viewer.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) viewer.updateVisible(m, 'enterScaleSet', true) }) }, destroy(){ model && MergeEditor.removeModel(model) } } 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(){ viewer.updateVisible(tag, 'byList', true) }, hide(){ viewer.updateVisible(tag, 'byList', false) }, destroy(){ if(tag){ tag.dispose() } viewer.dispatchEvent({ type: 'cancel_insertions', remove: true }) }, changeTitle(title){ tag.changeTitle(title) } } return result } } function getName(url){ return url.split('/').pop() } console.log('版本: 2022.8.29-1') return sdk } export default enter