4.3 Timing and Animation

4.3 Timing and Animation

To do animation correctly, we need to keep track of the time. In particular, we need to measure the amount of time that elapses between frames of animation. If the frame rate is high, these time intervals between frames will be very short; therefore, we need a timer with a high level of accuracy.

4.3.1 The Performance Timer

For accurate time measurements, we use the performance timer (or performance counter). To use the Win32 functions for querying the performance timer, we must #include <windows.h>.

The performance timer measures time in units called counts. We obtain the current time value, measured in counts, of the performance timer with the QueryPerformanceCounter function like so:

__int64 currTime;    QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

Observe that this function returns the current time value through its parameter, which is a 64-bit integer value.

To get the frequency (counts per second) of the performance timer, we use the QueryPerformanceFrequency function:

__int64 countsPerSec;    QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);

Then the number of seconds (or fractions of a second) per count is just the reciprocal of the counts per second:

mSecondsPerCount = 1.0 / (double)countsPerSec;

Thus, to convert a time reading valueInCounts to seconds, we just multiply it by the conversion factor mSecondsPerCount

valueInSecs = valueInCounts * mSecondsPerCount;

The values returned by the QueryPerformanceCounter function are not particularly interesting in and of themselves. What we do is get the cur rent time value using QueryPerformanceCounter, and then get the current time value a little later using QueryPerformanceCounter again. Then the time that elapsed between those two time calls is just the difference. That is, we always look at the relative difference between two time stamps to measure time, not the actual values returned by the performance counter. The following better illustrates the idea:

 
    
1 __int64 A = 0 ;
2 QueryPerformanceCounter((LARGE_INTEGER * ) & A);
3
4   /* Do work */
5
6 __int64 B = 0 ;
7 QueryPerformanceCounter((LARGE_INTEGER * ) & B);
8  

So it took (BA) counts to do the work, or (BA)*mSecondsPerCount seconds to do the work.

Note 

MSDN has the following remark about QueryPerformanceCounter: “On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL).” You can use the SetThreadAffinityMask function so that the main application thread does not get switched to another processor.

4.3.2 Game Timer Class

In the next two sections, we will discuss the implementation of the following GameTimer class.

 
     
1 class GameTimer
2 {
3 public :
4 GameTimer();
5
6 float getGameTime() const ; // in seconds
7 float getDeltaTime() const ; // in seconds
8
9 void reset(); // Call before message loop.
10 void start(); // Call when unpaused.
11 void stop(); // Call when paused.
12 void tick(); // Call every frame.
13
14 private :
15 double mSecondsPerCount;
16 double mDeltaTime;
17
18 __int64 mBaseTime;
19 __int64 mPausedTime;
20 __int64 mStopTime;
21 __int64 mPrevTime;
22 __int64 mCurrTime;
23
24 bool mStopped;
25 };

The constructor, in particular, queries the frequency of the performance counter. The other member functions are discussed in the next two sections.

 
      
1 GameTimer::GameTimer()
2 : mSecondsPerCount( 0.0 ), mDeltaTime( - 1.0 ), mBaseTime( 0 ),
3 mPausedTime( 0 ), mPrevTime( 0 ), mCurrTime( 0 ), mStopped( false )
4 {
5 __int64 countsPerSec;
6 QueryPerformanceFrequency((LARGE_INTEGER * ) & countsPerSec);
7 mSecondsPerCount = 1.0 / ( double )countsPerSec;
8 }

Note 

The GameTimer class and implementations are in the GameTimer.h and GameTimer.cpp files, which can be found in the Common directory of the sample code.

4.3.3 Time Elapsed between Frames

When we render our frames of animation, we need to know how much time has elapsed between frames so that we can update our game objects based on how much time has passed. Computing the time elapsed between frames proceeds as follows: Let ti be the time returned by the performance counter during the ith frame and let ti−1 be the time returned by the performance counter during the previous frame. Then the time elapsed between the ti1 reading and the ti reading is Δt = ti ti−1. For real-time rendering, we typically require at least 30 frames per second for smooth animation (and we usually have much higher rates); thus, Δt = ti ti−1 tends to be a relatively small number.

The following code shows how Δt is computed:

 
    
1 void GameTimer::tick()
2 {
3 if ( mStopped )
4 {
5 mDeltaTime = 0.0 ;
6 return ;
7 }
8
9 // Get the time this frame.
10 __int64 currTime;
11 QueryPerformanceCounter((LARGE_INTEGER * ) & currTime);
12 mCurrTime = currTime;
13
14 // Time difference between this frame and the previous.
15 mDeltaTime = (mCurrTime - mPrevTime) * mSecondsPerCount;
16
17 // Prepare for next frame.
18 mPrevTime = mCurrTime;
19 // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the
20 // processor goes into a power save mode or we get shuffled to
21 // another processor, then mDeltaTime can be negative.
22 if (mDeltaTime < 0.0 )
23 {
24 mDeltaTime = 0.0 ;
25 }
26 }
27
28 float GameTimer::getDeltaTime() const
29 {
30 return ( float )mDeltaTime;
31 }

The function tick is called in the application message loop as follows:

 
    
1 int D3DApp::run()
2 {
3 MSG msg = { 0 };
4
5 mTimer.reset();
6
7 while (msg.message != WM_QUIT)
8 {
9 // If there are Window messages then process them.
10 if (PeekMessage( & msg, 0 , 0 , 0 , PM_REMOVE ))
11 {
12 TranslateMessage( & msg );
13 DispatchMessage( & msg );
14 }
15 // Otherwise, do animation/game stuff.
16 else
17 {
18 mTimer.tick();
19
20 if ( ! mAppPaused )
21 updateScene(mTimer.getDeltaTime());
22 else
23 Sleep( 50 );
24
25 drawScene();
26 }
27 }
28 return ( int )msg.wParam;
29 }

 

 

In this way, Δ t is computed every frame and fed into the updateScene method so that the scene can be updated based on how much time has passed since the previous frame of animation. The implementation of the reset method is:
 
     
1 void GameTimer::reset()
2 {
3 __int64 currTime;
4 QueryPerformanceCounter((LARGE_INTEGER * ) & currTime);
5
6 mBaseTime = currTime;
7 mPrevTime = currTime;
8 mStopTime = 0 ;
9 mStopped = false ;
10 }
Some of the variables shown have not been discussed yet (see the next section). However, we see that this initializes mPrevTime to the current time when reset is called. It is important to do this because for the first frame of animation, there is no previous frame, and therefore, no previous time stamp t i1. Thus this value needs to be initialized in the reset method before the message loop starts.

4.3.4 Game Time

Another time measurement that can be useful is the amount of time that has elapsed since the application start, not counting paused time; we will call this game time. The following situation shows how this could be useful. Suppose the player has 300 seconds to complete a level. When the level starts, we can get the time tstart, which is the time elapsed since the application started. Then after the level has started, we can check the time t since the application started every so often. If t tstart > 300s (see Figure 4.7) then the player has been in the level for over 300 seconds and loses. Obviously in this situation we do not want to count any time the game was paused against the player.

 
Figure 4.7: Computing the time since the level started. Note that we choose the application start time as the origin (zero), and measure time values relative to that frame of reference.

Another application of game time is when we want to animate a quantity as a function of time. For instance, suppose we wish to have a light orbit the scene as a function of time. Its position can be described by the parametric equations:

 

Here t represents time, and as t (time) increases, the coordinates of the light are updated so that it moves in a circle with radius 10 in the y = 20 plane. Chapter 5 shows a sample program that animates the camera by describing its position as a function of time. For this kind of animation, we also do not want to count paused time; see Figure 4.8.

 
Figure 4.8: If we paused at t1 and unpaused at t2, and counted paused time, then when we unpause, the position will jump abruptly from p(t1) to p(t2).

To implement game time, we use the following variables:

 
     
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;

 

As we saw in §4.3.3, mBaseTime is initialized to the current time when reset was called. We can think of this as the time when the application started. In most cases, you will only call reset once before the message loop, so mBaseTime stays constant throughout the application’s lifetime. The variable mPausedTime accumulates all the time that passes while we are paused. We need to accumulate this time, so we can subtract it from the total running time in order to not count paused time. The mStopTime variable gives us the time when the timer is stopped (paused); this is used to help us keep track of paused time.

Two important methods of the GameTimer class are stop and start. They should be called when the application is paused and unpaused, respectively, so that the GameTimer can keep track of paused time. The code comments explain the details of these two methods.

 
      
1 void GameTimer::stop()
2 {
3 // If we are already stopped, then don't do anything.
4 if ( ! mStopped )
5 {
6 __int64 currTime;
7 QueryPerformanceCounter((LARGE_INTEGER * ) & currTime);
8
9 // Otherwise, save the time we stopped at, and set
10 // the Boolean flag indicating the timer is stopped.
11 mStopTime = currTime;
12 mStopped = true ;
13 }
14 }
15 void GameTimer::start()
16 {
17 __int64 startTime;
18 QueryPerformanceCounter((LARGE_INTEGER * ) & startTime);
19
20 // Accumulate the time elapsed between stop and start pairs.
21 //
22 // |<-------d------->|
23 // ---------------*-----------------*------------> time
24 // mStopTime startTime
25
26 // If we are resuming the timer from a stopped state...
27 if ( mStopped )
28 {
29 // then accumulate the paused time.
30 mPausedTime += (startTime - mStopTime);
31
32 // Since we are starting the timer back up, the current
33 // previous time is not valid, as it occurred while paused.
34 // So reset it to the current time.
35 mPrevTime = startTime;
36
37 // no longer stopped...
38 mStopTime = 0 ;
39 mStopped = false ;
40 }
41 }
Finally, the getGameTime member function, which returns the time elapsed since reset was called not counting paused time, is implemented as follows:
 
      
1 float GameTimer::getGameTime() const
2 {
3 // If we are stopped, do not count the time that has passed since
4 // we stopped.
5 //
6 // ----*---------------*------------------------------*------> time
7 // mBaseTime mStopTime mCurrTime
8
9 if ( mStopped )
10 {
11 return ( float )((mStopTime - mBaseTime) * mSecondsPerCount);
12 }
13
14 // The distance mCurrTime - mBaseTime includes paused time,
15 // which we do not want to count. To correct this, we can subtract
16 // the paused time from mCurrTime:
17 //
18 // (mCurrTime - mPausedTime) - mBaseTime
19 //
20 // |<-------d------->|
21 // ----*---------------*-----------------*------------*------> time
22 // mBaseTime mStopTime startTime mCurrTime
23 else
24 {
25 return ( float )
26 (((mCurrTime - mPausedTime) - mBaseTime) * mSecondsPerCount);
27 }
28 }

 

转载于:https://www.cnblogs.com/seanyou/archive/2010/09/20/1831422.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值