INTERFACEAnimate ; <* PRAGMA LL *> IMPORT RefList, MG, MGV, R2, R2Path, Thread; TYPE TimeFunction = OBJECT METHODS <* LL = {v.mu} *> map (t: REAL): REAL; (* map range [0.0, 1.0] onto itself *) END; (* A TimeFunction controls the rate of change within an animation. An animation moves uniformly through the values 0.0 to 1.0 for t. TimeFunction.map adjusts t to have a different behaviour. *) TimeDiscrete <: TimeDiscretePublic; TimeDiscretePublic = TimeFunction OBJECT <* LL = {v.mu} *> values: REF ARRAY OF RECORD step : REAL; value: REAL END; END; (* A TimeDiscrete.map(t) returns TimeDiscrete.values[i].value where i is the first TDiscrete.values[i].step >= t. *) TimeStep <: TimeStepPublic; TimeStepPublic = TimeFunction OBJECT <* LL = {v.mu} *> steps := 1; END;
A TimeState.map(t) returns FLOOR(t * steps) / steps
VAR tfZero : TimeFunction; (* tfLinear.map(t) returns 0.0 *) tfOne : TimeFunction; (* tfLinear.map(t) returns 1.0 *) tfLinear : TimeFunction; (* tfLinear.map(t) returns t *) tfInverse: TimeFunction; (* tfInverse.map(t) returns 1.0 - t *) TYPE T = MGV.AnimateT; REVEAL T <: TPublic; TYPE TPublic = OBJECT <* LL = {v.mu} *> tf: TimeFunction := NIL; (* NIL => tfLinear *) METHODS <* LL < v.mu *> init (tf: TimeFunction := NIL): T; (* Default assigns tfLinear to "tf" if NIL *) <* LL <= VBT.mu *> start(v: MG.V); end(v: MG.V); length(v: MG.V; mg: MG.T): INTEGER; (* number of animation steps *) <* LL <= VBT.mu *> doStep (time, timePrev: REAL; v: MG.V; mg: MG.T); (* Do a step in the animation from "timePrev" to "time". "time" and "timePrev"have already been transformed by self.tf.map. "time" may be greater than, equal to or less than "timePrev" *) END; TYPE Composite = OBJECT t: T; mg: MG.T END; TYPE Group = MGV.AnimateGroup; REVEAL Group <: GroupPublic; TYPE GroupPublic = T OBJECT elems: RefList.T := NIL; (* RefList of "Composite"s *) METHODS (* must call init method *) <* LL = {v.mu} *> add(v: MG.V; composite: Composite); remove(v: MG.V; composite: Composite); iterate(gi: GroupIterator): BOOLEAN; END; TYPE GroupIterator = OBJECT v: MG.V; METHODS proc(comp: Composite): BOOLEAN; END; <* LL < v.mu for following procedures *> PROCEDURE AddToGroup(g: Group; v: MG.V; comp: Composite); PROCEDURE RemoveFromGroup(g: Group; v: MG.V; comp: Composite); PROCEDURE IterateGroup(g: Group; v: MG.V; iter: GroupIterator): BOOLEAN; TYPE (* Animation effects *) Linear <: LinearPublic; LinearPublic = T OBJECT <* LL = {v.mu} *> (* READONLY use methods to set *) vector: R2.T; METHODS (* must call init method *) <* LL = {v.mu} *> setVector(v: MG.V; READONLY vector: R2.T) END; Rotate <: RotatePublic; RotatePublic = T OBJECT <* LL = {v.mu} *> (* READONLY use methods to set *) origin: R2.T; angle: REAL; (* degrees *) METHODS (* must call init method *) <* LL = {v.mu} *> setRotate(v: MG.V; READONLY origin: R2.T; angle: REAL); END; (* rotate "angle" degrees around "origin" clockwise *) Scale <: ScalePublic; ScalePublic = T OBJECT <* LL = {v.mu} *> (* READONLY use methods to set *) wrt: R2.T; factor: R2.T; METHODS (* must call init method *) <* LL = {v.mu} *> setScale(v: MG.V; READONLY wrt, factor: R2.T); END; (* "wrt" remains constant in the animation *) Path = R2Path.T; Translate <: TranslatePublic; TranslatePublic = T OBJECT <* LL = {v.mu} *> (* READONLY use methods to set *) path: Path; METHODS (* must call init method *) <* LL = {v.mu} *> setTranslate(v: MG.V; path: Path); END; Weight <: WeightPublic; WeightPublic = T OBJECT <* LL = {v.mu} *> (* READONLY use methods to set *) delta: REAL; METHODS (* must call init method *) <* LL = {v.mu} *> setWeightDelta(v: MG.V; delta: REAL); END; Highlight <: HighlightPublic; HighlightPublic = T OBJECT (* must call init method *) END; (* length = 30 *) Visibility <: VisibilityPublic; VisibilityPublic = T OBJECT (* must call init method *) END; (* length = 30 *) PROCEDURE Do(t: T; mg: MG.T; v: MG.V; duration := 1.0) RAISES {Thread.Alerted}; (* call t.doStep(t.tf.map(time), v, mg) where time increases (roughly linearly) from 0.0 to 1.0 so that the animation takes duration seconds. If "duration" = 0.0 then only the last scene (time = 1.0) of the animation occurs. t.start is called at the start of the animation and t.end is called at the end. Thread.Alerted may be called before or after any frame in the animation. LL <= VBT.mu *) PROCEDURE Undo(t: T; mg: MG.T; v: MG.V; duration := 1.0) RAISES {Thread.Alerted}; (* call t.doStep(t.tf.map(time), v, mg) where time decreases (roughly linearly) from 1.0 to 0.0 so that the animation takes duration seconds. If "duration" = 0.0 then only the last scene (time = 0.0) of the animation occurs. t.start is called at the start of the animation and t.end is called at the end. Thread.Alerted may be called before or after any frame in the animation. LL <= VBT.mu *)Animations are kept semi-synchronous by maintaining a global
animation
time
based on real time. Animation time is the real time that has
taken place, scaled by the current animation speed. Each active animation
calls ATime
to determine the current time of the animation to display.
Clients should call ResetAnimationTime at appropriate intervals between
animations. A call to Animate.Do/Undo without an intervening ResetATime
will only display the final scene of the animation.
Calling ResetATime or SetDuration during an animation will have undefined results.
<* LL = Arbitrary *> PROCEDURE ATime(): REAL; (* Returns the current animation time. *) PROCEDURE ResetATime(); (* Resets animation time to 0. *) PROCEDURE SetDuration(seconds: REAL); (* Makes 1 second of animation time last "seconds" seconds of real time *) END Animate.