From c1bc0b4af64cb9bcf4e9db4d60cc1a4ef2ead7be Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Fri, 31 Jul 2009 17:43:01 +0000 Subject: [PATCH] * Moved OpenMetaverse/Resources to bin/openmetaverse_data until we have a working xbuild and reorganize SVN * Complete rewrite of AppearanceManager. Appearance editing has not been (re)implemented yet, but the normal appearance setting is much more reliable * Added a setting (defaulted to true) for automatically setting appearance * Various baking hacks to get slightly less ugly avatars * Added baked texture uploading through CAPS in AssetManager.RequestUploadBakedTexture(). UDP fallback is not implemented yet * Added Parallel.Invoke() and overloads for all three methods that take a threadCount git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@3038 52acb1d6-8a22-11de-b505-999d5b087335 --- OpenMetaverse/AppearanceManager.cs | 2120 ++++++++--------- OpenMetaverse/AssetManager.cs | 72 + .../Assets/AssetTypes/AssetWearable.cs | 6 +- OpenMetaverse/Imaging/BakeLayer.cs | 149 +- OpenMetaverse/Messages/LindenMessages.cs | 28 +- OpenMetaverse/Settings.cs | 4 + OpenMetaverseTypes/Parallel.cs | 89 +- Programs/AvatarPreview/frmAvatar.cs | 40 +- Programs/examples/Dashboard/Dashboard.cs | 6 - .../Commands/Appearance/AppearanceCommand.cs | 27 +- .../Commands/Appearance/AvatarInfoCommand.cs | 2 +- .../Commands/Appearance/WearCommand.cs | 11 +- .../Commands/Inventory/DeleteFolderCommand.cs | 25 +- .../Commands/Inventory/DumpOutfitCommand.cs | 14 +- .../Commands/Prims/TexturesCommand.cs | 12 +- .../openmetaverse_data}/avatar_lad.xml | 0 .../openmetaverse_data}/blush_alpha.tga | Bin .../openmetaverse_data}/body_skingrain.tga | Bin .../bodyfreckles_alpha.tga | Bin .../bump_face_wrinkles.tga | Bin .../openmetaverse_data}/bump_head_base.tga | Bin .../bump_lowerbody_base.tga | Bin .../bump_pants_wrinkles.tga | Bin .../bump_shirt_wrinkles.tga | Bin .../bump_upperbody_base.tga | Bin .../openmetaverse_data}/eyebrows_alpha.tga | Bin .../openmetaverse_data}/eyeliner_alpha.tga | Bin .../eyeshadow_inner_alpha.tga | Bin .../eyeshadow_outer_alpha.tga | Bin .../openmetaverse_data}/eyewhite.tga | Bin .../facehair_chincurtains_alpha.tga | Bin .../facehair_moustache_alpha.tga | Bin .../facehair_sideburns_alpha.tga | Bin .../facehair_soulpatch_alpha.tga | Bin .../openmetaverse_data}/freckles_alpha.tga | Bin .../glove_length_alpha.tga | Bin .../gloves_fingers_alpha.tga | Bin .../openmetaverse_data}/head_alpha.tga | Bin .../openmetaverse_data}/head_color.tga | Bin .../openmetaverse_data}/head_hair.tga | Bin .../head_highlights_alpha.tga | Bin .../head_shading_alpha.tga | Bin .../openmetaverse_data}/head_skingrain.tga | Bin .../jacket_length_lower_alpha.tga | Bin .../jacket_length_upper_alpha.tga | Bin .../jacket_open_lower_alpha.tga | Bin .../jacket_open_upper_alpha.tga | Bin .../openmetaverse_data}/lipgloss_alpha.tga | Bin .../openmetaverse_data}/lips_mask.tga | Bin .../openmetaverse_data}/lipstick_alpha.tga | Bin .../openmetaverse_data}/lowerbody_color.tga | Bin .../lowerbody_highlights_alpha.tga | Bin .../lowerbody_shading_alpha.tga | Bin .../openmetaverse_data}/nailpolish_alpha.tga | Bin .../pants_length_alpha.tga | Bin .../openmetaverse_data}/pants_waist_alpha.tga | Bin .../openmetaverse_data}/rosyface_alpha.tga | Bin .../openmetaverse_data}/rouge_alpha.tga | Bin .../shirt_bottom_alpha.tga | Bin .../shirt_collar_alpha.tga | Bin .../shirt_collar_back_alpha.tga | Bin .../shirt_sleeve_alpha.tga | Bin .../openmetaverse_data}/shoe_height_alpha.tga | Bin .../skirt_length_alpha.tga | Bin .../skirt_slit_back_alpha.tga | Bin .../skirt_slit_front_alpha.tga | Bin .../skirt_slit_left_alpha.tga | Bin .../skirt_slit_right_alpha.tga | Bin .../underpants_trial_female.tga | Bin .../underpants_trial_male.tga | Bin .../undershirt_trial_female.tga | Bin .../openmetaverse_data}/upperbody_color.tga | Bin .../upperbody_highlights_alpha.tga | Bin .../upperbody_shading_alpha.tga | Bin .../upperbodyfreckles_alpha.tga | Bin 75 files changed, 1258 insertions(+), 1347 deletions(-) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/avatar_lad.xml (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/blush_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/body_skingrain.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bodyfreckles_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_face_wrinkles.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_head_base.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_lowerbody_base.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_pants_wrinkles.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_shirt_wrinkles.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/bump_upperbody_base.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/eyebrows_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/eyeliner_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/eyeshadow_inner_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/eyeshadow_outer_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/eyewhite.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/facehair_chincurtains_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/facehair_moustache_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/facehair_sideburns_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/facehair_soulpatch_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/freckles_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/glove_length_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/gloves_fingers_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_color.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_hair.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_highlights_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_shading_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/head_skingrain.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/jacket_length_lower_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/jacket_length_upper_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/jacket_open_lower_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/jacket_open_upper_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lipgloss_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lips_mask.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lipstick_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lowerbody_color.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lowerbody_highlights_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/lowerbody_shading_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/nailpolish_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/pants_length_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/pants_waist_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/rosyface_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/rouge_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/shirt_bottom_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/shirt_collar_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/shirt_collar_back_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/shirt_sleeve_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/shoe_height_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/skirt_length_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/skirt_slit_back_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/skirt_slit_front_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/skirt_slit_left_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/skirt_slit_right_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/underpants_trial_female.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/underpants_trial_male.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/undershirt_trial_female.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/upperbody_color.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/upperbody_highlights_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/upperbody_shading_alpha.tga (100%) rename {OpenMetaverse/Resources => bin/openmetaverse_data}/upperbodyfreckles_alpha.tga (100%) 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