association.ts 12 KB

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