Files
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

302 lines
9.1 KiB
C#

using Sandbox;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Sandbox.Interpolation;
/// <summary>
/// Euler angles. Unlike a <see cref="Rotation">Rotation</see>, Euler angles can represent multiple revolutions (rotations) around an axis,
/// but suffer from issues like gimbal lock and lack of a defined "up" vector. Use <see cref="Rotation">Rotation</see> for most cases.
/// </summary>
[JsonConverter( typeof( Sandbox.Internal.JsonConvert.AnglesConverter ) )]
[StructLayout( LayoutKind.Sequential )]
public struct Angles : IEquatable<Angles>, IParsable<Angles>, IInterpolator<Angles>
{
/// <summary>
/// The pitch component, typically up/down.
/// </summary>
public float pitch;
/// <summary>
/// The yaw component, typically left/right.
/// </summary>
public float yaw;
/// <summary>
/// The roll component, basically rotation around the axis.
/// </summary>
public float roll;
/// <summary>
/// Initializes the angles object with given components.
/// </summary>
/// <param name="pitch">The Pitch component.</param>
/// <param name="yaw">The Yaw component.</param>
/// <param name="roll">The roll component.</param>
[ActionGraphNode( "angles.new" ), Title( "Euler Angles" ), Group( "Math/Geometry/Angles" )]
public Angles( float pitch, float yaw, float roll )
{
this.pitch = pitch;
this.yaw = yaw;
this.roll = roll;
}
/// <summary>
/// Copies values of given angles object.
/// </summary>
public Angles( Angles other )
{
this.pitch = other.pitch;
this.yaw = other.yaw;
this.roll = other.roll;
}
/// <summary>
/// Where x, y and z represent the pitch, yaw and roll respectively.
/// </summary>
public Angles( Vector3 vector )
{
this.pitch = vector.x;
this.yaw = vector.y;
this.roll = vector.z;
}
/// <summary>
/// Initializes the angles object with all components set to given value.
/// </summary>
public Angles( float all = 0.0f ) : this( all, all, all )
{
}
/// <summary>
/// Converts these Euler angles to a rotation. The angles will be normalized.
/// </summary>
/// <returns></returns>
public readonly Rotation ToRotation()
{
return Rotation.From( this );
}
/// <summary>
/// Return as a Vector3, where x = pitch etc
/// </summary>
public readonly Vector3 AsVector3()
{
return new Vector3( pitch, yaw, roll );
}
public readonly override string ToString()
{
return $"Pitch = {pitch:0.00}, Yaw = {yaw:0.00}, Roll = {roll:0.00}";
}
/// <summary>
/// An angle constant that has all its values set to 0. Use this instead of making a static 0,0,0 object yourself.
/// </summary>
public static readonly Angles Zero = new( 0 );
/// <summary>
/// Returns the angles of a uniformly random rotation.
/// </summary>
[ActionGraphNode( "angles.random" ), Title( "Random Angles" ), Group( "Math/Geometry/Angles" )]
public static Angles Random => Rotation.Random.Angles();
/// <summary>
/// Returns true if this angles object's components are all nearly zero with given tolerance.
/// </summary>
public readonly bool IsNearlyZero( double tolerance = 0.000001 )
{
return MathF.Abs( pitch ) <= tolerance &&
MathF.Abs( yaw ) <= tolerance &&
MathF.Abs( roll ) <= tolerance;
}
/// <summary>
/// Returns this angles object with given pitch component.
/// </summary>
public readonly Angles WithPitch( float pitch ) => new Angles( pitch, yaw, roll );
/// <summary>
/// Returns this angles object with given yaw component.
/// </summary>
public readonly Angles WithYaw( float yaw ) => new Angles( pitch, yaw, roll );
/// <summary>
/// Returns this angles object with given roll component.
/// </summary>
public readonly Angles WithRoll( float roll ) => new Angles( pitch, yaw, roll );
/// <summary>
/// Given a string, try to convert this into an angles object. The format is "p,y,r".
/// </summary>
public static Angles Parse( string str )
{
if ( TryParse( str, null, out var res ) )
return res;
return default;
}
/// <inheritdoc cref="Parse(string)" />
public static Angles Parse( string str, IFormatProvider provider )
{
return Parse( str );
}
/// <inheritdoc cref="Parse(string)" />
public static bool TryParse( string str, out Angles result )
{
return TryParse( str, CultureInfo.InvariantCulture, out result );
}
/// <inheritdoc cref="Parse(string)" />
public static bool TryParse( [NotNullWhen( true )] string str, IFormatProvider provider, [MaybeNullWhen( false )] out Angles result )
{
result = Angles.Zero;
if ( string.IsNullOrWhiteSpace( str ) )
return false;
str = str.Trim( '[', ']', ' ', '\n', '\r', '\t', '"' );
var components = str.Split( new[] { ' ', ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries );
if ( components.Length != 3 )
return false;
if ( !float.TryParse( components[0], NumberStyles.Float, provider, out float p ) ||
!float.TryParse( components[1], NumberStyles.Float, provider, out float y ) ||
!float.TryParse( components[2], NumberStyles.Float, provider, out float r ) )
{
return false;
}
result = new Angles( p, y, r );
return true;
}
/// <summary>
/// Returns clamped version of this object, meaning the angle on each axis is transformed to range of [0,360).
/// </summary>
public readonly Angles Clamped()
{
return new Angles( ClampAngle( pitch ), ClampAngle( yaw ), ClampAngle( roll ) );
}
/// <summary>
/// Returns normalized version of this object, meaning the angle on each axis is normalized to range of (-180,180].
/// </summary>
public readonly Angles Normal => new Angles( NormalizeAngle( pitch ), NormalizeAngle( yaw ), NormalizeAngle( roll ) );
/// <summary>
/// Clamps the angle to range of [0, 360)
/// </summary>
public static float ClampAngle( float v )
{
v %= 360.0f;
return (v < 0.0f) ? v + 360.0f : v;
}
/// <summary>
/// Normalizes the angle to range of (-180, 180]
/// </summary>
public static float NormalizeAngle( float v )
{
v = ClampAngle( v );
return (v > 180.0f) ? v - 360.0f : v;
}
/// <summary>
/// Performs linear interpolation on the two given angle objects.
/// </summary>
/// <param name="source">Angle A</param>
/// <param name="target">Angle B</param>
/// <param name="frac">Fraction in range [0,1] between the 2 angle objects to use for interpolation.</param>
public static Angles Lerp( in Angles source, in Angles target, float frac )
{
return source + (target - source).Normal * frac;
}
/// <summary>
/// Performs linear interpolation on the two given angle objects.
/// </summary>
/// <param name="target">Angle B</param>
/// <param name="frac">Fraction in range [0,1] between the 2 angle objects to use for interpolation.</param>
public readonly Angles LerpTo( Angles target, float frac ) => Lerp( this, target, frac );
/// <summary>
/// Converts an angle to a forward vector.
/// </summary>
public static Vector3 AngleVector( Angles ang )
{
const float piOver180 = (float)(Math.PI / 180.0);
float[] vAngles = { ang.yaw, ang.pitch };
float[] vSines = new float[2];
float[] vCosines = new float[2];
vSines[0] = (float)Math.Sin( vAngles[0] * piOver180 );
vSines[1] = (float)Math.Sin( vAngles[1] * piOver180 );
vCosines[0] = (float)Math.Cos( vAngles[0] * piOver180 );
vCosines[1] = (float)Math.Cos( vAngles[1] * piOver180 );
return new Vector3( vCosines[1] * vCosines[0],
vCosines[1] * vSines[0],
-vSines[1] );
}
/// <summary>
/// The forward direction vector for this angle.
/// </summary>
public Vector3 Forward
{
readonly get { return AngleVector( this ); }
set { this = Vector3.VectorAngle( value ); }
}
/// <summary>
/// Snap to grid
/// </summary>
public readonly Angles SnapToGrid( float gridSize, bool sx = true, bool sy = true, bool sz = true )
{
return new Angles( sx ? pitch.SnapToGrid( gridSize ) : pitch, sy ? yaw.SnapToGrid( gridSize ) : yaw, sz ? roll.SnapToGrid( gridSize ) : roll );
}
#region operators
public static Angles operator +( Angles c1, Angles c2 )
{
return new Angles( c1.pitch + c2.pitch, c1.yaw + c2.yaw, c1.roll + c2.roll );
}
public static Angles operator +( Angles c1, Vector3 c2 )
{
return new Angles( c1.pitch + c2.x, c1.yaw + c2.y, c1.roll + c2.z );
}
public static Angles operator -( Angles c1, Angles c2 )
{
return new Angles( c1.pitch - c2.pitch, c1.yaw - c2.yaw, c1.roll - c2.roll );
}
public static Angles operator *( Angles c1, float c2 ) => new Angles( c1.pitch * c2, c1.yaw * c2, c1.roll * c2 );
public static Angles operator /( Angles c1, float c2 ) => new Angles( c1.pitch / c2, c1.yaw / c2, c1.roll / c2 );
#endregion
#region equality
public static bool operator ==( in Angles left, in Angles right ) => left.Equals( right );
public static bool operator !=( in Angles left, in Angles right ) => !(left == right);
public override readonly bool Equals( object obj ) => obj is Angles o && Equals( o );
public readonly bool Equals( Angles o ) => (pitch, yaw, roll) == (o.pitch, o.yaw, o.roll);
public readonly override int GetHashCode() => HashCode.Combine( pitch, yaw, roll );
#endregion
Angles IInterpolator<Angles>.Interpolate( Angles a, Angles b, float delta )
{
return a.LerpTo( b, delta );
}
}