Required Features for Menger Sponge Milestone 1


Menger Sponge Only Milesone

Due: Feb 27, 2024

The assignment is broken down into several features described below. Each feature is worth a number of points and will be graded separately (although later feature may build on and require a working implementation of earlier features). Your code should be correct and free of segfaults, deadlocks, or other buggy behavior. You will not be graded on software engineering (use design patterns and unit tests if they help you) but unreadable code is less likely to receive partial credit. You should not worry too much about performance or optimizing your code, but egregiously inefficient implementations of any of the features will be penalized.

(0 pts) Build

Your submitted archive should build smoothly when make-menger.py is run and should yield a web package that executes without fatal erros on common browsers (Chrome; Firefox; etc). Code that does not build or run will lose anywhere between 5 to 100 points, depending on the time and effort it requires the TA to fix your code. It is your responsibility to thoroughly test your submission.

(30 pts) Create the Menger Sponge

Your first task in this assignment is to procedurally generate the Menger sponge. The sponge is an example of a fractal---its geometry is self-similar across scales, so that if you zoom in on one part of the sponge, it looks the same as the whole sponge. The full sponge is thus infinitely detailed, and cannot be rendered and manufactured; however, better and better approximations of the sponge can be created using a recursive algorithm. Let L be the nesting level of the sponge; as L increases, the sponge becomes more detailed. Given L, the algorithm for building the L-level sponge is listed below.

Sponge generation code

Start with a single cube
for i = 2 to L do
    for each cube do
        Subdivide cube into 27 subcubes of one third the side length
        Delete the 7 inner subcubes
    end for
 end for

Notice that for \( L = 1 \), the Menger sponge is simply a cube. You will write code to generate the Menger sponge for \( 1\le L \le 4 \) , and modify the keyboard callback so that pressing any key from 1 to 4 draws the appropriate sponge on the screen. All sponges should fill a bounding box whose diametrically-opposite corners are \( (m, m, m) \) and \( (M, M, M) \), where \( m = -0.5 \) and \( M = 0.5 \).

Implementation Details and Hints

You will want to write a function which, given a list of existing vertices (in homogeneous coordinates) and triangle indices, appends the vertices and triangles necessary to draw a cube with diametrically-opposite corners at arbitrary locations (minx, miny, minz) and (maxx, maxy, maxz). This will entail appending the eight cube vertices, and enough triangles to draw all six faces of the cube.

You will be asked to flat-shade the cube (see below) and so you will need to create a "triangle soup" (where each vertex belongs to exactly one triangle; corners of the cube consist of multiple coincident vertices) and compute a normal for each vertex (perpendicular to the face to which that vertex belongs). You will need to ensure that the vertices you create have outward-pointing normals. Incorrectly-oriented triangles can be fixed by permuting the order of the triangle's vertex indices. If triangles are not oriented correctly, they will appear incorrectly shaded (probably black) when rendered, because they are "inside out."

Finally, modify MengerSponge.ts to create, given L, the Menger sponge of level L, properly positioned and scaled in space as described above, and to populate vertices, faces, and normals with the vertices and triangles of the sponge. The simplest implementation of the above pseudocode is probably to first compute where to place all cubes, and then independently add each cube's vertices, faces, and normals using the helper function suggested above.

Ensure that pressing any key from 1 to 4 generates and displays a Menger sponge of the appropriate level L. The geometry should only be recreated when one of these keys is pressed---do not procedurally generate the cube every frame! (Hint: any time you change the vertex or triangle list, you need to inform OpenGL about the new data by binding the vertex and triangle VBOs using glBindBuffer, passing the new data to the GPU using glBufferData, etc.) The skeleton code will do most of this for you, if you slot in your Menger-generation code in the right place.

You will need to implement camera controls to fully appreciate the Menger sponge; however, the sample code provides a hard-coded perspective camera, so that even before moving onto the next part of the assignment, you can get a sense for if the sponge is being generated correctly.

(45 pts) Camera Controls

Next, you will implement keyboard and mouse controls that let you navigate around the world. As discussed in class, the major pieces needed for setting up a camera are

  1. a view matrix, which transforms from world coordinates to camera coordinates; as the camera moves around the world, the view matrix changes;
  2. a perspective matrix, which transforms from 3D camera coordinates to 2D screen coordinates using a perspective projection;
  3. logic to move the camera in response to user input.

The camera's position and orientation is encoded using a set of points and vectors in world space:

The following default values should be used to initialize the camera at program start. It will ensure that the Menger sponge begins within the camera's field of view

The other default values can be inferred from these. The starter code should already create a camera at the correct location and with correct orientation.

You will implement a "first-person shooter" (FPS) camera, where the eye is fixed and the center moves around as the user moves the mouse.

(20 pts) Rotation

Implement camera rotation when left-clicking the mouse and dragging. The starter contains a mouse callback that detects mouse clicks and mouse drags. Track the mouse position during dragging, and compute the mouse drag direction based on the difference in mouse position on two consecutive frames. Calculate the corresponding vector in world coordinates; the camera should swivel in the direction of dragging, i.e. should rotate about an axis perpendicular to this vector and to the look direction. Rotate by an angle of rotationSpeed radians each frame that the mouse is dragged. You should be able to use the provided camera starter code to swivel the camera.

(5 pts) Forward/Back

Implement the behavior of the w and s keys. Pressing the w or s key should translate both the eye and center by zoomSpeed times the look direction.

(5 pts) Strafe

Implement the behavior of holding down the a and d keys. The keys should strafe, i.e. translate the eye by panSpeed times the camera tangent direction, left or right, respectively.

(10 pts) Roll

Pressing the left and right arrow keys should roll the camera: spin it counterclockwise or clockwise (respectively) by rollSpeed radians per frame.

(5 pts) Up/Down

Pressing the down the up or down arrow keys should translate the camera position, as with the a and d keys, but this time in the up direction (with the translation speed being again panSpeed units per frame).

(25 pts) Some Basic Shaders

You will do some more shader programming, to liven up the rendering of the cube, and place a ground plane on the screen so that the user has a point of reference when moving around the world.

(5 pts) Better Cube Shading

Fix the fragment shader so that it computes diffuse shading using per-vertex normals. Color the different faces of the cube based on what direction the face normal is pointing in world coordinates. Notice that the face normals will be axis-aligned no matter how the user moves or spins the camera, since transforming the camera does not move the cube in world coordinates. Refer to the following table for how to color the faces:

Normal Color
\( (1,0,0) \) red
\( (0,1,0) \) green
\( (0,0,1) \) blue
\( (-1,0,0)\) red
\( (0,-1,0)\) green
\( (0,0,-1)\) blue

All coloring code should take place in the fragment shader. The above colors are the base colors; you'll shade the cube based on the orientation of the face relative to the light source, as described by the diffuse term in the Phong illumination model.

(10 pts) Ground Plane

Add a ground plane to the scene. This plane should be placed at \( y = -2.0 \) and extend effectively infinitely in the x and z directions. Rendering of this plane should be completely separate from that of the cube: create new lists of vertices and triangles for the infinite plane, new VBOs for sending this geometry to the GPU, and a new shader program for rendering the plane. This shader program can reuse the cube's vertex shader, but should have its own fragment shader. You can use the cube shaders as skeletons for writing the floor shader.

You should be able to represent the geometry of the plane using only a small \( (\approx 4) \) number of triangles.

(10 pts) Ground Plane Shading

Write a fragment shader for the ground plane, so that the plane is colored using a checkerboard pattern. The checkerboard should be aligned to the x and z axes (in world coordinates), and each grid cell should have size \( 5.0 \times 5.0 \). The cell base color should alternate white and black (again, these are base colors, and you should diffuse-shade the floor to dim pixels facing away from the light, as in the cube's fragment shader).

There are many ways of coding the checkerboard pattern; use any method you like. You may find the following GLSL functions useful: mod, clamp, floor, sin. Hint: the fragment shader, by default, does not have access to the position of pixels in world coordinates (since that information was lost in the vertex shader, when the vertex positions are multiplied by the view and perspective matrices). You may find it useful to pass the untransformed world coordinates from the vertex shader to the fragment shader.

Build-in Tests

To help you debug your Menger Sponge implementation, we have provided some built-in unit tests. You will find these on the right side of the screen; each test mocks some inputs into your code, and then compares the image that your code produced to the "correct" image from a reference solution.

Extra Credit

The list below contains pre-approved optional features (worth five points per chime and ten points per bell), but this is not an exhaustive list---feel free to implement additional features (you can ask us to assess the point value of proposed features during office hours.) Additional optional features you implement will be awarded extra credit.

All optional features must be fully described in your README file (including instructions for how to invoke the feature behavior) to receive credit.