Files
opensim/OpenSim/Region/Framework/Scenes/Animation/ScenePresenceAnimator.cs
Robert Adams e5367d822b Merge of ubitworkvarnew with opensim/master as of 20150905.
This integrates the OpenSim refactoring to make physics, etc into modules.
AVN physics hasn't been moved to new location.
Does not compile yet.

Merge branch 'osmaster' into mbworknew1
2015-09-08 04:54:16 -07:00

737 lines
29 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using log4net;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.PhysicsModules.SharedBase;
namespace OpenSim.Region.Framework.Scenes.Animation
{
/// <summary>
/// Handle all animation duties for a scene presence
/// </summary>
public class ScenePresenceAnimator
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public AnimationSet Animations
{
get { return m_animations; }
}
protected AnimationSet m_animations = new AnimationSet();
/// <value>
/// The current movement animation
/// </value>
public string CurrentMovementAnimation { get; private set; }
private int m_animTickFall;
private int m_animTickLand;
private int m_animTickJump;
public bool m_jumping = false;
// private int m_landing = 0;
/// <summary>
/// Is the avatar falling?
/// </summary>
public bool Falling { get; private set; }
private float m_lastFallVelocity;
/// <value>
/// The scene presence that this animator applies to
/// </value>
protected ScenePresence m_scenePresence;
public ScenePresenceAnimator(ScenePresence sp)
{
m_scenePresence = sp;
CurrentMovementAnimation = "CROUCH";
}
public void AddAnimation(UUID animID, UUID objectID)
{
if (m_scenePresence.IsChildAgent)
return;
// m_log.DebugFormat("[SCENE PRESENCE ANIMATOR]: Adding animation {0} for {1}", animID, m_scenePresence.Name);
if (m_scenePresence.Scene.DebugAnimations)
m_log.DebugFormat(
"[SCENE PRESENCE ANIMATOR]: Adding animation {0} {1} for {2}",
GetAnimName(animID), animID, m_scenePresence.Name);
if (m_animations.Add(animID, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, objectID))
{
SendAnimPack();
m_scenePresence.TriggerScenePresenceUpdated();
}
}
// Called from scripts
public void AddAnimation(string name, UUID objectID)
{
if (m_scenePresence.IsChildAgent)
return;
// XXX: For some reason, we store all animations and use them with upper case names, but in LSL animations
// are referenced with lower case names!
UUID animID = DefaultAvatarAnimations.GetDefaultAnimation(name.ToUpper());
if (animID == UUID.Zero)
return;
// m_log.DebugFormat("[SCENE PRESENCE ANIMATOR]: Adding animation {0} {1} for {2}", animID, name, m_scenePresence.Name);
AddAnimation(animID, objectID);
}
/// <summary>
/// Remove the specified animation
/// </summary>
/// <param name='animID'></param>
/// <param name='allowNoDefault'>
/// If true, then the default animation can be entirely removed.
/// If false, then removing the default animation will reset it to the simulator default (currently STAND).
/// </param>
public void RemoveAnimation(UUID animID, bool allowNoDefault)
{
if (m_scenePresence.IsChildAgent)
return;
if (m_scenePresence.Scene.DebugAnimations)
m_log.DebugFormat(
"[SCENE PRESENCE ANIMATOR]: Removing animation {0} {1} for {2}",
GetAnimName(animID), animID, m_scenePresence.Name);
if (m_animations.Remove(animID, allowNoDefault))
{
SendAnimPack();
m_scenePresence.TriggerScenePresenceUpdated();
}
}
public void avnChangeAnim(UUID animID, bool addRemove, bool sendPack)
{
if (m_scenePresence.IsChildAgent)
return;
if (animID != UUID.Zero)
{
if (addRemove)
m_animations.Add(animID, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, UUID.Zero);
else
m_animations.Remove(animID, false);
}
if (sendPack)
SendAnimPack();
}
// Called from scripts
public void RemoveAnimation(string name)
{
if (m_scenePresence.IsChildAgent)
return;
// XXX: For some reason, we store all animations and use them with upper case names, but in LSL animations
// are referenced with lower case names!
UUID animID = DefaultAvatarAnimations.GetDefaultAnimation(name.ToUpper());
if (animID == UUID.Zero)
return;
RemoveAnimation(animID, true);
}
public void ResetAnimations()
{
if (m_scenePresence.Scene.DebugAnimations)
m_log.DebugFormat(
"[SCENE PRESENCE ANIMATOR]: Resetting animations for {0} in {1}",
m_scenePresence.Name, m_scenePresence.Scene.RegionInfo.RegionName);
m_animations.Clear();
}
UUID aoSitGndAnim = UUID.Zero;
/// <summary>
/// The movement animation is reserved for "main" animations
/// that are mutually exclusive, e.g. flying and sitting.
/// </summary>
/// <returns>'true' if the animation was updated</returns>
///
public bool TrySetMovementAnimation(string anim)
{
bool ret = false;
if (!m_scenePresence.IsChildAgent)
{
// m_log.DebugFormat(
// "[SCENE PRESENCE ANIMATOR]: Setting movement animation {0} for {1}",
// anim, m_scenePresence.Name);
if (aoSitGndAnim != UUID.Zero)
{
avnChangeAnim(aoSitGndAnim, false, true);
aoSitGndAnim = UUID.Zero;
}
UUID overridenAnim = m_scenePresence.Overrides.GetOverriddenAnimation(anim);
if (overridenAnim != UUID.Zero)
{
if (anim == "SITGROUND")
{
UUID defsit = DefaultAvatarAnimations.AnimsUUID["SIT_GROUND_CONSTRAINED"];
if (defsit == UUID.Zero)
return false;
m_animations.SetDefaultAnimation(defsit, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, m_scenePresence.UUID);
aoSitGndAnim = overridenAnim;
avnChangeAnim(overridenAnim, true, false);
}
else
{
m_animations.SetDefaultAnimation(overridenAnim, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, m_scenePresence.UUID);
}
m_scenePresence.SendScriptEventToAttachments("changed", new Object[] { (int)Changed.ANIMATION });
SendAnimPack();
ret = true;
}
else
{
// translate sit and sitground state animations
if (anim == "SIT" || anim == "SITGROUND")
anim = m_scenePresence.sitAnimation;
if (m_animations.TrySetDefaultAnimation(
anim, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, m_scenePresence.UUID))
{
// m_log.DebugFormat(
// "[SCENE PRESENCE ANIMATOR]: Updating movement animation to {0} for {1}",
// anim, m_scenePresence.Name);
// 16384 is CHANGED_ANIMATION
m_scenePresence.SendScriptEventToAttachments("changed", new Object[] { (int)Changed.ANIMATION });
SendAnimPack();
ret = true;
}
}
}
else
{
m_log.WarnFormat(
"[SCENE PRESENCE ANIMATOR]: Tried to set movement animation {0} on child presence {1}",
anim, m_scenePresence.Name);
}
return ret;
}
public enum motionControlStates : byte
{
sitted = 0,
flying,
falling,
jumping,
landing,
onsurface
}
public motionControlStates currentControlState = motionControlStates.onsurface;
/// <summary>
/// This method determines the proper movement related animation
/// </summary>
private string DetermineMovementAnimation()
{
const int FALL_DELAY = 800;
const int PREJUMP_DELAY = 200;
const int JUMP_PERIOD = 800;
#region Inputs
if (m_scenePresence.IsInTransit)
return CurrentMovementAnimation;
if (m_scenePresence.SitGround)
{
currentControlState = motionControlStates.sitted;
return "SITGROUND";
}
if (m_scenePresence.ParentID != 0 || m_scenePresence.ParentUUID != UUID.Zero)
{
currentControlState = motionControlStates.sitted;
return "SIT";
}
AgentManager.ControlFlags controlFlags = (AgentManager.ControlFlags)m_scenePresence.AgentControlFlags;
PhysicsActor actor = m_scenePresence.PhysicsActor;
const AgentManager.ControlFlags ANYXYMASK = (
AgentManager.ControlFlags.AGENT_CONTROL_AT_POS | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_POS |
AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG |
AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS |
AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG
);
// Check control flags
/* not in use
bool heldForward = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_AT_POS | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_POS)) != 0);
bool heldBack = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG)) != 0);
bool heldLeft = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS)) != 0);
bool heldRight = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG)) != 0);
*/
bool heldTurnLeft = (controlFlags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT) == AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT;
bool heldTurnRight = (controlFlags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT) == AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT;
// bool heldUp = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_UP_POS | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_POS)) != 0);
// excluded nudge up so it doesn't trigger jump state
bool heldUp = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_UP_POS)) != 0);
bool heldDown = ((controlFlags & (AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG | AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG)) != 0);
//bool flying = (controlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) == AgentManager.ControlFlags.AGENT_CONTROL_FLY;
//bool mouselook = (controlFlags & AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) == AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK;
bool heldOnXY = ((controlFlags & ANYXYMASK) != 0);
if (heldOnXY || heldUp || heldDown)
{
heldTurnLeft = false;
heldTurnRight = false;
}
#endregion Inputs
// no physics actor case
if (actor == null)
{
// well what to do?
currentControlState = motionControlStates.onsurface;
if (heldOnXY)
return "WALK";
return "STAND";
}
#region Flying
bool isColliding = actor.IsColliding;
if (actor.Flying)
{
m_animTickFall = 0;
m_animTickJump = 0;
m_jumping = false;
Falling = false;
currentControlState = motionControlStates.flying;
if (heldOnXY)
{
return (m_scenePresence.Scene.m_useFlySlow ? "FLYSLOW" : "FLY");
}
else if (heldUp)
{
return "HOVER_UP";
}
else if (heldDown)
{
if (isColliding)
{
actor.Flying = false;
currentControlState = motionControlStates.landing;
m_animTickLand = Environment.TickCount;
return "LAND";
}
else
return "HOVER_DOWN";
}
else
{
return "HOVER";
}
}
else
{
if (isColliding && currentControlState == motionControlStates.flying)
{
currentControlState = motionControlStates.landing;
m_animTickLand = Environment.TickCount;
return "LAND";
}
}
#endregion Flying
#region Falling/Floating/Landing
if (!isColliding && currentControlState != motionControlStates.jumping)
{
float fallVelocity = actor.Velocity.Z;
if (fallVelocity < -2.5f)
Falling = true;
if (m_animTickFall == 0 || (fallVelocity >= -0.5f))
{
m_animTickFall = Environment.TickCount;
}
else
{
int fallElapsed = (Environment.TickCount - m_animTickFall);
if ((fallElapsed > FALL_DELAY) && (fallVelocity < -3.0f))
{
currentControlState = motionControlStates.falling;
m_lastFallVelocity = fallVelocity;
// Falling long enough to trigger the animation
return "FALLDOWN";
}
}
// Check if the user has stopped walking just now
if (CurrentMovementAnimation == "WALK" && !heldOnXY && !heldDown && !heldUp)
return "STAND";
return CurrentMovementAnimation;
}
m_animTickFall = 0;
#endregion Falling/Floating/Landing
#region Jumping // section added for jumping...
if (isColliding && heldUp && currentControlState != motionControlStates.jumping)
{
// Start jumping, prejump
currentControlState = motionControlStates.jumping;
m_jumping = true;
Falling = false;
m_animTickJump = Environment.TickCount;
return "PREJUMP";
}
if (currentControlState == motionControlStates.jumping)
{
int jumptime = Environment.TickCount - m_animTickJump;
if ((jumptime > (JUMP_PERIOD * 1.5f)) && actor.IsColliding)
{
// end jumping
m_jumping = false;
Falling = false;
actor.Selected = false; // borrowed for jumping flag
m_animTickLand = Environment.TickCount;
currentControlState = motionControlStates.landing;
return "LAND";
}
else if (jumptime > JUMP_PERIOD)
{
// jump down
return "JUMP";
}
else if (jumptime > PREJUMP_DELAY)
{
// jump up
m_jumping = true;
return "JUMP";
}
return CurrentMovementAnimation;
}
#endregion Jumping
#region Ground Movement
if (currentControlState == motionControlStates.falling)
{
Falling = false;
currentControlState = motionControlStates.landing;
m_animTickLand = Environment.TickCount;
// TODO: SOFT_LAND support
float fallVsq = m_lastFallVelocity * m_lastFallVelocity;
if (fallVsq > 300f) // aprox 20*h
return "STANDUP";
else if (fallVsq > 160f)
return "SOFT_LAND";
else
return "LAND";
}
if (currentControlState == motionControlStates.landing)
{
Falling = false;
int landElapsed = Environment.TickCount - m_animTickLand;
int limit = 1000;
if (CurrentMovementAnimation == "LAND")
limit = 350;
// NB if the above is set too long a weird anim reset from some place prevents STAND from being sent to client
if ((m_animTickLand != 0) && (landElapsed <= limit))
{
return CurrentMovementAnimation;
}
else
{
currentControlState = motionControlStates.onsurface;
m_animTickLand = 0;
return "STAND";
}
}
// next section moved outside paren. and realigned for jumping
if (heldOnXY)
{
currentControlState = motionControlStates.onsurface;
Falling = false;
// Walking / crouchwalking / running
if (heldDown)
{
return "CROUCHWALK";
}
// We need to prevent these animations if the user tries to make their avatar walk or run whilst
// specifying AGENT_CONTROL_STOP (pressing down space on viewers).
else if (!m_scenePresence.AgentControlStopActive)
{
if (m_scenePresence.SetAlwaysRun)
return "RUN";
else
return "WALK";
}
}
else
{
currentControlState = motionControlStates.onsurface;
Falling = false;
// Not walking
if (heldDown)
return "CROUCH";
else if (heldTurnLeft)
return "TURNLEFT";
else if (heldTurnRight)
return "TURNRIGHT";
else
return "STAND";
}
#endregion Ground Movement
return CurrentMovementAnimation;
}
/// <summary>
/// Update the movement animation of this avatar according to its current state
/// </summary>
/// <returns>'true' if the animation was changed</returns>
public bool UpdateMovementAnimations()
{
// m_log.DebugFormat("[SCENE PRESENCE ANIMATOR]: Updating movement animations for {0}", m_scenePresence.Name);
bool ret = false;
lock (m_animations)
{
string newMovementAnimation = DetermineMovementAnimation();
if (CurrentMovementAnimation != newMovementAnimation)
{
CurrentMovementAnimation = newMovementAnimation;
// m_log.DebugFormat(
// "[SCENE PRESENCE ANIMATOR]: Determined animation {0} for {1} in UpdateMovementAnimations()",
// CurrentMovementAnimation, m_scenePresence.Name);
// Only set it if it's actually changed, give a script
// a chance to stop a default animation
ret = TrySetMovementAnimation(CurrentMovementAnimation);
}
}
return ret;
}
public bool ForceUpdateMovementAnimations()
{
lock (m_animations)
{
CurrentMovementAnimation = DetermineMovementAnimation();
return TrySetMovementAnimation(CurrentMovementAnimation);
}
}
public bool SetMovementAnimations(string motionState)
{
lock (m_animations)
{
CurrentMovementAnimation = motionState;
return TrySetMovementAnimation(CurrentMovementAnimation);
}
}
public UUID[] GetAnimationArray()
{
UUID[] animIDs;
int[] sequenceNums;
UUID[] objectIDs;
m_animations.GetArrays(out animIDs, out sequenceNums, out objectIDs);
return animIDs;
}
public BinBVHAnimation GenerateRandomAnimation()
{
int rnditerations = 3;
BinBVHAnimation anim = new BinBVHAnimation();
List<string> parts = new List<string>();
parts.Add("mPelvis"); parts.Add("mHead"); parts.Add("mTorso");
parts.Add("mHipLeft"); parts.Add("mHipRight"); parts.Add("mHipLeft"); parts.Add("mKneeLeft");
parts.Add("mKneeRight"); parts.Add("mCollarLeft"); parts.Add("mCollarRight"); parts.Add("mNeck");
parts.Add("mElbowLeft"); parts.Add("mElbowRight"); parts.Add("mWristLeft"); parts.Add("mWristRight");
parts.Add("mShoulderLeft"); parts.Add("mShoulderRight"); parts.Add("mAnkleLeft"); parts.Add("mAnkleRight");
parts.Add("mEyeRight"); parts.Add("mChest"); parts.Add("mToeLeft"); parts.Add("mToeRight");
parts.Add("mFootLeft"); parts.Add("mFootRight"); parts.Add("mEyeLeft");
anim.HandPose = 1;
anim.InPoint = 0;
anim.OutPoint = (rnditerations * .10f);
anim.Priority = 7;
anim.Loop = false;
anim.Length = (rnditerations * .10f);
anim.ExpressionName = "afraid";
anim.EaseInTime = 0;
anim.EaseOutTime = 0;
string[] strjoints = parts.ToArray();
anim.Joints = new binBVHJoint[strjoints.Length];
for (int j = 0; j < strjoints.Length; j++)
{
anim.Joints[j] = new binBVHJoint();
anim.Joints[j].Name = strjoints[j];
anim.Joints[j].Priority = 7;
anim.Joints[j].positionkeys = new binBVHJointKey[rnditerations];
anim.Joints[j].rotationkeys = new binBVHJointKey[rnditerations];
Random rnd = new Random();
for (int i = 0; i < rnditerations; i++)
{
anim.Joints[j].rotationkeys[i] = new binBVHJointKey();
anim.Joints[j].rotationkeys[i].time = (i * .10f);
anim.Joints[j].rotationkeys[i].key_element.X = ((float)rnd.NextDouble() * 2 - 1);
anim.Joints[j].rotationkeys[i].key_element.Y = ((float)rnd.NextDouble() * 2 - 1);
anim.Joints[j].rotationkeys[i].key_element.Z = ((float)rnd.NextDouble() * 2 - 1);
anim.Joints[j].positionkeys[i] = new binBVHJointKey();
anim.Joints[j].positionkeys[i].time = (i * .10f);
anim.Joints[j].positionkeys[i].key_element.X = 0;
anim.Joints[j].positionkeys[i].key_element.Y = 0;
anim.Joints[j].positionkeys[i].key_element.Z = 0;
}
}
AssetBase Animasset = new AssetBase(UUID.Random(), "Random Animation", (sbyte)AssetType.Animation, m_scenePresence.UUID.ToString());
Animasset.Data = anim.ToBytes();
Animasset.Temporary = true;
Animasset.Local = true;
Animasset.Description = "dance";
//BinBVHAnimation bbvhanim = new BinBVHAnimation(Animasset.Data);
m_scenePresence.Scene.AssetService.Store(Animasset);
AddAnimation(Animasset.FullID, m_scenePresence.UUID);
return anim;
}
/// <summary>
///
/// </summary>
/// <param name="animations"></param>
/// <param name="seqs"></param>
/// <param name="objectIDs"></param>
public void SendAnimPack(UUID[] animations, int[] seqs, UUID[] objectIDs)
{
m_scenePresence.SendAnimPack(animations, seqs, objectIDs);
}
public void GetArrays(out UUID[] animIDs, out int[] sequenceNums, out UUID[] objectIDs)
{
animIDs = null;
sequenceNums = null;
objectIDs = null;
if (m_animations != null)
m_animations.GetArrays(out animIDs, out sequenceNums, out objectIDs);
}
public void SendAnimPackToClient(IClientAPI client)
{
if (m_scenePresence.IsChildAgent)
return;
UUID[] animIDs;
int[] sequenceNums;
UUID[] objectIDs;
m_animations.GetArrays(out animIDs, out sequenceNums, out objectIDs);
client.SendAnimations(animIDs, sequenceNums, m_scenePresence.ControllingClient.AgentId, objectIDs);
}
/// <summary>
/// Send animation information about this avatar to all clients.
/// </summary>
public void SendAnimPack()
{
//m_log.Debug("Sending animation pack to all");
if (m_scenePresence.IsChildAgent)
return;
UUID[] animIDs;
int[] sequenceNums;
UUID[] objectIDs;
m_animations.GetArrays(out animIDs, out sequenceNums, out objectIDs);
// SendAnimPack(animIDs, sequenceNums, objectIDs);
m_scenePresence.SendAnimPack(animIDs, sequenceNums, objectIDs);
}
public string GetAnimName(UUID animId)
{
string animName;
if (!DefaultAvatarAnimations.AnimsNames.TryGetValue(animId, out animName))
{
AssetMetadata amd = m_scenePresence.Scene.AssetService.GetMetadata(animId.ToString());
if (amd != null)
animName = amd.Name;
else
animName = "Unknown";
}
return animName;
}
}
}