This is a list of some random generators I use quite frequently.
I was going to post this into Algorithms Mega-Thread, but it is clear to me that this is a little too long and in-depth, especially as I continue to expand this in the future. This is my first post, so let me know if anything can be improved. If you find any bugs in the code, let me know. Thanks for letting me into the community .
Some Definitions
Uniform distributions over a space or subspace implies that no point is any more likely to generate than any other point.
A probability distribution function describes how probable the generation of any value is relative to any other value. For example, these two probability distribution functions:
The rightward axis describes the output values. Both functions are limited to outputs between 0 and 1.
The upward axis tells us a number relating to the probability of generating said output value.
The blue function tells us that no value is any more likely to generate than any other value because the probabilities are uniform. This is a uniform distribution
The red function, for example, tells us that the value 0.5 is twice as likely to be generated as the value 0.25. This is a triangular distribution.
Uniform Random Variable [0, 1)
This generates a uniformly random variable r
, 0 <= r < 1
local r = math.random()
Uniform Random Variable (0, 1]
This generates a uniformly random variable r
, 0 < r <= 1
Usually inclusivity does not matter, but sometimes it will.
local r = 1 - math.random()
Uniform Random Variable [l0, l1)
This generates a uniformly random variable r
, l0 <= r < l1
local r = (l1 - l0)*math.random() + l0
Uniform Random 2D Unit Vector
This generates a random point (x, y)
on a circle
local a = 2*math.pi*math.random()
local x, y = math.cos(a), math.sin(a)
Triangular Random Variable [0, 1)
This generates a random variable r
, whose probability distribution is triangular.
It will be much more likely to generate points near 1 than near 0.
local r = math.sqrt(math.random())
-- This is a fun way to accomplish the same thing:
local r = 1 - math.abs(math.random() - math.random())
Uniform Random 2D Vector on a Disk
This generates a random point (x, y)
within a circle.
We accomplish this by generating a random 2D unit, then multiplying it by a triangular distribution. There are (proportionally to the radius) more points further out on the disk than near the center
local a = 2*math.pi*math.random()
local r = math.sqrt(math.random())
local x, y = r*math.cos(a), r*math.sin(a)
Uniform Random Unit 3D Vector
This generates a random point (x, y, z)
on a sphere.
We recognize that the number of points of any slice (of some thickness) out of a sphere is dependent only upon the radius of the sphere an the thickness of the slice.
So we can pick a random slice along the x axis and a random angle to generate a point on the surface of a sphere.
local a = 2*math.pi*math.random()
local x = 2*math.random() - 1
local r = math.sqrt(1 - x*x)
local y, z = r*math.cos(a), r*math.sin(a)
Uniform Random Unit 3D Vector Within an Angle
We can extend the above concept to generate points within a slice defined between two angles.
Not to scale
For example, we could choose to generate points just within 0 to 40 degrees of the -z axis, or 70 to 80 degrees of the -z axis. You can choose any axis.
-- assuming axis and perp are unit orthogonal vectors
local function uniformRandomUnit3DVectorWithinAngularRange(minAngle, maxAngle, axis, perp)
-- transform angle to length along axis
local l0 = math.cos(maxAngle)
local l1 = math.cos(minAngle)
-- generate random length between given lengths
local l = (l1 - l0)*math.random() + l0
-- compute radius given length along axis
local r = math.sqrt(1 - l*l)
-- compute the axis-perpendicular point
local a = 2*math.pi*math.random()
local u, v = r*math.cos(a), r*math.sin(a)
-- now transform into some world space
return l*axis + u*perp + v*axis:Cross(perp)
end
Uniform Random 3D Vector Within Sphere
Taking the unit sphere solutions above and multiplying the result by a radius generated from the correct probability distribution, we get (x, y, z)
is a uniform random point within a sphere.
local a = 2*math.pi*math.random()
local l = 2*math.random() - 1
local i = math.sqrt(1 - l*l)
-- we now compute a random radius, and multiply it into the result
local r = math.random()^(1/3)
local x = r*l
local y = r*i*math.cos(a)
local z = r*i*math.sin(a)
or alternatively:
local function uniformRandom3DVectorWithinSphereWithinAngularRange(minAngle, maxAngle, axis, perp)
local r = math.random()^(1/3)
return r*uniformRandomUnit3DVectorWithinAngularRange(minAngle, maxAngle, axis, perp)
end
Gaussian Distributed Random Variable(s) (-inf, inf), Centered at 0 with Standard Deviation 1
Gaussian distributed random variables are useful for two reasons:
- They are the result of adding a bunch of random variables together. For example: height is the result of nutrition, activity, and countless different genetic traits.
- Knowing the value of any combination of gaussian random values tells you nothing about any other orthogonal combination of the same gaussian random variables. An example of something that does not have this property: If I tell you I have a point within a unit circle, and the x coordinate is 0.8, you know that the y coordinate must be between -0.6 and 0.6. With a 2D gaussian point, knowing x tells you nothing about y.
It generates values (in 1D) according to this probability distribution:
It is not possible to algebraically generate a single gaussian random variable. However, we CAN algebraically generate the distance from the center of two gaussian random variables, and we CAN generate a random direction. This is called the Box-Muller transform.
-- we must use 1 - math.random(), otherwise we will sometimes get log(0) and generate nan values
local radius = math.sqrt(-2*math.log(1 - math.random()))
local angle = 2*math.pi*math.random()
local x, y = radius*math.cos(angle), radius*math.sin(angle)
-- the standard deviation of this 2D point is sqrt(2)
x
and y
are each gaussian random variables. You can throw away one if you only need one. For example, we could write some succinct code like:
local function gaussianRandom()
return math.sqrt(-2*math.log(1 - math.random()))*math.cos(2*math.pi*math.random())
end
Uniform Random Unit in Arbitrary Dimensions
Using property no. 2 of gaussian random variables, we can generate a random unit vector in any dimension.
local function randomUnit2D()
local x = gaussianRandom()
local y = gaussianRandom()
local r = math.sqrt(x*x + y*y)
return x/r, y/r
end
local function randomUnit3D()
local x = gaussianRandom()
local y = gaussianRandom()
local z = gaussianRandom()
local r = math.sqrt(x*x + y*y + z*z)
return x/r, y/r, z/r
end
-- The pattern continues...
Uniform Random Rotation CFrame
Quaternions map uniformly to rotation matrices, and quaternions can be interpreted as lying on the surface a 4D hyper sphere, so we can generate a 4D unit vector, and derive a random rotation CFrame from this. Roblox’s Quaternion CFrame constructor does not require a unitized quaternion, so we can be lazy about unitization.
local function randomRotationCFrame()
local w = gaussianRandom()
local x = gaussianRandom()
local y = gaussianRandom()
local z = gaussianRandom()
return CFrame.new(0, 0, 0, x, y, z, w)
end
Uniform Random 4D Unit (Quaternion)
If we want to be more efficient about it, we can make some optimizations to our naïve code:
local function randomUnit4D()
local log0 = math.log(1 - math.random())
local log1 = math.log(1 - math.random())
local ang0 = 2*math.pi*math.random()
local ang1 = 2*math.pi*math.random()
local sqrt0 = math.sqrt(log0/(log0 + log1))
local sqrt1 = math.sqrt(log1/(log0 + log1))
local w, x = sqrt0*math.cos(ang0), sqrt0*math.sin(ang0)
local y, z = sqrt1*math.cos(ang1), sqrt1*math.sin(ang1)
return w, x, y, z
end
Uniform Random Vector within N-Sphere
We can take any of our uniform random unit functions and multiply it by a correctly chosen radius relating to the dimension.
local function randomWithinSphere3D()
local x0 = gaussianRandom()
local x1 = gaussianRandom()
local x2 = gaussianRandom()
local r = math.random()^(1/3)/math.sqrt(x0*x0 + x1*x1 + x2*x2)
return r*x0, r*x1, r*x2
end
local function randomWithinSphere5D()
local x0 = gaussianRandom()
local x1 = gaussianRandom()
local x2 = gaussianRandom()
local x3 = gaussianRandom()
local x4 = gaussianRandom()
-- the power of our radius multiplier is 1/dimension
local r = math.random()^(1/5)/math.sqrt(x0*x0 + x1*x1 + x2*x2 + x3*x3 + x4*x4)
return r*x0, r*x1, r*x2, r*x3, r*x4
end
Picking an Object from a List of Objects and Given Probabilities
If we have a list of objects and the relative frequency with which we want each one to be chosen, we can pick a random number and map this to an object.
local objects = {
{
thing = "I am the most common";
frequency = 5;
}, {
thing = "I am somewhat common";
frequency = 3;
}, {
thing = "I am least common";
frequency = 1;
}
}
-- We can imagine we have a bag, put a number of each item in the bag, and
-- randomly choose one.
-- We can do this more efficiently in code by assigning numbers to objects, then
-- picking a random number, and taking the associated object.
-- The more numbers we assign to a single object, the more likely it will be to
-- choose that one.
local totalFrequency = 0
for i = 1, #objects do
totalFrequency = totalFrequency + objects[i].frequency
end
-- choose a number between 1 and totalFrequency
local randomChoice = totalFrequency*math.random()
-- and figure out which object is associated with that number
local count = 0
for i = 1, #objects do
count = count + objects[i].frequency
if randomChoice <= count then
return objects[i].thing
end
end
So How Was this Derived? AKA, How to Create a Random Generator given a PDF (probability distribution function)
r = integrate(PDF(x), x, -inf, x)/integrate(PDF(x), x, -inf, inf), solve for x
This will map a uniform random unit, r
, generated from math.random()
, into a random number, x
, distributed by PDF(x)
. It can be quite fickle, and often times is not solvable algebraically, but sometimes, like in all the cases above, it is!
For an intuitive explanation, watch this video: