anim.h
, which is automatically included when you include amulet.h
. Good examples of the use of animation constraints are in the files amulet/src/anim/testanimators.cc
, amulet/samples/circuit/circuit.cc
, amulet/samples/tree.cc
, and amulet/samples/space2.cc
.
Am_LEFT
and Am_TOP
slots of a graphical object, then whenever the object's position is changed from (L1, T1) to (L2, T2), the animation constraint will delay the change and smoothly interpolate Am_LEFT
from L1 to L2 and Am_TOP
from T1 to T2. Thus the animation happens once for each time the value is set (but see Section 6.10 for how to make the animation continuous).
Animation constraints are instances of the Am_Animator object. To add an animation constraint to an object, make an instance of Am_Animator and use Am_Animate_With to set it into each slot it should control. For instance, here is how a rectangle's position would be animated:
Am_Object my_animator = Am_Animator.Create (``my_animator'');
Am_Object moving_rectangle = Am_Rectangle.Create ()
.Set (Am_LEFT, Am_Animate_With (my_animator))
.Set (Am_TOP, Am_Animate_With (my_animator))
;
There are many different kinds of animator objects, and each kind has a number of parameters you can control. Also, you can access the low-level timer mechanism if you want to create a simple timer, or you need a new kind of animation constraint.http://www.cs.cmu.edu/~amulet/papers/animate.ps
6.3 Am_Time
To support animations portably across all platforms, the Gem level of Amulet exports the Am_Time class (defined in gdefs.h). Most Amulet users will not need to use the methods of Am_Time, and can just create Am_Times using an integer number of milliseconds, and set them into the appropriate slots. The interface to Am_Time is:class Am_Time {
Am_WRAPPER_DECL (Am_Time)
public:
Am_Time(); // defaults to 0 delta time
Am_Time (unsigned long milliseconds); // starting number of msec
static Am_Time Now(); // create a time object = to the current time
bool Is_Future () const; // is this time > Now()
bool Is_Past () const;
bool operator> (const Am_Time& other) const;
bool operator< (const Am_Time& other) const;
bool operator>= (const Am_Time& other) const;
bool operator<= (const Am_Time& other) const;
unsigned long Milliseconds() const; //return this time as msec
Am_Time operator- (unsigned long milliseconds) const;
Am_Time operator+ (unsigned long milliseconds) const;
Am_Time operator- (const Am_Time& other) const;
Am_Time operator+ (const Am_Time& other) const;
void operator+= (unsigned long milliseconds);
void operator-= (unsigned long milliseconds);
void operator+= (const Am_Time& other);
void operator-= (const Am_Time& other);
bool Zero() const; // is the delta time 0? (uninitialized time test)
};
ostream& operator<< (ostream& os, const Am_Time& time);
//returns the current time and date as a string, like
// "Fri Jan 17 16:03:55 EST 1997\n".
extern Am_String Am_Get_Time_And_Date();
6.4 General Interface
Animation constraints internally are subclasses of Am_Constraint
, but the interface to the programmer is more like graphical objects or interactors. Each Animator is represented by an Amulet Object, so you control the animators by setting slots. Once the animator object is configured, you attach it to a slot of an object using the function:Am_Constraint* Am_Animate_With (const Am_Object& animator);
which returns a constraint that you set into one or more slots. The animator object can be retrieved from a slot using:Am_Object Am_Get_Animator (Am_Object obj, Am_Slot_Key key);
The first routine gets the animator object from a slot of an object, and the second gets it from an Am_Constraint object, which is not likely to be needed much, since the constraints used for the animators are generally not accessed by programmers.
Am_Object Am_Get_Animator (Am_Constraint* constraint);
Am_LEFT
and Am_TOP
slots of moving_rectangle
, setting either of those slots will trigger the animation. Any time a new value is put in one of these slots, the animation is triggered, so a ``regular'' (formula or web) constraint on Am_LEFT
or Am_TOP
can also trigger the animation. For example, all of the following will cause moving_rectangle
to animate:moving_rectangle.Set(Am_LEFT, 300).Set(Am_TOP, 500);
moving_rectangle.Set(Am_LEFT, left_formula);
The result of the animation being triggered is that instead of the slot value jumping to the new value that is set into the slot, the slot instead takes on a series of values interpolated from the old to the new values. In the case of the constraint, this will cause the moving_rectangle to ``lag behind'' the constrained value, and travel at a fixed maximum rate (which is controllable by the programmer, see Section 6.7).
Because the animation constraints work by setting the slot to a sequence of values, constraints that depend on the slot will be continuously re-evaluated as well. For example, in the
amulet/samples/tree.cc
sample program, animation constraints are put on the position slots of nodes. The end-points of the lines are constrained to the nodes, so the lines move smoothly as well.6.5 Starting and Stopping Animations
There are two routines available to explicitly stop an animation. Am_Stop_Animator
causes the animation to stop and the slot value to immediately jump to its final value. Am_Abort_Animator
on the other hand, causes the animation to stop and the value in the slot to stay at its current value, no matter what it is. Thus using Am_Abort_Animator
can leave the slot with a value different from that the slot was originally set to.void Am_Abort_Animator (Am_Object interp);
void Am_Stop_Animator (Am_Object interp);
Normally the animators are automatically aborted when a new value is set. Thus, if the animation is busy setting the slot to a sequence of values, and a new value is set into the slot, the old animation will abort and a new animation will immediately be started towards the newly set value. This behavior can be changed using the Am_INTERRUPTIBLE
slot (see Section 6.9).
To explicitly start an animation, use
Am_Start_Animator
. value1
can be supplied to specify the original value for the animation to start at, and value2
can be supplied to specify the target or final value for the animation.void Am_Start_Animator (Am_Object interp,Am_Value value1 = Am_No_Value,
Am_Value value2 = Am_No_Value);
6.6 Turning On and Off Animations
Animators are normally on and operating whenever attached to a slot. There are a number of mechanisms for controlling when the animations operate more precisely.Am_ACTIVE
slot of the animator object. If Am_ACTIVE
is false
, the animator will not be started, and the slot Set will operate normally (jump to the new value). If Am_ACTIVE
is true
(the default), then the animation will cause the slot value to change smoothly. Setting the Am_ACTIVE
slot while the animation is running will not abort the animator. Use Am_Abort_Animator
(see below) for that.my_animator.Set(Am_ACTIVE, false); //this animator now won't run
Sometimes it is useful to Set a slot which contains an animation, but not have the animation operate. In this case, you can use the special Set flag Am_NO_ANIMATION
. If the slot has an attached animation, that animation is disabled for this one Set, and the slot value will jump to the specified value. Subsequent Set's will not be affected (unless they specify Am_NO_ANIMATION
as well):moving_rectangle.Set(Am_LEFT, 100, Am_NO_ANIMATION); //no animation
If a slot does not have an animator attached, then the Am_NO_ANIMATION
does nothing, so it is safe to use this flag whereever it might be important.Am_WITH_ANIMATION
set flag can be used. If there is an animator on the slot, then the animation will be run for this set ignoring the value of the Am_ACTIVE
slot of the animator. If the slot does not have an animator attached, then the Am_WITH_ANIMATION
flag is ignored.my_animator.Set(Am_ACTIVE, false); //this animator now will NOT normally run
Am_Object moving_rectangle = Am_Rectangle.Create ()
.Set (Am_LEFT, Am_Animate_With (my_animator))
.Set (Am_TOP, Am_Animate_With (my_animator));
moving_rectangle.Set(Am_LEFT, 300); //NO animation
moving_rectangle.Set(Am_LEFT, 122, Am_WITH_ANIMATION); //animates
6.7 Parameters of all Animators
Animation constraints take a number of parameters, which are slots in the instance of Am_Animator you create. Some of the parameters are global to all animators, and others are specific to one type of animator. This section lists the slots common to all animators:
Am_VELOCITY
.
Am_VELOCITY
or Am_DURATION
to set and the other is calculated from the one you set. The default is a fixed velocity of 200 pixels per second, with the duration depending on how far the values are apart.
src/anim/testanimators.cc
.
The current animators supplied in the library are as follows. These are organized approximately in order of usefulness (some of the later ones are mainly interesting for demos.)
6.8.1 Am_Animator
Am_Animator does a linear interpolation of any number of numeric slots (integers or floats). This is normally used for positions. Set the same animator into multiple slots to insure that all the values are animated at the same rate. For example, the dog is first constrained with regular formula constraints to be behind the horse, and then an animation constraint is added to the position slots. doganimation = Am_Animator.Create (``dog_position'');
dog.Set(Am_TOP, same_as_horse_bottom)
.Set(Am_LEFT, behind_horse)
.Set (Am_LEFT, Am_Animate_With (doganimation))
.Set (Am_TOP, Am_Animate_With (doganimation))
6.8.2 Am_Style_Animator
Am_Style_Animator linear interpolation of one Am_Style slot, and looks at changes in the color and line thickness parts of the style (other parts of the styles are ignored by the animator). The color can be animated in two ways: either through the Red-Green-Blue (RGB) color space or through the Hue-Saturation-Value (HSV) color space. The choice is controlled by the boolean slot Am_RGB_COLOR. Animations through colors spaces basically draw a line through the three dimensional color cube from the initial value to the final value, and animate along that line. Animations in the RGB color space often go through grey in the middle, which isn't as attactive as HSV animations in some cases. On the other hand, going from a color to black or white (fading the color in or out) looks better in RGB. The default for Am_RGB_COLOR is true.fetch_color
and fetch_line
). These are declared to be Multi_Constraint
so the constraints to not disappear if the slot is explicitly set. An Am_Style_Animator
is attached to the each slot so the colors and line-thickness will change smootly over 2000 milliseconds (2 seconds). The fill style will animate through the HSV color space (Am_RGB_COLOR
is false
) and the line animator will go through RGB color space (Am_RGB_COLOR
is the default = true
).Am_Object Color_Changer = Am_Rectangle.Create ()
.Set (Am_LEFT, 175)
.Set (Am_TOP, 125)
.Set (Am_WIDTH, 50)
.Set (Am_HEIGHT, 50)
.Set (Am_FILL_STYLE, fetch_color.Multi_Constraint())
.Set (Am_FILL_STYLE,
Am_Animate_With (Am_Style_Animator.Create (``fill_style_anim'')
.Set (Am_RGB_COLOR, false)
.Set (Am_DURATION, Am_Time (2000))))
.Set (Am_LINE_STYLE, Am_Black)
.Set (Am_LINE_STYLE, fetch_line.Multi_Constraint())
.Set (Am_LINE_STYLE,
Am_Animate_With (Am_Style_Animator.Create (``line_style_anim'')
.Set (Am_DURATION, Am_Time (2000))))
6.8.3 Am_Visible_Animator
Am_Visible_Animator
reacts to changes in Am_VISIBLE
by flying an object onscreen or offscreen, or by shrinking and growing the object. Unlike other animators, the Am_Visible_Animator
modifies multiple slots, not just the one it is attached to. For example, even though the animator goes in the Am_VISIBLE
slot, it can also modifiy the position and size slots of the object. (Amulet constraints can perform arbitrary side effects.) See src/anim/testanimators.cc
for examples.Am_Visible_Animator
, you can even use more than one effect at the same time, just by setting more than one of the controlling variables. See also the Am_Fly_Apart_Animator (Section 6.8.4) which is also attached to the Am_VISIBLE
slot.
6.8.3.1 Fading out
To have objects fade out they must be part of a Am_Fade_Group
(see Section 4.8.4 in the Opal manual). The Am_Visible_Animator
then sets the Am_VALUE
of the fade group with a series of values to make the group fade out or fade back in. To get the Am_Visible_Animator
to use fading, set the Am_USE_FADING slot of the animator to true. For example:
fade_group = Am_Fade_Group.Create (``Fade_Group'')
.Set (Am_WIDTH, Am_From_Owner(Am_WIDTH))
.Set (Am_HEIGHT, Am_From_Owner(Am_HEIGHT))
.Set (Am_VALUE, 0) //fully visible to start
.Set (Am_VISIBLE, Am_Animate_With (Am_Visible_Animator.Create(``fade'')
.Set (Am_USE_FADING, true)));
6.8.3.2 Fly Offscreen
Another option for changing visibility is for objects to fly offscreen. This is controlled by setting the animator's Am_LEFT
and/or Am_TOP
slots. If these slots contain integer values, then the object will be moved to those values when it becomes invisible, and moved from those values to the object's real position when it become visible. (The default values for these slots of the animator are Am_No_Value
.) For example, to have the object fly offscreen to the upper right, you might use:obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
.Set (Am_LEFT, 200)
.Set (Am_TOP, -200)));
To have the object just fly vertically, just set the Am_TOP and leave the Am_LEFT as Am_No_Value:obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
.Set (Am_TOP, -200)));
The position to move to (just like any other slot) can be computed by a constraint. For example, in the samples/checkers.cc sample program, the black pieces go down and the white pieces go up when they become invisible. Note that whether the piece is visible or not is also computed by a formula in this example. The animation is triggered no matter how the Am_VISIBLE
slot changes.Am_Define_Formula(int, black_at_bottom) {
if ((bool)self.Get_Object(Am_OPERATES_ON).Get(BLACK_PLAYER))
return 550; //at the bottom
else return -1000; //at the top
}
Am_Object visible_animator = Am_Visible_Animator.Create()
.Set (Am_LEFT, -1000)
.Set (Am_TOP, black_at_bottom)
.Set (Am_ACTIVE, visible_animator_active)
.Set (Am_DURATION, Am_Time(500))
;
Am_Object Piece = Am_Group.Create (``piece'')
...
.Set (Am_VISIBLE, piece_visible)
.Set (Am_VISIBLE, Am_Animate_With (visible_animator));
Another use for this effect might be to have the object move out from some source, for example a duplicated object could move from the original.6.8.3.3 Shrinking
Another option for changing visibility is for objects shrink and grow. This is controlled by setting the animator's Am_WIDTH
and/or Am_HEIGHT
slots. If these slots contain integer values, then the object will be shrink to those values when it becomes invisible, and grow from those values to the object's real size when it become visible. (The default values for these slots of the animator are Am_No_Value
.) For example, to have the object shrink to zero height, you might use:obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
.Set (Am_HEIGHT, 0)));
6.8.4 Am_Fly_Apart_Animator
The Am_Fly_Apart_Animator
reacts to changes in Am_VISIBLE
by simulating breaking the object up into parts and flying the parts offscreen (or onscreen, depending on whether the object is going invisible or visible). This is more special-purpose than the Am_Visible_Animator
. The effect was inspired by the way objects become invisible in the Alice system (see http://alice.virginia.edu)
The
Am_Fly_Apart_Animator
does not take any extra parameters, and it only is effective on groups.group3.Set(Am_VISIBLE, Am_Animate_With(Am_Fly_Apart_Animator.Create()));
6.8.5 Am_Blink_Animator
The Am_Blink_Animator alternates a slot between two values (of any type). Specify the two values in the Am_VALUE_1
and Am_VALUE_2
slots of the animator (which default to true
and false
). Unlike other animators, the blink animator by default runs forever, once it starts. To stop it, call Am_Abort_Animator()
. A typical use is to blink an object, by putting it into the Am_VISIBLE
slot:obj.Set (Am_VISIBLE, Am_Animate_With (Am_Blink_Animator.Create()));
Another example is to change the color of an object:obj.Set (Am_FILL_STYLE, Am_Blue)
.Set (Am_FILL_STYLE, Am_Animate_With (Am_Blink_Animator.Create()
.Set(Am_VALUE_1, Am_Blue)
.Set(Am_VALUE_2, Am_Green)));
6.8.6 Am_Through_List_Animator
Am_Through_List_Animator
iterates a slot through a list of values. This is especially useful on the Am_IMAGE
slot of an Am_Bitmap object, to animate through a sequence of images, but it can be used for any slot. For example, you might want an object to take on a specific set of colors.Am_LIST_OF_VALUES
slot. For example, the walking eye in src/anim/testanimators uses a formula in the Am_LIST_OF_VALUES
to pick a different list of images depending on whether the eye is walking left or right. The WALKER
is a different animator that is moving the eye's position. This also uses the command Am_Animation_Wrap_Command
to make the animator wrap around when it reaches its final value instead of stopping (see Section 6.10).
Am_Value_List eye_walking_right, eye_walking_left;
static void init_eye () {
int i;
for (i = 0; i < 6; i++)
eye_walking_right.Add (read_pixmap(pixmapfilename[i]));
for (; i < 12; i++)
eye_walking_left.Add (read_pixmap(pixmapfilename[i]));
}
Am_Define_Value_List_Formula(which_way) {
int x_offset = self.Get_Object(Am_OPERATES_ON)
.Get_Object (WALKER).Get_Object(Am_COMMAND).Get(Am_X_OFFSET);
if (x_offset > 0) return eye_walking_right;
else return eye_walking_left;
}
obj.Set (Am_IMAGE, Am_Animate_With (Am_Through_List_Animator.Create()
.Set(Am_LIST_OF_VALUES, which_way)
.Set_Part (Am_COMMAND, Am_Animation_Wrap_Command.Create ())))
;
6.8.7 Am_Point_List_Animator
The Am_Point_List_Animator
animates changes to the Am_POINT_LIST
slot of a polygon. It checks to see whether the old point list and the new point list differ by a single point, and if so, it animates the point list so that extra point grows out of the center of the line between the two adjacent points. If the new or old point list is empty, then the animation is from the center of the object. If neither of these cases holds, then the extra points are all added at the end of the point list. There are no extra parameters to the Am_Point_List_Animator
.polygon = Am_Polygon.Create (``Polygon'')
.Set(Am_FILL_STYLE, Am_Red)
.Set(Am_POINT_LIST, triangle_point_list)
.Set(Am_POINT_LIST, Am_Animate_With (Am_Point_List_Animator.Create()));
6.8.8 Am_Stepping_Animator
Am_Stepping_Animator
interpolates numeric slots in quantized steps. The steps are set into the Am_SMALL_INCREMENT
slot. Currently, the Am_Stepping_Animator
can only handle one slot, unlike the Am_Animator
. hopper = Am_Arc.Create (``Hopper'')
.Set (Am_LEFT, Am_From_Sibling (MOVER, Am_LEFT))
.Set (Am_LEFT, Am_Animate_With (
Am_Stepping_Animator.Create (``Hopper_Interp'')
.Set (Am_REPEAT_DELAY, Am_Time(1000))
.Set (Am_SMALL_INCREMENT, 20)))
.Set (Am_TOP, 20)
.Set (Am_FILL_STYLE, Am_Green)
;
window.Add_Part (hopper);
6.8.9 Am_Exaggerated_Animator
Am_Exaggerated_Animator
does a linear interpolation of numeric slots, with anticipation (``windup'') at start of animation, and feedback (``wiggling'') at end. Thus, the Am_Exaggerated_Animator
has three phases. Currently, the Am_Exaggerated_Animator
can only handle a single slot, unlike the Am_Animator
. The parameters to the Am_Exaggerated_Animator
are:
Am_WINDUP_DELAY
: How long is spent doing the windup. Default=Am_Time(200)
Am_WINDUP_AMOUNT
: How far (in pixels) the windup goes. Default=5
Am_WIGGLE_DELAY
: How long the wiggling takes. Default=Am_Time(100)
Am_WIGGLE_AMOUNT
: How many pixels back and forth the wiggles go. Default = 2.
Am_WIGGLES
: How many times the object wiggles. Default=4.
wiggler = Am_Arc.Create (``Wiggler'')
.Set(Am_WIDTH, 20)
.Set(Am_HEIGHT, 20)
.Set (Am_TOP, Am_From_Sibling (MOVER, Am_TOP))
.Set (Am_TOP, Am_Animate_With (Am_Exaggerated_Animator.Create ()
.Set(Am_WIGGLE_AMOUNT, 4)))
.Set (Am_LEFT, 60);
This is controlled using the slot
Am_INTERRUPTIBLE
. If Am_INTERRUPTIBLE
is true
(the default) then animations are interrupted when new values arrive. If false
, then the new values are queued.
Am_DO_METHOD
of the command that is its Am_COMMAND
part. Thus, you can add commands to animation objects just as you would for widgets or interactors. This can be used to achieve various affects. In the samples/circuit/circuit.cc sample program, the command in the animation triggers the value of the next circuit element, so the values do not propagate until the animation finishes and the previous value arrives. There are two methods important in the command in animations:
Am_DO_METHOD
: Is executed if the animation finishes normally.
Am_ABORT_DO_METHOD
: Is executed if the animation is aborted or interrupted by a new value being set into the slot while the animation is running.
Am_Animation_Wrap_Command
: causes the value to restart. Thus, when the animation reaches the final value, the slots is set with the original value (with animations off), and then with the new value again. Thus, it will start over with the same animation.
Am_Animation_Bounce_Command
: causes the value to move from the final value back to the original value. Thus, it runs the animation backwards.
Am_Define_Method (Am_Timing_Function, float, Am_Linear_Timing,
(Am_Object interp, Am_Time t))
Am_Time total_time = interp.Get (Am_CURRENT_DURATION);
unsigned long t_ms = t.Milliseconds();
unsigned long total_ms = total_time.Milliseconds();
if (t_ms >= total_ms)
return 1.0;
else
return (float)((double)t_ms / (double)total_ms);
}The built in timing functions are:
Am_Linear_Timing
: this is the default.
Am_Slow_In_Slow_Out
: start slowly, speed up, and then slow down again at the end. This is controlled by the slots (of the animator):
Am_SHARPNESS_1
: Must be set. Controls the length of the initial slow down. A good value is 3.
Am_SHARPNESS_2
: Must be set. Controls the length of the final slow down. A good value is 3.
doganimation = Am_Animator.Create (``dog_position'')
.Set (Am_TIMING_FUNCTION, Am_Slow_In_Slow_Out)
.Add (Am_SHARPNESS_1, 3)
.Add (Am_SHARPNESS_2, 3)
;
Am_Delayed_Timing
: This waits and starts the animation only after a specified period has passed. This is used by the scrollbars to implement clicking on the arrows. It starts an animation as soon as you press on an arrow, using the Am_Delayed_Timing
timing function, and thus if you hold long enough, then the animation will start moving the indicator smoothly. When you release the mouse button, the animator is aborted, which leaves it at the current value. The controlling slot is:
Am_INITIAL_DELAY
which is an Am_Time
. The scroll bar uses Am_Time(500)
(which is 0.5 sec).
Am_Animator
objects may be used as simple timers. To implement this, create an instance of Am_Animator
whose Am_REPEAT_DELAY
is the desired time interval and whose Am_ANIMATION_METHOD
is the code to run at every timer tick, and then start it running with Am_Start_Animator
.For example:
Am_Define_Method (Am_Timer_Method, void, my_timer_tick,
(Am_Object my_timer, const Am_Time& elapsed_time)) {
// ... code to execute once every timer tick
}
Am_Object my_timer = Am_Animator.Create (``my_timer'')
.Set (Am_REPEAT_DELAY, Am_Time (100)) // every 100 milliseconds,
.Set (Am_ANIMATION_METHOD, my_timer_tick)// call this method
;
Am_Start_Animator (my_timer); // start the timer runningTo stop the timer, use
Am_Stop_Animator().
6.14 Creating a New Animator
If you want to animate a new type of value, and if the available parameterizations are not sufficient, then you can create your own type of Am_Animator object. The simplest way to customize an animator is to specify a new Path Function.Am_Define_Method (Am_Path_Function, Am_Value, my_path_function,
(Am_Object anim, Am_Value value1, Am_Value value2, float tau))
The path function takes the return value from the timing function called tau
(from 0.0 to 1.0) and calculates a value between value1 and value2 and returns it. The path function is set into the slot Am_PATH_FUNCTION
:my_animator = Am_Animator.Create()
.Set (Am_PATH_FUNCTION, my_path_function)
If you need more explicit control over the initialization and finalization of the animator, you can specify the following methods