@webspatial/platform-visionos 1.7.0 → 2.0.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.
- package/package.json +1 -1
- package/web-spatial/JSBCommand.swift +1 -0
- package/web-spatial/WebMsgCommand.swift +26 -2
- package/web-spatial/WebSpatialApp.swift +26 -8
- package/web-spatial/manifest.swift +1 -1
- package/web-spatial/model/SpatialScene.swift +178 -39
- package/web-spatial/model/Spatialized2DElement.swift +2 -11
- package/web-spatial/model/SpatializedElement.swift +38 -0
- package/web-spatial/model/SpatializedStatic3DElement.swift +27 -8
- package/web-spatial/model/dynamic3d/EntityAnimationManager.swift +356 -0
- package/web-spatial/model/dynamic3d/EntityAnimationSession.swift +162 -0
- package/web-spatial/model/element-motion/SpatializedElementAnimationCommand.swift +15 -0
- package/web-spatial/model/element-motion/SpatializedElementAnimationManager.swift +263 -0
- package/web-spatial/model/element-motion/SpatializedElementAnimationObject.swift +399 -0
- package/web-spatial/model/element-motion/SpatializedElementAnimationTypes.swift +45 -0
- package/web-spatial/model/element-motion/SpatializedElementAnimationWriteAdapter.swift +65 -0
- package/web-spatial/model/element-motion/SpatializedElementMotionBridgeTypes.swift +43 -0
- package/web-spatial/model/element-motion/SpatializedElementMotionTimelineSampler.swift +99 -0
- package/web-spatial/model/element-motion/SpatializedElementMotionTiming.swift +99 -0
- package/web-spatial/model/element-motion/SpatializedElementMotionTransformTypes.swift +22 -0
- package/web-spatial/protocol/SpatializedElementContainer.swift +1 -1
- package/web-spatial/view/SpatialNavView.swift +37 -12
- package/web-spatial/view/SpatialSceneContentView.swift +9 -12
- package/web-spatial/view/Spatialized2DElementView.swift +10 -17
- package/web-spatial/view/SpatializedDynamic3DView.swift +2 -6
- package/web-spatial/view/SpatializedElementView.swift +3 -2
- package/web-spatial/view/SpatializedStatic3DView.swift +32 -33
- package/web-spatial/view/view-modifier/OrbitModifier.swift +84 -0
- package/web-spatial/webview/SpatialWebController.swift +26 -18
- package/web-spatial/webview/SpatialWebViewModel.swift +12 -7
- package/web-spatial.xcodeproj/project.pbxproj +29 -8
- package/web-spatial.xcodeproj/xcshareddata/xcschemes/web-spatial.xcscheme +1 -1
- package/web-spatialTests/NavigationCleanupTests.swift +2 -1
- package/web-spatialTests/SpatializedElementAnimationManagerTests.swift +1466 -0
- package/web-spatialTests/SpatializedElementMotionTimelineSamplerTests.swift +117 -0
package/package.json
CHANGED
|
@@ -278,6 +278,7 @@ struct UpdateSpatializedStatic3DElementProperties: SpatializedElementProperties
|
|
|
278
278
|
let currentTime: Double?
|
|
279
279
|
let posterURL: String?
|
|
280
280
|
let loading: String?
|
|
281
|
+
let stagemode: String?
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
struct UpdateSpatializedDynamic3DElementProperties: SpatializedElementProperties {
|
|
@@ -24,9 +24,8 @@ enum SpatialWebMsgType: String, Encodable {
|
|
|
24
24
|
case spatialrotateend
|
|
25
25
|
case spatialmagnify
|
|
26
26
|
case spatialmagnifyend
|
|
27
|
-
|
|
28
27
|
case animationstatechange
|
|
29
|
-
|
|
28
|
+
case entitytransformchange
|
|
30
29
|
case objectdestroy
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -129,6 +128,31 @@ struct AnimationStateChangeEvent: Encodable {
|
|
|
129
128
|
let detail: AnimationStateChangeDetail
|
|
130
129
|
}
|
|
131
130
|
|
|
131
|
+
/// 16-element column-major representation of the new `entityTransform`,
|
|
132
|
+
/// matching the wire format that JS already uses when sending the matrix to
|
|
133
|
+
/// native via `UpdateSpatializedStatic3DElementProperties.modelTransform`.
|
|
134
|
+
struct EntityTransformChangeDetail: Encodable {
|
|
135
|
+
let transform: [Double]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
struct EntityTransformChangeEvent: Encodable {
|
|
139
|
+
let type: SpatialWebMsgType = .entitytransformchange
|
|
140
|
+
let detail: EntityTransformChangeDetail
|
|
141
|
+
|
|
142
|
+
/// Emits the transform to the web layer as a 16-element column-major matrix.
|
|
143
|
+
init(_ transform: AffineTransform3D) {
|
|
144
|
+
let m = transform.matrix
|
|
145
|
+
let c0 = m.columns.0, c1 = m.columns.1, c2 = m.columns.2, c3 = m.columns.3
|
|
146
|
+
let array: [Double] = [
|
|
147
|
+
c0.x, c0.y, c0.z, 0,
|
|
148
|
+
c1.x, c1.y, c1.z, 0,
|
|
149
|
+
c2.x, c2.y, c2.z, 0,
|
|
150
|
+
c3.x, c3.y, c3.z, 1,
|
|
151
|
+
]
|
|
152
|
+
detail = EntityTransformChangeDetail(transform: array)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
struct SpatialObjectDestroiedEvent: Encodable {
|
|
133
157
|
let type: SpatialWebMsgType = .objectdestroy
|
|
134
158
|
}
|
|
@@ -40,13 +40,31 @@ struct WebSpatialApp: App {
|
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
func getSceneOrCreate(_ sceneID: String?, _ style: SpatialScene.WindowStyle) -> SpatialScene {
|
|
44
|
+
if let sceneID, let spatialScene = SpatialApp.Instance.getScene(sceneID) {
|
|
45
|
+
return spatialScene
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logger.debug("Missing scene for WindowGroup data. Recreating scene.")
|
|
49
|
+
return SpatialApp.Instance.createScene(
|
|
50
|
+
startURL,
|
|
51
|
+
style,
|
|
52
|
+
.visible,
|
|
53
|
+
app.getSceneOptions()
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func getSceneOrCreate(_ sceneID: String, _ style: SpatialScene.WindowStyle) -> SpatialScene {
|
|
58
|
+
return getSceneOrCreate(Optional(sceneID), style)
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
var body: some Scene {
|
|
44
62
|
WindowGroup(
|
|
45
63
|
id: SpatialScene.WindowStyle.window.rawValue,
|
|
46
64
|
for: String.self
|
|
47
65
|
) { $windowData in
|
|
48
|
-
let spatialScene =
|
|
49
|
-
SpatialSceneView(spatialScene: spatialScene
|
|
66
|
+
let spatialScene = getSceneOrCreate(windowData, .window)
|
|
67
|
+
SpatialSceneView(spatialScene: spatialScene)
|
|
50
68
|
}
|
|
51
69
|
defaultValue: {
|
|
52
70
|
let scene = SpatialApp.Instance.createScene(
|
|
@@ -66,20 +84,20 @@ struct WebSpatialApp: App {
|
|
|
66
84
|
)
|
|
67
85
|
|
|
68
86
|
WindowGroup(id: SpatialScene.WindowStyle.volume.rawValue, for: String.self) { $windowData in
|
|
69
|
-
let spatialScene =
|
|
70
|
-
SpatialSceneView(spatialScene: spatialScene
|
|
87
|
+
let spatialScene = getSceneOrCreate(windowData, .volume)
|
|
88
|
+
SpatialSceneView(spatialScene: spatialScene)
|
|
71
89
|
.frame(
|
|
72
90
|
minWidth: getCGFloat(
|
|
73
|
-
|
|
91
|
+
spatialScene.sceneConfig?.resizeRange?.minWidth
|
|
74
92
|
),
|
|
75
93
|
maxWidth: getCGFloat(
|
|
76
|
-
|
|
94
|
+
spatialScene.sceneConfig?.resizeRange?.maxWidth
|
|
77
95
|
),
|
|
78
96
|
minHeight: getCGFloat(
|
|
79
|
-
|
|
97
|
+
spatialScene.sceneConfig?.resizeRange?.minHeight
|
|
80
98
|
),
|
|
81
99
|
maxHeight: getCGFloat(
|
|
82
|
-
|
|
100
|
+
spatialScene.sceneConfig?.resizeRange?.maxHeight
|
|
83
101
|
)
|
|
84
102
|
)
|
|
85
103
|
}
|
|
@@ -6,7 +6,7 @@ var pwaManager = PWAManager()
|
|
|
6
6
|
struct PWAManager: Codable {
|
|
7
7
|
var isLocal: Bool = false
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/// var start_url: String = "http://localhost:5173/#/"
|
|
10
10
|
var start_url: String = "http://localhost:5173/#/runtime-capabilities"
|
|
11
11
|
|
|
12
12
|
// var start_url: String = "http://localhost:5173/#/spatial-drag-gesture"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Combine
|
|
2
2
|
import Foundation
|
|
3
|
+
import Observation
|
|
3
4
|
import RealityKit
|
|
4
5
|
import simd
|
|
5
6
|
import SwiftUI
|
|
@@ -40,6 +41,15 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
40
41
|
var parent: (any ScrollAbleSpatialElementContainer)?
|
|
41
42
|
|
|
42
43
|
var attachmentManager = AttachmentManager()
|
|
44
|
+
/// NOTE: `@Observable` + `lazy` 在新版本 Swift 宏展开下会触发编译器报错(init accessor 访问 backing storage)。
|
|
45
|
+
/// 这里不需要让动画管理器参与 Observation,避免生成 `@ObservationTracked` 相关访问器即可。
|
|
46
|
+
@ObservationIgnored
|
|
47
|
+
lazy var animationManager: EntityAnimationManager = .init(scene: self)
|
|
48
|
+
|
|
49
|
+
@ObservationIgnored
|
|
50
|
+
lazy var elementAnimationManager: SpatializedElementAnimationManager = .init(sendWebMsg: { [weak self] id, msg in
|
|
51
|
+
self?.sendWebMsg(id, msg)
|
|
52
|
+
})
|
|
43
53
|
|
|
44
54
|
/// Enum
|
|
45
55
|
enum WindowStyle: String, Codable, CaseIterable {
|
|
@@ -331,6 +341,10 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
331
341
|
|
|
332
342
|
spatialWebViewModel.addJSBListener(UpdateAttachmentEntityCommand.self, onUpdateAttachmentEntity)
|
|
333
343
|
|
|
344
|
+
spatialWebViewModel.addJSBListener(AnimateTransformCommand.self, onAnimateTransform)
|
|
345
|
+
|
|
346
|
+
spatialWebViewModel.addJSBListener(CreateSpatializedElementAnimationCommand.self, onCreateSpatializedElementAnimation)
|
|
347
|
+
spatialWebViewModel.addJSBListener(ControlSpatializedElementAnimationCommand.self, onControlSpatializedElementAnimation)
|
|
334
348
|
spatialWebViewModel.addOpenWindowListener(protocal: "webspatial", onOpenWindowHandler)
|
|
335
349
|
|
|
336
350
|
spatialWebViewModel
|
|
@@ -361,6 +375,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
361
375
|
}
|
|
362
376
|
|
|
363
377
|
var didFailLoad = false
|
|
378
|
+
private var currentPageGeneration = 0
|
|
364
379
|
|
|
365
380
|
private func setupWebViewStateListener() {
|
|
366
381
|
spatialWebViewModel.addStateListener(.didStartLoad) {
|
|
@@ -378,6 +393,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
spatialWebViewModel.addStateListener(.didReceive) {
|
|
396
|
+
self.injectPageEpoch()
|
|
381
397
|
if let meterToPtUnscaled = self.meterToPtUnscaled,
|
|
382
398
|
let meterToPtScaled = self.meterToPtScaled
|
|
383
399
|
{
|
|
@@ -394,27 +410,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
394
410
|
|
|
395
411
|
spatialWebViewModel.addStateListener(.didFinishLoad) {
|
|
396
412
|
if self.state == .pending {
|
|
397
|
-
self.
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private func checkHookExist(_ completion: ((Bool) -> Void)? = nil) {
|
|
403
|
-
let js = """
|
|
404
|
-
(function() {
|
|
405
|
-
return typeof window.xrCurrentSceneDefaults !== 'undefined';
|
|
406
|
-
})();
|
|
407
|
-
"""
|
|
408
|
-
|
|
409
|
-
spatialWebViewModel.evaluateJS(js) { result in
|
|
410
|
-
let exists = result as? Bool ?? false
|
|
411
|
-
|
|
412
|
-
if let completion = completion {
|
|
413
|
-
completion(exists)
|
|
414
|
-
} else {
|
|
415
|
-
if !exists {
|
|
416
|
-
self.moveToState(.willVisible, defaultSceneConfig)
|
|
417
|
-
}
|
|
413
|
+
self.moveToState(.willVisible, self.sceneConfig ?? defaultSceneConfig)
|
|
418
414
|
}
|
|
419
415
|
}
|
|
420
416
|
}
|
|
@@ -425,11 +421,17 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
425
421
|
return handleWindowOpenCustom(url)
|
|
426
422
|
} else if host == "createAttachment" {
|
|
427
423
|
return handleCreateAttachment(url)
|
|
428
|
-
} else {
|
|
424
|
+
} else if host == "createSpatialized2DElement" {
|
|
425
|
+
guard shouldAcceptSpatialRequest(url, command: host) else {
|
|
426
|
+
return nil
|
|
427
|
+
}
|
|
429
428
|
let spatialized2DElement: Spatialized2DElement = createSpatializedElement(
|
|
430
429
|
.Spatialized2DElement
|
|
431
430
|
)
|
|
432
431
|
return WebViewElementInfo(id: spatialized2DElement.id, element: spatialized2DElement.getWebViewModel())
|
|
432
|
+
} else {
|
|
433
|
+
logger.warning("Unknown webspatial open-window command: \(host)")
|
|
434
|
+
return nil
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
437
|
|
|
@@ -437,6 +439,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
437
439
|
private var pendingAttachmentWebViewModels = [String: SpatialWebViewModel]()
|
|
438
440
|
|
|
439
441
|
private func handleCreateAttachment(_ url: URL) -> WebViewElementInfo? {
|
|
442
|
+
guard shouldAcceptSpatialRequest(url, command: "createAttachment") else {
|
|
443
|
+
return nil
|
|
444
|
+
}
|
|
440
445
|
// Just create a bare webview — metadata arrives via InitializeAttachment JSB
|
|
441
446
|
let id = UUID().uuidString
|
|
442
447
|
let webViewModel = SpatialWebViewModel(url: nil)
|
|
@@ -481,6 +486,12 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
481
486
|
}
|
|
482
487
|
|
|
483
488
|
private func onPageStartLoad() {
|
|
489
|
+
currentPageGeneration += 1
|
|
490
|
+
injectPageEpoch()
|
|
491
|
+
logger.debug("SpatialScene page generation advanced to \(currentPageGeneration)")
|
|
492
|
+
// Clean up all animation sessions
|
|
493
|
+
animationManager.removeAll()
|
|
494
|
+
elementAnimationManager.removeAll()
|
|
484
495
|
// destroy all SpatialObject asset
|
|
485
496
|
let spatialObjectArray = spatialObjects.map { $0.value }
|
|
486
497
|
for spatialObject in spatialObjectArray {
|
|
@@ -490,6 +501,50 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
490
501
|
backgroundMaterial = .None
|
|
491
502
|
}
|
|
492
503
|
|
|
504
|
+
private func injectPageEpoch() {
|
|
505
|
+
// Mirror the native page generation into JS so frontend-created
|
|
506
|
+
// webspatial:// requests can carry page ownership metadata.
|
|
507
|
+
let js = """
|
|
508
|
+
window.__webspatialsdk__ = window.__webspatialsdk__ || {};
|
|
509
|
+
window.__webspatialsdk__.pageEpoch = "\(currentPageGeneration)";
|
|
510
|
+
"""
|
|
511
|
+
spatialWebViewModel.getController().callJS(js)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private struct SpatialRequestMetadata {
|
|
515
|
+
let rid: String?
|
|
516
|
+
let pageEpoch: String?
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private func parseSpatialRequestMetadata(_ url: URL) -> SpatialRequestMetadata {
|
|
520
|
+
let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? []
|
|
521
|
+
let rid = items.first { $0.name == "rid" }?.value
|
|
522
|
+
let pageEpoch = items.first { $0.name == "wsepoch" }?.value
|
|
523
|
+
return SpatialRequestMetadata(rid: rid, pageEpoch: pageEpoch)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private func shouldAcceptSpatialRequest(_ url: URL, command: String) -> Bool {
|
|
527
|
+
let metadata = parseSpatialRequestMetadata(url)
|
|
528
|
+
|
|
529
|
+
guard let pageEpoch = metadata.pageEpoch, !pageEpoch.isEmpty else {
|
|
530
|
+
// Compatibility mode for older frontend bundles that do not emit
|
|
531
|
+
// request epoch yet.
|
|
532
|
+
logger.warning("SpatialScene accepts \(command) without wsepoch in compatibility mode, rid=\(metadata.rid ?? "nil")")
|
|
533
|
+
return true
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let currentEpoch = String(currentPageGeneration)
|
|
537
|
+
if pageEpoch != currentEpoch {
|
|
538
|
+
// Drop requests that were initiated by an older page generation so
|
|
539
|
+
// they cannot recreate stale 2D content after refresh.
|
|
540
|
+
logger.warning("SpatialScene drops stale \(command), rid=\(metadata.rid ?? "nil"), requestEpoch=\(pageEpoch), currentEpoch=\(currentEpoch)")
|
|
541
|
+
return false
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
logger.debug("SpatialScene accepts \(command), rid=\(metadata.rid ?? "nil"), epoch=\(pageEpoch)")
|
|
545
|
+
return true
|
|
546
|
+
}
|
|
547
|
+
|
|
493
548
|
/// Some SPA navigations (history back/forward) do not trigger a full WKNavigation
|
|
494
549
|
/// lifecycle. SpatialNavView calls this before navigation actions to ensure
|
|
495
550
|
/// previously-created spatial objects are cleaned up.
|
|
@@ -551,7 +606,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
551
606
|
spatialObject.sources = sources
|
|
552
607
|
}
|
|
553
608
|
if let loading = command.loading {
|
|
554
|
-
spatialObject.loading = loading
|
|
609
|
+
spatialObject.loading = Loading(stringValue: loading)
|
|
555
610
|
}
|
|
556
611
|
|
|
557
612
|
resolve(.success(AddSpatializedElementReply(id: spatialObject.id)))
|
|
@@ -615,7 +670,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
615
670
|
let column3 = simd_double4(array[12], array[13], array[14], array[15])
|
|
616
671
|
let simd_double4x4 = simd_double4x4(columns: (column0, column1, column2, column3))
|
|
617
672
|
let affineTransform3D = AffineTransform3D(truncating: simd_double4x4)
|
|
618
|
-
spatializedElement.
|
|
673
|
+
if !spatializedElement.animatingMask.locksTransform {
|
|
674
|
+
spatializedElement.entityTransform = affineTransform3D
|
|
675
|
+
}
|
|
619
676
|
}
|
|
620
677
|
|
|
621
678
|
if let autoplay = command.autoplay {
|
|
@@ -643,7 +700,11 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
643
700
|
}
|
|
644
701
|
|
|
645
702
|
if let loading = command.loading {
|
|
646
|
-
spatializedElement.loading = loading
|
|
703
|
+
spatializedElement.loading = Loading(stringValue: loading)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if let stagemode = command.stagemode {
|
|
707
|
+
spatializedElement.stagemode = StageMode(stringValue: stagemode)
|
|
647
708
|
}
|
|
648
709
|
|
|
649
710
|
resolve(.success(baseReplyData))
|
|
@@ -755,7 +816,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
755
816
|
}
|
|
756
817
|
|
|
757
818
|
if let opacity = command.opacity {
|
|
758
|
-
spatializedElement.
|
|
819
|
+
if !spatializedElement.animatingMask.locksOpacity {
|
|
820
|
+
spatializedElement.opacity = opacity
|
|
821
|
+
}
|
|
759
822
|
}
|
|
760
823
|
|
|
761
824
|
if let scrollWithParent = command.scrollWithParent {
|
|
@@ -821,6 +884,11 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
821
884
|
return resolve(.failure(JsbError(code: .InvalidMatrix, message: "invalid UpdateSpatializedElementTransform matrix should have length 16!")))
|
|
822
885
|
}
|
|
823
886
|
|
|
887
|
+
if spatializedElement.animatingMask.locksTransform {
|
|
888
|
+
resolve(.success(baseReplyData))
|
|
889
|
+
return
|
|
890
|
+
}
|
|
891
|
+
|
|
824
892
|
let column0 = simd_double4(array[0], array[1], array[2], array[3])
|
|
825
893
|
let column1 = simd_double4(array[4], array[5], array[6], array[7])
|
|
826
894
|
let column2 = simd_double4(array[8], array[9], array[10], array[11])
|
|
@@ -859,17 +927,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
859
927
|
children.removeValue(forKey: spatializedElement.id)
|
|
860
928
|
}
|
|
861
929
|
|
|
862
|
-
func
|
|
863
|
-
return children.
|
|
864
|
-
switch type {
|
|
865
|
-
case .Spatialized2DElement:
|
|
866
|
-
return $0.value is Spatialized2DElement
|
|
867
|
-
case .SpatializedStatic3DElement:
|
|
868
|
-
return $0.value is SpatializedStatic3DElement
|
|
869
|
-
case .SpatializedDynamic3DElement:
|
|
870
|
-
return $0.value is SpatializedDynamic3DElement
|
|
871
|
-
}
|
|
872
|
-
}
|
|
930
|
+
func getChildren<T: SpatializedElement>(ofType type: T.Type) -> [T] {
|
|
931
|
+
return children.values.compactMap { $0 as? T }
|
|
873
932
|
}
|
|
874
933
|
|
|
875
934
|
func getChildren() -> [String: SpatializedElement] {
|
|
@@ -1289,6 +1348,76 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1289
1348
|
}
|
|
1290
1349
|
}
|
|
1291
1350
|
|
|
1351
|
+
private func onAnimateTransform(command: AnimateTransformCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1352
|
+
switch command.type {
|
|
1353
|
+
case "play":
|
|
1354
|
+
guard let entityId = command.entityId else {
|
|
1355
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "AnimateTransform play: entityId is required")))
|
|
1356
|
+
return
|
|
1357
|
+
}
|
|
1358
|
+
guard let entity: SpatialEntity = findSpatialObject(entityId) else {
|
|
1359
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "AnimateTransform play: entity \(entityId) not found")))
|
|
1360
|
+
return
|
|
1361
|
+
}
|
|
1362
|
+
animationManager.handlePlay(command: command, entity: entity, resolve: resolve)
|
|
1363
|
+
|
|
1364
|
+
case "pause":
|
|
1365
|
+
animationManager.handlePause(command: command, resolve: resolve)
|
|
1366
|
+
|
|
1367
|
+
case "resume":
|
|
1368
|
+
guard let session = animationManager.getSession(command.animationId),
|
|
1369
|
+
let entity: SpatialEntity = findSpatialObject(session.entityId)
|
|
1370
|
+
else {
|
|
1371
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "AnimateTransform resume: session or entity not found")))
|
|
1372
|
+
return
|
|
1373
|
+
}
|
|
1374
|
+
animationManager.handleResume(command: command, entity: entity, resolve: resolve)
|
|
1375
|
+
|
|
1376
|
+
case "cancel":
|
|
1377
|
+
guard let session = animationManager.getSession(command.animationId),
|
|
1378
|
+
let entity: SpatialEntity = findSpatialObject(session.entityId)
|
|
1379
|
+
else {
|
|
1380
|
+
// Session may have already been cleaned up - acknowledge silently.
|
|
1381
|
+
resolve(.success(nil))
|
|
1382
|
+
return
|
|
1383
|
+
}
|
|
1384
|
+
animationManager.handleCancel(command: command, entity: entity, resolve: resolve)
|
|
1385
|
+
|
|
1386
|
+
default:
|
|
1387
|
+
resolve(.failure(JsbError(code: .TypeError, message: "AnimateTransform: unknown command type '\(command.type)'")))
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
private func onCreateSpatializedElementAnimation(command: CreateSpatializedElementAnimationCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1392
|
+
guard let element: SpatializedElement = findSpatialObject(command.elementId) else {
|
|
1393
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "CreateSpatializedElementAnimation play: element \(command.elementId) not found")))
|
|
1394
|
+
return
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
do {
|
|
1398
|
+
let animation = try elementAnimationManager.createAnimation(command: command, target: element)
|
|
1399
|
+
addSpatialObject(animation)
|
|
1400
|
+
resolve(.success(AddSpatializedElementReply(id: animation.id)))
|
|
1401
|
+
} catch let SpatializedElementAnimationManagerError.invalidTarget(reason) {
|
|
1402
|
+
resolve(.failure(JsbError(code: .CommandError, message: reason)))
|
|
1403
|
+
} catch {
|
|
1404
|
+
resolve(.failure(JsbError(code: .CommandError, message: error.localizedDescription)))
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
private func onControlSpatializedElementAnimation(command: ControlSpatializedElementAnimationCommand, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1409
|
+
do {
|
|
1410
|
+
try elementAnimationManager.controlAnimation(command)
|
|
1411
|
+
resolve(.success(nil))
|
|
1412
|
+
} catch let SpatializedElementAnimationManagerError.animationNotFound(animationId) {
|
|
1413
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Animation \(animationId) not found")))
|
|
1414
|
+
} catch let SpatializedElementAnimationManagerError.invalidTarget(reason) {
|
|
1415
|
+
resolve(.failure(JsbError(code: .CommandError, message: reason)))
|
|
1416
|
+
} catch {
|
|
1417
|
+
resolve(.failure(JsbError(code: .CommandError, message: error.localizedDescription)))
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1292
1421
|
private func onUpdateUnlitMaterialProperties(command: UpdateUnlitMaterialProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1293
1422
|
guard let material = spatialObjects[command.id] as? SpatialUnlitMaterial else {
|
|
1294
1423
|
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Material \(command.id) not found")))
|
|
@@ -1359,6 +1488,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1359
1488
|
event: SpatialObject.Events.BeforeDestroyed.rawValue,
|
|
1360
1489
|
listener: onSptatialObjectDestroyed
|
|
1361
1490
|
)
|
|
1491
|
+
if let element = spatialObject as? SpatializedElement {
|
|
1492
|
+
elementAnimationManager.destroyAnimationsForElement(element.spatialId)
|
|
1493
|
+
}
|
|
1362
1494
|
spatialObjects.removeValue(forKey: spatialObject.spatialId)
|
|
1363
1495
|
|
|
1364
1496
|
// notify web side, spatialObject is destroyed
|
|
@@ -1393,6 +1525,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1393
1525
|
*/
|
|
1394
1526
|
|
|
1395
1527
|
override func onDestroy() {
|
|
1528
|
+
animationManager.removeAll()
|
|
1529
|
+
elementAnimationManager.removeAll()
|
|
1396
1530
|
let spatialObjectArray = spatialObjects.map { $0.value }
|
|
1397
1531
|
for spatialObject in spatialObjectArray {
|
|
1398
1532
|
spatialObject.destroy()
|
|
@@ -1402,7 +1536,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1402
1536
|
}
|
|
1403
1537
|
|
|
1404
1538
|
enum CodingKeys: String, CodingKey {
|
|
1405
|
-
case children, url, backgroundMaterial, cornerRadius, scrollOffset, webviewIsOpaque, spatialObjectCount, spatialObjectRefCount, spatialObjectList
|
|
1539
|
+
case children, url, backgroundMaterial, cornerRadius, scrollOffset, currentPageGeneration, childrenIds, sceneSpatialObjectIds, webviewIsOpaque, spatialObjectCount, spatialObjectRefCount, spatialObjectList
|
|
1406
1540
|
}
|
|
1407
1541
|
|
|
1408
1542
|
override func encode(to encoder: Encoder) throws {
|
|
@@ -1413,8 +1547,13 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1413
1547
|
try container.encode(cornerRadius, forKey: .cornerRadius)
|
|
1414
1548
|
try container.encode(scrollOffset, forKey: .scrollOffset)
|
|
1415
1549
|
try container.encode(children, forKey: .children)
|
|
1550
|
+
try container.encode(currentPageGeneration, forKey: .currentPageGeneration)
|
|
1416
1551
|
|
|
1417
1552
|
// for debug only
|
|
1553
|
+
let childrenIds = children.map { $0.key }
|
|
1554
|
+
try container.encode(childrenIds, forKey: .childrenIds)
|
|
1555
|
+
let sceneSpatialObjectIds = spatialObjects.map { $0.key }
|
|
1556
|
+
try container.encode(sceneSpatialObjectIds, forKey: .sceneSpatialObjectIds)
|
|
1418
1557
|
try container.encode(spatialWebViewModel.getController().webview?.isOpaque, forKey: .webviewIsOpaque)
|
|
1419
1558
|
try container.encode(SpatialObject.objects.count, forKey: .spatialObjectCount)
|
|
1420
1559
|
|
|
@@ -1425,4 +1564,4 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1425
1564
|
|
|
1426
1565
|
try container.encode(SpatialObjectWeakRefManager.weakRefObjects.count, forKey: .spatialObjectRefCount)
|
|
1427
1566
|
}
|
|
1428
|
-
}
|
|
1567
|
+
}
|
|
@@ -74,17 +74,8 @@ class Spatialized2DElement: SpatializedElement, ScrollAbleSpatialElementContaine
|
|
|
74
74
|
return children
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
func
|
|
78
|
-
return children.
|
|
79
|
-
switch type {
|
|
80
|
-
case .Spatialized2DElement:
|
|
81
|
-
return $0.value is Spatialized2DElement
|
|
82
|
-
case .SpatializedStatic3DElement:
|
|
83
|
-
return $0.value is SpatializedStatic3DElement
|
|
84
|
-
case .SpatializedDynamic3DElement:
|
|
85
|
-
return $0.value is SpatializedDynamic3DElement
|
|
86
|
-
}
|
|
87
|
-
}
|
|
77
|
+
func getChildren<T: SpatializedElement>(ofType type: T.Type) -> [T] {
|
|
78
|
+
return children.values.compactMap { $0 as? T }
|
|
88
79
|
}
|
|
89
80
|
|
|
90
81
|
func loadHtml(_ html: String) {
|
|
@@ -5,6 +5,43 @@ import SwiftUI
|
|
|
5
5
|
/// zIndex() have some bug, so use zOrderBias to simulate zIndex effect
|
|
6
6
|
let zOrderBias = 0.001
|
|
7
7
|
|
|
8
|
+
struct SpatializedElementAnimatingMask {
|
|
9
|
+
var transformAnimationId: String?
|
|
10
|
+
var opacityAnimationId: String?
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
extension SpatializedElementAnimatingMask {
|
|
14
|
+
mutating func acquire(transform animationId: String?) {
|
|
15
|
+
transformAnimationId = animationId
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
mutating func acquire(opacity animationId: String?) {
|
|
19
|
+
opacityAnimationId = animationId
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
mutating func release(animationId: String) {
|
|
23
|
+
if transformAnimationId == animationId {
|
|
24
|
+
transformAnimationId = nil
|
|
25
|
+
}
|
|
26
|
+
if opacityAnimationId == animationId {
|
|
27
|
+
opacityAnimationId = nil
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
mutating func clear() {
|
|
32
|
+
transformAnimationId = nil
|
|
33
|
+
opacityAnimationId = nil
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var locksTransform: Bool {
|
|
37
|
+
transformAnimationId != nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var locksOpacity: Bool {
|
|
41
|
+
opacityAnimationId != nil
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
enum SpatializedElementType: String, Codable {
|
|
9
46
|
case Spatialized2DElement
|
|
10
47
|
case SpatializedStatic3DElement
|
|
@@ -25,6 +62,7 @@ class SpatializedElement: SpatialObject {
|
|
|
25
62
|
var visible = true
|
|
26
63
|
var scrollWithParent = true
|
|
27
64
|
var zIndex: Double = 0
|
|
65
|
+
var animatingMask = SpatializedElementAnimatingMask()
|
|
28
66
|
|
|
29
67
|
var enableDragStartGesture: Bool = false
|
|
30
68
|
var enableDragGesture: Bool = false
|
|
@@ -6,11 +6,29 @@ struct ModelSource: Codable, Equatable {
|
|
|
6
6
|
let type: String?
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
enum Loading: String {
|
|
10
|
+
case eager
|
|
11
|
+
case lazy
|
|
12
|
+
|
|
13
|
+
init(stringValue value: String) {
|
|
14
|
+
self = Loading(rawValue: value) ?? .eager
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
enum StageMode: String {
|
|
19
|
+
case none
|
|
20
|
+
case orbit
|
|
21
|
+
|
|
22
|
+
init(stringValue value: String) {
|
|
23
|
+
self = StageMode(rawValue: value) ?? .none
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
@Observable
|
|
10
28
|
class SpatializedStatic3DElement: SpatializedElement {
|
|
11
29
|
var modelURL: String?
|
|
12
30
|
var sources: [ModelSource] = []
|
|
13
|
-
var
|
|
31
|
+
var entityTransform: AffineTransform3D = .identity
|
|
14
32
|
var autoplay: Bool = false
|
|
15
33
|
var loop: Bool = false
|
|
16
34
|
var animationPaused: Bool = true
|
|
@@ -19,14 +37,15 @@ class SpatializedStatic3DElement: SpatializedElement {
|
|
|
19
37
|
/// `SpatializedStatic3DView`, which clears it back to `nil`.
|
|
20
38
|
var pendingSeekTime: Double?
|
|
21
39
|
var posterURL: String?
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
var loading: String = "eager"
|
|
40
|
+
var loading: Loading = .eager
|
|
41
|
+
var stagemode: StageMode = .none
|
|
25
42
|
var allSources: [ModelSource] {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
if let modelURL { [ModelSource(src: modelURL, type: nil)] + sources }
|
|
44
|
+
else { sources }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override var enableGesture: Bool {
|
|
48
|
+
stagemode == .orbit || super.enableGesture
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
enum CodingKeys: String, CodingKey {
|