Animations of a 3d model, one has to carefully design the position of the 'camera' at each timestep of the animation. The sequence of positions is called a 'flight path' or 'flypath'.
Creating a good flight path is very difficult. Some commercial visualization software kits, like Maya and Virtual Director, come with tools to aid in the design of a good flight path.
Over the last three years, we at COSMUS have made several 3d models for astronomical and other data using the visualization software Partiview (which shares the same graphical libraries as Virtual Director but is free and open source), but we have had trouble making good movies with them.
Other than a Perl module tfm.pm written by its creator (Stuart Levy) that contains some nifty utilities, Partiview does not come with tools to help create flight paths. Therefore, we had to write our own. This involves the following tasks:
Suppose we want to create 30 seconds of animation at 24 frames per second. Starting with our camera at (10,20,30), we move to (1000,2e4,-1e5) with exponentially increasing velocity in a straight line for the first 20 seconds, followed by a rotation around (-1000,-1000,0) to (8888,2e4,-1e5). We always look at (0,0,0) and maintain field-of-view 45 degrees.
We wish to create, eventually, files called blah0001.png, blah0002.png, ..., blah0720.png each with resolution 640 x 480.
We create the file foo.config to have the following entries:
outname blah numdec 4 ext png frameoffset 1 xsize 640 ysize 480 fps 24 up 0 1 0 maintainvertical 1
If we create the file bar.flypath with the contents:
<FLYPATH take1 i_someplace2start m_zoomout 20 m_curve 10> <FLYPATH zoom4minute i_someplace2start m_zoomout 60> <INIT someplace2start> <CAMERA p_10:20:30> <LOOKAT p_0:0:0> <ROLL 0> <FOV 45> </INIT> <MOVE zoomout> <CAMERA p_1000:2e4:-1e5> <SPEED EXP> </MOVE> <MOVE zoomoutlinearly> <CAMERA p_1000:2e4:-1e5> </MOVE> <MOVE curve> <CAMERA p_8888:2e4:-1e5> <CENTER p_-1e3:-1e3:0> </MOVE>
Now if we say either of
perl ./flypathmaker.pl foo.config bar.flypath f_take1 perl ./flypathmaker.pl foo.config bar.flypath take1
Then the following files are produced:
Related to just the flight path:
Note that the .flypath file can contain details of several camera flight paths. For example, if we said
perl ./flypathmaker.pl foo.config bar.flypath f_zoom4minute blah
Then the resulting files would be called blah_zoom4minutes* instead of blah_take1* and be for a flight path involving only the straight-line motion from (10,20,30) to (1000,2e4,-1e5) with exponentially increasing speed, and lasting for an entire 60 seconds.
Details of the .flypath format and .config options are explained next.
A .flypath file contains details for several possible camera starting points (initial states) and of several possible segments ('moves') of the flight path. All moves are relative - they do not contain information on their starting state, though they can refer to it. A flight path is an initial state followed by at least one move - thus a .flypath file can potentially create a large variety of paths.
One can use relative variables in a .flypath file. Variables of the form t_blah are time units - they all eventually come down to seconds (not frames, since that depends on the number of frames per second). t_10s is the absolute time 10 seconds after the start of the flight path - other t_* variables will be explained later. There are also functions add(t_1,t_2) that adds two time values to make a third - saying add(t_1,-t_2) returns t_1-t_2. For now, t_2 must be an absolute time. You can also say add(t_1,x) where x is a real number in seconds.
The following tags are available. The order of tags in the .flypath file makes no difference. Within a .flypath file you can use variables instead of absolute values.
If you use the variable 'p_blah' then you should define p_blah somewhere in the .flypath file using
<POINT blah X Y Z>
where X, Y and Z are real numbers.
x_p_blah refers to the
x-coordinate of point p_blah i.e. X with the declaration above.
Ditto with y_p_blah = Y and z_p_blah = Z.
There is an addition function add(p_id1,p_id2). For now, p_id2 must be an absolute point.
<FLYPATH mine i_blah m_argh d1 m_bleah d2 ... m_finally dN >The real-valued durations d1, ..., dn are supplied in seconds. The nmoves m_argh, m_bleah, ..., m_finally must be defined with MOVE tags elsewhere in the .flypath file.
At the moment, you can never refer to the same move twice in the same FLYPATH tag. You'll have to create a duplicate of the move (i.e. cut-and-paste) and then change its name. Annoying, but fixable later.
<INIT id> <CAMERA p_c> # camera is at p_c at start of flight path <LOOKAT p_d> # camera looks at p_d move at start of flight path <ROLL r0> # camera rolls at r0 degrees to 'up' (0 1 0 or specified in .config file) <FOV f0> # move ends up with this field-of-view (linear interpolation only) </INIT>
<MOVE id> <CAMERA p_a> # camera position to be interpolated (linearly/logarithmically/exponentially/...) from p_m_curr_0 to p_m_curr_1:=p_a <LOOKAT p_b> # move ends up with camera pointing at p_b (linear interpolation only) <ROLL r> # move ends up with camera tilted r degrees from current 'up' axis (linear interpolation only) <FOV f> # move ends up with this field-of-view (linear interpolation only) <SPEED x> # x is LINEAR (default) or EXP (more options to be added) <CENTER ... > # explained later <NORMAL ... > # explained later </MOVE>
If part of the end state of a move is not specified, it is assumed to equal the corresponding part of the start state.
A move defined using tag <MOVE id>...</MOVE> can be referred to later using the variable m_id. Within a MOVE tag, one can make reference to aspects of the previous, current, or succeeding moves (w.r.t. flight path specifried in the command line) using m_prev, m_curr, and m_next.
t_m_blah_F = the absolute time (in seconds since the
flight path started) F percent of the way through move m_blah. F is a
number between 0 and 100) is the time F percent of the way through
move blah. 0 is the start of the move, 100 is the end.
For example, t_m_prev_100 is the time that the previous move ended and equals t_m_curr_0.
t_m_blah_3s is the time three seconds through move blah i.e. t_m_blah_3s = add(t_m_blah_F_0,t_3s). If move blah is less than 3 seconds, the time will be in some following move. (There's also t_m_prev_-4s, t_m_succ_13.31s, etc.)
p_m_blah_0 is the point at the start of move m_blah in the (presmoothed=raw) flightpath while
p_m_blah_100 or p_m_blah_1 is the point at the end of m_blah in the raw flightpath.
For example, p_m_curr_1 = p_m_next_0.
<EFFECT id> <LABEL g_id [... gidn] t_start END=t_y> # turns on labels for groups g_id,g_id2,...,g_idn at time t_x (which could be t_0s) and turns them off at time t_y # Note: one can use LABELON instead of LABEL. <LABEL g_id [... gidn] t_start DUR=t> # ditto, but t_y := t_x + t. <LABEL g_id [... gidn] t_start> # ditto, but labels never turned off <LABELOFF g_id [... gidn] t_start> # labels for these groups turned off at time t_start <ANIM t_start END=t_y STEP=n SPEED=m> # starts animation at time t_start and ends at time t_y # m = number of steps per second (default n=0, m=10) # equiv to m/FPS steps per frame i.e. one step every round(FPS/m) frames <ANIM t_start DUR=t STEP=n SPEED=m> # t_y := t_x + t <ANIM t_start STEP=n SPEED=m> # animation never stops <ON g_id [... gidn] t_x> # turns on groups g_id,g_id2,...,g_idn at time t_x (which could be t_0s or 0) <OFF g_id [... gidn] t_x> # turns off groups g_id,g_id2,...,g_idn at time t_x <FADEON g_id [... gidn] t_x DUR=t ALPHA=a INTERP=k> # turns on groups g_id,g_id2,...,g_idn at time t_x, ALPHA reaches a at time t_x+t # interpolation method is k ([0..1]^k so k=1 is linear, k=2 is quadratic) # or k is LOG or EXP <FADEON g_id [... gidn] t_x END=t_y ALPHA=a INTERP=k> # ditto but ALPHA reaches a at time t_y i.e. t_y := t_x + t <FADEON g_id [... gidn] t_x END=t_y ALPHA=a> # linear interpolation <FADEON g_id [... gidn] t_x DUR=t ALPHA=a> # linear interpolation with t_y := t_x + t <FADEON g_id [... gidn] t_x INTERP=k ALPHA=a> # k-interpolation with t_y := t_x + 5 (i.e. t=5) <FADEOFF g_id [... gidn] t_x END=t_y ALPHA=a INTERP=k> # turns off groups g_id,g_id2,...,g_idn by reducing ALPHA from a at time t_x to 0 at time t_y <FADEOFF g_id [... gidn] t_x DUR=t ALPHA=a INTERP=k> # t_y := t_x + t, k is 1 by default, t is 5 seconds by default <NEARCLIP t_x END=t_y FROM=c0 TO=c1 INTERP=k> # k-interpolate near-clipping plane from c0 at time t_x to c1 at time t_y <NEARCLIP t_x DUR=t FROM=c0 TO=c1 INTERP=k> # t_y := t_x + t, k is 1 by default, t is 5 seconds by default <FARCLIP t_x END=t_y FROM=c0 TO=c1 INTERP=k> # k-interpolate far-clipping plane from c0 at time t_x to c1 at time t_y <FARCLIP t_x DUR=t FROM=c0 TO=c1 INTERP=k> # t_y := t_x + t </EFFECT>
<GROUP id cfnum> # g_id returns gn where gn is declared Partiview CF file. cfnum is of the form gn e.g. g3 or g5
outname blah # final images begin with this (default is "snap") numdec 4 # final images have this many numeric places (e.g. blah0310.png has 4 numeric places) # numdec is always increased so that 10^numdec is larger than the number of frames to be generated. # default is 4 ext png # final images are of this format. Can be ppm or png or jpg (default) frameoffset 1 # final images start counting from here. In this case the first one will be blah0001.png. Default 0. xsize 640 # final images will be xsize x ysize pixels ysize 480 fps 30 # frames per second (default 24) up 0 0 1 # initial value of 'up' (default 0 1 0) vertical 0 # do not maintain vertical (default 1) equivalent to maintainvertical eyesep 0.001 # stereo eye separation (sign doesnt matter) domesize 2048 # final size of dome images (default 2200) dome 0 # if 0, do not generate dome*jump/snap files. Default 1. stereo 0 # if 0, do not generate stereo*jump/snap files. Default 1. smooth 3 # smoothing parameter ; duration of half-window-size in seconds. Default 1. pathscale 0.01 # scaling factor for creating path.cf files (don't use unless you can't figure out psize etc). Default 1.
MOVE tags define a 'move' of a certain duration. The duration is given in seconds, and will later be converted to frames. The following set of tags allow for any kind of movement other than orbiting the camera around a point. It includes rotation of the camera around while keeping it in the same place. Without anything in it is a 'wait for d seconds' move.
The simplest move is the 'wait' or empty move:
<MOVE id> </MOVE>
<MOVE id> <LOOKAT p_a> </MOVE>
Lookat is always interpolated linearly (regardless of the SPEED tag)
<MOVE id> <ROLL d> </MOVE>
If the roll angle of the camera was x at the start of the move, it is d+x at the end of the move.
<MOVE id> <FOV f> </MOVE>
If the field-of-view of the camera was f0 at the start of the move, it is f at the end of the move.
<MOVE id> <CENTER p_center> <NORMAL p_normal> # or <NORMAL p_normal ONLINE> </MOVE>
p_normal is a directional vector of nonzero magnitude, and must be orthogonal to p - p_center for any point p on the circle traced out. We refer to the line passing through the center parallel to the normal as the axis of the move.
To reverse direction in which the circle is traversed, supply the negation of p_normal.
<MOVE id> <CENTER p_center> <NORMAL p_blah ONLINE> </MOVE>
Sometimes, instead of supplying a direction vector p_normal, it is convenient to supply a point p_blah (other than p_center) on the axis. In this case, add ONLINE in the NORMAL tag.
<MOVE id> <CENTER p_center> <NORMAL p_normal> # add ONLINE if p_normal is really just a point on the axis <LOOKAT p_b> <NUMFULLROT n> # n is at least 1 <ROLL d> </MOVE>
<MOVE id> <CAMERA p_a> </MOVE>
p_a can be an absolute point or made relative using 'vector addition' : to move p_b from the start position, p_a can be add(p_prev_100,p_b) - default is start position
<MOVE id> <CAMERA p_a> <LOOKAT p_b> </MOVE>
<MOVE id> <CAMERA p_a> <LOOKAT p_b> <ROLL d> </MOVE>
<MOVE id> <CAMERA p_a> <LOOKAT p_b> <ROLL d> <SPEED EXP> </MOVE>
At a fraction k of the way through the move, the camera is at a point p :
p := p_from + (r0 + f(k)*(p_to-p_from)
f(k) is a logarithmic or exponential function satisfying 0<=k<=1, 0<=f(k)<=1.
This applies to all the other cases above, except with the SPEED tag added.
<MOVE id> <CAMERA p_to> <CENTER p_center> </MOVE>
The camera moves at uniform speed from p_from := p_m_curr_0 to p_to. Suppose r0 and r1 are the distances of p_from and p_to to p_center. If r0 == r1, then the camera path is the section of the r0-radius circle around p_center from p_from to p_to.
If r0 != r1 then at a fraction k (=0..1) of the way through the move, the camera is at point p such that
<MOVE id> <CAMERA p_to> <CENTER p_center> <LOOKAT p_b> <ROLL d> </MOVE>
<MOVE id> <CAMERA p_to> # this must be different from the camera position at the start of the move <CENTER p_center> <LOOKAT p_b> <NUMFULLROT n> <ROLL d> </MOVE>
Again, this actually allows for two possible spirals, but never mind that for now.
If n>0, then n full rotations are completed during the course of the move. n=0 is equivalent to leaving out the NUMFULLROT tag.
(not fully tested yet)
With 2D spirals, the center of rotation remains the same. For 3D spirals, the center changes uniformly from the point specified in the CENTER tag to a second point along the line passing through said center in the direction specified in the NORMAL tag.
<MOVE id> <CAMERA p_to> # this must be different from the camera position at the start of the move <CENTER p_center> <LOOKAT p_b> <NUMFULLROT n> <ROLL d> <NORMAL p_n> # add ONLINE if p_n is point on axis instead of direction </MOVE>
Note: we used the NORMAL tag earlier, when the starting point coincided with the ending point.