Formatting code for Animation2
""<a name="part2"></a>""====Part II. Sequencing with scripts====
By Ralf Flicker.
__Prerequisites__: This part of the tutorial requires the reader to be familiar with the [[Scripting Lua script extension]] for Oxidizer. For some of the scripts presented below, the [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=utils.lua utils.lua]] and [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=mods.lua mods.lua]] script packages are required.
__Disclaimer__: The scripts employed in this article only reflect the contemporary efforts of the author, and are in no way to be understood as definitive or the only way of doing things. The reader is positively encouraged to experiment and come up with their own preferred methods, and to write much better scripts than those presented below.
__Update (8 May 2009)__: The sequencing script described below has received an update, and is documented and demonstrated here: [[http://web.me.com/rflicker/algorhythm.htm algorhythm]].
===Overview===
Morphing and key frame animation is straightforward, and a good enough approach for short morph sequences and when you don't care precisely how flam3 interpolates between the key frames. It is a less viable approach, however, when:
- you want a morph to follow a specific path
- you want to combine morphing with other types of modulations
- you have long or complicated sequences with many key frames
Obviously, if you want the morphing to be done in a //specific// way, you have no way of telling flam3 to do this, so we must instead calculate every time step of each genome morph ourselves. Also, if you have been doing everything manually so far, inserting key frames and adjusting parameters etc - at some point in the growing length of the sequence this procedure simply becomes too cumbersome and tedious to carry out by hand. This kind of work is suitable for doing with a script, for efficiency and repeatability (at least for the time being; the day Oxidizer implements an Animation Sequencer GUI all this will become obsolete). Here are a few examples of animations that you would do with a sequencing script:
- beat-syncing an animation to music
- "storyboard" sequences of many separate steps
- randomized looping modulations
- random walk morphing between genomes
There are many ways one could go about these things, and the scripts presented below are merely offered as examples of the types of methods that can be employed. One method of key frame authoring for an interpolating script will be the topic of the final section. This code has gradually grown out of a series of more specialized, non-interpolating animation sequencers that were designed to do just one specific thing, for instance Xform rotations. It may be of interest to the reader to also look at some of these earlier non-interpolating codes first, which is the topic of the next section.----
===Non-interpolating sequencing===
For specific tasks of a limited scope, we may be able to formulate a simple algorithmic description of the motion, for instance: "rotate by 360 degrees over 60 frames," or "modulate the variation weight by a cosine over 100 frames." For such modest sequences, it is a simple matter write a script that generates a sequence of genomes tracing out the motion. Two examples are given below, that apply various modulations to the first genome in the Oxidizer main list (to remind yourself of the Oxidizer Lua implementation, see the [[Scripting scripting tutorial]]). The first example simply zooms by a factor of two (from -1 to +1) and rotates the image by 200 degrees. This could have been done faster by a simple morph directly in Oxidizer, but just for illustrating the principle. The second example uses the same principle but calculates a full Xform (looping over all xforms) and color map rotation by calling the functions ''affine_Prot'' and ''cmap_rot''. This sequence will loop around when animated.::c::<<Example 1: simple parameter sequencing
%%(lua;1)luadir = "/path-to-utils-and-mods-folder/" -- change to actual path
dofile(luadir.."utils.lua")
nframes = 100 -- number of frames
ang = 200 -- amount of rotation (in degrees)
zoom = 2 -- amount of zoom
da = ang/nframes -- delta-angle per frame
dz = zoom/nframes -- delta-zoom per frame
genome = oxidizer_genomes[1]
new_genomes = {}
for i=0,nframes do -- Loop over frames
new_genome = clone_genome(genome) -- copy original genome
new_genome.time = i+genome.time
new_genome.zoom = genome.zoom-1+i*dz
new_genome.rotate = genome.rotate+i*da
new_genomes[i+1] = new_genome
end
oxidizer_genomes = new_genomes -- update Oxidizer genome
oxidizer_status["action"] = "replace"%%<<>>Example 2: Xform and color rotations
%%(lua;1)luadir = "/path-to-utils-and-mods-folder/" -- change to actual path
dofile(luadir.."mods.lua")
dofile(luadir.."utils.lua")
nframes = 180 -- number of frames
da = 360/nframes -- delta-angle per frame
dc = 256/nframes -- delta-color per frame
genome = oxidizer_genomes[1]
nx = #genome.xforms
new_genomes = {}
for i=0,nframes-1 do
new_genome = clone_genome(genome)
new_genome.time = i+genome.time
for x=1,nx do affine_Prot(new_genome,{x,"a"},i*da) end
cmap_rot(new_genome,new_genome.colors,dc*i)
new_genomes[i+1] = new_genome
end
oxidizer_genomes = new_genomes
oxidizer_status["action"] = "replace"%%>>::c::The general structure is clear: first load required packages, define numerical values, then loop over the number of frames and apply the genome parameter adjustments. The starting genome is copied by value for each new frame (by the ''clone_genome'' function), the copy is then modified in-place and inserted into a new table, which is passed back into the global genome table ''oxidizer_genomes'' by reference at the end of the script. Most of the details of these codes should be familiar from the script tutorial. Remember to change the ''luadir'' variable on line 1 to the full path to the folder where you put the mods.lua and utils.lua files.
The arguments of the affine_Prot function on line 14 in example 2 has a special structure that will be important later on. The second element of the second argument ##{x,"v"}## is a modulation mode selector, which tells the function how to apply the numeric value given in the third argument to the function. This argument takes three different values:
~##"v"## value: set the genome parameter to this value
~##"a"## additive: add this number to the current parameter value
~##"m"## multiplicative: multiply the current value by this number
These modes offer a wide range of possibilities for different types of parameter modulations, and all the Xform related functions in the mods.lua package have this structure (the morphing function ''morph'' and the color adjustment functions, e.g., cmap_rot and ''cmod_hsl'', work differently). We could also sequence a genome morph between different genomes in this type of script, by using a Lua morphing function, although we pay the price of not being able to exploit all the special tricks that are coded into flam3, and which can not be reproduced on the scripting level. Consequently, the Lua morph function (in the utils package) only interpolates the continuous genome parameters, and currently does nothing with discrete parameters like genome symmetry and final xform. But if you have no jumps in the discrete parameters, the benefit of using the Lua morph function instead of letting flam3 do the genome interpolation is that you can now add other parameter modulations on the scripting level, simultaneously with the genome morph. We will see some examples of this in the next section.
To introduce the terminology of the next section, we shall henceforth think of the genome parameters being actively modified by the script as "modulation channels," and the starting and stopping configurations as "control points" (essentially key frames, but with a more general role than in [[Animation Part I]]). The above script examples show how to easily script a frame sequencer for simple morphs and loops, where the modulation channels and control points are hard-coded into a ##FOR## loop. But if we want to have many more control points for a longer animation sequence, all of which might address //different// modulation channels and be of different lengths, etc. - then doing it this way will be just as much work as doing key frame authoring by hand in Oxidizer. We need a more efficient interface for authoring movie sequences without being troubled by all the details that goes on under the hood.----
""<a name="intseq"></a>""===Interpolating sequencer===
One script that goes some way towards such a simplified interface goes by the unglamorous name [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=seq9.lua seq9.lua]]. This code is a parsing and interpolation engine that will do all the heavy lifting, while the movie director can focus on authoring "control scripts" for it. The technical details of this code is documented on the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm seq7/seq9 home page]], which also contains several examples of animations produced with it.
It must be emphasized that while I offer these scripts as examples of tools that facilitate animation authoring, they are by no means trivial to use, and a modicum of manual scripting is still required. Those of you who have worked with audio or video sequencers like Logic or Final Cut will know what I am talking about: the timing, shaping, blending and cross-fading of events and segments - even with scripts in Oxidizer, we can not escape the job of authoring the events of the sequence in some abbreviated form like this.
Having said all that, let's look at what the control scripts for the two examples given previously would look like:::c::<<Example 1 as a control script for seq9
%%(lua)mdef = { -- modulation channels
[1] = {"zoom","v"},
[2] = {"rotate","a"}
}
cp = {} -- control points
cp[1] = {
[1] = {0, -1},
[2] = {100, 1}}
cp[2] = {
[1] = {0, 0},
[2] = {100, 200}}%%<<>>Example 2 as a control script for seq9
%%(lua)
nx = #oxidizer_genomes[1].xforms
mdef,cp = {},{}
mdef[1] = {cmap_rot,genome.colors}
for i=1,nx do mdef[i+1] = {affine_Prot,{i,"a"}} end
cp[1] = {
[1] = {0, 0},
[2] = {180, 256}}
for i=1,nx do
cp[i+1] = {
[1] = {0, 0},
[2] = {180, 360}}
end%%>>::c::>>seq9 file paths and control script
%%(lua;22)luadir = "/Users/rflicker/Documents/Oxidizer/" -- change to your dir
mdir = luadir.."scripts_dev/" -- my utils and mods directory
sdir = luadir.."scripts_seq/" -- my control script directory
qfile = "cs1.lua" -- control script file name
infile = sdir..qfile -- full path to control script%%>>In order to test these scripts, you need to edit seq9 to set up the file paths and file names for the external packages and the input control script, as indicated on lines 22-26 in the box on the right. In this setup, ''mdir'' must be the full path to the folder containing the utils and mods packages, and ''infile'' must be the full path to the control script file, but otherwise you may rearrange things to better reflect your directory structure (for instance, you do not have to use the intermediate ''luadir'', ''sdir'' and ''qfile'' variables if you don't want to).
Looking at the example control scripts above, we see that the animated parameters are defined in the table ''mdef'' (modulations definition), and the key frames are defined in the table ''cp'' (control points). A modulation channel is a table containing the name of the genome parameter or a function, followed by another table containing any required arguments (which vary between different functions) and the mode selector already encountered. The element ##cp[i]## defines the control point sequence for the genome parameter with the same-numbered modulation channel definition ##mdef[i]##. Each control point is a table of time-value pairs, with optional curve and interpolation keywords (discussed below). These are the basics of a control scripts for seq9.
Compared to the non-interpolating formulation in the previous section, what was gained by all this? For one thing, adding more modulation channels and new movie segments is as easy as inserting additional table elements in the ##mdef## and ##cp## tables. For repeating segments (for instance, for beat-syncing) it is also easy to use ##FOR## loops over control points to define repeating modulations of indefinite length (which may also include a random element, to make them less predictable). Lastly, the interface is substantially cleaner, giving an easier overview of the sequence of control points and the parameters being modulated.
The ##mdef## and ##cp## tables are required objects within a control script. Two more optional tables that seq9 will parse if presented with are the morph sequence ''mseq'' and global environment ''genv'' tables. The morph sequence table is required if the morph function is specified as a modulation channel. If no morph sequence is present, seq9 will by default apply its modulations to the first genome in Oxidizer, that is, ##oxidizer_genomes[1]##. The global environment parameters are just static adjustments that are not animated; for instance, in case some of the genomes of the sequence do not have consistent rendering parameters, you can make sure from the control script that they are all set consistently. This also makes it easy to alternate between generating preview sequences and final rendering sequences, by switching off density estimation in the former.
==Morphing, curves and interpolation==::c::>>Example 3: morphing with seq9%%(lua;1)nf = 80 -- frames per morph
mdef,cp = {},{}
mdef[1] = {morph,{"pol","hsl",0}} -- 0=alternating polarity, 1=fixed
mdef[6] = {cmod_hsl,"s"}
mdef[2] = {affine_Prot,{2,"a"}}
mdef[3] = {affine_Prot,{4,"a"}}
mdef[4] = {affine_Orot,{3,"a"}}
--mdef[5] = {affine_Prot,{1,"a"}}
mdef[7] = {affine_Prot,{3,"a"}}
mseq = {
[1] = {0, nf, 1, 2},
[2] = {nf, nf, 2, 3},
[3] = {2*nf, nf, 3, 4}}
mc,mv = "exp",-1
mi,mt = "smooth",0
cp[1] = {
[1] = {0, 0, curve=mc, a=mv, inter=mi, t=mt},
[2] = {nf, 1, curve=mc, a=mv, inter=mi, t=mt},
[3] = {2*nf, 0, curve=mc, a=mv, inter=mi, t=mt},
[4] = {3*nf, 1, curve=mc, a=mv, inter=mi, t=mt},
[5] = {3.5*nf, 1, curve=mc, a=mv, inter=mi, t=mt}}
nt = 3.5*nf
cp[6] = {
[1] = {0, 1.25, curve="lin", a=1},
[2] = {nt, 1.25, curve="lin", a=1}}
--cp[5] = {
-- [1] = {0, 0, curve="tanh", a=1.5},
-- [2] = {nt, 720, curve="tanh", a=1.5}}
cp[2] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, 720, curve="tanh", a=1.5}}
cp[3] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, -720, curve="tanh", a=1.5}}
cp[4] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, 720, curve="tanh", a=1.5}}
cp[7] = {
[1] = {0, 0, curve="tanh", a=1},
[2] = {2.5*nf, 600, curve="tanh", a=1},
[3] = {nt, 400, curve="tanh", a=1}}
genv = {
supersample = 3,
filter_shape = "Gaussian",
filter = 0.6,
quality = 1,
estimator_radius = 4,
estimator_curve = 0.8,
estimator_minimum = 0,
temporal_samples = 60,
temporal_filter_type = "Exponent",
temporal_filter_exp = 4,
interpolation = "Linear",
interpolation_type = "Linear"}%%>>To see an example of morphing, and to introduce curves and interpolation, let's look at the control script in the box on the right, Example 3. This script morphs consecutively between four genomes using the ''morph'' Lua function mentioned previously, while simultaneously applying XForm rotations and other things. This marks the difference between seq7 and seq9: the former hands over the morphing to flam3, which disables the scripting action for the duration of the morph, while seq9 sequences the genome morph from the scripting level. These two implementations were so different that it broke the code into two versions, which I have opted to kept as separate scripts. The ability in seq9 to combine curves with morphing on the scripting level means that you can make smooth, flowing morphs even where flam3 would default to linear, like when you don't have enough boundary points for Catmull-Rom interpolation. The following video clip was produced by applying the control script in Example 3 to the same genome that was sequenced with Oxidizer/flam3 in Part I.---
""<script src="http://www.apple.com/library/quicktime/scripts/ac_quicktime.js" language="JavaScript" type="text/javascript"></script>
<script src="http://www.apple.com/library/quicktime/scripts/qtp_library.js" language="JavaScript" type="text/javascript"></script>
<link href="http://www.apple.com/library/quicktime/stylesheets/qtp_library.css" rel="StyleSheet" type="text/css" />
<table width=220px align=center><tr>
<td width=200px align=left valign=top style="background-color: rgb(20,20,20); cursor: pointer; font-weight: bold; font-size: 1.25em; opacity: .85; filter: alpha(opacity=85); -moz-border-radius: 1em; -webkit-border-radius: 1em; position: relative; top: 50%; zoom: 100%; padding: .5em 1em; color: rgb(230,220,210);">
<script type="text/javascript">
QT_WritePoster_XHTML('Click to Play', 'http://idisk.mac.com/rflicker-Public/Oxidizer/wiki/morph-seq9-poster.jpg',
'http://idisk.mac.com/rflicker-Public/Oxidizer/wiki/morph-seq9.mov',
'200', '141', '',
'controller', 'true',
'loop','true',
'autoplay', 'true',
'bgcolor', 'black',
'align','middle',
'scale', 'aspect');
</script>
</td></tr>
<tr><td align=left><em>The genome (<a href="http://www.rampant-mac.com/wiki/wikka.php?wakka=GenomePage/files.xml&action=download&file=sequence4.flam3">sequence4.flam3</a>) from <a href="http://www.rampant-mac.com/wiki/wikka.php?wakka=Animation">Part I</a> sequenced with seq9.</em></td>
</tr></table>""
The elements of the morphing sequence ##mseq## in seq9 have the following definition:
##{start_time, morph_length, source_genome, target_genome}##
with one such table per morphing instance (seq7 has a different definition). We also see a first example of the curve and interpolation keywords being present in the ##cp## table. The complete specification of a control point element is the table:
##{time, value, curve=, a=, b=, inter=, t=}##
where ''curve'' is a string specifying the type of curve to use (by default linear, if this keyword is omitted), and ''a'' and ''b'' are adjustable parameters of the curve. The interpolation key ''inter'' can be set to either ''smooth'' or ''linear'' (default linear), and ''t'' is the tension parameter of the Catmull-Rom spline discussed in [[Animation Part I]]. Currently available curves are ''exp'', ''tanh'', ''sinh'', ''cos'', ''cos2'', ''sina'' and ''cosa''. All curves take the parameter ##a##, and ##sina## and ##cosa## also take the second parameter ##b##. Plots of the curves are shown on the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm#curves seq7 home page]]. Lastly, curves and smooth interpolation can be used independently, but also combined together, as illustrated in the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm#smooth interpolation]] section.
Some more comments on the control script in Example 3 and features of seq9 (see also the mods.lua and utils.lua packages for more details):
- (line 4) ##morph## takes three arguments specifying the spatial interpolation ("pol" or "lin"), color interpolation ("hsl" or "rgb") and polarity, which can be either 1 (fixed polarity) or 0 (alternating). The "distance" argument to the morph function is normalized to [0,1], where 0 means the source genome and 1 means the target genome. Under alternating polarity, these roles will invert after each morph.
- (line 5) ##cmod_hsl## can do either hue, saturation or lightness correction by giving it the argument "h","s" or "l". Their value modes are hardwired as follows: hue is additive, saturation and lightness are multiplicative. By ramping the lightness you can implement cheap fade-to-black intros/outros, if you don't have a video editor.
- one could have set the ##mdef## and ##cp## definitions for ##affine_prot## with a ##FOR## loop just like in Example 2, if all channels were doing the same thing. One reason to define them separately, however, is that:
- (line 9, 32-34) you can then easily comment out channels, and insert them back in again. The channel definitions do not have to be consecutively numbered (you do not even have to start at 1 if you don't want to).
- (line 17-18) the morph here uses a combination of an exponential curve with ##a=-1## and smoothing with ##t=0##.
- (line 28-30) the saturation modulation is here just a constant adjustment by +25%, but it could also be varying or peaked (e.g. using the ##cos2## curve) to compensate for some particular genome with washed out colors compared to the rest.
I hope the above discussion, along with the example control scripts, have given you a flavor of one possible way to compose animation sequences via a scripting interface. Listing all the details and caveats of this particular code (seq7/seq9) is beyond the scope of this brief tutorial; for more information, the interested reader is referred to the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm seq7/seq9 home page]], and the [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=doq7.lua doq7.lua]] example control script (for seq7). See also the [[ScriptPage scripts page]] for more example scripts relevant to this tutorial. I leave you with a video clip of a random-walk beat-syncing experiment that was sequenced with seq7, enjoy!::c::
""<table width=384px align=center><tr>
<td width=384px align=center valign=top style="background-color: rgb(20,20,20); cursor: pointer; font-weight: bold; font-size: 1.25em; opacity: .85; filter: alpha(opacity=85); -moz-border-radius: 1em; -webkit-border-radius: 1em; position: relative; top: 50%; zoom: 100%; padding: .5em 1em; color: rgb(230,220,210);">
<script type="text/javascript">
QT_WritePoster_XHTML('Click to Play', 'http://homepage.mac.com/rflicker/images/ff3/Trance7/Trance7-poster.jpg',
'http://homepage.mac.com/rflicker/images/ff3/Trance7/Trance7.mov',
'384', '304', '',
'controller', 'true',
'loop','false',
'autoplay', 'true',
'bgcolor', 'black',
'align','middle',
'scale', 'aspect');
</script>
</td></tr>
<tr><td align=center><em>Random-walk beat-syncing with seq7.</em></td>
</tr></table>""
By Ralf Flicker.
__Prerequisites__: This part of the tutorial requires the reader to be familiar with the [[Scripting Lua script extension]] for Oxidizer. For some of the scripts presented below, the [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=utils.lua utils.lua]] and [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=mods.lua mods.lua]] script packages are required.
__Disclaimer__: The scripts employed in this article only reflect the contemporary efforts of the author, and are in no way to be understood as definitive or the only way of doing things. The reader is positively encouraged to experiment and come up with their own preferred methods, and to write much better scripts than those presented below.
__Update (8 May 2009)__: The sequencing script described below has received an update, and is documented and demonstrated here: [[http://web.me.com/rflicker/algorhythm.htm algorhythm]].
===Overview===
Morphing and key frame animation is straightforward, and a good enough approach for short morph sequences and when you don't care precisely how flam3 interpolates between the key frames. It is a less viable approach, however, when:
- you want a morph to follow a specific path
- you want to combine morphing with other types of modulations
- you have long or complicated sequences with many key frames
Obviously, if you want the morphing to be done in a //specific// way, you have no way of telling flam3 to do this, so we must instead calculate every time step of each genome morph ourselves. Also, if you have been doing everything manually so far, inserting key frames and adjusting parameters etc - at some point in the growing length of the sequence this procedure simply becomes too cumbersome and tedious to carry out by hand. This kind of work is suitable for doing with a script, for efficiency and repeatability (at least for the time being; the day Oxidizer implements an Animation Sequencer GUI all this will become obsolete). Here are a few examples of animations that you would do with a sequencing script:
- beat-syncing an animation to music
- "storyboard" sequences of many separate steps
- randomized looping modulations
- random walk morphing between genomes
There are many ways one could go about these things, and the scripts presented below are merely offered as examples of the types of methods that can be employed. One method of key frame authoring for an interpolating script will be the topic of the final section. This code has gradually grown out of a series of more specialized, non-interpolating animation sequencers that were designed to do just one specific thing, for instance Xform rotations. It may be of interest to the reader to also look at some of these earlier non-interpolating codes first, which is the topic of the next section.----
===Non-interpolating sequencing===
For specific tasks of a limited scope, we may be able to formulate a simple algorithmic description of the motion, for instance: "rotate by 360 degrees over 60 frames," or "modulate the variation weight by a cosine over 100 frames." For such modest sequences, it is a simple matter write a script that generates a sequence of genomes tracing out the motion. Two examples are given below, that apply various modulations to the first genome in the Oxidizer main list (to remind yourself of the Oxidizer Lua implementation, see the [[Scripting scripting tutorial]]). The first example simply zooms by a factor of two (from -1 to +1) and rotates the image by 200 degrees. This could have been done faster by a simple morph directly in Oxidizer, but just for illustrating the principle. The second example uses the same principle but calculates a full Xform (looping over all xforms) and color map rotation by calling the functions ''affine_Prot'' and ''cmap_rot''. This sequence will loop around when animated.::c::<<Example 1: simple parameter sequencing
%%(lua;1)luadir = "/path-to-utils-and-mods-folder/" -- change to actual path
dofile(luadir.."utils.lua")
nframes = 100 -- number of frames
ang = 200 -- amount of rotation (in degrees)
zoom = 2 -- amount of zoom
da = ang/nframes -- delta-angle per frame
dz = zoom/nframes -- delta-zoom per frame
genome = oxidizer_genomes[1]
new_genomes = {}
for i=0,nframes do -- Loop over frames
new_genome = clone_genome(genome) -- copy original genome
new_genome.time = i+genome.time
new_genome.zoom = genome.zoom-1+i*dz
new_genome.rotate = genome.rotate+i*da
new_genomes[i+1] = new_genome
end
oxidizer_genomes = new_genomes -- update Oxidizer genome
oxidizer_status["action"] = "replace"%%<<>>Example 2: Xform and color rotations
%%(lua;1)luadir = "/path-to-utils-and-mods-folder/" -- change to actual path
dofile(luadir.."mods.lua")
dofile(luadir.."utils.lua")
nframes = 180 -- number of frames
da = 360/nframes -- delta-angle per frame
dc = 256/nframes -- delta-color per frame
genome = oxidizer_genomes[1]
nx = #genome.xforms
new_genomes = {}
for i=0,nframes-1 do
new_genome = clone_genome(genome)
new_genome.time = i+genome.time
for x=1,nx do affine_Prot(new_genome,{x,"a"},i*da) end
cmap_rot(new_genome,new_genome.colors,dc*i)
new_genomes[i+1] = new_genome
end
oxidizer_genomes = new_genomes
oxidizer_status["action"] = "replace"%%>>::c::The general structure is clear: first load required packages, define numerical values, then loop over the number of frames and apply the genome parameter adjustments. The starting genome is copied by value for each new frame (by the ''clone_genome'' function), the copy is then modified in-place and inserted into a new table, which is passed back into the global genome table ''oxidizer_genomes'' by reference at the end of the script. Most of the details of these codes should be familiar from the script tutorial. Remember to change the ''luadir'' variable on line 1 to the full path to the folder where you put the mods.lua and utils.lua files.
The arguments of the affine_Prot function on line 14 in example 2 has a special structure that will be important later on. The second element of the second argument ##{x,"v"}## is a modulation mode selector, which tells the function how to apply the numeric value given in the third argument to the function. This argument takes three different values:
~##"v"## value: set the genome parameter to this value
~##"a"## additive: add this number to the current parameter value
~##"m"## multiplicative: multiply the current value by this number
These modes offer a wide range of possibilities for different types of parameter modulations, and all the Xform related functions in the mods.lua package have this structure (the morphing function ''morph'' and the color adjustment functions, e.g., cmap_rot and ''cmod_hsl'', work differently). We could also sequence a genome morph between different genomes in this type of script, by using a Lua morphing function, although we pay the price of not being able to exploit all the special tricks that are coded into flam3, and which can not be reproduced on the scripting level. Consequently, the Lua morph function (in the utils package) only interpolates the continuous genome parameters, and currently does nothing with discrete parameters like genome symmetry and final xform. But if you have no jumps in the discrete parameters, the benefit of using the Lua morph function instead of letting flam3 do the genome interpolation is that you can now add other parameter modulations on the scripting level, simultaneously with the genome morph. We will see some examples of this in the next section.
To introduce the terminology of the next section, we shall henceforth think of the genome parameters being actively modified by the script as "modulation channels," and the starting and stopping configurations as "control points" (essentially key frames, but with a more general role than in [[Animation Part I]]). The above script examples show how to easily script a frame sequencer for simple morphs and loops, where the modulation channels and control points are hard-coded into a ##FOR## loop. But if we want to have many more control points for a longer animation sequence, all of which might address //different// modulation channels and be of different lengths, etc. - then doing it this way will be just as much work as doing key frame authoring by hand in Oxidizer. We need a more efficient interface for authoring movie sequences without being troubled by all the details that goes on under the hood.----
""<a name="intseq"></a>""===Interpolating sequencer===
One script that goes some way towards such a simplified interface goes by the unglamorous name [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=seq9.lua seq9.lua]]. This code is a parsing and interpolation engine that will do all the heavy lifting, while the movie director can focus on authoring "control scripts" for it. The technical details of this code is documented on the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm seq7/seq9 home page]], which also contains several examples of animations produced with it.
It must be emphasized that while I offer these scripts as examples of tools that facilitate animation authoring, they are by no means trivial to use, and a modicum of manual scripting is still required. Those of you who have worked with audio or video sequencers like Logic or Final Cut will know what I am talking about: the timing, shaping, blending and cross-fading of events and segments - even with scripts in Oxidizer, we can not escape the job of authoring the events of the sequence in some abbreviated form like this.
Having said all that, let's look at what the control scripts for the two examples given previously would look like:::c::<<Example 1 as a control script for seq9
%%(lua)mdef = { -- modulation channels
[1] = {"zoom","v"},
[2] = {"rotate","a"}
}
cp = {} -- control points
cp[1] = {
[1] = {0, -1},
[2] = {100, 1}}
cp[2] = {
[1] = {0, 0},
[2] = {100, 200}}%%<<>>Example 2 as a control script for seq9
%%(lua)
nx = #oxidizer_genomes[1].xforms
mdef,cp = {},{}
mdef[1] = {cmap_rot,genome.colors}
for i=1,nx do mdef[i+1] = {affine_Prot,{i,"a"}} end
cp[1] = {
[1] = {0, 0},
[2] = {180, 256}}
for i=1,nx do
cp[i+1] = {
[1] = {0, 0},
[2] = {180, 360}}
end%%>>::c::>>seq9 file paths and control script
%%(lua;22)luadir = "/Users/rflicker/Documents/Oxidizer/" -- change to your dir
mdir = luadir.."scripts_dev/" -- my utils and mods directory
sdir = luadir.."scripts_seq/" -- my control script directory
qfile = "cs1.lua" -- control script file name
infile = sdir..qfile -- full path to control script%%>>In order to test these scripts, you need to edit seq9 to set up the file paths and file names for the external packages and the input control script, as indicated on lines 22-26 in the box on the right. In this setup, ''mdir'' must be the full path to the folder containing the utils and mods packages, and ''infile'' must be the full path to the control script file, but otherwise you may rearrange things to better reflect your directory structure (for instance, you do not have to use the intermediate ''luadir'', ''sdir'' and ''qfile'' variables if you don't want to).
Looking at the example control scripts above, we see that the animated parameters are defined in the table ''mdef'' (modulations definition), and the key frames are defined in the table ''cp'' (control points). A modulation channel is a table containing the name of the genome parameter or a function, followed by another table containing any required arguments (which vary between different functions) and the mode selector already encountered. The element ##cp[i]## defines the control point sequence for the genome parameter with the same-numbered modulation channel definition ##mdef[i]##. Each control point is a table of time-value pairs, with optional curve and interpolation keywords (discussed below). These are the basics of a control scripts for seq9.
Compared to the non-interpolating formulation in the previous section, what was gained by all this? For one thing, adding more modulation channels and new movie segments is as easy as inserting additional table elements in the ##mdef## and ##cp## tables. For repeating segments (for instance, for beat-syncing) it is also easy to use ##FOR## loops over control points to define repeating modulations of indefinite length (which may also include a random element, to make them less predictable). Lastly, the interface is substantially cleaner, giving an easier overview of the sequence of control points and the parameters being modulated.
The ##mdef## and ##cp## tables are required objects within a control script. Two more optional tables that seq9 will parse if presented with are the morph sequence ''mseq'' and global environment ''genv'' tables. The morph sequence table is required if the morph function is specified as a modulation channel. If no morph sequence is present, seq9 will by default apply its modulations to the first genome in Oxidizer, that is, ##oxidizer_genomes[1]##. The global environment parameters are just static adjustments that are not animated; for instance, in case some of the genomes of the sequence do not have consistent rendering parameters, you can make sure from the control script that they are all set consistently. This also makes it easy to alternate between generating preview sequences and final rendering sequences, by switching off density estimation in the former.
==Morphing, curves and interpolation==::c::>>Example 3: morphing with seq9%%(lua;1)nf = 80 -- frames per morph
mdef,cp = {},{}
mdef[1] = {morph,{"pol","hsl",0}} -- 0=alternating polarity, 1=fixed
mdef[6] = {cmod_hsl,"s"}
mdef[2] = {affine_Prot,{2,"a"}}
mdef[3] = {affine_Prot,{4,"a"}}
mdef[4] = {affine_Orot,{3,"a"}}
--mdef[5] = {affine_Prot,{1,"a"}}
mdef[7] = {affine_Prot,{3,"a"}}
mseq = {
[1] = {0, nf, 1, 2},
[2] = {nf, nf, 2, 3},
[3] = {2*nf, nf, 3, 4}}
mc,mv = "exp",-1
mi,mt = "smooth",0
cp[1] = {
[1] = {0, 0, curve=mc, a=mv, inter=mi, t=mt},
[2] = {nf, 1, curve=mc, a=mv, inter=mi, t=mt},
[3] = {2*nf, 0, curve=mc, a=mv, inter=mi, t=mt},
[4] = {3*nf, 1, curve=mc, a=mv, inter=mi, t=mt},
[5] = {3.5*nf, 1, curve=mc, a=mv, inter=mi, t=mt}}
nt = 3.5*nf
cp[6] = {
[1] = {0, 1.25, curve="lin", a=1},
[2] = {nt, 1.25, curve="lin", a=1}}
--cp[5] = {
-- [1] = {0, 0, curve="tanh", a=1.5},
-- [2] = {nt, 720, curve="tanh", a=1.5}}
cp[2] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, 720, curve="tanh", a=1.5}}
cp[3] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, -720, curve="tanh", a=1.5}}
cp[4] = {
[1] = {0, 0, curve="tanh", a=1.5},
[2] = {nt, 720, curve="tanh", a=1.5}}
cp[7] = {
[1] = {0, 0, curve="tanh", a=1},
[2] = {2.5*nf, 600, curve="tanh", a=1},
[3] = {nt, 400, curve="tanh", a=1}}
genv = {
supersample = 3,
filter_shape = "Gaussian",
filter = 0.6,
quality = 1,
estimator_radius = 4,
estimator_curve = 0.8,
estimator_minimum = 0,
temporal_samples = 60,
temporal_filter_type = "Exponent",
temporal_filter_exp = 4,
interpolation = "Linear",
interpolation_type = "Linear"}%%>>To see an example of morphing, and to introduce curves and interpolation, let's look at the control script in the box on the right, Example 3. This script morphs consecutively between four genomes using the ''morph'' Lua function mentioned previously, while simultaneously applying XForm rotations and other things. This marks the difference between seq7 and seq9: the former hands over the morphing to flam3, which disables the scripting action for the duration of the morph, while seq9 sequences the genome morph from the scripting level. These two implementations were so different that it broke the code into two versions, which I have opted to kept as separate scripts. The ability in seq9 to combine curves with morphing on the scripting level means that you can make smooth, flowing morphs even where flam3 would default to linear, like when you don't have enough boundary points for Catmull-Rom interpolation. The following video clip was produced by applying the control script in Example 3 to the same genome that was sequenced with Oxidizer/flam3 in Part I.---
""<script src="http://www.apple.com/library/quicktime/scripts/ac_quicktime.js" language="JavaScript" type="text/javascript"></script>
<script src="http://www.apple.com/library/quicktime/scripts/qtp_library.js" language="JavaScript" type="text/javascript"></script>
<link href="http://www.apple.com/library/quicktime/stylesheets/qtp_library.css" rel="StyleSheet" type="text/css" />
<table width=220px align=center><tr>
<td width=200px align=left valign=top style="background-color: rgb(20,20,20); cursor: pointer; font-weight: bold; font-size: 1.25em; opacity: .85; filter: alpha(opacity=85); -moz-border-radius: 1em; -webkit-border-radius: 1em; position: relative; top: 50%; zoom: 100%; padding: .5em 1em; color: rgb(230,220,210);">
<script type="text/javascript">
QT_WritePoster_XHTML('Click to Play', 'http://idisk.mac.com/rflicker-Public/Oxidizer/wiki/morph-seq9-poster.jpg',
'http://idisk.mac.com/rflicker-Public/Oxidizer/wiki/morph-seq9.mov',
'200', '141', '',
'controller', 'true',
'loop','true',
'autoplay', 'true',
'bgcolor', 'black',
'align','middle',
'scale', 'aspect');
</script>
</td></tr>
<tr><td align=left><em>The genome (<a href="http://www.rampant-mac.com/wiki/wikka.php?wakka=GenomePage/files.xml&action=download&file=sequence4.flam3">sequence4.flam3</a>) from <a href="http://www.rampant-mac.com/wiki/wikka.php?wakka=Animation">Part I</a> sequenced with seq9.</em></td>
</tr></table>""
The elements of the morphing sequence ##mseq## in seq9 have the following definition:
##{start_time, morph_length, source_genome, target_genome}##
with one such table per morphing instance (seq7 has a different definition). We also see a first example of the curve and interpolation keywords being present in the ##cp## table. The complete specification of a control point element is the table:
##{time, value, curve=, a=, b=, inter=, t=}##
where ''curve'' is a string specifying the type of curve to use (by default linear, if this keyword is omitted), and ''a'' and ''b'' are adjustable parameters of the curve. The interpolation key ''inter'' can be set to either ''smooth'' or ''linear'' (default linear), and ''t'' is the tension parameter of the Catmull-Rom spline discussed in [[Animation Part I]]. Currently available curves are ''exp'', ''tanh'', ''sinh'', ''cos'', ''cos2'', ''sina'' and ''cosa''. All curves take the parameter ##a##, and ##sina## and ##cosa## also take the second parameter ##b##. Plots of the curves are shown on the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm#curves seq7 home page]]. Lastly, curves and smooth interpolation can be used independently, but also combined together, as illustrated in the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm#smooth interpolation]] section.
Some more comments on the control script in Example 3 and features of seq9 (see also the mods.lua and utils.lua packages for more details):
- (line 4) ##morph## takes three arguments specifying the spatial interpolation ("pol" or "lin"), color interpolation ("hsl" or "rgb") and polarity, which can be either 1 (fixed polarity) or 0 (alternating). The "distance" argument to the morph function is normalized to [0,1], where 0 means the source genome and 1 means the target genome. Under alternating polarity, these roles will invert after each morph.
- (line 5) ##cmod_hsl## can do either hue, saturation or lightness correction by giving it the argument "h","s" or "l". Their value modes are hardwired as follows: hue is additive, saturation and lightness are multiplicative. By ramping the lightness you can implement cheap fade-to-black intros/outros, if you don't have a video editor.
- one could have set the ##mdef## and ##cp## definitions for ##affine_prot## with a ##FOR## loop just like in Example 2, if all channels were doing the same thing. One reason to define them separately, however, is that:
- (line 9, 32-34) you can then easily comment out channels, and insert them back in again. The channel definitions do not have to be consecutively numbered (you do not even have to start at 1 if you don't want to).
- (line 17-18) the morph here uses a combination of an exponential curve with ##a=-1## and smoothing with ##t=0##.
- (line 28-30) the saturation modulation is here just a constant adjustment by +25%, but it could also be varying or peaked (e.g. using the ##cos2## curve) to compensate for some particular genome with washed out colors compared to the rest.
I hope the above discussion, along with the example control scripts, have given you a flavor of one possible way to compose animation sequences via a scripting interface. Listing all the details and caveats of this particular code (seq7/seq9) is beyond the scope of this brief tutorial; for more information, the interested reader is referred to the [[http://homepage.mac.com/rflicker/TNS/ff3/ani.htm seq7/seq9 home page]], and the [[http://www.rampant-mac.com/wiki/wikka.php?wakka=ScriptPage/files.xml&action=download&file=doq7.lua doq7.lua]] example control script (for seq7). See also the [[ScriptPage scripts page]] for more example scripts relevant to this tutorial. I leave you with a video clip of a random-walk beat-syncing experiment that was sequenced with seq7, enjoy!::c::
""<table width=384px align=center><tr>
<td width=384px align=center valign=top style="background-color: rgb(20,20,20); cursor: pointer; font-weight: bold; font-size: 1.25em; opacity: .85; filter: alpha(opacity=85); -moz-border-radius: 1em; -webkit-border-radius: 1em; position: relative; top: 50%; zoom: 100%; padding: .5em 1em; color: rgb(230,220,210);">
<script type="text/javascript">
QT_WritePoster_XHTML('Click to Play', 'http://homepage.mac.com/rflicker/images/ff3/Trance7/Trance7-poster.jpg',
'http://homepage.mac.com/rflicker/images/ff3/Trance7/Trance7.mov',
'384', '304', '',
'controller', 'true',
'loop','false',
'autoplay', 'true',
'bgcolor', 'black',
'align','middle',
'scale', 'aspect');
</script>
</td></tr>
<tr><td align=center><em>Random-walk beat-syncing with seq7.</em></td>
</tr></table>""