新增动态水物理插件

This commit is contained in:
Bob.Song
2026-02-27 17:44:21 +08:00
parent a6e061d9ce
commit 60744d113d
2218 changed files with 698551 additions and 189 deletions

View File

@@ -0,0 +1,106 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 UnityEngine;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Extension methods for AnimationCurve manipulation and processing.
/// </summary>
public static class AnimationCurveExtensions
{
/// <summary>
/// Smooths out a scripting-generated AnimationCurve by calculating appropriate tangents.
/// Creates smooth transitions between keyframes.
/// </summary>
/// <param name="inCurve">The curve to smooth.</param>
/// <returns>A new smoothed AnimationCurve.</returns>
public static AnimationCurve MakeSmooth(this AnimationCurve inCurve)
{
AnimationCurve outCurve = new();
for (int i = 0; i < inCurve.keys.Length; i++)
{
float inTangent = 0;
float outTangent = 0;
bool intangentSet = false;
bool outtangentSet = false;
Vector2 point1;
Vector2 point2;
Vector2 deltapoint;
Keyframe key = inCurve[i];
if (i == 0)
{
inTangent = 0;
intangentSet = true;
}
if (i == inCurve.keys.Length - 1)
{
outTangent = 0;
outtangentSet = true;
}
if (!intangentSet)
{
point1.x = inCurve.keys[i - 1].time;
point1.y = inCurve.keys[i - 1].value;
point2.x = inCurve.keys[i].time;
point2.y = inCurve.keys[i].value;
deltapoint = point2 - point1;
inTangent = deltapoint.y / deltapoint.x;
}
if (!outtangentSet)
{
point1.x = inCurve.keys[i].time;
point1.y = inCurve.keys[i].value;
point2.x = inCurve.keys[i + 1].time;
point2.y = inCurve.keys[i + 1].value;
deltapoint = point2 - point1;
outTangent = deltapoint.y / deltapoint.x;
}
key.inTangent = inTangent;
key.outTangent = outTangent;
outCurve.AddKey(key);
}
return outCurve;
}
/// <summary>
/// Samples an AnimationCurve at regular intervals and returns the values as an array.
/// Useful for pre-calculating curve values for performance-critical code.
/// </summary>
/// <param name="self">The curve to sample.</param>
/// <param name="resolution">Number of samples to take. Higher values provide more precision.</param>
/// <returns>Array of sampled values from 0 to 1.</returns>
public static float[] GenerateCurveArray(this AnimationCurve self, int resolution = 256)
{
float[] returnArray = new float[resolution];
for (int j = 0; j < resolution; j++)
{
returnArray[j] = self.Evaluate(j / (float)resolution);
}
return returnArray;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34b51e6bc0d3493cbab61632bb388338
timeCreated: 1607279175

View File

@@ -0,0 +1,54 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 System;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Extension methods for array manipulation.
/// </summary>
public static class ArrayExtensions
{
/// <summary>
/// Efficiently fills an array by repeating a pattern of values.
/// Uses doubling strategy for performance.
/// </summary>
/// <typeparam name="T">Type of array elements.</typeparam>
/// <param name="destinationArray">Array to fill.</param>
/// <param name="value">Pattern of values to repeat throughout the array.</param>
public static void Fill<T>(this T[] destinationArray, params T[] value)
{
int destinationLength = destinationArray.Length;
if (destinationLength == 0)
{
return;
}
int valueLength = value.Length;
// set the initial array value
Array.Copy(value, destinationArray, valueLength);
int arrayToFillHalfLength = destinationLength / 2;
int copyLength;
for (copyLength = valueLength; copyLength < arrayToFillHalfLength; copyLength <<= 1)
{
Array.Copy(destinationArray, 0, destinationArray, copyLength, copyLength);
}
Array.Copy(destinationArray, 0, destinationArray, copyLength,
destinationLength - copyLength);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4845cbe074fb994d8b8e2fd57cf6009
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 UnityEngine;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Extension methods for GameObject and Transform operations.
/// </summary>
public static class GameObjectExtensions
{
/// <summary>
/// Calculates the combined bounds of all MeshRenderers in a GameObject and its children.
/// </summary>
/// <param name="gameObject">GameObject to calculate bounds for.</param>
/// <returns>Combined bounds encapsulating all child renderers.</returns>
public static Bounds FindBoundsIncludeChildren(this GameObject gameObject)
{
Bounds bounds = new();
foreach (MeshRenderer mr in gameObject.GetComponentsInChildren<MeshRenderer>())
{
bounds.Encapsulate(mr.bounds);
}
return bounds;
}
/// <summary>
/// Searches for a component in parent GameObjects, with option to include inactive objects.
/// More flexible than Unity's built-in GetComponentInParent.
/// </summary>
/// <typeparam name="T">Type of component to find.</typeparam>
/// <param name="transform">Starting transform.</param>
/// <param name="includeInactive">Include inactive GameObjects in search.</param>
/// <returns>First component of type T found in parents, or null if none found.</returns>
public static T GetComponentInParent<T>(this Transform transform, bool includeInactive = true)
where T : Component
{
Transform here = transform;
T result = null;
while (here && !result)
{
if (includeInactive || here.gameObject.activeSelf)
{
result = here.GetComponent<T>();
}
here = here.parent;
}
return result;
}
/// <summary>
/// Searches for a component in parents first, then children if not found.
/// Combines functionality of GetComponentInParent and GetComponentInChildren.
/// </summary>
/// <typeparam name="T">Type of component to find.</typeparam>
/// <param name="transform">Starting transform.</param>
/// <param name="includeInactive">Include inactive GameObjects in search.</param>
/// <returns>First component of type T found, or null if none found.</returns>
public static T GetComponentInParentsOrChildren<T>(this Transform transform, bool includeInactive = true)
where T : Component
{
T result = transform.GetComponentInParent<T>(includeInactive);
if (result == null)
{
result = transform.GetComponentInChildren<T>(includeInactive);
}
return result;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f2992a3b4066f44cb0e705d0b20db1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,771 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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
{
/// <summary>
/// Collection of geometric utility functions for 3D math operations.
/// Includes vector math, mesh calculations, triangle operations, and spatial queries.
/// </summary>
public static class GeomUtility
{
/// <summary>
/// Checks if two Vector3 values are approximately equal within a threshold.
/// Uses squared magnitude for performance.
/// </summary>
/// <param name="a">First vector.</param>
/// <param name="b">Second vector.</param>
/// <param name="threshold">Maximum squared distance to consider equal.</param>
/// <returns>True if vectors are within threshold distance.</returns>
public static bool NearEqual(this Vector3 a, Vector3 b, float threshold = 0.01f)
{
return Vector3.SqrMagnitude(a - b) < threshold;
}
/// <summary>
/// Checks if two Quaternion values are approximately equal.
/// </summary>
/// <param name="a">First quaternion.</param>
/// <param name="b">Second quaternion.</param>
/// <returns>True if angle between quaternions is less than 0.1 degrees.</returns>
public static bool Equal(this Quaternion a, Quaternion b)
{
return Mathf.Abs(Quaternion.Angle(a, b)) < 0.1f;
}
/// <summary>
/// Clamps the magnitude of a vector between minimum and maximum values.
/// </summary>
/// <param name="v">Vector to clamp.</param>
/// <param name="min">Minimum magnitude.</param>
/// <param name="max">Maximum magnitude.</param>
/// <returns>Vector with clamped magnitude.</returns>
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;
}
/// <summary>
/// Calculates a perpendicular vector to the given vector.
/// </summary>
/// <param name="v">Input vector.</param>
/// <returns>Perpendicular vector.</returns>
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));
}
/// <summary>
/// Copies the sign from one float to the magnitude of another.
/// </summary>
/// <param name="mag">Magnitude value.</param>
/// <param name="sgn">Sign donor value.</param>
/// <returns>Magnitude with the sign of sgn.</returns>
public static float CopySign(float mag, float sgn)
{
ref uint magI = ref UnsafeUtility.As<float, uint>(ref mag);
ref uint sgnI = ref UnsafeUtility.As<float, uint>(ref sgn);
uint result = (magI & ~(1u << 31)) | (sgnI & (1u << 31));
return UnsafeUtility.As<uint, float>(ref result);
}
/// <summary>
/// Returns a vector with only the largest component preserved (rounded to 1 or -1), others set to 0.
/// </summary>
/// <param name="v">Input vector.</param>
/// <returns>Vector with dominant axis isolated.</returns>
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;
}
/// <summary>
/// Finds the nearest point on an infinite line to a given point.
/// </summary>
/// <param name="linePnt">Point on the line.</param>
/// <param name="lineDir">Direction of the line.</param>
/// <param name="pnt">Point to find nearest point from.</param>
/// <returns>Nearest point on the line.</returns>
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;
}
/// <summary>
/// Calculates the distance between a point and a line segment.
/// </summary>
/// <param name="pt">Point to measure from.</param>
/// <param name="p1">First endpoint of segment.</param>
/// <param name="p2">Second endpoint of segment.</param>
/// <returns>Distance to the segment.</returns>
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);
}
/// <summary>
/// Calculates squared distance between two points. Faster than regular distance.
/// </summary>
/// <param name="a">First point.</param>
/// <param name="b">Second point.</param>
/// <returns>Squared distance.</returns>
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;
}
/// <summary>
/// Finds the intersection point between a line and a plane.
/// </summary>
/// <param name="planePoint">Point on the plane.</param>
/// <param name="planeNormal">Normal vector of the plane.</param>
/// <param name="linePoint">Point on the line.</param>
/// <param name="lineDirection">Direction of the line.</param>
/// <returns>Intersection point, or Vector3.zero if parallel.</returns>
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;
}
/// <summary>
/// Finds a point along the chord line of a quad at the specified percentage.
/// </summary>
/// <param name="a">First corner.</param>
/// <param name="b">Second corner.</param>
/// <param name="c">Third corner.</param>
/// <param name="d">Fourth corner.</param>
/// <param name="chordPercent">Position along chord (0-1).</param>
/// <returns>Point on chord line.</returns>
public static Vector3 FindChordLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float chordPercent)
{
return QuadLerp(a, b, c, d, 0.5f, chordPercent);
}
/// <summary>
/// Finds a point along the span line of a quad at the specified percentage.
/// </summary>
/// <param name="a">First corner.</param>
/// <param name="b">Second corner.</param>
/// <param name="c">Third corner.</param>
/// <param name="d">Fourth corner.</param>
/// <param name="spanPercent">Position along span (0-1).</param>
/// <returns>Point on span line.</returns>
public static Vector3 FindSpanLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float spanPercent)
{
return QuadLerp(a, b, c, d, spanPercent, 0.5f);
}
/// <summary>
/// Calculates the area of a quadrilateral defined by four points.
/// </summary>
/// <param name="A">First corner.</param>
/// <param name="B">Second corner.</param>
/// <param name="C">Third corner.</param>
/// <param name="D">Fourth corner.</param>
/// <returns>Area of the quad.</returns>
public static float FindArea(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
return TriArea(A, B, D) + TriArea(B, C, D);
}
/// <summary>
/// Finds the center point of a quad or triangle defined by 3 or 4 points.
/// </summary>
/// <param name="a">First corner.</param>
/// <param name="b">Second corner.</param>
/// <param name="c">Third corner.</param>
/// <param name="d">Fourth corner (can equal first corner for triangle).</param>
/// <returns>Center point.</returns>
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;
}
/// <summary>
/// Calculates the distance between two points projected along a normal vector.
/// </summary>
/// <param name="a">First point.</param>
/// <param name="b">Second point.</param>
/// <param name="normal">Normal vector to project along.</param>
/// <returns>Distance along normal.</returns>
public static float DistanceAlongNormal(Vector3 a, Vector3 b, Vector3 normal)
{
Vector3 dir = b - a;
return Vector3.Project(dir, normal).magnitude;
}
/// <summary>
/// Checks if a point lies inside a triangle.
/// </summary>
/// <param name="A">First triangle vertex.</param>
/// <param name="B">Second triangle vertex.</param>
/// <param name="C">Third triangle vertex.</param>
/// <param name="P">Point to test.</param>
/// <param name="dotThreshold">Tolerance for point-on-plane test.</param>
/// <returns>True if point is inside triangle.</returns>
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;
}
/// <summary>
/// Checks if a 2D point is inside the screen rectangle.
/// </summary>
/// <param name="point">Point to check.</param>
/// <returns>True if point is inside screen bounds.</returns>
public static bool PointIsInsideRect(Vector2 point)
{
return new Rect(0, 0, Screen.width, Screen.height).Contains(point);
}
/// <summary>
/// Checks if two float values are nearly equal within an epsilon threshold.
/// </summary>
/// <param name="a">First value.</param>
/// <param name="b">Second value.</param>
/// <param name="epsilon">Maximum difference to consider equal.</param>
/// <returns>True if values are within epsilon.</returns>
public static bool NearlyEqual(this float a, float b, double epsilon)
{
return Mathf.Abs(a - b) < epsilon;
}
/// <summary>
/// Calculates the area of a triangle from three points.
/// </summary>
/// <param name="p1">First point.</param>
/// <param name="p2">Second point.</param>
/// <param name="p3">Third point.</param>
/// <returns>Area of the triangle.</returns>
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;
}
/// <summary>
/// Calculates the area of a quadrilateral from four points.
/// </summary>
/// <param name="p1">First point.</param>
/// <param name="p2">Second point.</param>
/// <param name="p3">Third point.</param>
/// <param name="p4">Fourth point.</param>
/// <returns>Area of the quadrilateral.</returns>
public static float AreaFromFourPoints(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
{
return AreaFromThreePoints(p1, p2, p4) + AreaFromThreePoints(p2, p3, p4);
}
/// <summary>
/// Calculates area of a single triangle from it's three points.
/// </summary>
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;
}
/// <summary>
/// Calculates area of a complete mesh.
/// </summary>
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;
}
/// <summary>
/// Calculates area of a mesh as viewed from the direction vector.
/// </summary>
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;
}
/// <summary>
/// Calculates the area of a rectangle from four corner points.
/// </summary>
/// <param name="p1">First corner.</param>
/// <param name="p2">Second corner.</param>
/// <param name="p3">Third corner.</param>
/// <param name="p4">Fourth corner.</param>
/// <returns>Area of the rectangle.</returns>
public static float RectArea(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
{
return TriArea(p1, p2, p4) + TriArea(p2, p3, p4);
}
/// <summary>
/// Find mesh center by averaging. Returns local center.
/// </summary>
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;
}
/// <summary>
/// Calculates the signed volume contribution of a triangle relative to the origin.
/// </summary>
/// <param name="p1">First vertex.</param>
/// <param name="p2">Second vertex.</param>
/// <param name="p3">Third vertex.</param>
/// <returns>Signed volume.</returns>
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);
}
/// <summary>
/// Calculates the volume enclosed by a mesh.
/// </summary>
/// <param name="mesh">Mesh to calculate volume for.</param>
/// <returns>Volume of the mesh.</returns>
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);
}
/// <summary>
/// Transforms a point from local to world space without applying scale.
/// </summary>
/// <param name="transform">Transform to use.</param>
/// <param name="position">Local position.</param>
/// <returns>World position without scale.</returns>
public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position)
{
return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).MultiplyPoint3x4(position);
}
/// <summary>
/// Transforms a point from world to local space without applying scale.
/// </summary>
/// <param name="transform">Transform to use.</param>
/// <param name="position">World position.</param>
/// <returns>Local position without scale.</returns>
public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position)
{
return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse
.MultiplyPoint3x4(position);
}
/// <summary>
/// Changes the layer of a transform and all its children recursively.
/// </summary>
/// <param name="trans">Root transform.</param>
/// <param name="name">Layer name.</param>
public static void ChangeLayersRecursively(this Transform trans, string name)
{
trans.gameObject.layer = LayerMask.NameToLayer(name);
foreach (Transform child in trans)
{
child.ChangeLayersRecursively(name);
}
}
/// <summary>
/// Changes the color of a GameObject's material.
/// </summary>
/// <param name="gameObject">GameObject to modify.</param>
/// <param name="color">New color.</param>
public static void ChangeObjectColor(GameObject gameObject, Color color)
{
gameObject.GetComponent<MeshRenderer>().material.SetColor("_Color", color);
}
/// <summary>
/// Changes the alpha value of a GameObject's material color.
/// </summary>
/// <param name="gameObject">GameObject to modify.</param>
/// <param name="alpha">New alpha value (0-1).</param>
public static void ChangeObjectAlpha(GameObject gameObject, float alpha)
{
MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
Color currentColor = mr.material.GetColor("_Color");
currentColor.a = alpha;
mr.material.SetColor("_Color", currentColor);
}
/// <summary>
/// Returns a vector with absolute values of all components.
/// </summary>
/// <param name="v">Input vector.</param>
/// <returns>Vector with absolute values.</returns>
public static Vector3 Vector3Abs(Vector3 v)
{
return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
}
/// <summary>
/// Rounds all components of a vector to nearest integer.
/// </summary>
/// <param name="v">Input vector.</param>
/// <returns>Rounded vector.</returns>
public static Vector3 Vector3RoundToInt(Vector3 v)
{
return new Vector3(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y), Mathf.RoundToInt(v.z));
}
/// <summary>
/// Returns a vector with reciprocal values (1/x, 1/y, 1/z).
/// </summary>
/// <param name="v">Input vector.</param>
/// <returns>Vector with reciprocal values.</returns>
public static Vector3 Vector3OneOver(Vector3 v)
{
return new Vector3(1f / v.x, 1f / v.y, 1f / v.z);
}
/// <summary>
/// Rounds a value to the nearest multiple of step.
/// </summary>
/// <param name="value">Value to round.</param>
/// <param name="step">Step size.</param>
/// <returns>Rounded value.</returns>
public static float RoundToStep(float value, float step)
{
return Mathf.Round(value / step) * step;
}
/// <summary>
/// Rounds a value to the nearest multiple of step.
/// </summary>
/// <param name="value">Value to round.</param>
/// <param name="step">Step size.</param>
/// <returns>Rounded value.</returns>
public static float RoundToStep(int value, int step)
{
return Mathf.RoundToInt(Mathf.Round(value / step) * step);
}
/// <summary>
/// Rotates a point around a pivot by the specified angles.
/// </summary>
/// <param name="point">Point to rotate.</param>
/// <param name="pivot">Pivot point.</param>
/// <param name="angles">Euler angles for rotation.</param>
/// <returns>Rotated point.</returns>
public static Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angles)
{
return Quaternion.Euler(angles) * (point - pivot) + pivot;
}
/// <summary>
/// Performs bilinear interpolation on a quad defined by four points.
/// </summary>
/// <param name="a">First corner.</param>
/// <param name="b">Second corner.</param>
/// <param name="c">Third corner.</param>
/// <param name="d">Fourth corner.</param>
/// <param name="u">U parameter (0-1).</param>
/// <param name="v">V parameter (0-1).</param>
/// <returns>Interpolated point.</returns>
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);
}
/// <summary>
/// Linear interpolation between two vectors with value clamping.
/// </summary>
/// <param name="v1">Start vector.</param>
/// <param name="v2">End vector.</param>
/// <param name="value">Interpolation value (0-1).</param>
/// <returns>Interpolated vector.</returns>
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);
}
/// <summary>
/// Calculates the magnitude of a quaternion.
/// </summary>
/// <param name="q">Quaternion.</param>
/// <returns>Magnitude.</returns>
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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: beed53cc984d46af9792ca3197ced971
timeCreated: 1614097334

View File

@@ -0,0 +1,41 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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. ║
// ╚════════════════════════════════════════════════════════════════╝
namespace NWH.Common
{
/// <summary>
/// Mathematical utility functions for common calculations.
/// </summary>
public static class MathUtility
{
/// <summary>
/// Clamps a value to a range and outputs how much it exceeded the range.
/// Useful for clamping values while preserving overflow information.
/// </summary>
/// <param name="x">Value to clamp (will be modified).</param>
/// <param name="range">Range limit (value will be clamped to [-range, +range]).</param>
/// <param name="remainder">Amount by which x exceeded the range (output).</param>
public static void ClampWithRemainder(ref float x, in float range, out float remainder)
{
if (x > range)
{
remainder = x - range;
x = range;
}
else if (x < -range)
{
remainder = x + range;
x = -range;
}
else
{
remainder = 0;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 877cec20fa9639c46973b34a944dd302
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,156 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 UnityEngine;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Proportional-Integral-Derivative controller for smooth value regulation.
/// Used for automated control systems like cruise control, stability systems, and steering assistance.
/// </summary>
/// <remarks>
/// PID controllers combine three control strategies:
/// - Proportional: Reacts to current error
/// - Integral: Eliminates accumulated error over time
/// - Derivative: Anticipates future error based on rate of change
/// Tune the three gain values to achieve desired response characteristics.
/// </remarks>
public class PIDController
{
/// <summary>
/// Maximum output value. Output will be clamped to this value.
/// </summary>
public float maxValue;
/// <summary>
/// Minimum output value. Output will be clamped to this value.
/// </summary>
public float minValue;
private float _processVariable;
/// <summary>
/// Creates a new PID controller with specified gains and output limits.
/// </summary>
/// <param name="gainProportional">Proportional gain (Kp). Higher values increase response to current error.</param>
/// <param name="gainIntegral">Integral gain (Ki). Higher values eliminate steady-state error faster.</param>
/// <param name="gainDerivative">Derivative gain (Kd). Higher values dampen oscillations.</param>
/// <param name="outputMin">Minimum output value.</param>
/// <param name="outputMax">Maximum output value.</param>
public PIDController(float gainProportional, float gainIntegral, float gainDerivative, float outputMin,
float outputMax)
{
GainDerivative = gainDerivative;
GainIntegral = gainIntegral;
GainProportional = gainProportional;
maxValue = outputMax;
minValue = outputMin;
}
/// <summary>
/// The derivative term is proportional to the rate of
/// change of the error
/// </summary>
public float GainDerivative { get; set; }
/// <summary>
/// The integral term is proportional to both the magnitude
/// of the error and the duration of the error
/// </summary>
public float GainIntegral { get; set; }
/// <summary>
/// The proportional term produces an output value that
/// is proportional to the current error value
/// </summary>
/// <remarks>
/// Tuning theory and industrial practice indicate that the
/// proportional term should contribute the bulk of the output change.
/// </remarks>
public float GainProportional { get; set; }
/// <summary>
/// Adjustment made by considering the accumulated error over time
/// </summary>
/// <remarks>
/// An alternative formulation of the integral action, is the
/// proportional-summation-difference used in discrete-time systems
/// </remarks>
public float IntegralTerm { get; private set; }
/// <summary>
/// The current value
/// </summary>
public float ProcessVariable
{
get { return _processVariable; }
set
{
ProcessVariableLast = _processVariable;
_processVariable = value;
}
}
/// <summary>
/// The last reported value (used to calculate the rate of change)
/// </summary>
public float ProcessVariableLast { get; private set; }
/// <summary>
/// The desired value
/// </summary>
public float SetPoint { get; set; } = 0;
/// <summary>
/// The controller output
/// </summary>
/// <param name="timeSinceLastUpdate">
/// timespan of the elapsed time
/// since the previous time that ControlVariable was called
/// </param>
/// <returns> Value of the variable that needs to be controlled </returns>
public float ControlVariable(float timeSinceLastUpdate)
{
// Guard against zero or very small deltaTime to prevent NaN/Infinity
// This can happen during game pause or time manipulation
const float EPSILON = 0.0001f;
if (timeSinceLastUpdate < EPSILON)
{
// Return proportional-only control when time is too small
return Mathf.Clamp(GainProportional * (SetPoint - ProcessVariable), minValue, maxValue);
}
float error = SetPoint - ProcessVariable;
// integral term calculation
IntegralTerm += GainIntegral * error * timeSinceLastUpdate;
IntegralTerm = Mathf.Clamp(IntegralTerm, minValue, maxValue);
// derivative term calculation
float dInput = _processVariable - ProcessVariableLast;
float derivativeTerm = GainDerivative * (dInput / timeSinceLastUpdate);
// proportional term calculation
float proportionalTerm = GainProportional * error;
float output = proportionalTerm + IntegralTerm - derivativeTerm;
output = Mathf.Clamp(output, minValue, maxValue);
return output;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51dfb92a565312b48af1d19f4827cc24
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,103 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 UnityEngine;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Extension methods for advanced Quaternion operations.
/// Provides interpolation methods with control over rotation direction.
/// </summary>
public static class QuaternionExtensions
{
/// <summary>
/// Linear interpolation between two quaternions with optional short/long path control.
/// Unlike Unity's Quaternion.Lerp, this allows choosing rotation direction.
/// </summary>
/// <param name="p">Starting rotation.</param>
/// <param name="q">Target rotation.</param>
/// <param name="t">Interpolation factor (0 to 1).</param>
/// <param name="shortWay">True for shortest rotation path, false for longest.</param>
/// <returns>Interpolated quaternion.</returns>
public static Quaternion Lerp(Quaternion p, Quaternion q, float t, bool shortWay)
{
if (shortWay)
{
float dot = Quaternion.Dot(p, q);
if (dot < 0.0f)
{
return Lerp(ScalarMultiply(p, -1.0f), q, t, true);
}
}
Quaternion r = Quaternion.identity;
r.x = p.x * (1f - t) + q.x * t;
r.y = p.y * (1f - t) + q.y * t;
r.z = p.z * (1f - t) + q.z * t;
r.w = p.w * (1f - t) + q.w * t;
return r;
}
/// <summary>
/// Spherical linear interpolation between two quaternions with optional short/long path control.
/// Provides smooth rotation interpolation with control over rotation direction.
/// </summary>
/// <param name="p">Starting rotation.</param>
/// <param name="q">Target rotation.</param>
/// <param name="t">Interpolation factor (0 to 1).</param>
/// <param name="shortWay">True for shortest rotation path, false for longest.</param>
/// <returns>Interpolated quaternion.</returns>
public static Quaternion Slerp(Quaternion p, Quaternion q, float t, bool shortWay)
{
float dot = Quaternion.Dot(p, q);
if (shortWay)
{
if (dot < 0.0f)
{
return Slerp(ScalarMultiply(p, -1.0f), q, t, true);
}
}
float angle = Mathf.Acos(dot);
Quaternion first = ScalarMultiply(p, Mathf.Sin((1f - t) * angle));
Quaternion second = ScalarMultiply(q, Mathf.Sin(t * angle));
float division = 1f / Mathf.Sin(angle);
return ScalarMultiply(Add(first, second), division);
}
/// <summary>
/// Multiplies all components of a quaternion by a scalar value.
/// </summary>
/// <param name="input">Input quaternion.</param>
/// <param name="scalar">Scalar multiplier.</param>
/// <returns>Scaled quaternion.</returns>
public static Quaternion ScalarMultiply(Quaternion input, float scalar)
{
return new Quaternion(input.x * scalar, input.y * scalar, input.z * scalar, input.w * scalar);
}
/// <summary>
/// Adds two quaternions component-wise.
/// </summary>
/// <param name="p">First quaternion.</param>
/// <param name="q">Second quaternion.</param>
/// <returns>Component-wise sum.</returns>
public static Quaternion Add(Quaternion p, Quaternion q)
{
return new Quaternion(p.x + q.x, p.y + q.y, p.z + q.z, p.w + q.w);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 993b5ed1c02645b9b5bbf6037644a91d
timeCreated: 1611072600

View File

@@ -0,0 +1,293 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
using NWH.NUI;
#endif
#endregion
namespace NWH.Common.ShiftingOrigin
{
/// <summary>
/// Prevents floating point precision errors by shifting all scene objects back toward world origin
/// when the main camera exceeds the distance threshold.
/// </summary>
/// <remarks>
/// <para>
/// As objects move far from world origin [0,0,0], floating point precision degrades causing
/// physics jitter and rendering artifacts. ShiftingOrigin solves this by periodically moving
/// all scene content back toward origin, keeping the player near [0,0,0] at all times.
/// </para>
/// <para>
/// The shift is transparent to gameplay - relative positions remain identical. Useful for
/// open world games, flight simulators, or any scenario with large travel distances.
/// </para>
/// <para>
/// Only affects the current scene. For multi-scene setups, ensure one ShiftingOrigin instance
/// per loaded scene set.
/// </para>
/// </remarks>
public class ShiftingOrigin : MonoBehaviour
{
public static ShiftingOrigin Instance;
/// <summary>
/// Distance from world origin in meters that triggers an origin shift.
/// Default 500m works well for most scenarios.
/// </summary>
public float distanceThreshold = 500f;
/// <summary>
/// Event invoked after the origin shift completes and physics is re-synced.
/// </summary>
public UnityEvent onAfterJump = new();
/// <summary>
/// Event invoked before the origin shift begins. Rigidbody sleep thresholds are temporarily disabled.
/// </summary>
public UnityEvent onBeforeJump = new();
private Camera _cameraMain;
private Vector3 _cameraPosition;
private Transform _cameraTransform;
private ParticleSystem.Particle[] _particles;
// Cached to avoid expensive FindObjectsByType calls
private List<Rigidbody> _cachedRigidbodies = new List<Rigidbody>();
private List<float> _originalSleepThresholds = new List<float>();
private List<ParticleSystem> _cachedParticleSystems = new List<ParticleSystem>();
private int _cameraCheckFrameCounter = 0;
/// <summary>
/// Cumulative offset applied to all objects since scene start.
/// Useful for tracking absolute world position despite origin shifts.
/// </summary>
public Vector3 TotalOffset { get; private set; }
private void Awake()
{
Debug.Assert(Instance == null, "Only one ShiftingOrigin script can be present in a scene.");
Instance = this;
onBeforeJump.AddListener(BeforeJump);
onAfterJump.AddListener(AfterJump);
}
private void Start()
{
_cameraMain = Camera.main;
if (_cameraMain != null)
{
_cameraTransform = _cameraMain.transform;
}
RefreshCaches();
}
/// <summary>
/// Refreshes cached references to Rigidbodies and ParticleSystems.
/// Call this if new physics objects or particle systems are added to the scene at runtime.
/// </summary>
public void RefreshCaches()
{
_cachedRigidbodies.Clear();
_originalSleepThresholds.Clear();
_cachedParticleSystems.Clear();
// Cache Rigidbodies and preserve their original sleepThreshold values
var rbs = FindObjectsByType<Rigidbody>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var rb in rbs)
{
if (rb != null)
{
_cachedRigidbodies.Add(rb);
_originalSleepThresholds.Add(rb.sleepThreshold);
}
}
// Cache ParticleSystems
var pss = FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None);
_cachedParticleSystems.AddRange(pss);
}
private void LateUpdate()
{
// Check Camera.main every 60 frames to support runtime camera switching
_cameraCheckFrameCounter++;
if (_cameraCheckFrameCounter >= 60)
{
Camera newCamera = Camera.main;
if (newCamera != _cameraMain)
{
_cameraMain = newCamera;
_cameraTransform = _cameraMain != null ? _cameraMain.transform : null;
}
_cameraCheckFrameCounter = 0;
}
if (_cameraMain == null)
{
return;
}
if (_cameraTransform == null)
{
return;
}
_cameraPosition = _cameraTransform.position;
if (_cameraPosition.magnitude > distanceThreshold)
{
Jump();
}
}
private static List<T> FindObjects<T>() where T : Object
{
return FindObjectsByType<T>(FindObjectsInactive.Include, FindObjectsSortMode.None).ToList(); // Not the fastest solution
}
private void BeforeJump()
{
for (int i = 0; i < _cachedRigidbodies.Count; i++)
{
if (_cachedRigidbodies[i] != null)
{
_cachedRigidbodies[i].sleepThreshold = float.MaxValue;
}
}
}
private void AfterJump()
{
// Restore original values to preserve custom physics settings
for (int i = 0; i < _cachedRigidbodies.Count; i++)
{
if (_cachedRigidbodies[i] != null && i < _originalSleepThresholds.Count)
{
_cachedRigidbodies[i].sleepThreshold = _originalSleepThresholds[i];
}
}
Physics.SyncTransforms();
}
private void Jump()
{
onBeforeJump.Invoke();
TotalOffset += _cameraPosition;
// Move root transforms
for (int i = 0; i < SceneManager.sceneCount; i++)
{
foreach (GameObject g in SceneManager.GetSceneAt(i).GetRootGameObjects())
{
if (g != null && g.transform != null)
{
g.transform.position -= _cameraPosition;
}
}
}
// Move particles
for (int pi = 0; pi < _cachedParticleSystems.Count; pi++)
{
ParticleSystem ps = _cachedParticleSystems[pi];
if (ps == null)
{
continue;
}
ParticleSystem.MainModule main = ps.main;
if (main.simulationSpace != ParticleSystemSimulationSpace.World)
{
continue;
}
int maxParticles = main.maxParticles;
if (maxParticles == 0)
{
continue;
}
bool wasPaused = ps.isPaused;
bool wasPlaying = ps.isPlaying;
if (!wasPaused)
{
ps.Pause();
}
if (_particles == null || _particles.Length < maxParticles)
{
_particles = new ParticleSystem.Particle[maxParticles];
}
int num = ps.GetParticles(_particles);
for (int i = 0; i < num; i++)
{
_particles[i].position -= _cameraPosition;
}
ps.SetParticles(_particles, num);
if (wasPlaying)
{
ps.Play();
}
}
onAfterJump.Invoke();
}
}
}
#if UNITY_EDITOR
namespace NWH.Common.ShiftingOrigin
{
[CustomEditor(typeof(ShiftingOrigin))]
public class ShiftingOriginEditor : NUIEditor
{
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI())
{
return false;
}
drawer.Field("distanceThreshold");
drawer.Field("onBeforeJump");
drawer.Field("onAfterJump");
drawer.EndEditor(this);
return true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6192ed0dc72e14940b68626a9543724b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,231 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ 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 UnityEngine;
#endregion
namespace NWH.Common.Utility
{
/// <summary>
/// Static utility class for converting between various units of measurement.
/// Includes conversions for distance, speed, fuel efficiency, and angular velocity.
/// </summary>
public static class UnitConverter
{
/// <summary>
/// Converts inches to meters.
/// </summary>
/// <param name="inch">Value in inches.</param>
/// <returns>Value in meters.</returns>
public static float Inch_To_Meter(float inch)
{
return inch * 0.0254f;
}
/// <summary>
/// Converts meters to inches.
/// </summary>
/// <param name="meters">Value in meters.</param>
/// <returns>Value in inches.</returns>
public static float Meter_To_Inch(float meters)
{
return meters * 39.3701f;
}
/// <summary>
/// km/l to l/100km
/// </summary>
/// <param name="kml">Fuel efficiency in km/l.</param>
/// <returns>Fuel efficiency in l/100km.</returns>
public static float KmlToL100km(float kml)
{
return kml == 0 ? Mathf.Infinity : 100f / kml;
}
/// <summary>
/// km/l to mpg
/// </summary>
/// <param name="kml">Fuel efficiency in km/l.</param>
/// <returns>Fuel efficiency in mpg.</returns>
public static float KmlToMpg(float kml)
{
return kml * 2.825f;
}
/// <summary>
/// l/100km to km/l
/// </summary>
/// <param name="l100km">Fuel efficiency in l/100km.</param>
/// <returns>Fuel efficiency in km/l.</returns>
public static float L100kmToKml(float l100km)
{
return l100km == 0 ? 0 : 100f / l100km;
}
/// <summary>
/// l/100km to mpg
/// </summary>
/// <param name="l100km">Fuel efficiency in l/100km.</param>
/// <returns>Fuel efficiency in mpg.</returns>
public static float L100kmToMpg(float l100km)
{
return l100km == 0 ? 0 : 282.5f / l100km;
}
/// <summary>
/// Converts angular velocity (rad/s) to rotations per minute.
/// </summary>
/// <param name="angularVelocity">Angular velocity in rad/s.</param>
/// <returns>Rotations per minute (RPM).</returns>
public static float AngularVelocityToRPM(float angularVelocity)
{
return angularVelocity * 9.5492965855137f;
}
/// <summary>
/// Converts rotations per minute to angular velocity (rad/s).
/// </summary>
/// <param name="RPM">Rotations per minute.</param>
/// <returns>Angular velocity in rad/s.</returns>
public static float RPMToAngularVelocity(float RPM)
{
return RPM * 0.10471975511966f;
}
/// <summary>
/// mpg to km/l
/// </summary>
/// <param name="mpg">Fuel efficiency in mpg.</param>
/// <returns>Fuel efficiency in km/l.</returns>
public static float MpgToKml(float mpg)
{
return mpg * 0.354f;
}
/// <summary>
/// mpg to l/100km
/// </summary>
/// <param name="mpg">Fuel efficiency in mpg.</param>
/// <returns>Fuel efficiency in l/100km.</returns>
public static float MpgToL100km(float mpg)
{
return mpg == 0 ? Mathf.Infinity : 282.5f / mpg;
}
/// <summary>
/// miles/h to km/h
/// </summary>
/// <param name="value">Speed in mph.</param>
/// <returns>Speed in km/h.</returns>
public static float MphToKph(float value)
{
return value * 1.60934f;
}
/// <summary>
/// m/s to km/h
/// </summary>
/// <param name="value">Speed in m/s.</param>
/// <returns>Speed in km/h.</returns>
public static float MpsToKph(float value)
{
return value * 3.6f;
}
/// <summary>
/// m/s to miles/h
/// </summary>
/// <param name="value">Speed in m/s.</param>
/// <returns>Speed in mph.</returns>
public static float MpsToMph(float value)
{
return value * 2.23694f;
}
/// <summary>
/// Converts km/h to mph.
/// </summary>
/// <param name="kmh">Speed in km/h.</param>
/// <returns>Speed in mph.</returns>
public static float Speed_kmhToMph(float kmh)
{
return kmh * 0.621371f;
}
/// <summary>
/// Converts km/h to m/s.
/// </summary>
/// <param name="kmh">Speed in km/h.</param>
/// <returns>Speed in m/s.</returns>
public static float Speed_kmhToMs(float kmh)
{
return kmh * 0.277778f;
}
/// <summary>
/// Converts mph to km/h.
/// </summary>
/// <param name="mph">Speed in mph.</param>
/// <returns>Speed in km/h.</returns>
public static float Speed_mphToKmh(float mph)
{
return mph * 1.60934f;
}
/// <summary>
/// Converts mph to m/s.
/// </summary>
/// <param name="mph">Speed in mph.</param>
/// <returns>Speed in m/s.</returns>
public static float Speed_mphToMs(float mph)
{
return mph * 0.44704f;
}
/// <summary>
/// Converts m/s to km/h.
/// </summary>
/// <param name="ms">Speed in m/s.</param>
/// <returns>Speed in km/h.</returns>
public static float Speed_msToKph(float ms)
{
return ms * 3.6f;
}
/// <summary>
/// Converts m/s to mph.
/// </summary>
/// <param name="ms">Speed in m/s.</param>
/// <returns>Speed in mph.</returns>
public static float Speed_msToMph(float ms)
{
return ms * 2.23694f;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fbd1e55d09bbf4489f42741d50ea951
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: