Created
November 16, 2023 08:30
-
-
Save ynagatomo/9e89c0ca9bf1c408b05ded887e852920 to your computer and use it in GitHub Desktop.
A sample of Head-up Display Component for visionOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ContentView.swift | |
import SwiftUI | |
import RealityKit | |
import RealityKitContent | |
struct ContentView: View { | |
@State private var showImmersiveSpace = false | |
@State private var immersiveSpaceIsShown = false | |
@Environment(\.openImmersiveSpace) var openImmersiveSpace | |
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace | |
var body: some View { | |
VStack { | |
Model3D(named: "Scene", bundle: realityKitContentBundle) | |
.padding(.bottom, 50) | |
Text("Hello, world!") | |
Toggle("Show Immersive Space", isOn: $showImmersiveSpace) | |
.toggleStyle(.button) | |
.padding(.top, 50) | |
} | |
.padding() | |
.onChange(of: showImmersiveSpace) { _, newValue in | |
Task { | |
if newValue { | |
switch await openImmersiveSpace(id: "ImmersiveSpace") { | |
case .opened: | |
immersiveSpaceIsShown = true | |
case .error, .userCancelled: | |
fallthrough | |
@unknown default: | |
immersiveSpaceIsShown = false | |
showImmersiveSpace = false | |
} | |
} else if immersiveSpaceIsShown { | |
await dismissImmersiveSpace() | |
immersiveSpaceIsShown = false | |
} | |
} | |
} | |
} | |
} | |
#Preview(windowStyle: .automatic) { | |
ContentView() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// HUDComponent.swift | |
import ARKit | |
import RealityKit | |
import SwiftUI | |
import simd | |
/// HUDComponent provides a Head Up Display | |
/// - position: HUD position relative to the device (AVP) | |
/// - looking: if true (default), the HUD always looks at the device | |
public struct HUDComponent: Component, Codable { | |
let position: SIMD3<Float> | |
let looking: Bool | |
public init(at position: SIMD3<Float>, looking: Bool = true) { | |
self.position = position | |
self.looking = looking | |
} | |
} | |
/// HUDSystem | |
public struct HUDSystem: System { | |
static let query = EntityQuery(where: .has(HUDComponent.self)) | |
private let arkitSession = ARKitSession() | |
private let worldTrackingProvider = WorldTrackingProvider() | |
public init(scene: RealityKit.Scene) { | |
setUpSession() | |
} | |
private func setUpSession() { | |
Task { | |
do { | |
try await arkitSession.run([worldTrackingProvider]) | |
} catch { | |
print("Error: \(error)") | |
} | |
} | |
} | |
public func update(context: SceneUpdateContext) { | |
let entities = context.scene.performQuery(Self.query).map({ $0 }) | |
guard !entities.isEmpty, | |
let deviceAnchor = worldTrackingProvider.queryDeviceAnchor(atTimestamp: | |
CACurrentMediaTime()) else { return } | |
let cameraTransform = Transform(matrix: deviceAnchor.originFromAnchorTransform) | |
for entity in entities { | |
guard let hud = entity.components[HUDComponent.self] else { return } | |
if hud.looking { | |
entity.look(at: cameraTransform.translation, | |
from: entity.position(relativeTo: nil), | |
relativeTo: nil, | |
forward: .positiveZ) | |
} | |
entity.transform.translation | |
= simd_act(cameraTransform.rotation, hud.position) + cameraTransform.translation | |
} | |
} | |
} | |
#Preview { | |
RealityView { content, attachments in | |
HUDSystem.registerSystem() | |
HUDComponent.registerComponent() | |
if let entity = attachments.entity(for: "previewTag") { | |
let hudComponent = HUDComponent(at: SIMD3<Float>(0.5, 0.5, -1.0)) | |
entity.components[HUDComponent.self] = hudComponent | |
content.add(entity) | |
} | |
} attachments: { | |
Attachment(id: "previewTag") { | |
Text("Preview") | |
.font(.system(size: 100)) | |
.background(.pink) | |
} | |
} | |
.previewLayout(.sizeThatFits) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// HUDTestApp.swift | |
import SwiftUI | |
@main | |
struct HUDTestApp: App { | |
init() { | |
HUDSystem.registerSystem() | |
HUDComponent.registerComponent() | |
} | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
ImmersiveSpace(id: "ImmersiveSpace") { | |
ImmersiveView() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ImmersiveView.swift | |
import SwiftUI | |
import RealityKit | |
import RealityKitContent | |
struct ImmersiveView: View { | |
var body: some View { | |
RealityView { content, attachments in | |
// [HUD-01] | |
if let entity = attachments.entity(for: "hud01") { | |
let hudComponent = HUDComponent(at: SIMD3<Float>(0.5, 0.5, -1.0)) | |
entity.components[HUDComponent.self] = hudComponent | |
content.add(entity) | |
} | |
// [HUD-02] | |
if let entity = attachments.entity(for: "hud02") { | |
let hudComponent = HUDComponent(at: SIMD3<Float>(-0.5, 0.5, -1.0), looking: false) | |
entity.components[HUDComponent.self] = hudComponent | |
content.add(entity) | |
} | |
} attachments: { | |
Attachment(id: "hud01") { | |
HUDPanelView(title: "HUD-01") | |
} | |
Attachment(id: "hud02") { | |
HUDPanelView(title: "HUD-02") | |
} | |
} | |
} | |
} | |
#Preview { | |
ImmersiveView() | |
.previewLayout(.sizeThatFits) | |
} | |
struct HUDPanelView: View { | |
let title: String | |
var body: some View { | |
VStack { | |
Text(title) | |
.font(.title) | |
.padding() | |
HStack { | |
Button(action: {}, label: { Image(systemName: "star") }) | |
Button(action: {}, label: { Image(systemName: "list.bullet") }) | |
Button(action: {}, label: { Image(systemName: "list.bullet") }) | |
Button(action: {}, label: { Image(systemName: "slider.horizontal.3") }) | |
} | |
.font(.title2) | |
.padding() | |
Text("Status: 50%") | |
} | |
// .font(.system(size: 100)) | |
.padding() | |
.glassBackgroundEffect(in: .rect(cornerRadius: 20.0)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment