Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import UIKit
- import SceneKit
- import ARKit
- class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate {
- @IBOutlet var sceneView: ARSCNView!
- @IBOutlet weak var screenGazeLabel: UILabel!
- var faceNode: SCNNode = SCNNode()
- var eyeLNode: SCNNode = {
- let geometry = SCNCone(topRadius: 0.005, bottomRadius: 0, height: 0.2)
- geometry.radialSegmentCount = 3
- geometry.firstMaterial?.diffuse.contents = UIColor.blue
- let node = SCNNode()
- node.geometry = geometry
- node.eulerAngles.x = -.pi / 2
- node.position.z = 0.1
- let parentNode = SCNNode()
- parentNode.addChildNode(node)
- return parentNode
- }()
- var eyeRNode: SCNNode = {
- let geometry = SCNCone(topRadius: 0.005, bottomRadius: 0, height: 0.2)
- geometry.radialSegmentCount = 3
- geometry.firstMaterial?.diffuse.contents = UIColor.blue
- let node = SCNNode()
- node.geometry = geometry
- node.eulerAngles.x = -.pi / 2
- node.position.z = 0.1
- let parentNode = SCNNode()
- parentNode.addChildNode(node)
- return parentNode
- }()
- var lookAtTargetEyeLNode: SCNNode = SCNNode()
- var lookAtTargetEyeRNode: SCNNode = SCNNode()
- // actual physical size of iPhoneX screen
- let phoneScreenSize = CGSize(width: 0.0623908297, height: 0.135096943231532)
- // actual point size of iPhoneX screen
- let phoneScreenPointSize = CGSize(width: 375, height: 812)
- var virtualPhoneNode: SCNNode = SCNNode()
- var virtualScreenNode: SCNNode = {
- let screenGeometry = SCNPlane(width: 1, height: 1)
- screenGeometry.firstMaterial?.isDoubleSided = true
- screenGeometry.firstMaterial?.diffuse.contents = UIColor.green
- return SCNNode(geometry: screenGeometry)
- }()
- var eyeLookAtPositionXs: [CGFloat] = []
- var eyeLookAtPositionYs: [CGFloat] = []
- override var preferredStatusBarStyle: UIStatusBarStyle {
- return UIStatusBarStyle.lightContent
- }
- override func viewDidLoad() {
- super.viewDidLoad()
- sceneView.layer.cornerRadius = 28
- // Set the view's delegate
- sceneView.delegate = self
- sceneView.session.delegate = self
- sceneView.automaticallyUpdatesLighting = true
- // Setup Scenegraph
- sceneView.scene.rootNode.addChildNode(faceNode)
- sceneView.scene.rootNode.addChildNode(virtualPhoneNode)
- virtualPhoneNode.addChildNode(virtualScreenNode)
- faceNode.addChildNode(eyeLNode)
- faceNode.addChildNode(eyeRNode)
- eyeLNode.addChildNode(lookAtTargetEyeLNode)
- eyeRNode.addChildNode(lookAtTargetEyeRNode)
- // Set LookAtTargetEye at 2 meters away from the center of eyeballs to create segment vector
- lookAtTargetEyeLNode.position.z = 2
- lookAtTargetEyeRNode.position.z = 2
- }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- // Create a session configuration
- guard ARFaceTrackingConfiguration.isSupported else { return }
- let configuration = ARFaceTrackingConfiguration()
- configuration.isLightEstimationEnabled = true
- // Run the view's session
- sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
- }
- override func viewWillDisappear(_ animated: Bool) {
- super.viewWillDisappear(animated)
- // Pause the view's session
- sceneView.session.pause()
- }
- // MARK: - ARSCNViewDelegate
- func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
- faceNode.transform = node.transform
- guard let faceAnchor = anchor as? ARFaceAnchor else { return }
- update(withFaceAnchor: faceAnchor)
- }
- // MARK: - update(ARFaceAnchor)
- func update(withFaceAnchor anchor: ARFaceAnchor) {
- eyeRNode.simdTransform = anchor.rightEyeTransform
- eyeLNode.simdTransform = anchor.leftEyeTransform
- var eyeLLookAt = CGPoint()
- var eyeRLookAt = CGPoint()
- let heightCompensation: CGFloat = 312
- DispatchQueue.main.async {
- // Perform Hit test using the ray segments that are drawn by the center of the eyeballs to somewhere two meters away at direction of where users look at to the virtual plane that place at the same orientation of the phone screen
- let phoneScreenEyeRHitTestResults = self.virtualPhoneNode.hitTestWithSegment(from: self.lookAtTargetEyeRNode.worldPosition, to: self.eyeRNode.worldPosition, options: nil)
- let phoneScreenEyeLHitTestResults = self.virtualPhoneNode.hitTestWithSegment(from: self.lookAtTargetEyeLNode.worldPosition, to: self.eyeLNode.worldPosition, options: nil)
- for result in phoneScreenEyeRHitTestResults {
- eyeRLookAt.x = CGFloat(result.localCoordinates.x) / (self.phoneScreenSize.width / 2) * self.phoneScreenPointSize.width
- eyeRLookAt.y = CGFloat(result.localCoordinates.y) / (self.phoneScreenSize.height / 2) * self.phoneScreenPointSize.height + heightCompensation
- }
- for result in phoneScreenEyeLHitTestResults {
- eyeLLookAt.x = CGFloat(result.localCoordinates.x) / (self.phoneScreenSize.width / 2) * self.phoneScreenPointSize.width
- eyeLLookAt.y = CGFloat(result.localCoordinates.y) / (self.phoneScreenSize.height / 2) * self.phoneScreenPointSize.height + heightCompensation
- }
- // Add the latest position and keep up to 8 recent position to smooth with.
- let smoothThresholdNumber: Int = 10
- self.eyeLookAtPositionXs.append((eyeRLookAt.x + eyeLLookAt.x) / 2)
- self.eyeLookAtPositionYs.append(-(eyeRLookAt.y + eyeLLookAt.y) / 2)
- self.eyeLookAtPositionXs = Array(self.eyeLookAtPositionXs.suffix(smoothThresholdNumber))
- self.eyeLookAtPositionYs = Array(self.eyeLookAtPositionYs.suffix(smoothThresholdNumber))
- let smoothEyeLookAtPositionX = self.eyeLookAtPositionXs.average!
- let smoothEyeLookAtPositionY = self.eyeLookAtPositionYs.average!
- // update eye look at labels values
- let positionX = Int(round(smoothEyeLookAtPositionX + self.phoneScreenPointSize.width / 2))
- let positionY = Int(round(smoothEyeLookAtPositionY + self.phoneScreenPointSize.height / 2))
- // Checking if user looking at screen
- var screenGaze = true
- if -100 ... 500 ~= positionX && -700 ... 1000 ~= positionY {
- screenGaze = true
- }
- else {
- screenGaze = false
- }
- self.screenGazeLabel.text = "\(screenGaze)"
- }
- }
- func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
- virtualPhoneNode.transform = (sceneView.pointOfView?.transform)!
- }
- func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
- faceNode.transform = node.transform
- guard let faceAnchor = anchor as? ARFaceAnchor else { return }
- update(withFaceAnchor: faceAnchor)
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement