using System.Collections; using System.Collections.Generic; using UnityEngine; public class MathExtras : MonoBehaviour { // TYPES /// /// Serializable version of Keyframe struct. /// [System.Serializable] public struct Keyframe { [Tooltip("Time and Value of keyframe.")] public Vector2 timeValue; [Tooltip("Tangents of keyframe.")] public Vector2 tangents; } /// /// Representation of an AABB volume. /// [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; } } } /// /// An "octohedral neighborhood" for bucketing data points to the 6 cardinal directions. /// 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]; } } /// /// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average) /// [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; } } /// /// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average) /// [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; } } /// /// Implementation of simple Moving Average (https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average) /// [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 /// /// Returns a rotation vector whose elements are in the range (-180f, 180f). /// /// Rotation vector to center /// The rotation with each element in the range (-180f, 180f). 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 ); } /// /// Repeat then Inverse Lerp /// /// Input value /// Maximum value /// Remainder of (t % max), normalized to range [0, 1] 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' } /// /// Calculates and returns the closest points on a pair of given lines. /// /// Point on line 1. /// Direction of line 1. /// Point on line 2. /// Direction of line 2. /// Closest point between lines on line 1, outptut. /// Closest point between lines on line 2, outptut. /// True if calculation successful, false if not. 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; } } /// /// Three calls to Mathf.PerlinNoise1D packaged for brevity. /// Caller expected to handle offsets between dimensions to avoid variance issues. /// /// /// Random Vector3 in range [0,1]^3 public static Vector3 CheapPerlin3d(Vector3 coords) { return new Vector3 ( Mathf.PerlinNoise1D(coords.x), Mathf.PerlinNoise1D(coords.y), Mathf.PerlinNoise1D(coords.z) ); } /// /// Clips the given float at the given absolute value. /// /// Float to clip /// Maximum absolute value /// Float clipped to given absolute value. public static float ClipFloat(float op, float absMax) { return (Mathf.Abs(op) > absMax) ? Mathf.Sign(op) * absMax : op; } /// /// Clips the given vector at the given magnitude. /// /// Vector to clip /// Maximum magnitude /// Vector clipped to magnitude. public static Vector3 ClipVec2(Vector2 op, float max) { return (op.sqrMagnitude > max * max) ? op.normalized * max : op; } /// /// Clips the given vector at the given magnitude. /// /// Vector to clip /// Maximum magnitude /// Vector clipped to magnitude. public static Vector3 ClipVec3(Vector3 op, float max) { return (op.sqrMagnitude > max * max) ? op.normalized * max : op; } /// /// Creates an animation curve from the provided keyframes. /// /// Keyframes to create a curve from. /// An animation curve from the given keyframes. 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); } /// /// Samples the distribution specified by the given quantile function curve. /// /// Quantile curve for the desired distribution. /// Sample pulled from the distribution. [0,1] public static float SampleDistribution(AnimationCurve quantileCurve) { return quantileCurve.Evaluate(Random.Range(0f, 1f)); } }