From 0fbc36f80d873303b5246a47e0ad8651c6e5356a Mon Sep 17 00:00:00 2001 From: rUv Date: Sat, 22 Aug 2026 17:29:23 -0400 Subject: [PATCH] feat(ios): capture ARKit scene depth for RuView --- .../RuViewLiDAR/LiDARCaptureManager.swift | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 integrations/iphone-lidar/native/RuViewLiDAR/LiDARCaptureManager.swift diff --git a/integrations/iphone-lidar/native/RuViewLiDAR/LiDARCaptureManager.swift b/integrations/iphone-lidar/native/RuViewLiDAR/LiDARCaptureManager.swift new file mode 100644 index 00000000..e1a15c88 --- /dev/null +++ b/integrations/iphone-lidar/native/RuViewLiDAR/LiDARCaptureManager.swift @@ -0,0 +1,142 @@ +import ARKit +import CoreVideo +import Foundation + +@MainActor +final class LiDARCaptureManager: NSObject, ObservableObject { + enum State: Equatable { + case idle + case unsupported + case running + case failed(String) + } + + @Published private(set) var state: State = .idle + @Published private(set) var lastFrame: RuViewLiDARFrame? + @Published private(set) var framesPerSecond: Double = 0 + + let session = ARSession() + var onFrame: (@Sendable (RuViewLiDARFrame) -> Void)? + + private var sequence: UInt64 = 0 + private var lastTimestamp: TimeInterval? + private let processingQueue = DispatchQueue(label: "one.ruv.lidar.capture", qos: .userInitiated) + + override init() { + super.init() + session.delegate = self + session.delegateQueue = processingQueue + } + + func start(smoothed: Bool = false) { + let configuration = ARWorldTrackingConfiguration() + let semantic: ARConfiguration.FrameSemantics = smoothed ? .smoothedSceneDepth : .sceneDepth + + guard ARWorldTrackingConfiguration.supportsFrameSemantics(semantic) else { + state = .unsupported + return + } + + configuration.frameSemantics.insert(semantic) + configuration.worldAlignment = .gravity + session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) + state = .running + } + + func stop() { + session.pause() + state = .idle + } + + nonisolated private func makeFrame(from frame: ARFrame) -> RuViewLiDARFrame? { + guard let sceneDepth = frame.sceneDepth ?? frame.smoothedSceneDepth else { return nil } + let depthMap = sceneDepth.depthMap + let confidenceMap = sceneDepth.confidenceMap + + CVPixelBufferLockBaseAddress(depthMap, .readOnly) + defer { CVPixelBufferUnlockBaseAddress(depthMap, .readOnly) } + + guard CVPixelBufferGetPixelFormatType(depthMap) == kCVPixelFormatType_DepthFloat32, + let depthBase = CVPixelBufferGetBaseAddress(depthMap) else { + return nil + } + + let width = CVPixelBufferGetWidth(depthMap) + let height = CVPixelBufferGetHeight(depthMap) + let stride = CVPixelBufferGetBytesPerRow(depthMap) / MemoryLayout.size + let pointer = depthBase.assumingMemoryBound(to: Float.self) + + var meters = [Float]() + meters.reserveCapacity(width * height) + for y in 0.. 0 ? value : 0) + } + } + + var confidence = [UInt8](repeating: 0, count: width * height) + if let confidenceMap { + CVPixelBufferLockBaseAddress(confidenceMap, .readOnly) + if let confidenceBase = CVPixelBufferGetBaseAddress(confidenceMap) { + let confidenceStride = CVPixelBufferGetBytesPerRow(confidenceMap) + let confidencePointer = confidenceBase.assumingMemoryBound(to: UInt8.self) + for y in 0.. 0 { framesPerSecond = 1.0 / delta } + } + lastTimestamp = frame.timestamp + lastFrame = corrected + onFrame?(corrected) + } + } + + nonisolated func session(_ session: ARSession, didFailWithError error: Error) { + Task { @MainActor in + state = .failed(error.localizedDescription) + } + } +}