Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.