Skip to content

Commit 2ca5de7

Browse files
author
Mark Fine
committed
ARCore iOS SDK 1.46.0
1 parent 686fe38 commit 2ca5de7

15 files changed

Lines changed: 1393 additions & 311 deletions

File tree

Examples/AugmentedFacesExample/AugmentedFacesExample.xcodeproj/project.pbxproj

Lines changed: 118 additions & 90 deletions
Large diffs are not rendered by default.

Examples/AugmentedFacesExample/AugmentedFacesExample/AppDelegate.swift

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright 2024 Google LLC. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import SwiftUI
18+
19+
@main
20+
struct AugmentedFacesExampleApp: App {
21+
var body: some Scene {
22+
WindowGroup {
23+
ContentView()
24+
}
25+
}
26+
}

Examples/AugmentedFacesExample/AugmentedFacesExample/FacesViewController.swift renamed to Examples/AugmentedFacesExample/AugmentedFacesExample/AugmentedFacesViewModel.swift

Lines changed: 78 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,46 @@
1-
/*
2-
* Copyright 2019 Google LLC. All Rights Reserved.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
1+
//
2+
// Copyright 2024 Google LLC. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
1616

1717
import ARCore
1818
import AVFoundation
19+
import CoreGraphics
20+
import CoreImage
1921
import CoreMedia
2022
import CoreMotion
23+
import Foundation
2124
import SceneKit
2225
import UIKit
26+
import simd
2327

2428
/// Demonstrates how to use ARCore Augmented Faces with SceneKit.
25-
public final class FacesViewController: UIViewController {
29+
class AugmentedFacesViewModel: NSObject, ObservableObject {
2630

2731
// MARK: - Member Variables
28-
private var needToShowFatalError = false
29-
private var alertWindowTitle = "Nothing"
30-
private var alertMessage = "Nothing"
31-
private var viewDidAppearReached = false
32+
33+
@Published var showsAlert = false
34+
@Published private(set) var alertWindowTitle = ""
35+
@Published private(set) var alertMessage = ""
36+
var viewSize: CGSize?
3237

3338
// MARK: - Camera / Scene properties
3439

3540
private var captureDevice: AVCaptureDevice?
3641
private var captureSession: AVCaptureSession?
37-
private var videoFieldOfView = Float(0)
38-
private lazy var cameraImageLayer = CALayer()
39-
private lazy var sceneView = SCNView()
42+
@Published private(set) var scene: SCNScene?
43+
@Published private(set) var pointOfView = SCNNode()
4044
private lazy var sceneCamera = SCNCamera()
4145
private lazy var motionManager = CMMotionManager()
4246

@@ -55,8 +59,8 @@ public final class FacesViewController: UIViewController {
5559

5660
// MARK: - Implementation methods
5761

58-
override public func viewDidLoad() {
59-
super.viewDidLoad()
62+
override init() {
63+
super.init()
6064

6165
if !setupScene() {
6266
return
@@ -69,25 +73,17 @@ public final class FacesViewController: UIViewController {
6973
}
7074

7175
do {
72-
faceSession = try GARAugmentedFaceSession(fieldOfView: videoFieldOfView)
76+
faceSession = try GARAugmentedFaceSession(
77+
fieldOfView: captureDevice?.activeFormat.videoFieldOfView ?? 0)
7378
} catch {
7479
alertWindowTitle = "A fatal error occurred."
7580
alertMessage = "Failed to create session. Error description: \(error)"
76-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
77-
}
78-
}
79-
80-
override public func viewDidAppear(_ animated: Bool) {
81-
super.viewDidAppear(animated)
82-
83-
viewDidAppearReached = true
84-
85-
if needToShowFatalError {
86-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
81+
showsAlert = true
8782
}
8883
}
8984

9085
/// Create the scene view from a scene and supporting nodes, and add to the view.
86+
///
9187
/// The scene is loaded from 'fox_face.scn' which was created from 'canonical_face_mesh.fbx', the
9288
/// canonical face mesh asset.
9389
/// https://developers.google.com/ar/develop/developer-guides/creating-assets-for-augmented-faces
@@ -99,9 +95,10 @@ public final class FacesViewController: UIViewController {
9995
else {
10096
alertWindowTitle = "A fatal error occurred."
10197
alertMessage = "Failed to load face scene!"
102-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
98+
showsAlert = true
10399
return false
104100
}
101+
self.scene = scene
105102

106103
// SceneKit uses meters for units, while the canonical face mesh asset uses centimeters.
107104
modelRoot.simdScale = simd_float3(1, 1, 1) * 0.01
@@ -113,19 +110,7 @@ public final class FacesViewController: UIViewController {
113110
faceNode.addChildNode(faceOccluderNode)
114111
scene.rootNode.addChildNode(faceNode)
115112

116-
let cameraNode = SCNNode()
117-
cameraNode.camera = sceneCamera
118-
scene.rootNode.addChildNode(cameraNode)
119-
120-
sceneView.scene = scene
121-
sceneView.frame = view.bounds
122-
sceneView.delegate = self
123-
sceneView.rendersContinuously = true
124-
sceneView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
125-
sceneView.backgroundColor = .clear
126-
// Flip 'x' to mirror content to mimic 'selfie' mode
127-
sceneView.layer.transform = CATransform3DMakeScale(-1, 1, 1)
128-
view.addSubview(sceneView)
113+
pointOfView.camera = sceneCamera
129114

130115
faceTextureMaterial.diffuse.contents = faceImage
131116
// SCNMaterial does not premultiply alpha even with blendMode set to alpha, so do it manually.
@@ -145,7 +130,7 @@ public final class FacesViewController: UIViewController {
145130
else {
146131
alertWindowTitle = "A fatal error occurred."
147132
alertMessage = "Failed to get device from AVCaptureDevice."
148-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
133+
showsAlert = true
149134
return false
150135
}
151136

@@ -154,7 +139,7 @@ public final class FacesViewController: UIViewController {
154139
else {
155140
alertWindowTitle = "A fatal error occurred."
156141
alertMessage = "Failed to get device input from AVCaptureDeviceInput."
157-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
142+
showsAlert = true
158143
return false
159144
}
160145

@@ -169,24 +154,20 @@ public final class FacesViewController: UIViewController {
169154
captureSession = session
170155
captureDevice = device
171156

172-
videoFieldOfView = captureDevice?.activeFormat.videoFieldOfView ?? 0
173-
174-
cameraImageLayer.contentsGravity = .center
175-
cameraImageLayer.frame = self.view.bounds
176-
view.layer.insertSublayer(cameraImageLayer, at: 0)
177-
178157
// Start capturing images from the capture session once permission is granted.
179-
getVideoPermission(permissionHandler: { granted in
158+
getVideoPermission { [weak self] granted in
159+
guard let self else { return }
180160
guard granted else {
181161
NSLog("Permission not granted to use camera.")
182162
self.alertWindowTitle = "Alert"
183163
self.alertMessage = "Permission not granted to use camera."
184-
self.popupAlertWindowOnError(
185-
alertWindowTitle: self.alertWindowTitle, alertMessage: self.alertMessage)
164+
self.showsAlert = true
186165
return
187166
}
188-
self.captureSession?.startRunning()
189-
})
167+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
168+
self?.captureSession?.startRunning()
169+
}
170+
}
190171

191172
return true
192173
}
@@ -197,7 +178,7 @@ public final class FacesViewController: UIViewController {
197178
guard motionManager.isDeviceMotionAvailable else {
198179
alertWindowTitle = "Alert"
199180
alertMessage = "Device does not have motion sensors."
200-
popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
181+
showsAlert = true
201182
return false
202183
}
203184
motionManager.deviceMotionUpdateInterval = 0.01
@@ -242,49 +223,28 @@ public final class FacesViewController: UIViewController {
242223
// 'forward' (Z+) opposite of SceneKit's forward (Z-), so rotate to orient correctly.
243224
node.simdLocalRotate(by: simd_quatf(angle: .pi, axis: simd_float3(0, 1, 0)))
244225
}
245-
246-
private func popupAlertWindowOnError(alertWindowTitle: String, alertMessage: String) {
247-
if !self.viewDidAppearReached {
248-
self.needToShowFatalError = true
249-
// Then the process will proceed to viewDidAppear, which will popup an alert window when needToShowFatalError is true.
250-
return
251-
}
252-
// viewDidAppearReached is true, so we can pop up window now.
253-
let alertController = UIAlertController(
254-
title: alertWindowTitle, message: alertMessage, preferredStyle: .alert)
255-
alertController.addAction(
256-
UIAlertAction(
257-
title: NSLocalizedString("OK", comment: "Default action"), style: .default,
258-
handler: { _ in
259-
self.needToShowFatalError = false
260-
}))
261-
self.present(alertController, animated: true, completion: nil)
262-
}
263-
264226
}
265227

266228
// MARK: - Camera delegate
267229

268-
extension FacesViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
230+
extension AugmentedFacesViewModel: AVCaptureVideoDataOutputSampleBufferDelegate {
269231

270232
public func captureOutput(
271233
_ output: AVCaptureOutput,
272234
didOutput sampleBuffer: CMSampleBuffer,
273235
from connection: AVCaptureConnection
274236
) {
275-
guard let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
276-
let deviceMotion = motionManager.deviceMotion
237+
guard let imgBuffer = sampleBuffer.imageBuffer, let deviceMotion = motionManager.deviceMotion
277238
else {
278239
NSLog("In captureOutput, imgBuffer or deviceMotion is nil.")
279240
return
280241
}
281242

282-
let frameTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
283-
243+
let frameTime = sampleBuffer.presentationTimeStamp.seconds
284244
// Use the device's gravity vector to determine which direction is up for a face. This is the
285245
// positive counter-clockwise rotation of the device relative to landscape left orientation.
286246
let rotation = 2 * .pi - atan2(deviceMotion.gravity.x, deviceMotion.gravity.y) + .pi / 2
287-
let rotationDegrees = (UInt)(rotation * 180 / .pi) % 360
247+
let rotationDegrees = UInt(rotation * 180 / .pi) % 360
288248

289249
faceSession?.update(with: imgBuffer, timestamp: frameTime, recognitionRotation: rotationDegrees)
290250
}
@@ -293,7 +253,21 @@ extension FacesViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
293253

294254
// MARK: - Scene Renderer delegate
295255

296-
extension FacesViewController: SCNSceneRendererDelegate {
256+
extension AugmentedFacesViewModel: SCNSceneRendererDelegate {
257+
258+
/// Calculates the rectangle to crop the camera image to so it fits the viewport.
259+
///
260+
/// - Parameters:
261+
/// - viewSize: The size of the view.
262+
/// - extent: The extent of the `CIImage`.
263+
/// - Returns: the rectangle to crop to.
264+
private func cropRect(for viewSize: CGSize, extent: CGRect) -> CGRect {
265+
CGRect(
266+
x: extent.maxX / 2 - viewSize.width / 2,
267+
y: extent.minY / 2 - viewSize.height / 2,
268+
width: viewSize.width,
269+
height: viewSize.height)
270+
}
297271

298272
public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
299273
guard let frame = faceSession?.currentFrame else {
@@ -312,31 +286,26 @@ extension FacesViewController: SCNSceneRendererDelegate {
312286
updateTransform(face.transform(for: .foreheadLeft), for: foreheadLeftNode)
313287
updateTransform(face.transform(for: .foreheadRight), for: foreheadRightNode)
314288
}
289+
guard let viewSize else { return }
315290

316291
// Set the scene camera's transform to the projection matrix for this frame.
317-
sceneCamera.projectionTransform = SCNMatrix4.init(
292+
sceneCamera.projectionTransform = SCNMatrix4(
318293
frame.projectionMatrix(
319-
forViewportSize: cameraImageLayer.bounds.size,
294+
forViewportSize: viewSize,
320295
presentationOrientation: .portrait,
321296
mirrored: false,
322297
zNear: 0.05,
323-
zFar: 100)
324-
)
325-
326-
// Update the camera image layer's transform to the display transform for this frame.
327-
CATransaction.begin()
328-
CATransaction.setAnimationDuration(0)
329-
cameraImageLayer.contents = frame.capturedImage as CVPixelBuffer
330-
cameraImageLayer.setAffineTransform(
331-
frame.displayTransform(
332-
forViewportSize: cameraImageLayer.bounds.size,
333-
presentationOrientation: .portrait,
334-
mirrored: true)
335-
)
336-
CATransaction.commit()
298+
zFar: 100))
299+
300+
let ciImage = CIImage(cvPixelBuffer: frame.capturedImage).transformed(
301+
by: frame.displayTransform(
302+
forViewportSize: viewSize,
303+
presentationOrientation: .portraitUpsideDown,
304+
mirrored: false))
305+
scene?.background.contents = CIContext().createCGImage(
306+
ciImage, from: cropRect(for: viewSize, extent: ciImage.extent))
337307

338308
// Only show AR content when a face is detected.
339-
sceneView.scene?.rootNode.isHidden = frame.face == nil
309+
scene?.rootNode.isHidden = frame.face == nil
340310
}
341-
342311
}

0 commit comments

Comments
 (0)