I have a CGPath that represents an ellipse. I'd like to place a sprite node at a random point on that path. How can I get a random CGPoint on that CGPath?
-
The only solution I ever saw that didn't involve supermath^tm was one where the path was stroked with a dashed line of 1 and then the points were extrapolated from that stroked path. No clue if that is, or is still relevant. – Cooper Buckingham Jun 16 '14 at 04:25
-
Did you ever solve this? I am just having some vague strategy, that if the ellipse is perfectly round, like a clock, we could easily change the "starting point" by simply using a rotated transform when creating the ellipse. Now in my current case my ellipse is NOT perfectly round, but maybe with some tweaking it could still work... hmm.. – Jonny Mar 31 '15 at 01:55
-
@Jonny I haven't solved it but similarly to your idea, you could create a circle, rotate it randomly between 0 and 2pi, then transform it into an ellipse and the starting point of the ellipse will be random? It's been a while since I visited this problem, so I don't remember if "transform it into an ellipse" is a thing – JuJoDi Mar 31 '15 at 19:29
-
I "solved" it in another way which someone pointed out; we can draw a dotted line across the path, and then use CGPathApply (I think) to iterate over all draw actions/elements used to draw that dotted path. It's a bit complicated to get going but definitely works. We can use the points of those actions as dots on the path of the ellipse. It will take some tweaking. You probably won't get infinite _random_ points of the ellipse, just probably starting points of those dots, or however you tweak it. – Jonny Apr 01 '15 at 01:10
-
Hello guys, have anyone solved the prob. I also have same term with different shapes like Square, rhombus, Diamond etc.. and want to working with same... Please post an answer or some helpful links.. Thank You All. – Chetan Prajapati Feb 08 '16 at 06:38
2 Answers
Speaking about a closed CGPath
and thinking to a fast method to obtain a random point between infinite points inside a CGPath
, first of all I've translated to Swift this:
extension CGPath {
func forEach(@noescape body: @convention(block) (CGPathElement) -> Void) {
typealias Body = @convention(block) (CGPathElement) -> Void
func callback(info: UnsafeMutablePointer<Void>, element: UnsafePointer<CGPathElement>) {
let body = unsafeBitCast(info, Body.self)
body(element.memory)
}
//print(sizeofValue(body))
let unsafeBody = unsafeBitCast(body, UnsafeMutablePointer<Void>.self)
CGPathApply(self, unsafeBody, callback)
}
func getPathElementsPoints() -> [CGPoint] {
var arrayPoints : [CGPoint]! = [CGPoint]()
self.forEach { element in
switch (element.type) {
case CGPathElementType.MoveToPoint:
arrayPoints.append(element.points[0])
case .AddLineToPoint:
arrayPoints.append(element.points[0])
case .AddQuadCurveToPoint:
arrayPoints.append(element.points[0])
arrayPoints.append(element.points[1])
case .AddCurveToPoint:
arrayPoints.append(element.points[0])
arrayPoints.append(element.points[1])
arrayPoints.append(element.points[2])
default: break
}
}
return arrayPoints
}
func spriteKitCenter() ->CGPoint {
let shape = SKShapeNode(path: self)
return CGPointMake(CGRectGetMidX(shape.frame),CGRectGetMidY(shape.frame))
}
func uiKitCenter() ->CGPoint {
let layer = CAShapeLayer()
layer.path = self
return CGPointMake(CGRectGetMidX(layer.frame),CGRectGetMidY(layer.frame))
}
}
I used the follow method with my irregulars polygon, it's based on triangles so if you use arcs some curve parts will be losts (please if you have any advice comment it, I'd be grateful):
- get all
CGPath
border points (element that compose my path) - get the center of my
CGPath
- obtain a list of triangles between my points and the center
- choose a random triangle from the triangles list
- choose a random point inside the triangle
This is the triangle method:
func getRandomPointInTriangle (aPoint:CGPoint,bPoint:CGPoint,cPoint:CGPoint)->CGPoint {
var randomA = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
var randomB = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
if (randomA + randomB > 1) {
randomA = 1-randomA
randomB = 1-randomB
}
let randomC = 1-randomA-randomB
let rndX = (randomA*aPoint.x)+(randomB*bPoint.x)+(randomC*cPoint.x)
let rndY = (randomA*aPoint.y)+(randomB*bPoint.y)+(randomC*cPoint.y)
return CGPointMake(rndX,rndY)
}
This is the main method:
func getRandomPoint()->CGPoint {
let points = self.path!.getPathElementsPoints()
let center = self.path!.spriteKitCenter()
// divide the cgpath in triangles
var triangles = Array<Array<CGPoint>>()
for i in 0..<points.count-1 {
let triangle = [center,points[i],points[i+1]]
triangles.append(triangle)
}
let chooseTriangleNum = Int(arc4random()) % triangles.count
let chooseTriangle = triangles[chooseTriangleNum]
return getRandomPointInTriangle(chooseTriangle[0],bPoint: chooseTriangle[1],cPoint: chooseTriangle[2])
}

- 34,887
- 11
- 106
- 133
Excuse the resurrection :)
Take the bounding box and pick two a random point on two opposite sides ( this should guarantee a line that crosses some point of the path.
Walk the line using path.contains() to find the first point that is in the path.
This isn't very random of course, as it ignores 'inside' loops, so if you want more random, walk the entire line and record all crossings, then select a random one of the crossings.
It still isn't very random as it favours more 'central' points on the perimeter.
Next optimisation, maybe you need a bit faster, and path.contains() isn't cutting it.
Draw your path to an offscreen bitmap ( stroke, no fill), then to check if a point is on the path, you only need to get the colour at that point. This should be a lot faster than path.contains().

- 2,537
- 1
- 22
- 23