Make the eased value a reference and add source code doc to Easing.h

This allows to apply multiple easings to the same variable.
This commit is contained in:
Tobias Loose
2013-12-07 10:05:26 +01:00
parent 2b0f50bb88
commit 4514725aba
5 changed files with 177 additions and 72 deletions
+3 -4
View File
@@ -30,11 +30,10 @@ public:
void changeFilter(int offset); void changeFilter(int offset);
MTGCard *getActiveCard(); MTGCard *getActiveCard();
//maintains the current rotation for fluid animations
private: private:
InOutQuadEasing mScrollOffset; //[-1,1]. defines the current rotation of the cards float mScrollOffset, mSlideOffset;
InOutQuadEasing mSlide; //[-1,1]. defines, the y-offset of the cards InOutQuadEasing mScrollEasing;
InOutQuadEasing mSlideEasing;
}; };
#endif //_CAROUSEL_DECK_VIEW_H_ #endif //_CAROUSEL_DECK_VIEW_H_
+114 -5
View File
@@ -1,33 +1,98 @@
#ifndef _EASING_H_ #ifndef _EASING_H_
#define _EASING_H_ #define _EASING_H_
//see http://www.gizma.com/easing/ for more information /*! \brief A class for eased floats for use in animations
*
* Animations often defines values a floating point variable
* should have at given times and interpolates between them to
* calculate the value of that variable at any given intermediate
* time step.
*
* The simplest case would be linear interpolation:
* Suppose a float "position" should be a at time = 0 and
* b at time = x. If the current time is y, the value of
* "position" is then a + (b-a)*y/x.
*
* This class defines the interface needed to implement different
* kind of interpolations with a common interface. See
* http://www.gizma.com/easing/ for more information for a few
* examples.
*/
class Easing class Easing
{ {
public: public:
/*! \brief The value at the start of an animation.
*
* start_value is undefined if no animation is running.
*/
float start_value; float start_value;
/*! \brief The amount the value should change during the animation.
*
* delta_value is undefined if no animation is running.
*/
float delta_value; float delta_value;
float value;
/*! \brief The current value.
*
* Use this member to read the value or to write the value without
* to animate intermediate values and. Make sure that the easing
* is not used once value is deleted.
*/
float& value;
/*! \brief The duration the animation should take
*
* It is not relevant which unit is used. This value is undefined
* if no animation is running.
*/
float duration; float duration;
/*! \brief The accumulated time the animation did run until now.
*
* It is not relevant which unit is used. This values is undefined
* if no animation is running.
*/
float time_acc; float time_acc;
/*! \brief Sets Easing::float to val and sets the animation as not running.
Easing(float val): start_value(val), delta_value(0), value(val), duration(0), time_acc(0) *
* Make sure that the easing is not used once value is deleted.
*
* \param val The value to ease
*/
Easing(float& val): start_value(val), delta_value(0), value(val), duration(0), time_acc(0)
{ {
} }
/*! \brief Resets the animation to its initial value
*
* This method does set the value to the start value and sets the passed time to 0.
* If there is no animation animation running, the resulting value is undefined.
*/
void reset() void reset()
{ {
value = start_value; value = start_value;
time_acc = 0; time_acc = 0;
} }
/*! \brief Finishes the animation immediately
*
* Sets the value to the animations target value and the passed time to the
* animations duration. If there is no animation running, the behaviour is undefined.
*/
void finish() void finish()
{ {
value = start_value + delta_value; value = start_value + delta_value;
time_acc = duration; time_acc = duration;
} }
/*! \brief Lets dt time pass
*
* Advances the animation by dt time units and updates the value accordingly.
*
* \val dt The amount of time to jump forward
*/
void update(float dt) void update(float dt)
{ {
if(duration > 0) if(duration > 0)
@@ -46,8 +111,22 @@ public:
} }
} }
/*! \brief Calculates the value from all other members.
*
* This method gets implemented by all specific easing classes.
*/
virtual void updateValue() = 0; virtual void updateValue() = 0;
/*! \brief Starts the animation.
*
* Starts the interpolation from the current value (now) to
* targetValue (in now + _duration).
*
* If the animation is currently running, it gets replaced.
*
* \param targetValue The value to interpolate to
* \param _duration The duration the interpolation should take
*/
void start(float targetValue, float _duration) void start(float targetValue, float _duration)
{ {
start_value = value; start_value = value;
@@ -56,23 +135,53 @@ public:
duration = _duration; duration = _duration;
} }
/*! \brief Translates the current value and the target value by delta_value
*
* This method is mainly used for trickery. Suppose there is one object in the
* middle of the screen that should move to the top until it is outside of the
* screen and gets replaced by a second one entering the screen from the lower
* side once the first one disappeared. This method can be used to simulate this
* effect with one animation by translating (i.e. moving) the animation from the
* top to the bottom:
*
* Object1 and object2 are the same object: object1 whose y position is bound to value
* To start the transition, use start(SCREEN_HEIGHT, desired time); Once the first
* object left the screen (i.e. object.y < 0), change objects appearance to object2
* and translate the easing by (SCREEN_HEIGHT).
*
* \param delta_value The change in start_value and value
*/
void translate(float delta_value) void translate(float delta_value)
{ {
start_value += delta_value; start_value += delta_value;
value += delta_value; value += delta_value;
} }
/*! \brief Returns if the passed time exceeds duration.
*
* If ther is no animation running, it is ensured that this is true.
*/
bool finished() bool finished()
{ {
return time_acc >= duration; return time_acc >= duration;
} }
}; };
/*! \brief This class defines an easing with quadratic acceleration and decceleration.
*/
class InOutQuadEasing : public Easing class InOutQuadEasing : public Easing
{ {
public: public:
InOutQuadEasing(float val): Easing(val) {} /*! \brief Calls Easing::Easing(val).
*
* \see Easing::Easing(float& val)
*/
InOutQuadEasing(float& val): Easing(val) {}
/*! \brief Implements the value calculation.
*
* \see Easing::updateValue()
*/
void updateValue() void updateValue()
{ {
float time_tmp = (time_acc * 2) / duration; float time_tmp = (time_acc * 2) / duration;
+6 -5
View File
@@ -27,11 +27,12 @@ public:
MTGCard *getActiveCard(); MTGCard *getActiveCard();
private: private:
int mCols; //the number of cols. there is a hidden col to the left and right for prefetching int mCols;
int mRows; //the number of rows. int mRows;
InOutQuadEasing mSlide; //[-1,1]. defines the y-offset of the cards float mScrollOffset, mSlideOffset;
InOutQuadEasing mScrollOffset; //[-1,1]. defines the x-offset of the cards InOutQuadEasing mScrollEasing;
int mCurrentSelection; //0 <= mCurrentSelection < mCards.size(). defines the current selected and thus upscaled card InOutQuadEasing mSlideEasing;
int mCurrentSelection;
}; };
#endif //_GRID_DECK_VIEW_H #endif //_GRID_DECK_VIEW_H
+26 -25
View File
@@ -3,7 +3,7 @@
const float CarouselDeckView::slide_animation_duration = 0.6f; const float CarouselDeckView::slide_animation_duration = 0.6f;
CarouselDeckView::CarouselDeckView() : CarouselDeckView::CarouselDeckView() :
DeckView(10), mScrollOffset(0), mSlide(0) DeckView(10), mScrollOffset(0), mSlideOffset(0), mScrollEasing(mScrollOffset), mSlideEasing(mSlideOffset)
{ {
} }
@@ -12,19 +12,19 @@ CarouselDeckView::~CarouselDeckView()
void CarouselDeckView::UpdateViewState(float dt) void CarouselDeckView::UpdateViewState(float dt)
{ {
if(!mScrollOffset.finished()) if(!mScrollEasing.finished())
{ {
mScrollOffset.update(dt); mScrollEasing.update(dt);
if(mScrollOffset.value <= -1.0f) if(mScrollOffset <= -1.0f)
{ {
mScrollOffset.translate(1.0f); mScrollEasing.translate(1.0f);
deck()->prev(); deck()->prev();
reloadIndexes(); reloadIndexes();
} }
else if(mScrollOffset.value >= 1.0f) else if(mScrollOffset >= 1.0f)
{ {
mScrollOffset.translate(-1.0f); mScrollEasing.translate(-1.0f);
deck()->next(); deck()->next();
reloadIndexes(); reloadIndexes();
} }
@@ -32,25 +32,25 @@ void CarouselDeckView::UpdateViewState(float dt)
dirtyCardPos = true; dirtyCardPos = true;
} }
if(!mSlide.finished()) if(!mSlideEasing.finished())
{ {
mSlide.update(dt); mSlideEasing.update(dt);
if(mSlide.value < mSlide.start_value) if(mSlideOffset < mSlideEasing.start_value)
{ {
//going downwards //going downwards
if(mSlide.value < -1.0f) if(mSlideOffset < -1.0f)
{ {
mSlide.translate(2.0f); mSlideEasing.translate(2.0f);
SwitchFilter(1); SwitchFilter(1);
} }
} }
else if(mSlide.value > mSlide.start_value) else if(mSlideOffset > mSlideEasing.start_value)
{ {
//upwards //upwards
if(mSlide.value > 1.0f) if(mSlideOffset > 1.0f)
{ {
mSlide.translate(-2.0f); mSlideEasing.translate(-2.0f);
SwitchFilter(-1); SwitchFilter(-1);
} }
} }
@@ -61,18 +61,19 @@ void CarouselDeckView::UpdateViewState(float dt)
void CarouselDeckView::UpdateCardPosition(CardRep &rep, int index) void CarouselDeckView::UpdateCardPosition(CardRep &rep, int index)
{ {
float rotation = mScrollOffset.value + 8 - index; float rotation = mScrollOffset + 8 - index;
rep.x = x_center + cos((rotation) * M_PI / 12) * (right_border - x_center); rep.x = x_center + cos((rotation) * M_PI / 12) * (right_border - x_center);
rep.scale = max_scale / 1.12f * cos((rep.x - x_center) * 1.5f / (right_border - x_center)) + 0.2f * max_scale * cos( rep.scale = max_scale / 1.12f * cos((rep.x - x_center) * 1.5f / (right_border - x_center)) + 0.2f * max_scale * cos(
cos((rep.x - x_center) * 0.15f / (right_border - x_center))); cos((rep.x - x_center) * 0.15f / (right_border - x_center)));
rep.y = (SCREEN_HEIGHT_F) / 2.0f + SCREEN_HEIGHT_F * mSlide.value * (rep.scale + 0.2f); rep.y = (SCREEN_HEIGHT_F) / 2.0f + SCREEN_HEIGHT_F * mSlideOffset * (rep.scale + 0.2f);
} }
void CarouselDeckView::Reset() void CarouselDeckView::Reset()
{ {
mScrollOffset = 0; mSlideEasing.finish();
mSlide = 0; mScrollEasing.finish();
DeckView::Reset(); DeckView::Reset();
} }
@@ -96,13 +97,13 @@ void CarouselDeckView::Render()
renderCard(4); renderCard(4);
renderCard(0); renderCard(0);
if (mScrollOffset.value < 0.5 && mScrollOffset.value > -0.5) if (mScrollOffset < 0.5 && mScrollOffset > -0.5)
{ {
renderCard(1); renderCard(1);
renderCard(3); renderCard(3);
renderCard(2); renderCard(2);
} }
else if (mScrollOffset.value < -0.5) else if (mScrollOffset < -0.5)
{ {
renderCard(3); renderCard(3);
renderCard(2); renderCard(2);
@@ -122,7 +123,7 @@ MTGCard * CarouselDeckView::Click(int x, int y)
last_user_activity = 0; last_user_activity = 0;
//clicked active card, and no animation is running //clicked active card, and no animation is running
if(mSlide.finished() && mScrollOffset.finished()) if(mSlideEasing.finished() && mScrollEasing.finished())
{ {
if(n == 2) if(n == 2)
{ {
@@ -140,7 +141,7 @@ MTGCard * CarouselDeckView::Click(int x, int y)
void CarouselDeckView::changePosition(int offset) void CarouselDeckView::changePosition(int offset)
{ {
mScrollOffset.start(offset, 0.3f*abs(offset)); mScrollEasing.start(offset, 0.3f*abs(offset));
last_user_activity = 0; last_user_activity = 0;
} }
@@ -149,11 +150,11 @@ void CarouselDeckView::changeFilter(int offset)
{ {
if(offset < 0) if(offset < 0)
{ {
mSlide.start(-2.0f, slide_animation_duration); mSlideEasing.start(-2.0f, slide_animation_duration);
} }
else if(offset > 0) else if(offset > 0)
{ {
mSlide.start(2.0f, slide_animation_duration); mSlideEasing.start(2.0f, slide_animation_duration);
} }
last_user_activity = 0; last_user_activity = 0;
} }
+28 -33
View File
@@ -6,7 +6,8 @@ const float GridDeckView::card_scale_small = 0.48f;
const float GridDeckView::card_scale_big = 0.7f; const float GridDeckView::card_scale_big = 0.7f;
GridDeckView::GridDeckView() GridDeckView::GridDeckView()
: DeckView(16), mCols(8), mRows(2), mSlide(0), mScrollOffset(0), mCurrentSelection(-1) : DeckView(16), mCols(8), mRows(2), mScrollOffset(0), mSlideOffset(0),
mScrollEasing(mScrollOffset), mSlideEasing(mSlideOffset), mCurrentSelection(-1)
{ {
} }
@@ -18,31 +19,33 @@ GridDeckView::~GridDeckView()
void GridDeckView::Reset() void GridDeckView::Reset()
{ {
mSlide.finish(); mSlideEasing.finish();
mScrollOffset.finish(); mScrollEasing.finish();
mCurrentSelection = 0; mCurrentSelection = 0;
DeckView::Reset();
} }
void GridDeckView::UpdateViewState(float dt) void GridDeckView::UpdateViewState(float dt)
{ {
if(!mScrollOffset.finished()) if(!mScrollEasing.finished())
{ {
mScrollOffset.update(dt); mScrollEasing.update(dt);
if(mScrollOffset.value <= -1.0f) if(mScrollOffset <= -1.0f)
{ {
deck()->next(); deck()->next();
deck()->next(); deck()->next();
mScrollOffset.translate(1.0f); mScrollEasing.translate(1.0f);
reloadIndexes(); reloadIndexes();
mCurrentSelection = (mCurrentSelection >= 6) ? mCurrentSelection - 2 : -1; mCurrentSelection = (mCurrentSelection >= 6) ? mCurrentSelection - 2 : -1;
} }
else if(mScrollOffset.value >= 1.0f) else if(mScrollOffset >= 1.0f)
{ {
deck()->prev(); deck()->prev();
deck()->prev(); deck()->prev();
mScrollOffset.translate(-1.0f); mScrollEasing.translate(-1.0f);
reloadIndexes(); reloadIndexes();
mCurrentSelection = (mCurrentSelection >= 0 && mCurrentSelection < 10) ? mCurrentSelection + 2 : -1; mCurrentSelection = (mCurrentSelection >= 0 && mCurrentSelection < 10) ? mCurrentSelection + 2 : -1;
} }
@@ -50,27 +53,19 @@ void GridDeckView::UpdateViewState(float dt)
dirtyCardPos = true; dirtyCardPos = true;
} }
if(!mSlide.finished()) if(!mSlideEasing.finished())
{ {
mSlide.update(dt); mSlideEasing.update(dt);
if(mSlide.value < mSlide.start_value) if(mSlideOffset < -1.0f)
{ {
//going downwards mSlideEasing.translate(2.0f);
if(mSlide.value < -1.0f) SwitchFilter(1);
{
mSlide.translate(2.0f);
SwitchFilter(1);
}
} }
else if(mSlide.value > mSlide.start_value) else if(mSlideOffset > 1.0f)
{ {
//upwards mSlideEasing.translate(-2.0f);
if(mSlide.value > 1.0f) SwitchFilter(-1);
{
mSlide.translate(-2.0f);
SwitchFilter(-1);
}
} }
dirtyCardPos = true; dirtyCardPos = true;
@@ -84,8 +79,8 @@ void GridDeckView::UpdateCardPosition(CardRep &rep, int index)
float colWidth = SCREEN_WIDTH_F / (mCols - 3); float colWidth = SCREEN_WIDTH_F / (mCols - 3);
float rowHeight = SCREEN_HEIGHT_F / mRows; float rowHeight = SCREEN_HEIGHT_F / mRows;
rep.x = (col + mScrollOffset.value) * colWidth - colWidth; rep.x = (col + mScrollOffset) * colWidth - colWidth;
rep.y = row * rowHeight + mSlide.value*SCREEN_HEIGHT + rowHeight/2; rep.y = row * rowHeight + mSlideOffset*SCREEN_HEIGHT + rowHeight/2;
if(mCurrentSelection == index) if(mCurrentSelection == index)
{ {
@@ -110,9 +105,9 @@ void GridDeckView::Render()
int firstVisibleCard = 2; int firstVisibleCard = 2;
int lastVisibleCard = mCards.size() - 2; int lastVisibleCard = mCards.size() - 2;
if(!mScrollOffset.finished()) if(!mScrollEasing.finished())
{ {
if(mScrollOffset.delta_value > 0){ if(mScrollEasing.delta_value > 0){
firstVisibleCard = 0; firstVisibleCard = 0;
} }
else else
@@ -152,7 +147,7 @@ MTGCard * GridDeckView::Click(int x, int y)
int n = getCardIndexNextTo(x, y); int n = getCardIndexNextTo(x, y);
last_user_activity = 0; last_user_activity = 0;
if(mScrollOffset.finished() && mSlide.finished()) if(mScrollEasing.finished() && mSlideEasing.finished())
{ //clicked and no animations running { //clicked and no animations running
if(n == mCurrentSelection) if(n == mCurrentSelection)
{ {
@@ -178,7 +173,7 @@ MTGCard * GridDeckView::Click(int x, int y)
void GridDeckView::changePosition(int offset) void GridDeckView::changePosition(int offset)
{ {
mScrollOffset.start(-1.0f * offset, scroll_animation_duration * abs(offset)); mScrollEasing.start(-1.0f * offset, scroll_animation_duration * abs(offset));
last_user_activity = 0; last_user_activity = 0;
} }
@@ -186,11 +181,11 @@ void GridDeckView::changeFilter(int offset)
{ {
if(offset < 0) if(offset < 0)
{ {
mSlide.start(-2.0f, slide_animation_duration); mSlideEasing.start(-2.0f, slide_animation_duration);
} }
else if(offset > 0) else if(offset > 0)
{ {
mSlide.start(2.0f, slide_animation_duration); mSlideEasing.start(2.0f, slide_animation_duration);
} }
last_user_activity = 0; last_user_activity = 0;
} }