1 Introduction

What

Flare is a library designed to allow quick and precise particle effect creations. It does not concern itself with displaying and only with the management and movement of particles. As such, it can easily be integrated into any existing or future application.

So What's Different? Why Should I Use This?

Usually in particle systems the way you control things is by describing various states on the emitter. Flare instead takes a much more fine-grained approach by allowing you to control individual particles and describe their movement and change over time.

How To

As Flare does not do any graphics on its own, you will first have to create something that can render graphics of some kind. Once you have that, you should subclass particle to create your own particle types. You should also implement methods on paint so that the particle can be drawn. The last thing that's needed is a method on call-with-translation that should perform a translation on your drawing target. That should settle everything in terms of hooking Flare up to your graphics system.

Next you will need to keep a scene instance into which your particles will be spawned and your animations will be played. You should then call paint on the scene object in your drawing loop and call start on the scene object when your application is initialised.

Finally, the exciting part: defining animation progressions. This happens through define-progression, which creates a globally named progression-definition. The body of a definition is made up of a series of intervals and animations. Each animation is composed of a selector and a bunch of changes. The selector decides to which units in the scene the changes should apply, and the changes decide what happens while the animation runs.

So let's look at a simple example. We'll create a particle that spins on a circle.

The first animation takes place from the 0th second to the 0th second, so it will only ever happen exactly once. It applies to the scene object itself and causes only a single change. This change enters a ring of size 100 with a single sphere child of size 10 into the scene.

The second animation takes place from the 0th second to infinity, so it'll go on as long as we keep the scene running. It applies to the children of the scene object (in this case the ring) and has a single change that will increase the angle by 360 every second.

Here sphere is the name of a simple particle. If you named your particle class something else, replace that with your own. In order to make the progression have an effect on our scene, we need to instantiate it and add it to the scene. You can instantiate a progression with progression-instance and add it to the scene by enter. Finally, we'll want to start it to see it in action.

(start (enter (progression-instance 'spinner) scene-instance))

We can change the example to be a bit more exciting by making it be a pendulum instead of a mere spinner. To do this we substitute a calc cahnge for the increase change:

(calc angle :to (+ 90 (* (sin (* clock 4)) 40)))

You can redefine the progression on the fly while it is still running and it should gracefully update on your screen. This should allow you to easily hack out rather complex animations. If you want to start again from the beginning, call reset on the progression instance.

An example implementation of all this including some nifty show progressions can be found in the flare-viewer system. You can also use this system yourself to play around with progressions for a bit.

Internals

Flare must handle two parts-- one the animation itself, and two the objects in the scene.

Scene Graph

The scene graph is made up of units and containers. Each unit must have a symbol for a name and each container has some data structure that can hold other objects. You can add and remove objects from a container using enter and leave respectively. If you need to iterate over the entirety of the scene graph referenced by a container, use map-container-tree or do-container-tree. Since containers should only hold units, you can use container-unit for further nesting levels. At the bottom of the entire thing should be a scene-graph, which allows you to retrieve any unit within it by its name using the unit function.

Building up on this are the scene and entity classes. The scene furthermore is paintable, animatable, and a clock, so that it can be drawn, animated, and can keep the time. An entity can always contain further entities, and aside from being paintable and animatable as well, it also always has a location vector so that we can move it around. All entities are always relative to their parent in their location. The transformation of this is achieved by a call-with-translation around each paint call.

Any animatable can keep a set of progression instances that will be automatically updated when the animatable is, and thus carry out their transformations for as long as they should be active.

Animation

The animation process is divided up into progression-definitions, progressions, animations, and changes. Progression-definitions merely exist as containers to instantiate and update progressions from. When their animations slot is set, they automatically update all instances.

A progression is more complicated however as it has to actually manage all the various animations, ensure they're run in the proper order and for just long enough, and it has to make it possible to rewind everything and reset the state of the scene to how things were before. Thus, progressions each have three vectors of present-animations, past-animations, and future-animations. Since they must also know whom to act upon, they keep a back-link to the animatable that they should modify.

Each update then the progression checks if any new animations now have their time to shine and if so, move them onto the present set. All the present animations then get ticked with their appropriate time-step and the animatable passed along. Finally, all animations that are past their prime and have exceeded their duration are moved off onto the past set. If there are no more future or present animations available, the progression knows it's done and stops itself.

Now, when a progression is reset, it shuffles all the past animations into the present set, reorders everything in reverse, and then resets each animation in sequence. Each animation then causes each change to be reset, hopefully one by one back-tracking all the changes that were made to the scene.

Thus by coupling a reset with an explicit clock-set and an update, a progression can be explicitly fast-forwarded or rewound to any particular point in its life-cycle and all the updates should stay consistent throughout.

When an animation is ticked, the actual entity selection process starts to happen. When the progression is defined, each animation must be supplied with a selector, which is compiled down to a chain of function calls that either expand or limit the set of applicable objects. For a list of the possible selector constraints, see compile-constraint. Each change of the animation is then ticked with each object that passed the constraint functions.

Finally, changes. Changes are divided into operations and tweens. Operations change the scene in some way by adding, removing, or moving entities around. They should not affect any internal state of entities and only affect the object tree. Tweens on the other hand do the opposite and should only modify the internal state of whatever object they are passed in their tick call, but should never change the object tree in any way.

Flare supplies several default changes, some of which have parsers (define-change-parser) so that they can actually be used from a progression definition. On the operations side, we have enter-operation and leave-operation used to add or remove objects respectively. The enter-operation is significant in the sense that it uses a creator function that can potentially instantiate arbitrarily many or arbitrarily deep structures. On the tween side we have several mixins to make the definition of your own tweens easier (range-tweenconstant-tweenslot-tweenaccessor-tween), from which two usable tweens arise, range-accessor-tween (set) and increase-accessor-tween (increase). There's one more notable tween to mention: call-accessor-tween (calc), which sets the slot according to a user-definable function.

Each change must know by itself how to reset the objects it acted upon to their initial state. The slot-tween and accessor-tween know how to do this by saving the original value of each object they act upon. Similarly, the enter and leave operations keep track of where they added or removed which objects.

Videos

Here are some demo videos made during various points in the development of Flare.

The formal specification of the body intervals is as follows:
body ::= interval*
interval ::= start [end] animation*
animation ::= (selector change*)
change ::= (change-type argument*)
start — An integer (in seconds) that represents the starting time
of the animations
end — An integer (or T, indicating infinity) that represents the
ending time of the animations
selector — A valid selector as per COMPILE-SELECTOR
change — A valid change as per COMPILE-CHANGE

If the END is not specified for a given interval, then the next START
is taken as the end. If no next start exists, then the end is T. In order
to allow brevity, multiple animations can be specified between two time
codes. This is then normalised into the strict form of
(START DURATION ANIMATION) as per PARSE-INTERVALS.

At time 0, a ring is created with name :ring and 20 bullets of size 2 as
its children. It is entered into the scene-graph. Then from time 0 to 8,
the ring’s size is increased by 2 every second. Simultaneously from time
0 to 20 the ring’s angle is increased to 1000, eased by the quad-in-out
interpolation and the ring’s children (the 20 bullets) increase in size
to 50. At time 20, the ring is removed from the scene-graph again.

Note that the current time in the clock must not necessarily be 100% accurate.
In order to get perfectly accurate current time of the clock, you must call UPDATE
on it before retrieving its current time value with CLOCK.

Deregisters the unit with the scene-graph, making it accessible by its name.
Any unit that leaves from any part of the scene-graph must be deregistered by this
function. This should happen automatically provided you use the CONTAINER-UNIT class
for containers inside the scene-graph. Thus you need not call this function unless
you implement your own container.

Registers the unit with the scene-graph, making it accessible by its name.
Any unit that is entered into any part of the scene-graph must be registered by this
function. This should happen automatically provided you use the CONTAINER-UNIT class
for containers inside the scene-graph. Thus you need not call this function unless
you implement your own container.

When an animation is ticked, the following happens:
1. The selector is called with the given animatable and a function
2. Once the selector calls its function with a matching object,
each change in the animation is ticked with the matching object
as argument.

When an animation is reset, each change in the animation is also
reset. This should cause whatever effect it might have had to be
restored on the scene. This is particularly tricky for operations
as they need to ensure the scene stays consistent.

Represents an operation that introduces new objects into the scene graph.

Creation: (enter class :n number-of-copies :children child-forms :parent parent init-args..)
Child-forms being a list of enter forms, excluding the ENTER symbol at the start.
init-args being further initialisation arguments to be passed to the class that’s being instantiated.

Upon TICK, the CREATOR function is executed and each resulting object is ENTERed into the
given target animatable.

Aside from MAP-SET and DO-SET you can also use ITERATE
to go through the set by FOR .. ON-SET or FOR .. IN-SET.

See QUEUE
See SET
See MAKE-INDEXED-SET
See MAP-SET
See DO-SET
See SET-ADD
See SET-REMOVE
See SET-SIZE
See SET-FIRST
See SET-LAST
See SET-VALUE-AT
See SET-INDEX-OF
See CLEAR-SET
See IN-SET-P
See COERCE-SET

Contains an entire sequence of animations and controls their behaviour
and effects on the animatable.

When animations on the progression are set, the following happens:
1. The current clock is saved.
2. The progression is reset.
3. The new animations are set to the future set and sorted, the other
sets are cleared and reinitialised to match the appropriate length.
4. The clock is set to the previously saved time.
5. All applicable animations are put into effect in fast-forwarding by
calling UPDATE on the progression.

When a progression is reset, the following happens:
1. All past animations are pushed onto the present set.
2. The active animations are re-sorted to ensure consistency.
3. All the animations in the present set are reset in order.
4. All animations are pushed onto the future set.
5. The clock is fixed.

When a progression is updated, the following happens:
1. New animations that are now active during the current clock are
shifted from the future set to the present set.
2. When the progression has an animatable, each animation is ticked.
For this, the tick step must be calculated. If the duration of the
animation is infinite, the tick is T. If the animation exceeded its
duration, it is 1.0. Otherwise it is the linear interpolation
between the current clock time, the beginning of the animation, and
its duration.
3. Animations that have exceeded their duration are shifted from the
present set onto the past set.
4. If no present or future animations remain, the progression stops
itself.

See CLOCK
See DEFINITION
See ANIMATABLE
See ACTIVE
See ENDED
See FUTURE

The definition should at all time keep track of the existing instances
and update them in case the definition gets updated with new animations.
When the animations of the definition are set, the animations are also
set for each of the known instances of the definition.

Aside from MAP-QUEUE and DO-QUEUE you can also use ITERATE
to go through the set by FOR .. ON-QUEUE or FOR .. IN-QUEUE.

See HEAD
See TAIL
See SIZE
See MAP-QUEUE
See DO-QUEUE
See ENQUEUE
See DEQUEUE
See QUEUE-REMOVE
See QUEUE-SIZE
See QUEUE-FIRST
See QUEUE-LAST
See QUEUE-VALUE-AT
See QUEUE-INDEX-OF
See CLEAR-QUEUE
See IN-QUEUE-P
See COERCE-QUEUE

constraint ::= name | nth | this | children | everything | function | list
name — A keyword naming a unit in the scene-graph
nth — An integer specifying the nth unit in the scene-graph
this — The symbol T meaning the current object
children — A symbol with name ">" specifying all children of the current object
everything — A symbol with name "*" specifying all descendants as per DO-CONTAINER-TREE
function — A predicate function that is passed the current object
list — A quoted literal, function reference, or function form to use

Resulting from a compile-constraint call should be a function
that takes a single argument, the current object to constrain on.
The NEXT argument is the function to call next if the constraint
passes its test. A single constraint may call this next function
as many times as it wants.

Returned is a function of two arguments, a scene-graph and a function.
The scene-graph is the root of the scene graph that is selected on and
each unit within it that the selector is matching on results in a call
to function with that unit as its argument.

This means that if STABLE-SORT returns a new vector instead of re-using the given one,
the elements from the new vector are copied back into the old one so that it appears
as if it had been modified in-place. Always returns VEC.