diff --git a/OpenMetaverse/AppearanceManager.cs b/OpenMetaverse/AppearanceManager.cs
index 93322974..da994887 100644
--- a/OpenMetaverse/AppearanceManager.cs
+++ b/OpenMetaverse/AppearanceManager.cs
@@ -36,79 +36,69 @@ using OpenMetaverse.Assets;
namespace OpenMetaverse
{
- public class InvalidOutfitException : Exception
+ #region Enums
+
+ ///
+ /// Index of TextureEntry slots for avatar appearances
+ ///
+ public enum AvatarTextureIndex
{
- public InvalidOutfitException(string message) : base(message) { }
+ Unknown = -1,
+ HeadBodypaint = 0,
+ UpperShirt,
+ LowerPants,
+ EyesIris,
+ Hair,
+ UpperBodypaint,
+ LowerBodypaint,
+ LowerShoes,
+ HeadBaked,
+ UpperBaked,
+ LowerBaked,
+ EyesBaked,
+ LowerSocks,
+ UpperJacket,
+ LowerJacket,
+ UpperGloves,
+ UpperUndershirt,
+ LowerUnderpants,
+ Skirt,
+ SkirtBaked,
+ HairBaked
}
///
- /// Manager class to for agents appearance, both body parts and clothing
+ /// Bake layers for avatar appearance
///
+ public enum BakeType
+ {
+ Unknown = -1,
+ Head = 0,
+ UpperBody = 1,
+ LowerBody = 2,
+ Eyes = 3,
+ Skirt = 4,
+ Hair = 5
+ }
+
+ #endregion Enums
+
public class AppearanceManager
{
- ///
- ///
- ///
- public enum TextureIndex
- {
- Unknown = -1,
- HeadBodypaint = 0,
- UpperShirt,
- LowerPants,
- EyesIris,
- Hair,
- UpperBodypaint,
- LowerBodypaint,
- LowerShoes,
- HeadBaked,
- UpperBaked,
- LowerBaked,
- EyesBaked,
- LowerSocks,
- UpperJacket,
- LowerJacket,
- UpperGloves,
- UpperUndershirt,
- LowerUnderpants,
- Skirt,
- SkirtBaked,
- HairBaked
- }
+ #region Constants
- ///
- ///
- ///
- public enum BakeType
- {
- Unknown = -1,
- Head = 0,
- UpperBody = 1,
- LowerBody = 2,
- Eyes = 3,
- Skirt = 4,
- Hair = 5
- }
-
- public class WearableData
- {
- public InventoryWearable Item;
- public AssetWearable Asset;
- }
-
- ///
- ///
- ///
- public delegate void AgentWearablesCallback();
- ///
- ///
- ///
- ///
- public delegate void AppearanceUpdatedCallback(Primitive.TextureEntry te);
-
- ///
- public event AgentWearablesCallback OnAgentWearables;
- ///
- public event AppearanceUpdatedCallback OnAppearanceUpdated;
+ /// Maximum number of concurrent downloads for wearable assets and textures
+ const int MAX_CONCURRENT_DOWNLOADS = 5;
+ /// Maximum number of concurrent uploads for baked textures
+ const int MAX_CONCURRENT_UPLOADS = 3;
+ /// Timeout for fetching inventory listings
+ const int INVENTORY_TIMEOUT = 1000 * 20;
+ /// Timeout for fetching a single wearable
+ const int WEARABLE_TIMEOUT = 1000 * 10;
+ /// Timeout for fetching a single texture
+ const int TEXTURE_TIMEOUT = 1000 * 30;
+ /// Timeout for uploading a single baked texture
+ const int UPLOAD_TIMEOUT = 1000 * 30;
/// Total number of wearables for each avatar
public const int WEARABLE_COUNT = 13;
@@ -128,7 +118,7 @@ namespace OpenMetaverse
new WearableType[] { WearableType.Skirt, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
new WearableType[] { WearableType.Hair, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid }
};
- /// Secret values to finalize the cache check hashes for each
+ /// Magic values to finalize the cache check hashes for each
/// bake
public static readonly UUID[] BAKED_TEXTURE_HASH = new UUID[]
{
@@ -143,378 +133,285 @@ namespace OpenMetaverse
/// texture is not set for a face
public static readonly UUID DEFAULT_AVATAR_TEXTURE = new UUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
- private GridClient Client;
+ #endregion Constants
+
+ #region Structs / Classes
///
- /// An which keeps track of wearables data
+ /// A tuple containing a wearable inventory item and the corresponding asset
///
- public InternalDictionary Wearables = new InternalDictionary();
- // As wearable assets are downloaded and decoded, the textures are added to this array
- private UUID[] AgentTextures = new UUID[AVATAR_TEXTURE_COUNT];
- // A cache of the textures worn, needed for rebaking
- private AssetTexture[] AgentAssets = new AssetTexture[AVATAR_TEXTURE_COUNT];
-
- protected struct PendingAssetDownload
+ private class WearableData
{
- public UUID Id;
- public AssetType Type;
+ /// Inventory ItemID of the wearable
+ public UUID ItemID;
+ /// AssetID of the wearable asset
+ public UUID AssetID;
+ /// WearableType of the wearable
+ public WearableType WearableType;
+ /// AssetType of the wearable
+ public AssetType AssetType;
+ /// Asset data for the wearable
+ public AssetWearable Asset;
- public PendingAssetDownload(UUID id, AssetType type)
+ public override string ToString()
{
- Id = id;
- Type = type;
+ return String.Format("ItemID: {0}, AssetID: {1}, WearableType: {2}, AssetType: {3}, Asset: {4}",
+ ItemID, AssetID, WearableType, AssetType, Asset != null ? Asset.Name : "(null)");
}
}
- // Wearable assets are downloaded one at a time, a new request is pulled off the queue
- // and started when the previous one completes
- private Queue AssetDownloads = new Queue();
- // A list of all the images we are currently downloading, prior to baking
- private Dictionary ImageDownloads = new Dictionary();
- // A list of all the bakes we need to complete
- private Dictionary PendingBakes = new Dictionary(BAKED_TEXTURE_COUNT);
- // A list of all the uploads that are in progress
- private Dictionary PendingUploads = new Dictionary(BAKED_TEXTURE_COUNT);
- // Whether the handler for our current wearable list should automatically start downloading the assets
- //private bool DownloadWearables = false;
- private static int CacheCheckSerialNum = 1; //FIXME
- private static uint SetAppearanceSerialNum = 1; //FIXME
- private AutoResetEvent WearablesRequestEvent = new AutoResetEvent(false);
- private AutoResetEvent WearablesDownloadedEvent = new AutoResetEvent(false);
- private AutoResetEvent CachedResponseEvent = new AutoResetEvent(false);
- private AutoResetEvent UpdateEvent = new AutoResetEvent(false);
- // FIXME: Create a class-level appearance thread so multiple threads can't be launched
+ ///
+ /// A tuple containing a TextureID and a texture asset. Used to keep track
+ /// of currently worn textures and the corresponding texture data for baking
+ ///
+ private struct TextureData
+ {
+ /// A texture AssetID
+ public UUID TextureID;
+ /// Asset data for the texture
+ public AssetTexture Texture;
+
+ public override string ToString()
+ {
+ return String.Format("TextureID: {0}, Texture: {1}",
+ TextureID, Texture != null ? Texture.AssetData.Length + " bytes" : "(null)");
+ }
+ }
+
+ #endregion Structs / Classes
+
+ #region Delegates / Events
+
+ /// Triggered when an AgentWearablesUpdate packet is received,
+ /// telling us what our avatar is currently wearing
+ public delegate void AgentWearablesCallback();
+ /// Triggered when an AgentCachedTextureResponse packet is
+ /// received, giving a list of cached bakes that were found on the
+ /// server
+ public delegate void AgentCachedBakesCallback();
+
+ /// Triggered when an AgentWearablesUpdate packet is received,
+ /// telling us what our avatar is currently wearing
+ public event AgentWearablesCallback OnAgentWearables;
+ /// Triggered when an AgentCachedTextureResponse packet is
+ /// received, giving a list of cached bakes that were found on the
+ /// server
+ public event AgentCachedBakesCallback OnAgentCachedBakes;
+
+ #endregion Delegates / Events
+
+ #region Private Members
+
+ /// A cache of wearables currently being worn
+ private Dictionary Wearables = new Dictionary();
+ /// A cache of textures currently being worn
+ private TextureData[] Textures = new TextureData[AVATAR_TEXTURE_COUNT];
+ /// Incrementing serial number for AgentCachedTexture packets
+ private int CacheCheckSerialNum = -1;
+ /// Incrementing serial number for AgentSetAppearance packets
+ private int SetAppearanceSerialNum = 0;
+ /// Indicates whether or not the appearance thread is currently
+ /// running, to prevent multiple appearance threads from running
+ /// simultaneously
+ private int AppearanceThreadRunning = 0;
+ /// Reference to our agent
+ private GridClient Client;
+
+ #endregion Private Members
///
/// Default constructor
///
- /// This agents Object
+ /// A reference to our agent
public AppearanceManager(GridClient client)
{
Client = client;
- // Initialize AgentTextures to zero UUIDs
- for (int i = 0; i < AgentTextures.Length; i++)
- AgentTextures[i] = UUID.Zero;
-
Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, AgentWearablesUpdateHandler);
Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, AgentCachedTextureResponseHandler);
- Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures, RebakeAvatarTexturesHandler);
- Client.Network.OnDisconnected += Network_OnDisconnected;
+ //Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures, RebakeAvatarTexturesHandler);
+
+ Client.Network.OnEventQueueRunning += Network_OnEventQueueRunning;
}
- private static AssetType WearableTypeToAssetType(WearableType type)
+ #region Publics Methods
+
+ ///
+ /// Obsolete method for setting appearance. This function no longer does anything.
+ /// Use RequestSetAppearance() to manually start the appearance thread
+ ///
+ /// Unused parameter
+ [Obsolete("Appearance is now handled automatically")]
+ public void SetPreviousAppearance(bool allowBake)
{
- switch (type)
+ }
+
+ ///
+ /// Starts the appearance setting thread
+ ///
+ public void RequestSetAppearance()
+ {
+ RequestSetAppearance(false);
+ }
+
+ ///
+ /// Starts the appearance setting thread
+ ///
+ /// True to force rebaking, otherwise false
+ public void RequestSetAppearance(bool forceRebake)
+ {
+ if (Interlocked.CompareExchange(ref AppearanceThreadRunning, 1, 0) != 0)
{
- case WearableType.Shape:
- case WearableType.Skin:
- case WearableType.Hair:
- case WearableType.Eyes:
- return AssetType.Bodypart;
- case WearableType.Shirt:
- case WearableType.Pants:
- case WearableType.Shoes:
- case WearableType.Socks:
- case WearableType.Jacket:
- case WearableType.Gloves:
- case WearableType.Undershirt:
- case WearableType.Underpants:
- case WearableType.Skirt:
- return AssetType.Clothing;
- default:
- throw new Exception("Unhandled wearable type " + type);
- }
- }
-
- ///
- /// Returns the assetID for a given WearableType
- ///
- /// the of the asset
- /// The of the WearableType
- public UUID GetWearableAsset(WearableType type)
- {
- WearableData wearable;
-
- if (Wearables.TryGetValue(type, out wearable))
- return wearable.Item.AssetUUID;
- else
- return UUID.Zero;
- }
-
- ///
- /// Ask the server what we are wearing and set appearance based on that
- ///
- public void SetPreviousAppearance()
- {
- SetPreviousAppearance(true);
- }
-
- public void SetPreviousAppearance(bool bake)
- {
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartSetPreviousAppearance));
- appearanceThread.Start(bake);
- }
-
- private void StartSetPreviousAppearance(object thread_params)
- {
- bool bake = (bool)thread_params;
- SendAgentWearablesRequest();
- WearablesRequestEvent.WaitOne();
- UpdateAppearanceFromWearables(bake);
- }
-
- private class WearParams
- {
- public object Param;
- public bool Bake;
- public bool RemoveExistingAttachments;
-
- public WearParams(object param, bool bake, bool removeExistingAttachments)
- {
- Param = param;
- Bake = bake;
- RemoveExistingAttachments = removeExistingAttachments;
- }
- }
-
- ///
- /// Replace the current outfit with a list of wearables and set appearance
- ///
- /// List of wearables that define the new outfit
- public void WearOutfit(List ibs)
- {
- WearOutfit(ibs, true);
- }
-
- ///
- /// Replace the current outfit with a list of wearables and set appearance
- ///
- /// List of wearables that define the new outfit
- /// Whether to bake textures for the avatar or not
- public void WearOutfit(List ibs, bool bake)
- {
- WearParams wearParams = new WearParams(ibs, bake, true);
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfit));
- appearanceThread.Start(wearParams);
- }
-
- ///
- /// Add to the current outfit with the list supplied
- ///
- /// List of wearables that will be added to the outfit
- /// Whether to bake textures for the avatar or not
- public void AddToOutfit(List ibs_new, bool bake)
- {
- List ibs_total = new List();
-
- // Get what we are currently wearing
- lock (Wearables.Dictionary)
- {
- foreach (KeyValuePair kvp in Wearables.Dictionary)
- ibs_total.Add((InventoryBase)kvp.Value.Item);
-
- }
- // Add the new items at the end, ReplaceOutfitWearables() will do the right thing as it places each warable into a slot in order
- // so the end of the list will overwrite earlier parts if they use the same slot.
- foreach (InventoryBase item in ibs_new)
- {
- if (item is InventoryWearable)
- ibs_total.Add(item);
+ Logger.Log("Appearance thread is already running, skipping", Helpers.LogLevel.Warning);
+ return;
}
- WearParams wearParams = new WearParams(ibs_total, bake, false);
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfit));
- appearanceThread.Start(wearParams);
- }
-
- private void StartWearOutfit(object thread_params)
- {
- WearParams wearParams = (WearParams)thread_params;
-
- List ibs = (List)wearParams.Param;
- List wearables = new List();
- List attachments = new List();
-
- foreach (InventoryBase ib in ibs)
- {
- if (ib is InventoryWearable)
- wearables.Add((InventoryWearable)ib);
- else if (ib is InventoryAttachment || ib is InventoryObject)
- attachments.Add(ib);
- }
-
-
- SendAgentWearablesRequest();
- WearablesRequestEvent.WaitOne();
- ReplaceOutfitWearables(wearables);
- UpdateAppearanceFromWearables(wearParams.Bake);
- AddAttachments(attachments, wearParams.RemoveExistingAttachments);
- }
-
- ///
- /// Replace the current outfit with a folder and set appearance
- ///
- /// UUID of the inventory folder to wear
- public void WearOutfit(UUID folder)
- {
- WearOutfit(folder, true);
- }
-
- ///
- /// Replace the current outfit with a folder and set appearance
- ///
- /// Inventory path of the folder to wear
- public void WearOutfit(string[] path)
- {
- WearOutfit(path, true);
- }
-
- ///
- /// Replace the current outfit with a folder and set appearance
- ///
- /// Folder containing the new outfit
- /// Whether to bake the avatar textures or not
- public void WearOutfit(UUID folder, bool bake)
- {
- WearParams wearOutfitParams = new WearParams(folder, bake, true);
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
- appearanceThread.Start(wearOutfitParams);
- }
-
- ///
- /// Replace the current outfit with a folder and set appearance
- ///
- /// Path of folder containing the new outfit
- /// Whether to bake the avatar textures or not
- public void WearOutfit(string[] path, bool bake)
- {
- WearParams wearOutfitParams = new WearParams(path, bake, true);
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
- appearanceThread.Start(wearOutfitParams);
- }
-
- public void WearOutfit(InventoryFolder folder, bool bake)
- {
- WearParams wearOutfitParams = new WearParams(folder, bake, true);
- Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
- appearanceThread.Start(wearOutfitParams);
- }
-
- private void StartWearOutfitFolder(object thread_params)
- {
- WearParams wearOutfitParams = (WearParams)thread_params;
-
- SendAgentWearablesRequest(); // request current wearables async
- List wearables;
- List attachments;
-
- if (!GetFolderWearables(wearOutfitParams.Param, out wearables, out attachments)) // get wearables in outfit folder
- return; // TODO: this error condition should be passed back to the client somehow
-
- WearablesRequestEvent.WaitOne(); // wait for current wearables
- ReplaceOutfitWearables(wearables); // replace current wearables with outfit folder
- UpdateAppearanceFromWearables(wearOutfitParams.Bake);
- AddAttachments(attachments, wearOutfitParams.RemoveExistingAttachments);
- }
-
- private bool GetFolderWearables(object _folder, out List wearables, out List attachments)
- {
- UUID folder;
- wearables = null;
- attachments = null;
-
- if (_folder.GetType() == typeof(string[]))
- {
- string[] path = (string[])_folder;
-
- folder = Client.Inventory.FindObjectByPath(
- Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, String.Join("/", path), 1000 * 20);
-
- if (folder == UUID.Zero)
+ // This is the first time setting appearance, run through the entire sequence
+ Thread appearanceThread = new Thread(
+ delegate()
{
- Logger.Log("Outfit path " + path + " not found", Helpers.LogLevel.Error, Client);
- return false;
+ try
+ {
+ if (forceRebake)
+ {
+ // Set all of the baked textures to UUID.Zero to force rebaking
+ for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
+ Textures[(int)BakeTypeToAgentTextureIndex((BakeType)bakedIndex)].TextureID = UUID.Zero;
+ }
+
+ if (SetAppearanceSerialNum == 0)
+ {
+ // Fetch a list of the current agent wearables
+ if (!GetAgentWearables())
+ {
+ Logger.Log("Failed to retrieve a list of current agent wearables, appearance cannot be set",
+ Helpers.LogLevel.Error, Client);
+ return;
+ }
+ }
+
+ // Download and parse all of the agent wearables
+ if (!DownloadWearables())
+ {
+ Logger.Log("One or more agent wearables failed to download, appearance will be incomplete",
+ Helpers.LogLevel.Warning, Client);
+ }
+
+ // If this is the first time setting appearance and we're not forcing rebakes, check the server
+ // for cached bakes
+ if (SetAppearanceSerialNum == 0 && !forceRebake)
+ {
+ // Compute hashes for each bake layer and compare against what the simulator currently has
+ if (!GetCachedBakes())
+ {
+ Logger.Log("Failed to get a list of cached bakes from the simulator, appearance will be rebaked",
+ Helpers.LogLevel.Warning, Client);
+ }
+ }
+
+ // Download textures, compute bakes, and upload for any cache misses
+ if (!CreateBakes())
+ {
+ Logger.Log("Failed to create or upload one or more bakes, appearance will be incomplete",
+ Helpers.LogLevel.Warning, Client);
+ }
+
+ // Send the appearance packet
+ SendAgentSetAppearance();
+ }
+ finally
+ {
+ AppearanceThreadRunning = 0;
+ }
}
- }
- else
- folder = (UUID)_folder;
+ );
+ appearanceThread.Name = "Appearance";
+ appearanceThread.IsBackground = true;
+ appearanceThread.Start();
+ }
- wearables = new List();
- attachments = new List();
- List objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID,
- false, true, InventorySortOrder.ByName, 1000 * 20);
+ ///
+ /// Ask the server what textures our agent is currently wearing
+ ///
+ public void RequestAgentWearables()
+ {
+ AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
+ request.AgentData.AgentID = Client.Self.AgentID;
+ request.AgentData.SessionID = Client.Self.SessionID;
- if (objects != null)
+ Client.Network.SendPacket(request);
+ }
+
+ ///
+ /// Build hashes out of the texture assetIDs for each baking layer to
+ /// ask the simulator whether it has cached copies of each baked texture
+ ///
+ public void RequestCachedBakes()
+ {
+ List hashes = new List();
+
+ // Build hashes for each of the bake layers from the individual components
+ lock (Wearables)
{
- foreach (InventoryBase ib in objects)
+ for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
{
- if (ib is InventoryWearable)
+ // Don't do a cache request for a skirt bake if we're not wearing a skirt
+ if (bakedIndex == (int)BakeType.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
+ continue;
+
+ // Build a hash of all the texture asset IDs in this baking layer
+ UUID hash = UUID.Zero;
+ for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
{
- Logger.DebugLog("Adding wearable " + ib.Name, Client);
- wearables.Add((InventoryWearable)ib);
+ WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
+
+ WearableData wearable;
+ if (type != WearableType.Invalid && Wearables.TryGetValue(type, out wearable))
+ hash ^= wearable.AssetID;
}
- else if (ib is InventoryAttachment)
+
+ if (hash != UUID.Zero)
{
- Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client);
- attachments.Add(ib);
- }
- else if (ib is InventoryObject)
- {
- Logger.DebugLog("Adding attachment (object) " + ib.Name, Client);
- attachments.Add(ib);
- }
- else
- {
- Logger.DebugLog("Ignoring inventory item " + ib.Name, Client);
+ // Hash with our secret value for this baked layer
+ hash ^= BAKED_TEXTURE_HASH[bakedIndex];
+
+ // Add this to the list of hashes to send out
+ AgentCachedTexturePacket.WearableDataBlock block = new AgentCachedTexturePacket.WearableDataBlock();
+ block.ID = hash;
+ block.TextureIndex = (byte)bakedIndex;
+ hashes.Add(block);
+
+ Logger.DebugLog("Checking cache for " + (BakeType)block.TextureIndex + ", hash=" + block.ID, Client);
}
}
}
- else
+
+ // Only send the packet out if there's something to check
+ if (hashes.Count > 0)
{
- Logger.Log("Failed to download folder contents of + " + folder.ToString(),
- Helpers.LogLevel.Error, Client);
- return false;
- }
+ AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
+ cache.AgentData.AgentID = Client.Self.AgentID;
+ cache.AgentData.SessionID = Client.Self.SessionID;
+ cache.AgentData.SerialNum = Interlocked.Increment(ref CacheCheckSerialNum);
- return true;
- }
+ cache.WearableData = hashes.ToArray();
- // this method will download the assets for all inventory items in iws
- private void ReplaceOutfitWearables(List iws)
- {
- lock (Wearables.Dictionary)
- {
- Dictionary preserve = new Dictionary();
-
- foreach (KeyValuePair kvp in Wearables.Dictionary)
- {
- if (kvp.Value.Item.AssetType == AssetType.Bodypart)
- preserve.Add(kvp.Key, kvp.Value);
- }
-
- Wearables.Dictionary = preserve;
-
- foreach (InventoryWearable iw in iws)
- {
- WearableData wd = new WearableData();
- wd.Item = iw;
- Wearables.Dictionary[wd.Item.WearableType] = wd;
- }
+ Client.Network.SendPacket(cache);
}
}
+ #endregion Publics Methods
+
+ #region Attachments
+
///
- /// Adds a list of attachments to avatar
+ /// Adds a list of attachments to our agent
///
/// A List containing the attachments to add
/// If true, tells simulator to remove existing attachment
/// first
public void AddAttachments(List attachments, bool removeExistingFirst)
{
- // FIXME: Obey this
- //const int OBJECTS_PER_PACKET = 4;
-
// Use RezMultipleAttachmentsFromInv to clear out current attachments, and attach new ones
RezMultipleAttachmentsFromInvPacket attachmentsPacket = new RezMultipleAttachmentsFromInvPacket();
attachmentsPacket.AgentData.AgentID = Client.Self.AgentID;
@@ -557,8 +454,7 @@ namespace OpenMetaverse
}
else
{
- Logger.Log("Cannot attach inventory item of type " + attachments[i].GetType().ToString(),
- Helpers.LogLevel.Warning, Client);
+ Logger.Log("Cannot attach inventory item " + attachments[i].Name, Helpers.LogLevel.Warning, Client);
}
}
@@ -566,7 +462,7 @@ namespace OpenMetaverse
}
///
- /// Attach an item to an avatar at a specific attach point
+ /// Attach an item to our agent at a specific attach point
///
/// A to attach
/// the on the avatar
@@ -578,7 +474,7 @@ namespace OpenMetaverse
}
///
- /// Attach an item to an avatar specifying attachment details
+ /// Attach an item to our agent specifying attachment details
///
/// The of the item to attach
/// The attachments owner
@@ -586,14 +482,13 @@ namespace OpenMetaverse
/// The description of the attahment
/// The to apply when attached
/// The of the attachment
- /// the on the avatar
+ /// The on the agent
/// to attach the item to
public void Attach(UUID itemID, UUID ownerID, string name, string description,
Permissions perms, uint itemFlags, AttachmentPoint attachPoint)
{
// TODO: At some point it might be beneficial to have AppearanceManager track what we
// are currently wearing for attachments to make enumeration and detachment easier
-
RezSingleAttachmentFromInvPacket attach = new RezSingleAttachmentFromInvPacket();
attach.AgentData.AgentID = Client.Self.AgentID;
@@ -613,7 +508,7 @@ namespace OpenMetaverse
}
///
- /// Detach an item from avatar using an object
+ /// Detach an item from our agent using an object
///
/// An object
public void Detach(InventoryItem item)
@@ -622,9 +517,9 @@ namespace OpenMetaverse
}
///
- /// Detach an Item from avatar by items
+ /// Detach an item from our agent
///
- /// The items ID to detach
+ /// The inventory itemID of the item to detach
public void Detach(UUID itemID)
{
DetachAttachmentIntoInvPacket detach = new DetachAttachmentIntoInvPacket();
@@ -634,532 +529,353 @@ namespace OpenMetaverse
Client.Network.SendPacket(detach);
}
+ #endregion Attachments
- private void UpdateAppearanceFromWearables(bool bake)
+ #region Appearance Helpers
+
+ ///
+ /// Blocking method to populate the Wearables dictionary
+ ///
+ /// True on success, otherwise false
+ bool GetAgentWearables()
{
- lock (AgentTextures)
+ AutoResetEvent wearablesEvent = new AutoResetEvent(false);
+ AgentWearablesCallback wearablesCallback = delegate() { wearablesEvent.Set(); };
+
+ OnAgentWearables += wearablesCallback;
+
+ RequestAgentWearables();
+
+ bool success = wearablesEvent.WaitOne(1000 * 10);
+
+ OnAgentWearables -= wearablesCallback;
+
+ return success;
+ }
+
+ ///
+ /// Blocking method to populate the Textures array with cached bakes
+ ///
+ /// True on success, otherwise false
+ bool GetCachedBakes()
+ {
+ AutoResetEvent cacheCheckEvent = new AutoResetEvent(false);
+ AgentCachedBakesCallback cacheCallback = delegate() { cacheCheckEvent.Set(); };
+
+ OnAgentCachedBakes += cacheCallback;
+
+ RequestCachedBakes();
+
+ bool success = cacheCheckEvent.WaitOne(1000 * 10);
+
+ OnAgentCachedBakes -= cacheCallback;
+
+ return success;
+ }
+
+ ///
+ /// Blocking method to download and parse currently worn wearable assets
+ ///
+ /// True on success, otherwise false
+ private bool DownloadWearables()
+ {
+ bool success = true;
+
+ // Make a copy of the wearables dictionary to enumerate over
+ Dictionary wearables;
+ lock (Wearables)
+ wearables = new Dictionary(Wearables);
+
+ int pendingWearables = wearables.Count;
+ foreach (WearableData wearable in wearables.Values)
{
- for (int i = 0; i < AgentTextures.Length; i++)
- AgentTextures[i] = UUID.Zero;
+ if (wearable.Asset != null)
+ --pendingWearables;
}
- AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
- Client.Assets.OnAssetUploaded += uploadCallback;
+ if (pendingWearables == 0)
+ return true;
- // Download assets for what we are wearing and fill in AgentTextures
- DownloadWearableAssets();
- WearablesDownloadedEvent.WaitOne();
+ Logger.DebugLog("Downloading " + pendingWearables + " wearable assets");
- // Check if anything needs to be rebaked
- if (bake) RequestCachedBakes();
-
- // Tell the sim what we are wearing
- SendAgentIsNowWearing();
-
- // Wait for cached layer check to finish
- if (bake) CachedResponseEvent.WaitOne();
-
- // Unregister the image download and asset upload callbacks
- //Assets.OnImageReceived -= imageCallback;
- Client.Assets.OnAssetUploaded -= uploadCallback;
-
- Logger.DebugLog("CachedResponseEvent completed", Client);
-
- #region Send Appearance
-
- Primitive.TextureEntry te = null;
-
- ObjectManager.NewAvatarCallback updateCallback =
- delegate(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation)
+ Parallel.ForEach(Math.Min(pendingWearables, MAX_CONCURRENT_DOWNLOADS), wearables.Values,
+ delegate(WearableData wearable)
{
- if (avatar.LocalID == Client.Self.LocalID)
+ if (wearable.Asset == null)
{
- if (avatar.Textures.FaceTextures != null)
- {
- bool match = true;
+ AutoResetEvent downloadEvent = new AutoResetEvent(false);
- for (uint i = 0; i < AgentTextures.Length; i++)
+ // Fetch this wearable asset
+ Client.Assets.RequestAsset(wearable.AssetID, wearable.AssetType, true,
+ delegate(AssetDownload transfer, Asset asset)
{
- Primitive.TextureEntryFace face = avatar.Textures.FaceTextures[i];
-
- if (face == null)
+ if (transfer.Success && asset is AssetWearable)
{
- // If the texture is UUID.Zero the face should be null
- if (AgentTextures[i] != UUID.Zero)
+ // Update this wearable with the freshly downloaded asset
+ wearable.Asset = (AssetWearable)asset;
+
+ if (wearable.Asset.Decode())
{
- match = false;
- break;
+ Logger.DebugLog("Downloaded wearable asset with " + wearable.Asset.Params.Count +
+ " visual params and " + wearable.Asset.Textures.Count + " textures", Client);
+
+ // Loop through all of the texture IDs in this decoded asset and put them in our cache of worn textures
+ foreach (KeyValuePair entry in wearable.Asset.Textures)
+ {
+ int i = (int)entry.Key;
+
+ // If this texture changed, update the TextureID and clear out the old cached texture asset
+ if (Textures[i].TextureID != entry.Value)
+ {
+ // Treat DEFAULT_AVATAR_TEXTURE as null
+ if (entry.Value != DEFAULT_AVATAR_TEXTURE)
+ Textures[i].TextureID = entry.Value;
+ else
+ Textures[i].TextureID = UUID.Zero;
+ Logger.DebugLog("Set " + entry.Key + " to " + Textures[i].TextureID, Client);
+
+ Textures[i].Texture = null;
+ }
+ }
+ }
+ else
+ {
+ Logger.Log("Failed to decode asset:" + Environment.NewLine +
+ Utils.BytesToString(asset.AssetData), Helpers.LogLevel.Error, Client);
}
}
- else if (face.TextureID != AgentTextures[i] && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE)
+ else
{
- Logger.DebugLog("*** FACE is " + ((TextureIndex)i).ToString() + " " + face.TextureID.ToString() + " Agent Texture is " + AgentTextures[i].ToString());
- match = false;
- //break;
+ Logger.Log("Wearable " + wearable.AssetID + "(" + wearable.WearableType + ") failed to download, " +
+ transfer.Status, Helpers.LogLevel.Warning, Client);
}
+
+ downloadEvent.Set();
}
+ );
- if (!match)
- Logger.Log("TextureEntry mismatch after updating our appearance", Helpers.LogLevel.Warning, Client);
-
- te = avatar.Textures;
- UpdateEvent.Set();
- }
- else
+ if (!downloadEvent.WaitOne(WEARABLE_TIMEOUT))
{
- Logger.Log("Received an update for our avatar with a null FaceTextures array",
- Helpers.LogLevel.Warning, Client);
+ Logger.Log("Timed out downloading wearable asset " + wearable.AssetID + " (" + wearable.WearableType + ")",
+ Helpers.LogLevel.Error, Client);
+ success = false;
}
- }
- };
- Client.Objects.OnNewAvatar += updateCallback;
- // Send all of the visual params and textures for our agent
- SendAgentSetAppearance();
-
- // Wait for the ObjectUpdate to come in for our avatar after changing appearance
- if (UpdateEvent.WaitOne(1000 * 60, false))
- {
- if (OnAppearanceUpdated != null)
- {
- try { OnAppearanceUpdated(te); }
- catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
- }
- }
- else
- {
- Logger.Log("Timed out waiting for our appearance to update on the simulator", Helpers.LogLevel.Warning, Client);
- }
-
- Client.Objects.OnNewAvatar -= updateCallback;
- Logger.Log("Appearance update completed", Helpers.LogLevel.Info);
-
- #endregion Send Appearance
- }
-
- ///
- /// Build hashes out of the texture assetIDs for each baking layer to
- /// ask the simulator whether it has cached copies of each baked texture
- ///
- public void RequestCachedBakes()
- {
- Logger.DebugLog("RequestCachedBakes()", Client);
-
- List> hashes = new List>();
-
- AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
- cache.AgentData.AgentID = Client.Self.AgentID;
- cache.AgentData.SessionID = Client.Self.SessionID;
- cache.AgentData.SerialNum = CacheCheckSerialNum;
-
- // Build hashes for each of the bake layers from the individual components
- for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
- {
- // Don't do a cache request for a skirt bake if we're not wearing a skirt
- if (bakedIndex == (int)BakeType.Skirt &&
- (!Wearables.ContainsKey(WearableType.Skirt) || Wearables.Dictionary[WearableType.Skirt].Asset.AssetID == UUID.Zero))
- continue;
-
- UUID hash = new UUID();
-
- for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
- {
- WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
- UUID assetID = GetWearableAsset(type);
-
- // Build a hash of all the texture asset IDs in this baking layer
- if (assetID != UUID.Zero) hash ^= assetID;
- }
-
- if (hash != UUID.Zero)
- {
- // Hash with our secret value for this baked layer
- hash ^= BAKED_TEXTURE_HASH[bakedIndex];
-
- // Add this to the list of hashes to send out
- hashes.Add(new KeyValuePair(bakedIndex, hash));
- }
- }
-
- // Only send the packet out if there's something to check
- if (hashes.Count > 0)
- {
- cache.WearableData = new AgentCachedTexturePacket.WearableDataBlock[hashes.Count];
-
- for (int i = 0; i < hashes.Count; i++)
- {
- cache.WearableData[i] = new AgentCachedTexturePacket.WearableDataBlock();
- cache.WearableData[i].TextureIndex = (byte)hashes[i].Key;
- cache.WearableData[i].ID = hashes[i].Value;
-
- Logger.DebugLog("Checking cache for index " + cache.WearableData[i].TextureIndex +
- ", ID: " + cache.WearableData[i].ID, Client);
- }
-
- // Increment our serial number for this packet
- CacheCheckSerialNum++;
-
- // Send it out
- Client.Network.SendPacket(cache);
- }
- }
-
- ///
- /// Force a rebake of the currently worn textures
- ///
- public void ForceRebakeAvatarTextures()
- {
- Client.Assets.OnAssetUploaded += Assets_OnAssetUploaded;
- for (int i = 0; i < BAKED_TEXTURE_COUNT; i++)
- {
- // Don't bake skirt if not wearing one
- if (i == (int)BakeType.Skirt && (!Wearables.ContainsKey(WearableType.Skirt) || Wearables.Dictionary[WearableType.Skirt].Asset.AssetID == UUID.Zero))
- {
- continue;
- }
-
- RebakeLayer((BakeType)i);
- }
- if (PendingUploads.Count > 0)
- {
- CachedResponseEvent.WaitOne();
- }
- Client.Assets.OnAssetUploaded -= Assets_OnAssetUploaded;
- SendAgentSetAppearance();
- }
-
- ///
- /// Ask the server what textures our avatar is currently wearing
- ///
- public void SendAgentWearablesRequest()
- {
- AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
- request.AgentData.AgentID = Client.Self.AgentID;
- request.AgentData.SessionID = Client.Self.SessionID;
-
- Client.Network.SendPacket(request);
- }
-
- private void AgentWearablesUpdateHandler(Packet packet, Simulator simulator)
- {
- // Lock to prevent a race condition with multiple AgentWearables packets
- lock (WearablesRequestEvent)
- {
- AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)packet;
-
- // Reset the Wearables collection
- lock (Wearables.Dictionary) Wearables.Dictionary.Clear();
-
- for (int i = 0; i < update.WearableData.Length; i++)
- {
- if (update.WearableData[i].AssetID != UUID.Zero)
- {
- WearableType type = (WearableType)update.WearableData[i].WearableType;
- WearableData data = new WearableData();
- data.Item = new InventoryWearable(update.WearableData[i].ItemID);
- data.Item.WearableType = type;
- data.Item.AssetType = WearableTypeToAssetType(type);
- data.Item.AssetUUID = update.WearableData[i].AssetID;
-
- // Add this wearable to our collection
- lock (Wearables.Dictionary) Wearables.Dictionary[type] = data;
+ --pendingWearables;
}
}
- }
+ );
- WearablesRequestEvent.Set();
+ return success;
}
- private void SendAgentSetAppearance()
+ ///
+ /// Get a list of all of the textures that need to be downloaded for a
+ /// single bake layer
+ ///
+ /// Bake layer to get texture AssetIDs for
+ /// A list of texture AssetIDs to download
+ private List GetTextureDownloadList(BakeType bakeType)
{
- AgentSetAppearancePacket set = new AgentSetAppearancePacket();
- set.AgentData.AgentID = Client.Self.AgentID;
- set.AgentData.SessionID = Client.Self.SessionID;
- set.AgentData.SerialNum = SetAppearanceSerialNum++;
- set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[218];
+ List indices = BakeTypeToTextures(bakeType);
+ List textures = new List();
- float AgentSizeVPHeight = 0.0f;
- float AgentSizeVPHeelHeight = 0.0f;
- float AgentSizeVPPlatformHeight = 0.0f;
- float AgentSizeVPHeadSize = 0.5f;
- float AgentSizeVPLegLength = 0.0f;
- float AgentSizeVPNeckLength = 0.0f;
- float AgentSizeVPHipLength = 0.0f;
-
- lock (Wearables.Dictionary)
+ for (int i = 0; i < indices.Count; i++)
{
- // Only for debugging output
- int count = 0, vpIndex = 0;
+ AvatarTextureIndex index = indices[i];
- // Build the visual param array
- foreach (KeyValuePair kvp in VisualParams.Params)
+ if (index == AvatarTextureIndex.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
+ continue;
+
+ AddTextureDownload(index, textures);
+ }
+
+ return textures;
+ }
+
+ ///
+ /// Helper method to lookup the TextureID for a single layer and add it
+ /// to a list if it is not already present
+ ///
+ ///
+ ///
+ private void AddTextureDownload(AvatarTextureIndex index, List textures)
+ {
+ TextureData textureData = Textures[(int)index];
+ // Add the textureID to the list if this layer has a valid textureID set, it has not already
+ // been downloaded, and it is not already in the download list
+ if (textureData.TextureID != UUID.Zero && textureData.Texture == null && !textures.Contains(textureData.TextureID))
+ textures.Add(textureData.TextureID);
+ }
+
+ ///
+ /// Blocking method to download all of the textures needed for baking
+ /// the given bake layers
+ ///
+ /// A list of layers that need baking
+ /// No return value is given because the baking will happen
+ /// whether or not all textures are successfully downloaded
+ private void DownloadTextures(List bakeLayers)
+ {
+ List textureIDs = new List();
+
+ for (int i = 0; i < bakeLayers.Count; i++)
+ {
+ List layerTextureIDs = GetTextureDownloadList(bakeLayers[i]);
+
+ for (int j = 0; j < layerTextureIDs.Count; j++)
{
- VisualParam vp = kvp.Value;
+ UUID uuid = layerTextureIDs[j];
+ if (!textureIDs.Contains(uuid))
+ textureIDs.Add(uuid);
+ }
+ }
- // Only Group-0 parameters are sent in AgentSetAppearance packets
- if (vp.Group == 0)
- {
- set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
- set.VisualParam[vpIndex].ParamValue = Utils.FloatToByte(vp.DefaultValue, vp.MinValue, vp.MaxValue);
+ Logger.DebugLog("Downloading " + textureIDs.Count + " textures for baking");
- // Try and find this value in our collection of downloaded wearables
- foreach (WearableData data in Wearables.Dictionary.Values)
+ Parallel.ForEach(MAX_CONCURRENT_DOWNLOADS, textureIDs,
+ delegate(UUID textureID)
+ {
+ AutoResetEvent downloadEvent = new AutoResetEvent(false);
+
+ Client.Assets.RequestImage(textureID,
+ delegate(TextureRequestState state, AssetTexture assetTexture)
{
- if (data.Asset != null && data.Asset.Params.ContainsKey(vp.ParamID))
+ if (state == TextureRequestState.Finished)
{
- set.VisualParam[vpIndex].ParamValue = Utils.FloatToByte(data.Asset.Params[vp.ParamID], vp.MinValue, vp.MaxValue);
- count++;
+ assetTexture.Decode();
- switch (vp.ParamID)
+ for (int i = 0; i < Textures.Length; i++)
{
- case 33:
- AgentSizeVPHeight = data.Asset.Params[vp.ParamID];
- break;
- case 198:
- AgentSizeVPHeelHeight = data.Asset.Params[vp.ParamID];
- break;
- case 503:
- AgentSizeVPPlatformHeight = data.Asset.Params[vp.ParamID];
- break;
- case 682:
- AgentSizeVPHeadSize = data.Asset.Params[vp.ParamID];
- break;
- case 692:
- AgentSizeVPLegLength = data.Asset.Params[vp.ParamID];
- break;
- case 756:
- AgentSizeVPNeckLength = data.Asset.Params[vp.ParamID];
- break;
- case 842:
- AgentSizeVPHipLength = data.Asset.Params[vp.ParamID];
- break;
+ if (Textures[i].TextureID == textureID)
+ Textures[i].Texture = assetTexture;
}
- break;
}
+ else
+ {
+ Logger.Log("Texture " + textureID + " failed to download, one or more bakes will be incomplete",
+ Helpers.LogLevel.Warning);
+ }
+
+ downloadEvent.Set();
}
+ );
- ++vpIndex;
- }
+ downloadEvent.WaitOne(TEXTURE_TIMEOUT, false);
}
+ );
+ }
- // Build the texture entry for our agent
- Primitive.TextureEntry te = new Primitive.TextureEntry(DEFAULT_AVATAR_TEXTURE);
+ ///
+ /// Blocking method to create and upload baked textures for all of the
+ /// missing bakes
+ ///
+ /// True on success, otherwise false
+ private bool CreateBakes()
+ {
+ bool success = true;
+ List pendingBakes = new List(0);
- // Put our AgentTextures array in to TextureEntry
- lock (AgentTextures)
- {
- for (uint i = 0; i < AgentTextures.Length; i++)
- {
- if (AgentTextures[i] != UUID.Zero)
- {
- Primitive.TextureEntryFace face = te.CreateFace(i);
- face.TextureID = AgentTextures[i];
- }
- }
- }
-
- foreach (WearableData data in Wearables.Dictionary.Values)
- {
- if (data.Asset != null)
- {
- foreach (KeyValuePair texture in data.Asset.Textures)
- {
- Primitive.TextureEntryFace face = te.CreateFace((uint)texture.Key);
- face.TextureID = texture.Value;
-
- Logger.DebugLog("Setting agent texture " + ((TextureIndex)texture.Key).ToString() + " to " +
- texture.Value.ToString(), Client);
- }
- }
- }
-
- // Set the packet TextureEntry
- set.ObjectData.TextureEntry = te.GetBytes();
- }
-
- // FIXME: Our hackish algorithm is making squished avatars. See
- // http://www.OpenMetaverse.org/wiki/Agent_Size for discussion of the correct algorithm
- //float height = Utils.ByteToFloat(set.VisualParam[33].ParamValue, VisualParams.Params[33].MinValue,
- // VisualParams.Params[33].MaxValue);
-
- // Takes into account the Shoe Heel/Platform offsets but not the Head Size Offset. But seems to work.
- double AgentSizeBase = 1.706;
-
- // The calculation for the Head Size scalar may be incorrect. But seems to work.
- double AgentHeight = AgentSizeBase + (AgentSizeVPLegLength * .1918) + (AgentSizeVPHipLength * .0375) +
- (AgentSizeVPHeight * .12022) + (AgentSizeVPHeadSize * .01117) + (AgentSizeVPNeckLength * .038) +
- (AgentSizeVPHeelHeight * .08) + (AgentSizeVPPlatformHeight * .07);
-
- set.AgentData.Size = new Vector3(0.45f, 0.6f, (float)AgentHeight);
-
- // TODO: Account for not having all the textures baked yet
- set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
-
- // Build hashes for each of the bake layers from the individual components
+ // Check each bake layer in the Textures array for missing bakes
for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
{
- UUID hash = new UUID();
+ AvatarTextureIndex textureIndex = BakeTypeToAgentTextureIndex((BakeType)bakedIndex);
- for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
+ if (Textures[(int)textureIndex].TextureID == UUID.Zero)
{
- WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
- UUID assetID = GetWearableAsset(type);
+ // If this is the skirt layer and we're not wearing a skirt then skip it
+ if (bakedIndex == (int)BakeType.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
+ continue;
- // Build a hash of all the texture asset IDs in this baking layer
- if (assetID != UUID.Zero) hash ^= assetID;
+ pendingBakes.Add((BakeType)bakedIndex);
}
-
- if (hash != UUID.Zero)
- {
- // Hash with our secret value for this baked layer
- hash ^= BAKED_TEXTURE_HASH[bakedIndex];
- }
-
- // Tell the server what cached texture assetID to use for each bake layer
- set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
- set.WearableData[bakedIndex].TextureIndex = (byte)bakedIndex;
- set.WearableData[bakedIndex].CacheID = hash;
- Logger.DebugLog("Setting baked agent texture hash " + ((BakeType)bakedIndex).ToString() + " to " + hash, Client);
-
}
- // Finally, send the packet
- Client.Network.SendPacket(set);
+ if (pendingBakes.Count > 0)
+ {
+ DownloadTextures(pendingBakes);
+
+ Dictionary paramValues = MakeParamValues();
+
+ Parallel.ForEach(pendingBakes,
+ delegate(BakeType bakeType)
+ {
+ if (!CreateBake(bakeType, paramValues))
+ success = false;
+ }
+ );
+ }
+
+ return success;
}
-
- private void SendAgentIsNowWearing()
+ ///
+ /// Blocking method to create and upload a baked texture for a single
+ /// bake layer
+ ///
+ /// Layer to bake
+ /// Dictionary of current visual param values
+ /// True on success, otherwise false
+ private bool CreateBake(BakeType bakeType, Dictionary paramValues)
{
- Logger.DebugLog("SendAgentIsNowWearing()", Client);
+ List textureIndices = BakeTypeToTextures(bakeType);
+ Baker oven = new Baker(Client, bakeType, textureIndices.Count, paramValues);
- AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket();
- wearing.AgentData.AgentID = Client.Self.AgentID;
- wearing.AgentData.SessionID = Client.Self.SessionID;
- wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT];
-
- for (int i = 0; i < WEARABLE_COUNT; i++)
+ for (int i = 0; i < textureIndices.Count; i++)
{
- WearableType type = (WearableType)i;
- wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
- wearing.WearableData[i].WearableType = (byte)i;
+ AvatarTextureIndex textureIndex = textureIndices[i];
+ AssetTexture asset = Textures[(int)textureIndex].Texture;
+ bool baked;
- if (Wearables.ContainsKey(type))
- wearing.WearableData[i].ItemID = Wearables.Dictionary[type].Item.UUID;
+ if (asset != null)
+ baked = oven.AddTexture(textureIndex, asset, false);
else
- wearing.WearableData[i].ItemID = UUID.Zero;
- }
+ baked = oven.MissingTexture(textureIndex);
- Client.Network.SendPacket(wearing);
- }
-
- private TextureIndex BakeTypeToAgentTextureIndex(BakeType index)
- {
- switch (index)
- {
- case BakeType.Head:
- return TextureIndex.HeadBaked;
- case BakeType.UpperBody:
- return TextureIndex.UpperBaked;
- case BakeType.LowerBody:
- return TextureIndex.LowerBaked;
- case BakeType.Eyes:
- return TextureIndex.EyesBaked;
- case BakeType.Skirt:
- return TextureIndex.SkirtBaked;
- case BakeType.Hair:
- return TextureIndex.HairBaked;
- default:
- return TextureIndex.Unknown;
- }
- }
-
- private void DownloadWearableAssets()
- {
- lock (Wearables.Dictionary)
- {
- foreach (KeyValuePair kvp in Wearables.Dictionary)
+ if (baked)
{
- Logger.DebugLog("Requesting asset for wearable item " + kvp.Value.Item.WearableType + " (" + kvp.Value.Item.AssetUUID + ")", Client);
- AssetDownloads.Enqueue(new PendingAssetDownload(kvp.Value.Item.AssetUUID, kvp.Value.Item.AssetType));
+ UUID newAssetID = UploadBake(oven.BakedTexture.AssetData);
+ Textures[(int)BakeTypeToAgentTextureIndex(bakeType)].TextureID = newAssetID;
+ return newAssetID != UUID.Zero;
}
}
- if (AssetDownloads.Count > 0)
- {
- PendingAssetDownload pad = AssetDownloads.Dequeue();
- Client.Assets.RequestAsset(pad.Id, pad.Type, true, Assets_OnAssetReceived);
- }
+ return false;
}
- private void RebakeLayer(TextureIndex index)
+ ///
+ /// Blocking method to upload a baked texture
+ ///
+ /// Five channel JPEG2000 texture data to upload
+ /// UUID of the newly created asset on success, otherwise UUID.Zero
+ private UUID UploadBake(byte[] textureData)
{
- RebakeLayer(Baker.BakeTypeFor(index));
- }
+ UUID bakeID = UUID.Zero;
+ AutoResetEvent uploadEvent = new AutoResetEvent(false);
- private void RebakeLayer(BakeType bakeType)
- {
- Dictionary paramValues;
-
- // Build a dictionary of appearance parameter indices and values from the wearables
- paramValues = MakeParamValues();
-
- Baker bake = new Baker(Client, bakeType, 0, paramValues);
-
- for (int ii = 0; ii < AVATAR_TEXTURE_COUNT; ii++)
- {
- if (bakeType == Baker.BakeTypeFor((TextureIndex)ii) && AgentAssets[ii] != null)
+ Client.Assets.RequestUploadBakedTexture(textureData,
+ delegate(UUID newAssetID)
{
- Logger.Log("Adding asset " + AgentAssets[ii].AssetID.ToString() + " to baker", Helpers.LogLevel.Debug);
- bake.AddTexture((TextureIndex)ii, (AssetTexture)AgentAssets[ii], true);
+ bakeID = newAssetID;
+ uploadEvent.Set();
}
- }
+ );
- UploadBake(bake);
- }
-
- private void UploadBake(Baker bake)
- {
- lock (PendingUploads)
- {
- if (PendingUploads.ContainsKey(bake.BakedTexture.AssetID))
- {
- Logger.Log("UploadBake(): Skipping Asset id " + bake.BakedTexture.AssetID.ToString() + " Already in progress", Helpers.LogLevel.Info, Client);
- return;
- }
-
- // Upload the completed layer data and Add it to a pending uploads list
- UUID id = Client.Assets.RequestUpload(bake.BakedTexture, true);
- PendingUploads.Add(UUID.Combine(id, Client.Self.SecureSessionID), BakeTypeToAgentTextureIndex(bake.BakeType));
- }
-
- Logger.DebugLog(String.Format("Bake {0} completed. Uploading asset {1}", bake.BakeType,
- bake.BakedTexture.AssetID.ToString()), Client);
-
- }
-
- private int AddImageDownload(TextureIndex index)
- {
- UUID image = AgentTextures[(int)index];
-
- if (image != UUID.Zero)
- {
- if (!ImageDownloads.ContainsKey(image))
- {
- Logger.DebugLog("Downloading layer " + index.ToString(), Client);
- ImageDownloads.Add(image, index);
- }
-
- return 1;
- }
-
- return 0;
+ uploadEvent.WaitOne(UPLOAD_TIMEOUT, false);
+
+ return bakeID;
}
+ ///
+ /// Creates a dictionary of visual param values from the downloaded wearables
+ ///
+ /// A dictionary of visual param indices mapping to visual param
+ /// values for our agent that can be fed to the Baker class
private Dictionary MakeParamValues()
{
Dictionary paramValues = new Dictionary(VisualParams.Params.Count);
- lock (Wearables.Dictionary)
+ lock (Wearables)
{
foreach (KeyValuePair kvp in VisualParams.Params)
{
@@ -1170,11 +886,12 @@ namespace OpenMetaverse
VisualParam vp = kvp.Value;
// Try and find this value in our collection of downloaded wearables
- foreach (WearableData data in Wearables.Dictionary.Values)
+ foreach (WearableData data in Wearables.Values)
{
- if (data.Asset.Params.ContainsKey(vp.ParamID))
+ float paramValue;
+ if (data.Asset != null && data.Asset.Params.TryGetValue(vp.ParamID, out paramValue))
{
- paramValues.Add(vp.ParamID, data.Asset.Params[vp.ParamID]);
+ paramValues.Add(vp.ParamID, paramValue);
found = true;
break;
}
@@ -1185,373 +902,464 @@ namespace OpenMetaverse
}
}
}
+
return paramValues;
}
- private int AddImagesToDownload(BakeType bakeType)
+ ///
+ /// Create an AgentSetAppearance packet from Wearables data and the
+ /// Textures array and send it
+ ///
+ private void SendAgentSetAppearance()
{
- int imageCount = 0;
+ AgentSetAppearancePacket set = new AgentSetAppearancePacket();
+ set.AgentData.AgentID = Client.Self.AgentID;
+ set.AgentData.SessionID = Client.Self.SessionID;
+ set.AgentData.SerialNum = (uint)Interlocked.Increment(ref SetAppearanceSerialNum);
- // Download all of the images in this layer
- switch (bakeType)
+ // Visual params used in the agent height calculation
+ float agentSizeVPHeight = 0.0f;
+ float agentSizeVPHeelHeight = 0.0f;
+ float agentSizeVPPlatformHeight = 0.0f;
+ float agentSizeVPHeadSize = 0.5f;
+ float agentSizeVPLegLength = 0.0f;
+ float agentSizeVPNeckLength = 0.0f;
+ float agentSizeVPHipLength = 0.0f;
+
+ lock (Wearables)
{
- case BakeType.Head:
- lock (ImageDownloads)
+ #region VisualParam
+
+ int vpIndex = 0;
+ set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[218];
+
+ foreach (KeyValuePair kvp in VisualParams.Params)
+ {
+ VisualParam vp = kvp.Value;
+ float paramValue = 0f;
+ bool found = false;
+
+ // Try and find this value in our collection of downloaded wearables
+ foreach (WearableData data in Wearables.Values)
{
- imageCount += AddImageDownload(TextureIndex.HeadBodypaint);
- //imageCount += AddImageDownload(TextureIndex.Hair);
- }
- break;
- case BakeType.UpperBody:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.UpperBodypaint);
- imageCount += AddImageDownload(TextureIndex.UpperGloves);
- imageCount += AddImageDownload(TextureIndex.UpperUndershirt);
- imageCount += AddImageDownload(TextureIndex.UpperShirt);
- imageCount += AddImageDownload(TextureIndex.UpperJacket);
- }
- break;
- case BakeType.LowerBody:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.LowerBodypaint);
- imageCount += AddImageDownload(TextureIndex.LowerUnderpants);
- imageCount += AddImageDownload(TextureIndex.LowerSocks);
- imageCount += AddImageDownload(TextureIndex.LowerShoes);
- imageCount += AddImageDownload(TextureIndex.LowerPants);
- imageCount += AddImageDownload(TextureIndex.LowerJacket);
- }
- break;
- case BakeType.Eyes:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.EyesIris);
- }
- break;
- case BakeType.Skirt:
- if (Wearables.ContainsKey(WearableType.Skirt))
- {
- lock (ImageDownloads)
+ if (data.Asset != null && data.Asset.Params.TryGetValue(vp.ParamID, out paramValue))
{
- imageCount += AddImageDownload(TextureIndex.Skirt);
+ found = true;
+ break;
}
}
- break;
- case BakeType.Hair:
- lock (ImageDownloads)
+
+ // Use a default value if we don't have one set for it
+ if (!found)
+ paramValue = vp.DefaultValue;
+
+ // Only Group-0 parameters are sent in AgentSetAppearance packets
+ if (kvp.Value.Group == 0)
{
- imageCount += AddImageDownload(TextureIndex.Hair);
+ set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
+ set.VisualParam[vpIndex].ParamValue = Utils.FloatToByte(paramValue, vp.MinValue, vp.MaxValue);
+ ++vpIndex;
}
- break;
- default:
- Logger.Log("Unknown BakeType :" + bakeType.ToString(), Helpers.LogLevel.Warning, Client);
- break;
+
+ // Check if this is one of the visual params used in the agent height calculation
+ switch (vp.ParamID)
+ {
+ case 33:
+ agentSizeVPHeight = paramValue;
+ break;
+ case 198:
+ agentSizeVPHeelHeight = paramValue;
+ break;
+ case 503:
+ agentSizeVPPlatformHeight = paramValue;
+ break;
+ case 682:
+ agentSizeVPHeadSize = paramValue;
+ break;
+ case 692:
+ agentSizeVPLegLength = paramValue;
+ break;
+ case 756:
+ agentSizeVPNeckLength = paramValue;
+ break;
+ case 842:
+ agentSizeVPHipLength = paramValue;
+ break;
+ }
+ }
+
+ #endregion VisualParam
+
+ #region TextureEntry
+
+ Primitive.TextureEntry te = new Primitive.TextureEntry(DEFAULT_AVATAR_TEXTURE);
+
+ for (uint i = 0; i < Textures.Length; i++)
+ {
+ if (Textures[i].TextureID != UUID.Zero)
+ {
+ Primitive.TextureEntryFace face = te.CreateFace(i);
+ face.TextureID = Textures[i].TextureID;
+ Logger.DebugLog("Sending texture entry for " + (AvatarTextureIndex)i + " to " + Textures[i].TextureID, Client);
+ }
+ }
+
+ set.ObjectData.TextureEntry = te.GetBytes();
+
+ #endregion TextureEntry
+
+ #region WearableData
+
+ set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
+
+ // Build hashes for each of the bake layers from the individual components
+ for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
+ {
+ UUID hash = UUID.Zero;
+
+ for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
+ {
+ WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
+
+ WearableData wearable;
+ if (type != WearableType.Invalid && Wearables.TryGetValue(type, out wearable))
+ hash ^= wearable.AssetID;
+ }
+
+ if (hash != UUID.Zero)
+ {
+ // Hash with our magic value for this baked layer
+ hash ^= BAKED_TEXTURE_HASH[bakedIndex];
+ }
+
+ // Tell the server what cached texture assetID to use for each bake layer
+ set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
+ set.WearableData[bakedIndex].TextureIndex = (byte)bakedIndex;
+ set.WearableData[bakedIndex].CacheID = hash;
+ Logger.DebugLog("Sending TextureIndex " + (BakeType)bakedIndex + " with CacheID " + hash, Client);
+ }
+
+ #endregion WearableData
+
+ #region Agent Size
+
+ // Takes into account the Shoe Heel/Platform offsets but not the HeadSize offset. Seems to work.
+ double agentSizeBase = 1.706;
+
+ // The calculation for the HeadSize scalar may be incorrect, but it seems to work
+ double agentHeight = agentSizeBase + (agentSizeVPLegLength * .1918) + (agentSizeVPHipLength * .0375) +
+ (agentSizeVPHeight * .12022) + (agentSizeVPHeadSize * .01117) + (agentSizeVPNeckLength * .038) +
+ (agentSizeVPHeelHeight * .08) + (agentSizeVPPlatformHeight * .07);
+
+ set.AgentData.Size = new Vector3(0.45f, 0.6f, (float)agentHeight);
+
+ #endregion Agent Size
}
- return imageCount;
+ Client.Network.SendPacket(set);
+ Logger.DebugLog("Send AgentSetAppearance packet");
}
+ #endregion Appearance Helpers
+
+ #region Inventory Helpers
+
+ private bool GetFolderWearables(string[] folderPath, out List wearables, out List attachments)
+ {
+ UUID folder = Client.Inventory.FindObjectByPath(
+ Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, String.Join("/", folderPath), INVENTORY_TIMEOUT);
+
+ if (folder != UUID.Zero)
+ {
+ return GetFolderWearables(folder, out wearables, out attachments);
+ }
+ else
+ {
+ Logger.Log("Failed to resolve outfit folder path " + folderPath, Helpers.LogLevel.Error, Client);
+ wearables = null;
+ attachments = null;
+ return false;
+ }
+ }
+
+ private bool GetFolderWearables(UUID folder, out List wearables, out List attachments)
+ {
+ wearables = new List();
+ attachments = new List();
+ List objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID, false, true,
+ InventorySortOrder.ByName, INVENTORY_TIMEOUT);
+
+ if (objects != null)
+ {
+ foreach (InventoryBase ib in objects)
+ {
+ if (ib is InventoryWearable)
+ {
+ Logger.DebugLog("Adding wearable " + ib.Name, Client);
+ wearables.Add((InventoryWearable)ib);
+ }
+ else if (ib is InventoryAttachment)
+ {
+ Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client);
+ attachments.Add(ib);
+ }
+ else if (ib is InventoryObject)
+ {
+ Logger.DebugLog("Adding attachment (object) " + ib.Name, Client);
+ attachments.Add(ib);
+ }
+ else
+ {
+ Logger.DebugLog("Ignoring inventory item " + ib.Name, Client);
+ }
+ }
+ }
+ else
+ {
+ Logger.Log("Failed to download folder contents of + " + folder, Helpers.LogLevel.Error, Client);
+ return false;
+ }
+
+ return true;
+ }
+
+ #endregion Inventory Helpers
+
#region Callbacks
- private void RebakeAvatarTexturesHandler(Packet packet, Simulator simulator)
+ private void AgentWearablesUpdateHandler(Packet packet, Simulator simulator)
{
- RebakeAvatarTexturesPacket data = (RebakeAvatarTexturesPacket)packet;
- Logger.Log("Request rebake for :" + data.TextureData.TextureID.ToString(), Helpers.LogLevel.Info);
+ bool changed = false;
+ AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)packet;
- lock (AgentTextures)
+ lock (Wearables)
{
- Client.Assets.OnAssetUploaded += Assets_OnAssetUploaded;
- for (int i = 0; i < AgentTextures.Length; i++)
+ #region Test if anything changed in this update
+
+ for (int i = 0; i < update.WearableData.Length; i++)
{
- if (AgentTextures[i] == data.TextureData.TextureID)
+ AgentWearablesUpdatePacket.WearableDataBlock block = update.WearableData[i];
+
+ if (block.AssetID != UUID.Zero)
{
- // Its one of our baked layers, rebake this one
- RebakeLayer((TextureIndex)i);
+ WearableData wearable;
+ if (Wearables.TryGetValue((WearableType)block.WearableType, out wearable))
+ {
+ if (wearable.AssetID != block.AssetID || wearable.ItemID != block.ItemID)
+ {
+ // A different wearable is now set for this index
+ changed = true;
+ break;
+ }
+ }
+ else
+ {
+ // A wearable is now set for this index
+ changed = true;
+ break;
+ }
+ }
+ else if (Wearables.ContainsKey((WearableType)block.WearableType))
+ {
+ // This index is now empty
+ changed = true;
+ break;
}
}
- if (PendingUploads.Count > 0)
+
+ #endregion Test if anything changed in this update
+
+ if (changed)
{
- CachedResponseEvent.WaitOne();
+ Logger.DebugLog("New wearables received in AgentWearablesUpdate");
+ Wearables.Clear();
+
+ for (int i = 0; i < update.WearableData.Length; i++)
+ {
+ AgentWearablesUpdatePacket.WearableDataBlock block = update.WearableData[i];
+
+ if (block.AssetID != UUID.Zero)
+ {
+ WearableType type = (WearableType)block.WearableType;
+
+ WearableData data = new WearableData();
+ data.Asset = null;
+ data.AssetID = block.AssetID;
+ data.AssetType = WearableTypeToAssetType(type);
+ data.ItemID = block.ItemID;
+ data.WearableType = type;
+
+ // Add this wearable to our collection
+ Wearables[type] = data;
+ }
+ }
+ }
+ else
+ {
+ Logger.DebugLog("Duplicate AgentWearablesUpdate received, discarding");
+ }
+ }
+
+ if (changed)
+ {
+ // Fire the callback
+ AgentWearablesCallback callback = OnAgentWearables;
+ if (callback != null)
+ {
+ try { callback(); }
+ catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); }
}
- Client.Assets.OnAssetUploaded -= Assets_OnAssetUploaded;
- SendAgentSetAppearance();
}
}
private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator)
{
- Logger.DebugLog("AgentCachedTextureResponseHandler()", Client);
-
AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet;
- lock (AgentTextures)
+ for (int i = 0; i < response.WearableData.Length; i++)
{
- //If we are here then the user has tried to wear stuff or we are at login
- // In either case the existing uploads of this class are very shortly going to be no good
- PendingUploads.Clear();
+ AgentCachedTextureResponsePacket.WearableDataBlock block = response.WearableData[i];
+ BakeType bakeType = (BakeType)block.TextureIndex;
+ AvatarTextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
- foreach (AgentCachedTextureResponsePacket.WearableDataBlock block in response.WearableData)
+ Logger.DebugLog("Cache response for " + bakeType + ", TextureID=" + block.TextureID, Client);
+
+ if (block.TextureID != UUID.Zero)
{
- //UUID hash=new UUID();
- // For each missing element we need to bake our own texture
- Logger.DebugLog("Cache response, index: " + block.TextureIndex + ", ID: " +
- block.TextureID.ToString(), Client);
+ // A simulator has a cache of this bake layer
- // FIXME: Use this. Right now we treat baked images on other sims as if they were missing
+ // FIXME: Use this. Right now we don't bother to check if this is a foreign host
string host = Utils.BytesToString(block.HostName);
- if (host.Length > 0) Logger.DebugLog("Cached bake exists on foreign host " + host, Client);
- BakeType bakeType = (BakeType)block.TextureIndex;
-
- // Note, still should handle block.TextureID != UUID.Zero && host.Length == 0
- // Not sure what we should do as yet with that.
-
- // Convert the baked index to an AgentTexture index
- if (block.TextureID != UUID.Zero && host.Length != 0)
- {
- TextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
- AgentTextures[(int)index] = block.TextureID;
- AddImagesToDownload(bakeType); // We need to do this bit regardless for rebaking purposes later
- }
- else
- {
- int imageCount = AddImagesToDownload(bakeType);
-
- if (!PendingBakes.ContainsKey(bakeType))
- {
- Logger.DebugLog("Initializing " + bakeType.ToString() + " bake with " + imageCount + " textures", Client);
-
- Dictionary paramValues = MakeParamValues();
- // Build a dictionary of appearance parameter indices and values from the wearables
-
- if (imageCount == 0)
- {
- // if there are no textures to download, we can bake right away and start the upload
- Baker bake = new Baker(Client, bakeType, 0, paramValues);
- UploadBake(bake);
- }
- else
- {
- lock (PendingBakes)
- {
- Baker bake = new Baker(Client, bakeType, imageCount, paramValues);
- PendingBakes.Add(bakeType, bake);
- }
- }
- }
- else if (!PendingBakes.ContainsKey(bakeType))
- {
- Logger.Log("No cached bake for " + bakeType.ToString() + " and no textures for that " +
- "layer, this is an unhandled case", Helpers.LogLevel.Error, Client);
- }
- }
- }
- }
-
- if (ImageDownloads.Count > 0)
- {
- lock (ImageDownloads)
- {
- List imgKeys = new List(ImageDownloads.Keys);
- foreach (UUID image in imgKeys)
- {
- // Download all the images we need for baking
- Client.Assets.RequestImage(image, ImageType.Normal, Assets_OnImageReceived);
- }
- }
- }
- else
- {
- CachedResponseEvent.Set();
- }
- }
-
- private void Assets_OnAssetReceived(AssetDownload download, Asset asset)
- {
- lock (Wearables.Dictionary)
- {
- // Check if this is a wearable we were waiting on
- foreach (KeyValuePair kvp in Wearables.Dictionary)
- {
- if (kvp.Value.Item.AssetUUID == download.AssetID)
- {
- // Make sure the download succeeded
- if (download.Success)
- {
- kvp.Value.Asset = (AssetWearable)asset;
-
- Logger.DebugLog("Downloaded wearable asset " + kvp.Value.Asset.Name, Client);
-
- if (!kvp.Value.Asset.Decode())
- {
- Logger.Log("Failed to decode asset:" + Environment.NewLine +
- Utils.BytesToString(asset.AssetData), Helpers.LogLevel.Error, Client);
- }
-
- lock (AgentTextures)
- {
- foreach (KeyValuePair texture in kvp.Value.Asset.Textures)
- {
- if (texture.Value != DEFAULT_AVATAR_TEXTURE) // this texture is not meant to be displayed
- {
- Logger.DebugLog("Setting " + texture.Key + " to " + texture.Value, Client);
- AgentTextures[(int)texture.Key] = texture.Value;
- }
- }
- }
- }
- else
- {
- Logger.Log("Wearable " + kvp.Key + "(" + download.AssetID.ToString() + ") failed to download, " +
- download.Status.ToString(), Helpers.LogLevel.Warning, Client);
- }
-
- break;
- }
- }
- }
-
- if (AssetDownloads.Count > 0)
- {
- // Dowload the next wearable in line
- PendingAssetDownload pad = AssetDownloads.Dequeue();
- Client.Assets.RequestAsset(pad.Id, pad.Type, true, Assets_OnAssetReceived);
- }
- else
- {
- // Everything is downloaded
- if (OnAgentWearables != null)
- {
- try { OnAgentWearables(); }
- catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
- }
-
- WearablesDownloadedEvent.Set();
- }
- }
-
- private void Assets_OnImageReceived(TextureRequestState state, AssetTexture assetTexture)
- {
- lock (ImageDownloads)
- {
- if (ImageDownloads.ContainsKey(assetTexture.AssetID))
- {
- ImageDownloads.Remove(assetTexture.AssetID);
-
- // NOTE: This image may occupy more than one TextureIndex! We must finish this loop
- for (int at = 0; at < AgentTextures.Length; at++)
- {
- if (AgentTextures[at] == assetTexture.AssetID)
- {
- TextureIndex index = (TextureIndex)at;
- BakeType type = Baker.BakeTypeFor(index);
-
- //BinaryWriter writer = new BinaryWriter(File.Create("wearable_" + index.ToString() + "_" + image.ID.ToString() + ".jp2"));
- //writer.Write(image.AssetData);
- //writer.Close();
-
- bool baked = false;
- AgentAssets[at] = assetTexture; //Cache this asset for rebaking, todo this could be better rather than dropping in this list.
-
- if (PendingBakes.ContainsKey(type))
- {
- if (state == TextureRequestState.Finished)
- {
- Logger.DebugLog("Finished downloading texture for " + index.ToString(), Client);
- OpenJPEG.DecodeToImage(assetTexture.AssetData, out assetTexture.Image);
- baked = PendingBakes[type].AddTexture(index, assetTexture, false);
- }
- else
- {
- Logger.Log("Texture for " + index + " failed to download, " +
- "bake will be incomplete", Helpers.LogLevel.Warning, Client);
- baked = PendingBakes[type].MissingTexture(index);
- }
- }
-
- if (baked)
- {
- UploadBake(PendingBakes[type]);
- PendingBakes.Remove(type);
- }
-
- if (ImageDownloads.Count == 0 && PendingUploads.Count == 0)
- {
- // This is a failsafe catch, as the upload completed callback should normally
- // be triggering the event
- Logger.DebugLog("No pending downloads or uploads detected in OnImageReceived", Client);
- CachedResponseEvent.Set();
- }
- else
- {
- Logger.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
- ImageDownloads.Count, Client);
- }
-
- }
- }
+ Textures[(int)index].TextureID = block.TextureID;
}
else
{
- Logger.Log("Received an image download callback for an image we did not request " + assetTexture.AssetID,
- Helpers.LogLevel.Warning, Client);
+ // The server does not have a cache of this bake layer
+ // FIXME:
}
}
+
+ AgentCachedBakesCallback callback = OnAgentCachedBakes;
+ if (callback != null)
+ {
+ try { callback(); }
+ catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); }
+ }
}
- private void Assets_OnAssetUploaded(AssetUpload upload)
+ private void Network_OnEventQueueRunning(Simulator simulator)
{
- lock (PendingUploads)
+ if (simulator == Client.Network.CurrentSim && Client.Settings.SEND_AGENT_APPEARANCE)
{
- if (PendingUploads.ContainsKey(upload.AssetID))
- {
- if (upload.Success)
- {
- // Setup the TextureEntry with the new baked upload
- TextureIndex index = PendingUploads[upload.AssetID];
- AgentTextures[(int)index] = upload.AssetID;
+ // Update appearance each time we enter a new sim and capabilities have been retrieved
+ Client.Appearance.RequestSetAppearance();
+ }
+ }
- Logger.DebugLog("Upload complete, AgentTextures " + index.ToString() + " set to " +
- upload.AssetID.ToString(), Client);
- }
- else
- {
- Logger.Log("Asset upload " + upload.AssetID.ToString() + " failed",
- Helpers.LogLevel.Warning, Client);
- }
+ #endregion Callbacks
- PendingUploads.Remove(upload.AssetID);
+ #region Static Helpers
- Logger.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
- ImageDownloads.Count, Client);
-
- if (PendingUploads.Count == 0 && ImageDownloads.Count == 0)
- {
- Logger.DebugLog("All pending image downloads and uploads complete", Client);
-
- CachedResponseEvent.Set();
- }
- }
- else
- {
- // TEMP
- Logger.DebugLog("Upload " + upload.AssetID.ToString() + " was not found in PendingUploads", Client);
- }
+ ///
+ /// Converts a WearableType to a bodypart or clothing WearableType
+ ///
+ /// A WearableType
+ /// AssetType.Bodypart or AssetType.Clothing or AssetType.Unknown
+ public static AssetType WearableTypeToAssetType(WearableType type)
+ {
+ switch (type)
+ {
+ case WearableType.Shape:
+ case WearableType.Skin:
+ case WearableType.Hair:
+ case WearableType.Eyes:
+ return AssetType.Bodypart;
+ case WearableType.Shirt:
+ case WearableType.Pants:
+ case WearableType.Shoes:
+ case WearableType.Socks:
+ case WearableType.Jacket:
+ case WearableType.Gloves:
+ case WearableType.Undershirt:
+ case WearableType.Underpants:
+ case WearableType.Skirt:
+ return AssetType.Clothing;
+ default:
+ return AssetType.Unknown;
}
}
///
- /// Terminate any wait handles when the network layer disconnects
+ /// Converts a BakeType to the corresponding baked texture slot in AvatarTextureIndex
///
- private void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message)
+ /// A BakeType
+ /// The AvatarTextureIndex slot that holds the given BakeType
+ public static AvatarTextureIndex BakeTypeToAgentTextureIndex(BakeType index)
{
- WearablesRequestEvent.Set();
- WearablesDownloadedEvent.Set();
- CachedResponseEvent.Set();
- UpdateEvent.Set();
+ switch (index)
+ {
+ case BakeType.Head:
+ return AvatarTextureIndex.HeadBaked;
+ case BakeType.UpperBody:
+ return AvatarTextureIndex.UpperBaked;
+ case BakeType.LowerBody:
+ return AvatarTextureIndex.LowerBaked;
+ case BakeType.Eyes:
+ return AvatarTextureIndex.EyesBaked;
+ case BakeType.Skirt:
+ return AvatarTextureIndex.SkirtBaked;
+ case BakeType.Hair:
+ return AvatarTextureIndex.HairBaked;
+ default:
+ return AvatarTextureIndex.Unknown;
+ }
}
- #endregion Callbacks
+ ///
+ /// Converts a BakeType to a list of the texture slots that make up that bake
+ ///
+ /// A BakeType
+ /// A list of texture slots that are inputs for the given bake
+ public static List BakeTypeToTextures(BakeType bakeType)
+ {
+ List textures = new List();
+
+ switch (bakeType)
+ {
+ case BakeType.Head:
+ textures.Add(AvatarTextureIndex.HeadBodypaint);
+ //AddTextureDownload(AvatarTextureIndex.Hair, textures);
+ break;
+ case BakeType.UpperBody:
+ textures.Add(AvatarTextureIndex.UpperBodypaint);
+ textures.Add(AvatarTextureIndex.UpperGloves);
+ textures.Add(AvatarTextureIndex.UpperUndershirt);
+ textures.Add(AvatarTextureIndex.UpperShirt);
+ textures.Add(AvatarTextureIndex.UpperJacket);
+ break;
+ case BakeType.LowerBody:
+ textures.Add(AvatarTextureIndex.LowerBodypaint);
+ textures.Add(AvatarTextureIndex.LowerUnderpants);
+ textures.Add(AvatarTextureIndex.LowerSocks);
+ textures.Add(AvatarTextureIndex.LowerShoes);
+ textures.Add(AvatarTextureIndex.LowerPants);
+ textures.Add(AvatarTextureIndex.LowerJacket);
+ break;
+ case BakeType.Eyes:
+ textures.Add(AvatarTextureIndex.EyesIris);
+ break;
+ case BakeType.Skirt:
+ textures.Add(AvatarTextureIndex.Skirt);
+ break;
+ case BakeType.Hair:
+ textures.Add(AvatarTextureIndex.Hair);
+ break;
+ }
+
+ return textures;
+ }
+
+ #endregion Static Helpers
}
}
diff --git a/OpenMetaverse/AssetManager.cs b/OpenMetaverse/AssetManager.cs
index 1decab56..e2fa5d5b 100644
--- a/OpenMetaverse/AssetManager.cs
+++ b/OpenMetaverse/AssetManager.cs
@@ -31,6 +31,9 @@ using System.IO;
using OpenMetaverse;
using OpenMetaverse.Packets;
using OpenMetaverse.Assets;
+using OpenMetaverse.Http;
+using OpenMetaverse.StructuredData;
+using OpenMetaverse.Messages.Linden;
namespace OpenMetaverse
{
@@ -312,6 +315,11 @@ namespace OpenMetaverse
///
///
///
+ ///
+ public delegate void BakedTextureUploadedCallback(UUID newAssetID);
+ ///
+ ///
+ ///
///
public delegate void UploadProgressCallback(AssetUpload upload);
///
@@ -709,6 +717,70 @@ namespace OpenMetaverse
}
}
+ public void RequestUploadBakedTexture(byte[] textureData, BakedTextureUploadedCallback callback)
+ {
+ Uri url = null;
+
+ Caps caps = Client.Network.CurrentSim.Caps;
+ if (caps != null)
+ url = caps.CapabilityURI("UploadBakedTexture");
+
+ if (url != null)
+ {
+ // Fetch the uploader capability
+ CapsClient request = new CapsClient(url);
+ request.OnComplete +=
+ delegate(CapsClient client, OSD result, Exception error)
+ {
+ if (error == null && result is OSDMap)
+ {
+ UploadBakedTextureMessage message = new UploadBakedTextureMessage();
+ message.Deserialize((OSDMap)result);
+
+ if (message.Request.State == "upload")
+ {
+ Uri uploadUrl = ((UploaderRequestUpload)message.Request).Url;
+
+ if (uploadUrl != null)
+ {
+ // POST the asset data
+ CapsClient upload = new CapsClient(uploadUrl);
+ upload.OnComplete +=
+ delegate(CapsClient client2, OSD result2, Exception error2)
+ {
+ if (error2 == null && result2 is OSDMap)
+ {
+ UploadBakedTextureMessage message2 = new UploadBakedTextureMessage();
+ message2.Deserialize((OSDMap)result2);
+
+ if (message2.Request.State == "complete")
+ {
+ callback(((UploaderRequestComplete)message2.Request).AssetID);
+ return;
+ }
+ }
+
+ Logger.Log("Bake upload failed during asset upload", Helpers.LogLevel.Warning, Client);
+ callback(UUID.Zero);
+ };
+ upload.BeginGetResponse(textureData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT);
+ return;
+ }
+ }
+ }
+
+ Logger.Log("Bake upload failed during uploader retrieval", Helpers.LogLevel.Warning, Client);
+ callback(UUID.Zero);
+ };
+ request.BeginGetResponse(new OSDMap(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT);
+ }
+ else
+ {
+ Logger.Log("UploadBakedTexture not available, falling back to UDP method", Helpers.LogLevel.Info, Client);
+ // FIXME:
+ }
+ }
+
///
/// Request a texture asset from the simulator using the system to
/// manage the requests and re-assemble the image from the packets received from the simulator
diff --git a/OpenMetaverse/Assets/AssetTypes/AssetWearable.cs b/OpenMetaverse/Assets/AssetTypes/AssetWearable.cs
index 0adb20fc..97b007ea 100644
--- a/OpenMetaverse/Assets/AssetTypes/AssetWearable.cs
+++ b/OpenMetaverse/Assets/AssetTypes/AssetWearable.cs
@@ -61,7 +61,7 @@ namespace OpenMetaverse.Assets
/// A Dictionary containing Key/Value pairs of the objects parameters
public Dictionary Params = new Dictionary();
/// A Dictionary containing Key/Value pairs where the Key is the textures Index and the Value is the Textures
- public Dictionary Textures = new Dictionary();
+ public Dictionary Textures = new Dictionary();
/// Initializes a new instance of an AssetWearable object
public AssetWearable() { }
@@ -145,7 +145,7 @@ namespace OpenMetaverse.Assets
line = lines[stri].Trim();
fields = line.Split(' ');
- AppearanceManager.TextureIndex id = (AppearanceManager.TextureIndex)Int32.Parse(fields[0]);
+ AvatarTextureIndex id = (AvatarTextureIndex)Int32.Parse(fields[0]);
UUID texture = new UUID(fields[1]);
Textures[id] = texture;
@@ -249,7 +249,7 @@ namespace OpenMetaverse.Assets
}
data.Append("textures "); data.Append(Textures.Count); data.Append(NL);
- foreach (KeyValuePair texture in Textures)
+ foreach (KeyValuePair texture in Textures)
{
data.Append((byte)texture.Key); data.Append(" "); data.Append(texture.Value.ToString()); data.Append(NL);
}
diff --git a/OpenMetaverse/Imaging/BakeLayer.cs b/OpenMetaverse/Imaging/BakeLayer.cs
index 9c7b94c9..33a1c54a 100644
--- a/OpenMetaverse/Imaging/BakeLayer.cs
+++ b/OpenMetaverse/Imaging/BakeLayer.cs
@@ -42,11 +42,11 @@ namespace OpenMetaverse.Imaging
{
public AssetTexture BakedTexture { get { return _bakedTexture; } }
public Dictionary ParamValues { get { return _paramValues; } }
- public Dictionary Textures { get { return _textures; } }
+ public Dictionary Textures { get { return _textures; } }
public int TextureCount { get { return _textureCount; } }
public int BakeWidth { get { return _bakeWidth; } }
public int BakeHeight { get { return _bakeHeight; } }
- public AppearanceManager.BakeType BakeType { get { return _bakeType; } }
+ public BakeType BakeType { get { return _bakeType; } }
/// Reference to the GridClient object
protected GridClient _client;
@@ -55,7 +55,7 @@ namespace OpenMetaverse.Imaging
/// Appearance parameters the drive the baking process
protected Dictionary _paramValues;
/// Wearable textures
- protected Dictionary _textures = new Dictionary();
+ protected Dictionary _textures = new Dictionary();
/// Total number of textures in the bake
protected int _textureCount;
/// Width of the final baked image and scratchpad
@@ -63,7 +63,7 @@ namespace OpenMetaverse.Imaging
/// Height of the final baked image and scratchpad
protected int _bakeHeight;
/// Bake type
- protected AppearanceManager.BakeType _bakeType;
+ protected BakeType _bakeType;
///
/// Default constructor
@@ -74,13 +74,13 @@ namespace OpenMetaverse.Imaging
/// composed of
/// Appearance parameters the drive the
/// baking process
- public Baker(GridClient client, AppearanceManager.BakeType bakeType, int textureCount, Dictionary paramValues)
+ public Baker(GridClient client, BakeType bakeType, int textureCount, Dictionary paramValues)
{
_client = client;
_bakeType = bakeType;
_textureCount = textureCount;
- if (bakeType == AppearanceManager.BakeType.Eyes)
+ if (bakeType == BakeType.Eyes)
{
_bakeWidth = 128;
_bakeHeight = 128;
@@ -108,7 +108,7 @@ namespace OpenMetaverse.Imaging
/// called for the texture, otherwise false
/// True if this texture is completely baked and JPEG2000 data
/// is available, otherwise false
- public bool AddTexture(AppearanceManager.TextureIndex index, AssetTexture texture, bool needsDecode)
+ public bool AddTexture(AvatarTextureIndex index, AssetTexture texture, bool needsDecode)
{
lock (_textures)
{
@@ -140,7 +140,7 @@ namespace OpenMetaverse.Imaging
}
}
- public bool MissingTexture(AppearanceManager.TextureIndex index)
+ public bool MissingTexture(AvatarTextureIndex index)
{
Logger.DebugLog(String.Format("Missing texture {0} in bake {1}", index, _bakeType), _client);
_textureCount--;
@@ -161,18 +161,18 @@ namespace OpenMetaverse.Imaging
_bakedTexture = new AssetTexture(new ManagedImage(_bakeWidth, _bakeHeight,
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump));
- if (_bakeType == AppearanceManager.BakeType.Eyes)
+ if (_bakeType == BakeType.Eyes)
{
InitBakedLayerColor(255, 255, 255);
- DrawLayer(AppearanceManager.TextureIndex.EyesIris);
+ DrawLayer(AvatarTextureIndex.EyesIris);
}
- else if (_bakeType == AppearanceManager.BakeType.Head)
+ else if (_bakeType == BakeType.Head)
{
// FIXME: Need to use the visual parameters to determine the base skin color in RGB but
// it's not apparent how to define RGB levels from the skin color parameters, so
- // for now use a grey foundation for the skin
- InitBakedLayerColor(128, 128, 128);
- DrawLayer(AppearanceManager.TextureIndex.HeadBodypaint);
+ // for now use default color for the skin
+ InitBakedLayerColor(226, 160, 125);
+ DrawLayer(AvatarTextureIndex.HeadBodypaint);
// HACK: Bake the eyelashes in if we have them
ManagedImage eyelashes = LoadAlphaLayer("head_alpha.tga");
@@ -187,7 +187,7 @@ namespace OpenMetaverse.Imaging
Logger.Log("head_alpha.tga resource not found, skipping eyelashes", Helpers.LogLevel.Info);
}
}
- else if (_bakeType == AppearanceManager.BakeType.Skirt)
+ else if (_bakeType == BakeType.Skirt)
{
float skirtRed = 1.0f, skirtGreen = 1.0f, skirtBlue = 1.0f;
@@ -202,43 +202,44 @@ namespace OpenMetaverse.Imaging
Logger.Log("Unable to determine skirt color from visual params", Helpers.LogLevel.Warning, _client);
}
- InitBakedLayerColor((byte)(skirtRed * 255.0f), (byte)(skirtGreen * 255.0f), (byte)(skirtBlue * 255.0f));
- DrawLayer(AppearanceManager.TextureIndex.Skirt);
+ // FIXME: We should be drawing the skirt first, then tinting with the skirt color
+ //InitBakedLayerColor((byte)(skirtRed * 255.0f), (byte)(skirtGreen * 255.0f), (byte)(skirtBlue * 255.0f));
+ DrawLayer(AvatarTextureIndex.Skirt);
}
- else if (_bakeType == AppearanceManager.BakeType.UpperBody)
+ else if (_bakeType == BakeType.UpperBody)
{
- InitBakedLayerColor(128, 128, 128);
- DrawLayer(AppearanceManager.TextureIndex.UpperBodypaint);
- DrawLayer(AppearanceManager.TextureIndex.UpperUndershirt);
- DrawLayer(AppearanceManager.TextureIndex.UpperGloves);
- DrawLayer(AppearanceManager.TextureIndex.UpperShirt);
- DrawLayer(AppearanceManager.TextureIndex.UpperJacket);
+ InitBakedLayerColor(226, 160, 125);
+ DrawLayer(AvatarTextureIndex.UpperBodypaint);
+ DrawLayer(AvatarTextureIndex.UpperUndershirt);
+ DrawLayer(AvatarTextureIndex.UpperGloves);
+ DrawLayer(AvatarTextureIndex.UpperShirt);
+ DrawLayer(AvatarTextureIndex.UpperJacket);
}
- else if (_bakeType == AppearanceManager.BakeType.LowerBody)
+ else if (_bakeType == BakeType.LowerBody)
{
- InitBakedLayerColor(128, 128, 128);
- DrawLayer(AppearanceManager.TextureIndex.LowerBodypaint);
- DrawLayer(AppearanceManager.TextureIndex.LowerUnderpants);
- DrawLayer(AppearanceManager.TextureIndex.LowerSocks);
- DrawLayer(AppearanceManager.TextureIndex.LowerShoes);
- DrawLayer(AppearanceManager.TextureIndex.LowerPants);
- DrawLayer(AppearanceManager.TextureIndex.LowerJacket);
+ InitBakedLayerColor(226, 160, 125);
+ DrawLayer(AvatarTextureIndex.LowerBodypaint);
+ DrawLayer(AvatarTextureIndex.LowerUnderpants);
+ DrawLayer(AvatarTextureIndex.LowerSocks);
+ DrawLayer(AvatarTextureIndex.LowerShoes);
+ DrawLayer(AvatarTextureIndex.LowerPants);
+ DrawLayer(AvatarTextureIndex.LowerJacket);
}
- else if (_bakeType == AppearanceManager.BakeType.Hair)
+ else if (_bakeType == BakeType.Hair)
{
InitBakedLayerColor(255, 255, 255);
- DrawLayer(AppearanceManager.TextureIndex.Hair);
+ DrawLayer(AvatarTextureIndex.Hair);
}
_bakedTexture.Encode();
}
- private bool DrawLayer(AppearanceManager.TextureIndex textureIndex)
+ private bool DrawLayer(AvatarTextureIndex textureIndex)
{
AssetTexture texture;
bool useSourceAlpha =
- (textureIndex == AppearanceManager.TextureIndex.HeadBodypaint ||
- textureIndex == AppearanceManager.TextureIndex.Skirt);
+ (textureIndex == AvatarTextureIndex.HeadBodypaint ||
+ textureIndex == AvatarTextureIndex.Skirt);
if (_textures.TryGetValue(textureIndex, out texture))
return DrawLayer(texture.Image, useSourceAlpha);
@@ -266,9 +267,8 @@ namespace OpenMetaverse.Imaging
catch { return false; }
}
- int alpha = 255;
- //int alphaInv = 255 - alpha;
- int alphaInv = 256 - alpha;
+ byte alpha = Byte.MaxValue;
+ byte alphaInv = (byte)(Byte.MaxValue - alpha);
byte[] bakedRed = _bakedTexture.Image.Red;
byte[] bakedGreen = _bakedTexture.Image.Green;
@@ -279,14 +279,8 @@ namespace OpenMetaverse.Imaging
byte[] sourceRed = source.Red;
byte[] sourceGreen = source.Green;
byte[] sourceBlue = source.Blue;
- byte[] sourceAlpha = null;
- byte[] sourceBump = null;
-
- if (sourceHasAlpha)
- sourceAlpha = source.Alpha;
-
- if (sourceHasBump)
- sourceBump = source.Bump;
+ byte[] sourceAlpha = sourceHasAlpha ? source.Alpha : null;
+ byte[] sourceBump = sourceHasBump ? source.Bump : null;
for (int y = 0; y < _bakeHeight; y++)
{
@@ -295,8 +289,7 @@ namespace OpenMetaverse.Imaging
if (sourceHasAlpha)
{
alpha = sourceAlpha[i];
- //alphaInv = 255 - alpha;
- alphaInv = 256 - alpha;
+ alphaInv = (byte)(Byte.MaxValue - alpha);
}
if (sourceHasColor)
@@ -340,15 +333,15 @@ namespace OpenMetaverse.Imaging
gAlt = gByte;
bAlt = bByte;
- if (rByte < byte.MaxValue)
+ if (rByte < Byte.MaxValue)
rAlt++;
else rAlt--;
- if (gByte < byte.MaxValue)
+ if (gByte < Byte.MaxValue)
gAlt++;
else gAlt--;
- if (bByte < byte.MaxValue)
+ if (bByte < Byte.MaxValue)
bAlt++;
else bAlt--;
@@ -369,7 +362,7 @@ namespace OpenMetaverse.Imaging
red[i] = rAlt;
green[i] = gByte;
blue[i] = bByte;
- alpha[i] = 128;
+ alpha[i] = Byte.MaxValue;
bump[i] = 0;
}
else
@@ -377,7 +370,7 @@ namespace OpenMetaverse.Imaging
red[i] = rByte;
green[i] = gAlt;
blue[i] = bAlt;
- alpha[i] = 128;
+ alpha[i] = Byte.MaxValue;
bump[i] = 0;
}
@@ -426,39 +419,39 @@ namespace OpenMetaverse.Imaging
}
- public static AppearanceManager.BakeType BakeTypeFor(AppearanceManager.TextureIndex index)
+ public static BakeType BakeTypeFor(AvatarTextureIndex index)
{
switch (index)
{
- case AppearanceManager.TextureIndex.HeadBodypaint:
- return AppearanceManager.BakeType.Head;
+ case AvatarTextureIndex.HeadBodypaint:
+ return BakeType.Head;
- case AppearanceManager.TextureIndex.UpperBodypaint:
- case AppearanceManager.TextureIndex.UpperGloves:
- case AppearanceManager.TextureIndex.UpperUndershirt:
- case AppearanceManager.TextureIndex.UpperShirt:
- case AppearanceManager.TextureIndex.UpperJacket:
- return AppearanceManager.BakeType.UpperBody;
+ case AvatarTextureIndex.UpperBodypaint:
+ case AvatarTextureIndex.UpperGloves:
+ case AvatarTextureIndex.UpperUndershirt:
+ case AvatarTextureIndex.UpperShirt:
+ case AvatarTextureIndex.UpperJacket:
+ return BakeType.UpperBody;
- case AppearanceManager.TextureIndex.LowerBodypaint:
- case AppearanceManager.TextureIndex.LowerUnderpants:
- case AppearanceManager.TextureIndex.LowerSocks:
- case AppearanceManager.TextureIndex.LowerShoes:
- case AppearanceManager.TextureIndex.LowerPants:
- case AppearanceManager.TextureIndex.LowerJacket:
- return AppearanceManager.BakeType.LowerBody;
+ case AvatarTextureIndex.LowerBodypaint:
+ case AvatarTextureIndex.LowerUnderpants:
+ case AvatarTextureIndex.LowerSocks:
+ case AvatarTextureIndex.LowerShoes:
+ case AvatarTextureIndex.LowerPants:
+ case AvatarTextureIndex.LowerJacket:
+ return BakeType.LowerBody;
- case AppearanceManager.TextureIndex.EyesIris:
- return AppearanceManager.BakeType.Eyes;
+ case AvatarTextureIndex.EyesIris:
+ return BakeType.Eyes;
- case AppearanceManager.TextureIndex.Skirt:
- return AppearanceManager.BakeType.Skirt;
+ case AvatarTextureIndex.Skirt:
+ return BakeType.Skirt;
- case AppearanceManager.TextureIndex.Hair:
- return AppearanceManager.BakeType.Hair;
+ case AvatarTextureIndex.Hair:
+ return BakeType.Hair;
default:
- return AppearanceManager.BakeType.Unknown;
+ return BakeType.Unknown;
}
}
}
diff --git a/OpenMetaverse/Messages/LindenMessages.cs b/OpenMetaverse/Messages/LindenMessages.cs
index 47601709..9f84e1b3 100644
--- a/OpenMetaverse/Messages/LindenMessages.cs
+++ b/OpenMetaverse/Messages/LindenMessages.cs
@@ -1387,6 +1387,11 @@ namespace OpenMetaverse.Messages.Linden
/// Base class for Asset uploads/results via Capabilities
public abstract class AssetUploaderBlock
{
+ ///
+ /// The request state
+ ///
+ public string State;
+
///
/// Serialize the object
///
@@ -1406,23 +1411,26 @@ namespace OpenMetaverse.Messages.Linden
///
public class UploaderRequestUpload : AssetUploaderBlock
{
- /// The request state (Always "upload")
- public string State = "upload";
/// The Capability URL sent by the simulator to upload the baked texture to
- public string Url;
+ public Uri Url;
+
+ public UploaderRequestUpload()
+ {
+ State = "upload";
+ }
public override OSDMap Serialize()
{
OSDMap map = new OSDMap(2);
map["state"] = OSD.FromString(State);
- map["uploader"] = OSD.FromString(Url);
+ map["uploader"] = OSD.FromUri(Url);
return map;
}
public override void Deserialize(OSDMap map)
{
- Url = map["uploader"].AsString();
+ Url = map["uploader"].AsUri();
State = map["state"].AsString();
}
}
@@ -1433,11 +1441,14 @@ namespace OpenMetaverse.Messages.Linden
///
public class UploaderRequestComplete : AssetUploaderBlock
{
- /// The request state (Always "complete")
- public string State = "complete";
/// The uploaded texture asset ID
public UUID AssetID;
+ public UploaderRequestComplete()
+ {
+ State = "complete";
+ }
+
public override OSDMap Serialize()
{
OSDMap map = new OSDMap(2);
@@ -1483,11 +1494,8 @@ namespace OpenMetaverse.Messages.Linden
else if (map.ContainsKey("state") && map["state"].AsString().Equals("complete"))
Request = new UploaderRequestComplete();
else
- {
Logger.Log("Unable to deserialize UploadBakedTexture: No message handler exists for state " + map["state"].AsString(), Helpers.LogLevel.Warning);
- }
-
if (Request != null)
Request.Deserialize(map);
}
diff --git a/OpenMetaverse/Settings.cs b/OpenMetaverse/Settings.cs
index 5c8cc62e..f18ebd7d 100644
--- a/OpenMetaverse/Settings.cs
+++ b/OpenMetaverse/Settings.cs
@@ -158,6 +158,10 @@ namespace OpenMetaverse
/// Enable/disable sending periodic camera updates
public bool SEND_AGENT_UPDATES = true;
+ /// Enable/disable automatically setting agent appearance at
+ /// login and after sim crossing
+ public bool SEND_AGENT_APPEARANCE = true;
+
/// Enable/disable automatically setting the bandwidth throttle
/// after connecting to each simulator
/// The default throttle uses the equivalent of the maximum
diff --git a/OpenMetaverseTypes/Parallel.cs b/OpenMetaverseTypes/Parallel.cs
index db10a732..ca698aa7 100644
--- a/OpenMetaverseTypes/Parallel.cs
+++ b/OpenMetaverseTypes/Parallel.cs
@@ -35,15 +35,7 @@ namespace OpenMetaverse
///
public static class Parallel
{
- private static int threadCount = System.Environment.ProcessorCount;
-
- /// The number of parallel threads to run tasks on. Defaults
- /// to the number of system processors
- public static int ThreadCount
- {
- get { return threadCount; }
- set { threadCount = Math.Max(1, value); }
- }
+ private static readonly int processorCount = System.Environment.ProcessorCount;
///
/// Executes a for loop in which iterations may run in parallel
@@ -52,6 +44,18 @@ namespace OpenMetaverse
/// The loop will be terminated before this index is reached
/// Method body to run for each iteration of the loop
public static void For(int fromInclusive, int toExclusive, Action body)
+ {
+ For(processorCount, fromInclusive, toExclusive, body);
+ }
+
+ ///
+ /// Executes a for loop in which iterations may run in parallel
+ ///
+ /// The number of concurrent execution threads to run
+ /// The loop will be started at this index
+ /// The loop will be terminated before this index is reached
+ /// Method body to run for each iteration of the loop
+ public static void For(int threadCount, int fromInclusive, int toExclusive, Action body)
{
AutoResetEvent[] threadFinishEvent = new AutoResetEvent[threadCount];
--fromInclusive;
@@ -67,12 +71,12 @@ namespace OpenMetaverse
while (true)
{
- int index = Interlocked.Increment(ref fromInclusive);
+ int currentIndex = Interlocked.Increment(ref fromInclusive);
- if (index >= toExclusive)
+ if (currentIndex >= toExclusive)
break;
- body(index);
+ body(currentIndex);
}
threadFinishEvent[threadIndex].Set();
@@ -91,6 +95,18 @@ namespace OpenMetaverse
/// An enumerable collection to iterate over
/// Method body to run for each object in the collection
public static void ForEach(IEnumerable enumerable, Action body)
+ {
+ ForEach(processorCount, enumerable, body);
+ }
+
+ ///
+ /// Executes a foreach loop in which iterations may run in parallel
+ ///
+ /// Object type that the collection wraps
+ /// The number of concurrent execution threads to run
+ /// An enumerable collection to iterate over
+ /// Method body to run for each object in the collection
+ public static void ForEach(int threadCount, IEnumerable enumerable, Action body)
{
AutoResetEvent[] threadFinishEvent = new AutoResetEvent[threadCount];
IEnumerator enumerator = enumerable.GetEnumerator();
@@ -113,7 +129,7 @@ namespace OpenMetaverse
{
if (!enumerator.MoveNext())
break;
- entry = enumerator.Current;
+ entry = (T)enumerator.Current; // Explicit typecast for Mono's sake
}
body(entry);
@@ -127,5 +143,52 @@ namespace OpenMetaverse
for (int i = 0; i < threadCount; i++)
threadFinishEvent[i].WaitOne();
}
+
+ ///
+ /// Executes a series of tasks in parallel
+ ///
+ /// A series of method bodies to execute
+ public static void Invoke(params Action[] actions)
+ {
+ Invoke(processorCount, actions);
+ }
+
+ ///
+ /// Executes a series of tasks in parallel
+ ///
+ /// The number of concurrent execution threads to run
+ /// A series of method bodies to execute
+ public static void Invoke(int threadCount, params Action[] actions)
+ {
+ AutoResetEvent[] threadFinishEvent = new AutoResetEvent[threadCount];
+ int index = -1;
+
+ for (int i = 0; i < threadCount; i++)
+ {
+ threadFinishEvent[i] = new AutoResetEvent(false);
+
+ ThreadPool.QueueUserWorkItem(
+ delegate(object o)
+ {
+ int threadIndex = (int)o;
+
+ while (true)
+ {
+ int currentIndex = Interlocked.Increment(ref index);
+
+ if (currentIndex >= actions.Length)
+ break;
+
+ actions[currentIndex]();
+ }
+
+ threadFinishEvent[threadIndex].Set();
+ }, i
+ );
+ }
+
+ for (int i = 0; i < threadCount; i++)
+ threadFinishEvent[i].WaitOne();
+ }
}
}
diff --git a/Programs/AvatarPreview/frmAvatar.cs b/Programs/AvatarPreview/frmAvatar.cs
index 6519c19a..53a2fede 100644
--- a/Programs/AvatarPreview/frmAvatar.cs
+++ b/Programs/AvatarPreview/frmAvatar.cs
@@ -282,30 +282,30 @@ namespace AvatarPreview
#region Baking
Dictionary paramValues = GetParamValues();
- Dictionary layers =
- new Dictionary();
+ Dictionary layers =
+ new Dictionary();
int textureCount = 0;
if ((string)control.Tag == "Head")
{
if (picHair.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.Hair,
+ layers.Add(AvatarTextureIndex.Hair,
new AssetTexture(new ManagedImage((Bitmap)picHair.Image)));
++textureCount;
}
if (picHeadBodypaint.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.HeadBodypaint,
+ layers.Add(AvatarTextureIndex.HeadBodypaint,
new AssetTexture(new ManagedImage((Bitmap)picHeadBodypaint.Image)));
++textureCount;
}
// Compute the head bake
Baker baker = new Baker(
- _client, AppearanceManager.BakeType.Head, textureCount, paramValues);
+ _client, BakeType.Head, textureCount, paramValues);
- foreach (KeyValuePair kvp in layers)
+ foreach (KeyValuePair kvp in layers)
baker.AddTexture(kvp.Key, kvp.Value, false);
if (baker.BakedTexture != null)
@@ -324,40 +324,40 @@ namespace AvatarPreview
{
if (picUpperBodypaint.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.UpperBodypaint,
+ layers.Add(AvatarTextureIndex.UpperBodypaint,
new AssetTexture(new ManagedImage((Bitmap)picUpperBodypaint.Image)));
++textureCount;
}
if (picUpperGloves.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.UpperGloves,
+ layers.Add(AvatarTextureIndex.UpperGloves,
new AssetTexture(new ManagedImage((Bitmap)picUpperGloves.Image)));
++textureCount;
}
if (picUpperUndershirt.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.UpperUndershirt,
+ layers.Add(AvatarTextureIndex.UpperUndershirt,
new AssetTexture(new ManagedImage((Bitmap)picUpperUndershirt.Image)));
++textureCount;
}
if (picUpperShirt.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.UpperShirt,
+ layers.Add(AvatarTextureIndex.UpperShirt,
new AssetTexture(new ManagedImage((Bitmap)picUpperShirt.Image)));
++textureCount;
}
if (picUpperJacket.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.UpperJacket,
+ layers.Add(AvatarTextureIndex.UpperJacket,
new AssetTexture(new ManagedImage((Bitmap)picUpperJacket.Image)));
++textureCount;
}
// Compute the upper body bake
Baker baker = new Baker(
- _client, AppearanceManager.BakeType.UpperBody, textureCount, paramValues);
+ _client, BakeType.UpperBody, textureCount, paramValues);
- foreach (KeyValuePair kvp in layers)
+ foreach (KeyValuePair kvp in layers)
baker.AddTexture(kvp.Key, kvp.Value, false);
if (baker.BakedTexture != null)
@@ -376,40 +376,40 @@ namespace AvatarPreview
{
if (picLowerBodypaint.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.LowerBodypaint,
+ layers.Add(AvatarTextureIndex.LowerBodypaint,
new AssetTexture(new ManagedImage((Bitmap)picLowerBodypaint.Image)));
++textureCount;
}
if (picLowerUnderpants.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.LowerUnderpants,
+ layers.Add(AvatarTextureIndex.LowerUnderpants,
new AssetTexture(new ManagedImage((Bitmap)picLowerUnderpants.Image)));
++textureCount;
}
if (picLowerSocks.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.LowerSocks,
+ layers.Add(AvatarTextureIndex.LowerSocks,
new AssetTexture(new ManagedImage((Bitmap)picLowerSocks.Image)));
++textureCount;
}
if (picLowerShoes.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.LowerShoes,
+ layers.Add(AvatarTextureIndex.LowerShoes,
new AssetTexture(new ManagedImage((Bitmap)picLowerShoes.Image)));
++textureCount;
}
if (picLowerPants.Image != null)
{
- layers.Add(AppearanceManager.TextureIndex.LowerPants,
+ layers.Add(AvatarTextureIndex.LowerPants,
new AssetTexture(new ManagedImage((Bitmap)picLowerPants.Image)));
++textureCount;
}
// Compute the lower body bake
Baker baker = new Baker(
- _client, AppearanceManager.BakeType.LowerBody, textureCount, paramValues);
+ _client, BakeType.LowerBody, textureCount, paramValues);
- foreach (KeyValuePair kvp in layers)
+ foreach (KeyValuePair kvp in layers)
baker.AddTexture(kvp.Key, kvp.Value, false);
if (baker.BakedTexture != null)
diff --git a/Programs/examples/Dashboard/Dashboard.cs b/Programs/examples/Dashboard/Dashboard.cs
index b91351d4..acef11a9 100644
--- a/Programs/examples/Dashboard/Dashboard.cs
+++ b/Programs/examples/Dashboard/Dashboard.cs
@@ -63,7 +63,6 @@ namespace Dashboard
Client.Settings.USE_LLSD_LOGIN = true;
Client.Settings.USE_ASSET_CACHE = true;
- Client.Network.OnCurrentSimChanged += new NetworkManager.CurrentSimChangedCallback(Network_OnCurrentSimChanged);
Client.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected);
Client.Self.OnInstantMessage += new AgentManager.InstantMessageCallback(Self_OnInstantMessage);
@@ -101,11 +100,6 @@ namespace Dashboard
MessageBox.Show(group.Name + " = " + group.ID);
}
- void Network_OnCurrentSimChanged(Simulator PreviousSimulator)
- {
- Client.Appearance.SetPreviousAppearance(false);
- }
-
void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message)
{
InitializeClient(!ShuttingDown);
diff --git a/Programs/examples/TestClient/Commands/Appearance/AppearanceCommand.cs b/Programs/examples/TestClient/Commands/Appearance/AppearanceCommand.cs
index 995858c3..84f47c91 100644
--- a/Programs/examples/TestClient/Commands/Appearance/AppearanceCommand.cs
+++ b/Programs/examples/TestClient/Commands/Appearance/AppearanceCommand.cs
@@ -12,35 +12,14 @@ namespace OpenMetaverse.TestClient
public AppearanceCommand(TestClient testClient)
{
Name = "appearance";
- Description = "Set your current appearance to your last saved appearance";
+ Description = "Set your current appearance to your last saved appearance. Usage: appearance [rebake]";
Category = CommandCategory.Appearance;
}
public override string Execute(string[] args, UUID fromAgentID)
{
- bool success = false;
-
- // Register a handler for the appearance event
- AutoResetEvent appearanceEvent = new AutoResetEvent(false);
- AppearanceManager.AppearanceUpdatedCallback callback =
- delegate(Primitive.TextureEntry te) { appearanceEvent.Set(); };
- Client.Appearance.OnAppearanceUpdated += callback;
-
- // Start the appearance setting process (with baking enabled or disabled)
- Client.Appearance.SetPreviousAppearance(!(args.Length > 0 && args[0].Equals("nobake")));
-
- // Wait for the process to complete or time out
- if (appearanceEvent.WaitOne(1000 * 120, false))
- success = true;
-
- // Unregister the handler
- Client.Appearance.OnAppearanceUpdated -= callback;
-
- // Return success or failure message
- if (success)
- return "Successfully set appearance";
- else
- return "Timed out while setting appearance";
+ Client.Appearance.RequestSetAppearance((args.Length > 0 && args[0].Equals("rebake")));
+ return "Appearance sequence started";
}
}
}
diff --git a/Programs/examples/TestClient/Commands/Appearance/AvatarInfoCommand.cs b/Programs/examples/TestClient/Commands/Appearance/AvatarInfoCommand.cs
index 1fa9c6d2..e3f08d7d 100644
--- a/Programs/examples/TestClient/Commands/Appearance/AvatarInfoCommand.cs
+++ b/Programs/examples/TestClient/Commands/Appearance/AvatarInfoCommand.cs
@@ -37,7 +37,7 @@ namespace OpenMetaverse.TestClient.Commands.Appearance
if (foundAv.Textures.FaceTextures[i] != null)
{
Primitive.TextureEntryFace face = foundAv.Textures.FaceTextures[i];
- AppearanceManager.TextureIndex type = (AppearanceManager.TextureIndex)i;
+ AvatarTextureIndex type = (AvatarTextureIndex)i;
output.AppendFormat("{0}: {1}", type, face.TextureID);
output.AppendLine();
diff --git a/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs b/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs
index 1fbd4f4c..ce7ba1ea 100644
--- a/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs
+++ b/Programs/examples/TestClient/Commands/Appearance/WearCommand.cs
@@ -31,16 +31,9 @@ namespace OpenMetaverse.TestClient
target = target.TrimEnd();
- try
- {
- Client.Appearance.WearOutfit(target.Split('/'), bake);
- }
- catch (InvalidOutfitException ex)
- {
- return "Invalid outfit (" + ex.Message + ")";
- }
+ //Client.Appearance.WearOutfit(target.Split('/'), bake);
- return String.Empty;
+ return "FIXME: Implement this";
}
}
}
diff --git a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
index 0d225d07..ad3c82f7 100644
--- a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
+++ b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
@@ -31,22 +31,19 @@ namespace OpenMetaverse.TestClient
// initialize results list
List found = new List();
- try
+
+ // find the folder
+ found = Client.Inventory.LocalFind(Client.Inventory.Store.RootFolder.UUID, target.Split('/'), 0, true);
+
+ if (found.Count.Equals(1))
{
- // find the folder
- found = Client.Inventory.LocalFind(Client.Inventory.Store.RootFolder.UUID, target.Split('/'), 0, true);
- if (found.Count.Equals(1))
- {
- // move the folder to the trash folder
- Client.Inventory.MoveFolder(found[0].UUID, Client.Inventory.FindFolderForType(AssetType.TrashFolder));
- return String.Format("Moved folder {0} to Trash", found[0].Name);
- }
+ // move the folder to the trash folder
+ Client.Inventory.MoveFolder(found[0].UUID, Client.Inventory.FindFolderForType(AssetType.TrashFolder));
+
+ return String.Format("Moved folder {0} to Trash", found[0].Name);
}
- catch (InvalidOutfitException ex)
- {
- return "Folder Not Found: (" + ex.Message + ")";
- }
- return string.Empty;
+
+ return String.Empty;
}
}
}
\ No newline at end of file
diff --git a/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs b/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs
index acde1d1f..3853b778 100644
--- a/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs
+++ b/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs
@@ -55,20 +55,20 @@ namespace OpenMetaverse.TestClient
{
ImageType type = ImageType.Normal;
- switch ((AppearanceManager.TextureIndex)j)
+ switch ((AvatarTextureIndex)j)
{
- case AppearanceManager.TextureIndex.HeadBaked:
- case AppearanceManager.TextureIndex.EyesBaked:
- case AppearanceManager.TextureIndex.UpperBaked:
- case AppearanceManager.TextureIndex.LowerBaked:
- case AppearanceManager.TextureIndex.SkirtBaked:
+ case AvatarTextureIndex.HeadBaked:
+ case AvatarTextureIndex.EyesBaked:
+ case AvatarTextureIndex.UpperBaked:
+ case AvatarTextureIndex.LowerBaked:
+ case AvatarTextureIndex.SkirtBaked:
type = ImageType.Baked;
break;
}
OutfitAssets.Add(face.TextureID);
Client.Assets.RequestImage(face.TextureID, type, Assets_OnImageReceived);
- output.Append(((AppearanceManager.TextureIndex)j).ToString());
+ output.Append(((AvatarTextureIndex)j).ToString());
output.Append(" ");
}
}
diff --git a/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs b/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs
index 4fcb6b4e..e2886c71 100644
--- a/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs
+++ b/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs
@@ -60,14 +60,14 @@ namespace OpenMetaverse.TestClient
// Determine if this is a baked outfit texture or a normal texture
ImageType type = ImageType.Normal;
- AppearanceManager.TextureIndex index = (AppearanceManager.TextureIndex)i;
+ AvatarTextureIndex index = (AvatarTextureIndex)i;
switch (index)
{
- case AppearanceManager.TextureIndex.EyesBaked:
- case AppearanceManager.TextureIndex.HeadBaked:
- case AppearanceManager.TextureIndex.LowerBaked:
- case AppearanceManager.TextureIndex.SkirtBaked:
- case AppearanceManager.TextureIndex.UpperBaked:
+ case AvatarTextureIndex.EyesBaked:
+ case AvatarTextureIndex.HeadBaked:
+ case AvatarTextureIndex.LowerBaked:
+ case AvatarTextureIndex.SkirtBaked:
+ case AvatarTextureIndex.UpperBaked:
type = ImageType.Baked;
break;
}
diff --git a/OpenMetaverse/Resources/avatar_lad.xml b/bin/openmetaverse_data/avatar_lad.xml
similarity index 100%
rename from OpenMetaverse/Resources/avatar_lad.xml
rename to bin/openmetaverse_data/avatar_lad.xml
diff --git a/OpenMetaverse/Resources/blush_alpha.tga b/bin/openmetaverse_data/blush_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/blush_alpha.tga
rename to bin/openmetaverse_data/blush_alpha.tga
diff --git a/OpenMetaverse/Resources/body_skingrain.tga b/bin/openmetaverse_data/body_skingrain.tga
similarity index 100%
rename from OpenMetaverse/Resources/body_skingrain.tga
rename to bin/openmetaverse_data/body_skingrain.tga
diff --git a/OpenMetaverse/Resources/bodyfreckles_alpha.tga b/bin/openmetaverse_data/bodyfreckles_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/bodyfreckles_alpha.tga
rename to bin/openmetaverse_data/bodyfreckles_alpha.tga
diff --git a/OpenMetaverse/Resources/bump_face_wrinkles.tga b/bin/openmetaverse_data/bump_face_wrinkles.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_face_wrinkles.tga
rename to bin/openmetaverse_data/bump_face_wrinkles.tga
diff --git a/OpenMetaverse/Resources/bump_head_base.tga b/bin/openmetaverse_data/bump_head_base.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_head_base.tga
rename to bin/openmetaverse_data/bump_head_base.tga
diff --git a/OpenMetaverse/Resources/bump_lowerbody_base.tga b/bin/openmetaverse_data/bump_lowerbody_base.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_lowerbody_base.tga
rename to bin/openmetaverse_data/bump_lowerbody_base.tga
diff --git a/OpenMetaverse/Resources/bump_pants_wrinkles.tga b/bin/openmetaverse_data/bump_pants_wrinkles.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_pants_wrinkles.tga
rename to bin/openmetaverse_data/bump_pants_wrinkles.tga
diff --git a/OpenMetaverse/Resources/bump_shirt_wrinkles.tga b/bin/openmetaverse_data/bump_shirt_wrinkles.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_shirt_wrinkles.tga
rename to bin/openmetaverse_data/bump_shirt_wrinkles.tga
diff --git a/OpenMetaverse/Resources/bump_upperbody_base.tga b/bin/openmetaverse_data/bump_upperbody_base.tga
similarity index 100%
rename from OpenMetaverse/Resources/bump_upperbody_base.tga
rename to bin/openmetaverse_data/bump_upperbody_base.tga
diff --git a/OpenMetaverse/Resources/eyebrows_alpha.tga b/bin/openmetaverse_data/eyebrows_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/eyebrows_alpha.tga
rename to bin/openmetaverse_data/eyebrows_alpha.tga
diff --git a/OpenMetaverse/Resources/eyeliner_alpha.tga b/bin/openmetaverse_data/eyeliner_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/eyeliner_alpha.tga
rename to bin/openmetaverse_data/eyeliner_alpha.tga
diff --git a/OpenMetaverse/Resources/eyeshadow_inner_alpha.tga b/bin/openmetaverse_data/eyeshadow_inner_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/eyeshadow_inner_alpha.tga
rename to bin/openmetaverse_data/eyeshadow_inner_alpha.tga
diff --git a/OpenMetaverse/Resources/eyeshadow_outer_alpha.tga b/bin/openmetaverse_data/eyeshadow_outer_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/eyeshadow_outer_alpha.tga
rename to bin/openmetaverse_data/eyeshadow_outer_alpha.tga
diff --git a/OpenMetaverse/Resources/eyewhite.tga b/bin/openmetaverse_data/eyewhite.tga
similarity index 100%
rename from OpenMetaverse/Resources/eyewhite.tga
rename to bin/openmetaverse_data/eyewhite.tga
diff --git a/OpenMetaverse/Resources/facehair_chincurtains_alpha.tga b/bin/openmetaverse_data/facehair_chincurtains_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/facehair_chincurtains_alpha.tga
rename to bin/openmetaverse_data/facehair_chincurtains_alpha.tga
diff --git a/OpenMetaverse/Resources/facehair_moustache_alpha.tga b/bin/openmetaverse_data/facehair_moustache_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/facehair_moustache_alpha.tga
rename to bin/openmetaverse_data/facehair_moustache_alpha.tga
diff --git a/OpenMetaverse/Resources/facehair_sideburns_alpha.tga b/bin/openmetaverse_data/facehair_sideburns_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/facehair_sideburns_alpha.tga
rename to bin/openmetaverse_data/facehair_sideburns_alpha.tga
diff --git a/OpenMetaverse/Resources/facehair_soulpatch_alpha.tga b/bin/openmetaverse_data/facehair_soulpatch_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/facehair_soulpatch_alpha.tga
rename to bin/openmetaverse_data/facehair_soulpatch_alpha.tga
diff --git a/OpenMetaverse/Resources/freckles_alpha.tga b/bin/openmetaverse_data/freckles_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/freckles_alpha.tga
rename to bin/openmetaverse_data/freckles_alpha.tga
diff --git a/OpenMetaverse/Resources/glove_length_alpha.tga b/bin/openmetaverse_data/glove_length_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/glove_length_alpha.tga
rename to bin/openmetaverse_data/glove_length_alpha.tga
diff --git a/OpenMetaverse/Resources/gloves_fingers_alpha.tga b/bin/openmetaverse_data/gloves_fingers_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/gloves_fingers_alpha.tga
rename to bin/openmetaverse_data/gloves_fingers_alpha.tga
diff --git a/OpenMetaverse/Resources/head_alpha.tga b/bin/openmetaverse_data/head_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_alpha.tga
rename to bin/openmetaverse_data/head_alpha.tga
diff --git a/OpenMetaverse/Resources/head_color.tga b/bin/openmetaverse_data/head_color.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_color.tga
rename to bin/openmetaverse_data/head_color.tga
diff --git a/OpenMetaverse/Resources/head_hair.tga b/bin/openmetaverse_data/head_hair.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_hair.tga
rename to bin/openmetaverse_data/head_hair.tga
diff --git a/OpenMetaverse/Resources/head_highlights_alpha.tga b/bin/openmetaverse_data/head_highlights_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_highlights_alpha.tga
rename to bin/openmetaverse_data/head_highlights_alpha.tga
diff --git a/OpenMetaverse/Resources/head_shading_alpha.tga b/bin/openmetaverse_data/head_shading_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_shading_alpha.tga
rename to bin/openmetaverse_data/head_shading_alpha.tga
diff --git a/OpenMetaverse/Resources/head_skingrain.tga b/bin/openmetaverse_data/head_skingrain.tga
similarity index 100%
rename from OpenMetaverse/Resources/head_skingrain.tga
rename to bin/openmetaverse_data/head_skingrain.tga
diff --git a/OpenMetaverse/Resources/jacket_length_lower_alpha.tga b/bin/openmetaverse_data/jacket_length_lower_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/jacket_length_lower_alpha.tga
rename to bin/openmetaverse_data/jacket_length_lower_alpha.tga
diff --git a/OpenMetaverse/Resources/jacket_length_upper_alpha.tga b/bin/openmetaverse_data/jacket_length_upper_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/jacket_length_upper_alpha.tga
rename to bin/openmetaverse_data/jacket_length_upper_alpha.tga
diff --git a/OpenMetaverse/Resources/jacket_open_lower_alpha.tga b/bin/openmetaverse_data/jacket_open_lower_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/jacket_open_lower_alpha.tga
rename to bin/openmetaverse_data/jacket_open_lower_alpha.tga
diff --git a/OpenMetaverse/Resources/jacket_open_upper_alpha.tga b/bin/openmetaverse_data/jacket_open_upper_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/jacket_open_upper_alpha.tga
rename to bin/openmetaverse_data/jacket_open_upper_alpha.tga
diff --git a/OpenMetaverse/Resources/lipgloss_alpha.tga b/bin/openmetaverse_data/lipgloss_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/lipgloss_alpha.tga
rename to bin/openmetaverse_data/lipgloss_alpha.tga
diff --git a/OpenMetaverse/Resources/lips_mask.tga b/bin/openmetaverse_data/lips_mask.tga
similarity index 100%
rename from OpenMetaverse/Resources/lips_mask.tga
rename to bin/openmetaverse_data/lips_mask.tga
diff --git a/OpenMetaverse/Resources/lipstick_alpha.tga b/bin/openmetaverse_data/lipstick_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/lipstick_alpha.tga
rename to bin/openmetaverse_data/lipstick_alpha.tga
diff --git a/OpenMetaverse/Resources/lowerbody_color.tga b/bin/openmetaverse_data/lowerbody_color.tga
similarity index 100%
rename from OpenMetaverse/Resources/lowerbody_color.tga
rename to bin/openmetaverse_data/lowerbody_color.tga
diff --git a/OpenMetaverse/Resources/lowerbody_highlights_alpha.tga b/bin/openmetaverse_data/lowerbody_highlights_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/lowerbody_highlights_alpha.tga
rename to bin/openmetaverse_data/lowerbody_highlights_alpha.tga
diff --git a/OpenMetaverse/Resources/lowerbody_shading_alpha.tga b/bin/openmetaverse_data/lowerbody_shading_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/lowerbody_shading_alpha.tga
rename to bin/openmetaverse_data/lowerbody_shading_alpha.tga
diff --git a/OpenMetaverse/Resources/nailpolish_alpha.tga b/bin/openmetaverse_data/nailpolish_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/nailpolish_alpha.tga
rename to bin/openmetaverse_data/nailpolish_alpha.tga
diff --git a/OpenMetaverse/Resources/pants_length_alpha.tga b/bin/openmetaverse_data/pants_length_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/pants_length_alpha.tga
rename to bin/openmetaverse_data/pants_length_alpha.tga
diff --git a/OpenMetaverse/Resources/pants_waist_alpha.tga b/bin/openmetaverse_data/pants_waist_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/pants_waist_alpha.tga
rename to bin/openmetaverse_data/pants_waist_alpha.tga
diff --git a/OpenMetaverse/Resources/rosyface_alpha.tga b/bin/openmetaverse_data/rosyface_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/rosyface_alpha.tga
rename to bin/openmetaverse_data/rosyface_alpha.tga
diff --git a/OpenMetaverse/Resources/rouge_alpha.tga b/bin/openmetaverse_data/rouge_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/rouge_alpha.tga
rename to bin/openmetaverse_data/rouge_alpha.tga
diff --git a/OpenMetaverse/Resources/shirt_bottom_alpha.tga b/bin/openmetaverse_data/shirt_bottom_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/shirt_bottom_alpha.tga
rename to bin/openmetaverse_data/shirt_bottom_alpha.tga
diff --git a/OpenMetaverse/Resources/shirt_collar_alpha.tga b/bin/openmetaverse_data/shirt_collar_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/shirt_collar_alpha.tga
rename to bin/openmetaverse_data/shirt_collar_alpha.tga
diff --git a/OpenMetaverse/Resources/shirt_collar_back_alpha.tga b/bin/openmetaverse_data/shirt_collar_back_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/shirt_collar_back_alpha.tga
rename to bin/openmetaverse_data/shirt_collar_back_alpha.tga
diff --git a/OpenMetaverse/Resources/shirt_sleeve_alpha.tga b/bin/openmetaverse_data/shirt_sleeve_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/shirt_sleeve_alpha.tga
rename to bin/openmetaverse_data/shirt_sleeve_alpha.tga
diff --git a/OpenMetaverse/Resources/shoe_height_alpha.tga b/bin/openmetaverse_data/shoe_height_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/shoe_height_alpha.tga
rename to bin/openmetaverse_data/shoe_height_alpha.tga
diff --git a/OpenMetaverse/Resources/skirt_length_alpha.tga b/bin/openmetaverse_data/skirt_length_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/skirt_length_alpha.tga
rename to bin/openmetaverse_data/skirt_length_alpha.tga
diff --git a/OpenMetaverse/Resources/skirt_slit_back_alpha.tga b/bin/openmetaverse_data/skirt_slit_back_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/skirt_slit_back_alpha.tga
rename to bin/openmetaverse_data/skirt_slit_back_alpha.tga
diff --git a/OpenMetaverse/Resources/skirt_slit_front_alpha.tga b/bin/openmetaverse_data/skirt_slit_front_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/skirt_slit_front_alpha.tga
rename to bin/openmetaverse_data/skirt_slit_front_alpha.tga
diff --git a/OpenMetaverse/Resources/skirt_slit_left_alpha.tga b/bin/openmetaverse_data/skirt_slit_left_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/skirt_slit_left_alpha.tga
rename to bin/openmetaverse_data/skirt_slit_left_alpha.tga
diff --git a/OpenMetaverse/Resources/skirt_slit_right_alpha.tga b/bin/openmetaverse_data/skirt_slit_right_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/skirt_slit_right_alpha.tga
rename to bin/openmetaverse_data/skirt_slit_right_alpha.tga
diff --git a/OpenMetaverse/Resources/underpants_trial_female.tga b/bin/openmetaverse_data/underpants_trial_female.tga
similarity index 100%
rename from OpenMetaverse/Resources/underpants_trial_female.tga
rename to bin/openmetaverse_data/underpants_trial_female.tga
diff --git a/OpenMetaverse/Resources/underpants_trial_male.tga b/bin/openmetaverse_data/underpants_trial_male.tga
similarity index 100%
rename from OpenMetaverse/Resources/underpants_trial_male.tga
rename to bin/openmetaverse_data/underpants_trial_male.tga
diff --git a/OpenMetaverse/Resources/undershirt_trial_female.tga b/bin/openmetaverse_data/undershirt_trial_female.tga
similarity index 100%
rename from OpenMetaverse/Resources/undershirt_trial_female.tga
rename to bin/openmetaverse_data/undershirt_trial_female.tga
diff --git a/OpenMetaverse/Resources/upperbody_color.tga b/bin/openmetaverse_data/upperbody_color.tga
similarity index 100%
rename from OpenMetaverse/Resources/upperbody_color.tga
rename to bin/openmetaverse_data/upperbody_color.tga
diff --git a/OpenMetaverse/Resources/upperbody_highlights_alpha.tga b/bin/openmetaverse_data/upperbody_highlights_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/upperbody_highlights_alpha.tga
rename to bin/openmetaverse_data/upperbody_highlights_alpha.tga
diff --git a/OpenMetaverse/Resources/upperbody_shading_alpha.tga b/bin/openmetaverse_data/upperbody_shading_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/upperbody_shading_alpha.tga
rename to bin/openmetaverse_data/upperbody_shading_alpha.tga
diff --git a/OpenMetaverse/Resources/upperbodyfreckles_alpha.tga b/bin/openmetaverse_data/upperbodyfreckles_alpha.tga
similarity index 100%
rename from OpenMetaverse/Resources/upperbodyfreckles_alpha.tga
rename to bin/openmetaverse_data/upperbodyfreckles_alpha.tga