@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
@@ -0,0 +1,65 @@
1
+ import Foundation
2
+ import Spatial
3
+
4
+ struct SpatializedElementAnimationWriteAdapter {
5
+ func currentAffineTransform(for element: SpatializedElement) -> AffineTransform3D {
6
+ element.transform
7
+ }
8
+
9
+ func applyAffine(_ affine: AffineTransform3D, to element: SpatializedElement) {
10
+ element.transform = affine
11
+ }
12
+
13
+ func baselineOpacity(for element: SpatializedElement) -> Double {
14
+ element.opacity
15
+ }
16
+
17
+ func applyOpacity(_ opacity: Double, to element: SpatializedElement) {
18
+ element.opacity = opacity
19
+ }
20
+
21
+ func acquireMask(on element: SpatializedElement, animationId: String, animatesTransform: Bool, animatesOpacity: Bool) -> Bool {
22
+ guard canAcquireMask(on: element, animationId: animationId, animatesTransform: animatesTransform, animatesOpacity: animatesOpacity) else {
23
+ return false
24
+ }
25
+
26
+ if animatesTransform {
27
+ element.animatingMask.acquire(transform: animationId)
28
+ }
29
+ if animatesOpacity {
30
+ element.animatingMask.acquire(opacity: animationId)
31
+ }
32
+ return true
33
+ }
34
+
35
+ /// Checks whether all requested animated fields are free or already owned by this animation.
36
+ private func canAcquireMask(on element: SpatializedElement, animationId: String, animatesTransform: Bool, animatesOpacity: Bool) -> Bool {
37
+ if animatesTransform,
38
+ let owner = element.animatingMask.transformAnimationId,
39
+ owner != animationId
40
+ {
41
+ return false
42
+ }
43
+
44
+ if animatesOpacity,
45
+ let owner = element.animatingMask.opacityAnimationId,
46
+ owner != animationId
47
+ {
48
+ return false
49
+ }
50
+
51
+ return true
52
+ }
53
+
54
+ func releaseMaskAndApplyPending(on element: SpatializedElement, animationId: String) {
55
+ element.animatingMask.release(animationId: animationId)
56
+ }
57
+
58
+ func shouldAllowTransformWrite(on element: SpatializedElement, animationId: String) -> Bool {
59
+ element.animatingMask.transformAnimationId == nil || element.animatingMask.transformAnimationId == animationId
60
+ }
61
+
62
+ func shouldAllowOpacityWrite(on element: SpatializedElement, animationId: String) -> Bool {
63
+ element.animatingMask.opacityAnimationId == nil || element.animatingMask.opacityAnimationId == animationId
64
+ }
65
+ }
@@ -0,0 +1,43 @@
1
+ import Foundation
2
+
3
+ // MARK: - Timeline wire types (matches JS SpatializedMotionTimeline)
4
+
5
+ struct SpatializedMotionKeyframePayload: Decodable {
6
+ let at: Double
7
+ let value: Double
8
+ let timingFunction: String?
9
+ }
10
+
11
+ struct SpatializedMotionTrackPayload: Decodable {
12
+ let property: String
13
+ let keyframes: [SpatializedMotionKeyframePayload]
14
+ let timingFunction: String?
15
+ }
16
+
17
+ struct SpatializedMotionTimelinePayload: Decodable {
18
+ let duration: Double
19
+ let delay: Double?
20
+ let playbackRate: Double?
21
+ let loop: SpatializedMotionLoopConfig?
22
+ let tracks: [SpatializedMotionTrackPayload]
23
+ }
24
+
25
+ // MARK: - Event Payloads
26
+
27
+ /// Payload structure matching the JS-side spatialized motion values.
28
+ struct SpatializedMotionValuesPayload: Encodable {
29
+ let transform: SpatializedMotionTransformPayload?
30
+ let opacity: Double?
31
+ }
32
+
33
+ struct SpatializedMotionTransformPayload: Encodable {
34
+ let translate: Vec3Payload?
35
+ let rotate: Vec3Payload?
36
+ let scale: Vec3Payload?
37
+ }
38
+
39
+ struct Vec3Payload: Encodable {
40
+ let x: Double?
41
+ let y: Double?
42
+ let z: Double?
43
+ }
@@ -0,0 +1,99 @@
1
+ import Foundation
2
+
3
+ // MARK: - Timeline sampler (parity with Web evaluateMotionTimeline)
4
+
5
+ /// Per-track timeline sampling for native spatialized element motion.
6
+ final class SpatializedElementMotionTimelineSampler {
7
+ let duration: Double
8
+ private let tracks: [SpatializedMotionTrackPayload]
9
+ private let baselineTransform: SpatializedMotionTransformComponents
10
+ private let baselineOpacity: Double
11
+
12
+ private(set) var animatesTransform = false
13
+ private(set) var animatesOpacity = false
14
+
15
+ init(timeline: SpatializedMotionTimelinePayload, baselineTransform: SpatializedMotionTransformComponents, baselineOpacity: Double) {
16
+ duration = timeline.duration
17
+ tracks = timeline.tracks
18
+ self.baselineTransform = baselineTransform
19
+ self.baselineOpacity = baselineOpacity
20
+ for track in tracks {
21
+ if track.property == "opacity" {
22
+ animatesOpacity = true
23
+ }
24
+ if track.property.hasPrefix("transform.") {
25
+ animatesTransform = true
26
+ }
27
+ }
28
+ }
29
+
30
+ func sampleTransformAndOpacity(at timeSec: Double) -> (transform: SpatializedMotionTransformComponents, opacity: Double) {
31
+ let t = min(max(timeSec, 0), duration)
32
+ var transform = baselineTransform
33
+ var opacity = baselineOpacity
34
+
35
+ for track in tracks {
36
+ let value = sampleTrack(track, at: t)
37
+ applyScalar(property: track.property, value: value, transform: &transform, opacity: &opacity)
38
+ }
39
+
40
+ return (transform, opacity)
41
+ }
42
+
43
+ private func sampleTrack(_ track: SpatializedMotionTrackPayload, at timeSec: Double) -> Double {
44
+ let frames = track.keyframes.sorted { $0.at < $1.at }
45
+ guard let first = frames.first else { return 0 }
46
+ guard let last = frames.last else { return 0 }
47
+
48
+ if timeSec <= first.at { return first.value }
49
+ if timeSec >= last.at { return last.value }
50
+
51
+ for i in 0 ..< (frames.count - 1) {
52
+ let a = frames[i]
53
+ let b = frames[i + 1]
54
+ if timeSec >= a.at, timeSec <= b.at {
55
+ let span = b.at - a.at
56
+ if span <= 0 { return b.value }
57
+ let linear = (timeSec - a.at) / span
58
+ let eased = SpatializedMotionTimingFunction
59
+ .from(name: a.timingFunction ?? track.timingFunction ?? "linear")
60
+ .evaluate(linear)
61
+ return a.value + (b.value - a.value) * eased
62
+ }
63
+ }
64
+
65
+ return last.value
66
+ }
67
+
68
+ private func applyScalar(
69
+ property: String,
70
+ value: Double,
71
+ transform: inout SpatializedMotionTransformComponents,
72
+ opacity: inout Double
73
+ ) {
74
+ switch property {
75
+ case "opacity":
76
+ opacity = value
77
+ case "transform.translate.x":
78
+ transform.translateX = value
79
+ case "transform.translate.y":
80
+ transform.translateY = value
81
+ case "transform.translate.z":
82
+ transform.translateZ = value
83
+ case "transform.rotate.x":
84
+ transform.rotateX = value
85
+ case "transform.rotate.y":
86
+ transform.rotateY = value
87
+ case "transform.rotate.z":
88
+ transform.rotateZ = value
89
+ case "transform.scale.x":
90
+ transform.scaleX = value
91
+ case "transform.scale.y":
92
+ transform.scaleY = value
93
+ case "transform.scale.z":
94
+ transform.scaleZ = value
95
+ default:
96
+ break
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,99 @@
1
+ import Foundation
2
+
3
+ // MARK: - Loop Configuration
4
+
5
+ /// Loop configuration decoded from JS bridge.
6
+ /// Supports: true (reset loop), { reverse: true } (reverse loop), false/nil (play once).
7
+ enum SpatializedMotionLoopConfig: Decodable {
8
+ case none
9
+ case resetLoop
10
+ case reverseLoop
11
+
12
+ init(from decoder: Decoder) throws {
13
+ let container = try decoder.singleValueContainer()
14
+
15
+ // Try bool first
16
+ if let boolVal = try? container.decode(Bool.self) {
17
+ self = boolVal ? .resetLoop : .none
18
+ return
19
+ }
20
+
21
+ // Try object { reverse?: Bool }
22
+ struct LoopObject: Decodable {
23
+ let reverse: Bool?
24
+ }
25
+ if let obj = try? container.decode(LoopObject.self) {
26
+ self = (obj.reverse == true) ? .reverseLoop : .resetLoop
27
+ return
28
+ }
29
+
30
+ self = .none
31
+ }
32
+ }
33
+
34
+ // MARK: - Timing Functions
35
+
36
+ /// Cubic bezier timing functions for manual frame interpolation.
37
+ enum SpatializedMotionTimingFunction {
38
+ case linear
39
+ case easeIn
40
+ case easeOut
41
+ case easeInOut
42
+
43
+ /// Evaluate the timing function at progress t (0...1) -> output (0...1)
44
+ func evaluate(_ t: Double) -> Double {
45
+ switch self {
46
+ case .linear:
47
+ return t
48
+ case .easeIn:
49
+ // cubic-bezier(0.42, 0, 1, 1)
50
+ return cubicBezier(t, x1: 0.42, y1: 0.0, x2: 1.0, y2: 1.0)
51
+ case .easeOut:
52
+ // cubic-bezier(0, 0, 0.58, 1)
53
+ return cubicBezier(t, x1: 0.0, y1: 0.0, x2: 0.58, y2: 1.0)
54
+ case .easeInOut:
55
+ // cubic-bezier(0.42, 0, 0.58, 1)
56
+ return cubicBezier(t, x1: 0.42, y1: 0.0, x2: 0.58, y2: 1.0)
57
+ }
58
+ }
59
+
60
+ /// Solve cubic bezier curve for Y given input T (time fraction).
61
+ private func cubicBezier(_ t: Double, x1: Double, y1: Double, x2: Double, y2: Double) -> Double {
62
+ if x1 == 0 && y1 == 0 && x2 == 1 && y2 == 1 {
63
+ return t
64
+ }
65
+
66
+ // Newton-Raphson to solve for bezier parameter given x = t
67
+ var guessT = t
68
+ for _ in 0 ..< 8 {
69
+ let currentX = sampleCurveX(guessT, x1: x1, x2: x2)
70
+ let derivative = sampleCurveDerivativeX(guessT, x1: x1, x2: x2)
71
+ if abs(derivative) < 1e-6 { break }
72
+ guessT -= (currentX - t) / derivative
73
+ }
74
+ guessT = max(0.0, min(1.0, guessT))
75
+ return sampleCurveY(guessT, y1: y1, y2: y2)
76
+ }
77
+
78
+ private func sampleCurveX(_ t: Double, x1: Double, x2: Double) -> Double {
79
+ return ((1.0 - 3.0 * x2 + 3.0 * x1) * t + (3.0 * x2 - 6.0 * x1)) * t * t + 3.0 * x1 * t
80
+ }
81
+
82
+ private func sampleCurveY(_ t: Double, y1: Double, y2: Double) -> Double {
83
+ return ((1.0 - 3.0 * y2 + 3.0 * y1) * t + (3.0 * y2 - 6.0 * y1)) * t * t + 3.0 * y1 * t
84
+ }
85
+
86
+ private func sampleCurveDerivativeX(_ t: Double, x1: Double, x2: Double) -> Double {
87
+ return (3.0 * (1.0 - 3.0 * x2 + 3.0 * x1) * t + 2.0 * (3.0 * x2 - 6.0 * x1)) * t + 3.0 * x1
88
+ }
89
+
90
+ static func from(name: String) -> SpatializedMotionTimingFunction {
91
+ switch name {
92
+ case "linear": return .linear
93
+ case "easeIn": return .easeIn
94
+ case "easeOut": return .easeOut
95
+ case "easeInOut": return .easeInOut
96
+ default: return .easeInOut
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,22 @@
1
+ import Foundation
2
+
3
+ // MARK: - Transform Components
4
+
5
+ /// Fully resolved transform components used for interpolation and composition.
6
+ struct SpatializedMotionTransformComponents {
7
+ var translateX: Double
8
+ var translateY: Double
9
+ var translateZ: Double
10
+ var rotateX: Double // degrees
11
+ var rotateY: Double // degrees
12
+ var rotateZ: Double // degrees
13
+ var scaleX: Double
14
+ var scaleY: Double
15
+ var scaleZ: Double
16
+
17
+ static let identity = SpatializedMotionTransformComponents(
18
+ translateX: 0, translateY: 0, translateZ: 0,
19
+ rotateX: 0, rotateY: 0, rotateZ: 0,
20
+ scaleX: 1, scaleY: 1, scaleZ: 1
21
+ )
22
+ }
@@ -4,5 +4,5 @@ protocol SpatializedElementContainer {
4
4
  func addChild(_ spatializedElement: SpatializedElement)
5
5
  func removeChild(_ spatializedElement: SpatializedElement)
6
6
  func getChildren() -> [String: SpatializedElement]
7
- func getChildrenOfType(_ type: SpatializedElementType) -> [String: SpatializedElement]
7
+ func getChildren<T: SpatializedElement>(ofType type: T.Type) -> [T]
8
8
  }
@@ -76,13 +76,11 @@ struct SpatialNavView: View {
76
76
  @State private var showEnter: Double = 1
77
77
  @State private var showNav: Double = 0
78
78
  @State private var showUrl: Double = 0
79
+ @State private var canGoBack = false
80
+ @State private var canGoForward = false
81
+ @State private var navigationStateListenerID: UUID?
79
82
  @Namespace var hoverNamespace
80
83
 
81
- func checkButtonState() {
82
- canGoBack = model!.getController().webview!.canGoBack
83
- canGoForward = model!.getController().webview!.canGoForward
84
- }
85
-
86
84
  func goBack() {
87
85
  spatialScene.resetForNavigation()
88
86
  model?.getController().webview?.goBack()
@@ -107,9 +105,30 @@ struct SpatialNavView: View {
107
105
  return model?.getController().webview?.url
108
106
  }
109
107
 
110
- @State var canGoBack: Bool = false
108
+ func checkButtonState() {
109
+ canGoBack = model?.getController().webview?.canGoBack ?? false
110
+ canGoForward = model?.getController().webview?.canGoForward ?? false
111
+ }
112
+
113
+ func registerNavigationStateListener() {
114
+ guard navigationStateListenerID == nil else { return }
111
115
 
112
- @State var canGoForward: Bool = false
116
+ let listener: (SpatialWebViewState) -> Void = { state in
117
+ guard case .didUpdateNavigationState = state else { return }
118
+ Task { @MainActor in
119
+ self.checkButtonState()
120
+ }
121
+ }
122
+
123
+ navigationStateListenerID = model?.addStateListener(listener)
124
+ }
125
+
126
+ func unregisterNavigationStateListener() {
127
+ guard let navigationStateListenerID else { return }
128
+
129
+ model?.removeStateListener(navigationStateListenerID)
130
+ self.navigationStateListenerID = nil
131
+ }
113
132
 
114
133
  var navHoverGroup: HoverEffectGroup {
115
134
  HoverEffectGroup(hoverNamespace)
@@ -128,6 +147,7 @@ struct SpatialNavView: View {
128
147
  children: Image("arrow_left"),
129
148
  clearBackGround: true
130
149
  )
150
+ .opacity(self.canGoBack ? 1 : 0.5)
131
151
  .disabled(!(self.canGoBack))
132
152
  NavButton(
133
153
  action: { self.goForward()
@@ -135,7 +155,8 @@ struct SpatialNavView: View {
135
155
  children: Image("arrow_right"),
136
156
  clearBackGround: true
137
157
  )
138
- .disabled(!(self.canGoBack))
158
+ .opacity(self.canGoForward ? 1 : 0.5)
159
+ .disabled(!(self.canGoForward))
139
160
  NavButton(action: { self.reload() }, children: Image("refresh"), clearBackGround: true)
140
161
  NavDivider()
141
162
  }
@@ -160,13 +181,15 @@ struct SpatialNavView: View {
160
181
  },
161
182
  children: Image("arrow_left")
162
183
  )
184
+ .opacity(self.canGoBack ? 1 : 0.5)
163
185
  .disabled(!(self.canGoBack))
164
186
  NavButton(
165
187
  action: { self.goForward()
166
188
  },
167
189
  children: Image("arrow_right")
168
190
  )
169
- .disabled(!(self.canGoBack))
191
+ .opacity(self.canGoForward ? 1 : 0.5)
192
+ .disabled(!(self.canGoForward))
170
193
  NavButton(action: { self.reload() }, children: Image("refresh"))
171
194
  NavDivider()
172
195
  }
@@ -238,9 +261,11 @@ struct SpatialNavView: View {
238
261
  .opacity(showUrl)
239
262
  }
240
263
  .onAppear {
241
- model?.addStateListener(.didFinishLoad) {
242
- self.checkButtonState()
243
- }
264
+ checkButtonState()
265
+ registerNavigationStateListener()
266
+ }
267
+ .onDisappear {
268
+ unregisterNavigationStateListener()
244
269
  }
245
270
  }
246
271
 
@@ -32,31 +32,28 @@ struct SpatialSceneContentView: View {
32
32
  .frame(width: width, height: height)
33
33
  .offset(z: -1)
34
34
 
35
- let childrenOfSpatialized2DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.Spatialized2DElement).values)
35
+ let childrenOfSpatialized2DElement = spatialScene.getChildren(ofType: Spatialized2DElement.self)
36
36
 
37
37
  ForEach(childrenOfSpatialized2DElement, id: \.id) { child in
38
- SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
39
- Spatialized2DElementView()
38
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset, spatializedElement: child) {
39
+ Spatialized2DElementView(spatialized2DElement: child)
40
40
  }
41
- .environment(child)
42
41
  }
43
42
 
44
- let childrenOfSpatializedStatic3DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.SpatializedStatic3DElement).values)
43
+ let childrenOfSpatializedStatic3DElement = spatialScene.getChildren(ofType: SpatializedStatic3DElement.self)
45
44
 
46
45
  ForEach(childrenOfSpatializedStatic3DElement, id: \.id) { child in
47
- SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
48
- SpatializedStatic3DView()
46
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset, spatializedElement: child) {
47
+ SpatializedStatic3DView(spatializedStatic3DElement: child)
49
48
  }
50
- .environment(child)
51
49
  }
52
50
 
53
- let childrenOfSpatializedDynamic3DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.SpatializedDynamic3DElement).values)
51
+ let childrenOfSpatializedDynamic3DElement = spatialScene.getChildren(ofType: SpatializedDynamic3DElement.self)
54
52
 
55
53
  ForEach(childrenOfSpatializedDynamic3DElement, id: \.id) { child in
56
- SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
57
- SpatializedDynamic3DView()
54
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset, spatializedElement: child) {
55
+ SpatializedDynamic3DView(spatializedDynamic3DElement: child)
58
56
  }
59
- .environment(child)
60
57
  }
61
58
  }
62
59
  }
@@ -19,13 +19,9 @@ class Spatialized2DViewGestureData {
19
19
  }
20
20
 
21
21
  struct Spatialized2DElementView: View {
22
- @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
22
+ let spatialized2DElement: Spatialized2DElement
23
23
  @Environment(SpatialScene.self) var spatialScene: SpatialScene
24
24
 
25
- private var spatialized2DElement: Spatialized2DElement {
26
- return spatializedElement as! Spatialized2DElement
27
- }
28
-
29
25
  @State private var gestureData = Spatialized2DViewGestureData()
30
26
 
31
27
  var body: some View {
@@ -40,30 +36,27 @@ struct Spatialized2DElementView: View {
40
36
  )
41
37
  .simultaneousGesture(spatialized2DElement.scrollPageEnabled ? dragWebGesture : nil)
42
38
 
43
- let childrenOfSpatialized2DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.Spatialized2DElement).values)
39
+ let childrenOfSpatialized2DElement = spatialized2DElement.getChildren(ofType: Spatialized2DElement.self)
44
40
 
45
41
  ForEach(childrenOfSpatialized2DElement, id: \.id) { child in
46
- SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
47
- Spatialized2DElementView()
42
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset, spatializedElement: child) {
43
+ Spatialized2DElementView(spatialized2DElement: child)
48
44
  }
49
- .environment(child)
50
45
  }
51
46
 
52
- let childrenOfSpatializedStatic3DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.SpatializedStatic3DElement).values)
47
+ let childrenOfSpatializedStatic3DElement = spatialized2DElement.getChildren(ofType: SpatializedStatic3DElement.self)
53
48
  ForEach(childrenOfSpatializedStatic3DElement, id: \.id) { child in
54
- SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
55
- SpatializedStatic3DView()
49
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset, spatializedElement: child) {
50
+ SpatializedStatic3DView(spatializedStatic3DElement: child)
56
51
  }
57
- .environment(child)
58
52
  }
59
53
 
60
- let childrenOfSpatializedDynamic3DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.SpatializedDynamic3DElement).values)
54
+ let childrenOfSpatializedDynamic3DElement = spatialized2DElement.getChildren(ofType: SpatializedDynamic3DElement.self)
61
55
 
62
56
  ForEach(childrenOfSpatializedDynamic3DElement, id: \.id) { child in
63
- SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
64
- SpatializedDynamic3DView()
57
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset, spatializedElement: child) {
58
+ SpatializedDynamic3DView(spatializedDynamic3DElement: child)
65
59
  }
66
- .environment(child)
67
60
  }
68
61
  }
69
62
  }
@@ -2,16 +2,12 @@ import RealityKit
2
2
  import SwiftUI
3
3
 
4
4
  struct SpatializedDynamic3DView: View {
5
- @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
5
+ let spatializedDynamic3DElement: SpatializedDynamic3DElement
6
6
  @Environment(SpatialScene.self) var spatialScene: SpatialScene
7
7
  @State private var isDrag = false
8
8
  @State private var isRotate = false
9
9
  @State private var isScale = false
10
10
 
11
- private var spatializedDynamic3DElement: SpatializedDynamic3DElement {
12
- return spatializedElement as! SpatializedDynamic3DElement
13
- }
14
-
15
11
  var spatialTapEvent: some Gesture {
16
12
  SpatialTapGesture(count: 1).targetedToAnyEntity()
17
13
  .onEnded { value in
@@ -63,7 +59,7 @@ struct SpatializedDynamic3DView: View {
63
59
  }
64
60
 
65
61
  private func makeRotateGesture3D() -> RotateGesture3D {
66
- guard let raw = spatializedElement.rotateConstrainedToAxis else {
62
+ guard let raw = spatializedDynamic3DElement.rotateConstrainedToAxis else {
67
63
  return RotateGesture3D()
68
64
  }
69
65
  let dx = Double(raw.x)
@@ -7,7 +7,7 @@ final class GestureState {
7
7
  }
8
8
 
9
9
  struct SpatializedElementView<Content: View>: View {
10
- @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
10
+ let spatializedElement: SpatializedElement
11
11
  @Environment(SpatialScene.self) var spatialScene: SpatialScene
12
12
 
13
13
  var parentScrollOffset: Vec2
@@ -15,8 +15,9 @@ struct SpatializedElementView<Content: View>: View {
15
15
 
16
16
  @State private var gestureState = GestureState()
17
17
 
18
- init(parentScrollOffset: Vec2, @ViewBuilder content: () -> Content) {
18
+ init(parentScrollOffset: Vec2, spatializedElement: SpatializedElement, @ViewBuilder content: () -> Content) {
19
19
  self.parentScrollOffset = parentScrollOffset
20
+ self.spatializedElement = spatializedElement
20
21
  self.content = content()
21
22
  }
22
23