Reputation: 29896
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
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)))
Upvotes: 4
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