Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Asynchronous Animation of Realistic Motion

0.00/5 (No votes)
12 Mar 2014 1  
Interoperability between 3D graphics and calculations

Abstract nonsense

Useful links

1. Introduction.

Realistic motion is usually described by ordinary differential equations (ODE). Solution of ordinary differtial equations mostly requires numerical methods. Some equations assume a lot of calculations. For example motion model of artificail Eatrth's comtains model of Earth's gravity and Earth's atmosphere . Numerical methods calculate motion parameters at discrete values of time t1, t2, t3, ... The difference dt = ti+1-ti is called a step size. Reduction of step size supplies a more accurate solution of ODE. However there is the optimal value of dt0 such that there is no substantial difference between solutions given by step sizes dtopt and dt < dtopt. Value of dtopt depends on ODE. Moreveover this value is not a constant. In case of robot pilot control value of dtopt is bigger then in case of manual control. A lot of tasks require animation. If dtopt is small then animation can be performed by following way. Computer solves ODE step by step and simultaneously updates 3D images. This method is in fact synchronous animation which is well known long time ago. However if dtopt is not small then synchronous animation occurs considerable jumps of 3D images. There are following ways to avoid this problem:

  • Reduction of step size;
  • Application of asynchronous animation.
First way sometimes is not good because it requires substantial enlargement of calculation. Asynchronous animation is explained by following picture.

Asynchronous animation

Real traejctory of 3D object is replaced with piecewise linear trajectory, which is close to real. However synchronous animation corresponds to piecewise constant function which is presented below.

Synchronous animation

Following movies show difference between different methods of animation.

Modes of anination

Synchronous animation occurs substantial jumps of cube, jumps of asynchronous animation are scarcely visible.

Present day technologies support a lot of asynchronous animation methods with a powerful hardware support. In this article I have used WPF animation described here. However my soft contains abstract layer and it can be easy adapted for other types of animation.

2. Math Background

Uniform motion from 3D point p1 = (x1, y1, z1) to p2 = (x2, y2, z2) can be represented by following elementary expression

p(t) = (x1(1 - t) + x2t, y1(1 - t) + y2t, z1(1 - t) + z2t)

where t is a current progress (current progress corresponds to the Clock.CurrentProgress Property). Uniform rotational motion can be easy described by quaternions. Uniform rotation from quaternion Q1 = Q01 + Q11i + Q21j + Q11k to Q2=Q02 + Q21i + Q22j + Q12k is described by following equation

Q(t) = Q1Quniform(t)

where Quniform(t) corresponds to uniform rotation. Since uniform rotation is supported by WPF we do not need explicit calculation of Quniform(t). We need axis of rotation and rotation angle only. These parameters can be given from a following relative rotation quaternion

Qrelative = Q1*Q2

where Q1* = Q01 - Q11i - Q21j - Q11k.

Otherwise

Qrelative= cos (a/2) + rxsin(a/2)i + rysin(a/2)i + rzsin(a/2)k.

Where a is a full rotation angle, direction of vector r = (rx, ry, rz) coincides with rotation axis. If r= 0 (resp. r is close to 0) then rotation is abscent (resp. can be neglected). Above equations are implemented by Uniform6DMotion class which is contained is source code. This class is independent from WPF or any other animation implementation.

3. WPF Implementation of the Uniform 6D Motion

Uniform 6D motion is implemented by Uniform6DTransformation which is a custom animation class. Following code snippet explains its logics.

     /// <summary>
    /// Custom animation class for uniform 6D motion
    /// </summary>
    public class Uniform6DTransformation : AnimationTimeline
    {
        #region Fields

        /// <summary>
        /// Full transformation
        /// </summary>
        Transform3DGroup transform = new Transform3DGroup();

        /// <summary>
        /// Translation
        /// </summary>
        TranslateTransform3D translation = new TranslateTransform3D();

        /// <summary>
        /// Constant rotation
        /// </summary>
        RotateTransform3D rotate_const = new RotateTransform3D();

        /// <summary>
        /// Uniform rotation
        /// </summary>
        RotateTransform3D rotate_uniform = new RotateTransform3D();

        /// <summary>
        /// Quaternion of constant rotation
        /// </summary>
        QuaternionRotation3D quaternionConstRotation = new QuaternionRotation3D();

        /// <summary>
        /// Axis angle rotation for uniform rotation
        /// </summary>
        AxisAngleRotation3D angle_uniform = new AxisAngleRotation3D();

        /// <summary>
        /// Calculator
        /// </summary>
        Motion6D.Uniform6D.Uniform6DMotion calculator;

        // ...

        // Constructor
        internal Uniform6DTransformation(AnimatableWrapper wrapper, Motion6D.ReferenceFrame frame,
            bool realtime, double[] changeFrameTime, TimeSpan forecastTime)
        {

            transform.Children.Add(rotate_uniform); // Adds uniform rotation
            transform.Children.Add(rotate_const);   // Adds constant rptation
            transform.Children.Add(translation);    // Adds translation
            rotate_const.Rotation = quaternionConstRotation; // Setting quaterion for constant rotation
            rotate_uniform.Rotation = angle_uniform;         // Setting axis rotation for uniform rotation
        // ...

        }

        /// <summary>
        /// Sets progress time
        /// </summary>
        /// <param name="progressTime">Progress time</param>
        private void SetProgressTime(double progressTime)
        {
            double angle;               // Angle of rotation
            double x, y, z;             // Coordinates of transformation
            calculator.SetTime(progressTime, out angle, out x, out y, out z); // Calculation of coordinates and angle
            angle_uniform.Angle = (180 / Math.PI) * angle;                    // Sets value of rotation angle
            // Setting of coordinates of transformation
            translation.OffsetX = x;
            translation.OffsetY = y;
            translation.OffsetZ = z;
        }


        /// <summary>
        /// Overriden
        /// </summary>
        /// <param name="defaultOriginValue">Default Origin Value</param>
        /// <param name="defaultDestinationValue">Default Destination Value</param>
        /// <param name="animationClock">Animation Clock</param>
        /// <returns>Transformation</returns>
        public override object GetCurrentValue(object defaultOriginValue,
                object defaultDestinationValue, AnimationClock animationClock)
        {
            SetTime(animationClock.CurrentProgress.Value);
            return transform;
        }

        /// <summary>
        /// Overriden calculation of current value
        /// </summary>
        /// <param name="defaultOriginValue">Default Origin Value</param>
        /// <param name="defaultDestinationValue">Default Destination Value</param>
        /// <param name="animationClock">Animation Clock</param>
        /// <returns>Transformation</returns>
        public override object GetCurrentValue(object defaultOriginValue,
                object defaultDestinationValue, AnimationClock animationClock)
        {
            SetProgressTime(animationClock.CurrentProgress.Value);
            return transform;
        }

        // ...

    }

Above code means that a transformatin is decomoposed to

  • Uniform 3D translation;
  • Constant 3D rotation;
  • Uniform 3D rotation.

4. General Asynchronous Animation Algorithm

Asynchronous animation architecture contains abstract level which corresponds to following interface.

     /// <summary>
    /// Driver of animation
    /// </summary>
    public interface IAnimationDriver
    {
        /// <summary>
        /// Starts animation
        /// </summary>
        /// <param name="collection">Collection of components</param>
        /// <param name="reasons">Reasons</param>
        /// <param name="animationType">Type of animation</param>
        /// <param name="pause">Pause</param>
        /// <param name="timeScale">Time scale</param>
        /// <param name="realTime">The "real time" sign</param>
        /// <param name="absoluteTime">The "absolute time" sign</param>
        /// <returns>Animation asynchronous calculation</returns>
        object StartAnimation(IComponentCollection collection, string[] reasons,
          Enums.AnimationType animationType,
          TimeSpan pause, double timeScale, bool realTime, bool absoluteTime);

        /// <summary>
        /// Supports asynchronous
        /// </summary>
        bool SuppotrsAsynchronous
        {
            get;
        }
    }

    /// <summary>
    /// Type of action
    /// </summary>
    public enum ActionType
    {
        /// <summary>
        /// Calculation
        /// </summary>
        Calculation,
        /// <summary>
        /// Animation
        /// </summary>
        Animation
    }


    /// <summary>
    /// Type of animation
    /// </summary>
    public enum AnimationType
    {
        /// <summary>
        /// Synchronous
        /// </summary>
        Synchronous,

        /// <summary>
        /// Asynchronous
        /// </summary>
        Asynchronous
    }

The StartAnimation function returns object which implements following interface.

     /// <summary>
    /// Asynchronous calculation
    /// </summary>
    public interface IAsynchronousCalculation
    {
        /// <summary>
        /// Starts itself
        /// </summary>
        /// <param name="time"></param>
        void Start(double time);

        /// <summary>
        /// Step
        /// </summary>
        Action<double> Step
        {
            get;
        }

        /// <summary>
        /// Stops itself
        /// </summary>
        void Interrupt();

        /// <summary>
        /// Suspends itself
        /// </summary>
        void Suspend();

        /// <summary>
        /// The "is running" sign
        /// </summary>
        bool IsRunning
        {
            get;
        }

        /// <summary>
        /// Suspend event
        /// </summary>
        event Action OnSuspend;

        /// <summary>
        /// Finish event
        /// </summary>
        event Action Finish;

        /// <summary>
        /// Interrupt
        /// </summary>
        event Action OnInterrupt;

    }

Above interface is not intended for animation only. Following table contains different implementations of this interface.

NClass namePurpose
1PauseAsynchronousCalculationSynchronous animation
2WpfAsynchronousAnimatedCalculationAsynchronous animation for WPF
3WpfAsynchronousRealtimeAnimatedCalculationAsynchronous real-time animation for WPF

The real-time mode is described below.

The PauseAsynchronousCalculation does not depend on WPF or other animation technology. This class just performs a pause. Following code explains this circumstance.

     /// <summary>
    /// Pause asynchronous calculation
    /// </summary>
    public class PauseAsynchronousCalculation : IAsynchronousCalculation
    {
        #region Fields

        private Action<double> pause;

        // ...

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="pauseSpan">Pause</param>
        public PauseAsynchronousCalculation(TimeSpan pauseSpan)
        {
            // Pause action
            pause = (double time) =>
            {
                Thread.Sleep(pauseSpan);
            };
        }

        // ...

        Action<double> IAsynchronousCalculation.Step
        {
            get { return pause; }
        }

        // ...
    }    

Every step of synchronous animation assumes following operations:

  • Calculation of motion parameters;
  • Showing of 3D objects with new 6D positions;
  • Time pause.

Asynchronous animation assumes one thread of calculation and many threads for animation. Every step of asynchronous animation calculation contains following operations:

  • Calculation of motion parameters;
  • Enqueuing motion parameters and current time to every animation thread.

Every animation thread performs following operations:

5. Calculation of motion parameters

Calculation of motion parameters is already described in my previous article. However I overlap some text from my previous article for convenience.

A kinematics domain contains following basic types:

  • 3D Position (IPosition interface);
  • 3D Orientation (IOrientation interface;
  • Standard 3D Position (Position class which implements IPosition interface);
  • 3D Reference frame (ReferenceFrame class which implements both IPosition and IOrientation);
  • Holder 3D Reference frame (IReferenceFrame interface) ;
  • Reference frame binding (ReferenceFrameArrow class which implements ICategoryArrow interface).

Source (resp. target) of ReferenceFrameArrow is always IPosition (resp. IReferenceFrame). This arrow means that coordinates of IPosition are relative with respect to IReferenceFrame. Following code represents these types:

    /// <summary>
    /// 3D Position
    /// </summary>
    public interface IPosition
    {

        /// <summary>
        /// Absolute position coordinates
        /// </summary>
        double[] Position
        {
            get;
        }

        /// <summary>
        /// Parent frame
        /// </summary>
        IReferenceFrame Parent
        {
            get;
            set;
        }

        /// <summary>
        /// Position parameters
        /// </summary>
        object Parameters
        {
            get;
            set;
        }

        /// <summary>
        /// Updates itself
        /// </summary>
        void Update();
    }

     /// <summary>
    /// Object with orientation
    /// </summary>
    public interface IOrientation
    {
        /// <summary>
        /// Orientation quaternion
        /// </summary>
        double[] Quaternion
        {
            get;
        }

        /// <summary>
        /// Orientation matrix
        /// </summary>
        double[,] Matrix
        {
            get;
        }
    }

     /// <summary>
    /// Standard position
    /// </summary>
    public class Position : IPosition, IChildrenObject
    {

        #region Fields

        /// <summary>
        /// Parent frame
        /// </summary>
        protected IReferenceFrame parent;

        /// <summary>
        /// Own position
        /// </summary>
        protected double[] own = new double[] { 0, 0, 0 };

        /// <summary>
        /// Relatyive position
        /// </summary>
        protected double[] position = new double[3];

        /// <summary>
        /// Parameters
        /// </summary>
        protected object parameters;

        /// <summary>
        /// Children objects
        /// </summary>
        protected IAssociatedObject[] ch = new IAssociatedObject[1];

        #endregion

        #region Ctor

        /// <summary>
        /// Default constructor
        /// </summary>
        protected Position()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="position">Position coordinates</param>
        public Position(double[] position)
        {
            for (int i = 0; i < own.Length; i++)
            {
                own[i] = position[i];
            }
        }

        #endregion

        #region IPosition Members

        double[] IPosition.Position
        {
            get { return position; }
        }

        /// <summary>
        /// Parent frame
        /// </summary>
        public virtual IReferenceFrame Parent
        {
            get
            {
                return parent;
            }
            set
            {
                parent = value;
            }
        }

        /// <summary>
        /// Position parameters
        /// </summary>
        public virtual object Parameters
        {
            get
            {
                return parameters;
            }
            set
            {
                parameters = value;
                if (value is IAssociatedObject)
                {
                    IAssociatedObject ao = value as IAssociatedObject;
                    ch[0] = ao;
                }
            }
        }

        /// <summary>
        /// Updates itself
        /// </summary>
        public virtual void Update()
        {
            Update(BaseFrame);
        }

        #endregion

        // ...
    }

   /// <summary>
    /// Reference frame
    /// </summary>
    public class ReferenceFrame : IPosition, IOrientation
    {

        #region Fields

        /// <summary>
        /// Orientation quaternion
        /// </summary>
        protected double[] quaternion = new double[] { 1, 0, 0, 0 };

        /// <summary>
        /// Absolute position
        /// </summary>
        protected double[] position = new double[] { 0, 0, 0 };

        /// <summary>
        /// Orientation matrix
        /// </summary>
        protected double[,] matrix = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };

        /// <summary>
        /// Auxiliary array
        /// </summary>
        protected double[,] qq = new double[4, 4];

        /// <summary>
        /// Auxiliary array
        /// </summary>
        protected double[] p = new double[3];

        /// <summary>
        /// Parent frame
        /// </summary>
        protected IReferenceFrame parent;

        /// <summary>
        /// Parameters
        /// </summary>
        protected object parameters;

        /// <summary>
        /// Auxliary position
        /// </summary>
        private double[] auxPos = new double[3];

        #endregion

        #region Ctor

        /// <summary>
        /// Constructor
        /// </summary>
        public ReferenceFrame()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="b">Auxiliary</param>
        private ReferenceFrame(bool b)
        {
        }

        #endregion

        #region IPosition Members

        /// <summary>
        /// Absolute position
        /// </summary>
        public double[] Position
        {
            get { return position; }
        }

        /// <summary>
        /// Parent frame
        /// </summary>
        public virtual IReferenceFrame Parent
        {
            get
            {
                return parent;
            }
            set
            {
                parent = value;
            }
        }

        /// <summary>
        /// Position parameters
        /// </summary>
        public virtual object Parameters
        {
            get
            {
                return parameters;
            }
            set
            {
                parameters = value;
            }
        }

        /// <summary>
        /// Updates itself
        /// </summary>
        public virtual void Update()
        {
            ReferenceFrame p = ParentFrame;
            position = p.Position;
            quaternion = p.quaternion;
            matrix = p.matrix;
        }

        #endregion

        #region IOrientation Members

        /// <summary>
        /// Orientation quaternion
        /// </summary>
        public double[] Quaternion
        {
            get { return quaternion; }
        }

        /// <summary>
        /// Orientation matrix
        /// </summary>
        public double[,] Matrix
        {
            get { return matrix; }
        }

        #endregion

       // ...

    }

     /// <summary>
    /// Reference frame holder
    /// </summary>
    public interface IReferenceFrame : IPosition
    {
        /// <summary>
        /// Own frame
        /// </summary>
        ReferenceFrame Own
        {
            get;
        }

        /// <summary>
        /// Children objects
        /// </summary>
        List<IPosition> Children
        {
            get;
        }

    }

     /// <summary>
    /// Link of relative frame
    /// </summary>
    [Serializable()]
    public class ReferenceFrameArrow : CategoryArrow, ISerializable, IRemovableObject
    {
        #region Fields

        IPosition source;

        IReferenceFrame target;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public ReferenceFrameArrow()
        {

        }


        /// <summary>
        /// Deserialization constructor
        /// </summary>
        /// <param name="info">Serialization info</param>
        /// <param name="context">Streaming context</param>
        protected ReferenceFrameArrow(SerializationInfo info, StreamingContext context)
        {
        }

        #endregion

        #region ISerializable Members

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
        }

        #endregion

        #region ICategoryArrow Members

        /// <summary>
        /// The source of this arrow
        /// </summary>
        public override ICategoryObject Source
        {
            get
            {
                return source as ICategoryObject;
            }
            set
            {
                IPosition position = value.GetSource<IPosition>();
                if (position.Parent != null)
                {
                    throw new CategoryException("Root", this);
                }
                source = position;
            }
        }

        /// <summary>
        /// The target of this arrow
        /// </summary>
        public override ICategoryObject Target
        {
            get
            {
                return target as ICategoryObject;
            }
            set
            {
                IReferenceFrame rf = value.GetTarget<IReferenceFrame>();
                IAssociatedObject sa = source as IAssociatedObject;
                IAssociatedObject ta = value as IAssociatedObject;
                INamedComponent ns = sa.Object as INamedComponent;
                INamedComponent nt = ta.Object as INamedComponent;
                target = rf;
                source.Parent = target;
                target.Children.Add(source);
            }
        }


        #endregion



        //...

    }

Some code is omitted, a reader can find it in source files. Let us consider a following picture:

Point & Frame

Type of the Point is Position and the Frame implements IReferenceFrame interface. The Link arrow means that motion of Point is relative with respect to Frame. Absolute coordinates of Point are calculated by following way:

Xa = XF + A11Xr + A12Yr + A13Zr;

Ya = YF + A21Xr + A22Yr + A23Zr;

Za = ZF + A31Xr + A32Yr + A33Zr.

where

  • Xa, Ya, Za absolute coordinates of Point
  • XF, YF, ZF absolute coordinates of Frame
  • Xr, Yr, Zr relative coordinates of Point
  • A11,..., A33 - elements of 3D rotation matrix.

6. Animated objects

There is a many to many relation of following objects:

Following movies contains 7 shapes and 5 cameras with independent motion

Helicopter

Helicoper contains following shapes:

  • Fuselage;
  • Tail rotor;
  • 5 blades of main rotor.

Independent motion of blades is due to main rotor assembly

helicopter Rotor

WPF allows usage of single Visual3D in single camera only. So we need for copies of 3D shapes. Above picture contains 5 * 7 + 5 - 40 animatable (IAnimatable interface), they are 5 cameras and 5 copies for each 7 Visual3D objects. However simulation of motion is performed for 5 + 7 = 12 objects (5 cameras and 7 3D shapes). So we need 12 objects of the ReferenceFrame type. Following interface is responsible for interoperability between animated objects and objects of 6D motion simulation.

     /// <summary>
    /// Linear forecast
    /// </summary>
    public interface ILinear6DForecast
    {

        /// <summary>
        /// Frame
        /// </summary>
        ReferenceFrame ReferenceFrame
        {
            get;
        }


        /// <summary>
        /// Forecast time
        /// </summary>
        TimeSpan ForecastTime
        {
            get;
            set;
        }

        /// <summary>
        /// Error of coordinate
        /// </summary>
        double CoordinateError
        {
            get;
            set;
        }

        /// <summary>
        /// Error of angle
        /// </summary>
        double AngleError
        {
            get;
            set;
        }
    }

Above interface does not depend on 3D graphics technology, following interface is a WPF version.

     /// <summary>
    /// Animated object
    /// </summary>
    interface IAnimatedObject : ILinear6DForecast
    {
        /// <summary>
        /// Initialze animation
        /// </summary>
        void InitAnimation(AnimationType animationType);

        /// <summary>
        /// Initialze animation
        /// </summary>
        /// <param name="animationType">Type of animation</param>
        /// <param name="changeFrameTime">Time of frame change</param>
        void InitRealtime(AnimationType animationType, double[] changeFrameTime);

        /// <summary>
        /// Children
        /// </summary>
        AnimatableWrapper[] Children
        {
            get;
        }

        /// <summary>
        /// Change event
        /// </summary>
        event Action Change;

        /// <summary>
        /// Stops animation
        /// </summary>
        void StopAnimation();

        /// <summary>
        /// On stop action
        /// </summary>
        event Action OnStop;

        /// <summary>
        /// Supports animation events
        /// </summary>
        bool SupportsAnimationEvents
        {
            get;
            set;
        }

     }

The Children property contains wrappers of animatable (IAnimatable interface). In previous picture every 3D shape contains 5 children, every perspective camera has a single child. Following code contains wrapper of animatable object.

     /// <summary>
    /// Wrapper of animatable object
    /// </summary>
    public class AnimatableWrapper : IDisposable
    {
        #region Fields

        private IAnimatable animatable;

        private DependencyProperty dependencyProperty;

        private object value;

        private Action change = () => { };

        private IAnimatedObject animatedObject;

        private Uniform6DTransformation transformation;

        private Action finish = () => { };

        double[] auxQuaternion = new double[4];

        bool isStopped = false;

        object loc = new object();

        #endregion

        #region Ctor

       internal AnimatableWrapper(IAnimatable animatable, DependencyProperty dependencyProperty,
            IAnimatedObject animatedObject, bool realtime, double[] changeFrameTime)
        {
            this.animatable = animatable;
            this.dependencyProperty = dependencyProperty;
            value = (animatable as DependencyObject).GetValue(dependencyProperty);
            this.animatedObject = animatedObject;
            transformation = new Uniform6DTransformation(this,animatedObject.ReferenceFrame,
                realtime, changeFrameTime, animatedObject.ForecastTime);
        }

        #endregion

        #region Public Members

        /// <summary>
        /// Animatable
        /// </summary>
        public IAnimatable Animatable
        {
            get
            {
                return animatable;
            }
        }

        /// <summary>
        /// Dependency property
        /// </summary>
        public DependencyProperty DependencyProperty
        {
            get
            {
                return dependencyProperty;
            }
        }


        #endregion

        #region Internal Members

        internal IAnimatedObject Animated
        {
            get
            {
                return animatedObject;
            }
        }

        internal void Stop()
        {
            lock (loc)
            {
                transformation.Stop();
                isStopped = true;
                finish();
            }
        }

        internal void StartRealtime(double time, DateTime start)
        {
            transformation.StartRealtime(time, start);
        }

        internal void Init(double[] coord, double[] quaternion)
        {
            transformation.Init(coord, quaternion);
        }

        internal void StartAnimation(double[] coord, double[] quaternion, DateTime start)
        {
            transformation.StartAnimation(coord, quaternion, start);
        }


        internal void Enqueue(Tuple<TimeSpan, double[], double[]> parameters)
        {
             transformation.Enqueue(parameters);
        }

        internal void Finish()
        {
            finish();
        }

        internal event Action OnFinish
        {
            add { finish += value; }
            remove { finish -= value; }
        }

        internal  Action Event
        {
            get
            {
                return transformation.Event;
            }
        }

        #endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            (animatable as DependencyObject).SetValue(dependencyProperty, value);
            animatedObject.Change -= change;
        }

        #endregion
    }

Above class contains a transformation field of the Uniform6DTransformation : AnimationTimeline type. This field is responsible for animation.

7. Real-time

Recently I wrote an article devoted to real-time. This section can be regarded as an animation extension of the real-time simulation. Above animation is explained by following picture.

Asynchronous animation

However above scheme is not sufficient for real-time because animation lag behind simulation. Sufficient scheme is presented below.

Real-time

This scheme implies usage of the linear prediction. Both velocity vector and angular velocity vectors supply necessary data for linear prediction. My software supports calculation of velocity and angular velocity for different engineering problems (See here and here). Now these calculations are used for the asynchronous animation. Following interfaces are responsible for calculation of velocity vectors.

     /// <summary>
    /// Object with linear velocity
    /// </summary>
    public interface IVelocity
    {
        /// <summary>
        /// Linear velocity
        /// </summary>
        double[] Velocity
        {
            get;
        }

     }

    /// <summary>
    /// Object that have angular velocity
    /// </summary>
    public interface IAngularVelocity
    {
        /// <summary>
        /// Angular velocity of object
        /// </summary>
        double[] Omega
        {
            get;
        }
    }

Following code explains application of these interfaces for linear prediction.

         /// <summary>
        /// Reference frame
        /// </summary>
        ReferenceFrame frame;

        /// <summary>
        /// Velocity object
        /// </summary>
        IVelocity velocity;

        /// <summary>
        /// Angular velocity object
        /// </summary>
        IAngularVelocity angularVelocity;

        // ...


        /// <summary>
        /// Initialization of prediction
        /// </summary>
        public void InitializePrediction(double forecastTime)
        {
            if (!(frame is IVelocity))
            {
                throw new Exception("Frame does not support velocity");
            }
            if (!(frame is IAngularVelocity))
            {
                throw new Exception("Frame does not support angular velocity");
            }
            velocity = frame as IVelocity;
            angularVelocity = frame as IAngularVelocity;
        }

        /// <summary>
        /// Initialization of prediction
        /// </summary>
        public void InitializePrediction(double forecastTime)
        {
            if (!(frame is IVelocity))
            {
                throw new Exception("Frame does not support velocity");
            }
            if (!(frame is IAngularVelocity))
            {
                throw new Exception("Frame does not support angular velocity");
            }
            velocity = frame as IVelocity;
            angularVelocity = frame as IAngularVelocity;
        }

        /// <summary>
        /// Linear prediction
        /// </summary>
        /// <param name="time">Current time</param>
        private void LinearPrediction(double time)
        {
            lastTime = time;
            double delta = time - changeFrameTime[0]; // Forecast time
            double[] coord = frame.Position;          // Coordinates of frame
            double[] v = velocity.Velocity;           // Velocity vector
            for (int i = 0; i < 3; i++)
            {
                auxVectorFrame[i] = coord[i] + v[i] * delta; // Linear prediction of coordinates
            }
            double[] omega = angularVelocity.Omega; // Angular velocity vector
            double mod = omega[0] * omega[0] + omega[1] * omega[1] + omega[2] * omega[2];
            mod = Math.Sqrt(mod);
            Array.Copy(omega, 0, auxQuarterInter, 1, 3); // Calculation of "shift" quaternion
            double angle = 0.5 * mod * delta;
            double s = Math.Sin(angle);
            double c = Math.Cos(angle);
            auxQuarterInter[0] = c;
            double smod = s / mod;
            for (int i = 1; i < 3; i++)
            {
                auxQuarterInter[i] *= smod;
            }
            QuaternionMultiply(frame.Quaternion, auxQuarterInter, auxQuaterFrame); // Calculation of quaternion
        }

8. Examples

8.1 Animation of a Cube

This sample is rather demo than realistic. Motion of cube is described by following finite formulas.

Cube motion

Above formulas are used as coordinates and components of 3D orientation quaternion.

Cube motion parameters

Following movie represents animation of the cube.

Modes of anination

8.2 Animation of a Helicopter

Motion of helicopter is also rather demo and it also is defined by finite formulas. But it includes 7 3D shapes and 5 virtual cameras. Following movie represents motion of helicopter.

Helicopter

8.3 Animation of Artificial Earth's Satellite.

This sample is quite realistic because it uses following ingredients:

Description of math model details is here. Following movie represents this animation.

Orbital movie

8.4 Manual Control of an Airplane

This sample is close to realistic. It uses real-time manual control of an airplane. Motion model of airplane is quite realistic because it uses realistic model atmosphere and an aerodynamics model. However manual control is not realistic. Instead a pilot wheel the "roll pitch robot pilot" is used. The ForcedEventData class is used for manual control. Object of this class performs following operations:

  • Manual input of parameters (roll and pitch);
  • Raising of input event.

User can update required values of pith and roll. Any update occurs a transient state. Following chart contains two transient states.

Transient state

.

Real-time simulation is based on event driven calculations. Following scheme of events is used for airplane simulation.

Events

Our calculation is driven by Event collection object which is simultaneously a generator of events and an event handler. It is an object of the EventCollection class. The Event collection is a "sum" of Timer, Transition and Pilot Control events. The Timer raises one event per 2000 ms = 2 s. The Pilot Control raises event of user manual control. The Transition raises one event per 0.2 s during transition state. The Transition is an object of the TransientProcessEvent class. This class is simultaneously a source of event and event handler. Following code explains how does this class works.

     /// <summary>
    /// Transient State Event
    /// </summary>
    [Serializable()]
    public class TransientProcessEvent : CategoryObject, IEvent, IEventHandler, ISerializable
    {
        #region Fields

        TimeSpan[] spans;

        private Action ev = () => { };

        object loc = new object();

        bool isEnabled = false;

        DateTime previous = DateTime.Now;

        bool isRunning = false;

        // ...

        #endregion

        #region Ctor

        /// <summary>
        /// Default constructor
        /// </summary>
        public TransientProcessEvent()
        {
        }

        // ...

        #endregion

        #region IEvent Members

        event Action IEvent.Event
        {
            add { ev += value; }
            remove { ev -= value; }
        }

        // ...

        #endregion

        #region IEventHandler Members

        void IEventHandler.Add(IEvent ev)
        {
            events.Add(ev);
            onAdd(ev);
        }

        // ...

        #endregion

        #region Private Members

        /// <summary>
        /// Enanble/Disable operation
        /// </summary>
        void Enable()
        {
            lock (loc)
            {
                isRunning = false;
                if (isEnabled)
                {
                    previous = DateTime.Now;
                    foreach (IEvent ev in events)
                    {
                        ev.Event += EventHandler; // Adds event handler
                    }
                    return;
                }
                foreach (IEvent ev in events)
                {
                    ev.Event -= EventHandler;    // Removes event handler
                }
            }
        }

        /// <summary>
        /// Event handler
        /// </summary>
        void EventHandler()
        {
            if (isRunning)
            {
                previous = DateTime.Now;
                return;
            }
            lock (loc)
            {
                if (isRunning)
                {
                    return;
                }
                isRunning = true;
                currentStep = 0;
                Action act = AsyncEvent; // Asynchronous event
                act.BeginInvoke(null, null);
            }
        }

        /// <summary>
        /// Asynchronous event
        /// </summary>
        void AsyncEvent()
        {
            for (int i = 0; i < spans.Length; i++)
            {
                Thread.Sleep(spans[i]); // Sleep
                ev();                   // Raise event
            }
        }

        #endregion

    }

The AsyncEvent performs cyclic sleep operation and raising of event. Following movies explains how does it works.

transient

After user action (mouse click/move) we have 10 updates distinguished by update per 0.2 s. In passive mode we have one update per 2 s. Following movie shows this sample.

Avia movie

Points of Interest

I am a software developer since 1977. During development of this article I first time encoutered with deadlock.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here