You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
15 KiB
510 lines
15 KiB
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
public class MathExtras : MonoBehaviour
|
|
{
|
|
// TYPES
|
|
/// <summary>
|
|
/// Serializable version of Keyframe struct.
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public struct Keyframe
|
|
{
|
|
[Tooltip("Time and Value of keyframe.")]
|
|
public Vector2 timeValue;
|
|
|
|
[Tooltip("Tangents of keyframe.")]
|
|
public Vector2 tangents;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Representation of an AABB volume.
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public struct AabbVolume
|
|
{
|
|
[Tooltip("Minimum corner of AABB volume.")]
|
|
public Vector3 minCorner;
|
|
|
|
[Tooltip("Maximum corner of AABB volume.")]
|
|
public Vector3 maxCorner;
|
|
|
|
public AabbVolume(Vector3 min, Vector3 max)
|
|
{
|
|
this.minCorner = min;
|
|
this.maxCorner = max;
|
|
|
|
this.Validate();
|
|
}
|
|
|
|
public void Validate()
|
|
{
|
|
Vector3 mn, mx;
|
|
|
|
mn.x = Mathf.Min(this.minCorner.x, this.maxCorner.x);
|
|
mn.y = Mathf.Min(this.minCorner.y, this.maxCorner.y);
|
|
mn.z = Mathf.Min(this.minCorner.z, this.maxCorner.z);
|
|
|
|
mx.x = Mathf.Max(this.minCorner.x, this.maxCorner.x);
|
|
mx.y = Mathf.Max(this.minCorner.y, this.maxCorner.y);
|
|
mx.z = Mathf.Max(this.minCorner.z, this.maxCorner.z);
|
|
|
|
if (mn == mx)
|
|
Debug.LogError($"Bad AABB Volume: {this.minCorner} -> {this.maxCorner}");
|
|
|
|
this.minCorner = mn;
|
|
this.maxCorner = mx;
|
|
}
|
|
|
|
public bool ContainsPoint(Vector3 pt)
|
|
{
|
|
return
|
|
(
|
|
this.minCorner.x <= pt.x && this.maxCorner.x >= pt.x &&
|
|
this.minCorner.y <= pt.y && this.maxCorner.y >= pt.y &&
|
|
this.minCorner.z <= pt.z && this.maxCorner.z >= pt.z
|
|
);
|
|
}
|
|
|
|
public Vector3 GetNormalizedPoint(Vector3 pt)
|
|
{
|
|
return new Vector3
|
|
(
|
|
Mathf.InverseLerp(this.minCorner.x, this.maxCorner.x, pt.x),
|
|
Mathf.InverseLerp(this.minCorner.y, this.maxCorner.y, pt.y),
|
|
Mathf.InverseLerp(this.minCorner.z, this.maxCorner.z, pt.z)
|
|
);
|
|
}
|
|
|
|
public Vector3 GetPoint(Vector3 normalized)
|
|
{
|
|
return new Vector3
|
|
(
|
|
Mathf.Lerp(this.minCorner.x, this.maxCorner.x, normalized.x),
|
|
Mathf.Lerp(this.minCorner.y, this.maxCorner.y, normalized.y),
|
|
Mathf.Lerp(this.minCorner.z, this.maxCorner.z, normalized.z)
|
|
);
|
|
}
|
|
|
|
public Vector3 Extents { get { return this.maxCorner - this.minCorner; } }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// An "octohedral neighborhood" for bucketing data points to the 6 cardinal directions.
|
|
/// </summary>
|
|
public struct Neighborhood
|
|
{
|
|
// STATIC
|
|
private static Vector3[] _directions = new Vector3[]
|
|
{
|
|
new Vector3( 0f, 0f, 0f),
|
|
|
|
new Vector3( 1f, 0f, 0f),
|
|
new Vector3(-1f, 0f, 0f),
|
|
new Vector3( 0f, 1f, 0f),
|
|
new Vector3( 0f, -1f, 0f),
|
|
new Vector3( 0f, 0f, 1f),
|
|
new Vector3( 0f, 0f, -1f),
|
|
};
|
|
|
|
// TYPES
|
|
public enum Direction
|
|
{
|
|
INVALID = 0,
|
|
|
|
X_POS = 1,
|
|
X_NEG = 2,
|
|
Y_POS = 3,
|
|
Y_NEG = 4,
|
|
Z_POS = 5,
|
|
Z_NEG = 6
|
|
}
|
|
|
|
|
|
// INTERNALS
|
|
private bool _init;
|
|
private int[] _buckets;
|
|
|
|
|
|
// BEHAVIORS
|
|
private void Init()
|
|
{
|
|
this._init = true;
|
|
this._buckets = new int[_directions.Length];
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
if (!this._init)
|
|
this.Init();
|
|
else
|
|
{
|
|
this._buckets[0] = 0;
|
|
this._buckets[1] = 0;
|
|
this._buckets[2] = 0;
|
|
this._buckets[3] = 0;
|
|
this._buckets[4] = 0;
|
|
this._buckets[5] = 0;
|
|
this._buckets[6] = 0;
|
|
}
|
|
}
|
|
|
|
public Direction GetDir(Transform relative, Vector3 point)
|
|
{
|
|
return this.GetDir(relative.InverseTransformDirection(point));
|
|
}
|
|
|
|
public Direction GetDir(Vector3 point)
|
|
{
|
|
// Classify into direction
|
|
float bestDot = float.MinValue;
|
|
int bestBucket = 0;
|
|
|
|
for (int i = 0; i < _directions.Length; i++)
|
|
{
|
|
float dot = Vector3.Dot(point, _directions[i]);
|
|
|
|
if (dot > bestDot)
|
|
{
|
|
bestDot = dot;
|
|
bestBucket = i;
|
|
}
|
|
}
|
|
|
|
// Return
|
|
return (Direction)bestBucket;
|
|
}
|
|
|
|
public void Add(Transform relative, Vector3 point)
|
|
{
|
|
this.Add(relative.InverseTransformDirection(point));
|
|
}
|
|
|
|
public void Add(Vector3 point)
|
|
{
|
|
// Guard; not init
|
|
if (!this._init)
|
|
this.Init();
|
|
|
|
// Classify and increment
|
|
this._buckets[(int)this.GetDir(point)]++;
|
|
}
|
|
|
|
public int GetCount(Direction dir)
|
|
{
|
|
if (!this._init)
|
|
this.Init();
|
|
|
|
return this._buckets[(int)dir];
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average)
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public struct SMA
|
|
{
|
|
// PROPERTIES
|
|
public float CurAvg { get { return this._curAvg; } }
|
|
|
|
|
|
// INTERNALS
|
|
private bool _init;
|
|
|
|
private float[] _buffer;
|
|
private float _curAvg;
|
|
private int _index;
|
|
|
|
|
|
public SMA(int len, float fill)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = fill;
|
|
this._buffer = new float[len];
|
|
this._index = 0;
|
|
}
|
|
|
|
public void AddSample(float sample)
|
|
{
|
|
// Guard
|
|
if (!this._init)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = 0f;
|
|
this._buffer = new float[5];
|
|
this._index = 0;
|
|
}
|
|
|
|
this._curAvg += (sample - this._buffer[this._index]) / this._buffer.Length;
|
|
this._buffer[this._index] = sample;
|
|
this._index = (this._index + 1) % this._buffer.Length;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average)
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public struct SMA2
|
|
{
|
|
// PROPERTIES
|
|
public Vector2 CurAvg { get { return this._curAvg; } }
|
|
|
|
|
|
// INTERNALS
|
|
private bool _init;
|
|
|
|
private Vector2[] _buffer;
|
|
private Vector2 _curAvg;
|
|
private int _index;
|
|
|
|
|
|
public SMA2(int len, Vector2 fill)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = fill;
|
|
this._buffer = new Vector2[len];
|
|
this._index = 0;
|
|
}
|
|
|
|
public void AddSample(Vector2 sample)
|
|
{
|
|
// Guard
|
|
if (!this._init)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = Vector2.zero;
|
|
this._buffer = new Vector2[5];
|
|
this._index = 0;
|
|
}
|
|
|
|
this._curAvg += (sample - this._buffer[this._index]) / this._buffer.Length;
|
|
this._buffer[this._index] = sample;
|
|
this._index = (this._index + 1) % this._buffer.Length;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average)
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public struct SMA3
|
|
{
|
|
// PROPERTIES
|
|
public Vector3 CurAvg { get { return this._curAvg; } }
|
|
|
|
|
|
// INTERNALS
|
|
private bool _init;
|
|
|
|
private Vector3[] _buffer;
|
|
private Vector3 _curAvg;
|
|
private int _index;
|
|
|
|
|
|
public SMA3(int len, Vector3 fill)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = fill;
|
|
this._buffer = new Vector3[len];
|
|
this._index = 0;
|
|
}
|
|
|
|
public void AddSample(Vector3 sample)
|
|
{
|
|
// Guard
|
|
if (!this._init)
|
|
{
|
|
this._init = true;
|
|
|
|
this._curAvg = Vector3.zero;
|
|
this._buffer = new Vector3[5];
|
|
this._index = 0;
|
|
}
|
|
|
|
this._curAvg += (sample - this._buffer[this._index]) / this._buffer.Length;
|
|
this._buffer[this._index] = sample;
|
|
this._index = (this._index + 1) % this._buffer.Length;
|
|
}
|
|
}
|
|
|
|
|
|
// BEHAVIORS
|
|
/// <summary>
|
|
/// Returns a rotation vector whose elements are in the range (-180f, 180f).
|
|
/// </summary>
|
|
/// <param name="euler">Rotation vector to center</param>
|
|
/// <returns>The rotation with each element in the range (-180f, 180f).</returns>
|
|
public static Vector3 CenterEulerAngles(Vector3 euler)
|
|
{
|
|
return new Vector3
|
|
(
|
|
euler.x > 180f ? euler.x - 360f : euler.x,
|
|
euler.y > 180f ? euler.y - 360f : euler.y,
|
|
euler.z > 180f ? euler.z - 360f : euler.z
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repeat then Inverse Lerp
|
|
/// </summary>
|
|
/// <param name="t">Input value</param>
|
|
/// <param name="max">Maximum value</param>
|
|
/// <returns>Remainder of (t % max), normalized to range [0, 1]</returns>
|
|
public static float NormalizedRepeat(float t, float max)
|
|
{
|
|
return Mathf.InverseLerp(0f, max, Mathf.Repeat(t, max));
|
|
}
|
|
|
|
public static Vector3 Apply(Vector3 op, float w, Matrix4x4 mat)
|
|
{
|
|
Vector4 v = op;
|
|
v.w = w;
|
|
|
|
return mat * v;
|
|
}
|
|
|
|
public static float LerpSmooth(float a, float b, float dt, float smoothingTime)
|
|
{
|
|
// Thanks Freya
|
|
return Mathf.Lerp(a, b, 1f - Mathf.Pow(2f, (-1f * dt / (-1f * Mathf.Clamp(smoothingTime, 0.01f, 5f) / -4.321928f)))); // Constant is Mathf.Log(0.05f, 2f) which is 'precision'
|
|
}
|
|
|
|
public static Vector2 LerpSmooth2(Vector2 a, Vector2 b, float dt, float smoothingTime)
|
|
{
|
|
return Vector2.Lerp(a, b, 1f - Mathf.Pow(2f, (-1f * dt / (-1f * Mathf.Clamp(smoothingTime, 0.01f, 5f) / -4.321928f)))); // Constant is Mathf.Log(0.05f, 2f) which is 'precision'
|
|
}
|
|
|
|
public static Vector3 LerpSmooth3(Vector3 a, Vector3 b, float dt, float smoothingTime)
|
|
{
|
|
return Vector3.Lerp(a, b, 1f - Mathf.Pow(2f, (-1f * dt / (-1f * Mathf.Clamp(smoothingTime, 0.01f, 5f) / -4.321928f)))); // Constant is Mathf.Log(0.05f, 2f) which is 'precision'
|
|
}
|
|
|
|
public static Quaternion LerpSmoothRot(Quaternion a, Quaternion b, float dt, float smoothingTime)
|
|
{
|
|
return Quaternion.Slerp(a, b, 1f - Mathf.Pow(2f, (-1f * dt / (-1f * Mathf.Clamp(smoothingTime, 0.01f, 5f) / -4.321928f)))); // Constant is Mathf.Log(0.05f, 2f) which is 'precision'
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Calculates and returns the closest points on a pair of given lines.
|
|
/// </summary>
|
|
/// <param name="pos1">Point on line 1.</param>
|
|
/// <param name="dir1">Direction of line 1.</param>
|
|
/// <param name="pos2">Point on line 2.</param>
|
|
/// <param name="dir2">Direction of line 2.</param>
|
|
/// <param name="closest1">Closest point between lines on line 1, outptut.</param>
|
|
/// <param name="closest2">Closest point between lines on line 2, outptut.</param>
|
|
/// <returns>True if calculation successful, false if not.</returns>
|
|
public static bool GetClosestPoints(Vector3 pos1, Vector3 dir1, Vector3 pos2, Vector3 dir2, out Vector3 closest1, out Vector3 closest2)
|
|
{
|
|
Vector3 n = Vector3.Cross(dir1, dir2);
|
|
Vector3 n1 = Vector3.Cross(dir1, n);
|
|
Vector3 n2 = Vector3.Cross(dir2, n);
|
|
|
|
float d1n2 = Vector3.Dot(dir1, n2);
|
|
float d2n1 = Vector3.Dot(dir2, n1);
|
|
|
|
if (!Mathf.Approximately(d1n2, 0f) && !Mathf.Approximately(d2n1, 0f))
|
|
{
|
|
closest1 = pos1 + (Vector3.Dot(pos2 - pos1, n2) / (Vector3.Dot(dir1, n2))) * dir1;
|
|
closest2 = pos2 + (Vector3.Dot(pos1 - pos2, n1) / (Vector3.Dot(dir2, n1))) * dir2;
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
closest1 = Vector3.zero;
|
|
closest2 = Vector3.zero;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Three calls to Mathf.PerlinNoise1D packaged for brevity.
|
|
/// Caller expected to handle offsets between dimensions to avoid variance issues.
|
|
/// </summary>
|
|
/// <param name="coords"></param>
|
|
/// <returns>Random Vector3 in range [0,1]^3</returns>
|
|
public static Vector3 CheapPerlin3d(Vector3 coords)
|
|
{
|
|
return new Vector3
|
|
(
|
|
Mathf.PerlinNoise1D(coords.x),
|
|
Mathf.PerlinNoise1D(coords.y),
|
|
Mathf.PerlinNoise1D(coords.z)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clips the given float at the given absolute value.
|
|
/// </summary>
|
|
/// <param name="op">Float to clip</param>
|
|
/// <param name="absMax">Maximum absolute value</param>
|
|
/// <returns>Float clipped to given absolute value.</returns>
|
|
public static float ClipFloat(float op, float absMax)
|
|
{
|
|
return (Mathf.Abs(op) > absMax) ? Mathf.Sign(op) * absMax : op;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clips the given vector at the given magnitude.
|
|
/// </summary>
|
|
/// <param name="op">Vector to clip</param>
|
|
/// <param name="max">Maximum magnitude</param>
|
|
/// <returns>Vector clipped to magnitude.</returns>
|
|
public static Vector3 ClipVec2(Vector2 op, float max)
|
|
{
|
|
return (op.sqrMagnitude > max * max) ? op.normalized * max : op;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clips the given vector at the given magnitude.
|
|
/// </summary>
|
|
/// <param name="op">Vector to clip</param>
|
|
/// <param name="max">Maximum magnitude</param>
|
|
/// <returns>Vector clipped to magnitude.</returns>
|
|
public static Vector3 ClipVec3(Vector3 op, float max)
|
|
{
|
|
return (op.sqrMagnitude > max * max) ? op.normalized * max : op;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an animation curve from the provided keyframes.
|
|
/// </summary>
|
|
/// <param name="dkfs">Keyframes to create a curve from.</param>
|
|
/// <returns>An animation curve from the given keyframes.</returns>
|
|
public static AnimationCurve CurveFromKeyframes(Keyframe[] dkfs)
|
|
{
|
|
UnityEngine.Keyframe[] kfs = new UnityEngine.Keyframe[dkfs.Length];
|
|
|
|
for (int i = 0; i < kfs.Length; i++)
|
|
kfs[i] = new UnityEngine.Keyframe(dkfs[i].timeValue.x, dkfs[i].timeValue.y, dkfs[i].tangents.x, dkfs[i].tangents.y);
|
|
|
|
return new AnimationCurve(kfs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Samples the distribution specified by the given quantile function curve.
|
|
/// </summary>
|
|
/// <param name="quantileCurve">Quantile curve for the desired distribution.</param>
|
|
/// <returns>Sample pulled from the distribution. [0,1] </returns>
|
|
public static float SampleDistribution(AnimationCurve quantileCurve)
|
|
{
|
|
return quantileCurve.Evaluate(Random.Range(0f, 1f));
|
|
}
|
|
}
|