新增动态水物理插件
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34b51e6bc0d3493cbab61632bb388338
|
||||
timeCreated: 1607279175
|
||||
54
Packages/com.nwh.common/Runtime/Utility/ArrayExtensions.cs
Normal file
54
Packages/com.nwh.common/Runtime/Utility/ArrayExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4845cbe074fb994d8b8e2fd57cf6009
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f2992a3b4066f44cb0e705d0b20db1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
771
Packages/com.nwh.common/Runtime/Utility/GeomUtility.cs
Normal file
771
Packages/com.nwh.common/Runtime/Utility/GeomUtility.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: beed53cc984d46af9792ca3197ced971
|
||||
timeCreated: 1614097334
|
||||
41
Packages/com.nwh.common/Runtime/Utility/MathUtility.cs
Normal file
41
Packages/com.nwh.common/Runtime/Utility/MathUtility.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Utility/MathUtility.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Utility/MathUtility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 877cec20fa9639c46973b34a944dd302
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
156
Packages/com.nwh.common/Runtime/Utility/PIDController.cs
Normal file
156
Packages/com.nwh.common/Runtime/Utility/PIDController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51dfb92a565312b48af1d19f4827cc24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
103
Packages/com.nwh.common/Runtime/Utility/QuaternionExtensions.cs
Normal file
103
Packages/com.nwh.common/Runtime/Utility/QuaternionExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 993b5ed1c02645b9b5bbf6037644a91d
|
||||
timeCreated: 1611072600
|
||||
293
Packages/com.nwh.common/Runtime/Utility/ShiftingOrigin.cs
Normal file
293
Packages/com.nwh.common/Runtime/Utility/ShiftingOrigin.cs
Normal 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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6192ed0dc72e14940b68626a9543724b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
231
Packages/com.nwh.common/Runtime/Utility/UnitConverter.cs
Normal file
231
Packages/com.nwh.common/Runtime/Utility/UnitConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fbd1e55d09bbf4489f42741d50ea951
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user