Regular grid image:
easy index, filter
generality: can encode any image
suffer from anisotropic
Manifold: there exists a linear transform on the surface of geometry such that the geometry can be smooth.
manifold: stanford rabbit, donut
non-manifold: cone, intersected plane
Manifold polygon mesh has fans, but not fins. Every edge is contained in only two polygons (no "fins"). The polygons containing each vertex make a single "fan".
Boundary: every boundary forms a loop.
for polygon mesh: one polygon per boundary edge
boundary is usually defined in 2D
Usual Datastructure:
array: constant lookup, coherent access
linked list: linear lookup, incoherent, good amortized complexity
hashtable: hashtable is the answer in any interview including your name and address
Well, in theoretical computer science, one should not allow random memory access be in constant time. This is because speed of light is bounded, you can't access memory that is very faraway from the signal in constant time.
Polygon Soup: a list of triangles
no information about connectivity
"triangle cloud" (triangle version of point cloud)
redundant storage when vertex connected
hard to edit data, but fine for rasterization
Adjacency List: split shape and connectivity.
kinda like .obj
file
stores v
vertex coordinate
stores f
faces by listing vertice
very expensive to find neighborhood
# cube.obj
#
g cube
v 0.0 0.0 0.0
v 0.0 0.0 1.0
v 0.0 1.0 0.0
v 0.0 1.0 1.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 1.0 1.0 0.0
v 1.0 1.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 -1.0
vn 0.0 1.0 0.0
vn 0.0 -1.0 0.0
vn 1.0 0.0 0.0
vn -1.0 0.0 0.0
f 1//2 7//2 5//2
f 1//2 3//2 7//2
f 1//6 4//6 3//6
f 1//6 2//6 4//6
f 3//3 8//3 7//3
f 3//3 4//3 8//3
f 5//5 7//5 8//5
f 5//5 8//5 6//5
f 1//4 5//4 6//4
f 1//4 6//4 2//4
f 2//1 6//1 8//1
f 2//1 8//1 4//1
In above:
v
: vertex position
vn
: vertex normals
f
: faces
l
: lines
g
: group name
o
: object name
s
: whether smooth shading
Note that there are many representation of f
faces in .obj
file:
f v1 v2 v3 ...
: stores only vertex
f v1/vt1 v2/vt2 v3/vt3 ...
: stores vertex, and texture coordinates
f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...
stores vertex, texture coordinates, vertex normals
f v1//vn1 v2//vn2 v3//vn3 ...
: stores vertex and normals
Incidence Matrix:
store vertex to edge, edge to face
finding neighbores is O(1)
large storage cost (can reduce to sparse matrix data structure)
map
or hash table
)hard to change connectivity
can store non-manifold
struct Halfedge {
Halfedge* twin;
Halfedge* next;
Vertex* vertex; // by convention, vertex it come from
Edge* edge;
Face* face;
Vec2 corner_uv; // uv coordinate for this corner of the face
Vec3 corner_normal; // shading normal for this corner of the face
// Vertex normal provides information whether we need a hard or smooth edge
// Sometimes triangles mesh are not directly converted from a smooth surface,
// and vertex normals have to be computed on the fly.
// There are different techniques for computing vertex normals
}
struct Edge {
Halfedge* halfedge;
bool sharp; // should this edge be considered sharp when computing shading normals?
}
struct Face {
Halfedge* halfedge;
bool boundary; // is this a boundary loop? (boundary used to traverse mesh efficiently)
}
struct Vertex {
Halfedge* halfedge
Vec3 position; // location of the vertex
struct Bone_Weight {
uint32_t bone;
float weight;
};
std::vector< Bone_Weight > bone_weights; // how much this vertex follows each bone's transform (used for skinned meshes)
}
One can use minimal half-edge-like datastructure to do the job quickly
cpp std::unordered_map<std::pair<Index, Index>, HalfedgeRef> halfedges;
The above data structure represent a unordered map of half-edge. Thestd::pair<Index, Index>
is the indice for vertexfrom
and vertexto
. It maps to aHalfedgeRef
that is the address of the next half edge. When you want to walk back or find its twin, just look up thestd::unordered_map
.Another trick is to store twin halfedges as even and odd indices instead of pointers.
Besides, we also define 2D mesh to have boundary for easy navigation. (in green shaded region as shown in images below)
If you want to visit all vertices of a face
Halfedge* h = f->halfedge;
do {
h = h->next;
}
while (h != h->halfedge);
If you want to visit all neighbors of a vertex
Halfedge* h = v->halfedge;
do {
h = h->twin->next;
}
while (h != v->halfedge);
The datastructure only make sense if mesh is manifold: - there is only one twin for any halfedge -
twin->twin == this
-twin != this
- there is only one next halfedge -next == something
-this == someone's next
Well, actually the connectivity should be manifold, but adjusting vertex position can generates non-manifold-looking geometry. The below image is still a valid geometry.
We can have multiple different representation of the same geometry because the pointers are different. It is non-trivial to have a one-to-one map between representation and geometry. One would build a total order of halfedge to correctly assign halfedge to faces, edges, and vertex.
Completeness: we should prove Edge Flip, Edge Split, Edge Collapse can achieve all operations
all operations: transform one mesh into the other
First Step: Edge Collapse all the way to a single triangle
Second Step: Edge Split to desired mesh
With some thought, one can design halfedge type data structure with coherent data storage and support for non-manifold connectivity.
Adjacency List | Incidence Matrices | Halfedge | |
---|---|---|---|
O(1) neighborhood | No | Yes | Yes |
Add/Remove Mesh | No | No | Yes |
Non-manifold | Yes | Yes | No |
A Halfedge_Mesh
structure is a sea of pointers. It is surprisingly easy to tangle these pointers into a mess that fails to represent a manifold mesh (or, really, a consistent mesh at all). In order to provide guidance on what constitutes a reasonable setting for a mesh, we define a valid mesh as one for which the following properties hold:
e
, e->halfedge(->twin)^n
is a cycle of exactly two halfedges, and these are exactly the halfedges with h->edge
equal to e
.f
, f->halfedge(->next)^n
is a cycle of at least three halfedges, and these are exactly the halfedges with h->face
equal to f
.v
, v->halfedge(->twin->next)^n
is a cycle of at least two halfedges, and these are exactly the halfedges with h->vertex
equal to v
.
In subdivision modling, artists work on course geometry and preview the subdivided version. There are two reasons why we don't want to work on high resolution models:
it costs time to process
have less degree of freedom is good
Table of Content