/* * Copyright (c) 2006-2007, Second Life Reverse Engineering Team * 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 Second Life Reverse Engineering Team 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.ComponentModel; using System.Net; using System.Xml.Serialization; namespace libsecondlife { /// /// A 128-bit Universally Unique Identifier, used throughout the Second /// Life networking protocol /// [Serializable] public struct LLUUID { /// The System.Guid object this struct wraps around [XmlAttribute] public Guid UUID; /// Get a byte array of the 16 raw bytes making up the UUID public byte[] Data { get { return GetBytes(); } } /// /// Constructor that takes a string UUID representation /// /// A string representation of a UUID, case /// insensitive and can either be hyphenated or non-hyphenated /// LLUUID("11f8aa9c-b071-4242-836b-13b7abe0d489") public LLUUID(string val) { if (val == null) UUID = new Guid(); else UUID = new Guid(val); } /// /// Constructor that takes a System.Guid object /// /// A Guid object that contains the unique identifier /// to be represented by this LLUUID public LLUUID(Guid val) { UUID = val; } /// /// Constructor that takes a byte array containing a UUID /// /// Byte array containing a 16 byte UUID /// Beginning offset in the array public LLUUID(byte[] source, int pos) { UUID = 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]); } /// /// Constructor that takes an unsigned 64-bit unsigned integer to /// convert to a UUID /// /// 64-bit unsigned integer to convert to a UUID public LLUUID(ulong val) { UUID = new Guid(0, 0, 0, BitConverter.GetBytes(val)); } /// /// Returns the raw bytes for this UUID /// /// A 16 byte array containing this UUID public byte[] GetBytes() { byte[] bytes = UUID.ToByteArray(); if (BitConverter.IsLittleEndian) { 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; } else { return bytes; } } /// /// Calculate an LLCRC (cyclic redundancy check) for this LLUUID /// /// The CRC checksum for this LLUUID 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; } /// /// Combine two UUIDs together by taking the MD5 hash of a byte array /// containing both UUIDs /// /// The UUID to combine with this one /// The UUID product of the combination public LLUUID Combine(LLUUID other) { // Build the buffer to MD5 byte[] input = new byte[32]; Buffer.BlockCopy(GetBytes(), 0, input, 0, 16); Buffer.BlockCopy(other.GetBytes(), 0, input, 16, 16); return new LLUUID(Helpers.MD5Builder.ComputeHash(input), 0); } /// /// Generate a LLUUID from a string /// /// A string representation of a UUID, case /// insensitive and can either be hyphenated or non-hyphenated /// LLUUID.Parse("11f8aa9c-b071-4242-836b-13b7abe0d489") public static LLUUID Parse(string val) { return new LLUUID(val); } /// /// Generate a LLUUID 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 /// LLUUID.TryParse("11f8aa9c-b071-4242-836b-13b7abe0d489", result) public static bool TryParse(string val, out LLUUID result) { try { result = Parse(val); return true; } catch (Exception) { result = LLUUID.Zero; return false; } } /// /// /// /// public static LLUUID Random() { return new LLUUID(Guid.NewGuid()); } /// /// 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 UUID.GetHashCode(); } /// /// Comparison function /// /// An object to compare to this UUID /// False if the object is not an LLUUID, true if it is and /// byte for byte identical to this public override bool Equals(object o) { if (!(o is LLUUID)) return false; LLUUID uuid = (LLUUID)o; return UUID == uuid.UUID; } /// /// Equals operator /// /// First LLUUID for comparison /// Second LLUUID for comparison /// True if the UUIDs are byte for byte equal, otherwise false public static bool operator==(LLUUID lhs, LLUUID rhs) { return lhs.UUID == rhs.UUID; } /// /// Not equals operator /// /// First LLUUID for comparison /// Second LLUUID for comparison /// True if the UUIDs are not equal, otherwise true public static bool operator!=(LLUUID lhs, LLUUID rhs) { return !(lhs == rhs); } /// /// XOR operator /// /// First LLUUID /// Second LLUUID /// A UUID that is a XOR combination of the two input UUIDs public static LLUUID operator ^(LLUUID lhs, LLUUID 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 LLUUID(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 LLUUID(string val) { return new LLUUID(val); } /// /// Get a string representation of this UUID /// /// A string representation of this UUID, lowercase and /// without hyphens /// 11f8aa9cb0714242836b13b7abe0d489 public override string ToString() { string uuid = UUID.ToString(); return uuid.Replace("-", String.Empty); } /// /// Get a hyphenated string representation of this UUID /// /// A string representation of this UUID, lowercase and /// with hyphens /// 11f8aa9c-b071-4242-836b-13b7abe0d489 public string ToStringHyphenated() { return UUID.ToString(); } /// /// An LLUUID with a value of all zeroes /// public static readonly LLUUID Zero = new LLUUID(); } /// /// A three-dimensional vector with floating-point values /// [Serializable] public struct LLVector3 { /// X value [XmlAttribute] public float X; /// Y value [XmlAttribute] public float Y; /// Z value [XmlAttribute] public float Z; /// /// Constructor, builds a single-precision vector from a /// double-precision one /// /// A double-precision vector public LLVector3(LLVector3d vector) { X = (float)vector.X; Y = (float)vector.Y; Z = (float)vector.Z; } /// /// Constructor, builds a vector from a byte array /// /// Byte array containing a 12 byte vector /// Beginning position in the byte array public LLVector3(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { byte[] newArray = new byte[12]; Buffer.BlockCopy(byteArray, pos, newArray, 0, 12); Array.Reverse(newArray, 0, 4); Array.Reverse(newArray, 4, 4); Array.Reverse(newArray, 8, 4); X = BitConverter.ToSingle(newArray, 0); Y = BitConverter.ToSingle(newArray, 4); Z = BitConverter.ToSingle(newArray, 8); } else { X = BitConverter.ToSingle(byteArray, pos); Y = BitConverter.ToSingle(byteArray, pos + 4); Z = BitConverter.ToSingle(byteArray, pos + 8); } } /// /// Constructor, builds a vector for individual float values /// /// X value /// Y value /// Z value public LLVector3(float x, float y, float z) { X = x; Y = y; Z = z; } /// /// 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; } /// /// Get the distance to point /// /// /// public double GetDistanceTo(LLVector3 Pt) { return Math.Sqrt(((X - Pt.X) * (X - Pt.X)) + ((Y - Pt.Y) * (Y - Pt.Y)) + ((Z - Pt.Z) * (Z - Pt.Z))); } /// /// Get a formatted string representation of the vector /// /// A string representation of the vector, similar to the LSL /// vector to string conversion in Second Life public override string ToString() { return String.Format("<{0}, {1}, {2}>", X, Y, Z); } /// /// 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()); } /// /// Generate an LLVector3 from a string /// /// A string representation of a 3D vector, enclosed /// in arrow brackets and separated by commas public static LLVector3 Parse(string val) { char[] splitChar = { ',', ' ' }; string[] split = val.Replace("<","").Replace(">","").Split(splitChar, StringSplitOptions.RemoveEmptyEntries); return new LLVector3(float.Parse(split[0].Trim()), float.Parse(split[1].Trim()), float.Parse(split[2].Trim())); } public static bool TryParse(string val, out LLVector3 result) { try { result = Parse(val); return true; } catch (Exception) { result = new LLVector3(); return false; } } /// /// /// /// /// public override bool Equals(object o) { if (!(o is LLVector3)) return false; LLVector3 vector = (LLVector3)o; return (X == vector.X && Y == vector.Y && Z == vector.Z); } /// /// /// /// /// /// public static bool operator==(LLVector3 lhs, LLVector3 rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z); } /// /// /// /// /// /// public static bool operator!=(LLVector3 lhs, LLVector3 rhs) { return !(lhs == rhs); } public static LLVector3 operator +(LLVector3 lhs, LLVector3 rhs) { return new LLVector3(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z); } /// /// /// /// /// /// public static LLVector3 operator -(LLVector3 lhs, LLVector3 rhs) { return new LLVector3(lhs.X - rhs.X,lhs.Y - rhs.Y, lhs.Z - rhs.Z); } /// /// /// /// /// /// public static LLVector3 operator *(LLVector3 vec, float val) { return new LLVector3(vec.X * val, vec.Y * val, vec.Z * val); } /// /// /// /// /// /// public static LLVector3 operator *(float val, LLVector3 vec) { return new LLVector3(vec.X * val, vec.Y * val, vec.Z * val); } /// /// /// /// /// /// public static LLVector3 operator *(LLVector3 lhs, LLVector3 rhs) { return new LLVector3(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z); } public static LLVector3 operator *(LLVector3 vec, LLQuaternion quat) { LLQuaternion vq = new LLQuaternion(vec.X, vec.Y, vec.Z, 0); LLQuaternion nq = new LLQuaternion(-quat.X, -quat.Y, -quat.Z, quat.W); LLQuaternion result = (quat * vq) * nq; return new LLVector3(result.X, result.Y, result.Z); } /// /// An LLVector3 with a value of 0,0,0 /// public readonly static LLVector3 Zero = new LLVector3(); } /// /// A double-precision three-dimensional vector /// [Serializable] public struct LLVector3d { /// X value [XmlAttribute] public double X; /// Y value [XmlAttribute] public double Y; /// Z value [XmlAttribute] public double Z; /// /// /// /// /// /// public LLVector3d(double x, double y, double z) { X = x; Y = y; Z = z; } /// /// Create a double precision vector from a float vector /// /// public LLVector3d(LLVector3 llv3) { X = llv3.X; Y = llv3.Y; Z = llv3.Z; } /// /// /// /// /// public LLVector3d(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { byte[] newArray = new byte[24]; Buffer.BlockCopy(byteArray, pos, newArray, 0, 24); Array.Reverse(newArray, 0, 8); Array.Reverse(newArray, 8, 8); Array.Reverse(newArray, 16, 8); X = BitConverter.ToDouble(newArray, 0); Y = BitConverter.ToDouble(newArray, 8); Z = BitConverter.ToDouble(newArray, 16); } else { X = BitConverter.ToDouble(byteArray, pos); Y = BitConverter.ToDouble(byteArray, pos + 8); Z = BitConverter.ToDouble(byteArray, pos + 16); } } /// /// 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 LLVector3d)) return false; LLVector3d vector = (LLVector3d)o; return (X == vector.X && Y == vector.Y && Z == vector.Z); } /// /// /// /// /// /// public static bool operator ==(LLVector3d lhs, LLVector3d rhs) { return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z); } /// /// /// /// /// /// public static bool operator !=(LLVector3d lhs, LLVector3d rhs) { return !(lhs == rhs); } /// /// Get the distance to point /// /// /// public double GetDistanceTo(LLVector3d Pt) { return Math.Sqrt(((X - Pt.X) * (X - Pt.X)) + ((Y - Pt.Y) * (Y - Pt.Y)) + ((Z - Pt.Z) * (Z - Pt.Z))); } /// /// /// /// 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 override string ToString() { return String.Format("<{0}, {1}, {2}>", X, Y, Z); } /// /// An LLVector3d with a value of 0,0,0 /// public static readonly LLVector3d Zero = new LLVector3d(); } /// /// A four-dimensional vector /// [Serializable] public struct LLVector4 { /// [XmlAttribute] public float X; /// [XmlAttribute] public float Y; /// [XmlAttribute] public float Z; /// [XmlAttribute] public float S; /// /// /// /// /// public LLVector4(byte[] byteArray, int pos) { if (!BitConverter.IsLittleEndian) { byte[] newArray = new byte[16]; Buffer.BlockCopy(byteArray, pos, newArray, 0, 16); Array.Reverse(newArray, 0, 4); Array.Reverse(newArray, 4, 4); Array.Reverse(newArray, 8, 4); Array.Reverse(newArray, 12, 4); X = BitConverter.ToSingle(newArray, 0); Y = BitConverter.ToSingle(newArray, 4); Z = BitConverter.ToSingle(newArray, 8); S = BitConverter.ToSingle(newArray, 12); } else { 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 override string ToString() { return String.Format("<{0}, {1}, {2}, {3}>", X, Y, Z, S); } /// /// An LLVector4 with a value of 0,0,0,0 /// public readonly static LLVector4 Zero = new LLVector4(); } /// /// An 8-bit color structure including an alpha channel /// [Serializable] public struct LLColor { /// Red [XmlAttribute] public float R; /// Green [XmlAttribute] public float G; /// Blue [XmlAttribute] public float B; /// Alpha [XmlAttribute] public float A; /// /// /// /// /// /// /// public LLColor(byte r, byte g, byte b, byte a) { float quanta = 1.0f / 255.0f; R = (float)r * quanta; G = (float)g * quanta; B = (float)b * quanta; A = (float)a * quanta; } /// /// /// /// /// public LLColor(byte[] byteArray, int pos) { float quanta = 1.0f / 255.0f; R = (float)byteArray[pos] * quanta; G = (float)byteArray[pos + 1] * quanta; B = (float)byteArray[pos + 2] * quanta; A = (float)byteArray[pos + 3] * quanta; } /// /// /// /// public byte[] GetBytes() { byte[] byteArray = new byte[4]; byteArray[0] = Helpers.FloatToByte(R, 0.0f, 255.0f); byteArray[1] = Helpers.FloatToByte(G, 0.0f, 255.0f); byteArray[2] = Helpers.FloatToByte(B, 0.0f, 255.0f); byteArray[3] = Helpers.FloatToByte(A, 0.0f, 255.0f); return byteArray; } /// /// /// /// public override string ToString() { return String.Format("<{0}, {1}, {2}, {3}>", R, G, B, A); } /// /// /// /// public string ToStringRGB() { return String.Format("<{0}, {1}, {2}>", R, G, B); } /// /// An LLColor with a value of 0,0,0,255 /// public readonly static LLColor Black = new LLColor(0, 0, 0, 255); } /// /// A quaternion, used for rotations /// [Serializable] public struct LLQuaternion { /// X value [XmlAttribute] public float X; /// Y value [XmlAttribute] public float Y; /// Z value [XmlAttribute] public float Z; /// W value [XmlAttribute] public float W; /// /// Build 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 LLQuaternion(byte[] byteArray, int pos, bool normalized) { if (!normalized) { if (!BitConverter.IsLittleEndian) { byte[] newArray = new byte[16]; Buffer.BlockCopy(byteArray, pos, newArray, 0, 16); Array.Reverse(newArray, 0, 4); Array.Reverse(newArray, 4, 4); Array.Reverse(newArray, 8, 4); Array.Reverse(newArray, 12, 4); X = BitConverter.ToSingle(newArray, 0); Y = BitConverter.ToSingle(newArray, 4); Z = BitConverter.ToSingle(newArray, 8); W = BitConverter.ToSingle(newArray, 12); } else { 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) { byte[] newArray = new byte[12]; Buffer.BlockCopy(byteArray, pos, newArray, 0, 12); Array.Reverse(newArray, 0, 4); Array.Reverse(newArray, 4, 4); Array.Reverse(newArray, 8, 4); X = BitConverter.ToSingle(newArray, 0); Y = BitConverter.ToSingle(newArray, 4); Z = BitConverter.ToSingle(newArray, 8); } else { 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; } } /// /// 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 LLQuaternion(float x, float y, float z) { 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 LLQuaternion(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } /// /// 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; 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 override int GetHashCode() { return (X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode()); } /// /// /// /// /// public override bool Equals(object o) { if (!(o is LLQuaternion)) return false; LLQuaternion quaternion = (LLQuaternion)o; return X == quaternion.X && Y == quaternion.Y && Z == quaternion.Z && W == quaternion.W; } /// /// Comparison operator /// /// /// /// public static bool operator ==(LLQuaternion lhs, LLQuaternion rhs) { // Return true if the fields match: return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z && lhs.W == rhs.W; } /// /// Not comparison operator /// /// /// /// public static bool operator !=(LLQuaternion lhs, LLQuaternion rhs) { return !(lhs == rhs); } /// /// Multiplication operator /// /// /// /// public static LLQuaternion operator *(LLQuaternion lhs, LLQuaternion rhs) { LLQuaternion ret = new LLQuaternion(); ret.W = lhs.W * rhs.W - lhs.X * rhs.X - lhs.Y * rhs.Y - lhs.Z * rhs.Z; ret.X = lhs.W * rhs.X + lhs.X * rhs.W + lhs.Y * rhs.Z - lhs.Z * rhs.Y; ret.Y = lhs.W * rhs.Y + lhs.Y * rhs.W + lhs.Z * rhs.X - lhs.X * rhs.Z; ret.Z = lhs.W * rhs.Z + lhs.Z * rhs.W + lhs.X * rhs.Y - lhs.Y * rhs.X; return ret; } /// /// /// /// public override string ToString() { return "<" + X.ToString() + ", " + Y.ToString() + ", " + Z.ToString() + ", " + W.ToString() + ">"; } /// /// An LLQuaternion with a value of 0,0,0,1 /// public readonly static LLQuaternion Identity = new LLQuaternion(0, 0, 0, 1); } }