Fixing reverse animation with ranges
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/update/animation/scene-graph-animation.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath> // fmod
23
24 // INTERNAL INCLUDES
25 #include <dali/internal/common/memory-pool-object-allocator.h>
26 #include <dali/internal/render/common/performance-monitor.h>
27
28 namespace //Unnamed namespace
29 {
30 //Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
31 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
32
33 inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
34 {
35   if( elapsed > playRangeSeconds.y )
36   {
37     elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
38   }
39   else if( elapsed < playRangeSeconds.x )
40   {
41     elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
42   }
43 }
44
45 }
46
47 namespace Dali
48 {
49
50 namespace Internal
51 {
52
53 namespace SceneGraph
54 {
55
56 Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, EndAction endAction, EndAction disconnectAction )
57 {
58   return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
59 }
60
61 Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
62 : mDurationSeconds(durationSeconds),
63   mSpeedFactor( speedFactor ),
64   mEndAction(endAction),
65   mDisconnectAction(disconnectAction),
66   mState(Stopped),
67   mElapsedSeconds(playRange.x*mDurationSeconds),
68   mPlayedCount(0),
69   mLoopCount(loopCount),
70   mCurrentLoop(0),
71   mPlayRange( playRange )
72 {
73 }
74
75 Animation::~Animation()
76 {
77 }
78
79 void Animation::operator delete( void* ptr )
80 {
81   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
82 }
83
84 void Animation::SetDuration(float durationSeconds)
85 {
86   mDurationSeconds = durationSeconds;
87 }
88
89 void Animation::SetLoopCount(int loopCount)
90 {
91   mLoopCount = loopCount;
92   mCurrentLoop = 0;
93 }
94
95 void Animation::SetEndAction(Dali::Animation::EndAction action)
96 {
97   mEndAction = action;
98 }
99
100 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
101 {
102   if ( mDisconnectAction != action )
103   {
104     mDisconnectAction = action;
105
106     for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
107     {
108       (*iter)->SetDisconnectAction( action );
109     }
110   }
111 }
112
113 void Animation::SetPlayRange( const Vector2& range )
114 {
115   mPlayRange = range;
116
117   // Make sure mElapsedSeconds is within the new range
118
119   if( mState == Stopped )
120   {
121     // Ensure that the animation starts at the right place
122     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
123   }
124   else
125   {
126     // If already past the end of the range, but before end of duration, then clamp will
127     // ensure that the animation stops on the next update.
128     // If not yet at the start of the range, clamping will jump to the start
129     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
130   }
131 }
132
133 void Animation::Play()
134 {
135   mState = Playing;
136
137   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
138   {
139     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
140   }
141
142   SetAnimatorsActive( true );
143
144   mCurrentLoop = 0;
145 }
146
147 void Animation::PlayFrom( float progress )
148 {
149   // If the animation is already playing this has no effect
150   // Progress is guaranteed to be in range.
151   if( mState != Playing )
152   {
153     mElapsedSeconds = progress * mDurationSeconds;
154     mState = Playing;
155
156     SetAnimatorsActive( true );
157   }
158 }
159
160 void Animation::Pause()
161 {
162   if (mState == Playing)
163   {
164     mState = Paused;
165   }
166 }
167
168 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
169 {
170   if( action == Dali::Animation::BakeFinal )
171   {
172     if( mSpeedFactor > 0.0f )
173     {
174       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
175     }
176     else
177     {
178       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
179     }
180   }
181
182   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
183 }
184
185 void Animation::SetAnimatorsActive( bool active )
186 {
187   for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
188   {
189     (*iter)->SetActive( active );
190   }
191 }
192
193 bool Animation::Stop(BufferIndex bufferIndex)
194 {
195   bool animationFinished(false);
196
197   if (mState == Playing || mState == Paused)
198   {
199     animationFinished = true; // The actor-thread should be notified of this
200
201     if( mEndAction != Dali::Animation::Discard )
202     {
203       Bake( bufferIndex, mEndAction );
204
205       // Animators are automatically set to inactive in Bake
206     }
207     else
208     {
209       SetAnimatorsActive( false );
210     }
211
212     // The animation has now been played to completion
213     ++mPlayedCount;
214     mCurrentLoop = 0;
215   }
216
217   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
218   mState = Stopped;
219
220   return animationFinished;
221 }
222
223 void Animation::OnDestroy(BufferIndex bufferIndex)
224 {
225   if (mState == Playing || mState == Paused)
226   {
227     if (mEndAction != Dali::Animation::Discard)
228     {
229       Bake( bufferIndex, mEndAction );
230
231       // Animators are automatically set to inactive in Bake
232     }
233     else
234     {
235       SetAnimatorsActive( false );
236     }
237   }
238
239   mState = Destroyed;
240 }
241
242 void Animation::AddAnimator( AnimatorBase* animator )
243 {
244   animator->ConnectToSceneGraph();
245   animator->SetDisconnectAction( mDisconnectAction );
246
247   mAnimators.PushBack( animator );
248 }
249
250 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished )
251 {
252   looped = false;
253   finished = false;
254
255   if (mState == Stopped || mState == Destroyed)
256   {
257     // Short circuit when animation isn't running
258     return;
259   }
260
261   // The animation must still be applied when Paused/Stopping
262   if (mState == Playing)
263   {
264     mElapsedSeconds += elapsedSeconds * mSpeedFactor;
265   }
266
267   Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
268
269   if( 0 == mLoopCount )
270   {
271     // loop forever
272     WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
273
274     UpdateAnimators(bufferIndex, false, false);
275
276     // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
277   }
278   else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
279   {
280     // looping
281     looped =  (mState == Playing                                                 &&
282                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
283                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
284
285     WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
286
287     UpdateAnimators(bufferIndex, false, false);
288
289     if(looped)
290     {
291       ++mCurrentLoop;
292       // don't increment mPlayedCount until the finished final loop
293     }
294   }
295   else
296   {
297     // playing once (and last mCurrentLoop)
298     finished = (mState == Playing                                                 &&
299                 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
300                  ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
301
302     // update with bake if finished
303     UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished);
304
305     if(finished)
306     {
307       // The animation has now been played to completion
308       ++mPlayedCount;
309
310       // loop iterations come to this else branch for their final iterations
311       if( mCurrentLoop < mLoopCount)
312       {
313         ++mCurrentLoop;
314         DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
315       }
316
317       mElapsedSeconds = playRangeSeconds.x;
318       mState = Stopped;
319     }
320   }
321 }
322
323 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
324 {
325   const Vector2 playRange( mPlayRange * mDurationSeconds );
326   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
327
328   //Loop through all animators
329   bool applied(true);
330   for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
331   {
332     AnimatorBase *animator = *iter;
333
334     if( animator->Orphan() )
335     {
336       //Remove animators whose PropertyOwner has been destroyed
337       iter = mAnimators.Erase(iter);
338     }
339     else
340     {
341       if( animator->IsEnabled() )
342       {
343         const float initialDelay( animator->GetInitialDelay() );
344         if( elapsedSecondsClamped >= initialDelay )
345         {
346           // Calculate a progress specific to each individual animator
347           float progress(1.0f);
348           const float animatorDuration = animator->GetDuration();
349           if (animatorDuration > 0.0f) // animators can be "immediate"
350           {
351             progress = Clamp((elapsedSecondsClamped - initialDelay) / animatorDuration, 0.0f , 1.0f );
352           }
353           animator->Update(bufferIndex, progress, bake);
354         }
355         applied = true;
356       }
357       else
358       {
359         applied = false;
360       }
361
362       if ( animationFinished )
363       {
364         animator->SetActive( false );
365       }
366
367       if (applied)
368       {
369         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
370       }
371
372       ++iter;
373     }
374   }
375
376 }
377
378 } // namespace SceneGraph
379
380 } // namespace Internal
381
382 } // namespace Dali