I have an app which have an Immersive Space view and it needs the user to have a button in the bottom which have a fixed place in front of the user head like a dashboard in game or so but when the user get too close to any3d object in the view it could cover the button and make it inaccessible and it mainly would prevent the app for being approved like that in appstoreconnect I was working before on SceneKit and there was something like camera view Znear and Zfar which decide when to hide the 3d model if it comes too close or gets too far and I wonder if there is something like that in realityView / RealityKit 4. Here is My Code and the screenshots follows
import SwiftUI
import RealityKit
struct ContentView: View {
@State var myHead: Entity = {
let headAnchor = AnchorEntity(.head)
headAnchor.position = [-0.02, -0.023, -0.24]
return headAnchor
}()
@State var clicked = false
var body: some View {
RealityView { content, attachments in
// create a 3d box
let mainBox = ModelEntity(mesh: .generateBox(size: [0.1, 0.1, 0.1]))
mainBox.position = [0, 1.6, -0.3]
content.add(mainBox)
content.add(myHead)
guard let attachmentEntity = attachments.entity(for: "Dashboard") else {return}
myHead.addChild(attachmentEntity)
}
attachments: {
// SwiftUI Inside Immersivre View
Attachment(id: "Dashboard") {
VStack {
Spacer()
.frame(height: 300)
Button(action: {
goClicked()
}) {
Text(clicked ? "⏸️" : "▶️")
.frame(maxWidth: 48, maxHeight: 48, alignment: .center)
.font(.extraLargeTitle)
}
.buttonStyle(.plain)
}
}
}
}
func goClicked() {
clicked.toggle()
}
}
Thanks to Apple Pro Engineer, they have a very useful answer
It is not currently possible to render a reality view attachment such that it always appears in front of other 3D models in the scene.
but they also provide a useful workaround as
import SwiftUI
import RealityKit
struct ContentView: View {
@State var myHead: Entity = {
let headAnchor = AnchorEntity(.head)
headAnchor.position = [0, -0.15, -0.4]
return headAnchor
}()
// Use a model entity to act as a "dashboard" instead of an attachment.
@State var dashboardEntity: ModelEntity = {
let dashboardEntity = ModelEntity(mesh: .generateSphere(radius: 0.02), materials: [])
dashboardEntity.generateCollisionShapes(recursive: false)
dashboardEntity.components.set(InputTargetComponent())
return dashboardEntity
}()
@State var clicked = false
var clickedMaterial = SimpleMaterial(color: .green, isMetallic: false)
var unclickedMaterial = SimpleMaterial(color: .red, isMetallic: false)
var body: some View {
RealityView { content in
// create a 3d box
let mainBox = ModelEntity(mesh: .generateBox(size: [0.1, 0.1, 0.1]), materials: [SimpleMaterial()])
mainBox.position = [0, 1.6, -0.3]
content.add(mainBox)
content.add(myHead)
myHead.addChild(dashboardEntity)
// Create a model sort group for both entities.
let group = ModelSortGroup(depthPass: .postPass)
// Sort the box entity so that it is drawn first.
let mainBoxSortComponent = ModelSortGroupComponent(group: group, order: 1)
mainBox.components.set(mainBoxSortComponent)
// Sort the dashboard entity so that it is drawn second, on top of the box.
let dashboardSortComponent = ModelSortGroupComponent(group: group, order: 2)
dashboardEntity.components.set(dashboardSortComponent)
}
update: { content in
// Update the dashboard entity's material when the value of `clicked` changes.
dashboardEntity.model?.materials = clicked ? [clickedMaterial] : [unclickedMaterial]
}
.gesture(
TapGesture()
.targetedToEntity(dashboardEntity)
.onEnded({ value in
// Toggle `clicked` when the dashboard entity is tapped.
clicked.toggle()
})
)
}
}