// Received a list of animations for this avatar. Check to see if animation list has
        //   changed and update the scene presence.
        // Doing any updates to the Animator causes events to be sent out so don't change willy nilly.
        // Changes to the animation set must be done through sp.Animator so the proper side
        //   effects happen and updates are sent out.
        private void UpdateAvatarAnimations(ScenePresence sp, OSDArray pPackedAnimations)
        {
            AnimationSet newSet = new AnimationSet(pPackedAnimations);
            AnimationSet currentSet = sp.Animator.Animations;
            if (!newSet.Equals(currentSet))
            {
                // DebugLog.DebugFormat("{0} UpdateAvatarAnimations. spID={1},CurrAnims={2},NewAnims={3}",
                //                          LogHeader, sp.LocalId, currentSet, newSet); // DEBUG DEBUG

                // If something changed, stuff the new values in the existing animation collection.
                sp.Animator.Animations.FromOSDArray(pPackedAnimations);
            }
            // Doesn't matter if it changed or not. If someone sends us an animation update, tell any connected client.
            sp.Animator.SendAnimPack();
        }
        /// <summary>
        /// Compare the value (not "reference") of the given property. 
        /// Assumption: the caller has already checked if PhysActor exists
        /// if there are physics properties updated.
        /// If the value maintained here is different from that in SP data,
        /// synchronize the two: 
        /// (1) if the cached value has a timestamp newer than lastUpdateByLocalTS 
        /// overwrite the SP's property with the cached value (effectively 
        /// undoing the local write operation that just happened). 
        /// (2) otherwise, copy SP's data and update timestamp and syncID 
        /// as indicated by "lastUpdateByLocalTS" and "syncID".
        /// </summary>
        /// <param name="sp"></param>
        /// <param name="property"></param>
        /// <param name="lastUpdateByLocalTS"></param>
        /// <param name="syncID"></param>
        /// <returns>Return true if the property's value maintained in this 
        /// RegionSyncModule is replaced by SP's data.</returns>
        private bool CompareValue_UpdateByLocal(ScenePresence sp, SyncableProperties.Type property, long lastUpdateByLocalTS, string syncID)
        {
            //DebugLog.WarnFormat("[SYNC INFO PRESENCE] CompareValue_UpdateByLocal: Updating property {0} on sp {1}", property.ToString(), sp.UUID);
            // Check to see if this property is in the sync cache for this object.
            // If not, add it and initialize value to value in ScenePresence.
            bool ret = false;

            SyncedProperty syncedProperty;
            CurrentlySyncedProperties.TryGetValue(property, out syncedProperty);

            // If synced property is not in cache, add it now
            if (syncedProperty == null)
            {
                Object initValue = GetPropertyValue(sp, property);
                if (initValue != null)
                {
                    CurrentlySyncedProperties.Add(property, new SyncedProperty(property, initValue, lastUpdateByLocalTS, syncID));
                    ret = true;
                }
                return ret;
            }

            // First, check if the value maintained here is different from that in SP's.
            // If different, next check if the timestamp in SyncInfo is newer than lastUpdateByLocalTS;
            // if so (although ideally should not happen, but due to things likc clock not so perfectly
            // sync'ed, it might happen), overwrite SP's value with what's maintained
            // in SyncInfo; otherwise, copy SP's data to SyncInfo.
            Object value = GetPropertyValue(sp, property);

            // If both null, no update needed
            if (syncedProperty.LastUpdateValue == null && value == null)
                return false;

            switch (property)
            {
                default:
                    // If one is null and the other is not, or if the references are different, the property was changed.
                    // This will perform a value comparison for strings in C#. We could use String.Clone instead for string properties.
                    if ((value == null && syncedProperty.LastUpdateValue != null) ||
                        (value != null && syncedProperty.LastUpdateValue == null) ||
                        (!value.Equals(syncedProperty.LastUpdateValue)))
                    {
                        if (value != null)
                        {
                            // Some values, even if they are not 'equal', might be close enough to be equal.
                            // Note that the 'Equals()' above will most always return 'false' for lists and OSDMaps
                            //     since they are probably not the same object.
                            // Returning a 'false' here means the values don't need any updating (they are equal enough).
                            switch (property)
                            {
                                case SyncableProperties.Type.AvatarAppearance:
                                    String stringValue = OSDParser.SerializeJsonString((OSDMap)value);
                                    String lastStringValue = OSDParser.SerializeJsonString((OSDMap)syncedProperty.LastUpdateValue);
                                    if (stringValue == lastStringValue)
                                        return false;
                                    break;
                                case SyncableProperties.Type.Animations:
                                    if (syncedProperty.LastUpdateValue != null)
                                    {
                                        AnimationSet lastAnimations = new AnimationSet((OSDArray)syncedProperty.LastUpdateValue);

                                        // Get the home region for this presence (the client manager the presence is connected to).
                                        string cachedRealRegionName = (string)(CurrentlySyncedProperties[SyncableProperties.Type.RealRegion].LastUpdateValue);
                                        if (cachedRealRegionName != Scene.Name && sp.Animator.Animations.ToArray().Length == 0)
                                        {
                                            // If this is not the originating region for this presence and there is no additional
                                            //   animations being added, this simulator does not change the animation.
                                            // THIS IS A HORRIBLE KLUDGE. FIGURE OUT THE REAL SOLUTION!!
                                            // The problem is that animations are changed by every simulator (setting default
                                            //   sit and stand when parentID changes) and the updates conflict/override the real
                                            //   settings (like a scripted sit animation).
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Not home sim or no anim change. spID={1}, homeSim={2}, thisSim={3}, anims={4}",
                                            //                     LogHeader, sp.LocalId, cachedRealRegionName, Scene.Name, sp.Animator.Animations.ToArray().Length); // DEBUG DEBUG

                                            return false;
                                        }

                                        if (lastAnimations.Equals(sp.Animator.Animations))
                                        {
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Equal anims. spID={1}, sp.Anim={2}, lastAnim={3}",
                                            //                     LogHeader, sp.LocalId, sp.Animator.Animations, lastAnimations); // DEBUG DEBUG
                                            return false;
                                        }
                                        else
                                        {
                                            // If locally storing a new value of the animation, don't check for the time.
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Not equal anims. spID={1}, sp.Anim={2}, lastAnim={3}",
                                            //                     LogHeader, sp.LocalId, sp.Animator.Animations, lastAnimations); // DEBUG DEBUG
                                            syncedProperty.UpdateSyncInfoByLocal(lastUpdateByLocalTS, syncID, value);
                                            return true;
                                        }
                                    }
                                    break;
                                case SyncableProperties.Type.Velocity:
                                case SyncableProperties.Type.PA_Velocity:
                                case SyncableProperties.Type.PA_TargetVelocity:
                                case SyncableProperties.Type.RotationalVelocity:
                                case SyncableProperties.Type.AngularVelocity:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        // If velocity difference is small but not zero, don't update
                                        if (partVal.ApproxEquals(lastVal, VELOCITY_TOLERANCE) && !partVal.Equals(Vector3.Zero))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.RotationOffset:
                                case SyncableProperties.Type.Orientation:
                                    {
                                        Quaternion partVal = (Quaternion)value;
                                        Quaternion lastVal = (Quaternion)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, ROTATION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.OffsetPosition:
                                case SyncableProperties.Type.AbsolutePosition:
                                case SyncableProperties.Type.Position:
                                case SyncableProperties.Type.GroupPosition:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, POSITION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                            }
                        }

                        // If we get here, the values are not equal and we need to update the cached value if the
                        //     new value is timestamp newer.
                        if (lastUpdateByLocalTS >= syncedProperty.LastUpdateTimeStamp)
                        {
                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal (property={1}): TS >= lastTS (updating SyncInfo)", LogHeader, property);
                            syncedProperty.UpdateSyncInfoByLocal(lastUpdateByLocalTS, syncID, value);
                            return true;
                        }
                    }
                    break;
            }
            return false;
        }