association.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. import { sdk } from './sdk'
  2. import { toRaw, ref, watch, nextTick, watchEffect, reactive } from 'vue'
  3. import {
  4. viewModeStack,
  5. showLeftPanoStack,
  6. custom,
  7. } from '@/env'
  8. import {
  9. mount,
  10. diffArrayChange,
  11. shallowWatchArray,
  12. arrayChildEffectScope,
  13. showLoad,
  14. hideLoad,
  15. deepIsRevise,
  16. round,
  17. togetherCallback,
  18. asyncTimeout
  19. } from '@/utils'
  20. import {
  21. dynamicAddedModelIds,
  22. fuseModels,
  23. taggings,
  24. isEdit,
  25. sysBus,
  26. getFuseModelShowVariable,
  27. SceneType,
  28. MeasureType,
  29. measures,
  30. fuseModelsLoaded,
  31. getMeasureIsShow,
  32. SceneStatus,
  33. setting,
  34. caseProject,
  35. getGuidePaths,
  36. guidePaths
  37. } from '@/store'
  38. import { currentLayout, RoutesName } from '@/router'
  39. import TaggingComponent from '@/components/tagging/list.vue'
  40. import type { FuseModel, Tagging, Measure, FuseModels, Guide } from '@/store'
  41. import type {
  42. SDK,
  43. SceneModel,
  44. SceneGuidePath,
  45. ModelAttrRange,
  46. Measure as SceneMeasure
  47. } from '.'
  48. import { SettingResourceType } from '@/api/setting-resource'
  49. let isUnSet = false
  50. const unSet = ((fn: () => void) => {
  51. isUnSet = true
  52. fn()
  53. nextTick(() => isUnSet = false)
  54. })
  55. // -----------------模型关联--------------------
  56. export const modelRange: ModelAttrRange = {
  57. opacityRange: { min: 0, max: 100, step: 0.1 },
  58. bottomRange: { min: -30, max: 70, step: 0.1 },
  59. scaleRange: { min: 0, max: 200, step: 0.1 }
  60. }
  61. const sceneModelMap = reactive(new WeakMap<FuseModel, SceneModel>())
  62. export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
  63. const setModels = (models: FuseModels, oldModels: FuseModels) => {
  64. const { added, deleted } = diffArrayChange(models, oldModels)
  65. for (const item of added) {
  66. if (getSceneModel(item)) {
  67. continue;
  68. }
  69. if (item.status !== SceneStatus.SUCCESS) {
  70. item.error = true
  71. item.loaded = true
  72. continue;
  73. }
  74. const itemRaw = toRaw(item)
  75. let sceneModel: SceneModel
  76. try {
  77. sceneModel = sdk.addModel({
  78. ...itemRaw,
  79. ...modelRange,
  80. mode: RoutesName.signModel === currentLayout.value! ? 'single' : 'many',
  81. isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id),
  82. type: [SceneType.C_SWSS, SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? 'laser' : item.modelType,
  83. url: [SceneType.C_SWSS,SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? item.url : item.url && item.url,
  84. fromType: item.type
  85. })
  86. } catch(e) {
  87. console.error('模型加载失败', e)
  88. item.error = true
  89. return;
  90. }
  91. sceneModelMap.set(itemRaw, sceneModel)
  92. let changeId: NodeJS.Timeout
  93. sceneModel.bus.on('transformChanged', transform => {
  94. clearTimeout(changeId)
  95. changeId = setTimeout(() => {
  96. transform = { ...transform }
  97. if (transform.rotation) {
  98. transform.rotation = {
  99. x: round(transform.rotation.x, 5),
  100. y: round(transform.rotation.y, 5),
  101. z: round(transform.rotation.z, 5),
  102. }
  103. }
  104. if (transform.position) {
  105. transform.position = {
  106. x: round(transform.position.x, 5),
  107. y: round(transform.position.y, 5),
  108. z: round(transform.position.z, 5),
  109. }
  110. }
  111. delete transform.bottom
  112. // if (transform.bottom) {
  113. // transform.bottom = round(transform.bottom, 2)
  114. // }
  115. if (transform.scale) {
  116. transform.scale = round(transform.scale, 2)
  117. }
  118. const updateKeys = Object.keys(transform)
  119. const update: any = {}
  120. for (const key of updateKeys) {
  121. update[key] = (item as any)[key]
  122. }
  123. if (deepIsRevise(update, transform)) {
  124. unSet(() => Object.assign(item, transform))
  125. }
  126. }, 16)
  127. })
  128. sceneModel.bus.on('changeSelect', select => {
  129. unSet(() => {
  130. if (custom.currentModel === item && !select) {
  131. custom.currentModel = null
  132. } else if (custom.currentModel !== item && select) {
  133. custom.currentModel = item
  134. }
  135. })
  136. })
  137. showLoad()
  138. sceneModel.bus.on('loadDone', () => {
  139. item.loaded = true
  140. hideLoad()
  141. })
  142. sceneModel.bus.on('loadError', () => {
  143. item.error = true
  144. item.show = false
  145. custom.showModelsMap.delete(item)
  146. hideLoad()
  147. })
  148. sceneModel.bus.on('loadProgress', progress => item.progress = progress)
  149. }
  150. for (const item of deleted) {
  151. console.error('销毁', item)
  152. getSceneModel(item)?.destroy()
  153. }
  154. }
  155. const associationModels = (sdk: SDK) => {
  156. const getModels = () => fuseModels.value
  157. .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value)
  158. shallowWatchArray(getModels, (models, oldModels) => {
  159. setModels(models, oldModels)
  160. })
  161. arrayChildEffectScope(getModels, item => {
  162. const stopLoadedWatch = watch(
  163. () => item.loaded,
  164. (loaded) => {
  165. if (loaded) {
  166. const modelShow = getFuseModelShowVariable(item)
  167. watch(
  168. () => item.bottom,
  169. () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom),
  170. // { immediate: true }
  171. )
  172. watch(
  173. () => item.opacity,
  174. () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity),
  175. // { immediate: true }
  176. )
  177. watch(
  178. () => item.scale,
  179. () => isUnSet || getSceneModel(item)?.changeScale(item.scale),
  180. // { immediate: true }
  181. )
  182. watch(
  183. () => item.position,
  184. () => {
  185. if (!isUnSet) {
  186. getSceneModel(item)?.changePosition(item.position)
  187. }
  188. },
  189. // { immediate: true }
  190. )
  191. watch(
  192. () => item.rotation,
  193. () => {
  194. if (!isUnSet) {
  195. getSceneModel(item)?.changeRotation(item.rotation)
  196. }
  197. },
  198. // { immediate: true }
  199. )
  200. watch(
  201. () => modelShow.value,
  202. () => {
  203. const sceneModel = getSceneModel(item)
  204. if (!isUnSet && sceneModel) {
  205. sceneModel.changeSelect(false)
  206. sceneModel.changeShow(modelShow.value)
  207. }
  208. },
  209. { immediate: true }
  210. )
  211. watch(
  212. () => custom.currentModel === item,
  213. (selected) => {
  214. isUnSet || console.log(item.title, selected, getSceneModel(item))
  215. isUnSet || getSceneModel(item)?.changeSelect(selected)
  216. }
  217. )
  218. stopLoadedWatch()
  219. }
  220. },
  221. // { immediate: true }
  222. )
  223. })
  224. }
  225. // -----------------热点关联--------------------
  226. const associationTaggings = (el: HTMLDivElement) => {
  227. const getTaggings = () => taggings.value
  228. const taggingVMs = new WeakMap<Tagging, ReturnType<typeof mount>>()
  229. shallowWatchArray(getTaggings, (taggings, oldTaggings) => {
  230. const { added, deleted } = diffArrayChange(taggings, oldTaggings)
  231. for (const item of added) {
  232. taggingVMs.set(toRaw(item), mount(el, TaggingComponent, { tagging: item }))
  233. }
  234. for (const item of deleted) {
  235. const unMount = taggingVMs.get(toRaw(item))
  236. unMount && unMount()
  237. }
  238. })
  239. }
  240. // -----------------测量关联--------------------
  241. const sceneMeasureMap = reactive(new WeakMap<Measure , SceneMeasure>())
  242. export const getSceneMeasure = (measure?: Measure | null) => measure && sceneMeasureMap.get(toRaw(measure))
  243. export const getSceneMeasureDesc = (smMeasure: SceneMeasure, measure: Measure) => {
  244. const length = measure.type === MeasureType.area
  245. ? (smMeasure as unknown as SceneMeasure<MeasureType.area>).getArea()
  246. : (smMeasure as unknown as SceneMeasure<MeasureType.free>).getDistance()
  247. return round(length.value, 2).toString()
  248. }
  249. export const associationMessaure = <T extends MeasureType>(smMeasure: SceneMeasure<T>, measure: Measure<T>) => {
  250. smMeasure.bus.on('update', ([points, modelIds]) => {
  251. unSet(() => measure.positions = points.map((point, i) => ({ point, modelId: modelIds[i] })))
  252. })
  253. smMeasure.bus.on('highlight', selected => unSet(() => measure.selected = selected))
  254. }
  255. const associationMessaures = (sdk: SDK) => {
  256. const getMeasures = () => measures.value.filter(getMeasureIsShow)
  257. shallowWatchArray(getMeasures, (measures, oldMeasures) => {
  258. const { added, deleted } = diffArrayChange(measures, oldMeasures)
  259. for (const item of added) {
  260. const sceneMeasure = sdk.drawMeasure(
  261. item.type,
  262. item.positions.map(position => ({...position.point})),
  263. item.positions.map(position => position.modelId),
  264. )
  265. if (sceneMeasure.destroy) {
  266. sceneMeasureMap.set(toRaw(item), sceneMeasure)
  267. associationMessaure(sceneMeasure, item)
  268. }
  269. }
  270. for (const item of deleted) {
  271. const sceneMeasure = getSceneMeasure(item)
  272. sceneMeasure && sceneMeasure.destroy!()
  273. sceneMeasureMap.delete(toRaw(item))
  274. }
  275. })
  276. arrayChildEffectScope(getMeasures, measure => {
  277. watch(
  278. () => measure.selected,
  279. (selected = false) => isUnSet || getSceneMeasure(measure)?.changeSelect(selected)
  280. )
  281. watch(
  282. () => measure.positions,
  283. (positions) => isUnSet || getSceneMeasure(measure)?.setPositions(
  284. positions.map(position => ({...position.point})),
  285. positions.map(position => position.modelId),
  286. )
  287. )
  288. watch(
  289. () => custom.showMeasures,
  290. (show) => {
  291. if (!isUnSet) {
  292. const smMeasure = getSceneMeasure(measure)
  293. if (show) {
  294. smMeasure?.show()
  295. } else {
  296. smMeasure?.hide()
  297. }
  298. }
  299. },
  300. { immediate: true }
  301. )
  302. })
  303. }
  304. // -----------------导览关联--------------------
  305. const fullView = async (fn: () => void) => {
  306. const popViewMode = togetherCallback([
  307. viewModeStack.push(ref('full')),
  308. showLeftPanoStack.push(ref(false))
  309. ])
  310. let isFull = false;
  311. try {
  312. await document.documentElement.requestFullscreen()
  313. isFull = true;
  314. } catch {}
  315. const driving = () => document.fullscreenElement || fn()
  316. const stop = (ev: KeyboardEvent) => ev.key == "Escape" && fn()
  317. if (isFull) {
  318. document.addEventListener('fullscreenchange', driving)
  319. document.addEventListener('fullscreenerror', fn)
  320. } else {
  321. document.addEventListener("keyup", stop)
  322. }
  323. return () => {
  324. popViewMode()
  325. if (isFull) {
  326. document.fullscreenElement && document.exitFullscreen()
  327. document.removeEventListener('fullscreenchange', driving)
  328. document.removeEventListener('fullscreenerror', fn)
  329. } else {
  330. document.removeEventListener("keyup", stop)
  331. }
  332. }
  333. }
  334. export const recovery = async (guide: Guide) => {
  335. let rFuseModels: (FuseModel & {viewShow: boolean})[];
  336. try {
  337. if (!guide.recoveryContent) {
  338. throw "没有recovery";
  339. }
  340. rFuseModels = JSON.parse(guide.recoveryContent);
  341. } catch (e) {
  342. return () => {};
  343. }
  344. const initFuseModels = JSON.parse(JSON.stringify(fuseModels.value)) as FuseModels;
  345. const initViewShow = fuseModels.value.map(item => custom.showModelsMap.get(item))
  346. console.error(initFuseModels, rFuseModels)
  347. const setModels = async (models: (FuseModel & {viewShow: boolean})[]) => {
  348. for (let i = 0; i < models.length; i++) {
  349. const ndx = fuseModels.value.findIndex(({ modelId }) => modelId === models[i].modelId);
  350. if (~ndx) {
  351. Object.assign(fuseModels.value[ndx], models[i]);
  352. custom.showModelsMap.set(toRaw(fuseModels.value[ndx]), models[i].viewShow)
  353. } else {
  354. fuseModels.value.push(models[i]);
  355. custom.showModelsMap.set(toRaw(models[i]), models[i].viewShow)
  356. }
  357. }
  358. // console.log(models)
  359. for (let i = 0; i < fuseModels.value.length; i++) {
  360. const ndx = models.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId);
  361. if (!~ndx) {
  362. fuseModels.value.splice(i, 1);
  363. i--
  364. }
  365. }
  366. await asyncTimeout(100)
  367. await new Promise<void>((resolve) => {
  368. const stop = watchEffect(() => {
  369. if (fuseModelsLoaded.value) {
  370. setTimeout(() => stop())
  371. resolve()
  372. }
  373. })
  374. })
  375. };
  376. for (let i = 0; i < fuseModels.value.length; i++) {
  377. const ndx = rFuseModels.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId);
  378. if (!~ndx) {
  379. rFuseModels.push({...fuseModels.value[i], viewShow: false})
  380. }
  381. }
  382. await setModels(rFuseModels);
  383. return () =>
  384. setModels(initFuseModels.map((item, i) => ({...item, viewShow: initViewShow[i]!})));
  385. };
  386. export enum ScenePlayIngEnum {
  387. ing = 1,
  388. stop = 0,
  389. ready = 2
  390. }
  391. export const isScenePlayIng = ref<ScenePlayIngEnum>(ScenePlayIngEnum.stop)
  392. let pauseRecovery: () => void
  393. export const playSceneGuide = async (guide: Guide, changeIndexCallback?: (index: number) => void, forceFull = false, paths = getGuidePaths(guide)) => {
  394. console.log(guide, guidePaths.value)
  395. if (isScenePlayIng.value) {
  396. throw new Error('导览正在播放')
  397. }
  398. isScenePlayIng.value = ScenePlayIngEnum.ready
  399. pauseRecovery = await recovery(guide)
  400. isScenePlayIng.value = ScenePlayIngEnum.ing
  401. const sceneGuide = sdk.enterSceneGuide(paths)
  402. changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback)
  403. const quitHandler = pauseSceneGuide
  404. const clearHandler = !forceFull && isEdit.value ? null : await fullView(quitHandler)
  405. if (!clearHandler) {
  406. sysBus.on('leave', quitHandler, { last: true })
  407. sysBus.on('save', quitHandler, { last: true })
  408. }
  409. sceneGuide.play()
  410. const reces = [
  411. new Promise(resolve => sceneGuide.bus.on('playComplete', resolve)),
  412. new Promise<void>(resolve => {
  413. const stop = watch(isScenePlayIng, () => {
  414. if (!isScenePlayIng.value) {
  415. resolve()
  416. sceneGuide.pause()
  417. stop()
  418. }
  419. })
  420. }),
  421. ]
  422. await Promise.race(reces)
  423. pauseSceneGuide()
  424. if (clearHandler) {
  425. clearHandler()
  426. } else {
  427. sysBus.off('leave', quitHandler)
  428. sysBus.off('save', quitHandler)
  429. }
  430. sceneGuide.clear()
  431. sceneGuide.bus.off('changePoint')
  432. }
  433. export const pauseSceneGuide = () => {
  434. console.error('pause?')
  435. isScenePlayIng.value = ScenePlayIngEnum.stop
  436. pauseRecovery && pauseRecovery()
  437. }
  438. // -----------------启动关联--------------------
  439. export const setupAssociation = (mountEl: HTMLDivElement) => {
  440. associationModels(sdk)
  441. const stopWatch = watchEffect(() => {
  442. if (fuseModelsLoaded.value && setting.value) {
  443. associationTaggings(mountEl)
  444. associationMessaures(sdk)
  445. setting.value?.pose && sdk.comeTo(setting.value.pose)
  446. setBackdrop(setting.value!.back, setting.value!.backType, { scale: setting.value!.scale, rotate: setting.value!.rotate});
  447. setMap(setting.value!.mapOpen, setting.value!.mapType)
  448. watchEffect(() => {
  449. sdk.setCameraFov && sdk.setCameraFov(setting.value!.fov)
  450. })
  451. ;(document.querySelector('#direction') as HTMLDivElement)!.style.display = setting.value!.openCompass ? 'block' : 'none';
  452. nextTick(() => stopWatch())
  453. }
  454. })
  455. }
  456. export const setBackdrop = (back: string, type: SettingResourceType, tb: {scale?: number, rotate?: number} = { scale: 1, rotate: 0 }) => {
  457. ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none';
  458. if (type === SettingResourceType.map) {
  459. if (!caseProject.value!.tmProject?.latlng) {
  460. return;
  461. }
  462. const latlng = caseProject.value!.tmProject?.latlng.split(',').map(i => Number(i))
  463. ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'block';
  464. sdk.enableMap && sdk.enableMap(document.querySelector('#scene-map') as HTMLDivElement, latlng)
  465. sdk.switchMapType && sdk.switchMapType(back)
  466. // 'satellite' | 'standard'
  467. } else if (type!== SettingResourceType.icon) {
  468. setting.value?.back && sdk.setBackdrop(back, type, tb)
  469. } else {
  470. sdk.setBackdrop('none', type, {scale: 1, rotate: 0})
  471. }
  472. }
  473. let opened = false
  474. export const setMap = (open: boolean, type:string = 'satellite') => {
  475. if (!caseProject.value!.tmProject?.latlng) {
  476. ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none';
  477. return;
  478. }
  479. if (open) {
  480. ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'block';
  481. } else {
  482. ;(document.querySelector('#scene-map') as HTMLDivElement)!.style.display = 'none';
  483. return;
  484. }
  485. if (!opened) {
  486. const latlng = caseProject.value!.tmProject?.latlng.split(',').map(i => Number(i))
  487. console.log('open map')
  488. sdk.enableMap && sdk.enableMap(document.querySelector('#scene-map') as HTMLDivElement, latlng)
  489. opened = true
  490. }
  491. sdk.switchMapType && sdk.switchMapType(type)
  492. }