import { sdk } from './sdk' import { toRaw, ref, watch, nextTick, watchEffect, reactive } from 'vue' import { viewModeStack, showLeftPanoStack, custom, } from '@/env' import { mount, diffArrayChange, shallowWatchArray, arrayChildEffectScope, showLoad, hideLoad, deepIsRevise, round, togetherCallback, asyncTimeout } from '@/utils' import { dynamicAddedModelIds, fuseModels, taggings, isEdit, sysBus, getFuseModelShowVariable, SceneType, MeasureType, measures, fuseModelsLoaded, getMeasureIsShow, SceneStatus, setting, caseProject, getGuidePaths, guidePaths } from '@/store' import { currentLayout, RoutesName } from '@/router' import TaggingComponent from '@/components/tagging/list.vue' import type { FuseModel, Tagging, Measure, FuseModels, Guide } from '@/store' import type { SDK, SceneModel, SceneGuidePath, ModelAttrRange, Measure as SceneMeasure } from '.' import { SettingResourceType } from '@/api/setting-resource' let isUnSet = false const unSet = ((fn: () => void) => { isUnSet = true fn() nextTick(() => isUnSet = false) }) // -----------------模型关联-------------------- export const modelRange: ModelAttrRange = { opacityRange: { min: 0, max: 100, step: 0.1 }, bottomRange: { min: -30, max: 70, step: 0.1 }, scaleRange: { min: 0, max: 200, step: 0.1 } } const sceneModelMap = reactive(new WeakMap()) export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model)) const setModels = (models: FuseModels, oldModels: FuseModels) => { const { added, deleted } = diffArrayChange(models, oldModels) for (const item of added) { if (getSceneModel(item)) { continue; } if (item.status !== SceneStatus.SUCCESS) { item.error = true item.loaded = true continue; } const itemRaw = toRaw(item) let sceneModel: SceneModel try { sceneModel = sdk.addModel({ ...itemRaw, ...modelRange, mode: RoutesName.signModel === currentLayout.value! ? 'single' : 'many', isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id), type: [SceneType.C_SWSS, SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? 'laser' : item.modelType, url: [SceneType.C_SWSS,SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? item.url : item.url && item.url, fromType: item.type }) } catch(e) { console.error('模型加载失败', e) item.error = true return; } sceneModelMap.set(itemRaw, sceneModel) let changeId: NodeJS.Timeout sceneModel.bus.on('transformChanged', transform => { clearTimeout(changeId) changeId = setTimeout(() => { transform = { ...transform } if (transform.rotation) { transform.rotation = { x: round(transform.rotation.x, 5), y: round(transform.rotation.y, 5), z: round(transform.rotation.z, 5), } } if (transform.position) { transform.position = { x: round(transform.position.x, 5), y: round(transform.position.y, 5), z: round(transform.position.z, 5), } } delete transform.bottom // if (transform.bottom) { // transform.bottom = round(transform.bottom, 2) // } if (transform.scale) { transform.scale = round(transform.scale, 2) } const updateKeys = Object.keys(transform) const update: any = {} for (const key of updateKeys) { update[key] = (item as any)[key] } if (deepIsRevise(update, transform)) { unSet(() => Object.assign(item, transform)) } }, 16) }) sceneModel.bus.on('changeSelect', select => { unSet(() => { if (custom.currentModel === item && !select) { custom.currentModel = null } else if (custom.currentModel !== item && select) { custom.currentModel = item } }) }) showLoad() sceneModel.bus.on('loadDone', () => { item.loaded = true hideLoad() }) sceneModel.bus.on('loadError', () => { item.error = true item.show = false custom.showModelsMap.delete(item) hideLoad() }) sceneModel.bus.on('loadProgress', progress => item.progress = progress) } for (const item of deleted) { console.error('销毁', item) getSceneModel(item)?.destroy() } } const associationModels = (sdk: SDK) => { const getModels = () => fuseModels.value .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value) shallowWatchArray(getModels, (models, oldModels) => { setModels(models, oldModels) }) arrayChildEffectScope(getModels, item => { const stopLoadedWatch = watch( () => item.loaded, (loaded) => { if (loaded) { const modelShow = getFuseModelShowVariable(item) watch( () => item.bottom, () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom), // { immediate: true } ) watch( () => item.opacity, () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity), // { immediate: true } ) watch( () => item.scale, () => isUnSet || getSceneModel(item)?.changeScale(item.scale), // { immediate: true } ) watch( () => item.position, () => { if (!isUnSet) { getSceneModel(item)?.changePosition(item.position) } }, // { immediate: true } ) watch( () => item.rotation, () => { if (!isUnSet) { getSceneModel(item)?.changeRotation(item.rotation) } }, // { immediate: true } ) watch( () => modelShow.value, () => { const sceneModel = getSceneModel(item) if (!isUnSet && sceneModel) { sceneModel.changeSelect(false) sceneModel.changeShow(modelShow.value) } }, { immediate: true } ) watch( () => custom.currentModel === item, (selected) => { isUnSet || console.log(item.title, selected, getSceneModel(item)) isUnSet || getSceneModel(item)?.changeSelect(selected) } ) stopLoadedWatch() } }, // { immediate: true } ) }) } // -----------------热点关联-------------------- const associationTaggings = (el: HTMLDivElement) => { const getTaggings = () => taggings.value const taggingVMs = new WeakMap>() shallowWatchArray(getTaggings, (taggings, oldTaggings) => { const { added, deleted } = diffArrayChange(taggings, oldTaggings) for (const item of added) { taggingVMs.set(toRaw(item), mount(el, TaggingComponent, { tagging: item })) } for (const item of deleted) { const unMount = taggingVMs.get(toRaw(item)) unMount && unMount() } }) } // -----------------测量关联-------------------- const sceneMeasureMap = reactive(new WeakMap()) export const getSceneMeasure = (measure?: Measure | null) => measure && sceneMeasureMap.get(toRaw(measure)) export const getSceneMeasureDesc = (smMeasure: SceneMeasure, measure: Measure) => { const length = measure.type === MeasureType.area ? (smMeasure as unknown as SceneMeasure).getArea() : (smMeasure as unknown as SceneMeasure).getDistance() return round(length.value, 2).toString() } export const associationMessaure = (smMeasure: SceneMeasure, measure: Measure) => { smMeasure.bus.on('update', ([points, modelIds]) => { unSet(() => measure.positions = points.map((point, i) => ({ point, modelId: modelIds[i] }))) }) smMeasure.bus.on('highlight', selected => unSet(() => measure.selected = selected)) } const associationMessaures = (sdk: SDK) => { const getMeasures = () => measures.value.filter(getMeasureIsShow) shallowWatchArray(getMeasures, (measures, oldMeasures) => { const { added, deleted } = diffArrayChange(measures, oldMeasures) for (const item of added) { const sceneMeasure = sdk.drawMeasure( item.type, item.positions.map(position => ({...position.point})), item.positions.map(position => position.modelId), ) if (sceneMeasure.destroy) { sceneMeasureMap.set(toRaw(item), sceneMeasure) associationMessaure(sceneMeasure, item) } } for (const item of deleted) { const sceneMeasure = getSceneMeasure(item) sceneMeasure && sceneMeasure.destroy!() sceneMeasureMap.delete(toRaw(item)) } }) arrayChildEffectScope(getMeasures, measure => { watch( () => measure.selected, (selected = false) => isUnSet || getSceneMeasure(measure)?.changeSelect(selected) ) watch( () => measure.positions, (positions) => isUnSet || getSceneMeasure(measure)?.setPositions( positions.map(position => ({...position.point})), positions.map(position => position.modelId), ) ) watch( () => custom.showMeasures, (show) => { if (!isUnSet) { const smMeasure = getSceneMeasure(measure) if (show) { smMeasure?.show() } else { smMeasure?.hide() } } }, { immediate: true } ) }) } // -----------------导览关联-------------------- const fullView = async (fn: () => void) => { const popViewMode = togetherCallback([ viewModeStack.push(ref('full')), showLeftPanoStack.push(ref(false)) ]) let isFull = false; try { await document.documentElement.requestFullscreen() isFull = true; } catch {} const driving = () => document.fullscreenElement || fn() const stop = (ev: KeyboardEvent) => ev.key == "Escape" && fn() if (isFull) { document.addEventListener('fullscreenchange', driving) document.addEventListener('fullscreenerror', fn) } else { document.addEventListener("keyup", stop) } return () => { popViewMode() if (isFull) { document.fullscreenElement && document.exitFullscreen() document.removeEventListener('fullscreenchange', driving) document.removeEventListener('fullscreenerror', fn) } else { document.removeEventListener("keyup", stop) } } } export const recovery = async (guide: Guide) => { let rFuseModels: (FuseModel & {viewShow: boolean})[]; try { if (!guide.recoveryContent) { throw "没有recovery"; } rFuseModels = JSON.parse(guide.recoveryContent); } catch (e) { return () => {}; } const initFuseModels = JSON.parse(JSON.stringify(fuseModels.value)) as FuseModels; const initViewShow = fuseModels.value.map(item => custom.showModelsMap.get(item)) console.error(initFuseModels, rFuseModels) const setModels = async (models: (FuseModel & {viewShow: boolean})[]) => { for (let i = 0; i < models.length; i++) { const ndx = fuseModels.value.findIndex(({ modelId }) => modelId === models[i].modelId); if (~ndx) { Object.assign(fuseModels.value[ndx], models[i]); custom.showModelsMap.set(toRaw(fuseModels.value[ndx]), models[i].viewShow) } else { fuseModels.value.push(models[i]); custom.showModelsMap.set(toRaw(models[i]), models[i].viewShow) } } // console.log(models) for (let i = 0; i < fuseModels.value.length; i++) { const ndx = models.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId); if (!~ndx) { fuseModels.value.splice(i, 1); i-- } } await asyncTimeout(100) await new Promise((resolve) => { const stop = watchEffect(() => { if (fuseModelsLoaded.value) { setTimeout(() => stop()) resolve() } }) }) }; for (let i = 0; i < fuseModels.value.length; i++) { const ndx = rFuseModels.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId); if (!~ndx) { rFuseModels.push({...fuseModels.value[i], viewShow: false}) } } await setModels(rFuseModels); return () => setModels(initFuseModels.map((item, i) => ({...item, viewShow: initViewShow[i]!}))); }; export enum ScenePlayIngEnum { ing = 1, stop = 0, ready = 2 } export const isScenePlayIng = ref(ScenePlayIngEnum.stop) let pauseRecovery: () => void export const playSceneGuide = async (guide: Guide, changeIndexCallback?: (index: number) => void, forceFull = false, paths = getGuidePaths(guide)) => { console.log(guide, guidePaths.value) if (isScenePlayIng.value) { throw new Error('导览正在播放') } isScenePlayIng.value = ScenePlayIngEnum.ready pauseRecovery = await recovery(guide) isScenePlayIng.value = ScenePlayIngEnum.ing const sceneGuide = sdk.enterSceneGuide(paths) changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback) const quitHandler = pauseSceneGuide const clearHandler = !forceFull && isEdit.value ? null : await fullView(quitHandler) if (!clearHandler) { sysBus.on('leave', quitHandler, { last: true }) sysBus.on('save', quitHandler, { last: true }) } sceneGuide.play() const reces = [ new Promise(resolve => sceneGuide.bus.on('playComplete', resolve)), new Promise(resolve => { const stop = watch(isScenePlayIng, () => { if (!isScenePlayIng.value) { resolve() sceneGuide.pause() stop() } }) }), ] await Promise.race(reces) pauseSceneGuide() if (clearHandler) { clearHandler() } else { sysBus.off('leave', quitHandler) sysBus.off('save', quitHandler) } sceneGuide.clear() sceneGuide.bus.off('changePoint') } export const pauseSceneGuide = () => { console.error('pause?') isScenePlayIng.value = ScenePlayIngEnum.stop pauseRecovery && pauseRecovery() } // -----------------启动关联-------------------- export const setupAssociation = (mountEl: HTMLDivElement) => { associationModels(sdk) const stopWatch = watchEffect(() => { if (fuseModelsLoaded.value && setting.value) { associationTaggings(mountEl) associationMessaures(sdk) setting.value?.pose && sdk.comeTo(setting.value.pose) setBackdrop(setting.value!.back, setting.value!.backType, { scale: setting.value!.scale, rotate: setting.value!.rotate}); setMap(setting.value!.mapOpen, setting.value!.mapType) watchEffect(() => { sdk.setCameraFov && sdk.setCameraFov(setting.value!.fov) }) ;(document.querySelector('#direction') as HTMLDivElement)!.style.display = setting.value!.openCompass ? 'block' : 'none'; nextTick(() => stopWatch()) } }) } export const setBackdrop = (back: string, type: SettingResourceType, tb: {scale?: number, rotate?: number} = { scale: 1, rotate: 0 }) => { ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none'; if (type === SettingResourceType.map) { if (!caseProject.value!.tmProject?.latlng) { return; } const latlng = caseProject.value!.tmProject?.latlng.split(',').map(i => Number(i)) ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'block'; sdk.enableMap && sdk.enableMap(document.querySelector('#scene-map') as HTMLDivElement, latlng) sdk.switchMapType && sdk.switchMapType(back) // 'satellite' | 'standard' } else if (type!== SettingResourceType.icon) { setting.value?.back && sdk.setBackdrop(back, type, tb) } else { sdk.setBackdrop('none', type, {scale: 1, rotate: 0}) } } let opened = false export const setMap = (open: boolean, type:string = 'satellite') => { if (!caseProject.value!.tmProject?.latlng) { ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none'; return; } if (open) { ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'block'; } else { ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none'; return; } if (!opened) { const latlng = caseProject.value!.tmProject?.latlng.split(',').map(i => Number(i)) console.log('open map') sdk.enableMap && sdk.enableMap(document.querySelector('#scene-map') as HTMLDivElement, latlng) opened = true } sdk.switchMapType && sdk.switchMapType(type) }