import { sdk } from './sdk' import { toRaw, ref, watch, nextTick, watchEffect, reactive } from 'vue' import { viewModeStack, showLeftPanoStack, custom, getResource } from '@/env' import { mount, diffArrayChange, shallowWatchArray, arrayChildEffectScope, showLoad, hideLoad, deepIsRevise, round, togetherCallback } from '@/utils' import { dynamicAddedModelIds, fuseModels, taggings, isEdit, sysBus, getFuseModelShowVariable, SceneType, MeasureType, measures, fuseModelsLoaded, getMeasureIsShow } from '@/store' import TaggingComponent from '@/components/tagging/list.vue' import type { FuseModel, Tagging, Measure } from '@/store' import type { SDK, SceneModel, SceneGuidePath, ModelAttrRange, Measure as SceneMeasure } from '.' import { SceneStatus } from '@/api' 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 associationModels = (sdk: SDK) => { const getModels = () => fuseModels.value .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value) shallowWatchArray(getModels, (models, oldModels) => { 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 console.error('loaded', itemRaw) try { sceneModel = sdk.addModel({ ...itemRaw, ...modelRange, isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id), type: item.type === SceneType.SWSS ? 'laser' : item.modelType, url: item.type === SceneType.SWSS ? item.url : getResource(item.url) }) } 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('remove') getSceneModel(item)?.destroy() } }) 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 } ) }) watch(() => custom.currentModel, () => { }) } // -----------------热点关联-------------------- 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)) ]) await document.documentElement.requestFullscreen() const driving = () => document.fullscreenElement || fn() document.addEventListener('fullscreenchange', driving) document.addEventListener('fullscreenerror', fn) return () => { popViewMode() document.fullscreenElement && document.exitFullscreen() document.removeEventListener('fullscreenchange', driving) document.removeEventListener('fullscreenerror', fn) } } export const isScenePlayIng = ref(false) export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallback?: (index: number) => void, forceFull = false) => { if (isScenePlayIng.value) { throw new Error('导览正在播放') } isScenePlayIng.value = true const sceneGuide = sdk.enterSceneGuide(paths) changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback) const quitHandler = () => (isScenePlayIng.value = false) 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) isScenePlayIng.value = false if (clearHandler) { clearHandler() } else { sysBus.off('leave', quitHandler) sysBus.off('save', quitHandler) } sceneGuide.clear() sceneGuide.bus.off('changePoint') } export const pauseSceneGuide = () => isScenePlayIng.value = false // -----------------启动关联-------------------- export const setupAssociation = (mountEl: HTMLDivElement) => { associationModels(sdk) const stopWatch = watchEffect(() => { if (fuseModelsLoaded.value) { associationTaggings(mountEl) associationMessaures(sdk) nextTick(() => stopWatch()) } }) }