问题
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?
回答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])
}
回答2:
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().
来源:https://stackoverflow.com/questions/24225943/get-random-cgpoint-on-cgpath