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
1717import ARCore
1818import AVFoundation
19+ import CoreGraphics
20+ import CoreImage
1921import CoreMedia
2022import CoreMotion
23+ import Foundation
2124import SceneKit
2225import 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