mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
371 lines
11 KiB
C#
371 lines
11 KiB
C#
using Sandbox;
|
|
using Sandbox.Interpolation;
|
|
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json.Serialization;
|
|
|
|
/// <summary>
|
|
/// A struct containing a position, rotation and scale. This is commonly used in engine to describe
|
|
/// entity position, bone position and scene object position.
|
|
/// </summary>
|
|
[StructLayout( LayoutKind.Sequential )]
|
|
public struct Transform : System.IEquatable<Transform>, IInterpolator<Transform>
|
|
{
|
|
/// <summary>
|
|
/// Represents a zero transform, that being, a transform with scale of 1, position of <see cref="Vector3.Zero"/> and rotation of <see cref="Rotation.Identity"/>.
|
|
/// </summary>
|
|
public readonly static Transform Zero = new Transform( Vector3.Zero );
|
|
|
|
/// <summary>
|
|
/// Position of the transform.
|
|
/// </summary>
|
|
[JsonInclude, JsonPropertyName( "Position" ), ActionGraphInclude( AutoExpand = true )]
|
|
public Vector3 Position;
|
|
|
|
/// <summary>
|
|
/// Scale of the transform. Does not itself scale <see cref="Position"/> or <see cref="Rotation"/>.
|
|
/// </summary>
|
|
[JsonInclude, JsonPropertyName( "Scale" ), ActionGraphInclude( AutoExpand = true )]
|
|
public Vector3 Scale;
|
|
|
|
/// <summary>
|
|
/// A uniform scale component. Generally the scale is uniform, and we'll just access the .x component.
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public float UniformScale
|
|
{
|
|
readonly get => Scale.x;
|
|
set => Scale = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotation of this transform.
|
|
/// </summary>
|
|
[JsonInclude, JsonPropertyName( "Rotation" ), ActionGraphInclude( AutoExpand = true )]
|
|
public Rotation Rotation;
|
|
|
|
[JsonIgnore] public readonly Vector3 Forward => Rotation.Forward;
|
|
[JsonIgnore] public readonly Vector3 Backward => Rotation.Backward;
|
|
[JsonIgnore] public readonly Vector3 Up => Rotation.Up;
|
|
[JsonIgnore] public readonly Vector3 Down => Rotation.Down;
|
|
[JsonIgnore] public readonly Vector3 Right => Rotation.Right;
|
|
[JsonIgnore] public readonly Vector3 Left => Rotation.Left;
|
|
|
|
/// <summary>
|
|
/// This scale can be used if you need to divide by scale without resulting in infinity or NaN values.
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
internal readonly Vector3 SafeScale => new( Scale.x != 0f ? Scale.x : 1f, Scale.y != 0f ? Scale.y : 1f, Scale.z != 0f ? Scale.z : 1f );
|
|
|
|
public Transform( Vector3 pos = default )
|
|
{
|
|
Position = pos;
|
|
Rotation = Rotation.Identity;
|
|
Scale = 1.0f;
|
|
}
|
|
|
|
public Transform() : this( default )
|
|
{
|
|
}
|
|
|
|
public Transform( Vector3 position, Rotation rotation, float scale = 1.0f )
|
|
{
|
|
Position = position;
|
|
Rotation = rotation;
|
|
Scale = scale;
|
|
}
|
|
|
|
public Transform( Vector3 position, Rotation rotation, Vector3 scale )
|
|
{
|
|
Position = position;
|
|
Rotation = rotation;
|
|
Scale = scale;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if position, scale and rotation are valid
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public readonly bool IsValid
|
|
{
|
|
get
|
|
{
|
|
if ( Position.IsNaN ) return false;
|
|
if ( Scale.IsNaN ) return false;
|
|
|
|
// todo - how to dertermine if rotation is valid
|
|
if ( Rotation.x == 0 && Rotation.y == 0 && Rotation.z == 0 && Rotation.w == 0 ) return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a point in world space to a point in this transform's local space
|
|
/// </summary>
|
|
public readonly Vector3 PointToLocal( in Vector3 worldPoint )
|
|
{
|
|
return Rotation.Inverse * (worldPoint - Position) / SafeScale;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a world normal to a local normal
|
|
/// </summary>
|
|
public readonly Vector3 NormalToLocal( in Vector3 worldNormal )
|
|
{
|
|
return (Rotation.Inverse * worldNormal).Normal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a world rotation to a local rotation
|
|
/// </summary>
|
|
public readonly Rotation RotationToLocal( in Rotation worldRot )
|
|
{
|
|
return Rotation.Inverse * worldRot;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a point in this transform's local space to a point in world space
|
|
/// </summary>
|
|
public readonly Vector3 PointToWorld( in Vector3 localPoint )
|
|
{
|
|
return Position + (Rotation * (localPoint * Scale));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a local normal to a world normal
|
|
/// </summary>
|
|
public readonly Vector3 NormalToWorld( in Vector3 localNormal )
|
|
{
|
|
return (Rotation * localNormal).Normal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a local rotation to a world rotation
|
|
/// </summary>
|
|
public readonly Rotation RotationToWorld( in Rotation localRotation )
|
|
{
|
|
return Rotation * localRotation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert child transform from the world to a local transform
|
|
/// </summary>
|
|
public Transform ToLocal( in Transform child )
|
|
{
|
|
var rotInv = Rotation.Inverse;
|
|
|
|
var localSafeScale = new Vector3(
|
|
Scale.x != 0f ? child.Scale.x / Scale.x : child.Scale.x,
|
|
Scale.y != 0f ? child.Scale.y / Scale.y : child.Scale.y,
|
|
Scale.z != 0f ? child.Scale.z / Scale.z : child.Scale.z
|
|
);
|
|
|
|
return new Transform
|
|
{
|
|
Position = ((child.Position - Position) * rotInv) / SafeScale,
|
|
Rotation = rotInv * child.Rotation,
|
|
Scale = localSafeScale
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert child transform from local to the world
|
|
/// </summary>
|
|
public readonly Transform ToWorld( in Transform child )
|
|
{
|
|
return new Transform
|
|
{
|
|
Position = ((child.Position * Scale) * Rotation) + Position,
|
|
Rotation = Rotation * child.Rotation,
|
|
Scale = Scale * child.Scale
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform linear interpolation from one transform to another.
|
|
/// </summary>
|
|
public static Transform Lerp( in Transform a, in Transform b, float t, bool clamp )
|
|
{
|
|
return new Transform
|
|
{
|
|
Position = Vector3.Lerp( a.Position, b.Position, t, clamp ),
|
|
Rotation = Rotation.Slerp( a.Rotation, b.Rotation, t, clamp ),
|
|
Scale = Vector3.Lerp( a.Scale, b.Scale, t, clamp ),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Linearly interpolate from this transform to given transform.
|
|
/// </summary>
|
|
public readonly Transform LerpTo( in Transform target, float t, bool clamp = true )
|
|
{
|
|
return Lerp( this, target, t, clamp );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a position to this transform and return the result.
|
|
/// </summary>
|
|
public readonly Transform Add( in Vector3 position, bool worldSpace )
|
|
{
|
|
var t = this;
|
|
|
|
if ( worldSpace ) t.Position += position;
|
|
else t.Position = PointToWorld( position );
|
|
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this transform with a new position.
|
|
/// </summary>
|
|
public readonly Transform WithPosition( in Vector3 position )
|
|
{
|
|
var t = this;
|
|
t.Position = position;
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this transform with a new position and rotation
|
|
/// </summary>
|
|
public readonly Transform WithPosition( in Vector3 position, in Rotation rotation )
|
|
{
|
|
var t = this;
|
|
t.Position = position;
|
|
t.Rotation = rotation;
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this transform with a new rotation.
|
|
/// </summary>
|
|
public readonly Transform WithRotation( in Rotation rotation )
|
|
{
|
|
var t = this;
|
|
t.Rotation = rotation;
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this transform with a new scale.
|
|
/// </summary>
|
|
public readonly Transform WithScale( float scale )
|
|
{
|
|
var t = this;
|
|
t.Scale = scale;
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this transform with a new scale.
|
|
/// </summary>
|
|
public readonly Transform WithScale( in Vector3 scale )
|
|
{
|
|
var t = this;
|
|
t.Scale = scale;
|
|
return t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a transform that is the mirror of this
|
|
/// </summary>
|
|
public readonly Transform Mirror( in Sandbox.Plane plane )
|
|
{
|
|
// Mirror position
|
|
Vector3 mirroredPos = plane.ReflectPoint( Position );
|
|
|
|
// Reflect forward and up
|
|
Vector3 forward = plane.ReflectDirection( Rotation.Forward );
|
|
Vector3 up = plane.ReflectDirection( Rotation.Up );
|
|
|
|
Rotation mirroredRot = Rotation.LookAt( forward, up );
|
|
|
|
return new Transform( mirroredPos, mirroredRot, Scale );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a ray from this transform, which goes from the center along the Forward
|
|
/// </summary>
|
|
[JsonIgnore]
|
|
public readonly Ray ForwardRay => new Ray( Position, Rotation.Forward );
|
|
|
|
/// <summary>
|
|
/// Rotate this transform around given point by given rotation and return the result.
|
|
/// </summary>
|
|
/// <param name="center">Point to rotate around.</param>
|
|
/// <param name="rot">How much to rotate by. <see cref="Rotation.FromAxis(Vector3, float)"/> can be useful.</param>
|
|
/// <returns>The rotated transform.</returns>
|
|
public readonly Transform RotateAround( in Vector3 center, in Rotation rot )
|
|
{
|
|
Transform trans = this;
|
|
|
|
var dir = trans.Position - center;
|
|
dir = rot * dir;
|
|
trans.Position = center + dir;
|
|
|
|
var myRot = trans.Rotation;
|
|
trans.Rotation *= myRot.Inverse * rot * myRot;
|
|
|
|
return trans;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenate (add together) the 2 given transforms and return a new resulting transform.
|
|
/// </summary>
|
|
public static Transform Concat( Transform parent, Transform local )
|
|
{
|
|
return new Transform
|
|
{
|
|
Position = parent.Position + parent.Scale * (parent.Rotation * local.Position),
|
|
Scale = parent.Scale * local.Scale,
|
|
Rotation = parent.Rotation * (parent.Rotation.Distance( local.Rotation ) >= 180 ? local.Rotation.Inverse : local.Rotation),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a string, try to convert this into a transform. The format is <c>"px,py,pz,rx,ry,rz,rw"</c>.
|
|
/// </summary>
|
|
public static Transform Parse( string str )
|
|
{
|
|
str = str.Trim( '[', ']', ' ', '\n', '\r', '\t', '"' );
|
|
|
|
var p = str.Split( new[] { ' ', ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries );
|
|
if ( p.Length != 7 ) return default;
|
|
|
|
return new Transform( new Vector3( p[0].ToFloat(), p[1].ToFloat(), p[2].ToFloat() ), new Quaternion( p[3].ToFloat(), p[4].ToFloat(), p[5].ToFloat(), p[6].ToFloat() ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Formats the Transform into a string "pos, rot, scale"
|
|
/// </summary>
|
|
public override readonly string ToString()
|
|
{
|
|
return $"pos {Position}, rot {Rotation}, scale {Scale}";
|
|
}
|
|
|
|
#region equality
|
|
public static bool operator ==( Transform left, Transform right ) => left.Equals( right );
|
|
public static bool operator !=( Transform left, Transform right ) => !(left == right);
|
|
public readonly override bool Equals( object obj ) => obj is Transform o && Equals( o );
|
|
public readonly bool Equals( Transform o ) => (Position, Scale, Rotation) == (o.Position, o.Scale, o.Rotation);
|
|
public readonly override int GetHashCode() => HashCode.Combine( Position, Scale, Rotation );
|
|
|
|
/// <summary>
|
|
/// Returns true if we're nearly equal to the passed transform.
|
|
/// </summary>
|
|
/// <param name="tx">The value to compare with</param>
|
|
/// <param name="delta">The max difference between component values</param>
|
|
/// <returns>True if nearly equal</returns>
|
|
public readonly bool AlmostEqual( in Transform tx, float delta = 0.0001f )
|
|
{
|
|
return Position.AlmostEqual( tx.Position, delta ) && Scale.AlmostEqual( tx.Scale, delta ) && Rotation.AlmostEqual( tx.Rotation, delta );
|
|
}
|
|
#endregion
|
|
|
|
Transform IInterpolator<Transform>.Interpolate( Transform a, Transform b, float delta )
|
|
{
|
|
return Lerp( a, b, delta, true );
|
|
}
|
|
}
|