tabinterp - combine and interpolate multiple data files to create an animation script
tabinterp > table.final
tabinterp reads a series of commands from standard input which designate what parts of various data files should be used as input tables for various channels of animation parameters. Commands may extend across multiple lines, and are semi-colon (';') terminated. Each channel is then interpolated using one of a variety of interpolation techniques to provide an output table which has one line for each time step.
The overall notion is based on parameter tables. Each table is arranged so that every row (line) represents the state of some set of parameters at a given time. Each column of the table represents a single parameter, or data channel, with the left-most column always representing time.
The first task in preparing to use tabinterp (1) is to assign specific purposes to each channel in the output table. For example, channels 0, 1, and 2 might be used to represent the X, Y, and Z positions of an object, respectively, while channels 3, 4, and 5 might be used to represent the "aim point" of the virtual camera, while channel 6 might be used to represent the brightness of one of the objects or light sources, and channel 7 might be used to represent the zoom factor (viewsize) of the virtual camera. Once the channel assignment has been decided upon, the source file containing the table of raw values for each channel must be identified. Several output channels may get their raw values from different columns of a single input table (file). Up to 64 columns of input may appear in an input table.
For each file which contains an input table, the file command is given to load the necessary columns of raw values into the output channels. If a channel number in the list is given as a minus ('-'), that input column is skipped. Using the output channel assignments given above as an example, if an input table named "table1" existed which consisted of five columns of values representing (time, brightness, objX, objY, objZ), then these values would be loaded with this command:
file filename chan_num(s); file table1 6 0 1 2;This command indicates that from the file "table1", the current time and four columns of parameters should be read into the raw output table, with the first input column representing the time, the second input column representing the value for output channel 6 (brightness), the third input column representing the value for output channel 0 (objX), etc. Each row of the input file must fit on a single (newline terminated) line of text, with columns separated by one or more spaces and tabs.
After all the file commands have been given, it is necessary to define over what range of time values found in the raw output table will be processed, and how many rows of interpolated output should be produced for each second (time unit) in the input file. This can be thought of as the "frames per second" rate of the interpolation, and is usually set to 24 for film (cine) work, 30 for NTSC video, and 60 for field-at-a-time NTSC video. Any positive integer value is acceptable. (In fact, any time unit can be used, as the time channel is dimensionless. Nothing depends on the units being seconds.) For example, the command:
times start stop fps; times 1 7.3 24;would cause tabinterp to process data values from time 1 second to 7.3 seconds, producing 24 output rows uniformly separated in time for the passage of each second.
After the times command has been given, it is necessary to associate an interpolator procedure or a "value generator" procedure with each output channel. The available interpolator procedures are: step, linear, spline, cspline, and quat For example, the command:
interp type chan_num(s); interp linear 3 4 5;would indicate that output channels 3, 4, and 5 (representing the camera aim point) would be processed using linear interpolation. If only a starting and ending values are given in the input (i.e. the input file had only two rows), then this is an easy way of moving something from one place to another. In this case, if more than two input rows had been provided, there would be a noticeable "jerk" as the camera passed through each of the input parameter values, an effect which is rarely desired. To avoid this, the spline interpolator can be used, which fits an interpolating spline (with open end conditions) through the given data values, resulting in smooth motion. If the starting and ending values are the same, a continuous spline (with closed end conditions) can be used instead by specifying cspline. Both of the spline interpolators require at least three rows to have been provided in the input file.
If the output values are to "jump" from one input value to the next, (i.e. no interpolation at all is desired), then specify step. This can be useful for having lights switch between several intensities (for example, a 3-way bulb with 30, 70, and 100 watt settings), or for having objects "teleport" into position at just the right moment.
The interpolation method indicated on the interp command is assigned to all the output channels listed. One exception to this rule is the quat (Quaternion) interpolator. Quaternions are used to describe an orientation in space, and can be most easily thought of as containing a vector in space, from which they obtain a pointing direction, and a "twist" angle around that vector. To do this, quaternions are processed in blocks of four channels, which must be numbered sequentially (e.g. channels 7, 8, 9, 10). Giving the command
interp quat 7 15;assigns the quaternion interpolator to two blocks of four channels, the block starting with channel 7 (e.g. channels 7, 8, 9, 10), and the block starting with channel 15.
tabinterp is strictly an interpolator. It will not extrapolate values before the first input value, nor after the last output value. The first or last value is simple repeated.
In addition to interpolation, it is possible to specify rate and acceleration based output channels. In cases where the exact running time of a scene is not known, the rate and accel commands can be quite useful. One command is given for each output channel. For example,
rate chan_num init_value incr_per_sec; rate 6 1.5 0.5;says to make channel 6 a rate based channel, with the initial value (at time=0) of 1.5, linearly increasing with an increment of 0.5 for the passing of every additional second. In this case, the value would be 2.0 at time=1, 2.5 at time=2, and so on. This can be used to establish linear changes where it is the increment and not the final value that is important. For example, the rotation angle of a helicopter rotor could be specified in this way.
Similarly, the command
accel chan_num init_value mult_per_sec; accel 5 10 2;says to make channel 5 an acceleration based channel, with the initial value at time=0 of 10.0, which is multipled by 2 for every additional second. In this case, the value would be 20.0 at time=1, and 40.0 at time=2. This can be useful to create constant acceleration, such as a car accelerating smoothly away from it's position at rest in front of a stop sign. If the initial value is zero, all subsequent values will also be zero.
Sometimes it is desirable to create an output channel which looks ahead (or behind) in time. For example, a good way to animate a rocket flying on a complex course would be to simply animate the position of the base of the rocket, and then look ahead in time to see where the rocket is going to go next in order to determine where to aim the nose of the rocket (by rotating it). This kind of lookahead is easily implemented using the next command. (See also the fromto directive in tabsub (1) which is used in conjunction with this). The command
next dest_chan src_chan nsamp; next 4 5 +3;says to fill channel 4 with the values that will be present in channel 5 at 3 output rows later on. Negative values are also permitted. Since the lookahead is defined in terms of output rows rather than time steps, this means that the values generated for this column will change as the frames per second (fps) value on the times command is changed. This is almost always the effect which is desired, since as the temporal resolution of the interpolation is increased, the accuracy of the look-ahead will increase as well. However, if the effect desired is one of "have the camera track where the main actor was three seconds ago", then the number of steps given here will have to be changed when the fps value is changed. Be careful of the values generated for the last (or first) nsamp output rows. Looking forward or backward in time beyond the bounds of the interpolation will retrieve the last (or first) output values. So it takes nsamp output rows to "prime the pumps".
Whenever a pound sign ('#') is encountered in the command input, all characters from there to the end of the input line are discarded. This is the same commenting convention used in the Bourne shell, sh (1).
When tabinterp encounters an end-of-file on it's standard input, it computes the requested interpolations, and writes the output table on standard output. If no values have been assigned to an output channel, then the value given is a single dot ('.'). This preserves the positional white-space-separated columns nature of the output table. If this column is read as a numeric value by a downstream program, it will be accepted as a valid floating-point zero.
As an aid to debugging, it is possible to dump the raw values of columns of the output table before the interpolation is run:
idump; idump chan_num(s);If no output channel numbers are given, all channels are dumped, otherwise only the indicated channels are dumped.
The help command can be given to get a list of all available commands. (Don't forget the semi-colon).
#!/bin/sh cat << EOF > table.aim -1 0 0 0 42 250 3 1 2 3 28 300 7 3 4 5 17 350 EOF cat << EOF > table.obj 0 17 38 44 2 43 47 3 4 99 23 18 EOF tabinterp << EOF > table.final # Channel allocations: # 0,1,2 objX, objY, objZ main actor position # 3,4,5 aimX, aimY, aimZ camera aim point # 6 light brightness # 7 viewsize # # Input table column allocations: # time, aimX, aimY, aimZ, junk, viewsize file table.aim 3 4 5 - 7; # # Input table column allocations: # time, objX, objY, obxZ file table.obj 0 1 2; # Channel 6 is not read in here, # but is rate base. # # Tstart, Tstop, fps times 0 4 30; # # Assign interpolators to output channels rate 6 1000 50; # 1000 lumen bulb keeps getting brighter... interp linear 0 1 2; interp spline 3 4 5; interp spline 7; EOF
Try clipping this example out of the manual page (usually found in /usr/brlcad/man/man1/tabinterp.1) and running it. This example will be continued in the manual page for tabsub (1).
Because both the input and output tables consist of a single line of text for each time step, many of the standard UNIX tools can be brought to bear to assist in creating an animation. To visualize the exact position taken by the aim point in the example (output channels 3, 4, 5), a UNIX-plot file of that trajectory can be created with:
cut -f5,6,7 table.final | xyz-pl > aim.pl cut -f1,5,6,7 table.final | txyz-pl > aim.plSimilarly, the position of the main object can be viewed with
cut -f2,3,4 table.final | xyz-pl > obj.pltabinterp uses 0-based column numbering, while cut uses 1-based column numbering. Also, the first output column from tabinterp is always the time. The 0-th data column comes second.
The plot file just created can be viewed using pl-fb (1) or pl-sgi (1), or it can be viewed in mged (1) by giving the command
overlay aim.plto mged. If the model geometry is brought into view using the mged e command, then the camera aim track (or any other spatial parameter) can be viewed in direct relationship to the three dimensional geometry which is going to be animated.
The mged (1) savekey and saveview commands can be very useful for creating the input tables necessary for driving tabinterp. The details of doing this are beyond the scope of this manual page.
The awk (1) command can also be useful for routing through the output files of existing scientific analysis programs, and extracting the few gems of data burried in the heaps of "printout".
tabsub(1), xyz-pl(1), txyz-pl(1), cut(1), paste(1), rt(1), mged(1)
In it's present form, the program is a bit verbose, reporting on the progress of each command on standard error. This behavior will probably be placed under control of a -v flag in a future version.
You can't grep dead trees.
Michael John Muuss
The U. S. Army Research Laboratory Aberdeen Proving Ground, Maryland 21005
Reports of bugs or problems should be submitted via electronic mail to CAD@BRL.MIL.
tabsub - macro expand an input table into an animation script
tabsub template_file < table.final >> script
tabsub takes as input a data table on standard input (such as might have been produced by tabinterp (1) or similar tool), and a template file named on the command line. For each row (line) of the input table, one complete copy of the template file is output on standard output. As the template is output, any macro invocations in the template file are replaced with the data values from the input table's current row. In the input table, any blank lines or lines with a pound sign ('#') as the first character are ignored, allowing comments to be added to the input table.
Macro invocations in the template file all begin with an at-sign ('@'). In order to send an at-sign through to the output, a second at-sign must immediately follow it, e.g. when '@@' is encountered in the template, a single '@' is output. To output the data value found in a given channel in the current input row of the data table, the at-sign is followed by the channel number, e.g. to output the value in channel four, specify '@4', and to output the value in channel 42, specify '@42'. In some circumstances it my be desirable to highlight the difference between channel value substitution, and literal numeric values. To facilitate this, the channel number may be enclosed in parenthesis to explicitly delimit the macro invocation. For example, channel four could also be specified as '@(4)', and channel 42 as '@(42)'. This second notation is generally preferred.
The tabsub program is intended primarily for creating scripts relating to animation. To facilitate this, a variety of more complex macros also exist.
@(line)will output the row (line) number of the input table which is currently being processed, with the first line being numbered zero. This is useful for creating frame numbers, or other sequence tags in the output.
@(time)will output the time value which is always found in the left-most column of the current row.
The more complex macros can also take arguments. If the first character of an argument is an at-sign ('@') (or percent-sign ('%'), for backwards compatibility), then the number that follows signifies an input channel substitution as before. Otherwise the value is taken literally.
The rot macro is used to convert three Euler angles given in degrees into a rotation expressed as a 4x4 homogeneous transformation matrix.
@(rot x_angle y_angle z_angle)The arguments may be either numeric constants, column value macros, or a combination of both. The matrix is generated by calling the librt (3) routine mat_angles which performs the rotation around the Z axis first, then Y, then X. For example, the macro
@(rot 0 0 45)creates the following matrix, a 45 degree rotation about Z:
7.071067812e-01 -7.071067812e-01 0.000000000e+00 0.000000000e+00 7.071067812e-01 7.071067812e-01 -0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 1.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 1.000000000e+00Similarly, the macro
@(rot @4 @5 90)creates a rotation matrix where the angle of rotation around X is taken from input channel four, the Y angle is taken from input channel five, and the Z angle is fixed at 90 degrees.
The xlate macro converts three distances (which must be specified in millimeters if the output script is destined for processing by rt (1) or mged (1)) into a translation expressed as a 4x4 homogeneous transformation matrix.
@(xlate dx dy dz)The matrix is generated by invoking the C macro MAT_DELTAS found in h/vmath.h. For example, the macro
@(xlate 100 -20 300)creates the following matrix:
1.000000000e+00 0.000000000e+00 0.000000000e+00 1.000000000e+02 0.000000000e+00 1.000000000e+00 0.000000000e+00 -2.000000000e+01 0.000000000e+00 0.000000000e+00 1.000000000e+00 3.000000000e+02 0.000000000e+00 0.000000000e+00 0.000000000e+00 1.000000000e+00Similarly, the macro
@(xlate 13 @7 0)creates a matrix where the origin is translated 13 units (mm) in X, and the number of units found in input channel 7 in Y. No translation occurs in Z.
The orient macro combines the operation of the rot zand xlate macros, and also offers optional scaling. The invocation is one of:
@(orient tx ty tz rx ry rz) @(orient tx ty tz rx ry rz scale)where all rotation is done first, then the translation, and then the scaling (if given).
The ae command converts mged (1) style azimuth and elevation angle given in degrees into a rotation expressed as a 4x4 homogeneous transformation matrix.
@(ae azimuth elevation)The matrix is generated by calling the librt (3) routine mat_ae
The quat command converts a quaternion into a 4x4 homogeneous transformation matrix.
@(quat x y z w)
The fromto command is used to rotate the given axis to point in the same direction as the vector formed by subtracting the 'next' point from the 'cur' point.
@(fromto axis cur_x cur_y cur_z next_x next_y next_z)The axis argument must be one of these six strings: +X, -X, +Y, -Y, +Z, -Z, where the axis letter is capitalized. The matrix is generated by calling the librt (3) routine mat_fromto where the 'from' argument is derived from the axis given, and the 'to' argument is the unit-length difference 'next'-'cur'.
Based upon the example started in the manual page for tabinterp (1), here is a Bourne shell script which will generate the necessary template file using a "here document", and then process the 8-channel output table left in the file "table.final".
#!/bin/sh # This template will be instantiated # once for each frame to be made. cat << EOF > template start @(line); clean; lookat_pt @(3) @(4) @(5); viewsize @(7); anim all.g/actor.g matrix rmul @(xlate @0 @1 @2); anim all.g/light.r material rparam inten=@(6) angle=70 invisible=1; end; ! framedone.sh actor.pix.@(line); EOF # This is the start of the animation script, # which will be appended to below. cat << EOF > script viewsize 3000; eye_pt -4.429280979044739e+03 -1.633722950749571e+03 -1.624787858562220e+03; orientation 5.435778713738288e-01 4.980973490458696e-01 4.564221286261679e-01 4.980973490458693e-01; #frame data follows EOF # Append the data for each frame tabsub ./template < table.final >> script
The frame number is taken from the input table line number, and substituted into the start command. The main actor position is taken from channels 0,1,2 and applied (as an "articulation") to the matrix located along the arc between "all.g" and "actor.g" in the mged database. The camera (eye) position stays fixed for this animation, but the camera orientation is changed by substituting channels 3,4,5 into the lookat_pt command, and the viewsize (zoom lens setting) is changed by substituting channel 7 into the viewsize command. The argument to the light region's material property string is replaced with a new string that spells out the current light parameters. After the end command, a rt (1) shell escape is constructed, which will run a script called "framedone.sh" with the given argument (which has been arranged to be the file name of the pix (5) file that rt (1) just wrote, so that it can be post-processed, compressed, sent to a video recorder, etc.
Try clipping this example out of the manual page (usually found in /usr/brlcad/man/man1/tabsub.1) and running it.
In the tabinterp (1) manual page, mention was made of animating the flight of a rocket. This partial example outlines how that might be accomplished.
tabinterp << EOF > rocket.final # Channel allocations: # 0,1,2 position of base of rocket # 3,4,5 next position of base of rocket # # Input table column allocations: time, X, Y, Z file rocket.table 0 1 2; # times 0 4 60; # # Assign interpolators to output channels interp spline 0 1 2; # # Get +1 "look ahead" on values, for auto-guidance next 3 0 1; next 4 1 1; next 5 2 1; EOF cat << EOF > rocket.template start @(line); clean; anim all.g/rot.g matrix rmul @(xlate @0 @1 @2); anim rot.g/rocket.g matrix rmul @(fromto +Z @0 @1 @2 @3 @4 @5); end; EOF tabsub ./rocket.template < rocket.final >> script
The items worthy of note are the use of the tabinterp (1) next command to place the position look-ahead into channels 3,4,5 and the matching use of the tabsub fromto macro to convert the current and next positions into an appropriate rotation. In this case, the central axis of the rocket as found in the mged (1) database rises up the +Z axis. Translating the rocket into position is handled one matrix higher up the tree, using the xlate macro.
rt style animation scripts can be processed by rt (1) and remrt (1) by giving the -M option on the command line, and providing the script on standard input. For example, the rocket animation might be run like this:
rt -M -V4:3 -w1440 -n972 -p90 -o rocket.pix \ rocket.g all.g < scriptto produce images in NTSC ("Academy" 4:3) aspect ratio at double the normal resolution, suitable for later processing by pixhalve (1).
The same animation can be previewed in near real-time using mged (1). For this example, mged (1) would be started with
mged rocket.gfollowed by attaching to an appropriate display device. Then, these commands would be given:
e all.g preview scriptmged (1) will process each frame as fast as it can, and update the screen.
The U. S. Army Research Laboratory Aberdeen Proving Ground, Maryland 21005