Place sprite on a path

I'm trying to create a row of sprites that I want to place into a sequence on a curved path, kinda like christmas lights on a cable. I have a path already, and I would like to be able to place a sprite into a specific spot of the path, such as a fraction of the length from the starting point. The rotation of the sprite should also conform to the tangent of the path at that position. How might this be possible?


I've already tried animating the sprite to move on the path with SKAction.followPath(path, asOffset: false, orientToPath: true, duration: 3.0), which works, but I'm having trouble finding out how to place a sprite on a path without animation.


let path: CGMutablePathRef = CGPathCreateMutable()

var pathPointStart: CGPoint = CGPointMake(0, 0)
var pathPointEnd: CGPoint = CGPointMake(0, 600)
var pathPointControl1: CGPoint = CGPointMake(100, 200)
var pathPointControl2: CGPoint = CGPointMake(100, 400)

CGPathMoveToPoint(path, nil, pathPointStart!.x, pathPointStart!.y)
CGPathAddCurveToPoint(path, nil, pathPointControl1!.x, pathPointControl1!.y, pathPointControl2!.x, pathPointControl2!.y, pathPointEnd!.x, pathPointEnd!.y)


let sprite: SKSpriteNode = SKSpriteNode(imageNamed: "Sprite")
addChild(sprite)


Here I create a vertical path that bends 100.0 points to the right thanks to the two control points. Then I create the sprite. How can I for example place the sprite a fourth of the distance of the path starting from the starting point, and so that the sprite also takes its rotation from the curve tangent at that point?

override func viewDidLoad()
{
    super.viewDidLoad()

    let straightLinePointData: [[CGFloat]] = [[100, 200],  // start
                                              [700, 200],  // end
                                              [400, 200],  // control 1
                                              [400, 200]]  // control 2

    let curvedLinePointData1:  [[CGFloat]] = [[100, 300],
                                              [700, 300],
                                              [300, 400],
                                              [500, 400]]

    let curvedLinePointData2:  [[CGFloat]] = [[100, 500],
                                              [700, 500],
                                              [300, 700],
                                              [500, 300]]

    draw(straightLinePointData)
    draw(curvedLinePointData1)
    draw(curvedLinePointData2)
}


func draw(linePointData: [[CGFloat]])
{
    let path: CGMutablePathRef = CGPathCreateMutable()
    let pathPointStart    = CGPointMake(linePointData[0][0], linePointData[0][1])
    let pathPointEnd      = CGPointMake(linePointData[1][0], linePointData[1][1])
    let pathPointControl1 = CGPointMake(linePointData[2][0], linePointData[2][1])
    let pathPointControl2 = CGPointMake(linePointData[3][0], linePointData[3][1])

    let m: UnsafePointer<CGAffineTransform> = nil

    CGPathMoveToPoint(path, m, pathPointStart.x, pathPointStart.y)
    CGPathAddCurveToPoint(path, m, pathPointControl1.x, pathPointControl1.y,
                                   pathPointControl2.x, pathPointControl2.y,
                                   pathPointEnd.x, pathPointEnd.y)

    var visualPath = self.visualPath(path)
    self.view.layer.addSublayer(visualPath)


        func addNode(pathPercent: CGFloat)
        {
            let node = self.node()
      
            let position = CGPointMake(positionForCubicBezierCurve(pathPercent,
                                                                   p0: pathPointStart.x,
                                                                   p1: pathPointControl1.x,
                                                                   p2: pathPointControl2.x,
                                                                   p3: pathPointEnd.x),
                          
                                       positionForCubicBezierCurve(pathPercent,
                                                                   p0: pathPointStart.y,
                                                                   p1: pathPointControl1.y,
                                                                   p2: pathPointControl2.y,
                                                                   p3: pathPointEnd.y))
  
            // tangent
            let derivative = CGPointMake(derivativeForCubicBezierCurve(pathPercent,
                                                                       p0: pathPointStart.x,
                                                                       p1: pathPointControl1.x,
                                                                       p2: pathPointControl2.x,
                                                                       p3: pathPointEnd.x),
                           
                                         derivativeForCubicBezierCurve(pathPercent,
                                                                       p0: pathPointStart.y,
                                                                       p1: pathPointControl1.y,
                                                                       p2: pathPointControl2.y,
                                                                       p3: pathPointEnd.y))
      
            node.position = CGPointMake(position.x, position.y);
            node.setAffineTransform(CGAffineTransformMakeRotation(atan2(derivative.y, derivative.x)))
      
            node.allowsEdgeAntialiasing = true
            self.view.layer.addSublayer(node)
        }

    addNode(0.02)
    addNode(0.25)
    addNode(0.50)
    addNode(0.66)
    addNode(0.75)
    addNode(0.96)
    addNode(1.0)
}


// See Wikipedia: Cubic Bezier curves
// Explicit Bezier formulas shown here, else Swift error: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions.
// t parameter is percentage of path
func positionForCubicBezierCurve(t:CGFloat, p0:CGFloat, p1:CGFloat, p2:CGFloat, p3:CGFloat) -> CGFloat
{
    return     ((1-t) * (1-t) * (1-t))             * p0 +
           3 * ((1-t) * (1-t)        ) * t         * p1 +
           3 * ((1-t)                ) * t * t     * p2 +
                                         t * t * t * p3;
}

// tangent
func derivativeForCubicBezierCurve(t:CGFloat, p0:CGFloat, p1:CGFloat, p2:CGFloat, p3:CGFloat) -> CGFloat
{
    return 3 * ((1-t) * (1-t)) *         (p1 - p0) +
           6 * ( 1-t         ) * t *     (p2 - p1) +
           3                   * t * t * (p3 - p2)
}


func visualPath(path: CGMutablePathRef) -> CAShapeLayer
{
    let visualPath = CAShapeLayer()
    visualPath.path = path
    visualPath.fillColor = UIColor.clearColor().CGColor
    visualPath.strokeColor = UIColor.darkGrayColor().CGColor
    visualPath.lineWidth = 1.0

    return visualPath
}

func node() -> CAGradientLayer
{
    let node = CAGradientLayer()
    node.frame = CGRectMake(0, 0, 20, 20)
    node.borderColor = UIColor.darkGrayColor().CGColor
    node.borderWidth = 0.50

    // horizontal gradient
    node.colors = [UIColor.init(white: 1, alpha: 0.5).CGColor,                   // top
                   UIColor.init(red: 1, green: 0, blue: 0, alpha: 0.5).CGColor]  // bottom

    node.locations = [NSNumber(float: 0.44),
                      NSNumber(float: 1)]

    return node;
}


Place sprite on a path
 
 
Q