Detect a vertical plane and project a video on it. An image is loaded as a placeholder and replaced by the video when 'play' button is pressed
// this code requires two assets, an image "ocean" and an mp4 "team-walking" | |
import UIKit | |
import ARKit | |
import SceneKit | |
class ViewController: UIViewController, ARSCNViewDelegate { | |
// connections to storyboard | |
@IBOutlet weak var sceneView: ARSCNView! | |
@IBOutlet weak var buttonStart: UIButton! | |
@IBOutlet weak var buttonStop: UIButton! | |
// video player | |
var player : AVPlayer? | |
// standard world tracking config | |
let configuration = ARWorldTrackingConfiguration() | |
// used for video node | |
let videoNode = SCNNode(geometry: SCNPlane()) | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// set delegate so renderer fn gets called | |
sceneView.delegate = self | |
// type of plane detection | |
configuration.planeDetection = .vertical | |
// run, requests camera access first run | |
sceneView.session.run(configuration) | |
} | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
// debug | |
print("View will appear") | |
} | |
func createNode(planeAnchor: ARPlaneAnchor) -> SCNNode { | |
// set width & height | |
videoNode.geometry?.setValue(CGFloat(planeAnchor.extent.x), forKey: "width") | |
videoNode.geometry?.setValue(CGFloat(planeAnchor.extent.z), forKey: "height") | |
// now set node properties. The UIImage is a content placeholder, replaced by buttonStartTouch | |
videoNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "ocean") | |
videoNode.geometry?.firstMaterial?.isDoubleSided = true | |
videoNode.position = SCNVector3(planeAnchor.center.x, planeAnchor.center.y, planeAnchor.center.z) | |
// rotate 90 degrees about x axis <--x--> | |
videoNode.eulerAngles = SCNVector3(-90.degreesToRadians, 0, 0) | |
return videoNode | |
} | |
@IBAction func buttonStartTouch(_ sender: Any) { | |
// prepare the video reference | |
let fileURL = URL(fileURLWithPath: Bundle.main.path(forResource: "team-walking", ofType: "mp4")!) | |
player = AVPlayer(url: fileURL) | |
videoNode.geometry?.firstMaterial?.diffuse.contents = player | |
player!.play() | |
} | |
@IBAction func buttonStopTouch(_ sender: Any) { | |
// haven't found 'stop' so using 'pause' | |
player!.pause() | |
} | |
// This delegate fires when an anchor is added whenever an ARAnchor was added to the sceneview. An anchor encodes position, size and orientation of something ... the surface in this case. | |
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { | |
print("New surface detected") | |
// notice that we {return} early if we don't have an anchor | |
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} | |
if (player?.timeControlStatus == AVPlayer.TimeControlStatus.playing) { | |
print("playing, so not creating a new node") | |
} else { | |
// ... we proceed creating and adding the scene node | |
let childNode = createNode(planeAnchor: planeAnchor) | |
node.addChildNode(childNode) | |
} | |
} | |
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { | |
print("Surface updated") | |
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} | |
if (player?.timeControlStatus == AVPlayer.TimeControlStatus.playing) { | |
print("playing, so not updating nodes") | |
} else { | |
// remove all | |
node.enumerateChildNodes { (childNode, _) in | |
childNode.removeFromParentNode() | |
} | |
// add back | |
let childNode = createNode(planeAnchor: planeAnchor) | |
node.addChildNode(childNode) | |
} | |
} | |
func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { | |
print("Surface anchor removed") | |
// remove all | |
if (player?.timeControlStatus == AVPlayer.TimeControlStatus.playing) { | |
print("playing, so not removing nodes") | |
} else { | |
node.enumerateChildNodes { (childNode, _) in | |
childNode.removeFromParentNode() | |
} | |
} | |
} | |
} | |
extension Int { | |
var degreesToRadians: Double { return Double(self) * .pi/180 } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment