Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/ARCaptureDemoApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftUI

@main
struct ARCaptureDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
147 changes: 147 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/ARCaptureManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import Foundation
import ARKit

final class ARCaptureManager: NSObject, ObservableObject {
@Published var isCapturing = false
@Published var statusText = "Initializing AR Session..."
@Published var isSessionInterrupted = false

let session = ARSession()

private let recorder = FrameRecorder(frameInterval: 0.5)
private var hasConfiguredSession = false

override init() {
super.init()
session.delegate = self
}

func startSessionIfNeeded() {
guard !hasConfiguredSession else { return }
guard ARWorldTrackingConfiguration.isSupported else {
updatePublishedStatus(text: "ARWorldTrackingConfiguration is not supported on this device.")
return
}

let configuration = ARWorldTrackingConfiguration()
configuration.worldAlignment = .gravity

if ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) {
configuration.frameSemantics.insert(.sceneDepth)
}

session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
hasConfiguredSession = true

if ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) {
updatePublishedStatus(text: "AR running (sceneDepth supported). Ready to capture.")
} else {
updatePublishedStatus(text: "AR running (sceneDepth unavailable). Ready to capture.")
}
}

func startCapture() {
guard !isCapturing else { return }
startSessionIfNeeded()

let supportsDepth = ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth)

do {
try recorder.startNewSession(hasDepthSensor: supportsDepth)
updatePublishedStates(isCapturing: true, statusText: "Capturing...")
} catch {
updatePublishedStatus(text: "Failed to start capture: \(error.localizedDescription)")
}
}

func stopCapture() {
guard isCapturing else { return }

do {
let savedSessionURL = try recorder.stopAndFlushManifest()

if let sessionURL = savedSessionURL {
updatePublishedStates(isCapturing: false, statusText: "Capture saved: \(sessionURL.lastPathComponent)")
} else {
updatePublishedStates(isCapturing: false, statusText: "Capture stopped (no session directory).")
}
} catch {
updatePublishedStates(isCapturing: false, statusText: "Stop failed: \(error.localizedDescription)")
}
}

private func updatePublishedStatus(text: String) {
DispatchQueue.main.async {
self.statusText = text
}
}

private func updatePublishedStates(isCapturing: Bool, statusText: String) {
DispatchQueue.main.async {
self.isCapturing = isCapturing
self.statusText = statusText
}
}

private func updateInterruptionState(isInterrupted: Bool, statusText: String) {
DispatchQueue.main.async {
self.isSessionInterrupted = isInterrupted
self.statusText = statusText
}
}

private func trackingLimitedReasonDescription(_ reason: ARCamera.TrackingState.Reason) -> String {
switch reason {
case .initializing:
return "initializing"
case .excessiveMotion:
return "excessiveMotion"
case .insufficientFeatures:
return "insufficientFeatures"
case .relocalizing:
return "relocalizing"
@unknown default:
return "unknown"
}
}
}

extension ARCaptureManager: ARSessionDelegate {
func session(_ session: ARSession, didUpdate frame: ARFrame) {
guard isCapturing else { return }
recorder.appendFrame(frame)
}

func session(_ session: ARSession, didFailWithError error: Error) {
updatePublishedStatus(text: "ARSession failed: \(error.localizedDescription)")
}

func sessionWasInterrupted(_ session: ARSession) {
updateInterruptionState(isInterrupted: true, statusText: "Session interrupted. Capture paused.")
if isCapturing {
stopCapture()
}
}

func sessionInterruptionEnded(_ session: ARSession) {
updateInterruptionState(isInterrupted: false, statusText: "Interruption ended. Resetting session...")
hasConfiguredSession = false
startSessionIfNeeded()
}

func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
switch camera.trackingState {
case .normal:
if isCapturing {
updatePublishedStatus(text: "Capturing...")
} else {
updatePublishedStatus(text: "Tracking normal. Ready to capture.")
}
case .notAvailable:
updatePublishedStatus(text: "Tracking unavailable.")
case .limited(let reason):
let reasonText = trackingLimitedReasonDescription(reason)
updatePublishedStatus(text: "Tracking limited: \(reasonText)")
}
}
}
17 changes: 17 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/ARSessionView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI
import ARKit
import SceneKit

struct ARSessionView: UIViewRepresentable {
let session: ARSession

func makeUIView(context: Context) -> ARSCNView {
let view = ARSCNView(frame: .zero)
view.automaticallyUpdatesLighting = true
view.scene = SCNScene()
view.session = session
return view
}

func updateUIView(_ uiView: ARSCNView, context: Context) {}
}
11 changes: 11 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/CaptureRecord.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

struct CaptureRecord: Codable {
let imageFile: String
let timestamp: TimeInterval
let transform: [Float]
let depthFile: String?
let depthWidth: Int?
let depthHeight: Int?
let depthBytesPerRow: Int?
}
9 changes: 9 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/CaptureSessionManifest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

struct CaptureSessionManifest: Codable {
let sessionId: String
let startedAtISO8601: String
let hasDepthSensor: Bool
let frameIntervalSeconds: Double
let records: [CaptureRecord]
}
45 changes: 45 additions & 0 deletions ios-6dof-demo/ARCaptureDemo/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import SwiftUI

struct ContentView: View {
@StateObject private var manager = ARCaptureManager()

var body: some View {
ZStack(alignment: .bottom) {
ARSessionView(session: manager.session)
.ignoresSafeArea()

VStack(spacing: 12) {
Text(manager.statusText)
.font(.footnote)
.foregroundStyle(.white)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(.black.opacity(0.55), in: Capsule())

HStack(spacing: 12) {
Button("Start Capture") {
manager.startCapture()
}
.buttonStyle(.borderedProminent)
.tint(.green)
.disabled(manager.isCapturing)

Button("Stop Capture") {
manager.stopCapture()
}
.buttonStyle(.borderedProminent)
.tint(.red)
.disabled(!manager.isCapturing)
}
}
.padding(.bottom, 24)
}
.onAppear {
manager.startSessionIfNeeded()
}
}
}

#Preview {
ContentView()
}
Loading