/* * Copyright (c) 2006-2008, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.org nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using OpenMetaverse.StructuredData; namespace OpenMetaverse { /////////////////////////////////////////////////////////////////////////// // // The 3D math makes several assumptions to maintain consistency across // types. The math system is right-handed and the matrices are row-order. // Z axis is up. // // X = At = Roll = Bank // Y = Left = Pitch = Attitude // Z = Up = Yaw = Heading // /////////////////////////////////////////////////////////////////////////// /// /// A 128-bit Universally Unique Identifier, used throughout the Second /// Life networking protocol /// [Serializable] public struct UUID : IComparable { /// The System.Guid object this struct wraps around public Guid Guid; #region Constructors /// /// Constructor that takes a string UUID representation /// /// A string representation of a UUID, case /// insensitive and can either be hyphenated or non-hyphenated /// UUID("11f8aa9c-b071-4242-836b-13b7abe0d489") public UUID(string val) { if (String.IsNullOrEmpty(val)) Guid = new Guid(); else Guid = new Guid(val); } /// /// Constructor that takes a System.Guid object /// /// A Guid object that contains the unique identifier /// to be represented by this UUID public UUID(Guid val) { Guid = val; } /// /// Constructor that takes a byte array containing a UUID /// /// Byte array containing a 16 byte UUID /// Beginning offset in the array public UUID(byte[] source, int pos) { Guid = UUID.Zero.Guid; FromBytes(source, pos); } /// /// Constructor that takes an unsigned 64-bit unsigned integer to /// convert to a UUID /// /// 64-bit unsigned integer to convert to a UUID public UUID(ulong val) { Guid = new Guid(0, 0, 0, BitConverter.GetBytes(val)); } /// /// Copy constructor /// /// UUID to copy public UUID(UUID val) { Guid = val.Guid; } #endregion Constructors #region Public Methods /// /// IComparable.CompareTo implementation /// public int CompareTo(UUID id) { return Guid.CompareTo(id.Guid); } /// /// /// /// /// public void FromBytes(byte[] source, int pos) { Guid = new Guid( (source[pos + 0] << 24) | (source[pos + 1] << 16) | (source[pos + 2] << 8) | source[pos + 3], (short)((source[pos + 4] << 8) | source[pos + 5]), (short)((source[pos + 6] << 8) | source[pos + 7]), source[pos + 8], source[pos + 9], source[pos + 10], source[pos + 11], source[pos + 12], source[pos + 13], source[pos + 14], source[pos + 15]); } /// /// Returns a copy of the raw bytes for this UUID /// /// A 16 byte array containing this UUID public byte[] GetBytes() { byte[] bytes = Guid.ToByteArray(); byte[] output = new byte[16]; output[0] = bytes[3]; output[1] = bytes[2]; output[2] = bytes[1]; output[3] = bytes[0]; output[4] = bytes[5]; output[5] = bytes[4]; output[6] = bytes[7]; output[7] = bytes[6]; Buffer.BlockCopy(bytes, 8, output, 8, 8); return output; } /// /// Calculate an LLCRC (cyclic redundancy check) for this UUID /// /// The CRC checksum for this UUID public uint CRC() { uint retval = 0; byte[] bytes = GetBytes(); retval += (uint)((bytes[ 3] << 24) + (bytes[ 2] << 16) + (bytes[ 1] << 8) + bytes[ 0]); retval += (uint)((bytes[ 7] << 24) + (bytes[ 6] << 16) + (bytes[ 5] << 8) + bytes[ 4]); retval += (uint)((bytes[11] << 24) + (bytes[10] << 16) + (bytes[ 9] << 8) + bytes[ 8]); retval += (uint)((bytes[15] << 24) + (bytes[14] << 16) + (bytes[13] << 8) + bytes[12]); return retval; } /// /// Create a 64-bit integer representation of the first half of this UUID /// /// An integer created from the first eight bytes of this UUID public ulong GetULong() { return Helpers.BytesToUInt64(Guid.ToByteArray()); } #endregion Public Methods #region Static Methods /// /// Generate a UUID from a string /// /// A string representation of a UUID, case /// insensitive and can either be hyphenated or non-hyphenated /// UUID.Parse("11f8aa9c-b071-4242-836b-13b7abe0d489") public static UUID Parse(string val) { return new UUID(val); } /// /// Generate a UUID from a string /// /// A string representation of a UUID, case /// insensitive and can either be hyphenated or non-hyphenated /// Will contain the parsed UUID if successful, /// otherwise null /// True if the string was successfully parse, otherwise false /// UUID.TryParse("11f8aa9c-b071-4242-836b-13b7abe0d489", result) public static bool TryParse(string val, out UUID result) { try { result = Parse(val); return true; } catch (Exception) { result = UUID.Zero; return false; } } /// /// Combine two UUIDs together by taking the MD5 hash of a byte array /// containing both UUIDs /// /// First UUID to combine /// Second UUID to combine /// The UUID product of the combination public static UUID Combine(UUID first, UUID second) { // Construct the buffer that MD5ed byte[] input = new byte[32]; Buffer.BlockCopy(first.GetBytes(), 0, input, 0, 16); Buffer.BlockCopy(second.GetBytes(), 0, input, 16, 16); return new UUID(Helpers.MD5Builder.ComputeHash(input), 0); } /// /// /// /// public static UUID Random() { return new UUID(Guid.NewGuid()); } #endregion Static Methods #region Overrides /// /// Return a hash code for this UUID, used by .NET for hash tables /// /// An integer composed of all the UUID bytes XORed together public override int GetHashCode() { return Guid.GetHashCode(); } /// /// Comparison function /// /// An object to compare to this UUID /// False if the object is not an UUID, true if it is and /// byte for byte identical to this public override bool Equals(object o) { if (!(o is UUID)) return false; UUID uuid = (UUID)o; return Guid == uuid.Guid; } /// /// Get a hyphenated string representation of this UUID /// /// A string representation of this UUID, lowercase and /// with hyphens /// 11f8aa9c-b071-4242-836b-13b7abe0d489 public override string ToString() { return Guid.ToString(); } #endregion Overrides #region Operators /// /// Equals operator /// /// First UUID for comparison /// Second UUID for comparison /// True if the UUIDs are byte for byte equal, otherwise false public static bool operator==(UUID lhs, UUID rhs) { return lhs.Guid == rhs.Guid; } /// /// Not equals operator /// /// First UUID for comparison /// Second UUID for comparison /// True if the UUIDs are not equal, otherwise true public static bool operator!=(UUID lhs, UUID rhs) { return !(lhs == rhs); } /// /// XOR operator /// /// First UUID /// Second UUID /// A UUID that is a XOR combination of the two input UUIDs public static UUID operator ^(UUID lhs, UUID rhs) { byte[] lhsbytes = lhs.GetBytes(); byte[] rhsbytes = rhs.GetBytes(); byte[] output = new byte[16]; for (int i = 0; i < 16; i++) { output[i] = (byte)(lhsbytes[i] ^ rhsbytes[i]); } return new UUID(output, 0); } /// /// String typecasting operator /// /// A UUID in string form. Case insensitive, /// hyphenated or non-hyphenated /// A UUID built from the string representation public static implicit operator UUID(string val) { return new UUID(val); } #endregion Operators /// An UUID with a value of all zeroes public static readonly UUID Zero = new UUID(); } /// /// A two-dimensional vector with floating-point values /// [Serializable] public struct Vector2 : IComparable { /// X value public float X; /// Y value public float Y; // Used for little to big endian conversion on big endian architectures private byte[] conversionBuffer; #region Constructors /// /// Constructor, builds a vector for individual float values /// /// X value /// Y value public Vector2(float x, float y) { conversionBuffer = null; X = x; Y = y; } /// /// Copy constructor /// /// Vector to copy public Vector2(Vector2 vector) { conversionBuffer = null; X = vector.X; Y = vector.Y; } #endregion Constructors #region Public Methods /// /// Test if this vector is equal to another vector, within a given /// tolerance range /// /// Vector to test against /// The acceptable magnitude of difference /// between the two vectors /// True if the magnitude of difference between the two vectors /// is less than the given tolerance, otherwise false public bool ApproxEquals(Vector2 vec, float tolerance) { Vector2 diff = this - vec; float d = Vector2.Mag(diff); return (d <= tolerance); } /// /// IComparable.CompareTo implementation /// public int CompareTo(Vector2 vector) { float thisMag = X * X + Y * Y; float thatMag = vector.X * vector.X + vector.Y * vector.Y; return thisMag.CompareTo(thatMag); } /// /// Builds a vector from a byte array /// /// Byte array containing two four-byte floats /// Beginning position in the byte array public void FromBytes(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[8]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 8); Array.Reverse(conversionBuffer, 0, 4); Array.Reverse(conversionBuffer, 4, 4); X = BitConverter.ToSingle(conversionBuffer, 0); Y = BitConverter.ToSingle(conversionBuffer, 4); } else { // Little endian architecture X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); } } /// /// Returns the raw bytes for this vector /// /// An eight-byte array containing X and Y public byte[] GetBytes() { byte[] byteArray = new byte[8]; Buffer.BlockCopy(BitConverter.GetBytes(X), 0, byteArray, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(Y), 0, byteArray, 4, 4); if (!BitConverter.IsLittleEndian) { Array.Reverse(byteArray, 0, 4); Array.Reverse(byteArray, 4, 4); } return byteArray; } #endregion Public Methods #region Static Methods /// /// Calculate the magnitude of the supplied vector /// public static float Mag(Vector2 v) { return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y); } /// /// Calculate the squared magnitude of the supplied vector /// /// /// public static float MagSquared(Vector2 v) { return v.X * v.X + v.Y * v.Y; } #endregion Static Methods #region Overrides /// /// A hash of the vector, used by .NET for hash tables /// /// The hashes of the individual components XORed together public override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is Vector2)) return false; Vector2 vector = (Vector2)o; return (X == vector.X && Y == vector.Y); } /// /// Get a formatted string representation of the vector /// /// A string representation of the vector public override string ToString() { return String.Format(Helpers.EnUsCulture, "<{0}, {1}>", X, Y); } #endregion Overrides #region Operators public static bool operator ==(Vector2 lhs, Vector2 rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y); } public static bool operator !=(Vector2 lhs, Vector2 rhs) { return !(lhs == rhs); } public static Vector2 operator +(Vector2 lhs, Vector2 rhs) { return new Vector2(lhs.X + rhs.X, lhs.Y + rhs.Y); } public static Vector2 operator -(Vector2 vec) { return new Vector2(-vec.X, -vec.Y); } public static Vector2 operator -(Vector2 lhs, Vector2 rhs) { return new Vector2(lhs.X - rhs.X, lhs.Y - rhs.Y); } public static Vector2 operator *(Vector2 vec, float val) { return new Vector2(vec.X * val, vec.Y * val); } public static Vector2 operator *(float val, Vector2 vec) { return new Vector2(vec.X * val, vec.Y * val); } public static Vector2 operator *(Vector2 lhs, Vector2 rhs) { return new Vector2(lhs.X * rhs.X, lhs.Y * rhs.Y); } public static Vector2 operator /(Vector2 lhs, Vector2 rhs) { return new Vector2(lhs.X / rhs.X, lhs.Y / rhs.Y); } public static Vector2 operator /(Vector2 vec, float val) { return new Vector2(vec.X / val, vec.Y / val); } #endregion Operators /// An Vector2 with a value of 0,0,0 public readonly static Vector2 Zero = new Vector2(); } /// /// A three-dimensional vector with floating-point values /// [Serializable] public struct Vector3 : IComparable { /// X value public float X; /// Y value public float Y; /// Z value public float Z; // Used for little to big endian conversion on big endian architectures private byte[] conversionBuffer; #region Constructors /// /// Constructor, builds a vector from a byte array /// /// Byte array containing three four-byte floats /// Beginning position in the byte array public Vector3(byte[] byteArray, int pos) { conversionBuffer = null; X = Y = Z = 0; FromBytes(byteArray, pos); } /// /// Constructor, builds a vector for individual float values /// /// X value /// Y value /// Z value public Vector3(float x, float y, float z) { conversionBuffer = null; X = x; Y = y; Z = z; } /// /// Copy constructor /// /// Vector to copy public Vector3(Vector3 vector) { conversionBuffer = null; X = (float)vector.X; Y = (float)vector.Y; Z = (float)vector.Z; } #endregion Constructors #region Public Methods /// /// Test if this vector is equal to another vector, within a given /// tolerance range /// /// Vector to test against /// The acceptable magnitude of difference /// between the two vectors /// True if the magnitude of difference between the two vectors /// is less than the given tolerance, otherwise false public bool ApproxEquals(Vector3 vec, float tolerance) { Vector3 diff = this - vec; float d = Vector3.Mag(diff); return (d <= tolerance); } /// /// IComparable.CompareTo implementation /// public int CompareTo(Vector3 vector) { float thisMag = X * X + Y * Y + Z * Z; float thatMag = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; return thisMag.CompareTo(thatMag); } /// /// Test if this vector is composed of all finite numbers /// public bool IsFinite() { if (Helpers.IsFinite(X) && Helpers.IsFinite(Y) && Helpers.IsFinite(Z)) return true; else return false; } /// /// Builds a vector from a byte array /// /// Byte array containing a 12 byte vector /// Beginning position in the byte array public void FromBytes(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[12]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 12); Array.Reverse(conversionBuffer, 0, 4); Array.Reverse(conversionBuffer, 4, 4); Array.Reverse(conversionBuffer, 8, 4); X = BitConverter.ToSingle(conversionBuffer, 0); Y = BitConverter.ToSingle(conversionBuffer, 4); Z = BitConverter.ToSingle(conversionBuffer, 8); } else { // Little endian architecture X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); Z = BitConverter.ToSingle(byteArray, pos + 8); } } /// /// Returns the raw bytes for this vector /// /// A 12 byte array containing X, Y, and Z public byte[] GetBytes() { byte[] byteArray = new byte[12]; Buffer.BlockCopy(BitConverter.GetBytes(X), 0, byteArray, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(Y), 0, byteArray, 4, 4); Buffer.BlockCopy(BitConverter.GetBytes(Z), 0, byteArray, 8, 4); if(!BitConverter.IsLittleEndian) { Array.Reverse(byteArray, 0, 4); Array.Reverse(byteArray, 4, 4); Array.Reverse(byteArray, 8, 4); } return byteArray; } public LLSD ToLLSD() { LLSDArray array = new LLSDArray(); array.Add(LLSD.FromReal(X)); array.Add(LLSD.FromReal(Y)); array.Add(LLSD.FromReal(Z)); return array; } public void FromLLSD(LLSD llsd) { if (llsd.Type == LLSDType.Array) { LLSDArray array = (LLSDArray)llsd; if (array.Count == 3) { X = (float)array[0].AsReal(); Y = (float)array[1].AsReal(); Z = (float)array[2].AsReal(); return; } } this = Vector3.Zero; } #endregion Public Methods #region Static Methods /// /// Calculate the magnitude of the supplied vector /// public static float Mag(Vector3 v) { return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z); } /// /// Calculate the squared magnitude of the supplied vector /// /// /// public static float MagSquared(Vector3 v) { return v.X * v.X + v.Y * v.Y + v.Z * v.Z; } /// /// Returns a normalized version of the supplied vector /// /// The vector to normalize /// A normalized version of the vector public static Vector3 Norm(Vector3 vector) { float mag = Mag(vector); return new Vector3(vector.X / mag, vector.Y / mag, vector.Z / mag); } /// /// Return the cross product of two vectors /// /// First vector /// Second vector /// Cross product of first and second vector public static Vector3 Cross(Vector3 v1, Vector3 v2) { return new Vector3 ( v1.Y * v2.Z - v1.Z * v2.Y, v1.Z * v2.X - v1.X * v2.Z, v1.X * v2.Y - v1.Y * v2.X ); } /// /// Returns the dot product of two vectors /// public static float Dot(Vector3 v1, Vector3 v2) { return (v1.X * v2.X) + (v1.Y * v2.Y) + (v1.Z * v2.Z); } /// /// Calculates the distance between two vectors /// public static float Dist(Vector3 pointA, Vector3 pointB) { float xd = pointB.X - pointA.X; float yd = pointB.Y - pointA.Y; float zd = pointB.Z - pointA.Z; return (float)Math.Sqrt(xd * xd + yd * yd + zd * zd); } public static Vector3 Rot(Vector3 vector, Quaternion rotation) { return vector * rotation; } public static Vector3 Rot(Vector3 vector, Matrix3 rotation) { return vector * rotation; } /// /// Calculate the rotation between two vectors /// /// Directional vector, such as 1,0,0 for the forward face /// Target vector - normalize first with VecNorm public static Quaternion RotBetween(Vector3 a, Vector3 b) { //A and B should both be normalized float dotProduct = Dot(a, b); Vector3 crossProduct = Cross(a, b); float magProduct = Mag(a) * Mag(b); double angle = Math.Acos(dotProduct / magProduct); Vector3 axis = Norm(crossProduct); float s = (float)Math.Sin(angle / 2); return new Quaternion( axis.X * s, axis.Y * s, axis.Z * s, (float)Math.Cos(angle / 2)); } public static Vector3 Transform(Vector3 vector, Matrix3 matrix) { // Operates "from the right" on row vector return new Vector3( vector.X * matrix.M11 + vector.Y * matrix.M21 + vector.Z * matrix.M31, vector.X * matrix.M12 + vector.Y * matrix.M22 + vector.Z * matrix.M32, vector.X * matrix.M13 + vector.Y * matrix.M23 + vector.Z * matrix.M33 ); } /// /// Generate an Vector3 from a string /// /// A string representation of a 3D vector, enclosed /// in arrow brackets and separated by commas public static Vector3 Parse(string val) { char[] splitChar = { ',' }; string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(splitChar); return new Vector3( float.Parse(split[0].Trim(), Helpers.EnUsCulture), float.Parse(split[1].Trim(), Helpers.EnUsCulture), float.Parse(split[2].Trim(), Helpers.EnUsCulture)); } public static bool TryParse(string val, out Vector3 result) { try { result = Parse(val); return true; } catch (Exception) { result = new Vector3(); return false; } } #endregion Static Methods #region Overrides /// /// A hash of the vector, used by .NET for hash tables /// /// The hashes of the individual components XORed together public override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is Vector3)) return false; Vector3 vector = (Vector3)o; return (X == vector.X && Y == vector.Y && Z == vector.Z); } /// /// Get a formatted string representation of the vector /// /// A string representation of the vector public override string ToString() { return String.Format(Helpers.EnUsCulture, "<{0}, {1}, {2}>", X, Y, Z); } #endregion Overrides #region Operators public static bool operator==(Vector3 lhs, Vector3 rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z); } public static bool operator!=(Vector3 lhs, Vector3 rhs) { return !(lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z); } public static Vector3 operator +(Vector3 lhs, Vector3 rhs) { return new Vector3(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z); } public static Vector3 operator -(Vector3 vec) { return new Vector3(-vec.X, -vec.Y, -vec.Z); } public static Vector3 operator -(Vector3 lhs, Vector3 rhs) { return new Vector3(lhs.X - rhs.X,lhs.Y - rhs.Y, lhs.Z - rhs.Z); } public static Vector3 operator *(Vector3 vec, float val) { return new Vector3(vec.X * val, vec.Y * val, vec.Z * val); } public static Vector3 operator *(float val, Vector3 vec) { return new Vector3(vec.X * val, vec.Y * val, vec.Z * val); } public static Vector3 operator *(Vector3 lhs, Vector3 rhs) { return new Vector3(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z); } public static Vector3 operator *(Vector3 vec, Quaternion rot) { float rw = -rot.X * vec.X - rot.Y * vec.Y - rot.Z * vec.Z; float rx = rot.W * vec.X + rot.Y * vec.Z - rot.Z * vec.Y; float ry = rot.W * vec.Y + rot.Z * vec.X - rot.X * vec.Z; float rz = rot.W * vec.Z + rot.X * vec.Y - rot.Y * vec.X; float nx = -rw * rot.X + rx * rot.W - ry * rot.Z + rz * rot.Y; float ny = -rw * rot.Y + ry * rot.W - rz * rot.X + rx * rot.Z; float nz = -rw * rot.Z + rz * rot.W - rx * rot.Y + ry * rot.X; return new Vector3(nx, ny, nz); } public static Vector3 operator *(Vector3 vector, Matrix3 matrix) { return Vector3.Transform(vector, matrix); } public static Vector3 operator /(Vector3 lhs, Vector3 rhs) { return new Vector3(lhs.X / rhs.X, lhs.Y / rhs.Y, lhs.Z / rhs.Z); } public static Vector3 operator /(Vector3 vec, float val) { return new Vector3(vec.X / val, vec.Y / val, vec.Z / val); } public static Vector3 operator %(Vector3 lhs, Vector3 rhs) { return new Vector3( lhs.Y * rhs.Z - rhs.Y * lhs.Z, lhs.Z * rhs.X - rhs.Z * lhs.X, lhs.X * rhs.Y - rhs.X * lhs.Y); } #endregion Operators /// An Vector3 with a value of 0,0,0 public readonly static Vector3 Zero = new Vector3(); /// A unit vector facing up (Z axis) public readonly static Vector3 Up = new Vector3(0f, 0f, 1f); /// A unit vector facing forward (X axis) public readonly static Vector3 Fwd = new Vector3(1f, 0f, 0f); /// A unit vector facing left (Y axis) public readonly static Vector3 Left = new Vector3(0f, 1f, 0f); } /// /// A double-precision three-dimensional vector /// [Serializable] public struct Vector3d : IComparable { /// X value public double X; /// Y value public double Y; /// Z value public double Z; // Used for little to big endian conversion on big endian architectures private byte[] conversionBuffer; #region Constructors /// /// /// /// /// /// public Vector3d(double x, double y, double z) { conversionBuffer = null; X = x; Y = y; Z = z; } /// /// Create a double precision vector from a float vector /// /// public Vector3d(Vector3 llv3) { conversionBuffer = null; X = llv3.X; Y = llv3.Y; Z = llv3.Z; } /// /// /// /// /// public Vector3d(byte[] byteArray, int pos) { conversionBuffer = null; X = Y = Z = 0; FromBytes(byteArray, pos); } /// /// Copy constructor /// /// Vector to copy public Vector3d(Vector3d vector) { conversionBuffer = null; X = vector.X; Y = vector.Y; Z = vector.Z; } #endregion Constructors #region Public Methods /// /// Test if this vector is equal to another vector, within a given /// tolerance range /// /// Vector to test against /// The acceptable magnitude of difference /// between the two vectors /// True if the magnitude of difference between the two vectors /// is less than the given tolerance, otherwise false public bool ApproxEquals(Vector3d vec, double tolerance) { Vector3d diff = this - vec; double d = Vector3d.Mag(diff); return (d <= tolerance); } /// /// IComparable.CompareTo implementation /// public int CompareTo(Vector3d vector) { double thisMag = X * X + Y * Y + Z * Z; double thatMag = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; return thisMag.CompareTo(thatMag); } /// /// /// /// /// public void FromBytes(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[24]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 24); Array.Reverse(conversionBuffer, 0, 8); Array.Reverse(conversionBuffer, 8, 8); Array.Reverse(conversionBuffer, 16, 8); X = BitConverter.ToDouble(conversionBuffer, 0); Y = BitConverter.ToDouble(conversionBuffer, 8); Z = BitConverter.ToDouble(conversionBuffer, 16); } else { // Little endian architecture X = BitConverter.ToDouble(byteArray, pos); Y = BitConverter.ToDouble(byteArray, pos + 8); Z = BitConverter.ToDouble(byteArray, pos + 16); } } /// /// /// /// public byte[] GetBytes() { byte[] byteArray = new byte[24]; Buffer.BlockCopy(BitConverter.GetBytes(X), 0, byteArray, 0, 8); Buffer.BlockCopy(BitConverter.GetBytes(Y), 0, byteArray, 8, 8); Buffer.BlockCopy(BitConverter.GetBytes(Z), 0, byteArray, 16, 8); if (!BitConverter.IsLittleEndian) { Array.Reverse(byteArray, 0, 8); Array.Reverse(byteArray, 8, 8); Array.Reverse(byteArray, 16, 8); } return byteArray; } public LLSD ToLLSD() { LLSDArray array = new LLSDArray(); array.Add(LLSD.FromReal(X)); array.Add(LLSD.FromReal(Y)); array.Add(LLSD.FromReal(Z)); return array; } public void FromLLSD(LLSD llsd) { if (llsd.Type == LLSDType.Array) { LLSDArray array = (LLSDArray)llsd; if (array.Count == 3) { X = array[0].AsReal(); Y = array[1].AsReal(); Z = array[2].AsReal(); return; } } this = Vector3d.Zero; } #endregion Public Methods #region Static Methods /// /// Calculate the magnitude of the supplied vector /// public static double Mag(Vector3d v) { return Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z); } /// /// Calculate the squared magnitude of the supplied vector /// /// /// public static double MagSquared(Vector3d v) { return v.X * v.X + v.Y * v.Y + v.Z * v.Z; } /// /// Calculates the distance between two vectors /// public static double Dist(Vector3d pointA, Vector3d pointB) { double xd = pointB.X - pointA.X; double yd = pointB.Y - pointA.Y; double zd = pointB.Z - pointA.Z; return Math.Sqrt(xd * xd + yd * yd + zd * zd); } /// /// Generate an Vector3d from a string /// /// A string representation of a 3D vector, enclosed /// in arrow brackets and separated by commas public static Vector3d Parse(string val) { char[] splitChar = { ',' }; string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(splitChar); return new Vector3d( double.Parse(split[0].Trim(), Helpers.EnUsCulture), double.Parse(split[1].Trim(), Helpers.EnUsCulture), double.Parse(split[2].Trim(), Helpers.EnUsCulture)); } public static bool TryParse(string val, out Vector3d result) { try { result = Parse(val); return true; } catch (Exception) { result = new Vector3d(); return false; } } #endregion Static Methods #region Overrides /// /// A hash of the vector, used by .NET for hash tables /// /// The hashes of the individual components XORed together public override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is Vector3d)) return false; Vector3d vector = (Vector3d)o; return (X == vector.X && Y == vector.Y && Z == vector.Z); } /// /// /// /// public override string ToString() { return String.Format("<{0}, {1}, {2}>", X, Y, Z); } #endregion Overrides #region Operators public static bool operator ==(Vector3d lhs, Vector3d rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z); } public static bool operator !=(Vector3d lhs, Vector3d rhs) { return !(lhs == rhs); } public static Vector3d operator +(Vector3d lhs, Vector3d rhs) { return new Vector3d(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z); } public static Vector3d operator -(Vector3d vec) { return new Vector3d(-vec.X, -vec.Y, -vec.Z); } public static Vector3d operator -(Vector3d lhs, Vector3d rhs) { return new Vector3d(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z); } public static Vector3d operator *(Vector3d vec, float val) { return new Vector3d(vec.X * val, vec.Y * val, vec.Z * val); } public static Vector3d operator *(double val, Vector3d vec) { return new Vector3d(vec.X * val, vec.Y * val, vec.Z * val); } public static Vector3d operator *(Vector3d lhs, Vector3d rhs) { return new Vector3d(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z); } public static Vector3d operator /(Vector3d lhs, Vector3d rhs) { return new Vector3d(lhs.X / rhs.X, lhs.Y / rhs.Y, lhs.Z / rhs.Z); } public static Vector3d operator /(Vector3d vec, double val) { return new Vector3d(vec.X / val, vec.Y / val, vec.Z / val); } public static Vector3d operator %(Vector3d lhs, Vector3d rhs) { return new Vector3d( lhs.Y * rhs.Z - rhs.Y * lhs.Z, lhs.Z * rhs.X - rhs.Z * lhs.X, lhs.X * rhs.Y - rhs.X * lhs.Y); } #endregion Operators /// An Vector3d with a value of 0,0,0 public static readonly Vector3d Zero = new Vector3d(); } /// /// A four-dimensional vector /// [Serializable] public struct Vector4 : IComparable { /// public float X; /// public float Y; /// public float Z; /// public float S; // Used for little to big endian conversion on big endian architectures private byte[] conversionBuffer; #region Constructors /// /// Constructor, sets the vector members according to parameters /// /// X value /// Y value /// Z value /// S value public Vector4(float x, float y, float z, float s) { conversionBuffer = null; X = x; Y = y; Z = z; S = s; } /// /// /// /// /// public Vector4(byte[] byteArray, int pos) { conversionBuffer = null; X = Y = Z = S = 0; FromBytes(byteArray, pos); } /// /// Copy constructor /// /// Vector to copy public Vector4(Vector4 vector) { conversionBuffer = null; X = vector.X; Y = vector.Y; Z = vector.Z; S = vector.S; } #endregion Constructors #region Public Methods /// /// Test if this vector is equal to another vector, within a given /// tolerance range /// /// Vector to test against /// The acceptable magnitude of difference /// between the two vectors /// True if the magnitude of difference between the two vectors /// is less than the given tolerance, otherwise false public bool ApproxEquals(Vector4 vec, float tolerance) { Vector4 diff = this - vec; float d = Vector4.Mag(diff); return (d <= tolerance); } /// /// IComparable.CompareTo implementation /// public int CompareTo(Vector4 vector) { float thisMag = X * X + Y * Y + Z * Z + S * S; float thatMag = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z + vector.S * vector.S; return thisMag.CompareTo(thatMag); } /// /// /// /// /// public void FromBytes(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[16]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 16); Array.Reverse(conversionBuffer, 0, 4); Array.Reverse(conversionBuffer, 4, 4); Array.Reverse(conversionBuffer, 8, 4); Array.Reverse(conversionBuffer, 12, 4); X = BitConverter.ToSingle(conversionBuffer, 0); Y = BitConverter.ToSingle(conversionBuffer, 4); Z = BitConverter.ToSingle(conversionBuffer, 8); S = BitConverter.ToSingle(conversionBuffer, 12); } else { // Little endian architecture X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); Z = BitConverter.ToSingle(byteArray, pos + 8); S = BitConverter.ToSingle(byteArray, pos + 12); } } /// /// /// /// public byte[] GetBytes() { byte[] byteArray = new byte[16]; Buffer.BlockCopy(BitConverter.GetBytes(X), 0, byteArray, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(Y), 0, byteArray, 4, 4); Buffer.BlockCopy(BitConverter.GetBytes(Z), 0, byteArray, 8, 4); Buffer.BlockCopy(BitConverter.GetBytes(S), 0, byteArray, 12, 4); if(!BitConverter.IsLittleEndian) { Array.Reverse(byteArray, 0, 4); Array.Reverse(byteArray, 4, 4); Array.Reverse(byteArray, 8, 4); Array.Reverse(byteArray, 12, 4); } return byteArray; } public LLSD ToLLSD() { LLSDArray array = new LLSDArray(); array.Add(LLSD.FromReal(X)); array.Add(LLSD.FromReal(Y)); array.Add(LLSD.FromReal(Z)); array.Add(LLSD.FromReal(S)); return array; } public void FromLLSD(LLSD llsd) { if (llsd.Type == LLSDType.Array) { LLSDArray array = (LLSDArray)llsd; if (array.Count == 4) { X = (float)array[0].AsReal(); Y = (float)array[1].AsReal(); Z = (float)array[2].AsReal(); S = (float)array[3].AsReal(); return; } } this = Vector4.Zero; } #endregion Public Methods #region Static Methods /// /// Calculate the magnitude of the supplied vector /// public static float Mag(Vector4 v) { return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z + v.S * v.S); } /// /// Calculate the squared magnitude of the supplied vector /// /// /// public static float MagSquared(Vector4 v) { return v.X * v.X + v.Y * v.Y + v.Z * v.Z + v.S * v.S; } public static Vector4 Parse(string val) { char[] splitChar = { ',' }; string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(splitChar); return new Vector4( float.Parse(split[0].Trim(), Helpers.EnUsCulture), float.Parse(split[1].Trim(), Helpers.EnUsCulture), float.Parse(split[2].Trim(), Helpers.EnUsCulture), float.Parse(split[3].Trim(), Helpers.EnUsCulture)); } public static bool TryParse(string val, out Vector4 result) { try { result = Parse(val); return true; } catch (Exception) { result = new Vector4(); return false; } } #endregion Static Methods #region Overrides /// /// A hash of the vector, used by .NET for hash tables /// /// The hashes of the individual components XORed together public override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^ S.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is Vector4)) return false; Vector4 vector = (Vector4)o; return (X == vector.X && Y == vector.Y && Z == vector.Z && S == vector.S); } /// /// /// /// public override string ToString() { return String.Format("<{0}, {1}, {2}, {3}>", X, Y, Z, S); } #endregion Overrides #region Operators public static bool operator ==(Vector4 lhs, Vector4 rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z && lhs.S == rhs.S); } public static bool operator !=(Vector4 lhs, Vector4 rhs) { return !(lhs == rhs); } public static Vector4 operator +(Vector4 lhs, Vector4 rhs) { return new Vector4(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z, lhs.S + rhs.S); } public static Vector4 operator -(Vector4 vec) { return new Vector4(-vec.X, -vec.Y, -vec.Z, -vec.S); } public static Vector4 operator -(Vector4 lhs, Vector4 rhs) { return new Vector4(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z, lhs.S - rhs.S); } public static Vector4 operator *(Vector4 vec, float val) { return new Vector4(vec.X * val, vec.Y * val, vec.Z * val, vec.S * val); } public static Vector4 operator *(float val, Vector4 vec) { return new Vector4(vec.X * val, vec.Y * val, vec.Z * val, vec.S * val); } public static Vector4 operator *(Vector4 lhs, Vector4 rhs) { return new Vector4(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z, lhs.S * rhs.S); } public static Vector4 operator /(Vector4 lhs, Vector4 rhs) { return new Vector4(lhs.X / rhs.X, lhs.Y / rhs.Y, lhs.Z / rhs.Z, lhs.S / rhs.S); } public static Vector4 operator /(Vector4 vec, float val) { return new Vector4(vec.X / val, vec.Y / val, vec.Z / val, vec.S / val); } #endregion Operators /// An Vector4 with a value of 0,0,0,0 public readonly static Vector4 Zero = new Vector4(); } /// /// An 8-bit color structure including an alpha channel /// [Serializable] public struct Color4 : IComparable { /// Red public float R; /// Green public float G; /// Blue public float B; /// Alpha public float A; #region Constructors /// /// /// /// /// /// /// public Color4(byte r, byte g, byte b, byte a) { const float quanta = 1.0f / 255.0f; R = (float)r * quanta; G = (float)g * quanta; B = (float)b * quanta; A = (float)a * quanta; } public Color4(float r, float g, float b, float a) { if (r > 1f || g > 1f || b > 1f || a > 1f) Logger.Log( String.Format("Attempting to initialize Color4 with out of range values <{0},{1},{2},{3}>", r, g, b, a), Helpers.LogLevel.Warning); // Valid range is from 0.0 to 1.0 R = Helpers.Clamp(r, 0f, 1f); G = Helpers.Clamp(g, 0f, 1f); B = Helpers.Clamp(b, 0f, 1f); A = Helpers.Clamp(a, 0f, 1f); } public Color4(byte[] byteArray, int pos, bool inverted) { R = G = B = A = 0f; FromBytes(byteArray, pos, inverted); } public Color4(byte[] byteArray, int pos, bool inverted, bool alphaInverted) { R = G = B = A = 0f; FromBytes(byteArray, pos, inverted, alphaInverted); } /// /// Copy constructor /// /// Color to copy public Color4(Color4 color) { R = color.R; G = color.G; B = color.B; A = color.A; } #endregion Constructors #region Public Methods /// /// IComparable.CompareTo implementation /// /// Sorting ends up like this: |--Greyscale--||--Color--|. /// Alpha is only used when the colors are otherwise equivalent public int CompareTo(Color4 color) { float thisHue = GetHue(); float thatHue = color.GetHue(); if (thisHue < 0f && thatHue < 0f) { // Both monochromatic if (R == color.R) { // Monochromatic and equal, compare alpha return A.CompareTo(color.A); } else { // Compare lightness return R.CompareTo(R); } } else { if (thisHue == thatHue) { // RGB is equal, compare alpha return A.CompareTo(color.A); } else { // Compare hues return thisHue.CompareTo(thatHue); } } } public void FromBytes(byte[] byteArray, int pos, bool inverted) { const float quanta = 1.0f / 255.0f; if (inverted) { R = (float)(255 - byteArray[pos]) * quanta; G = (float)(255 - byteArray[pos + 1]) * quanta; B = (float)(255 - byteArray[pos + 2]) * quanta; A = (float)(255 - byteArray[pos + 3]) * quanta; } else { R = (float)byteArray[pos] * quanta; G = (float)byteArray[pos + 1] * quanta; B = (float)byteArray[pos + 2] * quanta; A = (float)byteArray[pos + 3] * quanta; } } public void FromBytes(byte[] byteArray, int pos, bool inverted, bool alphaInverted) { FromBytes(byteArray, pos, inverted); if (alphaInverted) A = 1.0f - A; } public byte[] GetBytes() { return GetBytes(false); } /// /// /// /// public byte[] GetBytes(bool inverted) { byte[] byteArray = new byte[4]; byteArray[0] = Helpers.FloatToByte(R, 0f, 1f); byteArray[1] = Helpers.FloatToByte(G, 0f, 1f); byteArray[2] = Helpers.FloatToByte(B, 0f, 1f); byteArray[3] = Helpers.FloatToByte(A, 0f, 1f); if (inverted) { byteArray[0] = (byte)(255 - byteArray[0]); byteArray[1] = (byte)(255 - byteArray[1]); byteArray[2] = (byte)(255 - byteArray[2]); byteArray[3] = (byte)(255 - byteArray[3]); } return byteArray; } public byte[] GetFloatBytes() { byte[] bytes = new byte[16]; Buffer.BlockCopy(BitConverter.GetBytes(R), 0, bytes, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(G), 0, bytes, 4, 4); Buffer.BlockCopy(BitConverter.GetBytes(B), 0, bytes, 8, 4); Buffer.BlockCopy(BitConverter.GetBytes(A), 0, bytes, 12, 4); return bytes; } public float GetHue() { const float HUE_MAX = 360f; float max = Math.Max(Math.Max(R, G), B); float min = Math.Min(Math.Min(R, B), B); if (max == min) { // Achromatic, hue is undefined return -1f; } else { if (R == max) { float bDelta = (((max - B) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); float gDelta = (((max - G) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); return bDelta - gDelta; } else if (G == max) { float rDelta = (((max - R) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); float bDelta = (((max - B) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); return (HUE_MAX / 3f) + rDelta - bDelta; } else // B == max { float gDelta = (((max - G) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); float rDelta = (((max - R) * (HUE_MAX / 6f)) + ((max - min) / 2f)) / (max - min); return ((2f * HUE_MAX) / 3f) + gDelta - rDelta; } } } /// /// /// /// public LLSD ToLLSD() { LLSDArray array = new LLSDArray(); array.Add(LLSD.FromReal(R)); array.Add(LLSD.FromReal(G)); array.Add(LLSD.FromReal(B)); array.Add(LLSD.FromReal(A)); return array; } public void FromLLSD(LLSD llsd) { if (llsd.Type == LLSDType.Array) { LLSDArray array = (LLSDArray)llsd; if (array.Count == 4) { R = (float)array[0].AsReal(); G = (float)array[1].AsReal(); B = (float)array[2].AsReal(); A = (float)array[3].AsReal(); return; } } this = Color4.Black; } /// /// /// /// public string ToStringRGB() { return String.Format("<{0}, {1}, {2}>", R, G, B); } #endregion Public Methods #region Static Methods #endregion Static Methods #region Overrides /// /// /// /// public override string ToString() { return String.Format("<{0}, {1}, {2}, {3}>", R, G, B, A); } /// /// /// /// /// public override bool Equals(object obj) { if (obj is Color4) { Color4 c = (Color4)obj; return (R == c.R) && (G == c.G) && (B == c.B) && (A == c.A); } return false; } /// /// /// /// public override int GetHashCode() { return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); } #endregion Overrides #region Operators /// /// Comparison operator /// /// /// /// public static bool operator ==(Color4 lhs, Color4 rhs) { // Return true if the fields match: return lhs.R == rhs.R && lhs.G == rhs.G && lhs.B == rhs.B && lhs.A == rhs.A; } /// /// Not comparison operator /// /// /// /// public static bool operator !=(Color4 lhs, Color4 rhs) { return !(lhs == rhs); } #endregion Operators /// A Color4 with zero RGB values and full alpha (1.0) public readonly static Color4 Black = new Color4(0f, 0f, 0f, 1f); /// A Color4 with full RGB values (1.0) and full alpha (1.0) public readonly static Color4 White = new Color4(1f, 1f, 1f, 1f); } /// /// A quaternion, used for rotations /// [Serializable] public struct Quaternion { /// X value public float X; /// Y value public float Y; /// Z value public float Z; /// W value public float W; // Used for little to big endian conversion on big endian architectures private byte[] conversionBuffer; #region Properties /// /// Returns the conjugate (spatial inverse) of this quaternion /// public Quaternion Conjugate { get { return new Quaternion(-this.X, -this.Y, -this.Z, this.W); } } #endregion Properties #region Constructors /// /// Constructor, builds a quaternion object from a byte array /// /// Byte array containing four four-byte floats /// Offset in the byte array to start reading at /// Whether the source data is normalized or /// not. If this is true 12 bytes will be read, otherwise 16 bytes will /// be read. public Quaternion(byte[] byteArray, int pos, bool normalized) { conversionBuffer = null; X = Y = Z = W = 0; FromBytes(byteArray, pos, normalized); } /// /// Build a quaternion from normalized float values /// /// X value from -1.0 to 1.0 /// Y value from -1.0 to 1.0 /// Z value from -1.0 to 1.0 public Quaternion(float x, float y, float z) { conversionBuffer = null; X = x; Y = y; Z = z; float xyzsum = 1 - X * X - Y * Y - Z * Z; W = (xyzsum > 0) ? (float)Math.Sqrt(xyzsum) : 0; } /// /// Build a quaternion from individual float values /// /// X value /// Y value /// Z value /// W value public Quaternion(float x, float y, float z, float w) { conversionBuffer = null; X = x; Y = y; Z = z; W = w; } /// /// Build a quaternion from an angle and a vector /// /// Angle value /// Vector value public Quaternion(float angle, Vector3 vec) { conversionBuffer = null; X = Y = Z = W = 0f; SetQuaternion(angle, vec); } /// /// Copy constructor /// /// Quaternion to copy public Quaternion(Quaternion quaternion) { conversionBuffer = null; X = quaternion.X; Y = quaternion.Y; Z = quaternion.Z; W = quaternion.W; } #endregion Constructors #region Public Methods /// /// Builds a quaternion object from a byte array /// /// The source byte array /// Offset in the byte array to start reading at /// Whether the source data is normalized or /// not. If this is true 12 bytes will be read, otherwise 16 bytes will /// be read. public void FromBytes(byte[] byteArray, int pos, bool normalized) { if (!normalized) { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[16]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 16); Array.Reverse(conversionBuffer, 0, 4); Array.Reverse(conversionBuffer, 4, 4); Array.Reverse(conversionBuffer, 8, 4); Array.Reverse(conversionBuffer, 12, 4); X = BitConverter.ToSingle(conversionBuffer, 0); Y = BitConverter.ToSingle(conversionBuffer, 4); Z = BitConverter.ToSingle(conversionBuffer, 8); W = BitConverter.ToSingle(conversionBuffer, 12); } else { // Little endian architecture X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); Z = BitConverter.ToSingle(byteArray, pos + 8); W = BitConverter.ToSingle(byteArray, pos + 12); } } else { if (!BitConverter.IsLittleEndian) { // Big endian architecture if (conversionBuffer == null) conversionBuffer = new byte[16]; Buffer.BlockCopy(byteArray, pos, conversionBuffer, 0, 12); Array.Reverse(conversionBuffer, 0, 4); Array.Reverse(conversionBuffer, 4, 4); Array.Reverse(conversionBuffer, 8, 4); X = BitConverter.ToSingle(conversionBuffer, 0); Y = BitConverter.ToSingle(conversionBuffer, 4); Z = BitConverter.ToSingle(conversionBuffer, 8); } else { // Little endian architecture X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); Z = BitConverter.ToSingle(byteArray, pos + 8); } float xyzsum = 1 - X * X - Y * Y - Z * Z; W = (xyzsum > 0) ? (float)Math.Sqrt(xyzsum) : 0; } } /// /// Normalize this quaternion and serialize it to a byte array /// /// A 12 byte array containing normalized X, Y, and Z floating /// point values in order using little endian byte ordering public byte[] GetBytes() { byte[] bytes = new byte[12]; float norm; norm = (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); if (norm != 0) { norm = 1 / norm; float x, y, z; if (W >= 0) { x = X; y = Y; z = Z; } else { x = -X; y = -Y; z = -Z; } Buffer.BlockCopy(BitConverter.GetBytes(norm * x), 0, bytes, 0, 4); Buffer.BlockCopy(BitConverter.GetBytes(norm * y), 0, bytes, 4, 4); Buffer.BlockCopy(BitConverter.GetBytes(norm * z), 0, bytes, 8, 4); if (!BitConverter.IsLittleEndian) { Array.Reverse(bytes, 0, 4); Array.Reverse(bytes, 4, 4); Array.Reverse(bytes, 8, 4); } } else { throw new Exception("Quaternion " + this.ToString() + " normalized to zero"); } return bytes; } public LLSD ToLLSD() { LLSDArray array = new LLSDArray(); array.Add(LLSD.FromReal(X)); array.Add(LLSD.FromReal(Y)); array.Add(LLSD.FromReal(Z)); array.Add(LLSD.FromReal(W)); return array; } public void FromLLSD(LLSD llsd) { if (llsd.Type == LLSDType.Array) { LLSDArray array = (LLSDArray)llsd; if (array.Count == 4) { X = (float)array[0].AsReal(); Y = (float)array[1].AsReal(); Z = (float)array[2].AsReal(); W = (float)array[3].AsReal(); return; } } this = Quaternion.Identity; } /// /// Convert this quaternion to euler angles /// /// X euler angle /// Y euler angle /// Z euler angle public void GetEulerAngles(out float roll, out float pitch, out float yaw) { // From http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/ float sqx = X * X; float sqy = Y * Y; float sqz = Z * Z; float sqw = W * W; // Unit will be a correction factor if the quaternion is not normalized float unit = sqx + sqy + sqz + sqw; double test = X * Y + Z * W; if (test > 0.499f * unit) { // Singularity at north pole yaw = 2f * (float)Math.Atan2(X, W); pitch = (float)Math.PI / 2f; roll = 0f; } else if (test < -0.499f * unit) { // Singularity at south pole yaw = -2f * (float)Math.Atan2(X, W); pitch = -(float)Math.PI / 2f; roll = 0f; } else { yaw = (float)Math.Atan2(2f * Y * W - 2f * X * Z, sqx - sqy - sqz + sqw); pitch = (float)Math.Asin(2f * test / unit); roll = (float)Math.Atan2(2f * X * W - 2f * Y * Z, -sqx + sqy - sqz + sqw); } } /// /// Converts this quaternion to a 3x3 row-major matrix /// /// A matrix representation of this quaternion public Matrix3 ToMatrix3() { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html Matrix3 m = new Matrix3(); float xx, xy, xz, xw, yy, yz, yw, zz, zw; xx = X * X; xy = X * Y; xz = X * Z; xw = X * W; yy = Y * Y; yz = Y * Z; yw = Y * W; zz = Z * Z; zw = Z * W; m.M11 = 1f - 2f * (yy + zz); m.M12 = 2f * (xy + zw); m.M13 = 2f * (xz - yw); m.M21 = 2f * (xy - zw); m.M22 = 1f - 2f * (xx + zz); m.M23 = 2f * (yz + xw); m.M31 = 2f * (xz + yw); m.M32 = 2f * (yz - xw); m.M33 = 1f - 2f * (xx + yy); return m; } /// /// Converts this quaternion to a 4x4 row-major matrix /// /// A matrix representation of this quaternion public Matrix4 ToMatrix4() { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html Matrix4 m = new Matrix4(); float xx, xy, xz, xw, yy, yz, yw, zz, zw; xx = X * X; xy = X * Y; xz = X * Z; xw = X * W; yy = Y * Y; yz = Y * Z; yw = Y * W; zz = Z * Z; zw = Z * W; m.M11 = 1f - 2f * (yy + zz); m.M12 = 2f * (xy + zw); m.M13 = 2f * (xz - yw); m.M14 = 0f; m.M21 = 2f * (xy - zw); m.M22 = 1f - 2f * (xx + zz); m.M23 = 2f * (yz + xw); m.M24 = 0f; m.M31 = 2f * (xz + yw); m.M32 = 2f * (yz - xw); m.M33 = 1f - 2f * (xx + yy); m.M34 = 0f; m.M41 = m.M42 = m.M43 = 0f; m.M44 = 1f; return m; } /// /// Construct this quaternion from an axis angle /// /// Angle /// X component of axis /// Y component of axis /// Z component of axis public void SetQuaternion(float angle, float x, float y, float z) { Vector3 vec = new Vector3(x, y, z); SetQuaternion(angle, vec); } /// /// Construct this quaternion from an axis angle /// /// Angle /// Axis public void SetQuaternion(float angle, Vector3 vec) { vec = Vector3.Norm(vec); angle *= 0.5f; float c = (float)Math.Cos(angle); float s = (float)Math.Sin(angle); X = vec.X * s; Y = vec.Y * s; Z = vec.Z * s; W = c; this = Quaternion.Norm(this); } #endregion Public Methods #region Static Methods /// /// Creates a quaternion from a vector containing roll, pitch, and yaw /// in radians /// /// Vector representation of the euler angles in /// radians /// Quaternion representation of the euler angles public static Quaternion FromEulers(Vector3 euler) { return FromEulers(euler.X, euler.Y, euler.Z); } /// /// Creates a quaternion from roll, pitch, and yaw euler angles in /// radians /// /// X angle in radians /// Y angle in radians /// Z angle in radians /// Quaternion representation of the euler angles public static Quaternion FromEulers(float roll, float pitch, float yaw) { if (roll > Helpers.TWO_PI || pitch > Helpers.TWO_PI || yaw > Helpers.TWO_PI) throw new ArgumentException("Euler angles must be in radians"); double atCos = Math.Cos(roll / 2); double atSin = Math.Sin(roll / 2); double leftCos = Math.Cos(pitch / 2); double leftSin = Math.Sin(pitch / 2); double upCos = Math.Cos(yaw / 2); double upSin = Math.Sin(yaw / 2); double atLeftCos = atCos * leftCos; double atLeftSin = atSin * leftSin; return new Quaternion( (float)(atSin * leftCos * upCos + atCos * leftSin * upSin), (float)(atCos * leftSin * upCos - atSin * leftCos * upSin), (float)(atLeftCos * upSin + atLeftSin * upCos), (float)(atLeftCos * upCos - atLeftSin * upSin) ); } /// /// Calculate the magnitude of the supplied quaternion /// public static float Mag(Quaternion q) { return (float)Math.Sqrt(q.W * q.W + q.X * q.X + q.Y * q.Y + q.Z * q.Z); } /// /// Returns a normalized version of the supplied quaternion /// /// The quaternion to normalize /// A normalized version of the quaternion public static Quaternion Norm(Quaternion q) { const float MAG_THRESHOLD = 0.0000001f; float mag = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W); if (mag > MAG_THRESHOLD) { float oomag = 1.0f / mag; q.X *= oomag; q.Y *= oomag; q.Z *= oomag; q.W *= oomag; } else { q.X = 0.0f; q.Y = 0.0f; q.Z = 0.0f; q.W = 1.0f; } return q; } public static Quaternion Parse(string val) { char[] splitChar = { ',' }; string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(splitChar); if (split.Length == 3) { return new Quaternion( float.Parse(split[0].Trim(), Helpers.EnUsCulture), float.Parse(split[1].Trim(), Helpers.EnUsCulture), float.Parse(split[2].Trim(), Helpers.EnUsCulture)); } else { return new Quaternion( float.Parse(split[0].Trim(), Helpers.EnUsCulture), float.Parse(split[1].Trim(), Helpers.EnUsCulture), float.Parse(split[2].Trim(), Helpers.EnUsCulture), float.Parse(split[3].Trim(), Helpers.EnUsCulture)); } } public static bool TryParse(string val, out Quaternion result) { try { result = Parse(val); return true; } catch (Exception) { result = new Quaternion(); return false; } } #endregion Static Methods #region Overrides /// /// /// /// public override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is Quaternion)) return false; Quaternion quaternion = (Quaternion)o; return X == quaternion.X && Y == quaternion.Y && Z == quaternion.Z && W == quaternion.W; } /// /// /// /// public override string ToString() { return "<" + X.ToString() + ", " + Y.ToString() + ", " + Z.ToString() + ", " + W.ToString() + ">"; } #endregion Overrides #region Operators public static bool operator ==(Quaternion lhs, Quaternion rhs) { // Return true if the fields match: return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z && lhs.W == rhs.W; } public static bool operator !=(Quaternion lhs, Quaternion rhs) { return !(lhs == rhs); } /// /// Performs quaternion multiplication /// /// /// /// public static Quaternion operator *(Quaternion lhs, Quaternion rhs) { Quaternion ret = new Quaternion( (lhs.W * rhs.X) + (lhs.X * rhs.W) + (lhs.Y * rhs.Z) - (lhs.Z * rhs.Y), (lhs.W * rhs.Y) - (lhs.X * rhs.Z) + (lhs.Y * rhs.W) + (lhs.Z * rhs.X), (lhs.W * rhs.Z) + (lhs.X * rhs.Y) - (lhs.Y * rhs.X) + (lhs.Z * rhs.W), (lhs.W * rhs.W) - (lhs.X * rhs.X) - (lhs.Y * rhs.Y) - (lhs.Z * rhs.Z) ); return ret; } /// /// Division operator (multiply by the conjugate) /// /// /// /// public static Quaternion operator /(Quaternion lhs, Quaternion rhs) { return lhs * rhs.Conjugate; } #endregion Operators /// An Quaternion with a value of 0,0,0,1 public readonly static Quaternion Identity = new Quaternion(0f, 0f, 0f, 1f); } /// /// A 3x3 row-major matrix /// [Serializable] public sealed class Matrix3 { public float M11, M12, M13; public float M21, M22, M23; public float M31, M32, M33; #region Properties public float Trace { get { return M11 + M22 + M33; } } public float Determinant { get { return M11 * M22 * M33 + M12 * M23 * M31 + M13 * M21 * M32 - M13 * M22 * M31 - M11 * M23 * M32 - M12 * M21 * M33; } } #endregion Properties #region Constructors public Matrix3() { } public Matrix3(float m11, float m12, float m13, float m21, float m22, float m23, float m31, float m32, float m33) { M11 = m11; M12 = m12; M13 = m13; M21 = m21; M22 = m22; M23 = m23; M31 = m31; M32 = m32; M33 = m33; } public Matrix3(float roll, float pitch, float yaw) { M11 = M12 = M13 = M21 = M22 = M23 = M31 = M32 = M33 = 0f; FromEulers(roll, pitch, yaw); } /// /// Copy constructor /// /// Matrix to copy public Matrix3(Matrix3 m) { M11 = m.M11; M12 = m.M12; M13 = m.M13; M21 = m.M21; M22 = m.M22; M23 = m.M23; M31 = m.M31; M32 = m.M32; M33 = m.M33; } #endregion Constructors #region Public Methods /// /// Construct this matrix from euler rotation values /// /// X euler angle /// Y euler angle /// Z euler angle public void FromEulers(float roll, float pitch, float yaw) { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html float a, b, c, d, e, f; float ad, bd; a = (float)Math.Cos(roll); b = (float)Math.Sin(roll); c = (float)Math.Cos(pitch); d = (float)Math.Sin(pitch); e = (float)Math.Cos(yaw); f = (float)Math.Sin(yaw); ad = a * d; bd = b * d; M11 = c * e; M12 = -c * f; M13 = d; M21 = bd * e + a * f; M22 = -bd * f + a * e; M23 = -b * c; M31 = -ad * e + b * f; M32 = ad * f + b * e; M33 = a * c; } /// /// Convert this matrix to euler rotations /// /// X euler angle /// Y euler angle /// Z euler angle public void GetEulerAngles(out float roll, out float pitch, out float yaw) { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html double angleX, angleY, angleZ; double cx, cy, cz; // cosines double sx, sz; // sines angleY = Math.Asin(Helpers.Clamp(M13, -1f, 1f)); cy = Math.Cos(angleY); if (Math.Abs(cy) > 0.005f) { // No gimbal lock cx = M33 / cy; sx = (-M23) / cy; angleX = (float)Math.Atan2(sx, cx); cz = M11 / cy; sz = (-M12) / cy; angleZ = (float)Math.Atan2(sz, cz); } else { // Gimbal lock angleX = 0; cz = M22; sz = M21; angleZ = Math.Atan2(sz, cz); } // Return only positive angles in [0,360] if (angleX < 0) angleX += 360d; if (angleY < 0) angleY += 360d; if (angleZ < 0) angleZ += 360d; roll = (float)angleX; pitch = (float)angleY; yaw = (float)angleZ; } /// /// Conver this matrix to a quaternion rotation /// /// A quaternion representation of this rotation matrix public Quaternion ToQuaternion() { // From http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ Quaternion quat = new Quaternion(); float trace = Trace + 1f; if (trace > Single.Epsilon) { float s = 0.5f / (float)Math.Sqrt(trace); quat.X = (M32 - M23) * s; quat.Y = (M13 - M31) * s; quat.Z = (M21 - M12) * s; quat.W = 0.25f / s; } else { if (M11 > M22 && M11 > M33) { float s = 2.0f * (float)Math.Sqrt(1.0f + M11 - M22 - M33); quat.X = 0.25f * s; quat.Y = (M12 + M21) / s; quat.Z = (M13 + M31) / s; quat.W = (M23 - M32) / s; } else if (M22 > M33) { float s = 2.0f * (float)Math.Sqrt(1.0f + M22 - M11 - M33); quat.X = (M12 + M21) / s; quat.Y = 0.25f * s; quat.Z = (M23 + M32) / s; quat.W = (M13 - M31) / s; } else { float s = 2.0f * (float)Math.Sqrt(1.0f + M33 - M11 - M22); quat.X = (M13 + M31) / s; quat.Y = (M23 + M32) / s; quat.Z = 0.25f * s; quat.W = (M12 - M21) / s; } } return quat; } #endregion Public Methods #region Static Methods public static Matrix3 Add(Matrix3 left, Matrix3 right) { return new Matrix3( left.M11 + right.M11, left.M12 + right.M12, left.M13 + right.M13, left.M21 + right.M21, left.M22 + right.M22, left.M23 + right.M23, left.M31 + right.M31, left.M32 + right.M32, left.M33 + right.M33 ); } public static Matrix3 Add(Matrix3 matrix, float scalar) { return new Matrix3( matrix.M11 + scalar, matrix.M12 + scalar, matrix.M13 + scalar, matrix.M21 + scalar, matrix.M22 + scalar, matrix.M23 + scalar, matrix.M31 + scalar, matrix.M32 + scalar, matrix.M33 + scalar ); } public static Matrix3 Subtract(Matrix3 left, Matrix3 right) { return new Matrix3( left.M11 - right.M11, left.M12 - right.M12, left.M13 - right.M13, left.M21 - right.M21, left.M22 - right.M22, left.M23 - right.M23, left.M31 - right.M31, left.M32 - right.M32, left.M33 - right.M33 ); } public static Matrix3 Subtract(Matrix3 matrix, float scalar) { return new Matrix3( matrix.M11 - scalar, matrix.M12 - scalar, matrix.M13 - scalar, matrix.M21 - scalar, matrix.M22 - scalar, matrix.M23 - scalar, matrix.M31 - scalar, matrix.M32 - scalar, matrix.M33 - scalar ); } public static Matrix3 Multiply(Matrix3 left, Matrix3 right) { return new Matrix3( left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31, left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32, left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33, left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31, left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32, left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33, left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31, left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32, left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 ); } /// /// Transposes a matrix /// /// Matrix to transpose public static void Transpose(Matrix3 m) { Helpers.Swap(ref m.M12, ref m.M21); Helpers.Swap(ref m.M13, ref m.M31); Helpers.Swap(ref m.M23, ref m.M32); } #endregion Static Methods #region Overrides public override int GetHashCode() { return M11.GetHashCode() ^ M12.GetHashCode() ^ M13.GetHashCode() ^ M21.GetHashCode() ^ M22.GetHashCode() ^ M23.GetHashCode() ^ M31.GetHashCode() ^ M32.GetHashCode() ^ M33.GetHashCode(); } public override bool Equals(object obj) { if (obj is Matrix3) { Matrix3 m = (Matrix3)obj; return (M11 == m.M11) && (M12 == m.M12) && (M13 == m.M13) && (M21 == m.M21) && (M22 == m.M22) && (M23 == m.M23) && (M31 == m.M31) && (M32 == m.M32) && (M33 == m.M33); } return false; } public bool Equals(Matrix3 m) { return (M11 == m.M11) && (M12 == m.M12) && (M13 == m.M13) && (M21 == m.M21) && (M22 == m.M22) && (M23 == m.M23) && (M31 == m.M31) && (M32 == m.M32) && (M33 == m.M33); } public override string ToString() { return string.Format("|{0}, {1}, {2}|\n|{3}, {4}, {5}|\n|{6}, {7}, {8}|", M11, M12, M13, M21, M22, M23, M31, M32, M33); } #endregion Overrides #region Operators public static bool operator ==(Matrix3 left, Matrix3 right) { return left.Equals(right); } public static bool operator !=(Matrix3 left, Matrix3 right) { return !left.Equals(right); } public static Matrix3 operator +(Matrix3 left, Matrix3 right) { return Matrix3.Add(left, right); } public static Matrix3 operator +(Matrix3 matrix, float scalar) { return Matrix3.Add(matrix, scalar); } public static Matrix3 operator -(Matrix3 left, Matrix3 right) { return Matrix3.Subtract(left, right); ; } public static Matrix3 operator -(Matrix3 matrix, float scalar) { return Matrix3.Subtract(matrix, scalar); } public static Matrix3 operator *(Matrix3 left, Matrix3 right) { return Matrix3.Multiply(left, right); ; } public Vector3 this[int row] { get { switch (row) { case 0: return new Vector3(M11, M12, M13); case 1: return new Vector3(M21, M22, M23); case 2: return new Vector3(M31, M32, M33); default: throw new IndexOutOfRangeException("Matrix3 row index must be from 0-2"); } } set { switch (row) { case 0: M11 = value.X; M12 = value.Y; M13 = value.Z; break; case 1: M21 = value.X; M22 = value.Y; M23 = value.Z; break; case 2: M31 = value.X; M32 = value.Y; M33 = value.Z; break; default: throw new IndexOutOfRangeException("Matrix3 row index must be from 0-2"); } } } public float this[int row, int column] { get { switch (row) { case 0: switch (column) { case 0: return M11; case 1: return M12; case 2: return M13; default: throw new IndexOutOfRangeException("Matrix3 row and column values must be from 0-2"); } case 1: switch (column) { case 0: return M21; case 1: return M22; case 2: return M23; default: throw new IndexOutOfRangeException("Matrix3 row and column values must be from 0-2"); } case 2: switch (column) { case 0: return M31; case 1: return M32; case 2: return M33; default: throw new IndexOutOfRangeException("Matrix3 row and column values must be from 0-2"); } default: throw new IndexOutOfRangeException("Matrix3 row and column values must be from 0-2"); } } } #endregion Operators /// A 3x3 matrix set to all zeroes public static readonly Matrix3 Zero = new Matrix3(); /// A 3x3 identity matrix public static readonly Matrix3 Identity = new Matrix3( 1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f ); } /// /// A 4x4 row-major matrix /// [Serializable] public sealed class Matrix4 { public float M11, M12, M13, M14; public float M21, M22, M23, M24; public float M31, M32, M33, M34; public float M41, M42, M43, M44; #region Properties public float Trace { get { return M11 + M22 + M33 + M44; } } public float Determinant { get { return M14 * M23 * M32 * M41 - M13 * M24 * M32 * M41 - M14 * M22 * M33 * M41 + M12 * M24 * M33 * M41 + M13 * M22 * M34 * M41 - M12 * M23 * M34 * M41 - M14 * M23 * M31 * M42 + M13 * M24 * M31 * M42 + M14 * M21 * M33 * M42 - M11 * M24 * M33 * M42 - M13 * M21 * M34 * M42 + M11 * M23 * M34 * M42 + M14 * M22 * M31 * M43 - M12 * M24 * M31 * M43 - M14 * M21 * M32 * M43 + M11 * M24 * M32 * M43 + M12 * M21 * M34 * M43 - M11 * M22 * M34 * M43 - M13 * M22 * M31 * M44 + M12 * M23 * M31 * M44 + M13 * M21 * M32 * M44 - M11 * M23 * M32 * M44 - M12 * M21 * M33 * M44 + M11 * M22 * M33 * M44; } } #endregion Properties #region Constructors public Matrix4() { } public Matrix4( float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) { M11 = m11; M12 = m12; M13 = m13; M14 = m14; M21 = m21; M22 = m22; M23 = m23; M24 = m24; M31 = m31; M32 = m32; M33 = m33; M34 = m34; M41 = m41; M42 = m42; M43 = m43; M44 = m44; } public Matrix4(float roll, float pitch, float yaw) { M11 = M12 = M13 = M14 = M21 = M22 = M23 = M24 = M31 = M32 = M33 = M34 = M41 = M42 = M43 = M44 = 0f; FromEulers(roll, pitch, yaw); } public Matrix4(Matrix3 m) { M11 = m.M11; M12 = m.M12; M13 = m.M13; M14 = 0f; M21 = m.M21; M22 = m.M22; M23 = m.M23; M24 = 0f; M31 = m.M31; M32 = m.M32; M33 = m.M33; M34 = 0f; M41 = 0f; M42 = 0f; M43 = 0f; M44 = 1f; } public Matrix4(Matrix3 m, Vector3 translation) { M11 = m.M11; M12 = m.M12; M13 = m.M13; M14 = 0f; M21 = m.M21; M22 = m.M22; M23 = m.M23; M24 = 0f; M31 = m.M31; M32 = m.M32; M33 = m.M33; M34 = 0f; M41 = translation.X; M42 = translation.Y; M43 = translation.Z; M44 = 1f; } /// /// Copy constructor /// /// Matrix to copy public Matrix4(Matrix4 m) { M11 = m.M11; M12 = m.M12; M13 = m.M13; M14 = m.M14; M21 = m.M21; M22 = m.M22; M23 = m.M23; M24 = m.M24; M31 = m.M31; M32 = m.M32; M33 = m.M33; M34 = m.M34; M41 = m.M41; M42 = m.M42; M43 = m.M43; M44 = m.M44; } #endregion Constructors #region Public Methods /// /// Construct this matrix from euler rotation values /// /// X euler angle /// Y euler angle /// Z euler angle public void FromEulers(float roll, float pitch, float yaw) { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html float a, b, c, d, e, f; float ad, bd; a = (float)Math.Cos(roll); b = (float)Math.Sin(roll); c = (float)Math.Cos(pitch); d = (float)Math.Sin(pitch); e = (float)Math.Cos(yaw); f = (float)Math.Sin(yaw); ad = a * d; bd = b * d; M11 = c * e; M12 = -c * f; M13 = d; M14 = 0f; M21 = bd * e + a * f; M22 = -bd * f + a * e; M23 = -b * c; M24 = 0f; M31 = -ad * e + b * f; M32 = ad * f + b * e; M33 = a * c; M34 = 0f; M41 = M42 = M43 = 0f; M44 = 1f; } /// /// Convert this matrix to euler rotations /// /// X euler angle /// Y euler angle /// Z euler angle public void GetEulerAngles(out float roll, out float pitch, out float yaw) { // From the Matrix and Quaternion FAQ: http://www.j3d.org/matrix_faq/matrfaq_latest.html double angleX, angleY, angleZ; double cx, cy, cz; // cosines double sx, sz; // sines angleY = Math.Asin(Helpers.Clamp(M13, -1f, 1f)); cy = Math.Cos(angleY); if (Math.Abs(cy) > 0.005f) { // No gimbal lock cx = M33 / cy; sx = (-M23) / cy; angleX = (float)Math.Atan2(sx, cx); cz = M11 / cy; sz = (-M12) / cy; angleZ = (float)Math.Atan2(sz, cz); } else { // Gimbal lock angleX = 0; cz = M22; sz = M21; angleZ = Math.Atan2(sz, cz); } // Return only positive angles in [0,360] if (angleX < 0) angleX += 360d; if (angleY < 0) angleY += 360d; if (angleZ < 0) angleZ += 360d; roll = (float)angleX; pitch = (float)angleY; yaw = (float)angleZ; } /// /// Conver this matrix to a quaternion rotation /// /// A quaternion representation of this rotation matrix public Quaternion ToQuaternion() { // From http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ Quaternion quat = new Quaternion(); float trace = Trace + 1f; if (trace > Single.Epsilon) { float s = 0.5f / (float)Math.Sqrt(trace); quat.X = (M32 - M23) * s; quat.Y = (M13 - M31) * s; quat.Z = (M21 - M12) * s; quat.W = 0.25f / s; } else { if (M11 > M22 && M11 > M33) { float s = 2.0f * (float)Math.Sqrt(1.0f + M11 - M22 - M33); quat.X = 0.25f * s; quat.Y = (M12 + M21) / s; quat.Z = (M13 + M31) / s; quat.W = (M23 - M32) / s; } else if (M22 > M33) { float s = 2.0f * (float)Math.Sqrt(1.0f + M22 - M11 - M33); quat.X = (M12 + M21) / s; quat.Y = 0.25f * s; quat.Z = (M23 + M32) / s; quat.W = (M13 - M31) / s; } else { float s = 2.0f * (float)Math.Sqrt(1.0f + M33 - M11 - M22); quat.X = (M13 + M31) / s; quat.Y = (M23 + M32) / s; quat.Z = 0.25f * s; quat.W = (M12 - M21) / s; } } return quat; } #endregion Public Methods #region Static Methods public static Matrix4 Add(Matrix4 left, Matrix4 right) { return new Matrix4( left.M11 + right.M11, left.M12 + right.M12, left.M13 + right.M13, left.M14 + right.M14, left.M21 + right.M21, left.M22 + right.M22, left.M23 + right.M23, left.M24 + right.M24, left.M31 + right.M31, left.M32 + right.M32, left.M33 + right.M33, left.M34 + right.M34, left.M41 + right.M41, left.M42 + right.M42, left.M43 + right.M43, left.M44 + right.M44 ); } public static Matrix4 Add(Matrix4 matrix, float scalar) { return new Matrix4( matrix.M11 + scalar, matrix.M12 + scalar, matrix.M13 + scalar, matrix.M14 + scalar, matrix.M21 + scalar, matrix.M22 + scalar, matrix.M23 + scalar, matrix.M24 + scalar, matrix.M31 + scalar, matrix.M32 + scalar, matrix.M33 + scalar, matrix.M34 + scalar, matrix.M41 + scalar, matrix.M42 + scalar, matrix.M43 + scalar, matrix.M44 + scalar ); } public static Matrix4 Subtract(Matrix4 left, Matrix4 right) { return new Matrix4( left.M11 - right.M11, left.M12 - right.M12, left.M13 - right.M13, left.M14 - right.M14, left.M21 - right.M21, left.M22 - right.M22, left.M23 - right.M23, left.M24 - right.M24, left.M31 - right.M31, left.M32 - right.M32, left.M33 - right.M33, left.M34 - right.M34, left.M41 - right.M41, left.M42 - right.M42, left.M43 - right.M43, left.M44 - right.M44 ); } public static Matrix4 Subtract(Matrix4 matrix, float scalar) { return new Matrix4( matrix.M11 - scalar, matrix.M12 - scalar, matrix.M13 - scalar, matrix.M14 - scalar, matrix.M21 - scalar, matrix.M22 - scalar, matrix.M23 - scalar, matrix.M24 - scalar, matrix.M31 - scalar, matrix.M32 - scalar, matrix.M33 - scalar, matrix.M34 - scalar, matrix.M41 - scalar, matrix.M42 - scalar, matrix.M43 - scalar, matrix.M44 - scalar ); } public static Matrix4 Multiply(Matrix4 left, Matrix4 right) { return new Matrix4( left.M11*right.M11 + left.M12*right.M21 + left.M13*right.M31 + left.M14*right.M41, left.M11*right.M12 + left.M12*right.M22 + left.M13*right.M32 + left.M14*right.M42, left.M11*right.M13 + left.M12*right.M23 + left.M13*right.M33 + left.M14*right.M43, left.M11*right.M14 + left.M12*right.M24 + left.M13*right.M34 + left.M14*right.M44, left.M21*right.M11 + left.M22*right.M21 + left.M23*right.M31 + left.M24*right.M41, left.M21*right.M12 + left.M22*right.M22 + left.M23*right.M32 + left.M24*right.M42, left.M21*right.M13 + left.M22*right.M23 + left.M23*right.M33 + left.M24*right.M43, left.M21*right.M14 + left.M22*right.M24 + left.M23*right.M34 + left.M24*right.M44, left.M31*right.M11 + left.M32*right.M21 + left.M33*right.M31 + left.M34*right.M41, left.M31*right.M12 + left.M32*right.M22 + left.M33*right.M32 + left.M34*right.M42, left.M31*right.M13 + left.M32*right.M23 + left.M33*right.M33 + left.M34*right.M43, left.M31*right.M14 + left.M32*right.M24 + left.M33*right.M34 + left.M34*right.M44, left.M41*right.M11 + left.M42*right.M21 + left.M43*right.M31 + left.M44*right.M41, left.M41*right.M12 + left.M42*right.M22 + left.M43*right.M32 + left.M44*right.M42, left.M41*right.M13 + left.M42*right.M23 + left.M43*right.M33 + left.M44*right.M43, left.M41*right.M14 + left.M42*right.M24 + left.M43*right.M34 + left.M44*right.M44 ); } /// /// Transposes a matrix /// /// Matrix to transpose public static void Transpose(Matrix4 m) { Helpers.Swap(ref m.M12, ref m.M21); Helpers.Swap(ref m.M13, ref m.M31); Helpers.Swap(ref m.M14, ref m.M41); Helpers.Swap(ref m.M23, ref m.M32); Helpers.Swap(ref m.M24, ref m.M42); Helpers.Swap(ref m.M34, ref m.M43); } #endregion Static Methods #region Overrides public override int GetHashCode() { return M11.GetHashCode() ^ M12.GetHashCode() ^ M13.GetHashCode() ^ M14.GetHashCode() ^ M21.GetHashCode() ^ M22.GetHashCode() ^ M23.GetHashCode() ^ M24.GetHashCode() ^ M31.GetHashCode() ^ M32.GetHashCode() ^ M33.GetHashCode() ^ M34.GetHashCode() ^ M41.GetHashCode() ^ M42.GetHashCode() ^ M43.GetHashCode() ^ M44.GetHashCode(); } public override bool Equals(object obj) { if (obj is Matrix4) { Matrix4 m = (Matrix4)obj; return (M11 == m.M11) && (M12 == m.M12) && (M13 == m.M13) && (M14 == m.M14) && (M21 == m.M21) && (M22 == m.M22) && (M23 == m.M23) && (M24 == m.M24) && (M31 == m.M31) && (M32 == m.M32) && (M33 == m.M33) && (M34 == m.M34) && (M41 == m.M41) && (M42 == m.M42) && (M43 == m.M43) && (M44 == m.M44); } return false; } public bool Equals(Matrix4 m) { return (M11 == m.M11) && (M12 == m.M12) && (M13 == m.M13) && (M14 == m.M14) && (M21 == m.M21) && (M22 == m.M22) && (M23 == m.M23) && (M24 == m.M24) && (M31 == m.M31) && (M32 == m.M32) && (M33 == m.M33) && (M34 == m.M34) && (M41 == m.M41) && (M42 == m.M42) && (M43 == m.M43) && (M44 == m.M44); } public override string ToString() { return string.Format( "|{0}, {1}, {2}, {3}|\n|{4}, {5}, {6}, {7}|\n|{8}, {9}, {10}, {11}|\n|{12}, {13}, {14}, {15}|", M11, M12, M13, M14, M21, M22, M23, M24, M31, M32, M33, M34, M41, M42, M43, M44); } #endregion Overrides #region Operators public static bool operator ==(Matrix4 left, Matrix4 right) { return left.Equals(right); } public static bool operator !=(Matrix4 left, Matrix4 right) { return !left.Equals(right); } public static Matrix4 operator +(Matrix4 left, Matrix4 right) { return Matrix4.Add(left, right); } public static Matrix4 operator +(Matrix4 matrix, float scalar) { return Matrix4.Add(matrix, scalar); } public static Matrix4 operator -(Matrix4 left, Matrix4 right) { return Matrix4.Subtract(left, right); ; } public static Matrix4 operator -(Matrix4 matrix, float scalar) { return Matrix4.Subtract(matrix, scalar); } public static Matrix4 operator *(Matrix4 left, Matrix4 right) { return Matrix4.Multiply(left, right); ; } public Vector4 this[int row] { get { switch (row) { case 0: return new Vector4(M11, M12, M13, M14); case 1: return new Vector4(M21, M22, M23, M24); case 2: return new Vector4(M31, M32, M33, M34); case 3: return new Vector4(M41, M42, M43, M44); default: throw new IndexOutOfRangeException("Matrix4 row index must be from 0-3"); } } set { switch (row) { case 0: M11 = value.X; M12 = value.Y; M13 = value.Z; M14 = value.S; break; case 1: M21 = value.X; M22 = value.Y; M23 = value.Z; M24 = value.S; break; case 2: M31 = value.X; M32 = value.Y; M33 = value.Z; M34 = value.S; break; case 3: M41 = value.X; M42 = value.Y; M43 = value.Z; M44 = value.S; break; default: throw new IndexOutOfRangeException("Matrix4 row index must be from 0-3"); } } } public float this[int row, int column] { get { switch (row) { case 0: switch (column) { case 0: return M11; case 1: return M12; case 2: return M13; case 3: return M14; default: throw new IndexOutOfRangeException("Matrix4 row and column values must be from 0-3"); } case 1: switch (column) { case 0: return M21; case 1: return M22; case 2: return M23; case 3: return M24; default: throw new IndexOutOfRangeException("Matrix4 row and column values must be from 0-3"); } case 2: switch (column) { case 0: return M31; case 1: return M32; case 2: return M33; case 3: return M34; default: throw new IndexOutOfRangeException("Matrix4 row and column values must be from 0-3"); } case 3: switch (column) { case 0: return M41; case 1: return M42; case 2: return M43; case 3: return M44; default: throw new IndexOutOfRangeException("Matrix4 row and column values must be from 0-3"); } default: throw new IndexOutOfRangeException("Matrix4 row and column values must be from 0-3"); } } } #endregion Operators /// A 4x4 matrix set to all zeroes public static readonly Matrix4 Zero = new Matrix4(); /// A 4x4 identity matrix public static readonly Matrix4 Identity = new Matrix4( 1f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1f ); } }