diff --git a/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs b/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs new file mode 100644 index 0000000000..d773ee7f28 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs @@ -0,0 +1,766 @@ +// Proprietary code of Avination Virtual Limited +// (c) 2012 Melanie Thielker +// + +using System; +using System.Timers; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Physics.Manager; +using OpenSim.Region.Framework.Scenes.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization; +using Timer = System.Timers.Timer; +using log4net; + +namespace OpenSim.Region.Framework.Scenes +{ + public class KeyframeTimer + { + private static Dictionarym_timers = + new Dictionary(); + + private Timer m_timer; + private Dictionary m_motions = new Dictionary(); + private object m_lockObject = new object(); + private object m_timerLock = new object(); + private const double m_tickDuration = 50.0; + private Scene m_scene; + + public double TickDuration + { + get { return m_tickDuration; } + } + + public KeyframeTimer(Scene scene) + { + m_timer = new Timer(); + m_timer.Interval = TickDuration; + m_timer.AutoReset = true; + m_timer.Elapsed += OnTimer; + + m_scene = scene; + + m_timer.Start(); + } + + private void OnTimer(object sender, ElapsedEventArgs ea) + { + if (!Monitor.TryEnter(m_timerLock)) + return; + + try + { + List motions; + + lock (m_lockObject) + { + motions = new List(m_motions.Keys); + } + + foreach (KeyframeMotion m in motions) + { + try + { + m.OnTimer(TickDuration); + } + catch (Exception inner) + { + // Don't stop processing + } + } + } + catch (Exception e) + { + // Keep running no matter what + } + finally + { + Monitor.Exit(m_timerLock); + } + } + + public static void Add(KeyframeMotion motion) + { + KeyframeTimer timer; + + if (motion.Scene == null) + return; + + lock (m_timers) + { + if (!m_timers.TryGetValue(motion.Scene, out timer)) + { + timer = new KeyframeTimer(motion.Scene); + m_timers[motion.Scene] = timer; + } + } + + lock (timer.m_lockObject) + { + timer.m_motions[motion] = null; + } + } + + public static void Remove(KeyframeMotion motion) + { + KeyframeTimer timer; + + if (motion.Scene == null) + return; + + lock (m_timers) + { + if (!m_timers.TryGetValue(motion.Scene, out timer)) + { + return; + } + } + + lock (timer.m_lockObject) + { + timer.m_motions.Remove(motion); + } + } + } + + [Serializable] + public class KeyframeMotion + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public enum PlayMode : int + { + Forward = 0, + Reverse = 1, + Loop = 2, + PingPong = 3 + }; + + [Flags] + public enum DataFormat : int + { + Translation = 2, + Rotation = 1 + } + + [Serializable] + public struct Keyframe + { + public Vector3? Position; + public Quaternion? Rotation; + public Quaternion StartRotation; + public int TimeMS; + public int TimeTotal; + public Vector3 AngularVelocity; + }; + + private Vector3 m_serializedPosition; + private Vector3 m_basePosition; + private Quaternion m_baseRotation; + + private Keyframe m_currentFrame; + + private List m_frames = new List(); + + private Keyframe[] m_keyframes; + + // skip timer events. + //timer.stop doesn't assure there aren't event threads still being fired + [NonSerialized()] + private bool m_timerStopped; + + [NonSerialized()] + private bool m_isCrossing; + + [NonSerialized()] + private bool m_waitingCrossing; + + // retry position for cross fail + [NonSerialized()] + private Vector3 m_nextPosition; + + [NonSerialized()] + private SceneObjectGroup m_group; + + private PlayMode m_mode = PlayMode.Forward; + private DataFormat m_data = DataFormat.Translation | DataFormat.Rotation; + + private bool m_running = false; + + [NonSerialized()] + private bool m_selected = false; + + private int m_iterations = 0; + + private int m_skipLoops = 0; + + [NonSerialized()] + private Scene m_scene; + + public Scene Scene + { + get { return m_scene; } + } + + public DataFormat Data + { + get { return m_data; } + } + + public bool Selected + { + set + { + if (m_group != null) + { + if (!value) + { + // Once we're let go, recompute positions + if (m_selected) + UpdateSceneObject(m_group); + } + else + { + // Save selection position in case we get moved + if (!m_selected) + { + StopTimer(); + m_serializedPosition = m_group.AbsolutePosition; + } + } + } + m_isCrossing = false; + m_waitingCrossing = false; + m_selected = value; + } + } + + private void StartTimer() + { + KeyframeTimer.Add(this); + m_timerStopped = false; + } + + private void StopTimer() + { + m_timerStopped = true; + KeyframeTimer.Remove(this); + } + + public static KeyframeMotion FromData(SceneObjectGroup grp, Byte[] data) + { + KeyframeMotion newMotion = null; + + try + { + MemoryStream ms = new MemoryStream(data); + BinaryFormatter fmt = new BinaryFormatter(); + + newMotion = (KeyframeMotion)fmt.Deserialize(ms); + + newMotion.m_group = grp; + + if (grp != null) + { + newMotion.m_scene = grp.Scene; + if (grp.IsSelected) + newMotion.m_selected = true; + } + + newMotion.m_timerStopped = false; + newMotion.m_running = true; + newMotion.m_isCrossing = false; + newMotion.m_waitingCrossing = false; + } + catch + { + newMotion = null; + } + + return newMotion; + } + + public void UpdateSceneObject(SceneObjectGroup grp) + { + m_isCrossing = false; + m_waitingCrossing = false; + StopTimer(); + + if (grp == null) + return; + + m_group = grp; + m_scene = grp.Scene; + + Vector3 grppos = grp.AbsolutePosition; + Vector3 offset = grppos - m_serializedPosition; + // avoid doing it more than once + // current this will happen draging a prim to other region + m_serializedPosition = grppos; + + m_basePosition += offset; + m_currentFrame.Position += offset; + + m_nextPosition += offset; + + for (int i = 0; i < m_frames.Count; i++) + { + Keyframe k = m_frames[i]; + k.Position += offset; + m_frames[i]=k; + } + + if (m_running) + Start(); + } + + public KeyframeMotion(SceneObjectGroup grp, PlayMode mode, DataFormat data) + { + m_mode = mode; + m_data = data; + + m_group = grp; + if (grp != null) + { + m_basePosition = grp.AbsolutePosition; + m_baseRotation = grp.GroupRotation; + m_scene = grp.Scene; + } + + m_timerStopped = true; + m_isCrossing = false; + m_waitingCrossing = false; + } + + public void SetKeyframes(Keyframe[] frames) + { + m_keyframes = frames; + } + + public KeyframeMotion Copy(SceneObjectGroup newgrp) + { + StopTimer(); + + KeyframeMotion newmotion = new KeyframeMotion(null, m_mode, m_data); + + newmotion.m_group = newgrp; + newmotion.m_scene = newgrp.Scene; + + if (m_keyframes != null) + { + newmotion.m_keyframes = new Keyframe[m_keyframes.Length]; + m_keyframes.CopyTo(newmotion.m_keyframes, 0); + } + + newmotion.m_frames = new List(m_frames); + + newmotion.m_basePosition = m_basePosition; + newmotion.m_baseRotation = m_baseRotation; + + if (m_selected) + newmotion.m_serializedPosition = m_serializedPosition; + else + { + if (m_group != null) + newmotion.m_serializedPosition = m_group.AbsolutePosition; + else + newmotion.m_serializedPosition = m_serializedPosition; + } + + newmotion.m_currentFrame = m_currentFrame; + + newmotion.m_iterations = m_iterations; + newmotion.m_running = m_running; + + if (m_running && !m_waitingCrossing) + StartTimer(); + + return newmotion; + } + + public void Delete() + { + m_running = false; + StopTimer(); + m_isCrossing = false; + m_waitingCrossing = false; + m_frames.Clear(); + m_keyframes = null; + } + + public void Start() + { + m_isCrossing = false; + m_waitingCrossing = false; + if (m_keyframes != null && m_group != null && m_keyframes.Length > 0) + { + StartTimer(); + m_running = true; + } + else + { + m_running = false; + StopTimer(); + } + } + + public void Stop() + { + m_running = false; + m_isCrossing = false; + m_waitingCrossing = false; + + StopTimer(); + + m_basePosition = m_group.AbsolutePosition; + m_baseRotation = m_group.GroupRotation; + + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + m_frames.Clear(); + } + + public void Pause() + { + m_running = false; + StopTimer(); + + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + + } + + private void GetNextList() + { + m_frames.Clear(); + Vector3 pos = m_basePosition; + Quaternion rot = m_baseRotation; + + if (m_mode == PlayMode.Loop || m_mode == PlayMode.PingPong || m_iterations == 0) + { + int direction = 1; + if (m_mode == PlayMode.Reverse || ((m_mode == PlayMode.PingPong) && ((m_iterations & 1) != 0))) + direction = -1; + + int start = 0; + int end = m_keyframes.Length; + + if (direction < 0) + { + start = m_keyframes.Length - 1; + end = -1; + } + + for (int i = start; i != end ; i += direction) + { + Keyframe k = m_keyframes[i]; + + if (k.Position.HasValue) + { + k.Position = (k.Position * direction); +// k.Velocity = (Vector3)k.Position / (k.TimeMS / 1000.0f); + k.Position += pos; + } + else + { + k.Position = pos; +// k.Velocity = Vector3.Zero; + } + + k.StartRotation = rot; + if (k.Rotation.HasValue) + { + if (direction == -1) + k.Rotation = Quaternion.Conjugate((Quaternion)k.Rotation); + k.Rotation = rot * k.Rotation; + } + else + { + k.Rotation = rot; + } + +/* ang vel not in use for now + + float angle = 0; + + float aa = k.StartRotation.X * k.StartRotation.X + k.StartRotation.Y * k.StartRotation.Y + k.StartRotation.Z * k.StartRotation.Z + k.StartRotation.W * k.StartRotation.W; + float bb = ((Quaternion)k.Rotation).X * ((Quaternion)k.Rotation).X + ((Quaternion)k.Rotation).Y * ((Quaternion)k.Rotation).Y + ((Quaternion)k.Rotation).Z * ((Quaternion)k.Rotation).Z + ((Quaternion)k.Rotation).W * ((Quaternion)k.Rotation).W; + float aa_bb = aa * bb; + + if (aa_bb == 0) + { + angle = 0; + } + else + { + float ab = k.StartRotation.X * ((Quaternion)k.Rotation).X + + k.StartRotation.Y * ((Quaternion)k.Rotation).Y + + k.StartRotation.Z * ((Quaternion)k.Rotation).Z + + k.StartRotation.W * ((Quaternion)k.Rotation).W; + float q = (ab * ab) / aa_bb; + + if (q > 1.0f) + { + angle = 0; + } + else + { + angle = (float)Math.Acos(2 * q - 1); + } + } + + k.AngularVelocity = (new Vector3(0, 0, 1) * (Quaternion)k.Rotation) * (angle / (k.TimeMS / 1000)); + */ + k.TimeTotal = k.TimeMS; + + m_frames.Add(k); + + pos = (Vector3)k.Position; + rot = (Quaternion)k.Rotation; + } + + m_basePosition = pos; + m_baseRotation = rot; + + m_iterations++; + } + } + + public void OnTimer(double tickDuration) + { + if (m_skipLoops > 0) + { + m_skipLoops--; + return; + } + + if (m_timerStopped) // trap events still in air even after a timer.stop + return; + + if (m_group == null) + return; + + bool update = false; + + if (m_selected) + { + if (m_group.RootPart.Velocity != Vector3.Zero) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); + + } + return; + } + + if (m_isCrossing) + { + // if crossing and timer running then cross failed + // wait some time then + // retry to set the position that evtually caused the outbound + // if still outside region this will call startCrossing below + m_isCrossing = false; + m_group.AbsolutePosition = m_nextPosition; + if (!m_isCrossing) + { + StopTimer(); + StartTimer(); + } + return; + } + + if (m_frames.Count == 0) + { + GetNextList(); + + if (m_frames.Count == 0) + { + Stop(); + Scene scene = m_group.Scene; + + IScriptModule[] scriptModules = scene.RequestModuleInterfaces(); + foreach (IScriptModule m in scriptModules) + { + if (m == null) + continue; + m.PostObjectEvent(m_group.RootPart.UUID, "moving_end", new object[0]); + } + + return; + } + + m_currentFrame = m_frames[0]; + m_currentFrame.TimeMS += (int)tickDuration; + + //force a update on a keyframe transition + update = true; + } + + m_currentFrame.TimeMS -= (int)tickDuration; + + // Do the frame processing + double steps = (double)m_currentFrame.TimeMS / tickDuration; + + if (steps <= 0.0) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + + m_nextPosition = (Vector3)m_currentFrame.Position; + m_group.AbsolutePosition = m_nextPosition; + + // we are sending imediate updates, no doing force a extra terseUpdate + // m_group.UpdateGroupRotationR((Quaternion)m_currentFrame.Rotation); + + m_group.RootPart.RotationOffset = (Quaternion)m_currentFrame.Rotation; + m_frames.RemoveAt(0); + if (m_frames.Count > 0) + m_currentFrame = m_frames[0]; + + update = true; + } + else + { + float complete = ((float)m_currentFrame.TimeTotal - (float)m_currentFrame.TimeMS) / (float)m_currentFrame.TimeTotal; + + Vector3 v = (Vector3)m_currentFrame.Position - m_group.AbsolutePosition; + Vector3 motionThisFrame = v / (float)steps; + v = v * 1000 / m_currentFrame.TimeMS; + + if (Vector3.Mag(motionThisFrame) >= 0.05f) + { + // m_group.AbsolutePosition += motionThisFrame; + m_nextPosition = m_group.AbsolutePosition + motionThisFrame; + m_group.AbsolutePosition = m_nextPosition; + + //m_group.RootPart.Velocity = v; + update = true; + } + + if ((Quaternion)m_currentFrame.Rotation != m_group.GroupRotation) + { + Quaternion current = m_group.GroupRotation; + + Quaternion step = Quaternion.Slerp(m_currentFrame.StartRotation, (Quaternion)m_currentFrame.Rotation, complete); + step.Normalize(); +/* use simpler change detection +* float angle = 0; + + float aa = current.X * current.X + current.Y * current.Y + current.Z * current.Z + current.W * current.W; + float bb = step.X * step.X + step.Y * step.Y + step.Z * step.Z + step.W * step.W; + float aa_bb = aa * bb; + + if (aa_bb == 0) + { + angle = 0; + } + else + { + float ab = current.X * step.X + + current.Y * step.Y + + current.Z * step.Z + + current.W * step.W; + float q = (ab * ab) / aa_bb; + + if (q > 1.0f) + { + angle = 0; + } + else + { + angle = (float)Math.Acos(2 * q - 1); + } + } + + if (angle > 0.01f) +*/ + if(Math.Abs(step.X - current.X) > 0.001f + || Math.Abs(step.Y - current.Y) > 0.001f + || Math.Abs(step.Z - current.Z) > 0.001f) + // assuming w is a dependente var + + { +// m_group.UpdateGroupRotationR(step); + m_group.RootPart.RotationOffset = step; + + //m_group.RootPart.UpdateAngularVelocity(m_currentFrame.AngularVelocity / 2); + update = true; + } + } + } + + if (update) + { + m_group.SendGroupRootTerseUpdate(); + } + } + + public Byte[] Serialize() + { + StopTimer(); + MemoryStream ms = new MemoryStream(); + + BinaryFormatter fmt = new BinaryFormatter(); + SceneObjectGroup tmp = m_group; + m_group = null; + if (!m_selected && tmp != null) + m_serializedPosition = tmp.AbsolutePosition; + fmt.Serialize(ms, this); + m_group = tmp; + if (m_running && !m_waitingCrossing) + StartTimer(); + + return ms.ToArray(); + } + + public void StartCrossingCheck() + { + // timer will be restart by crossingFailure + // or never since crossing worked and this + // should be deleted + StopTimer(); + + m_isCrossing = true; + m_waitingCrossing = true; + + // to remove / retune to smoth crossings + if (m_group.RootPart.Velocity != Vector3.Zero) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + } + } + + public void CrossingFailure() + { + m_waitingCrossing = false; + + if (m_group != null) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + + if (m_running) + { + StopTimer(); + m_skipLoops = 1200; // 60 seconds + StartTimer(); + } + } + } + } +}