// 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 { [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; [NonSerialized()] protected Timer m_timer = null; // timer lock [NonSerialized()] private object m_onTimerLock; // timer overrun detect // prevents overlap or timer events threads frozen on the lock [NonSerialized()] private bool m_inOnTimer; // 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 const double timerInterval = 50.0; 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() { if (m_timer == null) return; m_timerStopped = false; m_timer.Start(); } private void StopTimer() { if (m_timer == null || m_timerStopped) return; m_timerStopped = true; m_timer.Stop(); } private void RemoveTimer() { if (m_timer == null) return; m_timerStopped = true; m_timer.Stop(); m_timer.Elapsed -= OnTimer; m_timer = null; } 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 && grp.IsSelected) newMotion.m_selected = true; newMotion.m_onTimerLock = new object(); newMotion.m_timerStopped = false; newMotion.m_inOnTimer = false; newMotion.m_isCrossing = false; newMotion.m_waitingCrossing = false; } catch { newMotion = null; } return newMotion; } public void UpdateSceneObject(SceneObjectGroup grp) { // lock (m_onTimerLock) { m_isCrossing = false; m_waitingCrossing = false; StopTimer(); if (grp == null) return; m_group = grp; 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_onTimerLock = new object(); m_timerStopped = true; m_inOnTimer = false; 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; 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; RemoveTimer(); 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) { if (m_timer == null) { m_timer = new Timer(); m_timer.Interval = timerInterval; m_timer.AutoReset = true; m_timer.Elapsed += OnTimer; } else { StopTimer(); m_timer.Interval = timerInterval; } m_inOnTimer = false; StartTimer(); m_running = true; } else { m_running = false; RemoveTimer(); } } public void Stop() { m_running = false; m_isCrossing = false; m_waitingCrossing = false; RemoveTimer(); m_basePosition = m_group.AbsolutePosition; m_baseRotation = m_group.GroupRotation; m_group.RootPart.Velocity = Vector3.Zero; m_group.RootPart.UpdateAngularVelocity(Vector3.Zero); m_group.SendGroupRootTerseUpdate(); m_frames.Clear(); } public void Pause() { m_running = false; RemoveTimer(); m_group.RootPart.Velocity = Vector3.Zero; m_group.RootPart.UpdateAngularVelocity(Vector3.Zero); m_group.SendGroupRootTerseUpdate(); } 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 (m_mode == PlayMode.PingPong && m_keyframes.Length > 1) // end = m_keyframes.Length - 1; if (direction < 0) { start = m_keyframes.Length - 1; end = -1; // if (m_mode == PlayMode.PingPong && m_keyframes.Length > 1) // end = 0; } 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++; } } protected void OnTimer(object sender, ElapsedEventArgs e) { if (m_timerStopped) // trap events still in air even after a timer.stop return; if (m_inOnTimer) // don't let overruns to happen { m_log.Warn("[KeyFrame]: timer overrun"); return; } if (m_group == null) return; lock (m_onTimerLock) { m_inOnTimer = true; bool update = false; try { if (m_selected) { if (m_group.RootPart.Velocity != Vector3.Zero) { m_group.RootPart.Velocity = Vector3.Zero; m_group.SendGroupRootTerseUpdate(); } m_inOnTimer = false; 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(); m_timer.Interval = timerInterval; StartTimer(); } m_inOnTimer = false; return; } if (m_frames.Count == 0) { GetNextList(); if (m_frames.Count == 0) { Stop(); m_inOnTimer = false; return; } m_currentFrame = m_frames[0]; m_currentFrame.TimeMS += (int)timerInterval; //force a update on a keyframe transition update = true; } m_currentFrame.TimeMS -= (int)timerInterval; // Do the frame processing double steps = (double)m_currentFrame.TimeMS / timerInterval; if (steps <= 0.0) { m_group.RootPart.Velocity = Vector3.Zero; m_group.RootPart.UpdateAngularVelocity(Vector3.Zero); m_nextPosition = (Vector3)m_currentFrame.Position; m_group.AbsolutePosition = m_nextPosition; m_group.UpdateGroupRotationR((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.UpdateAngularVelocity(m_currentFrame.AngularVelocity / 2); update = true; } } } if (update) m_group.SendGroupRootTerseUpdate(); } catch ( Exception ex) { // still happening sometimes // lets try to see where m_log.Warn("[KeyFrame]: timer overrun" + ex.Message); } finally { // make sure we do not let this frozen m_inOnTimer = false; } } } 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(); } } public void CrossingFailure() { m_waitingCrossing = false; if (m_group != null) { m_group.RootPart.Velocity = Vector3.Zero; m_group.SendGroupRootTerseUpdate(); if (m_running && m_timer != null) { m_timer.Interval = 60000; StartTimer(); } } } } }