using Sandbox; using Sandbox.Interpolation; using System.Numerics; using System.Runtime.InteropServices; using System.Text.Json.Serialization; /// /// A struct containing a position, rotation and scale. This is commonly used in engine to describe /// entity position, bone position and scene object position. /// [StructLayout( LayoutKind.Sequential )] public struct Transform : System.IEquatable, IInterpolator { /// /// Represents a zero transform, that being, a transform with scale of 1, position of and rotation of . /// public readonly static Transform Zero = new Transform( Vector3.Zero ); /// /// Position of the transform. /// [JsonInclude, JsonPropertyName( "Position" ), ActionGraphInclude( AutoExpand = true )] public Vector3 Position; /// /// Scale of the transform. Does not itself scale or . /// [JsonInclude, JsonPropertyName( "Scale" ), ActionGraphInclude( AutoExpand = true )] public Vector3 Scale; /// /// A uniform scale component. Generally the scale is uniform, and we'll just access the .x component. /// [JsonIgnore] public float UniformScale { readonly get => Scale.x; set => Scale = value; } /// /// Rotation of this transform. /// [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; /// /// This scale can be used if you need to divide by scale without resulting in infinity or NaN values. /// [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; } /// /// Returns true if position, scale and rotation are valid /// [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; } } /// /// Convert a point in world space to a point in this transform's local space /// public readonly Vector3 PointToLocal( in Vector3 worldPoint ) { return Rotation.Inverse * (worldPoint - Position) / SafeScale; } /// /// Convert a world normal to a local normal /// public readonly Vector3 NormalToLocal( in Vector3 worldNormal ) { return (Rotation.Inverse * worldNormal).Normal; } /// /// Convert a world rotation to a local rotation /// public readonly Rotation RotationToLocal( in Rotation worldRot ) { return Rotation.Inverse * worldRot; } /// /// Convert a point in this transform's local space to a point in world space /// public readonly Vector3 PointToWorld( in Vector3 localPoint ) { return Position + (Rotation * (localPoint * Scale)); } /// /// Convert a local normal to a world normal /// public readonly Vector3 NormalToWorld( in Vector3 localNormal ) { return (Rotation * localNormal).Normal; } /// /// Convert a local rotation to a world rotation /// public readonly Rotation RotationToWorld( in Rotation localRotation ) { return Rotation * localRotation; } /// /// Convert child transform from the world to a local transform /// 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 }; } /// /// Convert child transform from local to the world /// public readonly Transform ToWorld( in Transform child ) { return new Transform { Position = ((child.Position * Scale) * Rotation) + Position, Rotation = Rotation * child.Rotation, Scale = Scale * child.Scale }; } /// /// Perform linear interpolation from one transform to another. /// 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 ), }; } /// /// Linearly interpolate from this transform to given transform. /// public readonly Transform LerpTo( in Transform target, float t, bool clamp = true ) { return Lerp( this, target, t, clamp ); } /// /// Add a position to this transform and return the result. /// public readonly Transform Add( in Vector3 position, bool worldSpace ) { var t = this; if ( worldSpace ) t.Position += position; else t.Position = PointToWorld( position ); return t; } /// /// Return this transform with a new position. /// public readonly Transform WithPosition( in Vector3 position ) { var t = this; t.Position = position; return t; } /// /// Return this transform with a new position and rotation /// public readonly Transform WithPosition( in Vector3 position, in Rotation rotation ) { var t = this; t.Position = position; t.Rotation = rotation; return t; } /// /// Return this transform with a new rotation. /// public readonly Transform WithRotation( in Rotation rotation ) { var t = this; t.Rotation = rotation; return t; } /// /// Return this transform with a new scale. /// public readonly Transform WithScale( float scale ) { var t = this; t.Scale = scale; return t; } /// /// Return this transform with a new scale. /// public readonly Transform WithScale( in Vector3 scale ) { var t = this; t.Scale = scale; return t; } /// /// Create a transform that is the mirror of this /// 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 ); } /// /// Return a ray from this transform, which goes from the center along the Forward /// [JsonIgnore] public readonly Ray ForwardRay => new Ray( Position, Rotation.Forward ); /// /// Rotate this transform around given point by given rotation and return the result. /// /// Point to rotate around. /// How much to rotate by. can be useful. /// The rotated transform. 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; } /// /// Concatenate (add together) the 2 given transforms and return a new resulting transform. /// 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), }; } /// /// Given a string, try to convert this into a transform. The format is "px,py,pz,rx,ry,rz,rw". /// 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() ) ); } /// /// Formats the Transform into a string "pos, rot, scale" /// 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 ); /// /// Returns true if we're nearly equal to the passed transform. /// /// The value to compare with /// The max difference between component values /// True if nearly equal 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.Interpolate( Transform a, Transform b, float delta ) { return Lerp( a, b, delta, true ); } }