//--------------------------------------------------------------------------// // Copyright 2023-2025 Chocolate Dinosaur Ltd. All rights reserved. // // For full documentation visit https://www.chocolatedinosaur.com // //--------------------------------------------------------------------------// using UnityEngine; using UnityInternal = UnityEngine.Internal; namespace ChocDino.UIFX { public enum FrustmIntersectResult { /// The object is completely outside of the planes. Out, /// The object is completely inside of the planes.The object is partially intersecting the planes. Partial, } [UnityInternal.ExcludeFromDocs] public static class MathUtils { public static FrustmIntersectResult GetFrustumIntersectsOBB(Plane[] planes, Vector3[] points) { Debug.Assert(planes != null); Debug.Assert(planes.Length == 6); Debug.Assert(points != null); Debug.Assert(points.Length == 8); FrustmIntersectResult result = FrustmIntersectResult.In; for (int j = 0; j < 6; j++) { var plane = planes[j]; int inCount = 0; int outCount = 0; for (int i = 0; i < 8 && (inCount == 0 || outCount == 0); i++) { // NOTE: We could use !s_planes[j].GetSide(s_boundsPoints[i]); but this doesn't allow // for points being ON the plane, which is a not uncommon scenario. bool isBehindPlane = (Vector3.Dot(plane.normal, points[i]) + plane.distance) < 0f; if (isBehindPlane) { outCount++; } else { inCount++; } } if (inCount == 0) { result = FrustmIntersectResult.Out; break; } else if (outCount != 0) { result = FrustmIntersectResult.Partial; // NOTE: Don't break, keep looking, the object may still be outside other planes } } return result; } [UnityInternal.ExcludeFromDocs] public static float Snap(float v, float snap) { float isnap = 1f / snap; return Mathf.FloorToInt(v * isnap) / isnap; } /// /// Adds padding to a number and then rounds up to the nearest multiple. /// This is useful for textures to ensure they have constant minimum padding amount, but also have a width/height that is a multiple size. /// This can allow a texture size that is frequently changing slightly (eg when filter sizes change) to not reallocate too frequently, and /// can stabilise flickering caused when downsampling very small which can cause textures to oscilate between odd/even sizes causing the /// texture sampling is jump around between frames and flicker. /// Eg params [9,10,10] = 20, [10,10,10] = 20, [11,10,10] = 30 /// [UnityInternal.ExcludeFromDocs] public static int PadAndRoundToNextMultiple(float v, int pad, int multiple) { multiple = Mathf.Max(1, multiple); int result = Mathf.CeilToInt(((float)System.Math.Ceiling((v + pad) / multiple)) * multiple); Debug.Assert(result >= v); Debug.Assert((result % multiple) == 0); return result; } [UnityInternal.ExcludeFromDocs] // Based on https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ public static float GetDampLerpFactor(float lambda, float deltaTime) { return 1f - Mathf.Exp(-lambda * deltaTime); } [UnityInternal.ExcludeFromDocs] public static float DampTowards(float a, float b, float lambda, float deltaTime) { return Mathf.Lerp(a, b, GetDampLerpFactor(lambda, deltaTime)); } [UnityInternal.ExcludeFromDocs] public static Vector2 DampTowards(Vector2 a, Vector2 b, float lambda, float deltaTime) { return Vector2.Lerp(a, b, GetDampLerpFactor(lambda, deltaTime)); } [UnityInternal.ExcludeFromDocs] public static Vector3 DampTowards(Vector3 a, Vector3 b, float lambda, float deltaTime) { return Vector3.Lerp(a, b, GetDampLerpFactor(lambda, deltaTime)); } [UnityInternal.ExcludeFromDocs] public static Vector4 DampTowards(Vector4 a, Vector4 b, float lambda, float deltaTime) { return Vector4.Lerp(a, b, GetDampLerpFactor(lambda, deltaTime)); } [UnityInternal.ExcludeFromDocs] public static Color DampTowards(Color a, Color b, float lambda, float deltaTime) { return Color.Lerp(a, b, GetDampLerpFactor(lambda, deltaTime)); } [UnityInternal.ExcludeFromDocs] public static Matrix4x4 DampTowards(Matrix4x4 a, Matrix4x4 b, float lambda, float deltaTime) { float t = GetDampLerpFactor(lambda, deltaTime); return Matrix4x4.identity; } [UnityInternal.ExcludeFromDocs] public static Matrix4x4 LerpUnclamped(Matrix4x4 a, Matrix4x4 b, float t, bool preserveScale) { Vector3 targetScale = Vector3.zero; if (preserveScale) { targetScale = Vector3.LerpUnclamped(a.lossyScale, b.lossyScale, t); } Matrix4x4 result = new Matrix4x4(); result.SetColumn(0, Vector4.LerpUnclamped(a.GetColumn(0), b.GetColumn(0), t)); result.SetColumn(1, Vector4.LerpUnclamped(a.GetColumn(1), b.GetColumn(1), t)); result.SetColumn(2, Vector4.LerpUnclamped(a.GetColumn(2), b.GetColumn(2), t)); result.SetColumn(3, Vector4.LerpUnclamped(a.GetColumn(3), b.GetColumn(3), t)); if (preserveScale) { Vector3 scale = result.lossyScale; result *= Matrix4x4.Scale(new Vector3(targetScale.x / scale.x, targetScale.y / scale.y, targetScale.z / scale.z)); } return result; } [UnityInternal.ExcludeFromDocs] public static void LerpUnclamped(ref Matrix4x4 result, Matrix4x4 b, float t, bool preserveScale) { Vector3 targetScale = Vector3.zero; if (preserveScale) { targetScale = Vector3.LerpUnclamped(result.lossyScale, b.lossyScale, t); } result.SetColumn(0, Vector4.LerpUnclamped(result.GetColumn(0), b.GetColumn(0), t)); result.SetColumn(1, Vector4.LerpUnclamped(result.GetColumn(1), b.GetColumn(1), t)); result.SetColumn(2, Vector4.LerpUnclamped(result.GetColumn(2), b.GetColumn(2), t)); result.SetColumn(3, Vector4.LerpUnclamped(result.GetColumn(3), b.GetColumn(3), t)); if (preserveScale) { Vector3 scale = result.lossyScale; result *= Matrix4x4.Scale(new Vector3(targetScale.x / scale.x, targetScale.y / scale.y, targetScale.z / scale.z)); } } /// /// Lerp between 3 values (a, b, c) using t with range [0..1] /// [UnityInternal.ExcludeFromDocs] public static float Lerp3(float a, float b, float c, float t) { // TODO: optimise this t *= 2.0f; float w1 = 1f - Mathf.Clamp01(t); float w2 = 1f - Mathf.Abs(1f - t); float w3 = Mathf.Clamp01(t-1f); return a * w1 + b * w2 + c * w3; } [UnityInternal.ExcludeFromDocs] public static bool HasMatrixChanged(Matrix4x4 a, Matrix4x4 b, bool ignoreTranslation) { // First check translation if (!ignoreTranslation) { if (a.m03 != b.m03 || a.m13 != b.m13 || a.m23 != b.m23) { return true; } } // Check the rest if (a.m00 != b.m00 || a.m01 != b.m01 || a.m02 != b.m02 || a.m10 != b.m10 || a.m11 != b.m11 || a.m12 != b.m12 || a.m20 != b.m20 || a.m21 != b.m21 || a.m22 != b.m22 || a.m30 != b.m30 || a.m31 != b.m31 || a.m32 != b.m32 || a.m33 != b.m33) { return true; } return false; } [UnityInternal.ExcludeFromDocs] public static void CreateRandomIndices(ref int[] array, int length) { // Only recreate if required to grow if (array == null || array.Length < length) { array = new int[length]; } // Populate for (int i = 0; i < length; i++) { array[i] = i; } // Shuffle for (int i = 0; i < length; i++) { int a = Random.Range(0, length); int b = Random.Range(0, length); if (a != b) { int t = array[a]; array[a] = array[b]; array[b] = t; } } } /// /// Given two rectangles in absolute coordinates, return the rectangle such that if src is remapped to range [0..1] relative to dst /// If src and dst are the same, rect(0, 0, 1, 1) will be returned /// If src is within dst, then rect values will be > 0 and < 1 /// If src is larger than dst, rect values will be < 0 and > 1 /// The returned rect could be used to offset and scale UV coordinates from one quad to another /// public static Rect GetRelativeRect(Rect src, Rect dst) { Rect r = Rect.zero; r.x = (src.x - dst.x) / dst.width; r.y = (src.y - dst.y) / dst.height; r.width = src.width / dst.width; r.height = src.height / dst.height; return r; } /// Move rect horizontally so that specific point along it's width matches the equivelent point along target's width. This is useful for snapping the ege of a rectangle to the edge of another. private static Rect SnapRectToRectHoriz(Rect rect, Rect target, float sizeT) { float posA = Mathf.LerpUnclamped(target.xMin, target.xMax, sizeT); float posB = Mathf.LerpUnclamped(rect.xMin, rect.xMax, sizeT); rect.x += (posA - posB); return rect; } /// Move rect vertically so that specific point along it's height matches the equivelent point along target's height. This is useful for snapping the ege of a rectangle to the edge of another. private static Rect SnapRectToRectVert(Rect rect, Rect target, float sizeT) { float posA = Mathf.LerpUnclamped(target.yMin, target.yMax, sizeT); float posB = Mathf.LerpUnclamped(rect.yMin, rect.yMax, sizeT); rect.y += (posA - posB); return rect; } /// /// Snap one rectangle to the edge of another (or fractional positions between) /// widthT 0 is left, widthT 1 is right /// heightT 0 is bottom, heightT 1 is top /// public static Rect SnapRectToRectEdges(Rect rect, Rect target, bool applyWidth, bool applyHeight, float widthT, float heightT) { if (applyWidth) { rect = SnapRectToRectHoriz(rect, target, widthT); } if (applyHeight) { rect = SnapRectToRectVert(rect, target, heightT); } return rect; } /// Return rectangle of aspect ratio using the scaling mode public static Rect ResizeRectToAspectRatio(Rect rect, ScaleMode scaleMode, float aspect) { float srcAspect = aspect; float dstAspect = rect.width / rect.height; float stretch = 1f; // src is wider than dst if (srcAspect > dstAspect) { stretch = dstAspect / srcAspect; } else { stretch = srcAspect / dstAspect; } Rect result = rect; switch (scaleMode) { case ScaleMode.StretchToFill: break; case ScaleMode.ScaleAndCrop: { // src is wider than dst if (srcAspect > dstAspect) { float newWidth = rect.width / stretch; result = new Rect(rect.xMin - (newWidth - rect.width) * 0.5f, rect.yMin, newWidth, rect.height); } else { float newHeight = rect.height / stretch; result = new Rect(rect.xMin, rect.yMin - (newHeight - rect.height) * 0.5f , rect.width, newHeight); } } break; case ScaleMode.ScaleToFit: { // src is wider than dst if (srcAspect > dstAspect) { result = new Rect(rect.xMin, rect.yMin + rect.height * (1f - stretch) * 0.5f, rect.width, stretch * rect.height); } else { result = new Rect(rect.xMin + rect.width * (1f - stretch) * 0.5f, rect.yMin, stretch * rect.width, rect.height); } } break; } return result; } } }