@webspatial/platform-visionos 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/package.json +2 -2
  2. package/web-spatial/EventEmitter.swift +56 -0
  3. package/web-spatial/JSBCommand.swift +348 -0
  4. package/web-spatial/SpatialObject.swift +108 -0
  5. package/web-spatial/WebMsgCommand.swift +137 -0
  6. package/web-spatial/WebSpatialApp.swift +111 -0
  7. package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
  8. package/web-spatial/manager/Dynamic3DManager.swift +114 -0
  9. package/web-spatial/manager/JSBManager.swift +148 -0
  10. package/web-spatial/manager/WKWebViewManager.swift +39 -0
  11. package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
  12. package/web-spatial/model/SpatialApp.swift +190 -0
  13. package/web-spatial/model/SpatialScene.swift +1078 -0
  14. package/web-spatial/model/Spatialized2DElement.swift +128 -0
  15. package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
  16. package/web-spatial/model/SpatializedElement.swift +95 -0
  17. package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
  18. package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
  19. package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
  20. package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
  21. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
  22. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
  23. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
  24. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
  25. package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
  26. package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
  27. package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
  28. package/web-spatial/protocol/WebMsgSender.swift +5 -0
  29. package/web-spatial/types/Vec2.swift +12 -0
  30. package/web-spatial/types/Vec3.swift +7 -0
  31. package/web-spatial/view/SceneHandlerUIView.swift +92 -0
  32. package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
  33. package/web-spatial/view/SpatialSceneContentView.swift +218 -0
  34. package/web-spatial/view/SpatialSceneView.swift +53 -0
  35. package/web-spatial/view/Spatialized2DElementView.swift +96 -0
  36. package/web-spatial/view/SpatializedDynamic3DView.swift +173 -0
  37. package/web-spatial/view/SpatializedElementView.swift +178 -0
  38. package/web-spatial/view/SpatializedStatic3DView.swift +72 -0
  39. package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
  40. package/web-spatial/webview/SpatialWebController.swift +300 -0
  41. package/web-spatial/webview/SpatialWebView.swift +34 -0
  42. package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
  43. package/web-spatial.xcodeproj/project.pbxproj +126 -207
  44. package/web-spatial/libs/EventEmitter.swift +0 -25
  45. package/web-spatial/libs/SpatialComponent.swift +0 -24
  46. package/web-spatial/libs/SpatialEntity.swift +0 -172
  47. package/web-spatial/libs/SpatialInputComponent.swift +0 -19
  48. package/web-spatial/libs/SpatialMeshResource.swift +0 -12
  49. package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
  50. package/web-spatial/libs/SpatialModelComponent.swift +0 -25
  51. package/web-spatial/libs/SpatialObject.swift +0 -140
  52. package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
  53. package/web-spatial/libs/SpatialViewComponent.swift +0 -8
  54. package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
  55. package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
  56. package/web-spatial/libs/Utils/CommandManager.swift +0 -823
  57. package/web-spatial/libs/Utils/PerfClock.swift +0 -43
  58. package/web-spatial/libs/Utils/SceneManager.swift +0 -101
  59. package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
  60. package/web-spatial/libs/json/JsonParser.swift +0 -45
  61. package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
  62. package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
  63. package/web-spatial/views/ImmersiveView.swift +0 -17
  64. package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
  65. package/web-spatial/views/PlainWindowContainerView.swift +0 -132
  66. package/web-spatial/views/SpatialModel3DView.swift +0 -187
  67. package/web-spatial/views/SpatialViewUI.swift +0 -168
  68. package/web-spatial/views/SpatialWebViewUI.swift +0 -179
  69. package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
  70. package/web-spatial/web_spatialApp.swift +0 -141
  71. /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
  72. /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
  73. /package/web-spatial/{views → view}/LoadingView.swift +0 -0
  74. /package/web-spatial/{views → view/view-modifier}/HideViewModifier.swift +0 -0
@@ -0,0 +1,218 @@
1
+ import RealityKit
2
+ import SwiftUI
3
+
4
+ struct SpatialSceneContentView: View {
5
+ @State var sceneId: String
6
+ var width: Double
7
+ var height: Double
8
+
9
+ var body: some View {
10
+ if let spatialScene = SpatialApp.Instance.getScene(sceneId) {
11
+ ZStack(alignment: Alignment.topLeading) {
12
+ // Display the main webview
13
+ if spatialScene.didFailLoad {
14
+ VStack {
15
+ Text("Failed to load webpage. Is the server running?")
16
+ .foregroundColor(.white)
17
+ Button("Reload") {
18
+ let url = spatialScene.spatialWebViewModel.url
19
+
20
+ spatialScene.spatialWebViewModel.load(url)
21
+ }
22
+ .foregroundColor(.white)
23
+ }
24
+ .frame(maxWidth: .infinity, maxHeight: .infinity).glassBackgroundEffect()
25
+ } else {
26
+ spatialScene.getView()
27
+ .materialWithBorderCorner(
28
+ spatialScene.backgroundMaterial,
29
+ spatialScene.cornerRadius,
30
+ spatialScene.windowStyle
31
+ )
32
+ .frame(width: width, height: height)
33
+ .offset(z: -1)
34
+
35
+ let childrenOfSpatialized2DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.Spatialized2DElement).values)
36
+
37
+ ForEach(childrenOfSpatialized2DElement, id: \.id) { child in
38
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
39
+ Spatialized2DElementView()
40
+ }
41
+ .environment(child)
42
+ }
43
+
44
+ let childrenOfSpatializedStatic3DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.SpatializedStatic3DElement).values)
45
+
46
+ ForEach(childrenOfSpatializedStatic3DElement, id: \.id) { child in
47
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
48
+ SpatializedStatic3DView()
49
+ }
50
+ .environment(child)
51
+ }
52
+
53
+ let childrenOfSpatializedDynamic3DElement: [SpatializedElement] = Array(spatialScene.getChildrenOfType(.SpatializedDynamic3DElement).values)
54
+
55
+ ForEach(childrenOfSpatializedDynamic3DElement, id: \.id) { child in
56
+ SpatializedElementView(parentScrollOffset: spatialScene.scrollOffset) {
57
+ SpatializedDynamic3DView()
58
+ }
59
+ .environment(child)
60
+ }
61
+ }
62
+ }
63
+ .opacity(spatialScene.opacity)
64
+ .environment(spatialScene)
65
+ .coordinateSpace(name: "SpatialScene")
66
+ }
67
+ }
68
+ }
69
+
70
+ struct PreviewSpatializedStatic3DElement: View {
71
+ var sceneId: String
72
+
73
+ init() {
74
+ let spatialScene = SpatialApp.Instance.createScene(
75
+ "http://localhost:5173/",
76
+ .window,
77
+ .visible
78
+ )
79
+
80
+ let spatializedStatic3DElement: SpatializedStatic3DElement = spatialScene.createSpatializedElement(
81
+ .SpatializedStatic3DElement
82
+ )
83
+ spatializedStatic3DElement.transform.translation.x = 200
84
+ spatializedStatic3DElement.transform.translation.y = 300
85
+ spatializedStatic3DElement.transform.translation.z = 0
86
+ spatializedStatic3DElement.name = "jack"
87
+
88
+ spatializedStatic3DElement.width = 200
89
+ spatializedStatic3DElement.height = 100
90
+
91
+ spatializedStatic3DElement.modelURL = "http://localhost:5173/public/modelasset/cone.usdz"
92
+ spatializedStatic3DElement.setParent(spatialScene)
93
+
94
+ let spatializedStatic3DElementB: SpatializedStatic3DElement = spatialScene.createSpatializedElement(
95
+ .SpatializedStatic3DElement
96
+ )
97
+ spatializedStatic3DElementB.transform.translation.x = 700
98
+ spatializedStatic3DElementB.transform.translation.y = 300
99
+ spatializedStatic3DElementB.transform.translation.z = 0
100
+ spatializedStatic3DElementB.width = 200
101
+ spatializedStatic3DElementB.height = 200
102
+ spatializedStatic3DElementB.name = "tom"
103
+
104
+ spatializedStatic3DElementB.modelURL = "http://localhost:5173/public/modelasset/vehicle-speedster.usdz"
105
+ spatializedStatic3DElementB.setParent(spatialScene)
106
+
107
+ sceneId = spatialScene.id
108
+
109
+ print("spatialScene \(spatialScene)")
110
+
111
+ let spatializedElement: SpatializedDynamic3DElement = spatialScene.createSpatializedElement(
112
+ .SpatializedDynamic3DElement
113
+ )
114
+
115
+ spatializedElement.name = "dynamicCubes"
116
+ spatializedElement.transform.translation.x = 400
117
+ spatializedElement.transform.translation.y = 400
118
+ spatializedElement.transform.translation.z = 0
119
+ spatializedElement.width = 200
120
+ spatializedElement.height = 200
121
+ spatializedElement.setParent(spatialScene)
122
+ }
123
+
124
+ var body: some View {
125
+ SpatialSceneContentView(sceneId: sceneId, width: 1200, height: 1000)
126
+ }
127
+ }
128
+
129
+ struct PreviewSpatialized2DElement: View {
130
+ var sceneId: String
131
+
132
+ init() {
133
+ let spatialScene = SpatialApp.Instance.createScene(
134
+ "http://localhost:5173/",
135
+ .window,
136
+ .visible
137
+ )
138
+
139
+ let spatializedElementA: Spatialized2DElement = spatialScene.createSpatializedElement(
140
+ .Spatialized2DElement
141
+ )
142
+ spatializedElementA.transform.translation.x = 200
143
+ spatializedElementA.transform.translation.y = 300
144
+ spatializedElementA.transform.translation.z = 0
145
+ spatializedElementA.name = "jack"
146
+
147
+ spatializedElementA.width = 200
148
+ spatializedElementA.height = 100
149
+ spatializedElementA.load("http://localhost:5173/src/")
150
+
151
+ spatializedElementA.setParent(spatialScene)
152
+
153
+ let spatializedElementB: Spatialized2DElement = spatialScene.createSpatializedElement(
154
+ .Spatialized2DElement
155
+ )
156
+ spatializedElementB.transform.translation.x = 400
157
+ spatializedElementB.transform.translation.y = 300
158
+ spatializedElementB.transform.translation.z = 0
159
+ spatializedElementB.name = "jack"
160
+
161
+ spatializedElementB.width = 200
162
+ spatializedElementB.height = 100
163
+ spatializedElementB.load("http://localhost:5173/src/embed/")
164
+ spatializedElementB.setParent(spatialScene)
165
+
166
+ sceneId = spatialScene.id
167
+
168
+ print("spatialScene \(spatialScene)")
169
+ }
170
+
171
+ var body: some View {
172
+ SpatialSceneContentView(sceneId: sceneId, width: 1200, height: 1000)
173
+ }
174
+ }
175
+
176
+ struct PreviewSpatializedDynamic3DElement: View {
177
+ var sceneId: String
178
+
179
+ init() {
180
+ let spatialScene = SpatialApp.Instance.createScene(
181
+ "http://localhost:5173/",
182
+ .window,
183
+ .visible
184
+ )
185
+
186
+ let spatializedElement: SpatializedDynamic3DElement = spatialScene.createSpatializedElement(
187
+ .SpatializedDynamic3DElement
188
+ )
189
+
190
+ spatializedElement.name = "dynamicCubes"
191
+ spatializedElement.transform.translation.x = 100
192
+ spatializedElement.transform.translation.y = 100
193
+ spatializedElement.transform.translation.z = 0
194
+ spatializedElement.width = 200
195
+ spatializedElement.height = 200
196
+ spatializedElement.setParent(spatialScene)
197
+
198
+ sceneId = spatialScene.id
199
+
200
+ print("spatialScene \(spatialScene)")
201
+ }
202
+
203
+ var body: some View {
204
+ SpatialSceneContentView(sceneId: sceneId, width: 1200, height: 1000)
205
+ }
206
+ }
207
+
208
+ #Preview("PreviewSpatializedDynamic3DElement") {
209
+ PreviewSpatializedDynamic3DElement()
210
+ }
211
+
212
+ #Preview("PreviewSpatializedStatic3DElementWithRotation") {
213
+ PreviewSpatializedStatic3DElement()
214
+ }
215
+
216
+ #Preview("PreviewSpatialized2DElement") {
217
+ PreviewSpatialized2DElement()
218
+ }
@@ -0,0 +1,53 @@
1
+ import _RealityKit_SwiftUI
2
+ import SwiftUI
3
+
4
+ struct SpatialSceneView: View {
5
+ @State var spatialScene: SpatialScene
6
+ @State private var windowResizeInProgress = false
7
+ @State private var timer: Timer?
8
+
9
+ var body: some View {
10
+ GeometryReader3D { proxy3D in
11
+ let width = proxy3D.size.width
12
+ let height = proxy3D.size.height
13
+
14
+ SceneHandlerUIView(spatialScene: spatialScene).onChange(of: proxy3D.size) {
15
+ windowResizeInProgress = true
16
+ if timer != nil {
17
+ timer!.invalidate()
18
+ }
19
+ // If we don't detect resolution change after x seconds we treat the resize as complete
20
+ timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
21
+ windowResizeInProgress = false
22
+ timer = nil
23
+ }
24
+ spatialScene.updateSize3D(proxy3D.size)
25
+ }
26
+ .onAppear {
27
+ spatialScene.moveToState(.visible, nil)
28
+ spatialScene.updateSize3D(proxy3D.size)
29
+ }
30
+
31
+ if windowResizeInProgress {
32
+ let x = width / 2
33
+ let y = height / 2
34
+ VStack {}.frame(width: width, height: height).glassBackgroundEffect().padding3D(.front, -100_000)
35
+ .position(x: x, y: y)
36
+ } else {
37
+ SpatialSceneContentView(sceneId: spatialScene.id, width: width, height: height)
38
+ .ornament(attachmentAnchor: .scene(.top), contentAlignment: .center) {
39
+ if pwaManager.display != .fullscreen {
40
+ ZStack {
41
+ SpatialNavView(
42
+ spatialScene: spatialScene
43
+ )
44
+ .offset(y: -15)
45
+ }.frame(height: 100)
46
+ }
47
+ }.volumeBaseplateVisibility(
48
+ spatialScene.sceneConfig?.baseplateVisibility ?? .automatic
49
+ )
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,96 @@
1
+ import RealityKit
2
+ import SwiftUI
3
+
4
+ extension View {
5
+ @ViewBuilder
6
+ func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
7
+ if condition {
8
+ transform(self)
9
+ } else {
10
+ self
11
+ }
12
+ }
13
+ }
14
+
15
+ class Spatialized2DViewGestureData {
16
+ var dragStarted = false
17
+ var dragStart: CGFloat = 0.0
18
+ var dragVelocity: CGFloat = 0.0
19
+ }
20
+
21
+ struct Spatialized2DElementView: View {
22
+ @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
23
+ @Environment(SpatialScene.self) var spatialScene: SpatialScene
24
+
25
+ private var spatialized2DElement: Spatialized2DElement {
26
+ return spatializedElement as! Spatialized2DElement
27
+ }
28
+
29
+ @State private var gestureData = Spatialized2DViewGestureData()
30
+
31
+ var body: some View {
32
+ // Display child spatialized2DElements
33
+ ZStack(alignment: Alignment.topLeading) {
34
+ // Display the main webview
35
+ spatialized2DElement.getView()
36
+ .materialWithBorderCorner(
37
+ spatialized2DElement.backgroundMaterial,
38
+ spatialized2DElement.cornerRadius,
39
+ .window
40
+ )
41
+ .simultaneousGesture(spatialized2DElement.scrollPageEnabled ? dragWebGesture : nil)
42
+
43
+ let childrenOfSpatialized2DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.Spatialized2DElement).values)
44
+
45
+ ForEach(childrenOfSpatialized2DElement, id: \.id) { child in
46
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
47
+ Spatialized2DElementView()
48
+ }
49
+ .environment(child)
50
+ }
51
+
52
+ let childrenOfSpatializedStatic3DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.SpatializedStatic3DElement).values)
53
+ ForEach(childrenOfSpatializedStatic3DElement, id: \.id) { child in
54
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
55
+ SpatializedStatic3DView()
56
+ }
57
+ .environment(child)
58
+ }
59
+
60
+ let childrenOfSpatializedDynamic3DElement: [SpatializedElement] = Array(spatialized2DElement.getChildrenOfType(.SpatializedDynamic3DElement).values)
61
+
62
+ ForEach(childrenOfSpatializedDynamic3DElement, id: \.id) { child in
63
+ SpatializedElementView(parentScrollOffset: spatialized2DElement.scrollOffset) {
64
+ SpatializedDynamic3DView()
65
+ }
66
+ .environment(child)
67
+ }
68
+ }
69
+ }
70
+
71
+ private var dragWebGesture: some Gesture {
72
+ DragGesture()
73
+ .onChanged { gesture in
74
+ print("\(spatialized2DElement.name) dragWebGesture")
75
+ if spatialized2DElement.scrollPageEnabled {
76
+ if !gestureData.dragStarted {
77
+ gestureData.dragStarted = true
78
+ gestureData.dragStart = (gesture.translation.height)
79
+ }
80
+
81
+ // TODO: this should have velocity
82
+ let delta = gestureData.dragStart - gesture.translation.height
83
+ gestureData.dragStart = gesture.translation.height
84
+ spatialScene.updateDeltaScrollOffset(Vec2(x: 0, y: delta))
85
+ }
86
+ }
87
+ .onEnded { _ in
88
+ print("\(spatialized2DElement.name) dragWebGestureEnd")
89
+ if spatialized2DElement.scrollPageEnabled {
90
+ gestureData.dragStarted = false
91
+ gestureData.dragStart = 0
92
+ spatialScene.stopScrolling()
93
+ }
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,173 @@
1
+ import RealityKit
2
+ import SwiftUI
3
+
4
+ struct SpatializedDynamic3DView: View {
5
+ @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
6
+ @Environment(SpatialScene.self) var spatialScene: SpatialScene
7
+ @State private var isDrag = false
8
+ @State private var isRotate = false
9
+ @State private var isScale = false
10
+
11
+ private var spatializedDynamic3DElement: SpatializedDynamic3DElement {
12
+ return spatializedElement as! SpatializedDynamic3DElement
13
+ }
14
+
15
+ var spatialTapEvent: some Gesture {
16
+ SpatialTapGesture(count: 1).targetedToAnyEntity()
17
+ .onEnded { value in
18
+ if let entity = value.entity as? SpatialEntity {
19
+ spatialScene.sendWebMsg(entity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D)))
20
+ } else {
21
+ if let spatialEntity = SpatialEntity.findNearestParent(entity: value.entity) {
22
+ spatialScene.sendWebMsg(spatialEntity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D)))
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ var rotate3dEvent: some Gesture {
29
+ RotateGesture3D().targetedToAnyEntity().onChanged { value in
30
+ // Always forward rotate gesture events to JS
31
+ if let entity = value.entity as? SpatialEntity {
32
+ if !isRotate {
33
+ let startEvent = WebSpatialRotateStartGuestureEvent(
34
+ detail: .init(
35
+ rotation: value.rotation,
36
+ startAnchor3D: value.startAnchor3D,
37
+ startLocation3D: value.startLocation3D
38
+ )
39
+ )
40
+ spatialScene.sendWebMsg(entity.spatialId, startEvent)
41
+ isRotate = true
42
+ } else {
43
+ let gestureEvent = WebSpatialRotateGuestureEvent(
44
+ detail: .init(
45
+ rotation: value.rotation,
46
+ startAnchor3D: value.startAnchor3D,
47
+ startLocation3D: value.startLocation3D
48
+ )
49
+ )
50
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
51
+ }
52
+ }
53
+ }.onEnded { value in
54
+ // Always forward rotate end event to JS
55
+ if let entity = value.entity as? SpatialEntity {
56
+ let gestureEvent = WebSpatialRotateEndGuestureEvent(
57
+ detail: .init(
58
+ rotation: value.rotation,
59
+ startAnchor3D: value.startAnchor3D,
60
+ startLocation3D: value.startLocation3D
61
+ )
62
+ )
63
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
64
+ }
65
+ isRotate = false
66
+ }
67
+ }
68
+
69
+ var magnifyEvent: some Gesture {
70
+ MagnifyGesture().targetedToAnyEntity().onChanged { value in
71
+ // Always forward magnify gesture events to JS
72
+ if let entity = value.entity as? SpatialEntity {
73
+ if !isScale {
74
+ let startEvent = WebSpatialMagnifyStartGuestureEvent(
75
+ detail: .init(
76
+ magnification: value.magnification,
77
+ velocity: value.velocity,
78
+ startLocation3D: value.startLocation3D,
79
+ startAnchor3D: value.startAnchor3D
80
+ )
81
+ )
82
+ spatialScene.sendWebMsg(entity.spatialId, startEvent)
83
+ isScale = true
84
+ } else {
85
+ let gestureEvent = WebSpatialMagnifyGuestureEvent(
86
+ detail: .init(
87
+ magnification: value.magnification,
88
+ velocity: value.velocity,
89
+ startLocation3D: value.startLocation3D,
90
+ startAnchor3D: value.startAnchor3D
91
+ )
92
+ )
93
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
94
+ }
95
+ }
96
+ }.onEnded { value in
97
+ // Always forward magnify end event to JS
98
+ if let entity = value.entity as? SpatialEntity {
99
+ let gestureEvent = WebSpatialMagnifyEndGuestureEvent(
100
+ detail: .init(
101
+ magnification: value.magnification,
102
+ velocity: value.velocity,
103
+ startLocation3D: value.startLocation3D,
104
+ startAnchor3D: value.startAnchor3D
105
+ )
106
+ )
107
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
108
+ }
109
+ isScale = false
110
+ }
111
+ }
112
+
113
+ var dragEvent: some Gesture {
114
+ DragGesture().targetedToAnyEntity().onChanged { value in
115
+ // Always forward drag gesture events to JS
116
+ if let entity = value.entity as? SpatialEntity {
117
+ if !isDrag {
118
+ let startEvent = WebSpatialDragStartGuestureEvent(
119
+ detail: .init(
120
+ location3D: value.location3D,
121
+ startLocation3D: value.startLocation3D,
122
+ translation3D: value.translation3D,
123
+ predictedEndTranslation3D: value.predictedEndTranslation3D,
124
+ predictedEndLocation3D: value.predictedEndLocation3D,
125
+ velocity: value.velocity
126
+ )
127
+ )
128
+ spatialScene.sendWebMsg(entity.spatialId, startEvent)
129
+ isDrag = true
130
+ } else {
131
+ let gestureEvent = WebSpatialDragGuestureEvent(
132
+ detail: .init(
133
+ location3D: value.location3D,
134
+ startLocation3D: value.startLocation3D,
135
+ translation3D: value.translation3D,
136
+ predictedEndTranslation3D: value.predictedEndTranslation3D,
137
+ predictedEndLocation3D: value.predictedEndLocation3D,
138
+ velocity: value.velocity
139
+ )
140
+ )
141
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
142
+ }
143
+ }
144
+ }.onEnded { value in
145
+ // Always forward drag end event to JS
146
+ if let entity = value.entity as? SpatialEntity {
147
+ let gestureEvent = WebSpatialDragEndGuestureEvent(
148
+ detail: .init(
149
+ location3D: value.location3D,
150
+ startLocation3D: value.startLocation3D,
151
+ translation3D: value.translation3D,
152
+ predictedEndTranslation3D: value.predictedEndTranslation3D,
153
+ predictedEndLocation3D: value.predictedEndLocation3D,
154
+ velocity: value.velocity
155
+ )
156
+ )
157
+ spatialScene.sendWebMsg(entity.spatialId, gestureEvent)
158
+ }
159
+ isDrag = false
160
+ }
161
+ }
162
+
163
+ var body: some View {
164
+ RealityView(make: { content in
165
+ let rootEntity = spatializedDynamic3DElement.getRoot()
166
+ content.add(rootEntity)
167
+ })
168
+ .simultaneousGesture(spatialTapEvent)
169
+ .simultaneousGesture(rotate3dEvent)
170
+ .simultaneousGesture(dragEvent)
171
+ .simultaneousGesture(magnifyEvent)
172
+ }
173
+ }