@webspatial/platform-visionos 1.0.4 → 1.1.0

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