// ╔════════════════════════════════════════════════════════════════╗ // ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║ // ║ Licensed under Unity Asset Store Terms of Service: ║ // ║ https://unity.com/legal/as-terms ║ // ║ Use permitted only in compliance with the License. ║ // ║ Distributed "AS IS", without warranty of any kind. ║ // ╚════════════════════════════════════════════════════════════════╝ #region using Unity.Collections.LowLevel.Unsafe; using UnityEngine; #endregion namespace NWH.Common.Utility { /// /// Collection of geometric utility functions for 3D math operations. /// Includes vector math, mesh calculations, triangle operations, and spatial queries. /// public static class GeomUtility { /// /// Checks if two Vector3 values are approximately equal within a threshold. /// Uses squared magnitude for performance. /// /// First vector. /// Second vector. /// Maximum squared distance to consider equal. /// True if vectors are within threshold distance. public static bool NearEqual(this Vector3 a, Vector3 b, float threshold = 0.01f) { return Vector3.SqrMagnitude(a - b) < threshold; } /// /// Checks if two Quaternion values are approximately equal. /// /// First quaternion. /// Second quaternion. /// True if angle between quaternions is less than 0.1 degrees. public static bool Equal(this Quaternion a, Quaternion b) { return Mathf.Abs(Quaternion.Angle(a, b)) < 0.1f; } /// /// Clamps the magnitude of a vector between minimum and maximum values. /// /// Vector to clamp. /// Minimum magnitude. /// Maximum magnitude. /// Vector with clamped magnitude. public static Vector3 ClampMagnitude(this Vector3 v, float min, float max) { float mag = v.magnitude; if (mag == 0) { return Vector3.zero; } return Mathf.Clamp(mag, min, max) / mag * v; } /// /// Calculates a perpendicular vector to the given vector. /// /// Input vector. /// Perpendicular vector. public static Vector3 Perpendicular(this Vector3 v) { return new Vector3(CopySign(v.z, v.x), CopySign(v.z, v.y), -CopySign(Mathf.Abs(v.x) + Mathf.Abs(v.y), v.z)); } /// /// Copies the sign from one float to the magnitude of another. /// /// Magnitude value. /// Sign donor value. /// Magnitude with the sign of sgn. public static float CopySign(float mag, float sgn) { ref uint magI = ref UnsafeUtility.As(ref mag); ref uint sgnI = ref UnsafeUtility.As(ref sgn); uint result = (magI & ~(1u << 31)) | (sgnI & (1u << 31)); return UnsafeUtility.As(ref result); } /// /// Returns a vector with only the largest component preserved (rounded to 1 or -1), others set to 0. /// /// Input vector. /// Vector with dominant axis isolated. public static Vector3 RoundedMax(this Vector3 v) { int maxIndex = -1; float maxValue = -Mathf.Infinity; for (int i = 0; i < 3; i++) { float value = Mathf.Abs(v[i]); if (value > maxValue) { maxValue = value; maxIndex = i; } } for (int i = 0; i < 3; i++) { v[i] = i == maxIndex ? Mathf.Sign(v[i]) * 1f : 0f; } return v; } /// /// Finds the nearest point on an infinite line to a given point. /// /// Point on the line. /// Direction of the line. /// Point to find nearest point from. /// Nearest point on the line. public static Vector3 NearestPointOnLine(Vector3 linePnt, Vector3 lineDir, Vector3 pnt) { lineDir.Normalize(); //this needs to be a unit vector Vector3 v = pnt - linePnt; float d = Vector3.Dot(v, lineDir); return linePnt + lineDir * d; } /// /// Calculates the distance between a point and a line segment. /// /// Point to measure from. /// First endpoint of segment. /// Second endpoint of segment. /// Distance to the segment. public static float FindDistanceToSegment(Vector3 pt, Vector3 p1, Vector3 p2) { float dx = p2.x - p1.x; float dy = p2.y - p1.y; if (dx == 0 && dy == 0) { // It's a point not a line segment. dx = pt.x - p1.x; dy = pt.y - p1.y; return Mathf.Sqrt(dx * dx + dy * dy); } // Calculate the t that minimizes the distance. float t = ((pt.x - p1.x) * dx + (pt.y - p1.y) * dy) / (dx * dx + dy * dy); // See if this represents one of the segment's // end points or a point in the middle. if (t < 0) { dx = pt.x - p1.x; dy = pt.y - p1.y; } else if (t > 1) { dx = pt.x - p2.x; dy = pt.y - p2.y; } else { Vector3 closest = new(p1.x + t * dx, p1.y + t * dy); dx = pt.x - closest.x; dy = pt.y - closest.y; } return Mathf.Sqrt(dx * dx + dy * dy); } /// /// Calculates squared distance between two points. Faster than regular distance. /// /// First point. /// Second point. /// Squared distance. public static float SquareDistance(Vector3 a, Vector3 b) { float x = a.x - b.x; float y = a.y - b.y; float z = a.z - b.z; return x * x + y * y + z * z; } /// /// Finds the intersection point between a line and a plane. /// /// Point on the plane. /// Normal vector of the plane. /// Point on the line. /// Direction of the line. /// Intersection point, or Vector3.zero if parallel. public static Vector3 LinePlaneIntersection(Vector3 planePoint, Vector3 planeNormal, Vector3 linePoint, Vector3 lineDirection) { if (Vector3.Dot(planeNormal, lineDirection.normalized) == 0) { return Vector3.zero; } float t = (Vector3.Dot(planeNormal, planePoint) - Vector3.Dot(planeNormal, linePoint)) / Vector3.Dot(planeNormal, lineDirection.normalized); return linePoint + lineDirection.normalized * t; } /// /// Finds a point along the chord line of a quad at the specified percentage. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner. /// Position along chord (0-1). /// Point on chord line. public static Vector3 FindChordLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float chordPercent) { return QuadLerp(a, b, c, d, 0.5f, chordPercent); } /// /// Finds a point along the span line of a quad at the specified percentage. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner. /// Position along span (0-1). /// Point on span line. public static Vector3 FindSpanLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float spanPercent) { return QuadLerp(a, b, c, d, spanPercent, 0.5f); } /// /// Calculates the area of a quadrilateral defined by four points. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner. /// Area of the quad. public static float FindArea(Vector3 A, Vector3 B, Vector3 C, Vector3 D) { return TriArea(A, B, D) + TriArea(B, C, D); } /// /// Finds the center point of a quad or triangle defined by 3 or 4 points. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner (can equal first corner for triangle). /// Center point. public static Vector3 FindCenter(Vector3 a, Vector3 b, Vector3 c, Vector3 d) { if (a == d) { return (a + b + c) / 4f; } return (a + b + c + d) / 4f; } /// /// Calculates the distance between two points projected along a normal vector. /// /// First point. /// Second point. /// Normal vector to project along. /// Distance along normal. public static float DistanceAlongNormal(Vector3 a, Vector3 b, Vector3 normal) { Vector3 dir = b - a; return Vector3.Project(dir, normal).magnitude; } /// /// Checks if a point lies inside a triangle. /// /// First triangle vertex. /// Second triangle vertex. /// Third triangle vertex. /// Point to test. /// Tolerance for point-on-plane test. /// True if point is inside triangle. public static bool PointInTriangle(Vector3 A, Vector3 B, Vector3 C, Vector3 P, float dotThreshold = 0.001f) { if (SameSide(P, A, B, C) && SameSide(P, B, A, C) && SameSide(P, C, A, B)) { Vector3 vc1 = Vector3.Cross(B - A, C - A).normalized; if (Mathf.Abs(Vector3.Dot(P - A, vc1)) <= dotThreshold) { return true; } } return false; } private static bool SameSide(Vector3 p1, Vector3 p2, Vector3 A, Vector3 B) { Vector3 cp1 = Vector3.Cross(B - A, p1 - A).normalized; Vector3 cp2 = Vector3.Cross(B - A, p2 - A).normalized; if (Vector3.Dot(cp1, cp2) > 0) { return true; } return false; } /// /// Checks if a 2D point is inside the screen rectangle. /// /// Point to check. /// True if point is inside screen bounds. public static bool PointIsInsideRect(Vector2 point) { return new Rect(0, 0, Screen.width, Screen.height).Contains(point); } /// /// Checks if two float values are nearly equal within an epsilon threshold. /// /// First value. /// Second value. /// Maximum difference to consider equal. /// True if values are within epsilon. public static bool NearlyEqual(this float a, float b, double epsilon) { return Mathf.Abs(a - b) < epsilon; } /// /// Calculates the area of a triangle from three points. /// /// First point. /// Second point. /// Third point. /// Area of the triangle. public static float AreaFromThreePoints(Vector3 p1, Vector3 p2, Vector3 p3) { Vector3 u, v; u.x = p2.x - p1.x; u.y = p2.y - p1.y; u.z = p2.z - p1.z; v.x = p3.x - p1.x; v.y = p3.y - p1.y; v.z = p3.z - p1.z; Vector3 crossUV = Vector3.Cross(u, v); return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f; } /// /// Calculates the area of a quadrilateral from four points. /// /// First point. /// Second point. /// Third point. /// Fourth point. /// Area of the quadrilateral. public static float AreaFromFourPoints(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) { return AreaFromThreePoints(p1, p2, p4) + AreaFromThreePoints(p2, p3, p4); } /// /// Calculates area of a single triangle from it's three points. /// public static float TriArea(Vector3 p1, Vector3 p2, Vector3 p3) { Vector3 u, v, crossUV; u.x = p2.x - p1.x; u.y = p2.y - p1.y; u.z = p2.z - p1.z; v.x = p3.x - p1.x; v.y = p3.y - p1.y; v.z = p3.z - p1.z; crossUV = Vector3.Cross(u, v); return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f; } /// /// Calculates area of a complete mesh. /// public static float MeshArea(Mesh mesh) { if (mesh.vertices.Length == 0) { return 0; } float area = 0; Vector3[] verts = mesh.vertices; int[] tris = mesh.triangles; for (int i = 0; i < tris.Length; i += 3) { area += TriArea(verts[tris[i]], verts[tris[i + 1]], verts[tris[i + 2]]); } return area; } /// /// Calculates area of a mesh as viewed from the direction vector. /// public static float ProjectedMeshArea(Mesh mesh, Vector3 direction) { float area = 0; Vector3[] verts = mesh.vertices; int[] tris = mesh.triangles; Vector3[] normals = mesh.normals; int count = 0; for (int i = 0; i < tris.Length; i += 3) { area += TriArea(verts[tris[i]], verts[tris[i + 1]], verts[tris[i + 2]], direction); count++; } return area; } /// /// Calculates the area of a rectangle from four corner points. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner. /// Area of the rectangle. public static float RectArea(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) { return TriArea(p1, p2, p4) + TriArea(p2, p3, p4); } /// /// Find mesh center by averaging. Returns local center. /// public static Vector3 FindMeshCenter(Mesh mesh) { if (mesh.vertices.Length == 0) { return Vector3.zero; } Vector3 sum = Vector3.zero; int count = 0; if (mesh != null) { foreach (Vector3 vert in mesh.vertices) { sum += vert; count++; } } return sum / count; } public static float TriArea(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 view) { Vector3 u, v, crossUV, normal; float crossMagnitude; u.x = p2.x - p1.x; u.y = p2.y - p1.y; u.z = p2.z - p1.z; v.x = p3.x - p1.x; v.y = p3.y - p1.y; v.z = p3.z - p1.z; crossUV = Vector3.Cross(u, v); crossMagnitude = Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z); // Normal if (crossMagnitude == 0) { normal.x = normal.y = normal.z = 0f; } else { normal.x = crossUV.x / crossMagnitude; normal.y = crossUV.y / crossMagnitude; normal.z = crossUV.z / crossMagnitude; } float angle = Vector3.Angle(normal, view); float cos = Mathf.Cos(angle); if (cos < 0) { return 0; } return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f * cos; } /// /// Calculates the signed volume contribution of a triangle relative to the origin. /// /// First vertex. /// Second vertex. /// Third vertex. /// Signed volume. public static float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3) { float v321 = p3.x * p2.y * p1.z; float v231 = p2.x * p3.y * p1.z; float v312 = p3.x * p1.y * p2.z; float v132 = p1.x * p3.y * p2.z; float v213 = p2.x * p1.y * p3.z; float v123 = p1.x * p2.y * p3.z; return 1.0f / 6.0f * (-v321 + v231 + v312 - v132 - v213 + v123); } /// /// Calculates the volume enclosed by a mesh. /// /// Mesh to calculate volume for. /// Volume of the mesh. public static float VolumeOfMesh(Mesh mesh) { float volume = 0; Vector3[] vertices = mesh.vertices; int[] triangles = mesh.triangles; for (int i = 0; i < mesh.triangles.Length; i += 3) { Vector3 p1 = vertices[triangles[i + 0]]; Vector3 p2 = vertices[triangles[i + 1]]; Vector3 p3 = vertices[triangles[i + 2]]; volume += SignedVolumeOfTriangle(p1, p2, p3); } return Mathf.Abs(volume); } /// /// Transforms a point from local to world space without applying scale. /// /// Transform to use. /// Local position. /// World position without scale. public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position) { return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).MultiplyPoint3x4(position); } /// /// Transforms a point from world to local space without applying scale. /// /// Transform to use. /// World position. /// Local position without scale. public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position) { return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse .MultiplyPoint3x4(position); } /// /// Changes the layer of a transform and all its children recursively. /// /// Root transform. /// Layer name. public static void ChangeLayersRecursively(this Transform trans, string name) { trans.gameObject.layer = LayerMask.NameToLayer(name); foreach (Transform child in trans) { child.ChangeLayersRecursively(name); } } /// /// Changes the color of a GameObject's material. /// /// GameObject to modify. /// New color. public static void ChangeObjectColor(GameObject gameObject, Color color) { gameObject.GetComponent().material.SetColor("_Color", color); } /// /// Changes the alpha value of a GameObject's material color. /// /// GameObject to modify. /// New alpha value (0-1). public static void ChangeObjectAlpha(GameObject gameObject, float alpha) { MeshRenderer mr = gameObject.GetComponent(); Color currentColor = mr.material.GetColor("_Color"); currentColor.a = alpha; mr.material.SetColor("_Color", currentColor); } /// /// Returns a vector with absolute values of all components. /// /// Input vector. /// Vector with absolute values. public static Vector3 Vector3Abs(Vector3 v) { return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z)); } /// /// Rounds all components of a vector to nearest integer. /// /// Input vector. /// Rounded vector. public static Vector3 Vector3RoundToInt(Vector3 v) { return new Vector3(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y), Mathf.RoundToInt(v.z)); } /// /// Returns a vector with reciprocal values (1/x, 1/y, 1/z). /// /// Input vector. /// Vector with reciprocal values. public static Vector3 Vector3OneOver(Vector3 v) { return new Vector3(1f / v.x, 1f / v.y, 1f / v.z); } /// /// Rounds a value to the nearest multiple of step. /// /// Value to round. /// Step size. /// Rounded value. public static float RoundToStep(float value, float step) { return Mathf.Round(value / step) * step; } /// /// Rounds a value to the nearest multiple of step. /// /// Value to round. /// Step size. /// Rounded value. public static float RoundToStep(int value, int step) { return Mathf.RoundToInt(Mathf.Round(value / step) * step); } /// /// Rotates a point around a pivot by the specified angles. /// /// Point to rotate. /// Pivot point. /// Euler angles for rotation. /// Rotated point. public static Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angles) { return Quaternion.Euler(angles) * (point - pivot) + pivot; } /// /// Performs bilinear interpolation on a quad defined by four points. /// /// First corner. /// Second corner. /// Third corner. /// Fourth corner. /// U parameter (0-1). /// V parameter (0-1). /// Interpolated point. public static Vector3 QuadLerp(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float u, float v) { Vector3 abu = Vector3Lerp(a, b, u); Vector3 dcu = Vector3Lerp(d, c, u); return Vector3Lerp(abu, dcu, v); } /// /// Linear interpolation between two vectors with value clamping. /// /// Start vector. /// End vector. /// Interpolation value (0-1). /// Interpolated vector. public static Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float value) { if (value > 1.0f) { return v2; } if (value < 0.0f) { return v1; } return new Vector3(v1.x + (v2.x - v1.x) * value, v1.y + (v2.y - v1.y) * value, v1.z + (v2.z - v1.z) * value); } /// /// Calculates the magnitude of a quaternion. /// /// Quaternion. /// Magnitude. public static float QuaternionMagnitude(Quaternion q) { return Mathf.Sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); } } }