Last active
February 8, 2018 09:52
-
-
Save snlehton/355234866efb2c15353441118fc5034d to your computer and use it in GitHub Desktop.
My RandomUtils for Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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