@webspatial/platform-visionos 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/package.json +2 -2
  2. package/web-spatial/EventEmitter.swift +56 -0
  3. package/web-spatial/JSBCommand.swift +348 -0
  4. package/web-spatial/SpatialObject.swift +108 -0
  5. package/web-spatial/WebMsgCommand.swift +119 -0
  6. package/web-spatial/WebSpatialApp.swift +111 -0
  7. package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
  8. package/web-spatial/manager/Dynamic3DManager.swift +114 -0
  9. package/web-spatial/manager/JSBManager.swift +148 -0
  10. package/web-spatial/manager/WKWebViewManager.swift +39 -0
  11. package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
  12. package/web-spatial/model/SpatialApp.swift +190 -0
  13. package/web-spatial/model/SpatialScene.swift +1042 -0
  14. package/web-spatial/model/Spatialized2DElement.swift +128 -0
  15. package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
  16. package/web-spatial/model/SpatializedElement.swift +95 -0
  17. package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
  18. package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
  19. package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
  20. package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
  21. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
  22. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
  23. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
  24. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
  25. package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
  26. package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
  27. package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
  28. package/web-spatial/protocol/WebMsgSender.swift +5 -0
  29. package/web-spatial/types/Vec2.swift +12 -0
  30. package/web-spatial/types/Vec3.swift +7 -0
  31. package/web-spatial/view/SceneHandlerUIView.swift +92 -0
  32. package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
  33. package/web-spatial/view/SpatialSceneContentView.swift +218 -0
  34. package/web-spatial/view/SpatialSceneView.swift +53 -0
  35. package/web-spatial/view/Spatialized2DElementView.swift +96 -0
  36. package/web-spatial/view/SpatializedDynamic3DView.swift +104 -0
  37. package/web-spatial/view/SpatializedElementView.swift +178 -0
  38. package/web-spatial/view/SpatializedStatic3DView.swift +70 -0
  39. package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
  40. package/web-spatial/webview/SpatialWebController.swift +300 -0
  41. package/web-spatial/webview/SpatialWebView.swift +34 -0
  42. package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
  43. package/web-spatial.xcodeproj/project.pbxproj +126 -207
  44. package/web-spatial/libs/EventEmitter.swift +0 -25
  45. package/web-spatial/libs/SpatialComponent.swift +0 -24
  46. package/web-spatial/libs/SpatialEntity.swift +0 -172
  47. package/web-spatial/libs/SpatialInputComponent.swift +0 -19
  48. package/web-spatial/libs/SpatialMeshResource.swift +0 -12
  49. package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
  50. package/web-spatial/libs/SpatialModelComponent.swift +0 -25
  51. package/web-spatial/libs/SpatialObject.swift +0 -140
  52. package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
  53. package/web-spatial/libs/SpatialViewComponent.swift +0 -8
  54. package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
  55. package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
  56. package/web-spatial/libs/Utils/CommandManager.swift +0 -823
  57. package/web-spatial/libs/Utils/PerfClock.swift +0 -43
  58. package/web-spatial/libs/Utils/SceneManager.swift +0 -101
  59. package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
  60. package/web-spatial/libs/json/JsonParser.swift +0 -45
  61. package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
  62. package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
  63. package/web-spatial/views/ImmersiveView.swift +0 -17
  64. package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
  65. package/web-spatial/views/PlainWindowContainerView.swift +0 -132
  66. package/web-spatial/views/SpatialModel3DView.swift +0 -187
  67. package/web-spatial/views/SpatialViewUI.swift +0 -168
  68. package/web-spatial/views/SpatialWebViewUI.swift +0 -179
  69. package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
  70. package/web-spatial/web_spatialApp.swift +0 -141
  71. /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
  72. /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
  73. /package/web-spatial/{views → view}/LoadingView.swift +0 -0
  74. /package/web-spatial/{views → view/view-modifier}/HideViewModifier.swift +0 -0
@@ -0,0 +1,1042 @@
1
+ import Combine
2
+ import Foundation
3
+ import simd
4
+ import SwiftUI
5
+
6
+ struct CustomReplyData: Codable {
7
+ let type: String
8
+ let name: String
9
+ }
10
+
11
+ struct AddSpatializedElementReply: Codable {
12
+ let id: String
13
+ }
14
+
15
+ struct ResizeRange: Codable {
16
+ var minWidth: Double?
17
+ var minHeight: Double?
18
+ var maxWidth: Double?
19
+ var maxHeight: Double?
20
+ }
21
+
22
+ struct ConvertReply: Codable {
23
+ let id: String
24
+ let position: SIMD3<Float>
25
+ }
26
+
27
+ let baseReplyData = CustomReplyData(type: "BasicData", name: "jsb call back")
28
+
29
+ let defaultSceneConfig = SceneOptions(
30
+ defaultSize: Size(width: 1280, height: 720),
31
+ windowResizability: .automatic,
32
+ worldScaling: .automatic,
33
+ worldAlignment: .automatic,
34
+ baseplateVisibility: .automatic
35
+ )
36
+
37
+ @Observable
38
+ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSender {
39
+ var parent: (any ScrollAbleSpatialElementContainer)?
40
+
41
+ // Enum
42
+ public enum WindowStyle: String, Codable, CaseIterable {
43
+ case window
44
+ case volume
45
+ }
46
+
47
+ // TOPIC begin
48
+ var openWindowData = PassthroughSubject<String, Never>()
49
+ var closeWindowData = PassthroughSubject<String, Never>()
50
+
51
+ var setLoadingWindowData = PassthroughSubject<XLoadingViewData, Never>()
52
+
53
+ var url: String = "" // start_url
54
+ public var windowStyle: WindowStyle {
55
+ didSet {
56
+ resetBackgroundMaterialOnWindowStyleChange(windowStyle)
57
+ }
58
+ }
59
+
60
+ private func resetBackgroundMaterialOnWindowStyleChange(_ windowStyle: WindowStyle) {
61
+ if windowStyle == .volume {
62
+ backgroundMaterial = .Transparent
63
+ } else {
64
+ backgroundMaterial = .None
65
+ }
66
+ }
67
+
68
+ enum SceneStateKind: String {
69
+ // default value
70
+ case idle
71
+ // when SpatialScene is loading
72
+ case pending
73
+ // when SpatialScen will visible after some time
74
+ case willVisible
75
+ // when SpatialScen load Succesfully
76
+ case visible
77
+ // when SpatialScen Failed to load
78
+ case fail
79
+ }
80
+
81
+ var state: SceneStateKind = .idle
82
+
83
+ // TOPIC end
84
+
85
+ var spatialWebViewModel: SpatialWebViewModel
86
+
87
+ init(
88
+ _ url: String,
89
+ _ windowStyle: WindowStyle,
90
+ _ state: SceneStateKind,
91
+ _ sceneOptions: SceneOptions?
92
+ ) {
93
+ self.windowStyle = windowStyle
94
+ self.url = url
95
+ spatialWebViewModel = SpatialWebViewModel(url: url)
96
+ super.init()
97
+ resetBackgroundMaterialOnWindowStyleChange(windowStyle)
98
+
99
+ setupSpatialWebView()
100
+
101
+ moveToState(state, sceneOptions)
102
+ }
103
+
104
+ // used to send message to spatial root webview
105
+ func sendWebMsg(_ id: String, _ msg: Encodable) {
106
+ spatialWebViewModel.sendWebEvent(id, msg)
107
+ }
108
+
109
+ private func setupSpatialWebView() {
110
+ setupJSBListeners()
111
+ setupWebViewStateListener()
112
+ }
113
+
114
+ private func handleNavigationCheck(_ url: URL) -> Bool {
115
+ // url in scope should open in place
116
+ return true
117
+ }
118
+
119
+ private func handleWindowOpenCustom(_ url: URL) -> WebViewElementInfo? {
120
+ // get config from url
121
+ guard let components = URLComponents(string: url.absoluteString),
122
+ let queryItems = components.queryItems
123
+ else {
124
+ print("❌ fail to parse URL")
125
+ return nil
126
+ }
127
+
128
+ guard let encodedUrl = queryItems.first(where: { $0.name == "url" })?.value,
129
+ let decodedUrl = encodedUrl.removingPercentEncoding
130
+ else {
131
+ print("❌ lack of required param url")
132
+ return nil
133
+ }
134
+
135
+ if let encodedConfig = queryItems.first(where: { $0.name == "config" })?.value,
136
+ let decodedConfig = encodedConfig.removingPercentEncoding
137
+ {
138
+ // open new Scene with Config
139
+ let decoder = JSONDecoder()
140
+ guard let configData = decodedConfig.data(using: .utf8) else {
141
+ print("❌ no config key")
142
+ // should not go here
143
+ return nil
144
+ }
145
+
146
+ if decodedConfig == "undefined" || decodedConfig == "null" {
147
+ // no scene config, need to create pending SpatialScene
148
+ let newScene = SpatialApp.Instance.createScene(
149
+ decodedUrl,
150
+ .window,
151
+ .pending,
152
+ nil
153
+ )
154
+
155
+ return WebViewElementInfo(
156
+ id: newScene.id,
157
+ element: newScene.spatialWebViewModel
158
+ )
159
+ } else {
160
+ do {
161
+ let config: XSceneOptionsJSB = try decoder.decode(XSceneOptionsJSB.self, from: configData)
162
+
163
+ let sceneType = config.type ?? .window
164
+
165
+ let newScene = SpatialApp.Instance.createScene(
166
+ decodedUrl,
167
+ sceneType,
168
+ .willVisible,
169
+ SceneOptions(config)
170
+ )
171
+
172
+ return WebViewElementInfo(
173
+ id: newScene.id,
174
+ element: newScene.spatialWebViewModel
175
+ )
176
+
177
+ } catch {
178
+ print("❌ config JSON decode fail: \(decodedConfig)")
179
+ return nil
180
+ }
181
+ }
182
+
183
+ } else {
184
+ return nil
185
+ }
186
+ }
187
+
188
+ private func handleWindowClose() {
189
+ print("window.close")
190
+ SpatialApp.Instance.closeWindowGroup(self)
191
+ }
192
+
193
+ public var sceneConfig: SceneOptions?
194
+
195
+ public func moveToState(_ newState: SceneStateKind, _ sceneConfig: SceneOptions?) {
196
+ print(" moveToState \(state) to \(newState) ")
197
+
198
+ let oldState = state
199
+ state = newState
200
+
201
+ if sceneConfig != nil {
202
+ self.sceneConfig = sceneConfig
203
+ }
204
+
205
+ if oldState == .idle, newState == .pending {
206
+ SpatialApp.Instance.openLoadingUI(self, true)
207
+ } else if oldState == .pending, newState == .willVisible {
208
+ SpatialApp.Instance.openLoadingUI(self, false)
209
+ // hack to fix windowGroup floating, we need it stay in place of loadingView
210
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
211
+ SpatialApp.Instance
212
+ .openWindowGroup(self, sceneConfig!)
213
+ }
214
+
215
+ } else if oldState == .idle, newState == .visible {
216
+ // SpatialApp opened SpatialScene
217
+ } else if oldState == .idle && newState == .willVisible {
218
+ // window.open with scene config
219
+ SpatialApp.Instance.openWindowGroup(self, sceneConfig!)
220
+ }
221
+ }
222
+
223
+ private func setupJSBListeners() {
224
+ spatialWebViewModel.addJSBListener(GetSpatialSceneStateCommand.self, onGetSpatialSceneState)
225
+ spatialWebViewModel.addJSBListener(InspectCommand.self, onInspect)
226
+ spatialWebViewModel.addJSBListener(UpdateSceneConfigCommand.self, onUpdateSceneConfig)
227
+ spatialWebViewModel
228
+ .addJSBListener(
229
+ FocusSceneCommand.self,
230
+ onFocusScene
231
+ )
232
+
233
+ spatialWebViewModel.addJSBListener(DestroyCommand.self, onDestroySpatialObjectCommand)
234
+
235
+ spatialWebViewModel.addJSBListener(UpdateSpatialSceneProperties.self, onUpdateSpatialSceneProperties)
236
+
237
+ spatialWebViewModel.addJSBListener(AddSpatializedElementToSpatialScene.self, onAddSpatializedElement)
238
+
239
+ spatialWebViewModel.addJSBListener(UpdateSpatialized2DElementProperties.self, onUpdateSpatialized2DElementProperties)
240
+
241
+ spatialWebViewModel.addJSBListener(UpdateSpatializedElementTransform.self, onUpdateSpatializedElementTransform)
242
+
243
+ spatialWebViewModel.addJSBListener(AddSpatializedElementToSpatialized2DElement.self, onAddSpatializedElementToSpatialized2DElement)
244
+
245
+ spatialWebViewModel.addJSBListener(UpdateSpatializedStatic3DElementProperties.self, onUpdateSpatializedStatic3DElementProperties)
246
+
247
+ spatialWebViewModel.addJSBListener(CreateSpatializedStatic3DElement.self, onCreateSpatializedStatic3DElement)
248
+
249
+ spatialWebViewModel.addJSBListener(CreateSpatializedDynamic3DElement.self, onCreateSpatializedDynamic3DElement)
250
+ spatialWebViewModel.addJSBListener(UpdateSpatializedDynamic3DElementProperties.self, onUpdateSpatializedDynamic3DElementProperties)
251
+ spatialWebViewModel.addJSBListener(CreateSpatialEntity.self, onCreateEntity)
252
+ spatialWebViewModel.addJSBListener(CreateGeometryProperties.self, onCreateGeometry)
253
+ spatialWebViewModel.addJSBListener(CreateUnlitMaterial.self, onCreateUnlitMaterial)
254
+ spatialWebViewModel.addJSBListener(CreateModelComponent.self, onCreateModelComponent)
255
+ spatialWebViewModel.addJSBListener(AddComponentToEntity.self, onAddComponentToEntity)
256
+ spatialWebViewModel.addJSBListener(AddEntityToDynamic3D.self, onAddEntityToDynamic3D)
257
+ spatialWebViewModel.addJSBListener(AddEntityToEntity.self, onAddEntityToEntity)
258
+ spatialWebViewModel.addJSBListener(SetParentForEntity.self, onSetParentForEntity)
259
+ spatialWebViewModel.addJSBListener(RemoveEntityFromParent.self, onRemoveEntityFromParent)
260
+ spatialWebViewModel.addJSBListener(UpdateEntityProperties.self, onUpdateEntityProperties)
261
+ spatialWebViewModel.addJSBListener(CreateModelAsset.self, onCreateModelAsset)
262
+ spatialWebViewModel.addJSBListener(CreateSpatialModelEntity.self, onCreateSpatialModelEntity)
263
+ spatialWebViewModel.addJSBListener(UpdateEntityEvent.self, onUpdateEntityEvent)
264
+ spatialWebViewModel.addJSBListener(ConvertFromEntityToEntity.self, onConvertFromEntityToEntity)
265
+ spatialWebViewModel.addJSBListener(ConvertFromEntityToScene.self, onConvertFromEntityToScene)
266
+ spatialWebViewModel.addJSBListener(ConvertFromSceneToEntity.self, onConvertFromSceneToEntity)
267
+
268
+ spatialWebViewModel.addOpenWindowListener(protocal: "webspatial", onOpenWindowHandler)
269
+
270
+ spatialWebViewModel
271
+ .addNavigationListener(protocal: SpatialApp.Instance.scope, event: handleNavigationCheck)
272
+ }
273
+
274
+ var width: Double = 0
275
+
276
+ var height: Double = 0
277
+
278
+ var depth: Double = 0
279
+
280
+ static let navHeight = 60.0
281
+
282
+ func updateSize3D(_ size: Size3D) {
283
+ width = size.width
284
+ height = size.height
285
+ depth = size.depth
286
+
287
+ // write through
288
+ spatialWebViewModel.updateWindowKV([
289
+ "innerDepth": depth,
290
+ "outerDepth": depth,
291
+ "outerHeight": height + SpatialScene.navHeight,
292
+ ])
293
+ }
294
+
295
+ public var didFailLoad = false
296
+
297
+ private func setupWebViewStateListener() {
298
+ spatialWebViewModel.addStateListener(.didStartLoad) {
299
+ self.didFailLoad = false
300
+ self.onPageStartLoad()
301
+ }
302
+
303
+ spatialWebViewModel.addScrollUpdateListener { _, point in
304
+ self._scrollOffset.x = point.x
305
+ self._scrollOffset.y = point.y
306
+ }
307
+
308
+ spatialWebViewModel.addStateListener(.didClose) {
309
+ self.handleWindowClose()
310
+ }
311
+
312
+ spatialWebViewModel.addStateListener(.didFailLoad) {
313
+ self.didFailLoad = true
314
+ }
315
+
316
+ spatialWebViewModel.addStateListener(.didFinishLoad) {
317
+ if self.state == .pending {
318
+ self.checkHookExist()
319
+ }
320
+ }
321
+ }
322
+
323
+ private func checkHookExist(_ completion: ((Bool) -> Void)? = nil) {
324
+ let js = """
325
+ (function() {
326
+ return typeof window.xrCurrentSceneDefaults !== 'undefined';
327
+ })();
328
+ """
329
+
330
+ spatialWebViewModel.evaluateJS(js) { result in
331
+ let exists = result as? Bool ?? false
332
+
333
+ if let completion = completion {
334
+ completion(exists)
335
+ } else {
336
+ if !exists {
337
+ self.moveToState(.willVisible, defaultSceneConfig)
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ private func onOpenWindowHandler(url: URL) -> WebViewElementInfo? {
344
+ let host = url.host ?? ""
345
+ if host == "createSpatialScene" {
346
+ return handleWindowOpenCustom(url)
347
+ } else {
348
+ let spatialized2DElement: Spatialized2DElement = createSpatializedElement(
349
+ .Spatialized2DElement
350
+ )
351
+ return WebViewElementInfo(id: spatialized2DElement.id, element: spatialized2DElement.getWebViewModel())
352
+ }
353
+ }
354
+
355
+ private func onPageStartLoad() {
356
+ // destroy all SpatialObject asset
357
+ let spatialObjectArray = spatialObjects.map { $0.value }
358
+ for spatialObject in spatialObjectArray {
359
+ spatialObject.destroy()
360
+ }
361
+ backgroundMaterial = .None
362
+ }
363
+
364
+ private func onGetSpatialSceneState(
365
+ command: GetSpatialSceneStateCommand,
366
+ resolve: @escaping JSBManager.ResolveHandler<Encodable>
367
+ ) {
368
+ resolve(.success(CustomReplyData(type: "state", name: state.rawValue)))
369
+ }
370
+
371
+ private func onInspect(command: InspectCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
372
+ if let targetId = command.id, !targetId.isEmpty {
373
+ if let spatialObject: SpatialObject = findSpatialObject(targetId) {
374
+ resolve(.success(spatialObject))
375
+ return
376
+ } else if let spatialEntity: SpatialEntity = findSpatialObject(targetId) {
377
+ resolve(.success(spatialEntity))
378
+ return
379
+ } else {
380
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid inspect spatial object id not exsit!")))
381
+ return
382
+ }
383
+ } else {
384
+ // inspect current SpatialScene
385
+ resolve(.success(self))
386
+ return
387
+ }
388
+ }
389
+
390
+ private func onDestroySpatialObjectCommand(command: DestroyCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
391
+ if let spatialObject: SpatialObject = findSpatialObject(command.id) {
392
+ spatialObject.destroy()
393
+ resolve(.success(nil))
394
+ return
395
+ } else if let spatialEntity: SpatialEntity = findSpatialObject(command.id) {
396
+ spatialEntity.destroy()
397
+ resolve(.success(nil))
398
+ return
399
+ } else {
400
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Failed to destroy SpatialObject: invalid inspect spatial object id \(command.id) not exsit!")))
401
+ return
402
+ }
403
+ }
404
+
405
+ private func onCreateSpatializedStatic3DElement(command: CreateSpatializedStatic3DElement, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
406
+ let spatialObject: SpatializedStatic3DElement = createSpatializedElement(.SpatializedStatic3DElement)
407
+ spatialObject.modelURL = command.modelURL
408
+
409
+ resolve(.success(AddSpatializedElementReply(id: spatialObject.id)))
410
+ }
411
+
412
+ private func onCreateSpatializedDynamic3DElement(command: CreateSpatializedDynamic3DElement, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
413
+ let spatialObject: SpatializedDynamic3DElement = createSpatializedElement(.SpatializedDynamic3DElement)
414
+
415
+ resolve(.success(AddSpatializedElementReply(id: spatialObject.id)))
416
+ }
417
+
418
+ private func onUpdateSpatializedDynamic3DElementProperties(command: UpdateSpatializedDynamic3DElementProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
419
+ guard let spatializedElement: SpatializedDynamic3DElement = findSpatialObject(command.id) else {
420
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid updateSpatializedDynamic3DElement spatial object id not exsit!")))
421
+ return
422
+ }
423
+ updateSpatializedElementProperties(spatializedElement, command)
424
+ resolve(.success(baseReplyData))
425
+ }
426
+
427
+ private func onUpdateSpatialSceneProperties(command: UpdateSpatialSceneProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
428
+ if let material = command.material {
429
+ backgroundMaterial = material
430
+ }
431
+
432
+ if let cornerRadius = command.cornerRadius {
433
+ self.cornerRadius = cornerRadius
434
+ }
435
+
436
+ if let opacity = command.opacity {
437
+ self.opacity = opacity
438
+ }
439
+ resolve(.success(baseReplyData))
440
+ }
441
+
442
+ private func onUpdateSpatializedStatic3DElementProperties(command: UpdateSpatializedStatic3DElementProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
443
+ guard let spatializedElement: SpatializedStatic3DElement = findSpatialObject(command.id) else {
444
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid updateSpatializedStatic3DElement spatial object id not exsit!")))
445
+ return
446
+ }
447
+
448
+ updateSpatializedElementProperties(spatializedElement, command)
449
+
450
+ if let modelURL = command.modelURL {
451
+ spatializedElement.modelURL = modelURL
452
+ }
453
+
454
+ if let array = command.modelTransform {
455
+ guard array.count == 16 else {
456
+ print("Received modelTransform matrix array does not have 16 elements.")
457
+ return resolve(.failure(JsbError(code: .InvalidMatrix, message: "invalid UpdateSpatializedStatic3DElementProperties matrix should have length 16!")))
458
+ }
459
+
460
+ let column0 = simd_double4(array[0], array[1], array[2], array[3])
461
+ let column1 = simd_double4(array[4], array[5], array[6], array[7])
462
+ let column2 = simd_double4(array[8], array[9], array[10], array[11])
463
+ let column3 = simd_double4(array[12], array[13], array[14], array[15])
464
+ let simd_double4x4 = simd_double4x4(columns: (column0, column1, column2, column3))
465
+ let affineTransform3D = AffineTransform3D(truncating: simd_double4x4)
466
+ spatializedElement.modelTransform = affineTransform3D
467
+ }
468
+
469
+ resolve(.success(baseReplyData))
470
+ }
471
+
472
+ private func onFocusScene(
473
+ command: FocusSceneCommand,
474
+ resolve: @escaping JSBManager.ResolveHandler<Encodable>
475
+ ) {
476
+ let sceneId = command.id
477
+ print("onFocusScene \(sceneId)")
478
+
479
+ if let targetScene = SpatialApp.Instance.getScene(sceneId) {
480
+ SpatialApp.Instance.focusScene(targetScene)
481
+ }
482
+
483
+ resolve(.success(baseReplyData))
484
+ }
485
+
486
+ private func onUpdateSceneConfig(command: UpdateSceneConfigCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
487
+ if state == .visible || state == .willVisible {
488
+ print("forbidden to update scene config after visible")
489
+ // prevent re-enter
490
+ resolve(.success(baseReplyData))
491
+ return
492
+ }
493
+
494
+ let sceneConfigJSBData = command.config
495
+ print("onUpdateSceneConfig \(command.config)")
496
+
497
+ // find scene
498
+ let sceneConfig = SceneOptions(sceneConfigJSBData)
499
+
500
+ // update sceneType
501
+ windowStyle = sceneConfigJSBData.type!
502
+
503
+ moveToState(.willVisible, sceneConfig)
504
+
505
+ resolve(.success(baseReplyData))
506
+ }
507
+
508
+ private func onAddSpatializedElementToSpatialized2DElement(command: AddSpatializedElementToSpatialized2DElement, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
509
+ guard let spatialized2DElement: Spatialized2DElement = findSpatialObject(command.id)
510
+ else {
511
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid AddSpatializedElementToSpatialized2DElement spatial object id not exsit!")))
512
+ return
513
+ }
514
+
515
+ guard let targetSpatializedElement: SpatializedElement = findSpatialObject(command.spatializedElementId) else {
516
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid AddSpatializedElementToSpatialized2DElement target spatial object id not exsit!")))
517
+ return
518
+ }
519
+
520
+ targetSpatializedElement.setParent(spatialized2DElement)
521
+ resolve(.success(baseReplyData))
522
+ }
523
+
524
+ private func onUpdateSpatialized2DElementProperties(command: UpdateSpatialized2DElementProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
525
+ guard let spatialized2DElement: Spatialized2DElement = findSpatialObject(command.id) else {
526
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid updateSpatializedElementProperties spatial object id not exsit!")))
527
+ return
528
+ }
529
+ updateSpatializedElementProperties(spatialized2DElement, command)
530
+ if let scrollPageEnabled = command.scrollPageEnabled {
531
+ spatialized2DElement.scrollPageEnabled = scrollPageEnabled
532
+ }
533
+ if let material = command.material {
534
+ spatialized2DElement.backgroundMaterial = material
535
+ }
536
+
537
+ if let cornerRadius = command.cornerRadius {
538
+ spatialized2DElement.cornerRadius = cornerRadius
539
+ }
540
+
541
+ if let scrollEdgeInsetsMarginRight = command.scrollEdgeInsetsMarginRight {
542
+ spatialized2DElement.scrollEdgeInsetsMarginRight = scrollEdgeInsetsMarginRight
543
+ }
544
+
545
+ resolve(.success(baseReplyData))
546
+ }
547
+
548
+ private func updateSpatializedElementProperties(_ spatializedElement: SpatializedElement, _ command: SpatializedElementProperties) {
549
+ if let name = command.name {
550
+ spatializedElement.name = name
551
+ }
552
+
553
+ if let clientX = command.clientX {
554
+ spatializedElement.clientX = clientX
555
+ }
556
+
557
+ if let clientY = command.clientY {
558
+ spatializedElement.clientY = clientY
559
+ }
560
+
561
+ if let width = command.width {
562
+ spatializedElement.width = width
563
+ }
564
+
565
+ if let height = command.height {
566
+ spatializedElement.height = height
567
+ }
568
+
569
+ if let depth = command.depth {
570
+ spatializedElement.depth = depth
571
+ }
572
+
573
+ if let backOffset = command.backOffset {
574
+ spatializedElement.backOffset = backOffset
575
+ }
576
+
577
+ if let opacity = command.opacity {
578
+ spatializedElement.opacity = opacity
579
+ }
580
+
581
+ if let scrollWithParent = command.scrollWithParent {
582
+ spatializedElement.scrollWithParent = scrollWithParent
583
+ }
584
+
585
+ if let visible = command.visible {
586
+ spatializedElement.visible = visible
587
+ }
588
+
589
+ if let zIndex = command.zIndex {
590
+ spatializedElement.zIndex = zIndex
591
+ }
592
+
593
+ if let rotationAnchor = command.rotationAnchor {
594
+ spatializedElement.rotationAnchor = .init(x: CGFloat(rotationAnchor.x), y: CGFloat(rotationAnchor.y), z: CGFloat(rotationAnchor.z))
595
+ }
596
+
597
+ if let enableTapGesture = command.enableTapGesture {
598
+ spatializedElement.enableTapGesture = enableTapGesture
599
+ }
600
+
601
+ if let enableDragStartGesture = command.enableDragStartGesture {
602
+ spatializedElement.enableDragStartGesture = enableDragStartGesture
603
+ }
604
+
605
+ if let enableDragGesture = command.enableDragGesture {
606
+ spatializedElement.enableDragGesture = enableDragGesture
607
+ }
608
+ if let enableDragEndGesture = command.enableDragEndGesture {
609
+ spatializedElement.enableDragEndGesture = enableDragEndGesture
610
+ }
611
+ if let enableRotateStartGesture = command.enableRotateStartGesture {
612
+ spatializedElement.enableRotateStartGesture = enableRotateStartGesture
613
+ }
614
+ if let enableRotateGesture = command.enableRotateGesture {
615
+ spatializedElement.enableRotateGesture = enableRotateGesture
616
+ }
617
+ if let enableRotateEndGesture = command.enableRotateEndGesture {
618
+ spatializedElement.enableRotateEndGesture = enableRotateEndGesture
619
+ }
620
+
621
+ if let enableMagnifyStartGesture = command.enableMagnifyStartGesture {
622
+ spatializedElement.enableMagnifyStartGesture = enableMagnifyStartGesture
623
+ }
624
+
625
+ if let enableMagnifyGesture = command.enableMagnifyGesture {
626
+ spatializedElement.enableMagnifyGesture = enableMagnifyGesture
627
+ }
628
+ if let enableMagnifyEndGesture = command.enableMagnifyEndGesture {
629
+ spatializedElement.enableMagnifyEndGesture = enableMagnifyEndGesture
630
+ }
631
+ }
632
+
633
+ private func onUpdateSpatializedElementTransform(command: UpdateSpatializedElementTransform, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
634
+ guard let spatializedElement: SpatializedElement = findSpatialObject(command.id) else {
635
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid UpdateSpatializedElementTransform spatial object id not exsit!")))
636
+ return
637
+ }
638
+
639
+ let array = command.matrix
640
+
641
+ guard array.count == 16 else {
642
+ print("Received matrix array does not have 16 elements.")
643
+ return resolve(.failure(JsbError(code: .InvalidMatrix, message: "invalid UpdateSpatializedElementTransform matrix should have length 16!")))
644
+ }
645
+
646
+ let column0 = simd_double4(array[0], array[1], array[2], array[3])
647
+ let column1 = simd_double4(array[4], array[5], array[6], array[7])
648
+ let column2 = simd_double4(array[8], array[9], array[10], array[11])
649
+ let column3 = simd_double4(array[12], array[13], array[14], array[15])
650
+ let simd_double4x4 = simd_double4x4(columns: (column0, column1, column2, column3))
651
+ let affineTransform3D = AffineTransform3D(truncating: simd_double4x4)
652
+ spatializedElement.transform = affineTransform3D
653
+
654
+ resolve(.success(baseReplyData))
655
+ }
656
+
657
+ private func onAddSpatializedElement(command: AddSpatializedElementToSpatialScene, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
658
+ guard let spatializedElement: SpatializedElement = findSpatialObject(command.spatializedElementId) else {
659
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "invalid addSpatializedElementCommand spatial object id not exsit!")))
660
+ return
661
+ }
662
+
663
+ spatializedElement.setParent(self)
664
+ resolve(.success(baseReplyData))
665
+ }
666
+
667
+ /*
668
+ * Begin Implement SpatializedElementContainer Protocol
669
+ */
670
+
671
+ // SpatialScene can hold a collection of SpatializedElement children
672
+ private var children = [String: SpatializedElement]()
673
+
674
+ // Called by SpatializedElement.setParent
675
+ func addChild(_ spatializedElement: SpatializedElement) {
676
+ children[spatializedElement.id] = spatializedElement
677
+ }
678
+
679
+ // Called by SpatializedElement.setParent
680
+ func removeChild(_ spatializedElement: SpatializedElement) {
681
+ children.removeValue(forKey: spatializedElement.id)
682
+ }
683
+
684
+ func getChildrenOfType(_ type: SpatializedElementType) -> [String: SpatializedElement] {
685
+ let typedChildren = children.filter {
686
+ switch type {
687
+ case .Spatialized2DElement:
688
+ return $0.value is Spatialized2DElement
689
+ case .SpatializedStatic3DElement:
690
+ return $0.value is SpatializedStatic3DElement
691
+ case .SpatializedDynamic3DElement:
692
+ return $0.value is SpatializedDynamic3DElement
693
+ }
694
+ }
695
+ return typedChildren
696
+ }
697
+
698
+ func getChildren() -> [String: SpatializedElement] {
699
+ return children
700
+ }
701
+
702
+ /*
703
+ * End Implement SpatializedElementContainer Protocol
704
+ */
705
+
706
+ /*
707
+ * Begin Implement SpatialScrollAble Protocol
708
+ */
709
+ let scrollPageEnabled: Bool = true
710
+
711
+ var _scrollOffset: Vec2 = .init(x: 0, y: 0)
712
+ var scrollOffset: Vec2 {
713
+ get {
714
+ return _scrollOffset
715
+ }
716
+ set(newValue) {
717
+ _scrollOffset = newValue
718
+ }
719
+ }
720
+
721
+ func updateDeltaScrollOffset(_ delta: Vec2) {
722
+ spatialWebViewModel.setScrollOffset(_scrollOffset + delta)
723
+ }
724
+
725
+ func stopScrolling() {
726
+ spatialWebViewModel.stopScrolling()
727
+ }
728
+
729
+ /*
730
+ * End Implement SpatialScrollAble Protocol
731
+ */
732
+
733
+ private var _backgroundMaterial = BackgroundMaterial.None
734
+ var backgroundMaterial: BackgroundMaterial {
735
+ get {
736
+ return _backgroundMaterial
737
+ }
738
+ set(newValue) {
739
+ _backgroundMaterial = newValue
740
+ if windowStyle == .volume {
741
+ // default background for volume scene is transparent
742
+ spatialWebViewModel
743
+ .setBackgroundTransparent(true)
744
+ } else {
745
+ spatialWebViewModel.setBackgroundTransparent(_backgroundMaterial != .None)
746
+ }
747
+ }
748
+ }
749
+
750
+ var cornerRadius: CornerRadius = .init()
751
+
752
+ var opacity: Double = 1.0
753
+
754
+ func getView() -> SpatialWebView {
755
+ return spatialWebViewModel.getView()
756
+ }
757
+
758
+ /*
759
+ * Begin SpatialObjects management
760
+ */
761
+
762
+ // Resources that will be destroyed when this webpage is destoryed or if it is navigated away from
763
+ private var spatialObjects = [String: any SpatialObjectProtocol]()
764
+
765
+ func createSpatializedElement<T: SpatializedElement>(_ type: SpatializedElementType) -> T {
766
+ let spatializedElement: T = switch type {
767
+ case .Spatialized2DElement:
768
+ Spatialized2DElement() as! T
769
+ case .SpatializedStatic3DElement:
770
+ SpatializedStatic3DElement() as! T
771
+ case .SpatializedDynamic3DElement:
772
+ SpatializedDynamic3DElement() as! T
773
+ }
774
+
775
+ addSpatialObject(spatializedElement)
776
+
777
+ return spatializedElement
778
+ }
779
+
780
+ private func onCreateGeometry(command: CreateGeometryProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
781
+ do {
782
+ let geometry = try Dynamic3DManager.createGeometry(command)
783
+ addSpatialObject(geometry)
784
+ resolve(.success(AddSpatializedElementReply(id: geometry.id)))
785
+ } catch let err as GeometryCreationError {
786
+ switch err {
787
+ case .invalidType:
788
+ resolve(.failure(JsbError(code: .TypeError, message: err.localizedDescription)))
789
+ case .missingFields:
790
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: err.localizedDescription)))
791
+ }
792
+ } catch {
793
+ resolve(.failure(JsbError(code: .CommandError, message: error.localizedDescription)))
794
+ }
795
+ }
796
+
797
+ private func onCreateEntity(command: CreateSpatialEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
798
+ let entity = Dynamic3DManager.createEntity(command)
799
+ addSpatialObject(entity)
800
+ resolve(.success(AddSpatializedElementReply(id: entity.spatialId)))
801
+ }
802
+
803
+ private func onCreateComponent() {
804
+ // @fukang: add Component here
805
+ }
806
+
807
+ private func onCreateUnlitMaterial(command: CreateUnlitMaterial, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
808
+ let material = Dynamic3DManager.createUnlitMaterial(command, nil)
809
+ addSpatialObject(material)
810
+ resolve(.success(AddSpatializedElementReply(id: material.id)))
811
+ }
812
+
813
+ private func onCreateModelComponent(command: CreateModelComponent, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
814
+ if let geometry = spatialObjects[command.geometryId] as? Geometry {
815
+ var materials: [SpatialMaterial] = []
816
+ for mid in command.materialIds {
817
+ if let material = spatialObjects[mid] as? SpatialMaterial {
818
+ materials.append(material)
819
+ } else {
820
+ print("material \(mid) not found ")
821
+ }
822
+ }
823
+ let component = Dynamic3DManager.createModelComponent(mesh: geometry, mats: materials)
824
+ addSpatialObject(component)
825
+ resolve(.success(AddSpatializedElementReply(id: component.id)))
826
+ } else {
827
+ print("geometry \(command.geometryId) not found")
828
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "geometry \(command.geometryId) not found")))
829
+ }
830
+ }
831
+
832
+ private func onAddComponentToEntity(command: AddComponentToEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
833
+ if let entity = spatialObjects[command.entityId] as? SpatialEntity,
834
+ let component = spatialObjects[command.componentId] as? SpatialComponent
835
+ {
836
+ entity.addComponent(component)
837
+ resolve(.success(baseReplyData))
838
+ return
839
+ }
840
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Add component failed")))
841
+ }
842
+
843
+ private func onAddEntityToDynamic3D(command: AddEntityToDynamic3D, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
844
+ if let entity = spatialObjects[command.entityId] as? SpatialEntity,
845
+ let dynamic3dElement = spatialObjects[command.dynamic3dId] as? SpatializedDynamic3DElement
846
+ {
847
+ dynamic3dElement.addEntity(entity)
848
+ resolve(.success(baseReplyData))
849
+ return
850
+ }
851
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Add Entity failed")))
852
+ }
853
+
854
+ private func onAddEntityToEntity(command: AddEntityToEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
855
+ if let entityChild = spatialObjects[command.childId] as? SpatialEntity,
856
+ let entityParent = spatialObjects[command.parentId] as? SpatialEntity
857
+ {
858
+ entityParent.addChild(entity: entityChild)
859
+ resolve(.success(baseReplyData))
860
+ return
861
+ }
862
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Add Entity failed")))
863
+ }
864
+
865
+ private func onSetParentForEntity(command: SetParentForEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
866
+ if let entity = spatialObjects[command.childId] as? SpatialEntity {
867
+ if let parentId = command.parentId {
868
+ if let parentEntity = spatialObjects[parentId] as? SpatialEntity {
869
+ parentEntity.addChild(entity: entity)
870
+ } else if let container = spatialObjects[parentId] as? SpatializedDynamic3DElement {
871
+ container.addEntity(entity)
872
+ } else {
873
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Parent \(parentId) not found")))
874
+ return
875
+ }
876
+ resolve(.success(baseReplyData))
877
+ return
878
+ } else {
879
+ entity.removeFromParent()
880
+ resolve(.success(baseReplyData))
881
+ return
882
+ }
883
+ }
884
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity \(command.childId) not found")))
885
+ }
886
+
887
+ private func onRemoveEntityFromParent(command: RemoveEntityFromParent, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
888
+ if let entity = spatialObjects[command.entityId] as? SpatialEntity {
889
+ if entity.parent != nil,
890
+ let parentEntity = entity.parent as? SpatialEntity
891
+ {
892
+ parentEntity.removeChild(id: entity.spatialId)
893
+ resolve(.success(baseReplyData))
894
+ return
895
+ }
896
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Parent not found")))
897
+ return
898
+ }
899
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity not found")))
900
+ }
901
+
902
+ private func onUpdateEntityProperties(command: UpdateEntityProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
903
+ if let entity = spatialObjects[command.entityId] as? SpatialEntity,
904
+ command.transform.count == 16
905
+ {
906
+ entity.updateTransform(command.transform)
907
+ resolve(.success(baseReplyData))
908
+ return
909
+ }
910
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Update Entity failed")))
911
+ }
912
+
913
+ private func onCreateModelAsset(command: CreateModelAsset, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
914
+ _ = SpatialModelResource(command.url) { onload in
915
+ switch onload {
916
+ case let .success(modelResource):
917
+ self.addSpatialObject(modelResource)
918
+ resolve(.success(AddSpatializedElementReply(id: modelResource.id)))
919
+ case let .failure(error):
920
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Failed to download model: \(error)")))
921
+ }
922
+ }
923
+ }
924
+
925
+ private func onCreateSpatialModelEntity(command: CreateSpatialModelEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
926
+ if let modelAsset = spatialObjects[command.modelAssetId] as? SpatialModelResource {
927
+ let spatialModelEntity = SpatialModelEntity(modelAsset, command.name ?? "")
928
+ addSpatialObject(spatialModelEntity)
929
+ resolve(.success(AddSpatializedElementReply(id: spatialModelEntity.spatialId)))
930
+ return
931
+ }
932
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "ModelAsset not found")))
933
+ }
934
+
935
+ private func onUpdateEntityEvent(command: UpdateEntityEvent, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
936
+ guard let entity = spatialObjects[command.entityId] as? SpatialEntity else {
937
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity not found")))
938
+ return
939
+ }
940
+ entity.updateGesture(command.type, command.isEnable)
941
+ resolve(.success(baseReplyData))
942
+ }
943
+
944
+ private func onConvertFromEntityToEntity(command: ConvertFromEntityToEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
945
+ guard let fromEntity = spatialObjects[command.fromEntityId] as? SpatialEntity else {
946
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity \(command.fromEntityId) not found")))
947
+ return
948
+ }
949
+
950
+ guard let toEntity = spatialObjects[command.toEntityId] as? SpatialEntity else {
951
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity \(command.toEntityId) not found")))
952
+ return
953
+ }
954
+ let position = SIMD3<Float>(Float(command.position.x), Float(command.position.y), Float(command.position.z))
955
+ let point = fromEntity.convert(position: position, to: toEntity)
956
+ resolve(.success(ConvertReply(id: command.fromEntityId, position: point)))
957
+ }
958
+
959
+ private func onConvertFromEntityToScene(command: ConvertFromEntityToScene, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
960
+ guard let fromEntity = spatialObjects[command.fromEntityId] as? SpatialEntity else {
961
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity \(command.fromEntityId) not found")))
962
+ return
963
+ }
964
+ let position = SIMD3<Float>(Float(command.position.x), Float(command.position.y), Float(command.position.z))
965
+ let point = fromEntity.convert(position: position, to: nil)
966
+ resolve(.success(ConvertReply(id: command.fromEntityId, position: point)))
967
+ }
968
+
969
+ private func onConvertFromSceneToEntity(command: ConvertFromSceneToEntity, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
970
+ guard let entity = spatialObjects[command.entityId] as? SpatialEntity else {
971
+ resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Entity \(command.entityId) not found")))
972
+ return
973
+ }
974
+ let position = SIMD3<Float>(Float(command.position.x), Float(command.position.y), Float(command.position.z))
975
+ let point = entity.convert(position: position, from: nil)
976
+ resolve(.success(ConvertReply(id: command.entityId, position: point)))
977
+ }
978
+
979
+ private func addSpatialObject(_ object: any SpatialObjectProtocol) {
980
+ var spatialObject = object
981
+ spatialObjects[spatialObject.spatialId] = spatialObject
982
+ spatialObject
983
+ .on(
984
+ event: SpatialObject.Events.BeforeDestroyed.rawValue,
985
+ listener: onSptatialObjectDestroyed
986
+ )
987
+ }
988
+
989
+ private func onSptatialObjectDestroyed(_ object: Any, _ data: Any) {
990
+ var spatialObject = object as! (any SpatialObjectProtocol)
991
+ spatialObject
992
+ .off(
993
+ event: SpatialObject.Events.BeforeDestroyed.rawValue,
994
+ listener: onSptatialObjectDestroyed
995
+ )
996
+ spatialObjects.removeValue(forKey: spatialObject.spatialId)
997
+
998
+ // notify web side, spatialObject is destroyed
999
+ sendWebMsg(spatialObject.spatialId, SpatialObjectDestroiedEvent())
1000
+ }
1001
+
1002
+ func findSpatialObject<T: SpatialObjectProtocol>(_ id: String) -> T? {
1003
+ return spatialObjects[id] as? T
1004
+ }
1005
+
1006
+ /*
1007
+ * End SpatialObjects management
1008
+ */
1009
+
1010
+ override func onDestroy() {
1011
+ let spatialObjectArray = spatialObjects.map { $0.value }
1012
+ for spatialObject in spatialObjectArray {
1013
+ spatialObject.destroy()
1014
+ }
1015
+ spatialWebViewModel.destroy()
1016
+ }
1017
+
1018
+ enum CodingKeys: String, CodingKey {
1019
+ case children, url, backgroundMaterial, cornerRadius, scrollOffset, webviewIsOpaque, spatialObjectCount, spatialObjectRefCount, spatialObjectList
1020
+ }
1021
+
1022
+ override func encode(to encoder: Encoder) throws {
1023
+ try super.encode(to: encoder)
1024
+ var container = encoder.container(keyedBy: CodingKeys.self)
1025
+ try container.encode(spatialWebViewModel.url, forKey: .url)
1026
+ try container.encode(backgroundMaterial, forKey: .backgroundMaterial)
1027
+ try container.encode(cornerRadius, forKey: .cornerRadius)
1028
+ try container.encode(scrollOffset, forKey: .scrollOffset)
1029
+ try container.encode(children, forKey: .children)
1030
+
1031
+ // for debug only
1032
+ try container.encode(spatialWebViewModel.getController().webview?.isOpaque, forKey: .webviewIsOpaque)
1033
+ try container.encode(SpatialObject.objects.count, forKey: .spatialObjectCount)
1034
+
1035
+ let spatialObjectList = SpatialObject.objects.map { object in
1036
+ ["id": object.key, "type": String(describing: type(of: object.value))]
1037
+ }
1038
+ try container.encode(spatialObjectList, forKey: .spatialObjectList)
1039
+
1040
+ try container.encode(SpatialObjectWeakRefManager.weakRefObjects.count, forKey: .spatialObjectRefCount)
1041
+ }
1042
+ }