Skip to content

Instantly share code, notes, and snippets.

@jungchris
Created August 21, 2020 18:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jungchris/64eb028ff419da62350b828814706702 to your computer and use it in GitHub Desktop.
Save jungchris/64eb028ff419da62350b828814706702 to your computer and use it in GitHub Desktop.
Animations and Interactions of "Solar System" in ARKIt
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
sceneView.addGestureRecognizer(tapGestureRecognizer)
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
// sceneView.showsStatistics = true
// light source
sceneView.autoenablesDefaultLighting = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// show world origin
// sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin, ARSCNDebugOptions.showFeaturePoints]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// sun
let sun = SCNNode(geometry: SCNSphere(radius: 0.33))
sun.name = "sun"
sun.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "starSunDiffuse")
sun.position = SCNVector3Make(0.0, 0.0, -1.5)
sceneView.scene.rootNode.addChildNode(sun)
// rotate the sun around itself
let rotationAction = SCNAction.rotateBy(x: 0.0, y: CGFloat(360.degreesToRadians), z: 0.0, duration: 60.0)
let rotationPeriod = SCNAction.repeatForever(rotationAction)
sun.runAction(rotationPeriod)
// create parent nodes of planets so they can have their own motions
let earthParentNode = SCNNode()
let moonParentNode = SCNNode()
earthParentNode.name = "earthParent"
moonParentNode.name = "moonParent"
earthParentNode.position = SCNVector3Make(0.0, 0.0, -1.5)
moonParentNode.position = SCNVector3Make(0.0, 0.0, 0.0)
sceneView.scene.rootNode.addChildNode(earthParentNode)
// earth appearance & position
let earth = astralBody(geometry: SCNSphere(radius: 0.2), diffuse: UIImage(named: "planetEarth")!, specular: UIImage(named: "planetEarthSpecular")!, emission: UIImage(named: "planetEarthEmissive")!, normal: UIImage(named: "planetEarthNormal")!, position: SCNVector3Make(1.2, 0.0, 0.0))
// moon appearance & position
let moon = astralBody(geometry: SCNSphere(radius: 0.14), diffuse: UIImage(named: "moonLunaDiffuse")!, specular: nil, emission: nil, normal: nil, position: SCNVector3Make(0.5, 0.0, 0.0))
// name them in case we want to interact
earth.name = "earth"
moon.name = "moon"
// add actual bodies to their parent nodes
earthParentNode.addChildNode(earth)
moonParentNode.addChildNode(moon)
earth.addChildNode(moonParentNode)
// earth rotation (spin on own axis)
let earthRotationPeriod = nodeRotation(time: 12.0, x: 0.0, y: -CGFloat(360.degreesToRadians), z: 0.0)
earth.runAction(earthRotationPeriod)
// earth orbit
let earthParentRotationPeriod = nodeRotation(time: 30.0, x: 0.0, y: CGFloat(360.degreesToRadians), z: 0.0)
earthParentNode.runAction(earthParentRotationPeriod)
// moon spin
let moonRotationPeriod = nodeRotation(time: 2.0, x: 0.0, y: CGFloat(360.degreesToRadians), z: 0.0)
moon.runAction(moonRotationPeriod)
// moon orbit
let moonParentRotationPeriod = nodeRotation(time: 8.0, x: 0.0, y: CGFloat(360.degreesToRadians), z: 0.0)
moonParentNode.runAction(moonParentRotationPeriod)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// used for setting spin and orbits
func nodeRotation(time: TimeInterval, x: CGFloat, y: CGFloat, z: CGFloat) -> SCNAction {
let rotationAction = SCNAction.rotateBy(x: x, y: y, z: z, duration: time)
let rotationPeriod = SCNAction.repeatForever(rotationAction)
return rotationPeriod
}
// used to create a sperical body and set its appearance
func astralBody(geometry: SCNGeometry, diffuse: UIImage?, specular: UIImage?, emission: UIImage?, normal: UIImage?, position: SCNVector3) -> SCNNode {
let body = SCNNode(geometry: geometry)
body.geometry?.firstMaterial?.diffuse.contents = diffuse
body.geometry?.firstMaterial?.specular.contents = specular
body.geometry?.firstMaterial?.emission.contents = emission
body.geometry?.firstMaterial?.normal.contents = normal
body.position = position
return body
}
@objc func handleTap(sender: UITapGestureRecognizer) {
let sceneViewTappedOn = sender.view as! SCNView
let touchCoordinates = sender.location(in: sceneViewTappedOn)
let hitTest = sceneViewTappedOn.hitTest(touchCoordinates)
if hitTest.isEmpty {
print("outside of scene")
} else {
let results = hitTest.first!
let geometry = results.node.geometry
let nameOfHit = results.node.name
print(geometry ?? "default value")
print(nameOfHit ?? "unnamed")
if (nameOfHit == "sun") {
animateNode(node: sceneView.scene.rootNode.childNode(withName: "sun", recursively: false)!)
} else if (nameOfHit == "earth") {
reverseNode(node: sceneView.scene.rootNode.childNode(withName: "earthParent", recursively: false)!)
} else if (nameOfHit == "moon") {
messWithTexture(node: sceneView.scene.rootNode.childNode(withName: "moon", recursively: true)!)
}
}
}
func animateNode(node: SCNNode) {
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 1
animation.toValue = 0
let animationX = CABasicAnimation(keyPath: "transform.scale.x")
let animationY = CABasicAnimation(keyPath: "transform.scale.y")
let animationZ = CABasicAnimation(keyPath: "transform.scale.z")
animationX.fromValue = 1
animationX.toValue = 2
animationY.fromValue = 1
animationY.toValue = 2
animationZ.fromValue = 1
animationZ.toValue = 2
node.addAnimation(animationX, forKey: "transform.scale.x")
node.addAnimation(animationY, forKey: "transform.scale.y")
node.addAnimation(animationZ, forKey: "transform.scale.z")
node.addAnimation(animation, forKey: "opacity")
}
func reverseNode(node: SCNNode) {
let orbitPeriod = nodeRotation(time: 10.00, x: 0.0, y: -CGFloat(360.degreesToRadians), z: 0.0)
node.runAction(orbitPeriod)
}
func messWithTexture(node: SCNNode) {
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "planetVenus")
}
// MARK: - ARSCNViewDelegate
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
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