Homework 2

In this assignment, you will build a 3-D wireframe renderer from the pieces of your previous assignment. You write a new parser to load the geometry, you will use your matrix library for transformations, and you will use your 2-D line drawer to rasterize the wireframe.

Homework 2 is due on Tuesday Oct. 21 at 11:59 pm

Wireframe lion's head

Parser and the Scene Description Language

OpenInventor File Format Example

We will be using the OpenInventor file format to store our geometry for this lab. You can find several files in the homework 2 data. Here is a simple example:

# Inventor V2.1 ascii  
   # (Above line required for use in actual Inventor environments, for future)
   # <-- Hash symbol means this is a comment until end of line
    PerspectiveCamera {   # Sets up Camera location, field of view, etc.
        position      0 0 1  
        orientation   0 0 1  0  
        nearDistance  1  
        farDistance   10  
        left          -1  # (Not true Inventor command, but we use it anyway).
        right         1   #    " "
        top           1   #    " "
        bottom        -1  #    " " 
     }  
   
   Separator {    # Defines a layer of scoping, to "group" geometric items.   
         Transform {   # rotation, translation, scaling in any order, or absent  
            translation  tx ty tz  # 3 real numbers 
            rotation     axisX axisY axisZ angle  # 4 real numbers for axis-angle rep of rotn
                                                  # this angle is in radians
            scaleFactor  sx sy sz  # 3 real numbers  
         }  # end of Transform

        .  
        .   possibly more transforms. 
        .

       # Note:          
       # Matrices for a series of transform blocks are set up on stacks.
       # The matrix commands are post-multiplied onto the current matrix --
       # The LAST command issued is the FIRST transformation applied to the object.
       # All these Transform blocks are applied to the following object

         # Creates list of 3D pts, named with integers starting at 0
         Coordinate3 {
            point [
                  # Each point is 3 real numbers. First point is indexed by 0
                  x0 y0 z0,
                  x1 y1 z1,
                     ...
                  xn yn zn
            ] # Note that the Coordinate3 list is within the scope of the transform
         }

         # Uses the integer names of the pts to make polygonal faces     
         IndexedFaceSet  {
            coordIndex [   
                       #Face 0, uses point numbers, -1 is terminator   
                       face0point0, face0point1, ... -1,
                       face1point0, face1point1, ... -1,  
                          ...  
                       faceNpoint0, faceNpoint1, ... -1  
          ]  
        }  # Note that the IndexedFaceSet (polyhedron) is in the same scope

  } # End of Separator

OpenInventor Grammar

A corresponding BNF grammar could look something like this (starting symbol is blocks, the notation [symbol] means 0 or 1 of symbol at that point, i.e. a ::= b [c] means a ::= b | b c):

blocks      ::= block [blocks]
block       ::= camerablock | sepblock

camerablock ::= PCAMERA open cameralines close
cameralines ::= cameraline [cameralines]
cameraline  ::= POS triple | ORIENT quad | NDIST NUMBER
              | FDIST NUMBER | LEFT NUMBER | RIGHT NUMBER
              | TOP NUMBER | BOTTOM NUMBER

sepblock    ::= SEPARATOR open sepitems close
sepitems    ::= sepitem [sepitems]
sepitem     ::= TRANSFORM open translines close
              | COORD3 open POINT sqopen triples sqclose close
              | IFACESET open ifslines close
translines  ::= transline [translines]
transline   ::= TRANSLAT triple
              | SFACTOR triple
              | ROT quad
ifslines    ::= ifsline [ifslines]
ifsline     ::= COORDINDEX sqopen singles sqclose

triple      ::= NUMBER NUMBER NUMBER
triples     ::= triple [COMMA triples]
quad        ::= NUMBER NUMBER NUMBER NUMBER
open        ::= LBRACE
close       ::= RBRACE
sqopen      ::= LBRACKET
sqclose     ::= RBRACKET

Rasterizing the scene

Data structures

You should implement data structures that are suitably flexible to be expanded for the next assignment. As you parse in the data, building a nested structure similar to the input format will simplify the rendering of the scene.

Iterating through Separators and rasterizing lines

Iterate through the Separator blocks, and for each Separator, multiply together all its transforms (in the proper order). Multiply this matrix by each point in each polygon (constructed from indices from the IndexedFaceSet and the Coordinate3 points). For each pair of vertices in the polygon, call your line rasterization function; spit out the resulting canvas as a .ppm file.

Transforming and coordinate space conversion

Transform blocks

A Separator block can contain multiple Transform blocks. Each block can can have a translation, rotation, or scaling. These are all optional and may be specified in any order; however, there can be no more than one of each. When you build a matrix from a transform block, scaling should be applied first, then rotation, then translation. Thus the formula should be S = TRS.

Object Space to World Space

Just multiply the 1 or more transformation matrices and create a single object matrix O . The first transform node encountered within a separator node is applied last, i.e. goes on the left side of all object transform matrices.

World Space to Camera Space

In the camera node, two sets of numbers, orientation and position, are specified. The first three numbers after orientation give you a rotation axis, and the last number is an angle. The three numbers after position describe a translation vector. The camera was first rotated by some angle around some axis (described by orientation) and then translated by some vector (described by position) with respect to the global coordinate system. This has an effect equivalent to applying the inverse transformation to every other point in the system. Let C = TR be the matrix describing the transform applied to the camera. There are two ways to get the inverse: you can either use the inverse function from your matrix library (C-1 = (TR)-1), or you can use the special formulae for inverses of translation and rotation matrices. If you choose to use the formulae, remember that when you invert a product of matrices, you get the product of inverses with the order reversed, so (C-1 = R-1 T-1, which is equivalent to translating by the negative vector and then rotating by the opposite angle (-theta) around the same axis.

The fourCubes.iv top left cube will tell you if implemented TRS in the right order.

Camera Space to NDC (Normalized Device Coordinate) Space

All you need to do here is take the numbers for top, bottom, left, right, near and far, and plug them into the formula for Perspective Projection as t, b, l, r, n, and f respectively. The formula is given on the page about Homogeneous Coordinates and Transformation Matrices.

Make sure you use perspective projection and not orthographic. This maps from some frustum shape (truncated wedge shape 6-hedron) to a cube).

Let O = O1*...*On = object transform clauses
C = camera transform (that's applied to the camera)
P = perspective projection

Then for each point, x (which has been homogenized by adding a fourth w corrdinate of 1.0) the transformed point x' = P*C-1*O*x
Take these x', divide by w, and plug them into the rasterizer (ignoring z at this point-- use just the new x and y coordinates) so that they draw polygons as described by your indexed face set.

What to submit

Your 3D wireframe program should be called wireframe and take the following arguments:

wireframe [xRes] [yRes]

where xRes, yRes are the dimensions of the image that will be drawn

The two integers xRes and yRes indicate the size of the image to be made.

Input

Input should be read from stdin. The input format is just the OpenInventor format discussed at length above.

Output

The output image should be written to stdout in the PPM file format, as discussed in assignment 1.

To view the result image, you can pipe the output of your program to display:

./wireframe 400 400 < cube.iv | display -

You can also save to a file using convert:

./wireframe 400 400 < cube.iv | convert - cube.png

Components

Now that you've got the canvas and lines and transformations from the previous assignment, you can string them all together with your .iv parser:

  1. Parse the .iv file
  2. Loop on the Separator -> Transform -> IndexedFaceSet -> Coordinate3 polygon/vertex input
  3. Multiply out the transform matrices
  4. Transform your points with your transform library
  5. Draw lines onto the canvas
  6. Output in the PPM format

You can use whatever colors you like, although the sample results are white on black.

Testing

A few test cases are provided. You can also develop your own test cases.