1

enter image description here

Above you see my UIView which is a rectangle with a black border. In the middle is the center (red dot) and furthermore, 2 circles.

My goal is to generate a random X and Y point with a minimum and maximum offset based on the center of the UIView. The green circle gives away the minimum X and Y positions, while the red outer circle indicates the maximum X and Y positions. My goal is to generate an X and Y value between those two circles.

Lets say I have a UIView which size is 100,100 and I want to get a random X and Y value with a minimum offset which is 80% of the view (green circle) and a maximum offset of 120% (red circle).

My bad attempt to generate a random X value:

var finalX = CGFloat.random(lower: heightUIView *  0.8, heightUIView * 1.2)

let toDown = Int.random(lower: 0, 1) == 0 ? true : false
if !toDown {
    finalX = -finalX
}

This would never generate a good X value since these values can not be 0 (while that could be an option, looking at the picture)

J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • What's the state of your knowledge of basic geometry? Do you know about polar coordinates? – matt Apr 05 '18 at 20:13
  • @matt no, I really do not have any clue :(. I am already searching and I found out about geometry and all. Maybe that is the key – J. Doe Apr 05 '18 at 20:14
  • https://en.wikipedia.org/wiki/Polar_coordinate_system So you need a random theta between zero and 2 pi, and you need a random r between the smaller radius and larger radius. Now convert that to cartesian coordinates, round to integer values, and offset the result by the coordinates of the center. Done. – matt Apr 05 '18 at 20:16
  • Note that `? true : false` is redundant. `let toDown = Int.random(lower: 0, 1) == 0` – Leo Dabus Apr 05 '18 at 20:19
  • Are you trying to perform this calculation many times? – Tim Fuqua Apr 05 '18 at 20:25

2 Answers2

3

The general formula to get a random number between min and max is:

let rand = arc4random_uniform(max - min) + min

But in your case, if you do that for an x and y value, you will not get values between the green and red circles. You will get values in boxes that contain the circles.

To get a random point between the two circles you need something along these lines:

let greenRadius = 4 // whatever the radius of the green circle is
let redRadius = 10 // whatever the radius of the red circle is
let randRadius = Double(arc4random_uniform(UInt32(redRadius - greenRadius)) + UInt32(greenRadius))
let randAngle = Double(arc4random_uniform(360)) / 180 * Double.pi
let x = cos(randAngle) * randRadius
let y = sin(randAngle) * randRadius

That will give you an x and y around a center of 0, 0. Add those to your actual center point to get your final values.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Perhaps not important, but note that this will not result in a uniform distribution (with respect to the area). – Martin R Apr 05 '18 at 20:23
  • @MartinR Can you clarify please? It's a random angle and random radius within the given area. – rmaddy Apr 05 '18 at 20:26
  • See for example http://mathworld.wolfram.com/DiskPointPicking.html. Points near the outer radius are less likely than points near the inner radius. – Martin R Apr 05 '18 at 20:27
  • @MartinR Right. Since points between two angles are further apart with greater radius. – rmaddy Apr 05 '18 at 20:28
  • Interesting. At first glance it seems like this *should* give a uniform distribution, but I see now why it doesn't. I didn't quite understand the solution to a uniform distribution though. Martin, care to post a solution that DOES yield a uniform distribution? – Duncan C Apr 05 '18 at 20:35
  • Oh, I see. `x=sqrt(r) *cos(theta)` `y=sqrt(r) *sin(theta)`. Makes sense. – Duncan C Apr 05 '18 at 20:38
  • Yea I ran some tests and it looks like the inner circle has more point in it than the outer circle: https://ibb.co/g4g4rc, but I am happy, I would never spawn so many points anyway :) – J. Doe Apr 05 '18 at 20:47
  • 1
    Hmm. Won't your angle code generate angles that are in discrete 1-degree steps since you're using an integer random value from 0-259 to calculate theta – Duncan C Apr 05 '18 at 23:20
  • @DuncanC I was waiting for that to come up. Yeah, I did that just because it was easier as I was typing out the code into the answer. I'll leave it as an exercise for the reader to calculate the values with more precision. – rmaddy Apr 05 '18 at 23:31
  • Martin's answer takes care of that, since he uses `arc4random())/(CGFloat(UInt32.max)` * scale in his calculations. – Duncan C Apr 06 '18 at 00:12
  • Don't I remember seeing new floating point random functions in Swift 4? Those would be easier still. – Duncan C Apr 06 '18 at 00:13
2

In order to get a random point with uniform distribution in the annulus (with respect to area measure) you need to

  • Compute a uniformly distributed angle φ in the range [0, 2π),
  • Compute a uniformly distributed radius r in the range [r12, r22],
  • Compute the point P = (sqrt(r) * cos(φ), sqrt(r) * sin(φ)).

(From Disk Point Picking on Wolfram Mathworld.)

Code example:

let r1: CGFloat = 2  // inner radius
let r2: CGFloat = 10 // outer radius

let phi = CGFloat(arc4random())/(CGFloat(UInt32.max) + 1) * 2 * .pi
let rSquared = (CGFloat(arc4random())/CGFloat(UInt32.max) * (r2 * r2 - r1 * r1) + r1 * r1)
let r = rSquared.squareRoot()

let randomPoint = CGPoint(x: r * cos(phi), y: r * sin(phi))
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Wauw!! This is amazing, I just accepted rmaddys answer but I will use this one :) – J. Doe Apr 05 '18 at 20:54
  • @J.Doe If you think this answer is better, you should accept it over mine. Don't worry, I won't be offended. :) – rmaddy Apr 05 '18 at 21:19
  • You passed up a chance to use the new `Double` random number functions in Swift 4 (`drand48()`) – Duncan C Apr 06 '18 at 00:15
  • @DuncanC: [`drand48`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html) is not new at all, and not necessarily better than `arc4random`, compare [Why use Float(arc4random()) / 0xFFFFFFFF instead of drand()](https://stackoverflow.com/questions/34490662/why-use-floatarc4random-0xffffffff-instead-of-drand). In particular, it has to be seeded. – Perhaps you were thinking of [SE-0202 Random Unification](https://github.com/apple/swift-evolution/blob/master/proposals/0202-random-unification.md)? That is not yet implemented in Swift 4.1, it is currently under review. – Martin R Apr 06 '18 at 03:58
  • I should have known `drand48()` was a C math library function. – Duncan C Apr 06 '18 at 15:30