|
CS 428 - Fall 2011 Project 3: Modeling and Animation
Due electronically: Wednesday, November 16th, 12 noon |
|
Description
The automatic generation of objects and their motions is required in applications such as games or simulations. This generation starts with a model of a particular phenomena (in this case: rocks, trees, or wandering motion). Ideally, this model takes into account knowledge such as the object's formation, growth, or biomechanics, to produce a greater degree of realism. More typically, such information simply inspires an approach to procedural generation.
Objective
From this assignment, you will understand how structures such as trees and rocks can be generated using procedural methods. You will also implement a behavior-based approach for animating some crawling bugs (where only the leg motions are keyframed).
Program
You will be provided with skeleton code for this program, which supplies the necessary user interface and main program structure. The basic data structures for the rock, tree and bug are provided for you, which you must complete in particular places. You will also need to create the "scene" which contains any number of rocks, trees or bugs. You will mostly be filling in the missing parts in functions (which are marked with "// ..."). In some cases, you will find code that is a placeholder for what you need to write (so that you at least see something where the tree and rock are) -- just remove it once you start on that part. In addition, you will probably have to add code elsewhere, and not just where the comment markers are. So don't feel confined by where these comments are placed; they're a guide.
The skeleton code for this project can be found (on the iLab
machines) in the directory
~decarlo/428/proj3/
Also included is a
Makefile.
This assignment is a bit vague so that you can make a lot of the design decisions yourself. There are several approaches to the problems here. Use your best judgement to try and get the most useful result. Ask for help or clarification when you need it.
Handing in
Hand in the following:
You will need to create a tar archive to hand in, that contains your java files, Makefile, and description:
tar cf proj3.tar Makefile *.java descrip.txtHere are the instructions on how to hand in this file.
This assignment is a bit vague so that you can make a lot of the design decisions yourself. There are several approaches to the problems here. Use your best judgement to try and get the most useful result. Ask for help or clarification when you need it.
Program use
To simply run the program with default options: java Main
There are a number of command line options available (italicized words indicate you need to specify a particular value):
For example, to run the program in "nice" mode with the seed value of 37:
java Main -nice -seed 37
Once the program is running, you can transform the scene, turn on/off animation, or reset the scene.
Making MPEG Movies
It's easy to make MPEG movies of your animations. You should not only do this when your program is completed, but also during its development to record any interesting or amusing "out-takes".
An example movie/image pair are in ~decarlo/428/proj3.mpg and ~decarlo/428/proj3.ppm
The "-dump" command-line switch described above is used to save a numbered series of images. You can use "ppmtompeg" to build an MPEG animation file from these images (type "man ppmtompeg" to see brief documentation). Instead of reading about the details of the encoder, you can use the pre-made encoding file, which tells the encoder where the files are, and how they are to be constructed. All you will need to do is edit the top of the file.
The sample encoding file is on cereal in: ~decarlo/428/encode.txt
Copy this file into your directory. The first few lines of this file specify the input and output files as:
OUTPUT anim.mpg INPUT_DIR /tmp/decarlo INPUT image*.ppm [0000-0299] END_INPUTThe filename given on the OUTPUT line states the file that is created when you run "ppmtompeg". Set INPUT_DIR to "/tmp/your-userid", or whichever dump directory you specified when running your project. Finally, determine the number of images that were dumped by your project (by typing "ls -lt" in the dump directory), and set this in the square brackets. If you don't want to include all the images, that's ok. Be sure to use 4 digit numbers (with leading zeros) so that the filenames are reconstructed properly.
Remember that "/tmp" is on the local machine, so that when you log in somewhere else, the images you left won't be there. Plus, if a machine reboots, your images in "/tmp" on that machine will probably be lost. It's also not a bad idea to clear everything out of that directory each time you make a movie, so you don't confuse which files you just created.
In the above example, the encoder will use the files "/tmp/decarlo/image0000.ppm" through "/tmp/decarlo/image0299.ppm" to produce an mpeg file called "anim.mpg". It uses 300 files: at 30 frames per second, this animation will last for 10 seconds.
If you want to include only a sub-range of the images (say you only wanted to use [0060-0209]), use the image viewing program "eog" to figure out which ones. If you load many images at once using "eog *.ppm", then you can advance to the next/previous one using the left/right mouse buttons (the scroll wheel works too). A useful trick to see every 10th image is to run "eog *0.ppm".
Once you're done, you can play your MPEG animations using "xine anim.mpg".
In summary:
Data structures
The rocks are represented using the Rock class. It contains information about the location and size of the rock, and it stores a two-dimensional array of height values.
The trees are represented using the Tree class, which contains information about the location of the tree, as well as a recursive data structure from the class TreePart which stores the branches and leaves of the tree as a hierarchical model.
Instances of Rock and Tree also implement the Obstacle interface, so they can be treated uniformly as scene objects when drawn, and when doing bug computations (obstacles should be avoided by bugs).
The bugs are represented using the Bug class, which is an extension of the Critter class. (This way, if you want to add another type of critter, it won't be too hard.) The keyframes for the bug are stored in the Bug class, although the Critter.keyframe() method is used to compute the current bug parameters given the time. The state (position, velocity, acceleration) is stored in Critter, which is also where you should compute accelerations, and do the numerical integration for the bug simulation.
The scene is represented using the Scene class, and stores the obstacles and critters in the scene, and has methods which you will need to complete which create and simulate the scene.
Requirements
The following is a description of what you must do for this project.
There are two 2-dimensional arrays: Rock.height stores the height values, and Rock.locked stores flags that are used to initialize the rock. If a locked value is true, then your code should not change the corresponding height value. This allows the center of the rock to be initialized off the ground, which gives the rock it's rough shape. See Rock.compute for details.
You need to complete Rock.computeFractal which is a recursive function that uses random midpoint displacement (of only the height values), as discussed in class. See § 8.23 of the textbook, starting on pp. 480 for details. When working on this, you might consider not adding a random offset at first, as it's easy to have errors here. If you do this, your rock should look like a smooth pyramid, no matter how finely the mesh is created. Also, don't forget that the rock is translated down a bit to yield a more interesting boundary, so you might need to look below the ground plane to see what's happening. Once you complete this part, uncomment the line in Scene.draw that is enabling the clipping plane (which will hide the junk below the ground plane).
The rock is created in Scene.build, and you should make adjustments there (for instance, use a finer mesh).
Methods affected: Rock.compute(), Rock.computeFractal(), Scene.build()
Most of the coding is in class TreePart. (In fact, you probably will only change Tree by adding arguments to the constructor, as you'll need more flexibility in defining your tree).
You need to complete the TreePart constructor which creates a particular tree sub-structure (where the argument depth is zero to represent a leaf -- the base case, and a larger value for branches -- the recursive case; the object type is represented in each TreePart with a flag called leaf). TreePart.draw is the method to draw a tree substructure. You need to apply a transformation to place the part, as well as write code to draw a leaf and the children.
Your tree should not have a regular structure -- it should have some non-trivial randomness in it (vary the branching factor, the length of branches, or the placement of child branches along the parent's limb, throughout the entire tree). This should be done using the methods discussed in class. § 8.23 of the text describes a similar algorithm, applied to 2D displays.
Methods affected: TreePart.TreePart(), TreePart.draw(), Scene.build()
That method should call Critter methods that compute accelerations, and then call Critter.integrate to update the current position based on the acceleration and velocity. Instead of taking one large time step (based on however much time has passed since the last computation), you need to break it down into several smaller steps to prevent numerical errors; so you will need to compute accelerations and integrate many times in succession each time you call process. When you run the program with a higher speed (using a command line argument -speed 5 for instance), the integration will need to take larger steps. As you turn up the speed, the program should remain stable (although if you do it enough, the animation will be choppy as the simulation can't proceed fast enough to be real-time anymore -- that's unavoidable).
To draw the bug in the correct position (and orientation, based on its velocity, so that it's facing the right way) you will need to complete the Bug.transform method. It's easiest to use Math.atan2() when computing the orientation of the bug, as it computes an angle in a 360 degree range. The bug drawing is already done for you in Bug.draw (the bug is facing in the positive x direction). The legs of the bug are specified with keyfames. Implement the Bug.keyframe method to compute the current set of bug parameters given the current time. Note that the keyframes are only provided for times in the range [0,1]; this is because the walking is cyclic, and has a cycle length of 1 second.
When writing Bug.keyframe, do not assume that there will be 4 keyframes in one cycle, or that the keyframes within a cycle are evenly spaced in time. Keep your implementation general. You can easily check that you've done this right by running the program at slow speed (using a command line argument -speed 0.1 for instance). Zoom in and check that the legs are moving smoothly and uniformly.
The rate at which the bug's legs move should depend on the speed the bug is traveling, and the size of the bug. This means you should not use the current time as the argument t to Bug.keyframe, but rather distance traveled taking the size of the bug (Bug.scale) and the length of its stride for unit scale (Bug.stride).
You only need to have 1 bug (see extra credit about having more).
See the notes from class [PDF].
Methods affected: Bug.transform(), Bug.keyframe(), Critter.integrate(), Critter.accelDrag(), Critter.accelAttract(), Scene.process() Scene.build()
Update the critter positions in the scene in Scene.process() where you implement the behaviors that ensure that the bug avoids the obstacles, yet stays in the scene.
The scene is drawn so that x is forward, y is right, and z is up.
Methods affected: Scene.build(), Scene.process()
Extensions
The following is a list of optional extensions. The first two are required for graduate students.
Hints
The following are some suggestions...