/* * Copyright (c) 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.IO; namespace libsecondlife { public abstract partial class LLObject { #region Enumerations /// /// The type of bump-mapping applied to a face /// public enum Bumpiness : byte { /// None = 0, /// Brightness = 1, /// Darkness = 2, /// Woodgrain = 3, /// Bark = 4, /// Bricks = 5, /// Checker = 6, /// Concrete = 7, /// Crustytile = 8, /// Cutstone = 9, /// Discs = 10, /// Gravel = 11, /// Petridish = 12, /// Siding = 13, /// Stonetile = 14, /// Stucco = 15, /// Suction = 16, /// Weave = 17 } /// /// The level of shininess applied to a face /// public enum Shininess { /// None = 0, /// Low = 0x40, /// Medium = 0x80, /// High = 0xC0 } /// /// The texture mapping style used for a face /// public enum Mapping { /// Default = 0, /// Planar = 2 } /// /// Flags in the TextureEntry block that describe which properties are /// set /// [Flags] public enum TextureAttributes : uint { /// None = 0, /// TextureID = 1 << 0, /// RGBA = 1 << 1, /// RepeatU = 1 << 2, /// RepeatV = 1 << 3, /// OffsetU = 1 << 4, /// OffsetV = 1 << 5, /// Rotation = 1 << 6, /// Material = 1 << 7, /// Media = 1 << 8, /// All = 0xFFFFFFFF } #endregion Enumerations /// /// Represents all of the texturable faces for an object /// /// Objects in Second Life have infinite faces, with each face /// using the properties of the default face unless set otherwise. So if /// you have a TextureEntry with a default texture uuid of X, and face 72 /// has a texture UUID of Y, every face would be textured with X except for /// face 72 that uses Y. In practice however, primitives utilize a maximum /// of nine faces and avatars utilize [Serializable] public class TextureEntry2 { /// public TextureEntryFace DefaultTexture; /// public TextureEntryFace[] FaceTextures; private const int MAX_FACES = 32; /// /// Default constructor, DefaultTexture will be null /// //public TextureEntry2() //{ // DefaultTexture = null; // FaceTextures = new TextureEntryFace[0]; //} /// /// Constructor that takes a default texture UUID /// /// Texture UUID to use as the default texture public TextureEntry2(LLUUID defaultTextureID) { DefaultTexture = new TextureEntryFace(null); DefaultTexture.TextureID = defaultTextureID; FaceTextures = new TextureEntryFace[MAX_FACES]; } /// /// Constructor that creates the TextureEntry class from a byte array /// /// Byte array containing the TextureEntry field /// Starting position of the TextureEntry field in /// the byte array /// Length of the TextureEntry field, in bytes public TextureEntry2(byte[] data, int pos, int length) { FromBytes(data, pos, length); } /// /// This will either create a new face if a custom face for the given /// index is not defined, or return the custom face for that index if /// it already exists /// /// The index number of the face to create or /// retrieve /// A TextureEntryFace containing all the properties for that /// face public TextureEntryFace CreateFace(uint index) { if (index >= MAX_FACES) throw new Exception(index + " is outside the range of MAX_FACES"); if (FaceTextures[index] == null) FaceTextures[index] = new TextureEntryFace(this.DefaultTexture); return FaceTextures[index]; } private void FromBytes(byte[] data, int pos, int length) { if (length <= 0) { // No TextureEntry to process DefaultTexture = null; FaceTextures = new TextureEntryFace[0]; return; } else { DefaultTexture = new TextureEntryFace(null); FaceTextures = new TextureEntryFace[MAX_FACES]; } uint bitfieldSize = 0; uint faceBits = 0; int i = pos; #region Texture DefaultTexture.TextureID = new LLUUID(data, i); i += 16; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { LLUUID tmpUUID = new LLUUID(data, i); i += 16; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).TextureID = tmpUUID; } #endregion Texture #region Color DefaultTexture.RGBA = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); i += 4; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { uint tmpUint = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); i += 4; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).RGBA = tmpUint; } #endregion Color #region RepeatU DefaultTexture.RepeatU = Helpers.BytesToFloat(data, i); i += 4; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { float tmpFloat = Helpers.BytesToFloat(data, i); i += 4; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).RepeatU = tmpFloat; } #endregion RepeatU #region RepeatV DefaultTexture.RepeatV = Helpers.BytesToFloat(data, i); i += 4; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { float tmpFloat = Helpers.BytesToFloat(data, i); i += 4; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).RepeatV = tmpFloat; } #endregion RepeatV #region OffsetU DefaultTexture.OffsetU = Helpers.TEOffsetFloat(data, i); i += 2; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { float tmpFloat = Helpers.TEOffsetFloat(data, i); i += 2; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).OffsetU = tmpFloat; } #endregion OffsetU #region OffsetV DefaultTexture.OffsetV = Helpers.TEOffsetFloat(data, i); i += 2; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { float tmpFloat = Helpers.TEOffsetFloat(data, i); i += 2; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).OffsetV = tmpFloat; } #endregion OffsetV #region Rotation DefaultTexture.Rotation = Helpers.TERotationFloat(data, i); i += 2; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { float tmpFloat = Helpers.TERotationFloat(data, i); i += 2; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).Rotation = tmpFloat; } #endregion Rotation #region Material DefaultTexture.material = data[i]; i++; while (ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { byte tmpByte = data[i]; i++; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).material = tmpByte; } #endregion Material #region Media DefaultTexture.media = data[i]; i++; while (i - pos < length && ReadFaceBitfield(data, ref i, ref faceBits, ref bitfieldSize)) { byte tmpByte = data[i]; i++; for (uint face = 0, bit = 1; face < bitfieldSize; face++, bit <<= 1) if ((faceBits & bit) != 0) CreateFace(face).media = tmpByte; } #endregion Media } /// /// /// /// public byte[] ToBytes() { if (DefaultTexture == null) return new byte[0]; MemoryStream memStream = new MemoryStream(); BinaryWriter binWriter = new BinaryWriter(memStream); #region Bitfield Setup uint[] textures = new uint[FaceTextures.Length]; InitializeArray(ref textures); uint[] rgbas = new uint[FaceTextures.Length]; InitializeArray(ref rgbas); uint[] repeatus = new uint[FaceTextures.Length]; InitializeArray(ref repeatus); uint[] repeatvs = new uint[FaceTextures.Length]; InitializeArray(ref repeatvs); uint[] offsetus = new uint[FaceTextures.Length]; InitializeArray(ref offsetus); uint[] offsetvs = new uint[FaceTextures.Length]; InitializeArray(ref offsetvs); uint[] rotations = new uint[FaceTextures.Length]; InitializeArray(ref rotations); uint[] materials = new uint[FaceTextures.Length]; InitializeArray(ref materials); uint[] medias = new uint[FaceTextures.Length]; InitializeArray(ref medias); for (int i = 0; i < FaceTextures.Length; i++) { if (FaceTextures[i] == null) continue; if (FaceTextures[i].TextureID != DefaultTexture.TextureID) { if (textures[i] == UInt32.MaxValue) textures[i] = 0; textures[i] |= (uint)(1 << i); } if (FaceTextures[i].RGBA != DefaultTexture.RGBA) { if (rgbas[i] == UInt32.MaxValue) rgbas[i] = 0; rgbas[i] |= (uint)(1 << i); } if (FaceTextures[i].RepeatU != DefaultTexture.RepeatU) { if (repeatus[i] == UInt32.MaxValue) repeatus[i] = 0; repeatus[i] |= (uint)(1 << i); } if (FaceTextures[i].RepeatV != DefaultTexture.RepeatV) { if (repeatvs[i] == UInt32.MaxValue) repeatvs[i] = 0; repeatvs[i] |= (uint)(1 << i); } if (Helpers.TEOffsetShort(FaceTextures[i].OffsetU) != Helpers.TEOffsetShort(DefaultTexture.OffsetU)) { if (offsetus[i] == UInt32.MaxValue) offsetus[i] = 0; offsetus[i] |= (uint)(1 << i); } if (Helpers.TEOffsetShort(FaceTextures[i].OffsetV) != Helpers.TEOffsetShort(DefaultTexture.OffsetV)) { if (offsetvs[i] == UInt32.MaxValue) offsetvs[i] = 0; offsetvs[i] |= (uint)(1 << i); } if (Helpers.TERotationShort(FaceTextures[i].Rotation) != Helpers.TERotationShort(DefaultTexture.Rotation)) { if (rotations[i] == UInt32.MaxValue) rotations[i] = 0; rotations[i] |= (uint)(1 << i); } if (FaceTextures[i].material != DefaultTexture.material) { if (materials[i] == UInt32.MaxValue) materials[i] = 0; materials[i] |= (uint)(1 << i); } if (FaceTextures[i].media != DefaultTexture.media) { if (medias[i] == UInt32.MaxValue) medias[i] = 0; medias[i] |= (uint)(1 << i); } } #endregion Bitfield Setup #region Texture binWriter.Write(DefaultTexture.TextureID.GetBytes()); for (int i = 0; i < textures.Length; i++) { if (textures[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(textures[i])); binWriter.Write(FaceTextures[i].TextureID.GetBytes()); } } binWriter.Write((byte)0); #endregion Texture #region Color binWriter.Write(DefaultTexture.RGBA); for (int i = 0; i < rgbas.Length; i++) { if (rgbas[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(rgbas[i])); binWriter.Write(FaceTextures[i].RGBA); } } binWriter.Write((byte)0); #endregion Color #region RepeatU binWriter.Write(DefaultTexture.RepeatU); for (int i = 0; i < repeatus.Length; i++) { if (repeatus[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(repeatus[i])); binWriter.Write(FaceTextures[i].RepeatU); } } binWriter.Write((byte)0); #endregion RepeatU #region RepeatV binWriter.Write(DefaultTexture.RepeatV); for (int i = 0; i < repeatvs.Length; i++) { if (repeatvs[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(repeatvs[i])); binWriter.Write(FaceTextures[i].RepeatV); } } binWriter.Write((byte)0); #endregion RepeatV #region OffsetU binWriter.Write(Helpers.TEOffsetShort(DefaultTexture.OffsetU)); for (int i = 0; i < offsetus.Length; i++) { if (offsetus[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(offsetus[i])); binWriter.Write(Helpers.TEOffsetShort(FaceTextures[i].OffsetU)); } } binWriter.Write((byte)0); #endregion OffsetU #region OffsetV binWriter.Write(Helpers.TEOffsetShort(DefaultTexture.OffsetV)); for (int i = 0; i < offsetvs.Length; i++) { if (offsetvs[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(offsetvs[i])); binWriter.Write(Helpers.TEOffsetShort(FaceTextures[i].OffsetV)); } } binWriter.Write((byte)0); #endregion OffsetV #region Rotation binWriter.Write(Helpers.TERotationShort(DefaultTexture.Rotation)); for (int i = 0; i < rotations.Length; i++) { if (rotations[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(rotations[i])); binWriter.Write(Helpers.TERotationShort(FaceTextures[i].Rotation)); } } binWriter.Write((byte)0); #endregion Rotation #region Material binWriter.Write(DefaultTexture.material); for (int i = 0; i < materials.Length; i++) { if (materials[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(materials[i])); binWriter.Write(FaceTextures[i].material); } } binWriter.Write((byte)0); #endregion Material #region Media binWriter.Write(DefaultTexture.media); for (int i = 0; i < medias.Length; i++) { if (medias[i] != UInt32.MaxValue) { binWriter.Write(GetFaceBitfieldBytes(medias[i])); binWriter.Write(FaceTextures[i].media); } } #endregion Media return memStream.ToArray(); } /// /// /// /// public override string ToString() { string output = String.Empty; output += "Default Face: " + DefaultTexture.ToString() + Environment.NewLine; for (int i = 0; i < FaceTextures.Length; i++) { if (FaceTextures[i] != null) output += "Face " + i + ": " + FaceTextures[i].ToString() + Environment.NewLine; } return output; } private void InitializeArray(ref uint[] array) { for (int i = 0; i < array.Length; i++) array[i] = UInt32.MaxValue; } private bool ReadFaceBitfield(byte[] data, ref int pos, ref uint faceBits, ref uint bitfieldSize) { faceBits = 0; bitfieldSize = 0; if (pos >= data.Length) return false; byte b = 0; do { b = data[pos]; faceBits = (faceBits << 7) | (uint)(b & 0x7F); bitfieldSize += 7; pos++; } while ((b & 0x80) != 0); return (faceBits != 0); } private byte[] GetFaceBitfieldBytes(uint bitfield) { int byteLength = 0; uint tmpBitfield = bitfield; while (tmpBitfield != 0) { tmpBitfield >>= 7; byteLength++; } if (byteLength == 0) return new byte[1] { 0 }; byte[] bytes = new byte[byteLength]; for (int i = 0; i < byteLength; i++) { bytes[i] = (byte)((bitfield >> (7 * (byteLength - i - 1))) & 0x7F); if (i < byteLength - 1) bytes[i] |= 0x80; } return bytes; } } } }