3
votes

I have a SCNNode, where I display a surface - on this surface I want to display a path. This path is a SCNNode itself which is added to the surface SCNNode. This SCNNode (path) consists of multiple SCNNodes which are small pieces of the whole path - so I add them all to the path SCNNode.

So workflow is the following:

  1. Calculate the SCNNode chunks of path
  2. Add the chunks to the full path SCNNode
  3. When every SCNNode chunk is added -> add the full path to the surface SCNNode

Problem: I don't just wanna add this I want to animate it from the start to the end (first chunk to last chunk) but how do I do that?

Thanks for any help!

1

1 Answers

2
votes

Since you haven't provided any code (please do so next time), I am going to provide a solution which should point you in the right direction.

Lets begin by creating a PathItem Class which we will use to make a complete path e.g. a line of them:

/// Path Item Node
class PathItem: SCNNode{


    /// Creates A PathItem
    ///
    /// - Parameters:
    ///   - size: CGFloat (Defaults To 20cm)
    ///   - texture: UIColour
    ///   - position: SCNVector3
    init(size: CGFloat = 0.2, texture: UIColor, position: SCNVector3){

        super.init()

        //1. Create Our Path Geometry
        let pathGeometry = SCNPlane(width: size, height: size)

        //2. Assign The Colour To The Geoemtry
        pathGeometry.firstMaterial?.diffuse.contents = texture

        //3. Assign The Geometry, Position The Node & Rotate The Node So It Is Horizontal
        self.geometry = pathGeometry
        self.position = position
        self.eulerAngles.x = GLKMathDegreesToRadians(-90)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("Path Item Coder Has Not Been Implemented") }

}

Now having done this, lets create a func which will create a line of PathItem (a Path).

First create a Global variable like so which references the size of each PathItem:

let pathItemSize: CGFloat = 0.2

Then we create our function which alternates the colour of each PathItem as well as providing them with a unique name or index which we will use later in our animation:

/// Create A Path With A Number Of Elements
///
/// - Parameter numberOfElements: Int
/// - Returns: PATH (SCNNode)
func createdPathOfSize(_ numberOfElements: Int) {

    var pathColour: UIColor!

    //2. Loop Through The Number Of Path Elements We Want & Place Them In A Line
    for pathIndex in 0 ..< numberOfElements{

        //a. Position Each Peice Next To Each Other Based On The Index
        let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

        //b. Alternate The Colour Of Our Path
        if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

        //c. Create Our Path Item With A Unique Index We Can Use For Animating
        let pathItem = PathItem(texture: pathColour, position: pathPosition)
        pathItem.name = String(pathIndex)

        //d. Set It To Hidden Initially
        pathItem.isHidden = true

        //e. Add It To Our Scene
       self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
    }

}

To generate a Path we can now do something like so:

override func viewDidLoad() {
    super.viewDidLoad()

    //1. Set Up Our ARSession
    augmentedRealityView.session = augmentedRealitySession
    sessionConfiguration.planeDetection = .horizontal
    augmentedRealityView.debugOptions = .showFeaturePoints
    augmentedRealitySession.run(sessionConfiguration, options: [.resetTracking, .removeExistingAnchors])

    //2. Create A Path Of 10 PathItems
    createdPathOfSize(10)
}

Whereby I have the following Global variables:

@IBOutlet var augmentedRealityView: ARSCNView!
let augmentedRealitySession = ARSession()
let sessionConfiguration = ARWorldTrackingConfiguration()

Now we have generated our Path we need to animate it!

To provide a bit of diversity let's create an Enum which we can use to create different PathAnimations:

 /// Path Item Animation
 ///
 /// - UnHide: UnHides The Path Item
 /// - FadeIn: Fades The Path Item In
 /// - FlipIn: Flips The Path Item In
 enum AnimationType{

     case UnHide
     case FadeIn
     case FlipIn

 }

No since we will be doing some animations lets create 2 more Global variables which will be used to run our animation on a Timer and keep track of where we are up to:

var pathAnimationTimer: Timer?
var time: Int = 0

Now lets create our animation function:

/// Animates The Laying Of The Path
///
/// - Parameters:
///   - numberOfElements: Int
///   - animation: AnimationType
func animatePathElements(_ numberOfElements: Int, withAnimation animation: AnimationType ){

    //1. If We Are Flipping The PathItems In We Need To 1st Unhide Them All & Rotate Them To A Vertical Postions
    if animation == .FlipIn {

        let pathItems = self.augmentedRealityView.scene.rootNode.childNodes

        pathItems.forEach({ (pathItemToAnimate) in
            pathItemToAnimate.isHidden = false
            pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(0)
        })

    }

    //2. Create Our Time Which Will Run Every .25 Seconds
    pathAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in

        //3. Whilst Our Time Doesnt Equal The Number Of Path Items Then Continue Our Animation
        if self.time != numberOfElements{

            //a. Get The Current Node Remembering Each One Has A Unique Name (Index
            guard let pathItemToAnimate = self.augmentedRealityView.scene.rootNode.childNode(withName: "\(self.time)", recursively: false) else { return }

            //b. Run The Desired Animation Sequence
            switch animation{
            case .UnHide:
                 //Simply Unhide Each PathItem
                 pathItemToAnimate.isHidden = false
            case .FadeIn:

                //1. Unhide The Item & Sets It's Opacity To 0 Rendering It Invisible
                 pathItemToAnimate.isHidden = false
                 pathItemToAnimate.opacity = 0

                 //2. Create An SCNAction To Fade In Our PathItem
                 let fadeInAction = SCNAction.fadeOpacity(to: 1, duration: 0.25)
                 pathItemToAnimate.runAction(fadeInAction)

            case .FlipIn:
                 //Simply Rotate The Path Item Horizontally
                 pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(-90)
            }

            self.time += 1

        }else{
            //4. Our Animation Has Finished So Invalidate The Timer
            self.pathAnimationTimer?.invalidate()
            self.time = 0
        }
    }
}

We then need to add this to the end of our createPathOfSize function like so:

/// Create A Path With A Number Of Elements Which Can Be Animated
///
/// - Parameter numberOfElements: Int
/// - Returns: PATH (SCNNode)
func createdPathOfSize(_ numberOfElements: Int) {

    var pathColour: UIColor!

    //1. Loop Through The Number Of Path Elements We Want & Place Them In A Line
    for pathIndex in 0 ..< numberOfElements{

        //a. Position Each Peice Next To Each Other Based On The Index
        let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

        //b. Alternate The Colour Of Our Path

        if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

        //b. Create Our Path Item With A Unique Index We Can Use For Animating
        let pathItem = PathItem(texture: pathColour, position: pathPosition)
        pathItem.name = String(pathIndex)

        //c. Set It To Hidden Initially
        pathItem.isHidden = true

        //d. Add It To Our Scene
       self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
    }

    //2. Animate The Path
    animatePathElements(10, withAnimation: .FlipIn)

}

And here is the full example:

import UIKit
import ARKit

//----------------------
//MARK: - Path Animation
//----------------------

/// Path Item Animation
///
/// - Show: UnHides The Path Item
/// - FadeIn: Fades The Path Item In
enum AnimationType{

    case UnHide
    case FadeIn
    case FlipIn

}

//-----------------
//MARK: - Path Item
//-----------------

/// Path Item Node
class PathItem: SCNNode{


    /// Creates A PathItem
    ///
    /// - Parameters:
    ///   - size: CGFloat (Defaults To 20cm)
    ///   - texture: UIColour
    ///   - position: SCNVector3
    init(size: CGFloat = 0.2, texture: UIColor, position: SCNVector3){

        super.init()

        //1. Create Our Path Geometry
        let pathGeometry = SCNPlane(width: size, height: size)

        //2. Assign The Colour To The Geoemtry
        pathGeometry.firstMaterial?.diffuse.contents = texture

        //3. Assign The Geometry, Position The Node & Rotate The Node So It Is Horizontal
        self.geometry = pathGeometry
        self.position = position
        self.eulerAngles.x = GLKMathDegreesToRadians(-90)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("Path Item Coder Has Not Been Implemented") }

}

class ViewController: UIViewController {

    typealias PATH = SCNNode
    @IBOutlet var augmentedRealityView: ARSCNView!
    let augmentedRealitySession = ARSession()
    let sessionConfiguration = ARWorldTrackingConfiguration()

    var pathPlaced = false
    let pathItemSize: CGFloat = 0.2
    var pathAnimationTimer: Timer?
    var time: Int = 0

    //----------------------
    //MARK: - View LifeCycle
    //----------------------

    override func viewDidLoad() {
        super.viewDidLoad()

        //1. Set Up Our ARSession
        augmentedRealityView.session = augmentedRealitySession
        sessionConfiguration.planeDetection = .horizontal
        augmentedRealityView.debugOptions = .showFeaturePoints
        augmentedRealitySession.run(sessionConfiguration, options: [.resetTracking, .removeExistingAnchors])

        //2. Create A Path Of 10 Path Items
        createdPathOfSize(10)
    }

    //---------------------------------
    //MARK: - Path Creation & Animation
    //---------------------------------

    /// Animates The Laying Of The Path
    ///
    /// - Parameters:
    ///   - numberOfElements: Int
    ///   - animation: AnimationType
    func animatePathElements(_ numberOfElements: Int, withAnimation animation: AnimationType ){

        if animation == .FlipIn {

            let pathItems = self.augmentedRealityView.scene.rootNode.childNodes

            pathItems.forEach({ (pathItemToAnimate) in
                pathItemToAnimate.isHidden = false
                pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(0)
            })

        }

        pathAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in

            //1. Whilst Our Time Doesnt Equal The Number Of Path Items Then Continue Our Animation
            if self.time != numberOfElements{

                guard let pathItemToAnimate = self.augmentedRealityView.scene.rootNode.childNode(withName: "\(self.time)", recursively: false) else { return }

                //2. Run The Desired Animation Sequence
                switch animation{
                case .UnHide:
                    pathItemToAnimate.isHidden = false
                case .FadeIn:
                    pathItemToAnimate.isHidden = false
                    pathItemToAnimate.opacity = 0
                    let fadeInAction = SCNAction.fadeOpacity(to: 1, duration: 0.3)
                    pathItemToAnimate.runAction(fadeInAction)
                case .FlipIn:

                    pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(-90)
                }

                self.time += 1

            }else{
                self.pathAnimationTimer?.invalidate()
                self.time = 0
            }
        }


    }


    /// Create A Path With A Number Of Elements Which Can Be Animated
    ///
    /// - Parameter numberOfElements: Int
    /// - Returns: PATH (SCNNode)
    func createdPathOfSize(_ numberOfElements: Int) {

        var pathColour: UIColor!

        //1. Loop Through The Number Of Path Elements We Want & Place Them In A Line
        for pathIndex in 0 ..< numberOfElements{

            //a. Position Each Peice Next To Each Other Based On The Index
            let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

            //b. Alternate The Colour Of Our Path

            if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

            //b. Create Our Path Item With A Unique Index We Can Use For Animating
            let pathItem = PathItem(texture: pathColour, position: pathPosition)
            pathItem.name = String(pathIndex)

            //c. Set It To Hidden Initially
            pathItem.isHidden = true

            //d. Add It To Our Scene
            self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
        }

        //2. Animate The Path
        animatePathElements(10, withAnimation: .FlipIn)

    }

}

This should be more than enough to point you in the right direction ^__________^.