@webspatial/platform-visionos 1.2.1 → 1.4.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 -1
- package/web-spatial/EventEmitter.swift +11 -11
- package/web-spatial/JSBCommand.swift +38 -3
- package/web-spatial/WebMsgCommand.swift +5 -16
- package/web-spatial/WebSpatialApp.swift +10 -10
- package/web-spatial/Window.swift +2 -2
- package/web-spatial/manager/AttachmentManager.swift +84 -0
- package/web-spatial/manager/Dynamic3DManager.swift +10 -0
- package/web-spatial/manager/JSBManager.swift +1 -2
- package/web-spatial/manager/WKWebViewManager.swift +4 -4
- package/web-spatial/manifest.swift +11 -6
- package/web-spatial/model/SpatialApp.swift +60 -56
- package/web-spatial/model/SpatialScene.swift +233 -16
- package/web-spatial/model/Spatialized2DElement.swift +4 -5
- package/web-spatial/model/SpatializedDynamic3DElement.swift +12 -0
- package/web-spatial/model/SpatializedElement.swift +40 -0
- package/web-spatial/model/SpatializedStatic3DElement.swift +1 -1
- package/web-spatial/model/dynamic3d/SpatialComponent.swift +27 -27
- package/web-spatial/model/dynamic3d/SpatialEntity.swift +8 -2
- package/web-spatial/model/dynamic3d/SpatialMaterial.swift +15 -15
- package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +10 -10
- package/web-spatial/model/dynamic3d/SpatialModelResource.swift +1 -1
- package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +8 -8
- package/web-spatial/view/SceneHandlerUIView.swift +29 -1
- package/web-spatial/view/SpatialNavView.swift +52 -47
- package/web-spatial/view/SpatializedDynamic3DView.swift +88 -5
- package/web-spatial/view/SpatializedElementView.swift +85 -47
- package/web-spatial/view/SpatializedStatic3DView.swift +9 -7
- package/web-spatial/view/view-modifier/HideViewModifier.swift +2 -2
- package/web-spatial/webview/SpatialWebController.swift +42 -25
- package/web-spatial/webview/SpatialWebView.swift +5 -1
- package/web-spatial/webview/SpatialWebViewModel.swift +13 -7
- package/web-spatial.xcodeproj/project.pbxproj +13 -0
- package/web-spatialTests/NavigationCleanupTests.swift +33 -0
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
1
|
import RealityKit
|
|
2
|
+
import SwiftUI
|
|
3
3
|
|
|
4
4
|
@Observable
|
|
5
5
|
class SpatialMaterial: SpatialObject {
|
|
6
6
|
let type: SpatialMaterialType
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var resource:RealityKit.Material? {
|
|
7
|
+
|
|
8
|
+
var _resource: RealityKit.Material?
|
|
9
|
+
var resource: RealityKit.Material? {
|
|
10
10
|
_resource
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
init(_ _type:SpatialMaterialType){
|
|
12
|
+
|
|
13
|
+
init(_ _type: SpatialMaterialType) {
|
|
14
14
|
type = _type
|
|
15
15
|
super.init()
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
override func onDestroy() {
|
|
19
19
|
_resource = nil
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
@Observable
|
|
24
|
-
class SpatialUnlitMaterial: SpatialMaterial{
|
|
25
|
-
let color:UIColor
|
|
26
|
-
|
|
27
|
-
init(_ color:String, _ texture:TextureResource? = nil, _ transparent:Bool = true, _ opacity:Float = 1){
|
|
28
|
-
self.color = UIColor
|
|
24
|
+
class SpatialUnlitMaterial: SpatialMaterial {
|
|
25
|
+
let color: UIColor
|
|
26
|
+
|
|
27
|
+
init(_ color: String, _ texture: TextureResource? = nil, _ transparent: Bool = true, _ opacity: Float = 1) {
|
|
28
|
+
self.color = UIColor(Color(hex: color))
|
|
29
29
|
super.init(.UnlitMaterial)
|
|
30
30
|
var mat = UnlitMaterial()
|
|
31
|
-
mat.color = .init(tint:UIColor(Color
|
|
31
|
+
mat.color = .init(tint: UIColor(Color(hex: color)), texture: texture != nil ? .init(texture!) : nil)
|
|
32
32
|
mat.blending = transparent ? .transparent(opacity: .init(scale: opacity)) : .opaque
|
|
33
33
|
_resource = mat
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
enum SpatialMaterialType: String{
|
|
38
|
-
case UnlitMaterial
|
|
37
|
+
enum SpatialMaterialType: String {
|
|
38
|
+
case UnlitMaterial
|
|
39
39
|
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
1
|
import RealityKit
|
|
2
|
+
import SwiftUI
|
|
3
3
|
|
|
4
4
|
@Observable
|
|
5
|
-
class SpatialModelEntity: SpatialEntity{
|
|
6
|
-
private var modelEntity:Entity?
|
|
7
|
-
required init(_ modelResource:SpatialModelResource, _ _name:String = ""){
|
|
5
|
+
class SpatialModelEntity: SpatialEntity {
|
|
6
|
+
private var modelEntity: Entity?
|
|
7
|
+
required init(_ modelResource: SpatialModelResource, _ _name: String = "") {
|
|
8
8
|
super.init(_name)
|
|
9
9
|
modelEntity = modelResource.resource
|
|
10
10
|
addChild(modelEntity!)
|
|
11
11
|
generateCollisionShapes(recursive: true)
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
required init() {
|
|
15
15
|
super.init()
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
override
|
|
17
|
+
|
|
18
|
+
override func onDestroy() {
|
|
19
19
|
super.onDestroy()
|
|
20
|
-
if let modelEntity =
|
|
20
|
+
if let modelEntity = modelEntity {
|
|
21
21
|
removeChild(modelEntity)
|
|
22
22
|
}
|
|
23
23
|
modelEntity = nil
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
enum CodingKeys: String, CodingKey {
|
|
27
27
|
case id, name, isDestroyed, children, components, model
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
override func encode(to encoder: any Encoder) throws {
|
|
31
31
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
32
32
|
try container.encode(spatialId, forKey: .id)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
1
|
import RealityKit
|
|
2
|
+
import SwiftUI
|
|
3
3
|
|
|
4
4
|
@Observable
|
|
5
|
-
class SpatialTextureResource:SpatialObject {
|
|
6
|
-
|
|
7
|
-
var resource:TextureResource? {
|
|
5
|
+
class SpatialTextureResource: SpatialObject {
|
|
6
|
+
var _resource: TextureResource?
|
|
7
|
+
var resource: TextureResource? {
|
|
8
8
|
_resource
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
override init(_ url:String){
|
|
10
|
+
|
|
11
|
+
override init(_ url: String) {
|
|
12
12
|
super.init()
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
override
|
|
14
|
+
|
|
15
|
+
override func onDestroy() {
|
|
16
16
|
_resource = nil
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -10,6 +10,9 @@ struct SceneHandlerUIView: View {
|
|
|
10
10
|
@State var spatialScene: SpatialScene
|
|
11
11
|
|
|
12
12
|
@Environment(\.scenePhase) private var scenePhase
|
|
13
|
+
@Environment(\.physicalMetrics) private var converter
|
|
14
|
+
@State private var latestScaled: Double?
|
|
15
|
+
@State private var latestUnscaled: Double?
|
|
13
16
|
|
|
14
17
|
private func setResizibility(resizingRestrictions: UIWindowScene.ResizingRestrictions) {
|
|
15
18
|
sceneDelegate.window?.windowScene?
|
|
@@ -41,10 +44,35 @@ struct SceneHandlerUIView: View {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
private func updatePhysicalMetricsIfReady() {
|
|
48
|
+
if let scaled = latestScaled, let unscaled = latestUnscaled {
|
|
49
|
+
spatialScene.onUpdatePhysicalMetrics(meterToPtUnscaled: unscaled, meterToPtScaled: scaled)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
var body: some View {
|
|
54
|
+
let meterToPtScaled = converter.worldScalingCompensation(.scaled).convert(
|
|
55
|
+
1,
|
|
56
|
+
from: .meters
|
|
57
|
+
)
|
|
58
|
+
let meterToPtUnscaled = converter.worldScalingCompensation(.unscaled).convert(
|
|
59
|
+
1,
|
|
60
|
+
from: .meters
|
|
61
|
+
)
|
|
45
62
|
VStack {}
|
|
46
63
|
.onAppear {
|
|
47
|
-
|
|
64
|
+
latestScaled = meterToPtScaled
|
|
65
|
+
latestUnscaled = meterToPtUnscaled
|
|
66
|
+
updatePhysicalMetricsIfReady()
|
|
67
|
+
}
|
|
68
|
+
.onChange(of: meterToPtScaled) { _, newValue in
|
|
69
|
+
latestScaled = newValue
|
|
70
|
+
updatePhysicalMetricsIfReady()
|
|
71
|
+
}
|
|
72
|
+
.onChange(of: meterToPtUnscaled) { _, newValue in
|
|
73
|
+
latestUnscaled = newValue
|
|
74
|
+
updatePhysicalMetricsIfReady()
|
|
75
|
+
}.onAppear {
|
|
48
76
|
guard spatialScene.windowStyle == .window else {
|
|
49
77
|
return
|
|
50
78
|
}
|
|
@@ -21,7 +21,6 @@ struct NavDivider: View {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
24
|
struct NavButton: View {
|
|
26
25
|
var action: () -> Void
|
|
27
26
|
var children: Image
|
|
@@ -55,21 +54,18 @@ struct NavButton: View {
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
59
57
|
struct SpatialNavView: View {
|
|
60
58
|
static let navHeight: CGFloat = SpatialScene.navHeight
|
|
61
59
|
static let minWidth: CGFloat = 400
|
|
62
60
|
var spatialScene: SpatialScene
|
|
63
|
-
var model:SpatialWebViewModel? {
|
|
64
|
-
|
|
65
|
-
return spatialScene.spatialWebViewModel
|
|
66
|
-
}
|
|
61
|
+
var model: SpatialWebViewModel? {
|
|
62
|
+
return spatialScene.spatialWebViewModel
|
|
67
63
|
}
|
|
64
|
+
|
|
68
65
|
var url: String {
|
|
69
|
-
|
|
70
|
-
return spatialScene.url
|
|
71
|
-
}
|
|
66
|
+
return spatialScene.url
|
|
72
67
|
}
|
|
68
|
+
|
|
73
69
|
@State var navWidth: CGFloat = 0
|
|
74
70
|
@State private var showCopyTip = false
|
|
75
71
|
@State private var contentHeight: CGFloat = 60
|
|
@@ -81,36 +77,40 @@ struct SpatialNavView: View {
|
|
|
81
77
|
@State private var showNav: Double = 0
|
|
82
78
|
@State private var showUrl: Double = 0
|
|
83
79
|
@Namespace var hoverNamespace
|
|
84
|
-
|
|
85
|
-
func checkButtonState(){
|
|
80
|
+
|
|
81
|
+
func checkButtonState() {
|
|
86
82
|
canGoBack = model!.getController().webview!.canGoBack
|
|
87
83
|
canGoForward = model!.getController().webview!.canGoForward
|
|
88
84
|
}
|
|
89
|
-
|
|
85
|
+
|
|
90
86
|
func goBack() {
|
|
87
|
+
spatialScene.resetForNavigation()
|
|
91
88
|
model?.getController().webview?.goBack()
|
|
92
89
|
}
|
|
93
|
-
|
|
90
|
+
|
|
94
91
|
func goForward() {
|
|
92
|
+
spatialScene.resetForNavigation()
|
|
95
93
|
model?.getController().webview?.goForward()
|
|
96
94
|
}
|
|
97
|
-
|
|
95
|
+
|
|
98
96
|
func reload() {
|
|
97
|
+
spatialScene.resetForNavigation()
|
|
99
98
|
model?.getController().webview?.reload()
|
|
100
99
|
}
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
func navigateToURL(url: URL) {
|
|
102
|
+
spatialScene.resetForNavigation()
|
|
103
103
|
model?.load(url.absoluteString)
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
func getURL() -> URL? {
|
|
107
107
|
return model?.getController().webview?.url
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
@State var canGoBack: Bool = false
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
@State var canGoForward: Bool = false
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
var navHoverGroup: HoverEffectGroup {
|
|
115
115
|
HoverEffectGroup(hoverNamespace)
|
|
116
116
|
}
|
|
@@ -126,13 +126,15 @@ struct SpatialNavView: View {
|
|
|
126
126
|
action: { self.goBack()
|
|
127
127
|
},
|
|
128
128
|
children: Image("arrow_left"),
|
|
129
|
-
clearBackGround: true
|
|
129
|
+
clearBackGround: true
|
|
130
|
+
)
|
|
130
131
|
.disabled(!(self.canGoBack))
|
|
131
132
|
NavButton(
|
|
132
133
|
action: { self.goForward()
|
|
133
134
|
},
|
|
134
135
|
children: Image("arrow_right"),
|
|
135
|
-
clearBackGround: true
|
|
136
|
+
clearBackGround: true
|
|
137
|
+
)
|
|
136
138
|
.disabled(!(self.canGoBack))
|
|
137
139
|
NavButton(action: { self.reload() }, children: Image("refresh"), clearBackGround: true)
|
|
138
140
|
NavDivider()
|
|
@@ -156,12 +158,14 @@ struct SpatialNavView: View {
|
|
|
156
158
|
NavButton(
|
|
157
159
|
action: { self.goBack()
|
|
158
160
|
},
|
|
159
|
-
children: Image("arrow_left")
|
|
161
|
+
children: Image("arrow_left")
|
|
162
|
+
)
|
|
160
163
|
.disabled(!(self.canGoBack))
|
|
161
164
|
NavButton(
|
|
162
165
|
action: { self.goForward()
|
|
163
166
|
},
|
|
164
|
-
children: Image("arrow_right")
|
|
167
|
+
children: Image("arrow_right")
|
|
168
|
+
)
|
|
165
169
|
.disabled(!(self.canGoBack))
|
|
166
170
|
NavButton(action: { self.reload() }, children: Image("refresh"))
|
|
167
171
|
NavDivider()
|
|
@@ -187,14 +191,14 @@ struct SpatialNavView: View {
|
|
|
187
191
|
self.getURL()?.absoluteString ?? ""
|
|
188
192
|
)
|
|
189
193
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
.lineLimit(1)
|
|
195
|
+
.textSelection(.enabled)
|
|
196
|
+
.padding(12)
|
|
197
|
+
.frame(minWidth: 200)
|
|
198
|
+
.frame(maxWidth: 500)
|
|
199
|
+
.frame(height: 44)
|
|
200
|
+
.background(.black)
|
|
201
|
+
.cornerRadius(100)
|
|
198
202
|
NavButton(action: {
|
|
199
203
|
UIPasteboard.general.string = self.getURL()?.absoluteString ?? ""
|
|
200
204
|
showCopyTip = true
|
|
@@ -204,21 +208,22 @@ struct SpatialNavView: View {
|
|
|
204
208
|
withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
|
|
205
209
|
}, children: Image("copy"))
|
|
206
210
|
NavButton(
|
|
207
|
-
action: {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
action: {
|
|
212
|
+
print("open browser")
|
|
213
|
+
UIApplication.shared
|
|
214
|
+
.open(
|
|
215
|
+
URL(
|
|
216
|
+
string: url.count > 0 ? url : (
|
|
217
|
+
self.getURL()?.absoluteString ?? ""
|
|
218
|
+
)
|
|
219
|
+
)!,
|
|
220
|
+
options: [:],
|
|
221
|
+
completionHandler: nil
|
|
222
|
+
)
|
|
223
|
+
withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
|
|
224
|
+
},
|
|
225
|
+
children: Image("browser")
|
|
214
226
|
)
|
|
215
|
-
)!,
|
|
216
|
-
options: [:],
|
|
217
|
-
completionHandler: nil
|
|
218
|
-
)
|
|
219
|
-
withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
|
|
220
|
-
},
|
|
221
|
-
children: Image("browser"))
|
|
222
227
|
NavButton(action: { withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 } }, children: Image("close"))
|
|
223
228
|
}
|
|
224
229
|
.popover(isPresented: $showCopyTip) {
|
|
@@ -232,8 +237,8 @@ action: {
|
|
|
232
237
|
.cornerRadius(100)
|
|
233
238
|
.opacity(showUrl)
|
|
234
239
|
}
|
|
235
|
-
.onAppear
|
|
236
|
-
model?.addStateListener(.didFinishLoad){
|
|
240
|
+
.onAppear {
|
|
241
|
+
model?.addStateListener(.didFinishLoad) {
|
|
237
242
|
self.checkButtonState()
|
|
238
243
|
}
|
|
239
244
|
}
|
|
@@ -16,17 +16,28 @@ struct SpatializedDynamic3DView: View {
|
|
|
16
16
|
SpatialTapGesture(count: 1).targetedToAnyEntity()
|
|
17
17
|
.onEnded { value in
|
|
18
18
|
if let entity = value.entity as? SpatialEntity {
|
|
19
|
-
|
|
19
|
+
// Convert local gesture coordinates into world (global) coordinates via RealityKit.
|
|
20
|
+
let globalLocation3D = entity.convert(position: SIMD3<Float>(Float(value.location3D.x), Float(value.location3D.y), Float(value.location3D.z)), to: nil)
|
|
21
|
+
let globalPoint3D = Point3D(x: Double(globalLocation3D.x), y: Double(globalLocation3D.y), z: Double(globalLocation3D.z))
|
|
22
|
+
|
|
23
|
+
spatialScene.sendWebMsg(entity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D, globalLocation3D: globalPoint3D)))
|
|
20
24
|
} else {
|
|
21
25
|
if let spatialEntity = SpatialEntity.findNearestParent(entity: value.entity) {
|
|
22
|
-
|
|
26
|
+
// Convert using the hit entity's coordinate space, then forward to the nearest SpatialEntity.
|
|
27
|
+
let globalLocation3D = value.entity.convert(
|
|
28
|
+
position: SIMD3<Float>(Float(value.location3D.x), Float(value.location3D.y), Float(value.location3D.z)),
|
|
29
|
+
to: nil
|
|
30
|
+
)
|
|
31
|
+
let globalPoint3D = Point3D(x: Double(globalLocation3D.x), y: Double(globalLocation3D.y), z: Double(globalLocation3D.z))
|
|
32
|
+
|
|
33
|
+
spatialScene.sendWebMsg(spatialEntity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D, globalLocation3D: globalPoint3D)))
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
var rotate3dEvent: some Gesture {
|
|
29
|
-
|
|
40
|
+
makeRotateGesture3D().targetedToAnyEntity().onChanged { value in
|
|
30
41
|
// Always forward rotate gesture events to JS
|
|
31
42
|
if let entity = value.entity as? SpatialEntity {
|
|
32
43
|
let gestureEvent = WebSpatialRotateGuestureEvent(
|
|
@@ -51,6 +62,21 @@ struct SpatializedDynamic3DView: View {
|
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
private func makeRotateGesture3D() -> RotateGesture3D {
|
|
66
|
+
guard let raw = spatializedElement.rotateConstrainedToAxis else {
|
|
67
|
+
return RotateGesture3D()
|
|
68
|
+
}
|
|
69
|
+
let dx = Double(raw.x)
|
|
70
|
+
let dy = Double(raw.y)
|
|
71
|
+
let dz = Double(raw.z)
|
|
72
|
+
let len = (dx * dx + dy * dy + dz * dz).squareRoot()
|
|
73
|
+
if len < 1e-9 {
|
|
74
|
+
return RotateGesture3D()
|
|
75
|
+
}
|
|
76
|
+
let axis = RotationAxis3D(x: dx / len, y: dy / len, z: dz / len)
|
|
77
|
+
return RotateGesture3D(constrainedToAxis: axis)
|
|
78
|
+
}
|
|
79
|
+
|
|
54
80
|
var magnifyEvent: some Gesture {
|
|
55
81
|
MagnifyGesture().targetedToAnyEntity().onChanged { value in
|
|
56
82
|
// Always forward magnify gesture events to JS
|
|
@@ -76,9 +102,16 @@ struct SpatializedDynamic3DView: View {
|
|
|
76
102
|
// Always forward drag gesture events to JS
|
|
77
103
|
if let entity = value.entity as? SpatialEntity {
|
|
78
104
|
if !isDrag {
|
|
105
|
+
let globalStartLocation3D = value.entity.convert(
|
|
106
|
+
position: SIMD3<Float>(Float(value.startLocation3D.x), Float(value.startLocation3D.y), Float(value.startLocation3D.z)),
|
|
107
|
+
to: nil
|
|
108
|
+
)
|
|
109
|
+
let globalStartPoint3D = Point3D(x: Double(globalStartLocation3D.x), y: Double(globalStartLocation3D.y), z: Double(globalStartLocation3D.z))
|
|
110
|
+
|
|
79
111
|
let startEvent = WebSpatialDragStartGuestureEvent(
|
|
80
112
|
detail: .init(
|
|
81
|
-
startLocation3D: value.startLocation3D
|
|
113
|
+
startLocation3D: value.startLocation3D,
|
|
114
|
+
globalLocation3D: globalStartPoint3D
|
|
82
115
|
)
|
|
83
116
|
)
|
|
84
117
|
spatialScene.sendWebMsg(entity.spatialId, startEvent)
|
|
@@ -101,13 +134,63 @@ struct SpatializedDynamic3DView: View {
|
|
|
101
134
|
}
|
|
102
135
|
|
|
103
136
|
var body: some View {
|
|
104
|
-
RealityView(make: { content in
|
|
137
|
+
RealityView(make: { content, attachments in
|
|
105
138
|
let rootEntity = spatializedDynamic3DElement.getRoot()
|
|
106
139
|
content.add(rootEntity)
|
|
140
|
+
spatializedDynamic3DElement.setViewContent(content)
|
|
141
|
+
|
|
142
|
+
// Add existing attachments on initial creation
|
|
143
|
+
for (_, info) in spatialScene.attachmentManager.attachments {
|
|
144
|
+
if let attachmentEntity = attachments.entity(for: info.id) {
|
|
145
|
+
attachmentEntity.position = info.position
|
|
146
|
+
if let parentEntity = findSpatialEntity(info.parentEntityId) {
|
|
147
|
+
parentEntity.addChild(attachmentEntity)
|
|
148
|
+
} else {
|
|
149
|
+
rootEntity.addChild(attachmentEntity)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}, update: { _, attachments in
|
|
154
|
+
let rootEntity = spatializedDynamic3DElement.getRoot()
|
|
155
|
+
// Update attachment positions and parenting
|
|
156
|
+
for (_, info) in spatialScene.attachmentManager.attachments {
|
|
157
|
+
if let attachmentEntity = attachments.entity(for: info.id) {
|
|
158
|
+
attachmentEntity.position = info.position
|
|
159
|
+
// Re-parent if not already under the correct parent
|
|
160
|
+
if let parentEntity = findSpatialEntity(info.parentEntityId) {
|
|
161
|
+
if attachmentEntity.parent != parentEntity {
|
|
162
|
+
parentEntity.addChild(attachmentEntity)
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Parent entity might have been destroyed; fall back to root.
|
|
166
|
+
if attachmentEntity.parent != rootEntity {
|
|
167
|
+
rootEntity.addChild(attachmentEntity)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}, attachments: {
|
|
173
|
+
ForEach(Array(spatialScene.attachmentManager.attachments.values)) { info in
|
|
174
|
+
Attachment(id: info.id) {
|
|
175
|
+
info.webViewModel.getView()
|
|
176
|
+
.frame(
|
|
177
|
+
width: info.size.width,
|
|
178
|
+
height: info.size.height
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
107
182
|
})
|
|
108
183
|
.simultaneousGesture(spatialTapEvent)
|
|
109
184
|
.simultaneousGesture(rotate3dEvent)
|
|
110
185
|
.simultaneousGesture(dragEvent)
|
|
111
186
|
.simultaneousGesture(magnifyEvent)
|
|
187
|
+
.onDisappear {
|
|
188
|
+
spatializedDynamic3DElement.setViewContent(nil)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func findSpatialEntity(_ spatialId: String) -> SpatialEntity? {
|
|
193
|
+
// Look up the SpatialEntity from the SpatialScene's spatial object registry
|
|
194
|
+
return spatialScene.findSpatialObject(spatialId)
|
|
112
195
|
}
|
|
113
196
|
}
|