Utils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
using System.Text.Json;
public enum EasingType
{
None = -1,
Linear = 0,
SineIn, SineOut, SineInOut,
QuadIn, QuadOut, QuadInOut,
CubicIn, CubicOut, CubicInOut,
QuartIn, QuartOut, QuartInOut,
QuintIn, QuintOut, QuintInOut,
ExpoIn, ExpoOut, ExpoInOut,
ExtremeIn, ExtremeOut, ExtremeInOut,
ElasticIn, ElasticOut, ElasticInOut,
ElasticSoftIn, ElasticSoftOut, ElasticSoftInOut,
BackIn, BackOut, BackInOut,
BounceIn, BounceOut, BounceInOut
};
public struct IntVector2 : IEquatable<IntVector2>
{
public static implicit operator Vector2( IntVector2 vec )
{
return new Vector2( vec.x, vec.y );
}
public static explicit operator IntVector2( Vector2 vec )
{
return new IntVector2( (int)vec.x, (int)vec.y );
}
/// <summary>
/// A vector with zero for all components.
/// </summary>
public static readonly IntVector2 Zero = new IntVector2( 0, 0 );
/// <summary>
/// A normalized vector along the positive X axis.
/// </summary>
public static readonly IntVector2 UnitX = new IntVector2( 1, 0 );
/// <summary>
/// A normalized vector along the positive Y axis.
/// </summary>
public static readonly IntVector2 UnitY = new IntVector2( 0, 1 );
#region Operators
/// <summary>
/// The identity operator.
/// </summary>
public static IntVector2 operator +( IntVector2 vec )
{
return vec;
}
/// <summary>
/// Component-wise addition of a vector to another.
/// </summary>
public static IntVector2 operator +( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x + b.x, a.y + b.y );
}
/// <summary>
/// Finds the negation of a vector.
/// </summary>
public static IntVector2 operator -( IntVector2 vec )
{
return new IntVector2( -vec.x, -vec.y );
}
/// <summary>
/// Component-wise subtraction of a vector from another.
/// </summary>
public static IntVector2 operator -( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x - b.x, a.y - b.y );
}
/// <summary>
/// Component-wise multiplication of a vector by another.
/// </summary>
public static IntVector2 operator *( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x * b.x, a.y * b.y );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static IntVector2 operator *( IntVector2 vec, int val )
{
return new IntVector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static Vector2 operator *( IntVector2 vec, float val )
{
return new Vector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static IntVector2 operator *( int val, IntVector2 vec )
{
return new IntVector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Multiplies a vector by a scalar.
/// </summary>
public static Vector2 operator *( float val, IntVector2 vec )
{
return new Vector2( vec.x * val, vec.y * val );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static IntVector2 operator /( IntVector2 a, IntVector2 b )
{
return new IntVector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static Vector2 operator /( Vector2 a, IntVector2 b )
{
return new Vector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Component-wise division of a vector by another.
/// </summary>
public static Vector2 operator /( IntVector2 a, Vector2 b )
{
return new Vector2( a.x / b.x, a.y / b.y );
}
/// <summary>
/// Division of this vector by a scalar.
/// </summary>
public static IntVector2 operator /( IntVector2 vec, int val )
{
return new IntVector2( vec.x / val, vec.y / val );
}
public static Vector2 operator /( IntVector2 vec, float val )
{
return new Vector2( vec.x / val, vec.y / val );
}
public static IntVector2 operator /( int val, IntVector2 vec )
{
return new IntVector2( val / vec.x, val / vec.y );
}
public static Vector2 operator /( float val, IntVector2 vec )
{
return new Vector2( val / vec.x, val / vec.y );
}
#endregion
/// <summary>
/// Horizontal component.
/// </summary>
public int x;
/// <summary>
/// Vertical component.
/// </summary>
public int y;
/// <summary>
/// Floating point magnitude of the vector.
/// </summary>
public float Length { get { return (float)MathF.Sqrt( x * x + y * y ); } }
/// <summary>
/// Magnitude of the vector in Taxicab geometry.
/// </summary>
public int ManhattanLength { get { return Math.Abs( x ) + Math.Abs( y ); } }
/// <summary>
/// Sum of each component squared.
/// </summary>
public int LengthSquared { get { return x * x + y * y; } }
/// <summary>
/// Gets a normalized vector in the same direction as this one.
/// </summary>
public Vector2 Normalized { get { return LengthSquared >= float.Epsilon ? this / Length : Zero; } }
/// <summary>
/// Gets a vector equal to this one rotated counter-clockwise by 90 degrees.
/// </summary>
public IntVector2 Left { get { return new IntVector2( -y, x ); } }
/// <summary>
/// Gets a vector equal to this one rotated clockwise 90 degrees.
/// </summary>
public IntVector2 Right { get { return new IntVector2( y, -x ); } }
/// <summary>
/// Gets a vector equal to this one rotated 180 degrees.
/// </summary>
public IntVector2 Back { get { return -this; } }
/// <summary>
/// Constructs a vector from the given X and Y components.
/// </summary>
public IntVector2( int x, int y )
{
this.x = x; this.y = y;
}
/// <summary>
/// Finds the scalar product of this vector and another.
/// </summary>
public int Dot( IntVector2 vec )
{
return x * vec.x + y * vec.y;
}
public override bool Equals( object obj )
{
return obj is IntVector2 && Equals( (IntVector2)obj );
}
/// <summary>
/// Tests for equality with another vector.
/// </summary>
public bool Equals( IntVector2 vec )
{
return x == vec.x && y == vec.y;
}
public override int GetHashCode()
{
return x ^ y;
}
/// <summary>
/// Gets a string representing this vector in (X, Y) format.
/// </summary>
public override string ToString()
{
return string.Format( "({0}, {1})", x, y );
}
}
public struct AStarEdge<T>
{
/// <summary>
/// The node this connection leads to.
/// </summary>
public readonly T Dest;
/// <summary>
/// The cost of using this connection.
/// </summary>
public readonly float Cost;
internal AStarEdge( T dest, float cost )
{
Dest = dest;
Cost = cost;
}
}
public static class Utils
{
// projects vector A onto B
public static Vector3 ProjectVector( Vector3 a, Vector3 b )
{
float dotProduct = Vector3.Dot( a, b );
float lengthSquared = Vector3.Dot( b, b );
return (dotProduct / lengthSquared) * b;
}
// returns vector B or C, whichever is in the closest direction compared to A
public static Vector3 FindCloserVector( Vector3 a, Vector3 b, Vector3 c )
{
// Normalize B and C
b = b.Normal;
c = c.Normal;
// Compute dot products
float dotProductB = Vector3.Dot( a, b );
float dotProductC = Vector3.Dot( a, c );
// Determine which dot product is larger
return dotProductB > dotProductC ? b : c;
}
public static GameObject FindChild( this Component component, string name )
{
return component.GameObject.Children.Where( x => x.Name == name ).FirstOrDefault();
}
public static Color GetRandomColor( float total = 1.5f )
{
float r = Game.Random.Float( 0f, 1f );
total = Math.Max( total - r, 0f );
float g = MathF.Min( Game.Random.Float( 0f, 1f ), total );
total = Math.Max( total - g, 0f );
float b = MathF.Min( Game.Random.Float( 0f, 1f ), total );
return new Color( r, g, b );
}
public static T ReadJsonCustom<T>( this BaseFileSystem fs, string filename, T defaultValue = default( T ) )
{
string text = fs.ReadAllText( filename );
if ( string.IsNullOrWhiteSpace( text ) )
{
return defaultValue;
}
JsonSerializerOptions options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
};
return JsonSerializer.Deserialize<T>( text, options );
}
public const float Deg2Rad = MathF.PI / 180f;
public const float Rad2Deg = 360f / (MathF.PI * 2f);
public static Vector2 RotateVector( Vector2 v, float degrees )
{
var rads = Deg2Rad * degrees;
var ca = MathF.Cos( rads );
var sa = MathF.Sin( rads );
return new Vector2( ca * v.x - sa * v.y, sa * v.x + ca * v.y );
}
public static Vector2 RotatePointAround( Vector2 p, Vector2 anchor, float degrees )
{
var offset = p - anchor;
var newVec = RotateVector( offset, degrees );
return anchor + newVec;
}
public static Vector2 DegreesToVector( float degrees )
{
var rads = Deg2Rad * degrees;
return new Vector2( MathF.Cos( rads ), MathF.Sin( rads ) );
}
public static float VectorToDegrees( Vector2 vec )
{
float radians = (float)Math.Atan2( vec.y, vec.x );
float degrees = radians * (180f / (float)Math.PI);
return degrees;
}
public static Vector2 GetPerpendicularVector( Vector2 vec )
{
return new Vector2( -vec.y, vec.x );
}
public static int GetDistance( int x, int y )
{
return (int)Math.Round( Math.Sqrt( x * x + y * y ) );
}
public static Vector2 GetRandomVector()
{
return DegreesToVector( Game.Random.Float( 0f, 360f ) );
}
public static float FastSin( float input )
{
// wrap input angle to -PI..PI
while ( input < -3.14159265f )
input += 6.28318531f;
while ( input > 3.14159265f )
input -= 6.28318531f;
return 1.27323954f * input + 0.405284735f * (input < 0f ? 1f : -1f) * input * input;
}
public static float DynamicEaseTo( float current, float goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
return current + (goal - current) * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Vector2 DynamicEaseTo( Vector2 current, Vector2 goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
var diff = goal - current;
return current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Vector3 DynamicEaseTo( Vector3 current, Vector3 goal, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goal;
var diff = goal - current;
return current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
}
public static Rotation DynamicEaseTo( Rotation currentRot, Rotation goalRot, float factorPercent, float dt, float referenceFrameRate = 60f )
{
if ( float.IsPositiveInfinity( dt ) )
return goalRot;
Vector3 current = currentRot.Angles().AsVector3();
Vector3 goal = goalRot.Angles().AsVector3();
var diff = goal - current;
var output = current + diff * (1f - MathF.Pow( 1f - MathX.Clamp( factorPercent, 0f, 1f ), dt * referenceFrameRate ));
return Rotation.From( new Angles( output ) );
}
public static float EaseUnclamped( float value, EasingType easingType )
{
switch ( easingType )
{
case EasingType.SineIn: return SineIn( value );
case EasingType.SineOut: return SineOut( value );
case EasingType.SineInOut: return SineInOut( value );
case EasingType.QuadIn: return QuadIn( value );
case EasingType.QuadOut: return QuadOut( value );
case EasingType.QuadInOut: return QuadInOut( value );
case EasingType.CubicIn: return CubicIn( value );
case EasingType.CubicOut: return CubicOut( value );
case EasingType.CubicInOut: return CubicInOut( value );
case EasingType.QuartIn: return QuartIn( value );
case EasingType.QuartOut: return QuartOut( value );
case EasingType.QuartInOut: return QuartInOut( value );
case EasingType.QuintIn: return QuintIn( value );
case EasingType.QuintOut: return QuintOut( value );
case EasingType.QuintInOut: return QuintInOut( value );
case EasingType.ExpoIn: return ExpoIn( value );
case EasingType.ExpoOut: return ExpoOut( value );
case EasingType.ExpoInOut: return ExpoInOut( value );
case EasingType.ExtremeIn: return ExtremeIn( value );
case EasingType.ExtremeOut: return ExtremeOut( value );
case EasingType.ExtremeInOut: return ExtremeInOut( value );
case EasingType.ElasticIn: return ElasticIn( value );
case EasingType.ElasticOut: return ElasticOut( value );
case EasingType.ElasticInOut: return ElasticInOut( value );
case EasingType.ElasticSoftIn: return ElasticSoftIn( value );
case EasingType.ElasticSoftOut: return ElasticSoftOut( value );
case EasingType.ElasticSoftInOut: return ElasticSoftInOut( value );
case EasingType.BackIn: return BackIn( value );
case EasingType.BackOut: return BackOut( value );
case EasingType.BackInOut: return BackInOut( value );
case EasingType.BounceIn: return BounceIn( value );
case EasingType.BounceOut: return BounceOut( value );
case EasingType.BounceInOut: return BounceInOut( value );
default: return value;
}
}
public static float Map( float value, float inputMin, float inputMax, float outputMin, float outputMax, EasingType easingType = EasingType.Linear, bool clamp = true )
{
if ( inputMin.Equals( inputMax ) || outputMin.Equals( outputMax ) )
return outputMin;
// if(inputMin.Equals(inputMax) || outputMin.Equals(outputMax))
// return outputMax;
if ( clamp )
{
// clamp input
if ( inputMax > inputMin )
{
if ( value < inputMin ) value = inputMin;
else if ( value > inputMax ) value = inputMax;
}
else if ( inputMax < inputMin )
{
if ( value > inputMin ) value = inputMin;
else if ( value < inputMax ) value = inputMax;
}
}
var ratio = EaseUnclamped( (value - inputMin) / (inputMax - inputMin), easingType );
var outVal = outputMin + ratio * (outputMax - outputMin);
// // clamp output
// if(outputMax < outputMin) {
// if(outVal < outputMax) outVal = outputMax;
// else if(outVal > outputMin) outVal = outputMin;
// } else {
// if(outVal > outputMax) outVal = outputMax;
// else if(outVal < outputMin) outVal = outputMin;
// }
return outVal;
}
public static float MapReturn( float value, float inputMin, float inputMax, float outputMin, float outputMax, EasingType easingType = EasingType.Linear )
{
var halfway = inputMin + (inputMax - inputMin) * 0.5f;
if ( value < halfway ) return Map( value, inputMin, halfway, outputMin, outputMax, easingType );
else return Map( value, halfway, inputMax, outputMax, outputMin, GetOppositeEasingType( easingType ) );
}
public static float EasePercent( float percent, EasingType easingType )
{
return Map( percent, 0f, 1f, 0f, 1f, easingType );
}
public static EasingType GetOppositeEasingType( EasingType easingType )
{
var opposite = EasingType.Linear;
switch ( easingType )
{
case EasingType.SineIn: opposite = EasingType.SineOut; break;
case EasingType.SineOut: opposite = EasingType.SineIn; break;
case EasingType.SineInOut: opposite = EasingType.SineInOut; break;
case EasingType.QuadIn: opposite = EasingType.QuadOut; break;
case EasingType.QuadOut: opposite = EasingType.QuadIn; break;
case EasingType.QuadInOut: opposite = EasingType.QuadInOut; break;
case EasingType.CubicIn: opposite = EasingType.CubicOut; break;
case EasingType.CubicOut: opposite = EasingType.CubicIn; break;
case EasingType.CubicInOut: opposite = EasingType.CubicInOut; break;
case EasingType.QuartIn: opposite = EasingType.QuartOut; break;
case EasingType.QuartOut: opposite = EasingType.QuartIn; break;
case EasingType.QuartInOut: opposite = EasingType.QuartInOut; break;
case EasingType.QuintIn: opposite = EasingType.QuintOut; break;
case EasingType.QuintOut: opposite = EasingType.QuintIn; break;
case EasingType.QuintInOut: opposite = EasingType.QuintInOut; break;
case EasingType.ExpoIn: opposite = EasingType.ExpoOut; break;
case EasingType.ExpoOut: opposite = EasingType.ExpoIn; break;
case EasingType.ExpoInOut: opposite = EasingType.ExpoInOut; break;
case EasingType.ExtremeIn: opposite = EasingType.ExtremeOut; break;
case EasingType.ExtremeOut: opposite = EasingType.ExtremeIn; break;
case EasingType.ExtremeInOut: opposite = EasingType.ExtremeInOut; break;
case EasingType.ElasticIn: opposite = EasingType.ElasticOut; break;
case EasingType.ElasticOut: opposite = EasingType.ElasticIn; break;
case EasingType.ElasticInOut: opposite = EasingType.ElasticInOut; break;
case EasingType.ElasticSoftIn: opposite = EasingType.ElasticSoftOut; break;
case EasingType.ElasticSoftOut: opposite = EasingType.ElasticSoftIn; break;
case EasingType.ElasticSoftInOut: opposite = EasingType.ElasticSoftInOut; break;
case EasingType.BackIn: opposite = EasingType.BackOut; break;
case EasingType.BackOut: opposite = EasingType.BackIn; break;
case EasingType.BackInOut: opposite = EasingType.BackInOut; break;
case EasingType.BounceIn: opposite = EasingType.BounceOut; break;
case EasingType.BounceOut: opposite = EasingType.BounceIn; break;
case EasingType.BounceInOut: opposite = EasingType.BounceInOut; break;
}
return opposite;
}
public static float SineIn( float t ) { return 1f - MathF.Cos( t * MathF.PI * 0.5f ); }
public static float SineOut( float t ) { return MathF.Sin( t * (MathF.PI * 0.5f) ); }
public static float SineInOut( float t ) { return -0.5f * (MathF.Cos( MathF.PI * t ) - 1f); }
public static float QuadIn( float t ) { return t * t; }
public static float QuadOut( float t ) { return t * (2f - t); }
public static float QuadInOut( float t ) { return t < 0.5f ? 2f * t * t : -1f + (4f - 2f * t) * t; }
public static float CubicIn( float t ) { return t * t * t; }
public static float CubicOut( float t ) { var t1 = t - 1f; return t1 * t1 * t1 + 1f; }
public static float CubicInOut( float t ) { return t < 0.5f ? 4f * t * t * t : (t - 1f) * (2f * t - 2f) * (2f * t - 2f) + 1f; }
public static float QuartIn( float t ) { return t * t * t * t; }
public static float QuartOut( float t ) { var t1 = t - 1f; return 1f - t1 * t1 * t1 * t1; }
public static float QuartInOut( float t ) { return t < 0.5f ? 4f * t * t * t : (t - 1f) * (2f * t - 2f) * (2f * t - 2f) + 1f; }
public static float QuintIn( float t ) { return t * t * t * t * t; }
public static float QuintOut( float t ) { var t1 = t - 1f; return 1f + t1 * t1 * t1 * t1 * t1; }
public static float QuintInOut( float t ) { var t1 = t - 1f; return t < 0.5f ? 16f * t * t * t * t * t : 1f + 16f * t1 * t1 * t1 * t1 * t1; }
public static float ExpoIn( float t ) { return MathF.Pow( 2f, 10f * (t - 1f) ); }
public static float ExpoOut( float t ) { return 1f - MathF.Pow( 2f, -10f * t ); }
public static float ExpoInOut( float t ) { return t < 0.5f ? ExpoIn( t * 2f ) * 0.5f : 1f - ExpoIn( 2f - t * 2f ) * 0.5f; }
public static float ExtremeIn( float t ) { return MathF.Pow( 10f, 10f * (t - 1f) ); }
public static float ExtremeOut( float t ) { return 1f - MathF.Pow( 10f, -10f * t ); }
public static float ExtremeInOut( float t ) { return t < 0.5f ? ExtremeIn( t * 2f ) * 0.5f : 1f - ExtremeIn( 2f - t * 2f ) * 0.5f; }
public static float ElasticIn( float t ) { return 1f - ElasticOut( 1f - t ); }
public static float ElasticOut( float t ) { var p = 0.3f; return MathF.Pow( 2f, -10f * t ) * MathF.Sin( (t - p / 4f) * (2f * (float)Math.PI) / p ) + 1f; }
public static float ElasticInOut( float t ) { return t < 0.5f ? ElasticIn( t * 2f ) * 0.5f : 1f - ElasticIn( 2f - t * 2f ) * 0.5f; }
public static float ElasticSoftIn( float t ) { return 1f - ElasticSoftOut( 1f - t ); }
public static float ElasticSoftOut( float t ) { var p = 0.5f; return MathF.Pow( 2f, -10f * t ) * MathF.Sin( (t - p / 4f) * (2f * (float)Math.PI) / p ) + 1f; }
public static float ElasticSoftInOut( float t ) { return t < 0.5f ? ElasticSoftIn( t * 2f ) * 0.5f : 1f - ElasticSoftIn( 2f - t * 2f ) * 0.5f; }
public static float BackIn( float t ) { var p = 1f; return t * t * ((p + 1f) * t - p); }
public static float BackOut( float t ) { var p = 1f; var scaledTime = t / 1f - 1f; return scaledTime * scaledTime * ((p + 1f) * scaledTime + p) + 1f; }
public static float BackInOut( float t )
{
var p = 1f;
var scaledTime = t * 2f;
var scaledTime2 = scaledTime - 2f;
var s = p * 1.525f;
if ( scaledTime < 1f ) return 0.5f * scaledTime * scaledTime * ((s + 1f) * scaledTime - s);
else return 0.5f * (scaledTime2 * scaledTime2 * ((s + 1f) * scaledTime2 + s) + 2f);
}
public static float BounceIn( float t ) { return 1f - BounceOut( 1f - t ); }
public static float BounceOut( float t )
{
var scaledTime = t / 1f;
if ( scaledTime < 1 / 2.75f )
{
return 7.5625f * scaledTime * scaledTime;
}
else if ( scaledTime < 2 / 2.75 )
{
var scaledTime2 = scaledTime - 1.5f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.75f;
}
else if ( scaledTime < 2.5 / 2.75 )
{
var scaledTime2 = scaledTime - 2.25f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.9375f;
}
else
{
var scaledTime2 = scaledTime - 2.625f / 2.75f;
return 7.5625f * scaledTime2 * scaledTime2 + 0.984375f;
}
}
public static float BounceInOut( float t )
{
if ( t < 0.5 ) return BounceIn( t * 2f ) * 0.5f;
else return BounceOut( t * 2f - 1f ) * 0.5f + 0.5f;
}
public static void Shuffle<T>( this IList<T> list )
{
System.Random rng = new System.Random();
int n = list.Count;
while ( n > 1 )
{
n--;
int k = rng.Next( n + 1 );
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static string FirstCharToUpper( this string input ) =>
input switch
{
null => throw new ArgumentNullException( nameof( input ) ),
"" => throw new ArgumentException( $"{nameof( input )} cannot be empty", nameof( input ) ),
_ => string.Concat( input[0].ToString().ToUpper(), input.AsSpan( 1 ) )
};
public static string GetRandomIcon( string i0, string i1 ) { int rand = Game.Random.Int( 0, 1 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; } }
public static string GetRandomIcon( string i0, string i1, string i2 ) { int rand = Game.Random.Int( 0, 2 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3 ) { int rand = Game.Random.Int( 0, 3 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4 ) { int rand = Game.Random.Int( 0, 4 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5 ) { int rand = Game.Random.Int( 0, 5 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5, string i6 ) { int rand = Game.Random.Int( 0, 6 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; case 6: return i6; } }
public static string GetRandomIcon( string i0, string i1, string i2, string i3, string i4, string i5, string i6, string i7 ) { int rand = Game.Random.Int( 0, 7 ); switch ( rand ) { case 0: default: return i0; case 1: return i1; case 2: return i2; case 3: return i3; case 4: return i4; case 5: return i5; case 6: return i6; case 7: return i7; } }
private class NodeInfo<T>
{
private const int MaxPoolSize = 8192;
private static List<NodeInfo<T>> _sPool;
internal static NodeInfo<T> Create( T node, NodeInfo<T> prev = null, float costAdd = 0f )
{
NodeInfo<T> nodeInfo;
if ( _sPool == null || _sPool.Count == 0 )
{
nodeInfo = new NodeInfo<T>();
}
else
{
nodeInfo = _sPool[_sPool.Count - 1];
_sPool.RemoveAt( _sPool.Count - 1 );
}
nodeInfo.Node = node;
nodeInfo.Prev = prev;
if ( prev == null )
{
nodeInfo.Depth = 0;
nodeInfo.Cost = 0f;
}
else
{
nodeInfo.Depth = prev.Depth + 1;
nodeInfo.Cost = prev.Cost + costAdd;
}
return nodeInfo;
}
internal static void Pool( NodeInfo<T> nodeInfo )
{
if ( _sPool == null ) _sPool = new List<NodeInfo<T>>( MaxPoolSize );
if ( _sPool.Count >= MaxPoolSize ) return;
_sPool.Add( nodeInfo );
}
private float _heuristic;
public T Node { get; private set; }
public NodeInfo<T> Prev { get; private set; }
public int Depth { get; private set; }
public float Cost { get; private set; }
public float Total { get; private set; }
public float Heuristic
{
get { return _heuristic; }
set
{
_heuristic = value;
Total = Cost + value;
}
}
}
/// <summary>
/// Convenience method to produce a graph connection for use when calling AStar().
/// </summary>
/// <typeparam name="T">Graph node type.</typeparam>
/// <param name="dest">Destination node of the connection.</param>
/// <param name="cost">Cost of taking the connection.</param>
public static AStarEdge<T> Edge<T>( T dest, float cost )
{
return new AStarEdge<T>( dest, cost );
}
private static class AStarWrapper<T>
where T : IEquatable<T>
{
public static NodeInfo<T> FirstMatchOrDefault( List<NodeInfo<T>> list, T toCompare )
{
var count = list.Count;
for ( var i = count - 1; i >= 0; --i )
{
var item = list[i];
if ( item.Node.Equals( toCompare ) )
return item;
}
return null;
}
private static List<NodeInfo<T>> _sOpen;
private static List<NodeInfo<T>> _sClosed;
public static bool AStar( T origin, T target, List<T> destList,
Func<T, IEnumerable<AStarEdge<T>>> adjFunc, Func<T, T, float> heuristicFunc )
{
var open = _sOpen ?? (_sOpen = new List<NodeInfo<T>>());
var clsd = _sClosed ?? (_sClosed = new List<NodeInfo<T>>());
open.Clear();
clsd.Clear();
var first = NodeInfo<T>.Create( origin );
first.Heuristic = heuristicFunc( origin, target );
open.Add( first );
try
{
while ( open.Count > 0 )
{
NodeInfo<T> cur = null;
foreach ( var node in open )
{
if ( cur == null || node.Total < cur.Total ) cur = node;
}
if ( cur.Node.Equals( target ) )
{
for ( var i = cur.Depth; i >= 0; --i )
{
destList.Add( cur.Node );
cur = cur.Prev;
}
destList.Reverse();
return true;
}
open.Remove( cur );
clsd.Add( cur );
foreach ( var adj in adjFunc( cur.Node ) )
{
var node = NodeInfo<T>.Create( adj.Dest, cur, adj.Cost );
var existing = FirstMatchOrDefault( clsd, adj.Dest );
if ( existing != null )
{
if ( existing.Cost <= node.Cost ) continue;
clsd.Remove( existing );
node.Heuristic = existing.Heuristic;
NodeInfo<T>.Pool( existing );
}
existing = FirstMatchOrDefault( open, adj.Dest );
if ( existing != null )
{
if ( existing.Cost <= node.Cost ) continue;
open.Remove( existing );
node.Heuristic = existing.Heuristic;
NodeInfo<T>.Pool( existing );
}
else
{
node.Heuristic = heuristicFunc( node.Node, target );
}
open.Add( node );
}
}
return false;
}
finally
{
foreach ( var nodeInfo in open )
{
NodeInfo<T>.Pool( nodeInfo );
}
foreach ( var nodeInfo in clsd )
{
NodeInfo<T>.Pool( nodeInfo );
}
}
}
}
/// <summary>
/// An implementation of the AStar path finding algorithm.
/// </summary>
/// <typeparam name="T">Graph node type.</typeparam>
/// <param name="origin">Node to start path finding from.</param>
/// <param name="target">The goal node to reach.</param>
/// <param name="adjFunc">Function returning the neighbouring connections for a node.</param>
/// <param name="heuristicFunc">Function returning the estimated cost of travelling between two nodes.</param>
/// <returns>A sequence of nodes representing a path if one is found, otherwise an empty array.</returns>
public static bool AStar<T>( T origin, T target, List<T> destPath,
Func<T, IEnumerable<AStarEdge<T>>> adjFunc, Func<T, T, float> heuristicFunc )
where T : IEquatable<T>
{
return AStarWrapper<T>.AStar( origin, target, destPath, adjFunc, heuristicFunc );
}
}