// 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 = 1, Rotation = 2 } [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_basePosition; private Quaternion m_baseRotation; private Vector3 m_serializedPosition; private Keyframe m_currentFrame; private List<Keyframe> m_frames = new List<Keyframe>(); private Keyframe[] m_keyframes; [NonSerialized()] protected Timer m_timer = new Timer(); [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 (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) m_serializedPosition = m_group.AbsolutePosition; } m_selected = value; } } public static KeyframeMotion FromData(SceneObjectGroup grp, Byte[] data) { MemoryStream ms = new MemoryStream(data); BinaryFormatter fmt = new BinaryFormatter(); KeyframeMotion newMotion = (KeyframeMotion)fmt.Deserialize(ms); // This will be started when position is updated newMotion.m_timer = new Timer(); newMotion.m_timer.Interval = (int)timerInterval; newMotion.m_timer.AutoReset = true; newMotion.m_timer.Elapsed += newMotion.OnTimer; return newMotion; } public void UpdateSceneObject(SceneObjectGroup grp) { m_group = grp; Vector3 offset = grp.AbsolutePosition - m_serializedPosition; m_basePosition += offset; m_currentFrame.Position += 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; m_basePosition = grp.AbsolutePosition; m_baseRotation = grp.GroupRotation; m_timer.Interval = (int)timerInterval; m_timer.AutoReset = true; m_timer.Elapsed += OnTimer; } public void SetKeyframes(Keyframe[] frames) { m_keyframes = frames; } public void Start() { if (m_keyframes.Length > 0) m_timer.Start(); m_running = true; } public void Stop() { // Failed object creation if (m_timer == null) return; m_timer.Stop(); 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(); m_running = false; } public void Pause() { m_group.RootPart.Velocity = Vector3.Zero; m_group.RootPart.UpdateAngularVelocity(Vector3.Zero); m_group.SendGroupRootTerseUpdate(); m_timer.Stop(); m_running = false; } 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) + pos; else k.Position = pos; 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; } 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_frames.Count == 0) { GetNextList(); if (m_frames.Count == 0) { Stop(); return; } m_currentFrame = m_frames[0]; } if (m_selected) { if (m_group.RootPart.Velocity != Vector3.Zero) { m_group.RootPart.Velocity = Vector3.Zero; m_group.SendGroupRootTerseUpdate(); } return; } // Do the frame processing double steps = (double)m_currentFrame.TimeMS / timerInterval; float complete = ((float)m_currentFrame.TimeTotal - (float)m_currentFrame.TimeMS) / (float)m_currentFrame.TimeTotal; if (steps <= 1.0) { m_currentFrame.TimeMS = 0; m_group.AbsolutePosition = (Vector3)m_currentFrame.Position; m_group.UpdateGroupRotationR((Quaternion)m_currentFrame.Rotation); } else { Vector3 v = (Vector3)m_currentFrame.Position - m_group.AbsolutePosition; Vector3 motionThisFrame = v / (float)steps; v = v * 1000 / m_currentFrame.TimeMS; bool update = false; if (Vector3.Mag(motionThisFrame) >= 0.05f) { m_group.AbsolutePosition += motionThisFrame; 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); 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) { m_group.UpdateGroupRotationR(step); //m_group.RootPart.UpdateAngularVelocity(m_currentFrame.AngularVelocity / 2); update = true; } } if (update) m_group.SendGroupRootTerseUpdate(); } m_currentFrame.TimeMS -= (int)timerInterval; if (m_currentFrame.TimeMS <= 0) { m_group.RootPart.Velocity = Vector3.Zero; m_group.RootPart.UpdateAngularVelocity(Vector3.Zero); m_group.SendGroupRootTerseUpdate(); m_frames.RemoveAt(0); if (m_frames.Count > 0) m_currentFrame = m_frames[0]; } } public Byte[] Serialize() { MemoryStream ms = new MemoryStream(); m_timer.Stop(); BinaryFormatter fmt = new BinaryFormatter(); SceneObjectGroup tmp = m_group; m_group = null; m_serializedPosition = tmp.AbsolutePosition; fmt.Serialize(ms, this); m_group = tmp; return ms.ToArray(); } public void CrossingFailure() { // The serialization has stopped the timer, so let's wait a moment // then retry the crossing. We'll get back here if it fails. Util.FireAndForget(delegate (object x) { Thread.Sleep(60000); if (m_running) m_timer.Start(); }); } } }