Skip to content

Instantly share code, notes, and snippets.

@snlehton
Last active February 8, 2018 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snlehton/355234866efb2c15353441118fc5034d to your computer and use it in GitHub Desktop.
Save snlehton/355234866efb2c15353441118fc5034d to your computer and use it in GitHub Desktop.
My RandomUtils for Unity
using UnityEngine;
/* Random Utils By Sampsa Lehtonen, sampsa.lehtonen(AT)iki.fi, @snlehton
Some of my Random utils created for procedural generation and deterministic randomness.
Uses Linear Feedback Shift Register (LFSR) for quick and dirty random.
Lacks Perlin noise... need to reimplement the one in Unity in case they go and change the implementation.
CAUTION: I haven't verified the distribution of any of these functions, so use at your own risk. The aim has been to have
deterministic Unity-implementation-free random functions with features that Unity hasn't offered.
License is Creative Commons CC0
Included methods:
value
valueBipolar
GetPointInsideCircle
GetPointInsideCircle with inner & outer radius
GetPointOnSphere
GetPointOnHemisphere
GetPointInsideSphere
GetPointInCube
GetNormalDistribution
GetRandomRotation
GetPointInsideHexagon
GetPointInsideHexagon2D
Range (int & float)
Easy usage:
Quaternion rot = RandomUtils.GetRandomRotation();
Vector3 pointInsideUnitSphere = RandomUtils.GetPointInsideSphere(1.0f);
Deterministic usage for procgen etc (the random state is captured by LFSRState):
LFSRState randomState = new LFSRState(123);
Quaternion rot = RandomUtils.GetRandomRotation(ref randomState);
Vector3 pointInsideUnitSphere = RandomUtils.GetPointInsideSphere(ref randomState, 1.0f);
Use randomState.Shuffle(seed or index) to generate new random state for recursive procgen functions.
void ProcGen(LFSRState random)
{
... do stuff with random...
// create branches with deterministic random for each branch using Shuffle
int branches = random.Range(3, 5);
for (int i = 0; i < branches; i++)
ProcGen(random.Shuffle(i));
}
*/
/*
Linear Feeback Shift Register
FROM: http://www.maximintegrated.com/en/app-notes/index.mvp/id/4400
*/
public struct LFSRState
{
private const uint POLY_MASK_32 = 0xB4BCD35C;
private const uint POLY_MASK_31 = 0x7A5BC2E3;
private const int MaxRandomInteger = 0x7fffffff;
public uint lfsr32, lfsr31;
public LFSRState(int seed)
{
lfsr31 = 0;
lfsr32 = 0;
Seed(seed);
}
private uint shift_lfsr(ref uint lfsr, uint mask)
{
uint feedback = lfsr & (uint)1;
lfsr = (lfsr >> 1);
if (feedback == 1)
lfsr ^= mask;
return lfsr;
}
public void Seed(int seed)
{
lfsr32 = (uint)((seed & 0xffff) * POLY_MASK_32);
lfsr31 = (uint)((seed >> 15) * POLY_MASK_31);
// Guard against zero seeds
if (lfsr32 == 0)
lfsr32 = 1;
if (lfsr31 == 0)
lfsr31 = 1;
}
public static int Random(ref LFSRState state)
{
state.shift_lfsr(ref state.lfsr32, POLY_MASK_32);
uint v = state.shift_lfsr(ref state.lfsr31, POLY_MASK_31) ^ state.shift_lfsr(ref state.lfsr32, POLY_MASK_32);
int value = (int)(v & MaxRandomInteger);
return value;
}
public static float RandomFloat(ref LFSRState state)
{
return Random(ref state) / (float)MaxRandomInteger;
}
// Creates a new LFSRState by shuffling this state. Useful for implementing recursive procedural content generation
public LFSRState Shuffle(int shuffle)
{
LFSRState newState = new LFSRState();
newState.lfsr31 = (uint)(lfsr31 + shuffle * POLY_MASK_31);
newState.lfsr32 = (uint)(lfsr32 + shuffle * POLY_MASK_32);
return newState;
}
public override string ToString()
{
return "(" + lfsr31 + ", " + lfsr32 + ")";
}
}
public static class RandomUtils
{
public static LFSRState _random = GetRandomState();
public static float value
{
get { return GetRandomFloat(ref _random); }
}
public static float valueBipolar
{
get { return GetRandomFloat(ref _random) * 2 - 1; }
}
public static float GetRandomFloat(ref LFSRState state)
{
var value = LFSRState.RandomFloat(ref state);
return value;
}
public static int GetRandomInt(ref LFSRState state)
{
var value = LFSRState.Random(ref state);
return value;
}
public static LFSRState GetRandomState(int seed = 1234567)
{
var random = new LFSRState();
random.Seed(seed);
return random;
}
// GetPointInsideCircle
public static Vector2 GetPointInsideCircle2D(ref LFSRState state, float radius = 1.0f)
{
Vector2 randomPoint;
do
{
randomPoint.x = LFSRState.RandomFloat(ref state) * 2 - 1;
randomPoint.y = LFSRState.RandomFloat(ref state) * 2 - 1;
} while (randomPoint.sqrMagnitude > 1.0f);
return randomPoint * radius;
}
// https://stackoverflow.com/questions/19337314/generate-random-point-on-a-2d-disk-in-3d-space-given-normal-vector
public static Vector3 GetPointInsideCircle(ref LFSRState state, Vector3 position, Vector3 normal, float radius = 1.0f)
{
Vector3 u;
Vector3 v;
GetOrthogonalBasisFromNormal(normal, out u, out v);
return GetPointInsideCircle(ref state, position, u, v, radius);
}
public static Vector3 GetPointInsideCircle(Vector3 position, Vector3 normal, float radius = 1.0f)
{
return GetPointInsideCircle(ref _random, position, normal, radius);
}
public static Vector3 GetPointInsideCircle(ref LFSRState state, Vector3 position, Vector3 u, Vector3 v, float radius = 1.0f)
{
Vector2 random2D = GetPointInsideCircle2D(ref state, radius);
return position + u * random2D.x + v * random2D.y;
}
public static Vector3 GetPointInsideCircle(Vector3 position, Vector3 u, Vector3 v, float radius = 1.0f)
{
return GetPointInsideCircle(ref _random, position, u, v, radius);
}
// GetPointInsideHexagon
public static Vector2 GetPointInsideHexagon2D(ref LFSRState state)
{
float x = LFSRState.RandomFloat(ref state) - 1;
float y = LFSRState.RandomFloat(ref state) * 3 - 1;
// slice
if (y > x + 1)
{
x += 1;
y -= 1;
}
// tilt
x -= y * 0.5f;
// squash with sqrt(1 - 0.5^2) to make y size 1
y *= 0.8660254037844386f;
return new Vector2(x, y);
}
public static Vector3 GetPointInsideHexagon(ref LFSRState state, Vector3 position, Vector3 dirX, Vector3 dirY, float radius = 1.0f)
{
var pointInsideHexagon = GetPointInsideHexagon2D(ref state);
return pointInsideHexagon.x * dirX + pointInsideHexagon.y * dirY + position;
}
public static Vector3 GetPointInsideHexagon(Vector3 position, Vector3 dirX, Vector3 dirY, float radius = 1.0f)
{
return GetPointInsideHexagon(ref _random, position, dirX, dirY, radius);
}
// GetPointInsideCircle (inner & outer)
// uniform circle random based on http://marc-b-reynolds.github.io/distribution/2016/11/28/Uniform.html
public static Vector3 GetPointInsideCircle(ref LFSRState state, Vector3 position, Vector3 normal, float innerRadius, float outerRadius)
{
Vector3 u;
Vector3 v;
GetOrthogonalBasisFromNormal(normal, out u, out v);
float angle = LFSRState.RandomFloat(ref state) * Mathf.PI * 2;
float min = 0;
if (innerRadius < outerRadius)
{
min = innerRadius / outerRadius;
}
else
{
min = outerRadius / innerRadius;
}
// convert min to squared space
min = min * min;
float a = LFSRState.RandomFloat(ref state);
a = a *(1 - min) + min;
// convert back to linear space
a = Mathf.Sqrt(a);
float x = Mathf.Cos(angle) * a;
float y = Mathf.Sin(angle) * a;
return position + x * u + y * v;
}
public static Vector3 GetPointInsideCircle(Vector3 position, Vector3 normal, float innerRadius,
float outerRadius)
{
return GetPointInsideCircle(ref _random, position, normal, innerRadius, outerRadius);
}
// GetPointOnSphere
// from http://www.pouet.net/topic.php?which=9613&page=1
public static Vector3 GetPointOnSphere(ref LFSRState state, float radius = 1.0f)
{
float s = LFSRState.RandomFloat(ref state) * Mathf.PI * 2.0f;
float t = LFSRState.RandomFloat(ref state) * 2.0f - 1.0f;
float temp = Mathf.Sqrt(1.0f - t * t);
return new Vector3(Mathf.Sin(s) * temp, Mathf.Cos(s) * temp, t) * radius;
}
public static Vector3 GetPointOnSphere(float radius = 1.0f)
{
return GetPointOnSphere(ref _random, radius);
}
// GetPointOnHemisphere
public static Vector3 GetPointOnHemisphere(ref LFSRState state, Vector3 dir, float radius = 1.0f)
{
Vector3 v = GetPointOnSphere(ref state, radius);
return v * Mathf.Sign(Vector3.Dot(v, dir));
}
public static Vector3 GetPointOnHemisphere(Vector3 dir, float radius = 1.0f)
{
return GetPointOnHemisphere(ref _random, dir, radius);
}
// GetPointInsideSphere
public static Vector3 GetPointInsideSphere(ref LFSRState state, float radius = 1.0f)
{
while (true)
{
var point = GetPointInCube(ref state, Vector3.one);
if (point.sqrMagnitude < 1)
return point * radius;
}
}
public static Vector3 GetPointInsideSphere(float radius = 1.0f)
{
return GetPointInsideSphere(ref _random, radius);
}
// GetPointInCube
public static Vector3 GetPointInCube(ref LFSRState state, Vector3 extent)
{
float x = LFSRState.RandomFloat(ref state) * 2 - 1;
float y = LFSRState.RandomFloat(ref state) * 2 - 1;
float z = LFSRState.RandomFloat(ref state) * 2 - 1;
return new Vector3(x * extent.x, y * extent.y, z * extent.z);
}
public static Vector3 GetPointInCube(Vector3 extent)
{
return GetPointInCube(ref _random, extent);
}
// GetNormalDistribution
public static float GetNormalDistribution(ref LFSRState state, float mean, float deviation)
{
float alpha = 2 * Mathf.PI * LFSRState.RandomFloat(ref state);
float unit = LFSRState.RandomFloat(ref state);
return mean + Mathf.Cos(alpha) * Mathf.Sqrt(-2 * Mathf.Log(unit)) * deviation;
}
public static float GetNormalDistribution(float mean, float deviation)
{
return GetNormalDistribution(ref _random, mean, deviation);
}
// GetRandomRotation
// http://planning.cs.uiuc.edu/node198.html
public static Quaternion GetRandomRotation(ref LFSRState state)
{
float u1 = LFSRState.RandomFloat(ref state);
float u2 = LFSRState.RandomFloat(ref state) * 2 * Mathf.PI;
float u3 = LFSRState.RandomFloat(ref state) * 2 * Mathf.PI;
float a = Mathf.Sqrt(1 - u1);
float b = Mathf.Sqrt(u1);
Quaternion quat = new Quaternion(a * Mathf.Sin(u2), a * Mathf.Cos(u2), b * Mathf.Sin(u3), b * Mathf.Cos(u3));
return quat;
}
public static Quaternion GetRandomRotation()
{
return GetRandomRotation(ref _random);
}
// Range
public static float Range(ref LFSRState state, float min, float max)
{
if (max < min)
{
float temp = min;
min = max;
max = temp;
}
float range = max - min;
return min + LFSRState.RandomFloat(ref state) * range;
}
public static float Range(float min, float max)
{
return Range(ref _random, min, max);
}
/**
<summary>random range, max is exclusive</summary>
*/
public static int Range(ref LFSRState state, int min, int max)
{
if (max < min)
{
int temp = min;
min = max;
max = temp;
}
int count = max - min;
int ran = LFSRState.Random(ref state) % count;
return min + ran;
}
public static int Range(int min, int max)
{
return Range(ref _random, min, max);
}
// GetOrthogonalBasisFromNormal
public static void GetOrthogonalBasisFromNormal(Vector3 normal, out Vector3 u, out Vector3 v)
{
u = normal;
v = normal;
float a = normal.x;
float b = normal.y;
float c = normal.z;
float absA = Mathf.Abs(a);
float absB = Mathf.Abs(b);
float absC = Mathf.Abs(c);
if (absA < absB)
{
if (absA < absC)
{
// x is smallest
u = new Vector3(0, -c, b);
// u dot n = -c*b+b*c = 0
v = new Vector3(b * b + c * c, -a * b, -a * c);
// v dot n = a*b*c-a*b*c = 0
}
else
{
// z is smallest
u = new Vector3(b, -a, 0);
//u dot n = b*a - a*b = 0
v = new Vector3(-c * a, -c * b, a * a + b * b);
// v dot n = -c*a*a -c*b*b + a*a*c + b*b*c = 0
// v dot u = -a*b*c + a*b*c = 0
}
}
else
{
// x is not smallest
if (absB < absC)
{
// y is smallest
u = new Vector3(-c, 0, a);
// u dot n = -c*a+a*c = 0
v = new Vector3(-b * a, a * a + c * c, -b * c);
// v dot n = -a*b*a + a*a*b + c*c*b - b*c*c = 0
// v dot u = a*b*c -a*b*c = 0
}
else
{
// z is smallest (same as above)
u = new Vector3(b, -a, 0);
v = new Vector3(-c * a, -c * b, a * a + b * b);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment