// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable CompareOfFloatsByEqualityOperator namespace FlaxEngine { [Serializable] partial struct Transform : IEquatable, IFormattable { private static readonly string _formatString = "Translation:{0} Orientation:{1} Scale:{2}"; /// /// The size of the type, in bytes /// public static readonly int SizeInBytes = Marshal.SizeOf(typeof(Transform)); /// /// A identity with all default values /// public static readonly Transform Identity = new Transform(Vector3.Zero); /// /// Init /// /// Position in 3D space public Transform(Vector3 position) { Translation = position; Orientation = Quaternion.Identity; Scale = Vector3.One; } /// /// Init /// /// Position in 3D space /// Rotation in 3D space public Transform(Vector3 position, Quaternion rotation) { Translation = position; Orientation = rotation; Scale = Vector3.One; } /// /// Init /// /// Position in 3D space /// Rotation in 3D space /// Transform scale public Transform(Vector3 position, Quaternion rotation, Vector3 scale) { Translation = position; Orientation = rotation; Scale = scale; } /// /// Creates a new Transform from a matrix /// /// World matrix public Transform(Matrix transform) { transform.Decompose(out Scale, out Orientation, out Translation); } /// /// Creates a new Transform from a matrix /// /// World matrix public Transform(ref Matrix transform) { transform.Decompose(out Scale, out Orientation, out Translation); } /// /// Gets a value indicting whether this transform is identity /// public bool IsIdentity => Equals(Identity); /// /// Gets the forward vector. /// public Vector3 Forward => Vector3.Transform(Vector3.Forward, Orientation); /// /// Gets the backward vector. /// public Vector3 Backward => Vector3.Transform(Vector3.Backward, Orientation); /// /// Gets the up vector. /// public Vector3 Up => Vector3.Transform(Vector3.Up, Orientation); /// /// Gets the down vector. /// public Vector3 Down => Vector3.Transform(Vector3.Down, Orientation); /// /// Gets the left vector. /// public Vector3 Left => Vector3.Transform(Vector3.Left, Orientation); /// /// Gets the right vector. /// public Vector3 Right => Vector3.Transform(Vector3.Right, Orientation); /// /// Gets rotation matrix (from Orientation). /// /// Rotation matrix public Matrix GetRotation() { Matrix.RotationQuaternion(ref Orientation, out var result); return result; } /// /// Gets rotation matrix (from Orientation). /// /// Matrix to set public void GetRotation(out Matrix result) { Matrix.RotationQuaternion(ref Orientation, out result); } /// /// Sets rotation matrix (from Orientation). /// /// Rotation matrix public void SetRotation(Matrix value) { Quaternion.RotationMatrix(ref value, out Orientation); } /// /// Sets rotation matrix (from Orientation). /// /// Rotation matrix public void SetRotation(ref Matrix value) { Quaternion.RotationMatrix(ref value, out Orientation); } /// /// Gets world matrix that describes transformation as a 4 by 4 matrix. /// /// World matrix public Matrix GetWorld() { Matrix.Transformation(ref Scale, ref Orientation, ref Translation, out var result); return result; } /// /// Gets world matrix that describes transformation as a 4 by 4 matrix. /// /// World matrix public void GetWorld(out Matrix result) { Matrix.Transformation(ref Scale, ref Orientation, ref Translation, out result); } /// /// Adds two transforms. /// /// The first transform to add. /// The second transform to add. /// The sum of the two transforms. public static Transform Add(Transform left, Transform right) { Transform result; Quaternion.Multiply(ref left.Orientation, ref right.Orientation, out result.Orientation); Vector3.Multiply(ref left.Scale, ref right.Scale, out result.Scale); Vector3.Add(ref left.Translation, ref right.Translation, out result.Translation); return result; } /// /// Subtracts two transforms. /// /// The first transform to subtract from. /// The second transform to subtract. /// The difference of the two transforms. public static Transform Subtract(Transform left, Transform right) { Transform result; Vector3.Subtract(ref left.Translation, ref right.Translation, out result.Translation); Quaternion invRotation = right.Orientation.Conjugated(); Quaternion.Multiply(ref left.Orientation, ref invRotation, out result.Orientation); Vector3.Divide(ref left.Scale, ref right.Scale, out result.Scale); return result; } /// /// Perform transformation of the given transform in local space /// /// Local space transform /// World space transform public Transform LocalToWorld(Transform other) { Transform result; Quaternion.Multiply(ref Orientation, ref other.Orientation, out result.Orientation); Vector3.Multiply(ref Scale, ref other.Scale, out result.Scale); result.Translation = LocalToWorld(other.Translation); return result; } /// /// Perform transformation of the given point in local space /// /// Local space point /// World space point public Vector3 LocalToWorld(Vector3 point) { point *= Scale; Vector3.Transform(ref point, ref Orientation, out point); return point + Translation; } /// /// Performs transformation of the given vector in local space to the world space of this transform. /// /// The local space vector. /// The world space vector. public Vector3 LocalToWorldVector(Vector3 vector) { vector *= Scale; Vector3.Transform(ref vector, ref Orientation, out vector); return vector; } /// /// Perform transformation of the given points in local space /// /// Local space points /// World space points public void LocalToWorld(Vector3[] points, Vector3[] result) { for (int i = 0; i < points.Length; i++) { result[i] = Vector3.Transform(points[i] * Scale, Orientation) + Translation; } } /// /// Perform transformation of the given transform in world space /// /// World space transform /// Local space transform public Transform WorldToLocal(Transform other) { Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; if (invScale.Y != 0.0f) invScale.Y = 1.0f / invScale.Y; if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; Transform result; result.Orientation = Orientation; result.Orientation.Invert(); Quaternion.Multiply(ref result.Orientation, ref other.Orientation, out result.Orientation); Vector3.Multiply(ref other.Scale, ref invScale, out result.Scale); result.Translation = WorldToLocal(other.Translation); return result; } /// /// Perform transformation of the given point in world space /// /// World space point /// Local space point public Vector3 WorldToLocal(Vector3 point) { Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; if (invScale.Y != 0.0f) invScale.Y = 1.0f / invScale.Y; if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; Quaternion invRotation = Orientation; invRotation.Invert(); Vector3 result = point - Translation; Vector3.Transform(ref result, ref invRotation, out result); return result * invScale; } /// /// Perform transformation of the given vector in world space /// /// World space vector /// Local space vector public Vector3 WorldToLocalVector(Vector3 vector) { Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; if (invScale.Y != 0.0f) invScale.Y = 1.0f / invScale.Y; if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; Quaternion invRotation = Orientation; invRotation.Invert(); Vector3.Transform(ref vector, ref invRotation, out var result); return result * invScale; } /// /// Perform transformation of the given points in world space /// /// World space points /// Local space points public void WorldToLocal(Vector3[] points, Vector3[] result) { Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; if (invScale.Y != 0.0f) invScale.Y = 1.0f / invScale.Y; if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; Quaternion invRotation = Orientation; invRotation.Invert(); for (int i = 0; i < points.Length; i++) { result[i] = points[i] - Translation; Vector3.Transform(ref result[i], ref invRotation, out result[i]); result[i] *= invScale; } } /// /// Transforms the direction vector from the local space to the world space. /// /// /// This operation is not affected by scale or position of the transform. The returned vector has the same length as direction. /// Use for the conversion if the vector represents a position rather than a direction. /// /// The direction. /// The transformed direction vector. public Vector3 TransformDirection(Vector3 direction) { Vector3.Transform(ref direction, ref Orientation, out var result); return result; } /// /// Transforms the position from the local space to the world space. /// /// /// Use for the conversion if the vector represents a direction rather than a position. /// /// The position. /// The transformed position. public Vector3 TransformPoint(Vector3 position) { return LocalToWorld(position); } /// /// Performs a linear interpolation between two transformations. /// /// Start transformation. /// End transformation. /// Value between 0 and 1 indicating the weight of . /// The linear interpolation of the two transformations. /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static Transform Lerp(Transform start, Transform end, float amount) { Transform result; Vector3.Lerp(ref start.Translation, ref end.Translation, amount, out result.Translation); Quaternion.Slerp(ref start.Orientation, ref end.Orientation, amount, out result.Orientation); Vector3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale); return result; } /// /// Performs a linear interpolation between two transformations. /// /// Start transformation. /// End transformation. /// Value between 0 and 1 indicating the weight of . /// When the method completes, contains the linear interpolation of the two transformations. /// Passing a value of 0 will cause to be returned; a value of 1 will cause to be returned. public static void Lerp(ref Transform start, ref Transform end, float amount, out Transform result) { Vector3.Lerp(ref start.Translation, ref end.Translation, amount, out result.Translation); Quaternion.Slerp(ref start.Orientation, ref end.Orientation, amount, out result.Orientation); Vector3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale); } /// /// Tests for equality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has the same value as ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Transform left, Transform right) { return left.Equals(ref right); } /// /// Tests for inequality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has a different value than ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Transform left, Transform right) { return !left.Equals(ref right); } /// /// Adds two transformations. /// /// The first transform to add. /// The second transform to add. /// The sum of the two transformations. public static Transform operator +(Transform left, Transform right) { return Add(left, right); } /// /// Subtracts two transformations. /// /// The first transform to subtract from. /// The second transform to subtract. /// The difference of the two transformations. public static Transform operator -(Transform left, Transform right) { return Subtract(left, right); } /// /// Returns a that represents this instance. /// /// A that represents this instance. public override string ToString() { return string.Format(CultureInfo.CurrentCulture, _formatString, Translation, Orientation, Scale); } /// /// Returns a that represents this instance. /// /// The format. /// A that represents this instance. public string ToString(string format) { if (format == null) return ToString(); return string.Format(CultureInfo.CurrentCulture, _formatString, Translation.ToString(format, CultureInfo.CurrentCulture), Orientation.ToString(format, CultureInfo.CurrentCulture), Scale.ToString(format, CultureInfo.CurrentCulture)); } /// /// Returns a that represents this instance. /// /// The format provider. /// A that represents this instance. public string ToString(IFormatProvider formatProvider) { return string.Format(formatProvider, _formatString, Translation, Orientation, Scale); } /// /// Returns a that represents this instance. /// /// The format. /// The format provider. /// A that represents this instance. public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return ToString(formatProvider); return string.Format(formatProvider, _formatString, Translation.ToString(format, formatProvider), Orientation.ToString(format, formatProvider), Scale.ToString(format, formatProvider)); } /// /// Returns a hash code for this instance. /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() { unchecked { int hashCode = Translation.GetHashCode(); hashCode = (hashCode * 397) ^ Orientation.GetHashCode(); hashCode = (hashCode * 397) ^ Scale.GetHashCode(); return hashCode; } } /// /// Tests whether one transform is near another transform. /// /// The left transform. /// The right transform. /// The epsilon. /// true if left and right are near another, false otherwise public static bool NearEqual(Transform left, Transform right, float epsilon = Mathf.Epsilon) { return NearEqual(ref left, ref right, epsilon); } /// /// Tests whether one transform is near another transform. /// /// The left transform. /// The right transform. /// The epsilon. /// true if left and right are near another, false otherwise public static bool NearEqual(ref Transform left, ref Transform right, float epsilon = Mathf.Epsilon) { return Vector3.NearEqual(ref left.Translation, ref right.Translation, epsilon) && Quaternion.NearEqual(ref left.Orientation, ref right.Orientation, epsilon) && Vector3.NearEqual(ref left.Scale, ref right.Scale, epsilon); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Transform other) { return Translation == other.Translation && Orientation == other.Orientation && Scale == other.Scale; } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Transform other) { return Equals(ref other); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { if (!(value is Transform)) return false; var strongValue = (Transform)value; return Equals(ref strongValue); } } }