some_id
some_id

Reputation: 29896

Sine Wave UIBezierPath between two points

How does one create a path of a sine wave between two points?

I am able to create a path of a sine wave from an origin, but am not sure how the direction can be transformed so that the sine wave ends at a target CGPoint.

I would like to animate a SKNode along the path using SKAction.followPath

Upvotes: 1

Views: 1489

Answers (2)

Grimxn
Grimxn

Reputation: 22487

The simplest way to think about this is to transform the coordinate system, rotating by the angle between the two points, scaling by the distance between them and translating by the first point (assuming the sine starts at 0,0).

The OP has specified that he doesn't just want to draw the curve (in which case all one needs to do is apply the transform to the graphics context), but rather to use the curve in a SpriteKit SKAction.followPath call, so the transform has to be applied to the coordinates in the path, not to the context.

Here's a solution using CGPath rather than UIBezierPath, but they are equivalent, and you can get the UI version simply by let uip = UIBezierPath(cgPath: path). (I prefer CoreGraphics as they are cross-platform).

Playground code...

class MyView: UIView {

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.setFillColor(UIColor.red.cgColor)
        context.fill(self.bounds)

        // Calculate the transform
        let p1 = CGPoint(x: 100, y: 100)
        let p2 = CGPoint(x: 400, y: 400)
        let dx = p2.x - p1.x
        let dy = p2.y - p1.y
        let d = sqrt(dx * dx + dy * dy)
        let a = atan2(dy, dx)
        let cosa = cos(a) // Calculate only once...
        let sina = sin(a) // Ditto

        // Initialise our path
        let path = CGMutablePath()
        path.move(to: p1)

        // Plot a parametric function with 100 points
        let nPoints = 100
        for t in 0 ... nPoints {
            // Calculate the un-transformed x,y
            let tx = CGFloat(t) / CGFloat(nPoints) // 0 ... 1
            let ty = 0.1 * sin(tx * 2 * CGFloat.pi ) // 0 ... 2π, arbitrary amplitude
            // Apply the transform
            let x = p1.x + d * (tx * cosa - ty * sina)
            let y = p1.y + d * (tx * sina + ty * cosa)
            // Add the transformed point to the path
            path.addLine(to: CGPoint(x: x, y: y))
        }

        // Draw the path
        context.setStrokeColor(UIColor.blue.cgColor)
        context.addPath(path)
        context.strokePath()
    }
}

let v = MyView(frame: CGRect(origin: CGPoint(x: 0, y:0), size: CGSize(width: 500, height: 500)))

Playground output...

Upvotes: 4

abby yorker
abby yorker

Reputation: 417

Not crystal clear what you want but here's one possibility assuming you want a tilted sin curve:

Assume that the start point is (0, 0) and the end point is (x, y).

Let L be the distance between the origin and your point: L = sqrt(x^2 + y^2)

Write a loop that starts at 0 and ends at L, with increment dL and running sum l (which ends up running between 0 and L). This loop will allow us to create the points on your Bezier.

Then the x coordinate of your sin graph will be: x_P = l * cos(theta), ranging from 0 to L * cos(theta) = x

To get the y coordinate, we add a sin function with the correct period to the sloping line between the origin and your point: y_P = l * sin(theta) + sin(2 * PI * l / L)

note that, at l = L, (x_P, y_P) = (x, y) which is as it should be.

Was this what you wanted? It is not a rotation and so will not behave when the angle theta is large.

Upvotes: 1

Related Questions