CS 184 - HW 2

Webpage: https://cal-cs184-student.github.io/hw2-meshedit-sp24-ddr/

Overview

In this project, we took coordinates in screen space and mapped them onto 3D objects in world space to create a mesh. We then made use of the Halfedge data structure, to implement vertex-based shading, to flip and split the edges of the mesh, and to upsample the components of the mesh. In doing this, we were able to manipulate the topology of an object.

Section I - Bezier Curves and Surfaces

Part 1 - Bezier Curves with 1D de Casteljau Subdivision

De Casteljau’s algorithm recursively interpolates between n-1 pairs of points points by value t, until a singular point is produced. In order to evaluate Bezier curves we used de Casteljau’s algorithm for each step, or for each new level of control points.

Part 2 - Bezier Surfaces with Separable 1D de Casteljau

De Casteljau’s algorithm allows us to evaluate multiple levels of intermediate control points until a single final point is found on the Bezier curve. In part 2, we worked with Bezier surfaces which are comprised of Bezier curves. To find the surface point given x and y of this Bezier surface, we simply iterated through each Bezier curve and found the final point of each with respect to `u` using the recursive algorithm we wrote in the previous part. Next, we took this set of points to create its own "moving" Bezier curve and once again called our De Casteljau’s algorithm to evaluate a single final point given v; the point return is the surface point.

Section II: Triangle Meshes and Half-Edge Data Structure

Part 3 - Area-Weighted Vertex Normals

To implement the area-weighted vertex normals, we iterated through each triangle in the target vertex’s manifold. In each neighboring triangle we took the vertex’s 2 connecting vectors and computed its cross-product to get our face’s normal vector. Then we got the area of the triangle by multiplying the face’s normal vector by 1/2. After, we summed all of the face’s normal vectors and divided it by its normalization to compute the area weighted normal.

no shading
smoothing/shading

Part 4 - Edge Flip

Edge Flip Operation Process

Starting off, we drew out a diagram for the edge flip operation and wrote down all of the pointer assignments. (Warning: we had to debug so these initial assignments may not be correct.)

  1. We stored all the pointers for the current halfedges (h0-h9), vertices (v0-v3), edges (e0-e4), and faces (f0-f1) into variables.
  1. Then we reassigned all the pointers of the attributes in each halfedge (its next, twin, vertex, edge, and face).
  1. Finally, we reassigned all the halfedge pointers inside of all vertices, edges, and faces.

Screenshots

Debugging Journey

Thankfully, there was no debugging journey for Part 4.

Part 5 - Edge Split

Edge Split Operation Process

Similar to the edge flip, we first drew a diagram and wrote down all of the pointer assignments.

  1. We stored all the pointers for the current halfedges (h0-h9), vertices (v0-v3), edges (e0-e4), and faces (f0-f1) into variables.
  1. We then instantiated 6 new halfedges, 1 new vertex, 3 new edges, and 2 new faces to account for the additional elements that would appear when the edge was split.
    1. In order to get the new vertex position, we took the average of the orginal splitting edge’s (e0) connecting vertex positions.
  1. Then we reassigned all the pointers of the attributes in each halfedge (its next, twin, vertex, edge, and face).
  1. Finally, we reassigned all the halfedge pointers inside of all vertices, edges, and faces.
    1. We also had to change the 2 new edges’s (e5 and e6) isNew attribute to true, since the split creates 1 new long edge segmented into 2.

Screenshots

Debugging Journey

The main bug we ran into was the operation successfully running, but the face to the west disappearing (which left what looked like a hole). We debugged it by rechecking our diagram and the corresponding code to each pointer. After posting on Edstem to ask for debuggging advice and doubly rechecking our diagram, we eventually realized that the next reassignment in h0’s setNeighbor call was wrong. Instead of h0→next() = h1 the code read h0→next() = h0.

Part 6 - Loop Subdivision

Loop Subdivision Implementation

  1. We started by iterating through each vertex in the mesh to update their old positions.
    1. We iterated through each neighbor to calculate the sum of the neighboring vertex positions.
    1. Then we calculated the updated vertex position by computing the constant of the vertex according to the formula below (u) and extracting our known vertex degree value (n) and applying this formula: (1 - n * u) * original_position + u * original_neighbor_position_sum .
    1. We then changed the vertex’s isNew attribute to false.
  1. Next, we computed the new vertex positions that will be created by the new edges. To do this, we iterated through the pre-existing edges and found its 4 surrounding vertices that would formulate 2 neighboring triangles (like in the diagram below). We stored the calculated position using this formula, 3/8 * (A + B) + 1/8 * (C + D), and stored it in Edge::newPosition . We also changed the edge’s isNew value to false.
  1. Next, we re-iterated through the edges in the mesh and split them using the splitEdge function we created in the previous part. As we split, we updated the returned vertex’s isNew attribute to true and assigned its position to the newPosition stored in the edge.
  1. We re-iterated through the edges again, this time using flipEdge to flip edges that were connected to a new and old vertex.
  1. Finally, we iterated through each vertex in the mesh and copied its newPosition value over to position to update it.

Screenshots

After loop subdivision, the triangles of the mesh become smaller and greater in number. Sharp corners and edges are physically smoothed out. You can reduce this effect by pre-splitting edges near the sharp areas and creating a ring or an edge loop around it.

You can pre-process the cube by splitting the faces so each one has a cross on it. This ensures that each face has matching edges and faces. This enables the upsampling to equally split and flip itself. Before, the cube would skew itself to the right when it was upsampled since the faces only had 1 split edge that pointed in the direction of the skew.