@webspatial/platform-visionos 1.6.1 → 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 +2 -2
- package/web-spatial/JSBCommand.swift +12 -0
- package/web-spatial/WebMsgCommand.swift +29 -2
- package/web-spatial/WebSpatialApp.swift +26 -8
- package/web-spatial/manager/Dynamic3DManager.swift +97 -37
- package/web-spatial/manifest.swift +2 -1
- package/web-spatial/model/MaterialSceneRefresh.swift +41 -0
- package/web-spatial/model/SpatialScene.swift +255 -45
- package/web-spatial/model/Spatialized2DElement.swift +2 -11
- package/web-spatial/model/SpatializedElement.swift +38 -0
- package/web-spatial/model/SpatializedStatic3DElement.swift +31 -4
- package/web-spatial/model/dynamic3d/EntityAnimationManager.swift +356 -0
- package/web-spatial/model/dynamic3d/EntityAnimationSession.swift +162 -0
- package/web-spatial/model/dynamic3d/SpatialMaterial.swift +26 -10
- package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +5 -3
- package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +25 -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 +104 -43
- 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 +30 -17
- 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
|
@@ -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 {
|
|
@@ -307,6 +317,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
307
317
|
spatialWebViewModel.addJSBListener(CreateSpatialEntity.self, onCreateEntity)
|
|
308
318
|
spatialWebViewModel.addJSBListener(CreateGeometryProperties.self, onCreateGeometry)
|
|
309
319
|
spatialWebViewModel.addJSBListener(CreateUnlitMaterial.self, onCreateUnlitMaterial)
|
|
320
|
+
spatialWebViewModel.addJSBListener(CreateTexture.self, onCreateTexture)
|
|
321
|
+
spatialWebViewModel.addJSBListener(UpdateTextureProperties.self, onUpdateTextureProperties)
|
|
310
322
|
spatialWebViewModel.addJSBListener(CreateModelComponent.self, onCreateModelComponent)
|
|
311
323
|
spatialWebViewModel.addJSBListener(AddComponentToEntity.self, onAddComponentToEntity)
|
|
312
324
|
spatialWebViewModel.addJSBListener(AddEntityToDynamic3D.self, onAddEntityToDynamic3D)
|
|
@@ -329,6 +341,10 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
329
341
|
|
|
330
342
|
spatialWebViewModel.addJSBListener(UpdateAttachmentEntityCommand.self, onUpdateAttachmentEntity)
|
|
331
343
|
|
|
344
|
+
spatialWebViewModel.addJSBListener(AnimateTransformCommand.self, onAnimateTransform)
|
|
345
|
+
|
|
346
|
+
spatialWebViewModel.addJSBListener(CreateSpatializedElementAnimationCommand.self, onCreateSpatializedElementAnimation)
|
|
347
|
+
spatialWebViewModel.addJSBListener(ControlSpatializedElementAnimationCommand.self, onControlSpatializedElementAnimation)
|
|
332
348
|
spatialWebViewModel.addOpenWindowListener(protocal: "webspatial", onOpenWindowHandler)
|
|
333
349
|
|
|
334
350
|
spatialWebViewModel
|
|
@@ -359,6 +375,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
359
375
|
}
|
|
360
376
|
|
|
361
377
|
var didFailLoad = false
|
|
378
|
+
private var currentPageGeneration = 0
|
|
362
379
|
|
|
363
380
|
private func setupWebViewStateListener() {
|
|
364
381
|
spatialWebViewModel.addStateListener(.didStartLoad) {
|
|
@@ -376,6 +393,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
376
393
|
}
|
|
377
394
|
|
|
378
395
|
spatialWebViewModel.addStateListener(.didReceive) {
|
|
396
|
+
self.injectPageEpoch()
|
|
379
397
|
if let meterToPtUnscaled = self.meterToPtUnscaled,
|
|
380
398
|
let meterToPtScaled = self.meterToPtScaled
|
|
381
399
|
{
|
|
@@ -392,27 +410,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
392
410
|
|
|
393
411
|
spatialWebViewModel.addStateListener(.didFinishLoad) {
|
|
394
412
|
if self.state == .pending {
|
|
395
|
-
self.
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
private func checkHookExist(_ completion: ((Bool) -> Void)? = nil) {
|
|
401
|
-
let js = """
|
|
402
|
-
(function() {
|
|
403
|
-
return typeof window.xrCurrentSceneDefaults !== 'undefined';
|
|
404
|
-
})();
|
|
405
|
-
"""
|
|
406
|
-
|
|
407
|
-
spatialWebViewModel.evaluateJS(js) { result in
|
|
408
|
-
let exists = result as? Bool ?? false
|
|
409
|
-
|
|
410
|
-
if let completion = completion {
|
|
411
|
-
completion(exists)
|
|
412
|
-
} else {
|
|
413
|
-
if !exists {
|
|
414
|
-
self.moveToState(.willVisible, defaultSceneConfig)
|
|
415
|
-
}
|
|
413
|
+
self.moveToState(.willVisible, self.sceneConfig ?? defaultSceneConfig)
|
|
416
414
|
}
|
|
417
415
|
}
|
|
418
416
|
}
|
|
@@ -423,11 +421,17 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
423
421
|
return handleWindowOpenCustom(url)
|
|
424
422
|
} else if host == "createAttachment" {
|
|
425
423
|
return handleCreateAttachment(url)
|
|
426
|
-
} else {
|
|
424
|
+
} else if host == "createSpatialized2DElement" {
|
|
425
|
+
guard shouldAcceptSpatialRequest(url, command: host) else {
|
|
426
|
+
return nil
|
|
427
|
+
}
|
|
427
428
|
let spatialized2DElement: Spatialized2DElement = createSpatializedElement(
|
|
428
429
|
.Spatialized2DElement
|
|
429
430
|
)
|
|
430
431
|
return WebViewElementInfo(id: spatialized2DElement.id, element: spatialized2DElement.getWebViewModel())
|
|
432
|
+
} else {
|
|
433
|
+
logger.warning("Unknown webspatial open-window command: \(host)")
|
|
434
|
+
return nil
|
|
431
435
|
}
|
|
432
436
|
}
|
|
433
437
|
|
|
@@ -435,6 +439,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
435
439
|
private var pendingAttachmentWebViewModels = [String: SpatialWebViewModel]()
|
|
436
440
|
|
|
437
441
|
private func handleCreateAttachment(_ url: URL) -> WebViewElementInfo? {
|
|
442
|
+
guard shouldAcceptSpatialRequest(url, command: "createAttachment") else {
|
|
443
|
+
return nil
|
|
444
|
+
}
|
|
438
445
|
// Just create a bare webview — metadata arrives via InitializeAttachment JSB
|
|
439
446
|
let id = UUID().uuidString
|
|
440
447
|
let webViewModel = SpatialWebViewModel(url: nil)
|
|
@@ -479,6 +486,12 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
479
486
|
}
|
|
480
487
|
|
|
481
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()
|
|
482
495
|
// destroy all SpatialObject asset
|
|
483
496
|
let spatialObjectArray = spatialObjects.map { $0.value }
|
|
484
497
|
for spatialObject in spatialObjectArray {
|
|
@@ -488,6 +501,50 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
488
501
|
backgroundMaterial = .None
|
|
489
502
|
}
|
|
490
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
|
+
|
|
491
548
|
/// Some SPA navigations (history back/forward) do not trigger a full WKNavigation
|
|
492
549
|
/// lifecycle. SpatialNavView calls this before navigation actions to ensure
|
|
493
550
|
/// previously-created spatial objects are cleaned up.
|
|
@@ -548,6 +605,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
548
605
|
if let sources = command.sources {
|
|
549
606
|
spatialObject.sources = sources
|
|
550
607
|
}
|
|
608
|
+
if let loading = command.loading {
|
|
609
|
+
spatialObject.loading = Loading(stringValue: loading)
|
|
610
|
+
}
|
|
551
611
|
|
|
552
612
|
resolve(.success(AddSpatializedElementReply(id: spatialObject.id)))
|
|
553
613
|
}
|
|
@@ -610,7 +670,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
610
670
|
let column3 = simd_double4(array[12], array[13], array[14], array[15])
|
|
611
671
|
let simd_double4x4 = simd_double4x4(columns: (column0, column1, column2, column3))
|
|
612
672
|
let affineTransform3D = AffineTransform3D(truncating: simd_double4x4)
|
|
613
|
-
spatializedElement.
|
|
673
|
+
if !spatializedElement.animatingMask.locksTransform {
|
|
674
|
+
spatializedElement.entityTransform = affineTransform3D
|
|
675
|
+
}
|
|
614
676
|
}
|
|
615
677
|
|
|
616
678
|
if let autoplay = command.autoplay {
|
|
@@ -629,6 +691,22 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
629
691
|
spatializedElement.playbackRate = playbackRate
|
|
630
692
|
}
|
|
631
693
|
|
|
694
|
+
if let currentTime = command.currentTime {
|
|
695
|
+
spatializedElement.pendingSeekTime = currentTime
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if let posterURL = command.posterURL {
|
|
699
|
+
spatializedElement.posterURL = posterURL.isEmpty ? nil : posterURL
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if let loading = command.loading {
|
|
703
|
+
spatializedElement.loading = Loading(stringValue: loading)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if let stagemode = command.stagemode {
|
|
707
|
+
spatializedElement.stagemode = StageMode(stringValue: stagemode)
|
|
708
|
+
}
|
|
709
|
+
|
|
632
710
|
resolve(.success(baseReplyData))
|
|
633
711
|
}
|
|
634
712
|
|
|
@@ -738,7 +816,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
738
816
|
}
|
|
739
817
|
|
|
740
818
|
if let opacity = command.opacity {
|
|
741
|
-
spatializedElement.
|
|
819
|
+
if !spatializedElement.animatingMask.locksOpacity {
|
|
820
|
+
spatializedElement.opacity = opacity
|
|
821
|
+
}
|
|
742
822
|
}
|
|
743
823
|
|
|
744
824
|
if let scrollWithParent = command.scrollWithParent {
|
|
@@ -804,6 +884,11 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
804
884
|
return resolve(.failure(JsbError(code: .InvalidMatrix, message: "invalid UpdateSpatializedElementTransform matrix should have length 16!")))
|
|
805
885
|
}
|
|
806
886
|
|
|
887
|
+
if spatializedElement.animatingMask.locksTransform {
|
|
888
|
+
resolve(.success(baseReplyData))
|
|
889
|
+
return
|
|
890
|
+
}
|
|
891
|
+
|
|
807
892
|
let column0 = simd_double4(array[0], array[1], array[2], array[3])
|
|
808
893
|
let column1 = simd_double4(array[4], array[5], array[6], array[7])
|
|
809
894
|
let column2 = simd_double4(array[8], array[9], array[10], array[11])
|
|
@@ -842,17 +927,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
842
927
|
children.removeValue(forKey: spatializedElement.id)
|
|
843
928
|
}
|
|
844
929
|
|
|
845
|
-
func
|
|
846
|
-
return children.
|
|
847
|
-
switch type {
|
|
848
|
-
case .Spatialized2DElement:
|
|
849
|
-
return $0.value is Spatialized2DElement
|
|
850
|
-
case .SpatializedStatic3DElement:
|
|
851
|
-
return $0.value is SpatializedStatic3DElement
|
|
852
|
-
case .SpatializedDynamic3DElement:
|
|
853
|
-
return $0.value is SpatializedDynamic3DElement
|
|
854
|
-
}
|
|
855
|
-
}
|
|
930
|
+
func getChildren<T: SpatializedElement>(ofType type: T.Type) -> [T] {
|
|
931
|
+
return children.values.compactMap { $0 as? T }
|
|
856
932
|
}
|
|
857
933
|
|
|
858
934
|
func getChildren() -> [String: SpatializedElement] {
|
|
@@ -964,8 +1040,28 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
964
1040
|
// @fukang: add Component here
|
|
965
1041
|
}
|
|
966
1042
|
|
|
1043
|
+
private func onCreateTexture(command: CreateTexture, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1044
|
+
let texture = SpatialTextureResource(command.url)
|
|
1045
|
+
addSpatialObject(texture)
|
|
1046
|
+
Task {
|
|
1047
|
+
do {
|
|
1048
|
+
try await texture.load()
|
|
1049
|
+
resolve(.success(AddSpatializedElementReply(id: texture.id)))
|
|
1050
|
+
} catch {
|
|
1051
|
+
texture.destroy()
|
|
1052
|
+
resolve(.failure(JsbError(code: .CommandError, message: "Failed to load texture from \(command.url): \(error.localizedDescription)")))
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
967
1057
|
private func onCreateUnlitMaterial(command: CreateUnlitMaterial, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
968
|
-
|
|
1058
|
+
var tex: TextureResource? = nil
|
|
1059
|
+
if let textureId = command.textureId,
|
|
1060
|
+
let texObj = spatialObjects[textureId] as? SpatialTextureResource
|
|
1061
|
+
{
|
|
1062
|
+
tex = texObj.resource
|
|
1063
|
+
}
|
|
1064
|
+
let material = Dynamic3DManager.createUnlitMaterial(command, tex)
|
|
969
1065
|
addSpatialObject(material)
|
|
970
1066
|
resolve(.success(AddSpatializedElementReply(id: material.id)))
|
|
971
1067
|
}
|
|
@@ -1225,20 +1321,124 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1225
1321
|
resolve(.success(baseReplyData))
|
|
1226
1322
|
}
|
|
1227
1323
|
|
|
1324
|
+
private func onUpdateTextureProperties(command: UpdateTextureProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1325
|
+
guard let texture = spatialObjects[command.id] as? SpatialTextureResource else {
|
|
1326
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Texture \(command.id) not found")))
|
|
1327
|
+
return
|
|
1328
|
+
}
|
|
1329
|
+
guard let newURL = command.url else {
|
|
1330
|
+
resolve(.success(baseReplyData))
|
|
1331
|
+
return
|
|
1332
|
+
}
|
|
1333
|
+
Task {
|
|
1334
|
+
do {
|
|
1335
|
+
try await texture.updateURL(newURL)
|
|
1336
|
+
let refreshedMaterialIds = MaterialSceneRefresh.pushReloadedTextureToBoundUnlitMaterials(
|
|
1337
|
+
texture: texture,
|
|
1338
|
+
textureSpatialId: command.id,
|
|
1339
|
+
spatialObjects: spatialObjects
|
|
1340
|
+
)
|
|
1341
|
+
for mid in refreshedMaterialIds {
|
|
1342
|
+
MaterialSceneRefresh.refreshComponentsUsingMaterial(mid, spatialObjects: spatialObjects)
|
|
1343
|
+
}
|
|
1344
|
+
resolve(.success(baseReplyData))
|
|
1345
|
+
} catch {
|
|
1346
|
+
resolve(.failure(JsbError(code: .CommandError, message: error.localizedDescription)))
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
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
|
+
|
|
1228
1421
|
private func onUpdateUnlitMaterialProperties(command: UpdateUnlitMaterialProperties, resolve: @escaping JSBManager.ResolveHandler<Encodable>) {
|
|
1229
1422
|
guard let material = spatialObjects[command.id] as? SpatialUnlitMaterial else {
|
|
1230
1423
|
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Material \(command.id) not found")))
|
|
1231
1424
|
return
|
|
1232
1425
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1426
|
+
var texture: TextureResource?? = nil
|
|
1427
|
+
if let textureId = command.textureId {
|
|
1428
|
+
if textureId.isEmpty {
|
|
1429
|
+
texture = .some(nil)
|
|
1430
|
+
} else if let texObj = spatialObjects[textureId] as? SpatialTextureResource {
|
|
1431
|
+
texture = .some(texObj.resource)
|
|
1432
|
+
} else {
|
|
1433
|
+
resolve(.failure(JsbError(code: .InvalidSpatialObject, message: "Texture \(textureId) not found")))
|
|
1434
|
+
return
|
|
1240
1435
|
}
|
|
1241
1436
|
}
|
|
1437
|
+
material.updateProperties(color: command.color, texture: texture, transparent: command.transparent, opacity: command.opacity)
|
|
1438
|
+
if let tid = command.textureId {
|
|
1439
|
+
material.textureSpatialId = tid.isEmpty ? nil : tid
|
|
1440
|
+
}
|
|
1441
|
+
MaterialSceneRefresh.refreshComponentsUsingMaterial(command.id, spatialObjects: spatialObjects)
|
|
1242
1442
|
resolve(.success(baseReplyData))
|
|
1243
1443
|
}
|
|
1244
1444
|
|
|
@@ -1288,6 +1488,9 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1288
1488
|
event: SpatialObject.Events.BeforeDestroyed.rawValue,
|
|
1289
1489
|
listener: onSptatialObjectDestroyed
|
|
1290
1490
|
)
|
|
1491
|
+
if let element = spatialObject as? SpatializedElement {
|
|
1492
|
+
elementAnimationManager.destroyAnimationsForElement(element.spatialId)
|
|
1493
|
+
}
|
|
1291
1494
|
spatialObjects.removeValue(forKey: spatialObject.spatialId)
|
|
1292
1495
|
|
|
1293
1496
|
// notify web side, spatialObject is destroyed
|
|
@@ -1322,6 +1525,8 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1322
1525
|
*/
|
|
1323
1526
|
|
|
1324
1527
|
override func onDestroy() {
|
|
1528
|
+
animationManager.removeAll()
|
|
1529
|
+
elementAnimationManager.removeAll()
|
|
1325
1530
|
let spatialObjectArray = spatialObjects.map { $0.value }
|
|
1326
1531
|
for spatialObject in spatialObjectArray {
|
|
1327
1532
|
spatialObject.destroy()
|
|
@@ -1331,7 +1536,7 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1331
1536
|
}
|
|
1332
1537
|
|
|
1333
1538
|
enum CodingKeys: String, CodingKey {
|
|
1334
|
-
case children, url, backgroundMaterial, cornerRadius, scrollOffset, webviewIsOpaque, spatialObjectCount, spatialObjectRefCount, spatialObjectList
|
|
1539
|
+
case children, url, backgroundMaterial, cornerRadius, scrollOffset, currentPageGeneration, childrenIds, sceneSpatialObjectIds, webviewIsOpaque, spatialObjectCount, spatialObjectRefCount, spatialObjectList
|
|
1335
1540
|
}
|
|
1336
1541
|
|
|
1337
1542
|
override func encode(to encoder: Encoder) throws {
|
|
@@ -1342,8 +1547,13 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1342
1547
|
try container.encode(cornerRadius, forKey: .cornerRadius)
|
|
1343
1548
|
try container.encode(scrollOffset, forKey: .scrollOffset)
|
|
1344
1549
|
try container.encode(children, forKey: .children)
|
|
1550
|
+
try container.encode(currentPageGeneration, forKey: .currentPageGeneration)
|
|
1345
1551
|
|
|
1346
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)
|
|
1347
1557
|
try container.encode(spatialWebViewModel.getController().webview?.isOpaque, forKey: .webviewIsOpaque)
|
|
1348
1558
|
try container.encode(SpatialObject.objects.count, forKey: .spatialObjectCount)
|
|
1349
1559
|
|
|
@@ -1354,4 +1564,4 @@ class SpatialScene: SpatialObject, ScrollAbleSpatialElementContainer, WebMsgSend
|
|
|
1354
1564
|
|
|
1355
1565
|
try container.encode(SpatialObjectWeakRefManager.weakRefObjects.count, forKey: .spatialObjectRefCount)
|
|
1356
1566
|
}
|
|
1357
|
-
}
|
|
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,19 +6,46 @@ 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
|
|
17
35
|
var playbackRate: Double = 1.0
|
|
36
|
+
/// Requested seek position in seconds. Setting it triggers a seek in
|
|
37
|
+
/// `SpatializedStatic3DView`, which clears it back to `nil`.
|
|
38
|
+
var pendingSeekTime: Double?
|
|
39
|
+
var posterURL: String?
|
|
40
|
+
var loading: Loading = .eager
|
|
41
|
+
var stagemode: StageMode = .none
|
|
18
42
|
var allSources: [ModelSource] {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
enum CodingKeys: String, CodingKey {
|