Files
libremetaverse/libsecondlife/AssetSystem/AppearanceManager.cs
jedediah 937f15ab6f This is a big merge of my work from the Pleiades internal repo.
Not yet tested on Linux or OSX!
Notable changes:
* AppearanceManager works much better though baking is only a placeholder
* Fixes to openjpeg/TGA code
* VisualParams class interface changed
* Some TestClient fixes
* Rest of TGA files added to Resources (they will eventually be used for baking)This is a big merge of my work from the Pleiades internal repo. Notable changes:
* AppearanceManager works much better though baking is only a placeholder
* Fixes to openjpeg/TGA code
* VisualParams class interface changed
* Some TestClient fixes
* Rest of TGA files added to Resources (they will eventually be used for baking)


git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1284 52acb1d6-8a22-11de-b505-999d5b087335
2007-07-11 09:17:46 +00:00

664 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using libsecondlife;
using libsecondlife.AssetSystem;
using libsecondlife.InventorySystem;
using libsecondlife.Packets;
using System.Text;
namespace libsecondlife.AssetSystem
{
public class AppearanceManager
{
public enum WearableType
{
Shape = 0,
Skin = 1,
Hair = 2,
Eyes = 3,
Shirt = 4,
Pants = 5,
Shoes = 6,
Socks = 7,
Jacket = 8,
Gloves = 9,
Undershirt = 10,
Underpants = 11,
Skirt = 12,
Count = 13,
Invalid = 255
};
protected SecondLife Client;
protected AssetManager AManager;
protected uint SerialNum = 1;
protected ManualResetEvent AgentWearablesSignal = new ManualResetEvent(false);
protected Dictionary<LLUUID, AssetWearable> WearableCache = new Dictionary<LLUUID, AssetWearable>();
protected List<LLUUID> WearableAssetQueue = new List<LLUUID>();
protected Mutex WearableCacheQueueMutex = new Mutex();
// This data defines all appearance info for an avatar
public AgentWearablesUpdatePacket.WearableDataBlock[] AgentWearablesData;
public SerializableDictionary<int, float> AgentAppearanceParams = new SerializableDictionary<int, float>();
public LLObject.TextureEntry AgentTextureEntry = new LLObject.TextureEntry("C228D1CF4B5D4BA884F4899A0796AA97"); // if this isn't valid, blame JH ;-)
public bool LogWearableAssetQueue = false;
/// <summary>
///
/// </summary>
/// <param name="client"></param>
public AppearanceManager(SecondLife client)
{
Client = client;
Client.Network.RegisterCallback(libsecondlife.Packets.PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesUpdateCallbackHandler));
AManager = client.Assets;
AManager.TransferRequestCompletedEvent += new AssetManager.On_TransferRequestCompleted(AManager_TransferRequestCompletedEvent);
}
#region Wear Stuff
/// <summary>
/// Add a single wearable to your outfit, replacing if nessesary.
/// </summary>
/// <param name="wearable"></param>
public void Wear(InventoryWearable wearable)
{
List<InventoryWearable> x = new List<InventoryWearable>();
x.Add(wearable);
Wear(x);
}
/// <summary>
/// Add the specified wearables to your outfit, replace existing ones if nessesary.
/// </summary>
/// <param name="wearables"></param>
public void Wear(List<InventoryWearable> wearables)
{
// Make sure we have some Wearable Data to start with.
if (AgentWearablesSignal.WaitOne(1000, false) == false)
{
Client.Log("You must have set appearance at least once, before calling Wear(). AgentWearablesSignal not set.", Helpers.LogLevel.Error);
return;
}
// Update with specified wearables
foreach (InventoryWearable iw in wearables)
{
byte type = (byte)((AssetWearable)iw.Asset).AppearanceLayer;
AgentWearablesData[type].ItemID = iw.ItemID;
AgentWearablesData[type].AssetID = iw.AssetID;
}
// Create AgentIsNowWearing Packet, and send it
SendAgentIsNowWearing();
// Update local Appearance Info
GetAvatarAppearanceInfoFromWearableAssets();
// Send updated AgentSetAppearance to the grid
BeginAgentSendAppearance();
}
/// <summary>
/// Equivalent to the SL "Replace Outfit" command. All clothing is removed, and replaced with wearables in given folder. Body wearables will be replaced if provided.
/// </summary>
/// <param name="outfitFolder">Contains the wearable items to put on.</param>
public void WearOutfit(InventoryFolder outfitFolder)
{
WearOutfit(outfitFolder, 10000, true);
}
/// <summary>
/// Equivalent to the SL "Replace Outfit" command. All clothing is removed, and replaced with wearables in given folder. Body wearables will be replaced if provided.
/// </summary>
/// <param name="outfitFolder">Contains the wearable items to put on.</param>
/// <param name="TimeOut">How long to wait for outfit directory information to download</param>
public void WearOutfit(InventoryFolder outfitFolder, int TimeOut, bool removeExistingAttachments)
{
// Refresh download of outfit folder
if (!outfitFolder.RequestDownloadContents(false, false, true).RequestComplete.WaitOne(TimeOut, false))
{
Client.Log("Outfit not changed. An error occured while downloads the folder contents of : " + outfitFolder.Name, Helpers.LogLevel.Error);
return;
}
// Make sure we have some Wearable Data to start with.
if (AgentWearablesSignal.WaitOne(1000, false) == false)
{
Client.Log("You must have set appearance at least once, before calling WearOutfit(). AgentWearablesSignal not set.", Helpers.LogLevel.Error);
return;
}
// Flush the cached clothing wearables so we can redefine them
for (byte i = 4; i <= 12; i++)
{
AgentWearablesData[i].ItemID = LLUUID.Zero;
AgentWearablesData[i].AssetID = LLUUID.Zero;
}
List<InventoryItem> attachments = new List<InventoryItem>();
// Replace with wearables from Outfit folder
foreach (InventoryBase ib in outfitFolder.GetContents())
{
if (ib is InventoryWearable)
{
try
{
InventoryWearable iw = (InventoryWearable)ib;
Client.Log("Retrieving asset for " + iw.Name + "("+iw.AssetID+")", Helpers.LogLevel.Info);
AssetWearable.AppearanceLayerType AppearanceLayer = ((AssetWearable)iw.Asset).AppearanceLayer;
Client.Log("Adding skin/clothing layer for " + AppearanceLayer, Helpers.LogLevel.Info);
AgentWearablesData[(byte)AppearanceLayer].ItemID = iw.ItemID;
AgentWearablesData[(byte)AppearanceLayer].AssetID = iw.AssetID;
}
catch (Exception e)
{
Client.Log("Asset for " + ib._Name + " unavailable: " + e.Message, Helpers.LogLevel.Error);
}
}
else if (ib is InventoryItem)
{
InventoryItem ii = (InventoryItem)ib;
attachments.Add(ii);
}
}
// Change attachments
AddAttachments(attachments, removeExistingAttachments);
// Create AgentIsNowWearing Packet, and send it
SendAgentIsNowWearing();
// Send updated AgentSetAppearance to the grid
SendAgentSetAppearance();
}
public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst)
{
// Use RezMultipleAttachmentsFromInv to clear out current attachments, and attach new ones
RezMultipleAttachmentsFromInvPacket attachmentsPacket = new RezMultipleAttachmentsFromInvPacket();
attachmentsPacket.AgentData.AgentID = Client.Network.AgentID;
attachmentsPacket.AgentData.SessionID = Client.Network.SessionID;
attachmentsPacket.HeaderData.CompoundMsgID = LLUUID.Random();
attachmentsPacket.HeaderData.FirstDetachAll = true;
attachmentsPacket.HeaderData.TotalObjects = (byte)attachments.Count;
attachmentsPacket.ObjectData = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock[attachments.Count];
for (int i = 0; i < attachments.Count; i++)
{
attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock();
attachmentsPacket.ObjectData[i].AttachmentPt = 0;
attachmentsPacket.ObjectData[i].EveryoneMask = attachments[i].EveryoneMask;
attachmentsPacket.ObjectData[i].GroupMask = attachments[i].GroupMask;
attachmentsPacket.ObjectData[i].ItemFlags = attachments[i].Flags;
attachmentsPacket.ObjectData[i].ItemID = attachments[i].ItemID;
attachmentsPacket.ObjectData[i].Name = Helpers.StringToField(attachments[i].Name);
attachmentsPacket.ObjectData[i].Description = Helpers.StringToField(attachments[i].Description);
attachmentsPacket.ObjectData[i].NextOwnerMask = attachments[i].NextOwnerMask;
attachmentsPacket.ObjectData[i].OwnerID = attachments[i].OwnerID;
}
Client.Network.SendPacket(attachmentsPacket);
}
#endregion
/// <summary>
/// Creates and sends an AgentIsNowWearing packet based on the local cached AgentWearablesData array.
/// </summary>
protected void SendAgentIsNowWearing()
{
AgentIsNowWearingPacket nowWearing = new AgentIsNowWearingPacket();
nowWearing.AgentData.AgentID = Client.Network.AgentID;
nowWearing.AgentData.SessionID = Client.Network.SessionID;
nowWearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[13];
for (byte i = 0; i <= 12; i++)
{
nowWearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
nowWearing.WearableData[i].WearableType = i;
nowWearing.WearableData[i].ItemID = AgentWearablesData[i].ItemID;
}
Client.Network.SendPacket(nowWearing);
}
/// <summary>
/// Update the local Avatar Appearance information based on the contents of the assets as defined in the cached wearable data info.
/// </summary>
protected void GetAvatarAppearanceInfoFromWearableAssets()
{
// Make sure we have some Wearable Data to start with.
if (AgentWearablesSignal.WaitOne(1000, false) == false)
{
Client.Log("Cannot get Visual Param data from wearable assets. AgentWearablesSignal not set.", Helpers.LogLevel.Error);
return;
}
// Clear current look
AgentTextureEntry = new LLObject.TextureEntry("C228D1CF4B5D4BA884F4899A0796AA97"); // if this isn't valid, blame JH ;-)
AgentAppearanceParams = new SerializableDictionary<int, float>();
// Build params and texture entries from wearable data
foreach (AgentWearablesUpdatePacket.WearableDataBlock wdb in AgentWearablesData)
{
if (wdb.ItemID == LLUUID.Zero)
{
continue;
}
AssetWearable wearableAsset;
switch (wdb.WearableType)
{
case 0:
case 1:
case 2:
case 3:
wearableAsset = new AssetWearable_Body(wdb.AssetID, null);
break;
default:
wearableAsset = new AssetWearable_Clothing(wdb.AssetID, null);
break;
}
AssetRequestDownload request = Client.Assets.RequestInventoryAsset(wearableAsset.AssetID, wearableAsset.Type);
if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success)
{
Client.Log("Asset (" + wearableAsset.AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ")", Helpers.LogLevel.Error);
}
else
{
wearableAsset.SetAssetData(request.GetAssetData());
}
if ((wearableAsset.AssetData == null) || (wearableAsset.AssetData.Length == 0))
{
Client.Log("Asset retrieval failed for AssetID: " + wearableAsset.AssetID, Helpers.LogLevel.Warning);
}
UpdateAgentTextureEntryAndAppearanceParams(wearableAsset);
}
UpdateAgentTextureEntryOrder();
}
/// <summary>
/// TextureEntry must have it's face textures in a specific order for avatars.
/// Should be called at least once before sending an AgentSetAppearance packet.
/// </summary>
protected void UpdateAgentTextureEntryOrder()
{
// Correct the order of the textures
foreach (uint faceid in AgentTextureEntry.FaceTextures.Keys)
{
if (faceid > 18)
{
Client.Log("Unknown order for FaceID: " + faceid + Environment.NewLine +
"Your wearables define a face that we don't know the order of. Please " +
"capture a AgentSetAppearance packet for your current outfit and submit to " +
"static.sprocket@gmail.com, thanks!", Helpers.LogLevel.Info);
break;
}
}
//Re-order texture faces to match Linden Labs internal data structure.
LLObject.TextureEntry te2 = new LLObject.TextureEntry(AgentTextureEntry.DefaultTexture.TextureID);
te2.CreateFace(18).TextureID = AgentTextureEntry.GetFace(18).TextureID;
te2.CreateFace(17).TextureID = AgentTextureEntry.GetFace(17).TextureID;
te2.CreateFace(16).TextureID = AgentTextureEntry.GetFace(16).TextureID;
te2.CreateFace(15).TextureID = AgentTextureEntry.GetFace(15).TextureID;
te2.CreateFace(14).TextureID = AgentTextureEntry.GetFace(14).TextureID;
te2.CreateFace(13).TextureID = AgentTextureEntry.GetFace(13).TextureID;
te2.CreateFace(12).TextureID = AgentTextureEntry.GetFace(12).TextureID;
// I wonder if shoes are somewhere in here?
te2.CreateFace(7).TextureID = AgentTextureEntry.GetFace(7).TextureID;
te2.CreateFace(6).TextureID = AgentTextureEntry.GetFace(6).TextureID;
te2.CreateFace(5).TextureID = AgentTextureEntry.GetFace(5).TextureID;
te2.CreateFace(4).TextureID = AgentTextureEntry.GetFace(4).TextureID;
te2.CreateFace(3).TextureID = AgentTextureEntry.GetFace(3).TextureID;
te2.CreateFace(2).TextureID = AgentTextureEntry.GetFace(2).TextureID;
te2.CreateFace(1).TextureID = AgentTextureEntry.GetFace(1).TextureID;
te2.CreateFace(0).TextureID = AgentTextureEntry.GetFace(0).TextureID;
AgentTextureEntry = te2;
}
/// <summary>
/// Updates the TextureEntry and Appearance Param structures with the data from an asset wearable.
/// Called once for each weable asset.
/// </summary>
/// <param name="wearableAsset"></param>
protected void UpdateAgentTextureEntryAndAppearanceParams(AssetWearable wearableAsset)
{
try
{
foreach (KeyValuePair<uint, LLUUID> texture in wearableAsset.Textures)
{
AgentTextureEntry.CreateFace(texture.Key).TextureID = texture.Value;
}
lock (AgentAppearanceParams)
{
foreach (KeyValuePair<int, float> kvp in wearableAsset.Parameters)
{
AgentAppearanceParams[kvp.Key] = kvp.Value;
}
}
}
catch (Exception e)
{
Client.Log(e.ToString() + Environment.NewLine + wearableAsset.AssetDataToString(), Helpers.LogLevel.Error);
}
}
/// <summary>
/// Non-blocking async request of wearables, construction and sending of AgentSetAppearance
/// </summary>
public void BeginAgentSendAppearance()
{
AgentWearablesSignal.Reset();
AgentWearablesRequestPacket p = new AgentWearablesRequestPacket();
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
Client.Network.SendPacket(p);
}
/// <summary>
/// Send an AgentSetAppearance packet to the server to update your appearance.
/// </summary>
protected void SendAgentSetAppearance()
{
// Get latest appearance info
GetAvatarAppearanceInfoFromWearableAssets();
AgentSetAppearancePacket p = new AgentSetAppearancePacket();
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
p.AgentData.SerialNum = SerialNum++;
// Add Texture Data
p.ObjectData.TextureEntry = AgentTextureEntry.ToBytes();
p.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[218];
string visualParamData = "";
int vpIndex = 0;
// Add Visual Params
lock (AgentAppearanceParams)
{
foreach (KeyValuePair<int,VisualParam> kvp in VisualParams.Params)
{
VisualParam param = kvp.Value;
p.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
visualParamData += vpIndex + "," + param.ParamID + ",";
if (AgentAppearanceParams.ContainsKey(param.ParamID))
{
p.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(AgentAppearanceParams[param.ParamID],
param.MinValue, param.MaxValue);
visualParamData += AgentAppearanceParams[param.ParamID] + "," + p.VisualParam[vpIndex].ParamValue + Environment.NewLine;
}
else
{
// Use the default value for this parameter
p.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(param.DefaultValue, param.MinValue,
param.MaxValue);
visualParamData += "NA," + p.VisualParam[vpIndex].ParamValue + Environment.NewLine;
}
vpIndex++;
}
}
// Add Size Data
p.AgentData.Size = GetAgentSizeFromVisualParam(Helpers.ByteToFloat(p.VisualParam[33].ParamValue,
VisualParams.Params[33].MinValue, VisualParams.Params[33].MaxValue));
Client.Network.SendPacket(p);
}
/// <summary>
/// Determine agent size for AgentSetAppearance based on Visual Param data.
/// </summary>
/// <param name="heightParam"></param>
/// <returns></returns>
protected LLVector3 GetAgentSizeFromVisualParam(float heightParam)
{
float AV_Height_Range = 2.025506f - 1.50856f;
float AV_Height = 1.50856f + ((heightParam / 255.0f) * AV_Height_Range);
return new LLVector3(0.45f, 0.6f, AV_Height);
//return new LLVector3(0.45f, 0.6f, 1.0f);
}
#region Callback Handlers
private void AgentWearablesUpdateCallbackHandler(Packet packet, Simulator simulator)
{
AgentWearablesUpdatePacket wearablesPacket = (AgentWearablesUpdatePacket)packet;
AgentWearablesData = wearablesPacket.WearableData;
AgentWearablesSignal.Set();
// Grab access mutex...
WearableCacheQueueMutex.WaitOne();
// Queue download of wearables
foreach (AgentWearablesUpdatePacket.WearableDataBlock wdb in AgentWearablesData)
{
// Don't try to download if AssetID is zero
if (wdb.AssetID == LLUUID.Zero)
{
continue;
}
// Don't try to download, if it's already cached.
if (WearableCache.ContainsKey(wdb.AssetID))
{
AssetWearable aw = WearableCache[wdb.AssetID];
if (aw._AssetData != null)
{
continue;
}
}
// Don't try to download, if it's already in the download queue
lock (WearableAssetQueue)
{
if (WearableAssetQueue.Contains(wdb.AssetID))
{
continue;
}
}
AssetWearable wearableAsset;
switch (wdb.WearableType)
{
case 0:
case 1:
case 2:
case 3:
wearableAsset = new AssetWearable_Body(wdb.AssetID, null);
break;
default:
wearableAsset = new AssetWearable_Clothing(wdb.AssetID, null);
break;
}
WearableCache[wdb.AssetID] = wearableAsset;
lock (WearableAssetQueue)
{
if (!WearableAssetQueue.Contains(wdb.AssetID))
{
WearableAssetQueue.Add(wdb.AssetID);
LogWearableAssetQueueActivity("Added wearable asset to download queue: " + wearableAsset.GetType().Name + " : " + wdb.AssetID);
}
}
}
RequestNextQueuedWearableAsset();
WearableCacheQueueMutex.ReleaseMutex();
}
/// <summary>
/// Sends a request for the next wearable asset.
/// </summary>
protected void RequestNextQueuedWearableAsset()
{
lock (WearableAssetQueue)
{
if (WearableAssetQueue.Count > 0)
{
AssetWearable wearableAsset = WearableCache[WearableAssetQueue[0]];
/*AssetRequestDownload request =*/Client.Assets.RequestInventoryAsset(wearableAsset.AssetID, wearableAsset.Type);
LogWearableAssetQueueActivity("Requesting: " + wearableAsset.AssetID);
}
else
{
if (AgentWearablesSignal.WaitOne(0, false) == true)
{
// Send updated AgentSetAppearance
SendAgentSetAppearance();
}
}
}
}
/// <summary>
/// use to debug wearable asset queue activity
/// </summary>
/// <param name="msg"></param>
protected void LogWearableAssetQueueActivity(string msg)
{
if (LogWearableAssetQueue)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("==================");
sb.AppendLine(msg);
sb.AppendLine("Current Queue:");
foreach (LLUUID uuid in WearableAssetQueue)
{
sb.AppendLine(" ** " + uuid.ToStringHyphenated());
}
Client.Log(sb.ToString(), Helpers.LogLevel.Info);
}
}
/// <summary>
/// Called each time a wearable asset is done downloading
/// </summary>
/// <param name="request"></param>
void AManager_TransferRequestCompletedEvent(AssetRequest request)
{
if( !(request is AssetRequestDownload) )
{
return;
}
AssetRequestDownload dlrequest = (AssetRequestDownload)request;
if (dlrequest.AssetID == null)
{
Client.Log("AssetID is null in AssetRequestDownload: " + dlrequest.StatusMsg, Helpers.LogLevel.Error);
}
WearableCacheQueueMutex.WaitOne();
// Remove from the download queue
lock (WearableAssetQueue)
{
if (!WearableAssetQueue.Contains(dlrequest.AssetID))
{
// Looks like we got an asset for something other then what we're waiting for, ignore it
WearableCacheQueueMutex.ReleaseMutex();
return;
}
}
// Since we got a response for this asset, remove it from the queue
WearableAssetQueue.Remove(dlrequest.AssetID);
LogWearableAssetQueueActivity("Received queued asset, and removed: " + dlrequest.AssetID);
// If the request wasn't successful, then don't try to process it.
if (request.Status != AssetRequest.RequestStatus.Success)
{
Client.Log("Error downloading wearable asset: " + dlrequest.AssetID, Helpers.LogLevel.Error);
WearableCacheQueueMutex.ReleaseMutex();
return;
}
AssetWearable wearableAsset = WearableCache[dlrequest.AssetID];
wearableAsset.SetAssetData(dlrequest.GetAssetData());
if ((wearableAsset.AssetData == null) || (wearableAsset.AssetData.Length == 0))
{
Client.Log("Asset retrieval failed for AssetID: " + wearableAsset.AssetID, Helpers.LogLevel.Error);
WearableCacheQueueMutex.ReleaseMutex();
return;
}
else
{
UpdateAgentTextureEntryAndAppearanceParams(wearableAsset);
UpdateAgentTextureEntryOrder();
lock(WearableAssetQueue)
{
if (WearableAssetQueue.Count > 0)
{
RequestNextQueuedWearableAsset();
WearableCacheQueueMutex.ReleaseMutex();
return;
}
}
// Now that all the wearable assets are done downloading,
// send an appearance packet
SendAgentSetAppearance();
WearableCacheQueueMutex.ReleaseMutex();
return;
}
}
#endregion
}
}