@@ -14,7 +16,7 @@ Scotty3D uses a variety of geometric data structures, depending on the task. Som
The basic idea behind the halfedge data structure is that, in addition to the usual vertices, edges, and faces that make up a polygonal mesh, we also have an entity called a _halfedge_ that acts like "glue" connecting the different elements. It is this glue that allow us to easily "navigate" the mesh, i.e., easily access mesh elements adjacent to a given element.
In particular, there are two halfedges associated with each edge (see picture above). For an edge connecting two vertices i and j, one of its halfedges points from i to j; the other one points from j to i. In other words, we say that the two halfedges are _oppositely oriented_. On of the halfedges is associated with the face to the "left" of the edge; the other is associated with the face to the "right." Each halfedge knows about the opposite halfedge, which we call its _twin_. It also knows about the _next_ halfedge around its face, as well as its associated edge, face, and vertex.
...
...
@@ -35,7 +37,7 @@ In summary, we have the following relationships:
This list emphasizes that it is really the **halfedges** that connect everything up. An easy example is if we want to visit all the vertices of a given face. We can start at the face's halfedge, and follow the "next" pointer until we're back at the beginning. A more interesting example is visiting all the vertices adjacent to a given vertex v. We can start by getting its outgoing halfedge, then its twin, then its next halfedge; this final halfedge will also point out of vertex v, but it will point **toward** a different vertex than the first halfedge. By repeating this process, we can visit all the neighboring vertices:
In some sense, a halfedge mesh is kind of like a supercharged linked list. For instance, the halfedges around a given face (connected by `next` pointers) form a sort of "cyclic" linked list, where the tail points back to the head.
...
...
@@ -86,7 +88,7 @@ Note, however, that one should be **very, very careful** when adding or deleting
v->halfedge() = h;
Likewise, if we delete a mesh element, we must be certain that no existing elements still point to it; the halfedge data structure does not take care of these relationships for you automatically. In fact, that is exactly the point of this assignment: to get some practice directly manipulating the halfedge data structure. Being able to perform these low-level manipulations will enable you to write useful and interesting mesh code far beyond the basic operations in this assignment. The `Halfedge_Mesh` class provides a helper function called `validate` that checks whether the mesh iterators are valid. You might find it worthwhile calling this function to debug your implementation (please note that `validate` only checks that your mesh is valid - passing it does not imply that your specific operation is correct).
Likewise, if we delete a mesh element, we must be certain that no existing elements still point to it; the halfedge data structure does not take care of these relationships for you automatically. In fact, that is exactly the point of this assignment: to get some practice directly manipulating the halfedge data structure. Being able to perform these low-level manipulations will enable you to write useful and interesting mesh code far beyond the basic operations in this assignment. The `Halfedge_Mesh` class provides a helper function called `validate` that checks whether the mesh iterators are valid. You might find it worthwhile calling this function to debug your implementation (please note that `validate` only checks that your mesh is valid - passing it does not imply that your specific operation is correct).
Finally, the **boundary** of the surface (e.g., the ankles and waist of a pair of pants) requires special care in our halfedge implementation. At first glance, it would seem that the routine `printNeighborPositions()` above might break if the vertex `v` is on the boundary, because at some point we worry that we have no `twin()` element to visit. Fortunately, our implementation has been designed to avoid this kind of catastrophe. In particular, rather than having an actual hole in the mesh, we create a "virtual" boundary face whose edges are all the edges of the boundary loop. This way, we can iterate over boundary elements just like any other mesh element. If we ever need to check whether an element is on the boundary, we have the methods.
...
...
@@ -103,10 +105,10 @@ These methods return true if and only if the element is contained in the domain
These virtual faces are not stored in the usual face list, i.e., they will not show up when iterating over faces. The figure below should help to further explain the behavior of `Halfedge_Mesh` for surfaces with boundary:
Dark blue regions indicate interior faces, whereas light blue regions indicate virtual boundary faces. Note that for vertices and edges, ``on_boundary()`` will return true if the element is attached to a boundary face, but ``is_boundary()`` for halfedges is only true if the halfedge is 'inside' the boundary face. For example, in the figure above the region ``b`` is a virtual boundary face, which means that vertex ``v'``, edge ``e'``, and halfedge ``h'`` are all part of the boundary; their methods will return true. In contrast, vertex ``v``, edge ``e``, face `f`, and halfedge `h` are not part of the boundary, and their methods will return false. Notice also that the boundary face b is a polygon with 12 edges.
_Note:_ _the edge degree and face degree of a boundary vertex is not the same!_ Notice, for instance, that vertex `v'` is contained in three edges but only two interior faces. By convention, `Vertex::degree()` returns the face degree, not the edge degree. The edge degree can be computed by finding the face degree, and adding 1 if the vertex is a boundary vertex.
Please refer to the inline comments (e.g. of `geometry/halfedge.h`) for further details about the `Halfedge_Mesh` data structure.
\ No newline at end of file
Please refer to the inline comments (e.g. of `geometry/halfedge.h`) for further details about the `Halfedge_Mesh` data structure.
@@ -18,11 +20,11 @@ Given these lists, `Halfedge_Mesh::from_poly` will take care of allocating halfe
Both linear and Catmull-Clark subdivision schemes will handle general _n_-gons (i.e., polygons with _n_ sides) rather than, say, quads only or triangles only. Each _n_-gon (including but not limited to quadrilaterals) will be split into _n_ quadrilaterals according to the following template:
The high-level procedure is outlined in greater detail in `student/meshedit.cpp`.
##### Vertex Positions
### Vertex Positions
For global linear or Catmull-Clark subdivision, the strategy for assigning new vertex positions may at first appear a bit strange: in addition to updating positions at vertices, we will also calculate vertex positions associated with the _edges_ and _faces_ of the original mesh. Storing new vertex positions on edges and faces will make it extremely convenient to generate the polygons in our new mesh, since we can still use the halfedge data structure to decide which four positions get connected up to form a quadrilateral. In particular, each quad in the new mesh will consist of:
...
...
@@ -38,11 +40,11 @@ For linear subdivision, the rules for computing new vertex positions are very si
These values should be assigned to the members `Face::new_pos`, `Edge::new_pos`, and `Vertex::new_pos`, respectively. For instance, `f->new_pos = Vec3( x, y, z );` will assign the coordinates (x,y,z) to the new vertex associated with face `f`. The general strategy for assigning these new positions is to iterate over all vertices, then all edges, then all faces, assigning appropriate values to `new_pos`. **Note:** you _must_ copy the original vertex position `Vertex::pos` to the new vertex position `Vertex::new_pos`; these values will not be used automatically.
This step should be implemented in the method `Halfedge_Mesh::linear_subdivide_positions` in `student/meshedit.cpp`.
This step should be implemented in the method `Halfedge_Mesh::linear_subdivide_positions` in `student/meshedit.cpp`.
Steps 2 and 3 are already implemented by `Halfedge_Mesh::subdivide` in `geometry/halfedge.cpp`. For your understanding, an explanation of how these are implemented is provided below:
##### Polygons
### Polygons
Recall that in linear and Catmull-Clark subdivision _all polygons are subdivided simultaneously_. In other words, if we focus on the whole mesh (rather than a single polygon), then we are globally
@@ -17,11 +21,11 @@ A good recipe for ensuring that all pointers are still valid after a local remes
The reason for setting all the pointers (and not just the ones that changed) is that it is very easy to miss a pointer, causing your code to crash.
#### Interface with global mesh operations
### Interface with global mesh operations
To facilitate user interaction, as well as global mesh processing operations (described below), local mesh operations should return the following values when possible. However, should it happen that the specified values are not available, or that the operation should not work on the given input, we need a way to signify the failure case. To do so, each local operation actually returns a ``std::optional`` value parameterized on the type of element it returns. For example, ``Halfedge_Mesh::erase_vertex`` returns a ``std::optional<Halfedge_Mesh::Face>``. An ``optional`` can hold a value of the specified type, or, similarly to a pointer, a null value (``std::nullopt``). See ``student/meshedit.cpp`` for specific examples.
To facilitate user interaction, as well as global mesh processing operations (described below), local mesh operations should return the following values when possible. However, should it happen that the specified values are not available, or that the operation should not work on the given input, we need a way to signify the failure case. To do so, each local operation actually returns a ``std::optional`` value parameterized on the type of element it returns. For example, ``Halfedge_Mesh::erase_vertex`` returns a ``std::optional<Halfedge_Mesh::Face>``. An ``optional`` can hold a value of the specified type, or, similarly to a pointer, a null value (``std::nullopt``). See ``student/meshedit.cpp`` for specific examples.
Also, remember that in any case, _the program should not crash!_ So for instance, you should never return a pointer to an element that was deleted.
Also, remember that in any case, _the program should not crash!_ So for instance, you should never return a pointer to an element that was deleted.
See the [User Guide](/Scotty3D/guide/model) for demonstrations of each local operation.