association.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { sdk } from './sdk'
  2. import { toRaw, ref, watch, nextTick, watchEffect } from 'vue'
  3. import {
  4. viewModeStack,
  5. showLeftPanoStack,
  6. custom,
  7. getResource
  8. } from '@/env'
  9. import {
  10. mount,
  11. diffArrayChange,
  12. shallowWatchArray,
  13. arrayChildEffectScope,
  14. showLoad,
  15. hideLoad,
  16. deepIsRevise,
  17. round,
  18. togetherCallback
  19. } from '@/utils'
  20. import {
  21. fuseModels,
  22. taggings,
  23. isEdit,
  24. sysBus,
  25. getFuseModelShowVariable,
  26. SceneType,
  27. MeasureType,
  28. measures,
  29. fuseModelsLoaded,
  30. getMeasureIsShow,
  31. unSetModelUpdate
  32. } from '@/store'
  33. import TaggingComponent from '@/components/tagging/list.vue'
  34. import type { FuseModel, Tagging, Measure } from '@/store'
  35. import type {
  36. SDK,
  37. SceneModel,
  38. SceneGuidePath,
  39. ModelAttrRange,
  40. Measure as SceneMeasure
  41. } from '.'
  42. let isUnSet = false
  43. const unSet = ((fn: () => void) => {
  44. isUnSet = true
  45. fn()
  46. nextTick(() => isUnSet = false)
  47. })
  48. // -----------------模型关联--------------------
  49. export const modelRange: ModelAttrRange = {
  50. opacityRange: { min: 0, max: 100, step: 0.1 },
  51. bottomRange: { min: -30, max: 70, step: 0.1 },
  52. scaleRange: { min: 0, max: 200, step: 0.1 }
  53. }
  54. const sceneModelMap = new WeakMap<FuseModel, SceneModel>()
  55. export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
  56. const associationModels = (sdk: SDK) => {
  57. const getModels = () => fuseModels.value
  58. .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value)
  59. shallowWatchArray(getModels, (models, oldModels) => {
  60. const { added, deleted } = diffArrayChange(models, oldModels)
  61. for (const item of added) {
  62. if (getSceneModel(item)) {
  63. continue;
  64. }
  65. const itemRaw = toRaw(item)
  66. const sceneModel = sdk.addModel({
  67. ...itemRaw,
  68. ...modelRange,
  69. type: item.type === SceneType.SWSS ? 'laser' : 'glb',
  70. url: item.type === SceneType.SWSS ? item.url : getResource(item.url)
  71. })
  72. sceneModelMap.set(itemRaw, sceneModel)
  73. let changeId: NodeJS.Timeout
  74. sceneModel.bus.on('transformChanged', transform => {
  75. clearTimeout(changeId)
  76. changeId = setTimeout(() => {
  77. transform = { ...transform }
  78. if (transform.rotation) {
  79. transform.rotation = {
  80. x: round(transform.rotation.x, 5),
  81. y: round(transform.rotation.y, 5),
  82. z: round(transform.rotation.z, 5),
  83. }
  84. }
  85. if (transform.position) {
  86. transform.position = {
  87. x: round(transform.position.x, 5),
  88. y: round(transform.position.y, 5),
  89. z: round(transform.position.z, 5),
  90. }
  91. }
  92. const updateKeys = Object.keys(transform)
  93. const update: any = {}
  94. for (const key of updateKeys) {
  95. update[key] = (item as any)[key]
  96. }
  97. if (deepIsRevise(update, transform)) {
  98. unSet(() => Object.assign(item, transform))
  99. }
  100. }, 16)
  101. })
  102. sceneModel.bus.on('changeSelect', select => {
  103. if (custom.currentModel === item && !select) {
  104. custom.currentModel = null
  105. } else if (custom.currentModel !== item && select) {
  106. custom.currentModel = item
  107. }
  108. })
  109. showLoad()
  110. sceneModel.bus.on('loadDone', () => {
  111. item.loaded = true
  112. hideLoad()
  113. })
  114. sceneModel.bus.on('loadError', () => {
  115. item.error = true
  116. item.show = false
  117. custom.showModelsMap.delete(item)
  118. hideLoad()
  119. })
  120. sceneModel.bus.on('loadProgress', progress => item.progress = progress)
  121. }
  122. for (const item of deleted) {
  123. getSceneModel(item)?.destroy()
  124. }
  125. })
  126. arrayChildEffectScope(getModels, item => {
  127. const stopLoadedWatch = watch(
  128. () => item.loaded,
  129. (loaded) => {
  130. if (loaded) {
  131. const modelShow = getFuseModelShowVariable(item)
  132. watch(
  133. () => item.bottom,
  134. () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom),
  135. { immediate: true }
  136. )
  137. watch(
  138. () => item.opacity,
  139. () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity),
  140. { immediate: true }
  141. )
  142. watch(
  143. () => item.scale,
  144. () => isUnSet || getSceneModel(item)?.changeScale(item.scale),
  145. { immediate: true }
  146. )
  147. watch(
  148. () => item.position,
  149. () => {
  150. if (!isUnSet) {
  151. console.log('set position', item.position)
  152. getSceneModel(item)?.changePosition(item.position)
  153. }
  154. },
  155. { immediate: true }
  156. )
  157. watch(
  158. () => item.rotation,
  159. () => {
  160. if (!isUnSet) {
  161. console.log('set rotation', item.rotation)
  162. getSceneModel(item)?.changeRotation(item.rotation)
  163. }
  164. },
  165. { immediate: true }
  166. )
  167. watch(
  168. () => modelShow.value,
  169. () => isUnSet || getSceneModel(item)?.changeShow(modelShow.value),
  170. { immediate: true }
  171. )
  172. stopLoadedWatch()
  173. }
  174. }
  175. )
  176. })
  177. }
  178. // -----------------热点关联--------------------
  179. const associationTaggings = (el: HTMLDivElement) => {
  180. const getTaggings = () => taggings.value
  181. const taggingVMs = new WeakMap<Tagging, ReturnType<typeof mount>>()
  182. shallowWatchArray(getTaggings, (taggings, oldTaggings) => {
  183. const { added, deleted } = diffArrayChange(taggings, oldTaggings)
  184. for (const item of added) {
  185. taggingVMs.set(toRaw(item), mount(el, TaggingComponent, { tagging: item }))
  186. }
  187. for (const item of deleted) {
  188. const unMount = taggingVMs.get(toRaw(item))
  189. unMount && unMount()
  190. }
  191. })
  192. }
  193. // -----------------测量关联--------------------
  194. const sceneMeasureMap = new WeakMap<Measure , SceneMeasure>()
  195. export const getSceneMeasure = (measure?: Measure | null) => measure && sceneMeasureMap.get(toRaw(measure))
  196. export const associationMessaure = <T extends MeasureType>(smMeasure: SceneMeasure<T>, measure: Measure<T>) => {
  197. const getMessaureDesc = () => {
  198. const length = measure.type === MeasureType.area
  199. ? (smMeasure as unknown as SceneMeasure<MeasureType.area>).getArea()
  200. : (smMeasure as unknown as SceneMeasure<MeasureType.free>).getDistance()
  201. return round(length.value, 2).toString()
  202. }
  203. smMeasure.bus.on('update', ([points, modelIds]) => {
  204. measure.positions = points.map((point, i) => ({ point, modelId: modelIds[i] }))
  205. measure.desc = getMessaureDesc()!
  206. })
  207. smMeasure.bus.on('highlight', selected => unSetModelUpdate(() => measure.selected = selected))
  208. unSetModelUpdate(() => {
  209. measure.desc = getMessaureDesc()!
  210. })
  211. }
  212. const associationMessaures = (sdk: SDK) => {
  213. const getMeasures = () => measures.value.filter(getMeasureIsShow)
  214. shallowWatchArray(getMeasures, (measures, oldMeasures) => {
  215. const { added, deleted } = diffArrayChange(measures, oldMeasures)
  216. for (const item of added) {
  217. const sceneMeasure = sdk.drawMeasure(
  218. item.type,
  219. item.positions.map(position => ({...position.point})),
  220. item.positions.map(position => position.modelId),
  221. )
  222. if (sceneMeasure.destroy) {
  223. sceneMeasureMap.set(toRaw(item), sceneMeasure)
  224. associationMessaure(sceneMeasure, item)
  225. }
  226. }
  227. for (const item of deleted) {
  228. const sceneMeasure = getSceneMeasure(item)
  229. sceneMeasure && sceneMeasure.destroy!()
  230. sceneMeasureMap.delete(toRaw(item))
  231. }
  232. })
  233. arrayChildEffectScope(getMeasures, measure => {
  234. watch(
  235. () => measure.selected,
  236. (selected = false) => isUnSet || getSceneMeasure(measure)?.changeSelect(selected)
  237. )
  238. watch(
  239. () => custom.showMeasures,
  240. (show) => {
  241. if (!isUnSet) {
  242. const smMeasure = getSceneMeasure(measure)
  243. if (show) {
  244. smMeasure?.show()
  245. } else {
  246. smMeasure?.hide()
  247. }
  248. }
  249. },
  250. { immediate: true }
  251. )
  252. })
  253. }
  254. // -----------------导览关联--------------------
  255. const fullView = async (fn: () => void) => {
  256. const popViewMode = togetherCallback([
  257. viewModeStack.push(ref('full')),
  258. showLeftPanoStack.push(ref(false))
  259. ])
  260. await document.documentElement.requestFullscreen()
  261. const driving = () => document.fullscreenElement || fn()
  262. document.addEventListener('fullscreenchange', driving)
  263. document.addEventListener('fullscreenerror', fn)
  264. return () => {
  265. popViewMode()
  266. document.fullscreenElement && document.exitFullscreen()
  267. document.removeEventListener('fullscreenchange', driving)
  268. document.removeEventListener('fullscreenerror', fn)
  269. }
  270. }
  271. export const isScenePlayIng = ref(false)
  272. export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallback?: (index: number) => void) => {
  273. if (isScenePlayIng.value) {
  274. throw new Error('导览正在播放')
  275. }
  276. isScenePlayIng.value = true
  277. const sceneGuide = sdk.enterSceneGuide(paths)
  278. changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback)
  279. const quitHandler = () => (isScenePlayIng.value = false)
  280. const clearHandler = isEdit.value ? null : await fullView(quitHandler)
  281. if (!clearHandler) {
  282. sysBus.on('leave', quitHandler, { last: true })
  283. sysBus.on('save', quitHandler, { last: true })
  284. }
  285. sceneGuide.play()
  286. const reces = [
  287. new Promise(resolve => sceneGuide.bus.on('playComplete', resolve)),
  288. new Promise<void>(resolve => {
  289. const stop = watch(isScenePlayIng, () => {
  290. if (!isScenePlayIng.value) {
  291. resolve()
  292. sceneGuide.pause()
  293. stop()
  294. }
  295. })
  296. }),
  297. ]
  298. await Promise.race(reces)
  299. isScenePlayIng.value = false
  300. if (clearHandler) {
  301. clearHandler()
  302. } else {
  303. sysBus.off('leave', quitHandler)
  304. sysBus.off('save', quitHandler)
  305. }
  306. sceneGuide.clear()
  307. sceneGuide.bus.off('changePoint')
  308. }
  309. export const pauseSceneGuide = () => isScenePlayIng.value = false
  310. // -----------------启动关联--------------------
  311. export const setupAssociation = (mountEl: HTMLDivElement) => {
  312. associationModels(sdk)
  313. const stopWatch = watchEffect(() => {
  314. if (fuseModelsLoaded.value) {
  315. associationTaggings(mountEl)
  316. associationMessaures(sdk)
  317. nextTick(() => stopWatch())
  318. }
  319. })
  320. }