3

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?

JuJoDi
  • 14,627
  • 23
  • 80
  • 126
  • 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 Answers2

1

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])
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
0

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().

Gordon Dove
  • 2,537
  • 1
  • 22
  • 23