Overview
In this project you will implement a simple sketch-based modeling
system. The basic system supports two kinds of
primitives (or basic shapes): boxes
and cylinders. Extended versions of the system support additional
primitive types such as spheres, ducts, surfaces of revolution, and
extruded shapes. Each primitive type is matched to a set of input
gestures, which are 2D strokes drawn by the user. The user can thus
create a 3D scene interactively by "sketching" various primitives.
In addition to sketching shapes with the left mouse button, the user
can move existing shapes
around using the middle button. Every shape is
given a constraint that limits how the shape moves when grabbed by the
user via the middle mouse button. Initially, each object is constrained
to move in the plane of the surface on which it was created. The user
can interactively define new constraints to move the object in a
different plane, or along a given direction. Primitives are organized
into a hierarchy via nested transforms. When a new primitive is
sketched on top of an existing one, it is added to the scene as a
"child" of the existing one, meaning it is defined in the coordinate
system of the existing primitive. When the existing one is moved, all
of its children automatically move with it. But when a child is moved,
it moves independently.
This project is loosely based on the SIGGRAPH 1996 paper:
SKETCH: An
Interface for Sketching 3D Scenes. (We will see the video in class.)
You are given support code for this project and will need to
- link to the jot library
(the same as for projects 1 and 2),
- copy the project support code, follow the instructions in the README file to compile, and
- implement the missing functionality.
For more information about
jot, see
the instructions given in
project 1.
Support Code
Copy the project support
code to your local machine. (On windows, you can execute the
following commands using cygwin, which we recommend):
% scp -r red.engin.umich.edu:/afs/engin.umich.edu/class/perm/eecs487/proj3 .
(Note the 'dot' at the end of line.) The above command overwrites all
proj3 files, including any files that
you may have made changes to. To copy 'old' versions of files while
leaving the new untouched (sync-ing operation):
% rsync -avz red.engin.umich.edu:/afs/engin.umich.edu/class/perm/eecs487/proj3 .
(Do this from the parent directory containing the proj3 subdirectory.)
Windows users can use set up
an sftp session to red.engin.umich.edu and
grab the files from the above path or, on (CAEN) machines with AFS
mount, copy the files from:
K:\perm\eecs487\proj3
Note: the above path depends on how CAEN space is mounted on your PC.
See the project 3
README file if you need
help mapping a network drive to connect to the course directory.
The C++ source code files are:
axis.C
axis.H floor.C floor.H node.C node.H node_manip.C node_manip.H p3.C
sketch_pen.C sketch_pen.H.
These provide useful functionality, but not everything is implemented.
Read through these
files to get
familiar with them, then implement the following missing functionality
(usually marked by the phrase "YOUR CODE HERE"). You are permitted to
modify any of the provided source files as you see fit.
Basic primitives:
- Complete the unfinished parts of sketch_pen.C that handle the
creation of boxes and cylinders. The
provided support code already
checks the user's input strokes and classifies them according to
various types, such as straight line, tap, and curved stroke. In the
function SketchPen::line_cb(),
each
straight line drawn by the user is compared to three "canonical" 3D
axes (described below). When a canonical axis (projected to image space
at the start of the user's stroke) and user-drawn 2D line are
sufficiently
parallel (in screen space), the support code creates a 3D line segment
(using class AXIS,
defined in axis.H)
that is parallel to the canonical axis and whose length corresponds
to the user's stroke. The AXIS
class keeps a list of all currently active axes in the scene. This list
should be cleared every time a new primitive is finished, or when the
user makes a "tap" gesture to cancel the current axes (see SketchPen::tap_cb()).
In the function SketchPen::line_cb(),
if no axes currently exist, the system should record the (world space)
3D point p and surface normal n corresponding to the start of the
user's stroke, as well as the scene object G that contains p. (G
is of type NODE, a
subclass of GEL, which
stands for "geometric element" and represents a generic object in jot
that can be added to the sccene.)
- When the user draws 3 mutually perpendicular axes, the
function SketchPen::create_box()
is called to create a box and add it to the scene.
The provided code shows how to create a new NODE N to represent the box, and add it
to the scene as a child of G
(meaning G's transform also
affects N). However, the code
does not assign the correct transform T
to N. The desired end result
is to map
the canonical cube (created in the support code) to world space so that
it matches the orientation and dimensions of the axes. E.g., you could
define the desired box to be the minimum box that is aligned to the 3
axes and contains
them. Alternately, you can make the simplifying assumption that the
three axes either start or end close to p. This makes some input illegal
that might otherwise be handled, but is probably easier to code. You
can proceed either way. In any case, T should be chosen so
that, when combined with the transform from G's object space to world
space, the canonical cube is scaled, rotated, and translated to
match the desired shape. The helper function SketchPen::obj_to_world()
returns the matrix that maps from G's
object space to world space, and SketchPen::world_to_obj()
returns the inverse matrix. 15 points
- If the user draws 2 parallel axes instead of 3 perpendicular
ones, the function SketchPen::create_cylinder()
is called to create a cylinder. Complete the implementation of this
function to create a cylinder that is parallel to the two axes (in
world space), whose base is adjacent to p, whose height is the length of the
first axis, and whose diameter is chosen so that the cylinder lies
approximately between the two axes (in image space). 15 points
Whether the user creates a box or cylinder, you should call the method NODE::set_plane_constraint() to
restrict N to move in the
plane perpendicular to n.
(This default plane constraint can be changed by the user, as described
below.)
- The provided support code in SketchPen::line_cb() is not
very smart about how it chooses the
"canonical" axes x, y, and z: it just takes x = (1,0,0), y = (0,1,0),
and z = (0,0,1). You should
change the code in SketchPen::line_cb()
so
that y = n (the surface normal mentioned
above), and x , y, and z form a right-handed orthonormal
basis.
Preferably, x and z should line up with the features
of the parent node G. E.g., if
G represents a cylinder,
then at a point on the side of the cylinder, x and z would span the tangent plane, with
x pointing along the length
of the cylinder and z pointing
across it. If G is a box, the
directions should line up with the axes of the box. 10 points
Moving objects:
- The NodeManip class
defined in node_manip.H
is intended to let
the user move objects around by grabbing them with the middle mouse
button. Mouse down, motion and up events trigger callbacks to NodeManip::down(), NodeManip:move(), and NodeManip::up(), respectively.
Finish the implementation of these functions to move objects according
to the user's input stroke. 15 points.
In NodeManip::down(),
determine the
node N
(if any) grabbed by the user, and check whether N is constrained to move parallel to
a given 3D plane (if NODE::constrain_plane()
is true) or line (if NODE::constrain_line()
is true). Then find the 3D point x
on N's constraint plane or
line that is closest to the current mouse location. To do this,
construct a 3D line from the mouse location (use class Wline), and find its
intersection with the constraint plane or line (using Wplane::intersect(Wline) or Wline::intersect(Wline)).
In NodeManip::move(),
find the new point x' on the
constraint line or plane corresponding to the new mouse location. The
desired translation is then x'
- x. This translation should
be expressed in the coordinate
system of N's parent. Apply
the translation by calling
the method NODE::mult_by()
(inherited from the base class GEOM).
After applying the translation, replace the "old" location x with the new one: x = x'
(to be used the next time NodeManip::move()
is called).
- Add the ability to define new constraints for nodes. If the user
grabs a node when a single
axis is active in the scene, in NodeManip::down()
you
should define a new constraint (via Node::set_line_constraint())
for the node that
restricts it to move parallel to the axis direction. If there are 2
active axes, and they are not parallel, you should define a plane
constraint for the node (via Node::set_plane_constraint())
restricting it to move parallel to the plane
whose normal is the cross product of the two axes. After creating
a new node, always clear the set of active axes using AXIS::clear_axes(). 15 points.
Renderer:
- Implement a custom renderer for your sketch system. Something
based on toon shading and/or sketchy lines rendered along silhouettes
and creases (or both) could be appropriate, but you are free to
implement any rendering style you choose. It is okay to use GLSL. If
you do, please mention it in your write-up so we can be sure to run
your system on a suitable platform. 10
points.
Scene
design:
- Use your system to create an interesting scene, including
geometry, lighting design, and rendering style. Render the scene from
an interesting viewpoint, grab a screen shot and and show the class by
posting the image on the phorum as an attachment. Please use
the thread dedicated to project 3 images. You can post multiple
images. (Please include your images in the directory that you hand in
as well.) 5
points.
Write-up:
- Described below. 5 points.
Code quality:
- Comment your code reasonably, particularly your design decisions
and choice of approach.
- Modularize your code: use a suitable number helper functions with
descriptive names.
- Use descriptive variable and function names: apply a suitable
tradeoff between symbol-length and descriptiveness.
Bottom
line: Make your code readable!
10
points
Extended system (up to 15 bonus
points):
The following are optional, but can be attempted by ambitious
students:
- Additional primitives.
Implement additional primitives, mapping them to gestures of your
choice. Possible ideas are: sphere, duct (i.e., a tube that
follows the path of a curve in 3D), surface of revolution, and extruded
shape defined from a planar shape. Corresponding gestures might be as
follows. Sphere: draw a circle and tap the center. Duct: draw a circle,
then draw a free-hand stroke from the center of the circle. Surface of
revolution: draw an axis, then draw a "profile" curve indicating the
shape to sweep around the axis. Extrude: first draw a planar shape by
drawing its outline, then draw an axis perpendicular to it to define an
inflated shape composed of 2 copies of the outline shape offset from
each other, and joined with a ruled surface. For any primitive you
contruct, consider assigning texture coordinates as well. These can be
used in your renderer, either to apply conventional texture maps or for
more elaborate effects like bump mapping (or both).
- Additional constraints.
Add support for an additional type of motion constraint:
rotation. If the user grabs a node with the middle button while a
single axis is active in the scene, then the constraint should depend
on the user's initial motion (the first few mouse motions while the
cursor is still near the down point). If the motion is roughly parallel
to the axis, use a line constraint as before. But if the motion is
cross-wise to the axis, activate a rotation constraint that restricts
the node to rotate around the rooted vector defined by the axis.
Handing
in
Turn in your proj3 directory, including:
- all your project 3 source files
- a subdirectory, 'images', containing images you created (e.g.
anything you posted on the phorum)
- no model files or binary files please!
- a brief
write-up in text
format that discusses:
- anything about your
implementation that is noteworthy, and
- feedback on
this assignment, including suggestions for how it should be changed
next time.
Name your file
writeup-<uniqname>.txt .
Example:
writeup-rmanoj.txt
Copy your files
to the following
directory on IFS:
/afs/umich.edu/class/eecs487/f06/submit/<uniqname>/p3/
This path is accessible from any machine you've logged into
using your ITCS (umich.edu) password. Report problems to ITCS.
- The timestamp on your key files (p3.C and your writeup) will
indicate the time of submission and if this is past the deadline your
submission will be considered late. Therefore, you are allowed multiple
'submissions' as long as you respect the deadline.
- Test
the compilation: Your submission must compile without errors and
warnings (except those from external libraries like OpenGL, JOT etc.)
on CAEN Linux or Windows machines. Mention
the platform in your writeup. Code that doesn't compile will be
heavily penalized. (There is no need to compile on IFS; we will do the
grading using the CAEN setup.)
Multiple
submissions:
- You are allowed to overwrite your files in the above directory as
many times as you want.
- If the timestamp on these files is past the deadline your
submission will be considered late.
- Test this submission procedure and please let the GSI know well
in advance if you encounter and problems.
Due date
The project is due on November 8, 2006, by 11:59pm. Turning it in 48
hours earlier is worth a 4% bonus; turning it in 24 hours early is
worth a 2% bonus.
Last
updated: October 31, 2006.