@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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/web-spatial/JSBCommand.swift +1 -0
  3. package/web-spatial/WebMsgCommand.swift +26 -2
  4. package/web-spatial/WebSpatialApp.swift +26 -8
  5. package/web-spatial/manifest.swift +1 -1
  6. package/web-spatial/model/SpatialScene.swift +178 -39
  7. package/web-spatial/model/Spatialized2DElement.swift +2 -11
  8. package/web-spatial/model/SpatializedElement.swift +38 -0
  9. package/web-spatial/model/SpatializedStatic3DElement.swift +27 -8
  10. package/web-spatial/model/dynamic3d/EntityAnimationManager.swift +356 -0
  11. package/web-spatial/model/dynamic3d/EntityAnimationSession.swift +162 -0
  12. package/web-spatial/model/element-motion/SpatializedElementAnimationCommand.swift +15 -0
  13. package/web-spatial/model/element-motion/SpatializedElementAnimationManager.swift +263 -0
  14. package/web-spatial/model/element-motion/SpatializedElementAnimationObject.swift +399 -0
  15. package/web-spatial/model/element-motion/SpatializedElementAnimationTypes.swift +45 -0
  16. package/web-spatial/model/element-motion/SpatializedElementAnimationWriteAdapter.swift +65 -0
  17. package/web-spatial/model/element-motion/SpatializedElementMotionBridgeTypes.swift +43 -0
  18. package/web-spatial/model/element-motion/SpatializedElementMotionTimelineSampler.swift +99 -0
  19. package/web-spatial/model/element-motion/SpatializedElementMotionTiming.swift +99 -0
  20. package/web-spatial/model/element-motion/SpatializedElementMotionTransformTypes.swift +22 -0
  21. package/web-spatial/protocol/SpatializedElementContainer.swift +1 -1
  22. package/web-spatial/view/SpatialNavView.swift +37 -12
  23. package/web-spatial/view/SpatialSceneContentView.swift +9 -12
  24. package/web-spatial/view/Spatialized2DElementView.swift +10 -17
  25. package/web-spatial/view/SpatializedDynamic3DView.swift +2 -6
  26. package/web-spatial/view/SpatializedElementView.swift +3 -2
  27. package/web-spatial/view/SpatializedStatic3DView.swift +32 -33
  28. package/web-spatial/view/view-modifier/OrbitModifier.swift +84 -0
  29. package/web-spatial/webview/SpatialWebController.swift +26 -18
  30. package/web-spatial/webview/SpatialWebViewModel.swift +12 -7
  31. package/web-spatial.xcodeproj/project.pbxproj +29 -8
  32. package/web-spatial.xcodeproj/xcshareddata/xcschemes/web-spatial.xcscheme +1 -1
  33. package/web-spatialTests/NavigationCleanupTests.swift +2 -1
  34. package/web-spatialTests/SpatializedElementAnimationManagerTests.swift +1466 -0
  35. package/web-spatialTests/SpatializedElementMotionTimelineSamplerTests.swift +117 -0
@@ -2,31 +2,31 @@ import RealityKit
2
2
  import SwiftUI
3
3
 
4
4
  struct SpatializedStatic3DView: View {
5
- @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
5
+ @Bindable var spatializedStatic3DElement: SpatializedStatic3DElement
6
6
  @Environment(SpatialScene.self) var spatialScene: SpatialScene
7
7
 
8
8
  @State private var loadState: LoadState = .idle
9
9
 
10
- private var spatializedStatic3DElement: SpatializedStatic3DElement {
11
- return spatializedElement as! SpatializedStatic3DElement
12
- }
13
-
14
10
  private var asset: Model3DAsset? {
15
11
  if case let .loaded(asset, _) = loadState { return asset }
16
12
  return nil
17
13
  }
18
14
 
19
15
  func onLoadSuccess(src: String) {
20
- spatialScene.sendWebMsg(spatializedElement.id, ModelLoadSuccess(src: src))
16
+ spatialScene.sendWebMsg(spatializedStatic3DElement.id, ModelLoadSuccess(src: src))
21
17
  }
22
18
 
23
19
  func onLoadFailure() {
24
- spatialScene.sendWebMsg(spatializedElement.id, ModelLoadFailure())
20
+ spatialScene.sendWebMsg(spatializedStatic3DElement.id, ModelLoadFailure())
21
+ }
22
+
23
+ private func onOrbit(_ transform: AffineTransform3D) {
24
+ spatialScene.sendWebMsg(spatializedStatic3DElement.id, EntityTransformChangeEvent(transform))
25
25
  }
26
26
 
27
27
  var body: some View {
28
- let depth = spatializedElement.depth
29
- let transform = spatializedStatic3DElement.modelTransform
28
+ let depth = spatializedStatic3DElement.depth
29
+ let transform = spatializedStatic3DElement.entityTransform
30
30
  let translation = transform.translation
31
31
  let scale = transform.scale
32
32
  let rotation = transform.rotation!
@@ -34,8 +34,8 @@ struct SpatializedStatic3DView: View {
34
34
  let y = translation.y
35
35
  let z = translation.z
36
36
 
37
- let enableGesture = spatializedElement.enableGesture
38
- if !spatializedStatic3DElement.allSources.isEmpty {
37
+ let enableGesture = spatializedStatic3DElement.enableGesture
38
+ if spatializedStatic3DElement.loading == .eager {
39
39
  Group {
40
40
  switch loadState {
41
41
  case .idle, .loading:
@@ -44,24 +44,23 @@ struct SpatializedStatic3DView: View {
44
44
  Model3D(asset: asset) { resolvedModel3D in
45
45
  resolvedModel3D
46
46
  .resizable(true)
47
- .aspectRatio(
48
- nil,
49
- contentMode: .fit
50
- )
47
+ .aspectRatio(nil, contentMode: .fit)
51
48
  .if(!depth.isZero) { view in view.scaledToFit3D() }
52
49
  .if(enableGesture) { view in view.hoverEffect() }
53
50
  }
54
51
  case .failed:
55
- posterView {}
52
+ posterView {
53
+ // Transparent view is required so that lifecycle modifiers like .task still work
54
+ Color.clear
55
+ }
56
56
  }
57
57
  }
58
- .scaleEffect(
59
- x: scale.width,
60
- y: scale.height,
61
- z: scale.depth
62
- )
63
- .rotation3DEffect(
64
- rotation
58
+ .scaleEffect(scale)
59
+ .rotation3DEffect(rotation)
60
+ .orbit(
61
+ enabled: spatializedStatic3DElement.stagemode == .orbit && asset != nil,
62
+ entityTransform: $spatializedStatic3DElement.entityTransform,
63
+ onChange: onOrbit
65
64
  )
66
65
  .offset(x: x, y: y)
67
66
  .offset(z: z)
@@ -80,7 +79,9 @@ struct SpatializedStatic3DView: View {
80
79
  }
81
80
  }
82
81
  .onChange(of: spatializedStatic3DElement.animationPaused) { onPlayback(isPaused: $1) }
83
- .onChange(of: spatializedStatic3DElement.playbackRate) { asset?.animationPlaybackController?.speed = Float($1) }
82
+ .onChange(of: spatializedStatic3DElement.playbackRate) {
83
+ asset?.animationPlaybackController?.speed = spatializedStatic3DElement.animationPaused ? 0 : Float($1)
84
+ }
84
85
  .onChange(of: spatializedStatic3DElement.pendingSeekTime) { _, time in onSeek(time: time) }
85
86
  .task(id: spatializedStatic3DElement.allSources) { await loadSources() }
86
87
  } else {
@@ -122,7 +123,7 @@ struct SpatializedStatic3DView: View {
122
123
  asset.selectedAnimation = asset.availableAnimations.first
123
124
  }
124
125
  let controller = asset.animationPlaybackController
125
- controller?.speed = Float(spatializedStatic3DElement.playbackRate)
126
+ controller?.speed = isPaused ? 0 : Float(spatializedStatic3DElement.playbackRate)
126
127
  if let time = spatializedStatic3DElement.pendingSeekTime, let controller {
127
128
  controller.time = time
128
129
  spatializedStatic3DElement.pendingSeekTime = nil
@@ -149,7 +150,7 @@ struct SpatializedStatic3DView: View {
149
150
  let duration = controller?.duration ?? 0
150
151
  let currentTime = controller?.time ?? 0
151
152
  spatialScene.sendWebMsg(
152
- spatializedElement.id,
153
+ spatializedStatic3DElement.id,
153
154
  AnimationStateChangeEvent(
154
155
  detail: AnimationStateChangeDetail(
155
156
  paused: isPaused,
@@ -183,13 +184,11 @@ struct SpatializedStatic3DView: View {
183
184
  if let result {
184
185
  loadState = .loaded(result.asset, result.url.absoluteString)
185
186
  onLoadSuccess(src: result.url.absoluteString)
186
- if spatializedStatic3DElement.autoplay {
187
- // If animationPaused didn't change then SwiftUI will not trigger onChange so manually trigger playback
188
- // This happens when play is called before load and autoplay is enabled
189
- if spatializedStatic3DElement.animationPaused {
190
- spatializedStatic3DElement.animationPaused = false
191
- } else { onPlayback(isPaused: false) }
192
- }
187
+ // If animationPaused didn't change then SwiftUI will not trigger onChange so manually trigger playback
188
+ // This happens when play is called before load and autoplay is enabled
189
+ if spatializedStatic3DElement.autoplay, spatializedStatic3DElement.animationPaused {
190
+ spatializedStatic3DElement.animationPaused = false
191
+ } else { onPlayback(isPaused: !spatializedStatic3DElement.autoplay) }
193
192
  } else {
194
193
  loadState = .failed
195
194
  onLoadFailure()
@@ -0,0 +1,84 @@
1
+ import SwiftUI
2
+
3
+ private let degreesPerPoint = 0.5
4
+ private let pitchLimitDegrees = 100.0
5
+ private let springBack = Animation.spring(response: 0.45, dampingFraction: 0.7)
6
+
7
+ /// Adds an orbital camera-style rotation gesture to a 3D model.
8
+ ///
9
+ /// Both horizontal yaw (around the Y-axis) and vertical pitch (around the
10
+ /// X-axis, clamped to ±100° so the model can't flip past upright) are folded
11
+ /// directly into the bound `entityTransform` on each drag tick, so the web
12
+ /// layer always observes an accurate transform. On release, the pitch
13
+ /// elastically springs back to 0: the data is set to the unpitched final state
14
+ /// immediately while SwiftUI animates the existing `.rotation3DEffect` from the
15
+ /// old (pitched) rotation to the new (unpitched) rotation for a smooth visual.
16
+ struct OrbitModifier: ViewModifier {
17
+ let enabled: Bool
18
+ @Binding var entityTransform: AffineTransform3D
19
+ let onChange: (AffineTransform3D) -> Void
20
+
21
+ @State private var dragStartTransform: AffineTransform3D?
22
+ @State private var dragYawDegrees: Double = 0
23
+
24
+ func body(content: Content) -> some View {
25
+ content
26
+ .simultaneousGesture(enabled ? orbitGesture : nil)
27
+ .onChange(of: enabled) { _, isEnabled in
28
+ if !isEnabled { releaseElastic() }
29
+ }
30
+ }
31
+
32
+ private var orbitGesture: some Gesture {
33
+ DragGesture(minimumDistance: 0)
34
+ .onChanged { value in
35
+ let base = dragStartTransform ?? entityTransform
36
+ if dragStartTransform == nil { dragStartTransform = base }
37
+
38
+ dragYawDegrees = Double(value.translation.width) * degreesPerPoint
39
+ let pitchTarget = Double(-value.translation.height) * degreesPerPoint
40
+ let pitchDegrees = max(-pitchLimitDegrees, min(pitchLimitDegrees, pitchTarget))
41
+
42
+ let new = compose(base: base, yawDegrees: dragYawDegrees, pitchDegrees: pitchDegrees)
43
+ entityTransform = new
44
+ onChange(new)
45
+ }
46
+ .onEnded { _ in releaseElastic() }
47
+ }
48
+
49
+ private func releaseElastic() {
50
+ guard let base = dragStartTransform else { return }
51
+ let unpitched = compose(base: base, yawDegrees: dragYawDegrees, pitchDegrees: 0)
52
+ withAnimation(springBack) { entityTransform = unpitched }
53
+ onChange(unpitched)
54
+ dragStartTransform = nil
55
+ dragYawDegrees = 0
56
+ }
57
+
58
+ private func compose(
59
+ base: AffineTransform3D,
60
+ yawDegrees: Double,
61
+ pitchDegrees: Double
62
+ ) -> AffineTransform3D {
63
+ let translation = AffineTransform3D(translation: base.translation)
64
+ let yaw = AffineTransform3D(rotation: Rotation3D(angle: .degrees(yawDegrees), axis: .y))
65
+ let pitch = AffineTransform3D(rotation: Rotation3D(angle: .degrees(pitchDegrees), axis: .x))
66
+ let baseRotation = AffineTransform3D(rotation: base.rotation ?? .identity)
67
+ let scale = AffineTransform3D(scale: base.scale)
68
+ return translation
69
+ .concatenating(yaw)
70
+ .concatenating(pitch)
71
+ .concatenating(baseRotation)
72
+ .concatenating(scale)
73
+ }
74
+ }
75
+
76
+ extension View {
77
+ func orbit(
78
+ enabled: Bool,
79
+ entityTransform: Binding<AffineTransform3D>,
80
+ onChange: @escaping (AffineTransform3D) -> Void
81
+ ) -> some View {
82
+ modifier(OrbitModifier(enabled: enabled, entityTransform: entityTransform, onChange: onChange))
83
+ }
84
+ }
@@ -5,6 +5,9 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
5
5
  weak var model: SpatialWebViewModel?
6
6
  var webview: WKWebView?
7
7
  private var isObserving = false
8
+ private var urlObservation: NSKeyValueObservation?
9
+ private var canGoBackObservation: NSKeyValueObservation?
10
+ private var canGoForwardObservation: NSKeyValueObservation?
8
11
  private var navigationInvoke: ((_ data: URL) -> Bool)?
9
12
  private var openWindowInvoke: ((_ data: URL) -> WebViewElementInfo?)?
10
13
  private var webviewStateChangeInvoke: ((_ type: SpatialWebViewState) -> Void)?
@@ -205,32 +208,37 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
205
208
 
206
209
  func startObserving() {
207
210
  guard !isObserving else { return }
208
- webview?.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)
211
+ urlObservation = webview?.observe(\.url, options: [.new]) { [weak self] webView, _ in
212
+ guard let self, let url = webView.url?.absoluteString else { return }
213
+ Task { @MainActor in
214
+ self.model?.url = url
215
+ self.webviewStateChangeInvoke?(.didUpdateURL)
216
+ }
217
+ }
218
+ canGoBackObservation = webview?.observe(\.canGoBack, options: [.new]) { [weak self] _, _ in
219
+ Task { @MainActor in
220
+ self?.webviewStateChangeInvoke?(.didUpdateNavigationState)
221
+ }
222
+ }
223
+ canGoForwardObservation = webview?.observe(\.canGoForward, options: [.new]) { [weak self] _, _ in
224
+ Task { @MainActor in
225
+ self?.webviewStateChangeInvoke?(.didUpdateNavigationState)
226
+ }
227
+ }
209
228
  isObserving = true
210
229
  }
211
230
 
212
231
  func stopObserving() {
213
232
  guard isObserving else { return }
214
- webview?.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
233
+ urlObservation?.invalidate()
234
+ canGoBackObservation?.invalidate()
235
+ canGoForwardObservation?.invalidate()
236
+ urlObservation = nil
237
+ canGoBackObservation = nil
238
+ canGoForwardObservation = nil
215
239
  isObserving = false
216
240
  }
217
241
 
218
- override func observeValue(
219
- forKeyPath keyPath: String?,
220
- of object: Any?,
221
- change: [NSKeyValueChangeKey: Any]?,
222
- context: UnsafeMutableRawPointer?
223
- ) {
224
- if keyPath == #keyPath(WKWebView.url),
225
- let url = (object as? WKWebView)?.url?.absoluteString
226
- {
227
- DispatchQueue.main.async {
228
- // print("url change", url)
229
- self.model?.url = url
230
- }
231
- }
232
- }
233
-
234
242
  func destroy() {
235
243
  destroyView()
236
244
  navigationInvoke = nil
@@ -10,7 +10,7 @@ class SpatialWebViewModel {
10
10
  private var navigationList: [String: (_ data: URL) -> Bool] = [:]
11
11
  private var openWindowList: [String: (_ data: URL) -> WebViewElementInfo?] = [:]
12
12
  private var stateListeners: [SpatialWebViewState: [() -> Void]] = [:]
13
- private var stateChangeListeners: [(_ type: SpatialWebViewState) -> Void] = []
13
+ private var stateChangeListeners: [(id: UUID, listener: (_ type: SpatialWebViewState) -> Void)] = []
14
14
  private var scrollUpdateListeners: [(_ type: ScrollState, _ point: CGPoint) -> Void] = []
15
15
  private var backgroundTransparent: Bool = false
16
16
 
@@ -150,8 +150,11 @@ class SpatialWebViewModel {
150
150
  }
151
151
 
152
152
  /// webview state event
153
- func addStateListener(_ event: @escaping (_ type: SpatialWebViewState) -> Void) {
154
- stateChangeListeners.append(event)
153
+ @discardableResult
154
+ func addStateListener(_ event: @escaping (_ type: SpatialWebViewState) -> Void) -> UUID {
155
+ let id = UUID()
156
+ stateChangeListeners.append((id: id, listener: event))
157
+ return id
155
158
  }
156
159
 
157
160
  func addStateListener(_ state: SpatialWebViewState, _ event: @escaping () -> Void) {
@@ -161,13 +164,13 @@ class SpatialWebViewModel {
161
164
  stateListeners[state]?.append(event)
162
165
  }
163
166
 
164
- func removeStateListener(_ event: @escaping (_ type: String) -> Void) {
167
+ func removeStateListener(_ id: UUID) {
165
168
  stateChangeListeners.removeAll(where: {
166
- $0 as AnyObject === event as AnyObject
169
+ $0.id == id
167
170
  })
168
171
  }
169
172
 
170
- func removeStateListener(_ state: SpatialWebViewState, _ event: @escaping (_ object: Any, _ data: Any) -> Void) {
173
+ func removeStateListener(_ state: SpatialWebViewState, _ event: @escaping () -> Void) {
171
174
  stateListeners[state]?.removeAll(where: {
172
175
  $0 as AnyObject === event as AnyObject
173
176
  })
@@ -235,7 +238,7 @@ class SpatialWebViewModel {
235
238
  load()
236
239
  }
237
240
  for onStateChange in stateChangeListeners {
238
- onStateChange(state)
241
+ onStateChange.listener(state)
239
242
  }
240
243
  stateListeners[state]?.forEach { onStateChange in
241
244
  onStateChange()
@@ -300,6 +303,8 @@ enum SpatialWebViewState: String, CaseIterable {
300
303
  case didReceive
301
304
  case didFinishLoad
302
305
  case didFailLoad
306
+ case didUpdateURL
307
+ case didUpdateNavigationState
303
308
  case didUnload
304
309
  case didClose
305
310
  case didMakeView
@@ -14,6 +14,8 @@
14
14
  2B06AEE62E4C1AE8000327E9 /* JSBCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B06AEC02E4C1AE8000327E9 /* JSBCommand.swift */; };
15
15
  2B06AEE92E4C1AE8000327E9 /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B06AEBF2E4C1AE8000327E9 /* EventEmitter.swift */; };
16
16
  2B1008A22F0B000800000002 /* NavigationCleanupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1008A12F0B000800000001 /* NavigationCleanupTests.swift */; };
17
+ 2B1008A52F0B000800000005 /* SpatializedElementMotionTimelineSamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1008A42F0B000800000004 /* SpatializedElementMotionTimelineSamplerTests.swift */; };
18
+ 2B1008A72F0B000800000007 /* SpatializedElementAnimationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1008A62F0B000800000006 /* SpatializedElementAnimationManagerTests.swift */; };
17
19
  2B2F1D712BEBFAAC006897EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B2F1D702BEBFAAC006897EE /* Assets.xcassets */; };
18
20
  2B2F1D742BEBFAAC006897EE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B2F1D732BEBFAAC006897EE /* Preview Assets.xcassets */; };
19
21
  2B89E38C2E699104004079AA /* WebMsgCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B89E38B2E699104004079AA /* WebMsgCommand.swift */; };
@@ -38,12 +40,14 @@
38
40
  2B06AEC32E4C1AE8000327E9 /* WebSpatialApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSpatialApp.swift; sourceTree = "<group>"; };
39
41
  2B06AEC42E4C1AE8000327E9 /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
40
42
  2B1008A12F0B000800000001 /* NavigationCleanupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCleanupTests.swift; sourceTree = "<group>"; };
43
+ 2B1008A42F0B000800000004 /* SpatializedElementMotionTimelineSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpatializedElementMotionTimelineSamplerTests.swift; sourceTree = "<group>"; };
44
+ 2B1008A62F0B000800000006 /* SpatializedElementAnimationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpatializedElementAnimationManagerTests.swift; sourceTree = "<group>"; };
41
45
  2B2F1D632BEBFAAA006897EE /* WebSpatial.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebSpatial.app; sourceTree = BUILT_PRODUCTS_DIR; };
42
46
  2B2F1D672BEBFAAA006897EE /* RealityKitContent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealityKitContent; sourceTree = "<group>"; };
43
47
  2B2F1D702BEBFAAC006897EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
44
48
  2B2F1D732BEBFAAC006897EE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
45
49
  2B2F1D752BEBFAAC006897EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
46
- 2B2F1D7A2BEBFAAD006897EE /* WebSpatial.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebSpatial.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
50
+ 2B2F1D7A2BEBFAAD006897EE /* WebSpatial.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = WebSpatial.xctest; path = "web-spatialTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
47
51
  2B89E38B2E699104004079AA /* WebMsgCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebMsgCommand.swift; sourceTree = "<group>"; };
48
52
  2BDBF9B52C4ED9F600D269D7 /* static-web */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "static-web"; sourceTree = "<group>"; };
49
53
  /* End PBXFileReference section */
@@ -63,6 +67,8 @@
63
67
  2B06AF312E4C1AF8000327E9 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
64
68
  isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
65
69
  membershipExceptions = (
70
+ dynamic3d/EntityAnimationManager.swift,
71
+ dynamic3d/EntityAnimationSession.swift,
66
72
  dynamic3d/Geometry.swift,
67
73
  dynamic3d/SpatialComponent.swift,
68
74
  dynamic3d/SpatialEntity.swift,
@@ -71,6 +77,15 @@
71
77
  dynamic3d/SpatialModelResource.swift,
72
78
  dynamic3d/SpatialRootEntity.swift,
73
79
  dynamic3d/SpatialTextureResource.swift,
80
+ "element-motion/SpatializedElementAnimationCommand.swift",
81
+ "element-motion/SpatializedElementAnimationManager.swift",
82
+ "element-motion/SpatializedElementAnimationObject.swift",
83
+ "element-motion/SpatializedElementAnimationTypes.swift",
84
+ "element-motion/SpatializedElementAnimationWriteAdapter.swift",
85
+ "element-motion/SpatializedElementMotionBridgeTypes.swift",
86
+ "element-motion/SpatializedElementMotionTimelineSampler.swift",
87
+ "element-motion/SpatializedElementMotionTiming.swift",
88
+ "element-motion/SpatializedElementMotionTransformTypes.swift",
74
89
  MaterialSceneRefresh.swift,
75
90
  SpatialApp.swift,
76
91
  Spatialized2DElement.swift,
@@ -113,6 +128,7 @@
113
128
  SpatialSceneView.swift,
114
129
  "view-modifier/HideViewModifier.swift",
115
130
  "view-modifier/MaterialWithBorderCornerModifier.swift",
131
+ "view-modifier/OrbitModifier.swift",
116
132
  );
117
133
  target = 2B2F1D622BEBFAAA006897EE /* web-spatial */;
118
134
  };
@@ -159,6 +175,8 @@
159
175
  isa = PBXGroup;
160
176
  children = (
161
177
  2B1008A12F0B000800000001 /* NavigationCleanupTests.swift */,
178
+ 2B1008A42F0B000800000004 /* SpatializedElementMotionTimelineSamplerTests.swift */,
179
+ 2B1008A62F0B000800000006 /* SpatializedElementAnimationManagerTests.swift */,
162
180
  );
163
181
  path = "web-spatialTests";
164
182
  sourceTree = "<group>";
@@ -346,6 +364,8 @@
346
364
  buildActionMask = 2147483647;
347
365
  files = (
348
366
  2B1008A22F0B000800000002 /* NavigationCleanupTests.swift in Sources */,
367
+ 2B1008A52F0B000800000005 /* SpatializedElementMotionTimelineSamplerTests.swift in Sources */,
368
+ 2B1008A72F0B000800000007 /* SpatializedElementAnimationManagerTests.swift in Sources */,
349
369
  );
350
370
  runOnlyForDeploymentPostprocessing = 0;
351
371
  };
@@ -542,13 +562,14 @@
542
562
  GENERATE_INFOPLIST_FILE = YES;
543
563
  MARKETING_VERSION = 1.0;
544
564
  PRODUCT_BUNDLE_IDENTIFIER = com.webspatial.test;
545
- PRODUCT_NAME = WebSpatial;
565
+ PRODUCT_MODULE_NAME = WebSpatialTests;
566
+ PRODUCT_NAME = "$(TARGET_NAME)";
546
567
  SUPPORTED_PLATFORMS = "xros xrsimulator";
547
568
  SWIFT_EMIT_LOC_STRINGS = NO;
548
569
  SWIFT_VERSION = 5.0;
549
570
  TARGETED_DEVICE_FAMILY = "1,2,7";
550
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/web-spatial.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/web-spatial";
551
- XROS_DEPLOYMENT_TARGET = 1.1;
571
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebSpatial.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WebSpatial";
572
+ XROS_DEPLOYMENT_TARGET = 26.0;
552
573
  };
553
574
  name = Debug;
554
575
  };
@@ -561,13 +582,14 @@
561
582
  GENERATE_INFOPLIST_FILE = YES;
562
583
  MARKETING_VERSION = 1.0;
563
584
  PRODUCT_BUNDLE_IDENTIFIER = com.webspatial.test;
564
- PRODUCT_NAME = WebSpatial;
585
+ PRODUCT_MODULE_NAME = WebSpatialTests;
586
+ PRODUCT_NAME = "$(TARGET_NAME)";
565
587
  SUPPORTED_PLATFORMS = "xros xrsimulator";
566
588
  SWIFT_EMIT_LOC_STRINGS = NO;
567
589
  SWIFT_VERSION = 5.0;
568
590
  TARGETED_DEVICE_FAMILY = "1,2,7";
569
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/web-spatial.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/web-spatial";
570
- XROS_DEPLOYMENT_TARGET = 1.1;
591
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebSpatial.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WebSpatial";
592
+ XROS_DEPLOYMENT_TARGET = 26.0;
571
593
  };
572
594
  name = Release;
573
595
  };
@@ -602,7 +624,6 @@
602
624
  defaultConfigurationName = Release;
603
625
  };
604
626
  /* End XCConfigurationList section */
605
-
606
627
  };
607
628
  rootObject = 2B2F1D5B2BEBFAAA006897EE /* Project object */;
608
629
  }
@@ -36,7 +36,7 @@
36
36
  <BuildableReference
37
37
  BuildableIdentifier = "primary"
38
38
  BlueprintIdentifier = "2B2F1D792BEBFAAD006897EE"
39
- BuildableName = "WebSpatial.xctest"
39
+ BuildableName = "web-spatialTests.xctest"
40
40
  BlueprintName = "web-spatialTests"
41
41
  ReferencedContainer = "container:web-spatial.xcodeproj">
42
42
  </BuildableReference>
@@ -18,7 +18,8 @@ final class NavigationCleanupTests: XCTestCase {
18
18
  id: "test-attachment",
19
19
  parentEntityId: "test-parent-entity",
20
20
  position: SIMD3<Float>(0, 0, 0),
21
- size: CGSize(width: 100, height: 100)
21
+ size: CGSize(width: 100, height: 100),
22
+ webViewModel: SpatialWebViewModel(url: "http://localhost:5173/")
22
23
  )
23
24
  XCTAssertFalse(scene.attachmentManager.attachments.isEmpty)
24
25