Line 175... |
Line 175... |
175 |
Vec3f p2 = p0 + p1;
|
175 |
Vec3f p2 = p0 + p1;
|
176 |
p2 += p1; // Same as p2 = p2 + p1
|
176 |
p2 += p1; // Same as p2 = p2 + p1
|
177 |
\end{verbatim}
|
177 |
\end{verbatim}
|
178 |
Thus both the normal form of \texttt{+} and the assignment form are supported for \texttt{Vec3f}. The same is true of the other arithmetic operators \texttt{-*/} and the operators are supported for most CGLA entities including all matrix and vector types.
|
178 |
Thus both the normal form of \texttt{+} and the assignment form are supported for \texttt{Vec3f}. The same is true of the other arithmetic operators \texttt{-*/} and the operators are supported for most CGLA entities including all matrix and vector types.
|
179 |
|
179 |
|
180 |
Multiplication is also supported for matrix vector products. Below an example with 4D double precision vector and matrix:
|
180 |
Multiplication is also supported for matrix vector products:
|
181 |
\begin{verbatim}
|
181 |
\begin{verbatim}
|
182 |
Vec4d x(1,1,0,1);
|
182 |
Vec4d p0(1,1,0,1);
|
183 |
|
183 |
|
184 |
Mat4x4d m = rotation_Mat4x4d(ZAXIS, 3.14);
|
184 |
Mat4x4d m = rotation_Mat4x4d(ZAXIS, 3.14);
|
185 |
|
185 |
|
186 |
Vec4d y = m * x;
|
186 |
Vec4d p1 = m * p0;
|
187 |
\end{verbatim}
|
187 |
\end{verbatim}
|
188 |
|
188 |
|
189 |
but all vectors are column vectors, so only right multiplication is supported. We cannot multiply a vector on the left side of a matrix. However, there is, of course, a \texttt{dot} function which computes the dot product of two vectors.
|
189 |
but all vectors are column vectors, so only right multiplication is supported. We cannot multiply a vector on the left side of a matrix. However, there is, of course, a \texttt{dot} function which computes the dot product of two vectors.
|
190 |
|
190 |
|
191 |
A very common operation is to compute the normal of a triangle from
|
191 |
A very common operation is to compute the normal of a triangle from
|
Line 348... |
Line 348... |
348 |
\subsection{Walker}
|
348 |
\subsection{Walker}
|
349 |
To loop over all vertices is one way of traversing the mesh. It ensures that we visit all vertices, but it does not relate to the connectivity of the mesh.
|
349 |
To loop over all vertices is one way of traversing the mesh. It ensures that we visit all vertices, but it does not relate to the connectivity of the mesh.
|
350 |
To traverse the mesh in a way that respects connectivity, we use the \texttt{Walker} class. A \texttt{Walker} always points to a specific halfedge, but unlike a \texttt{HalfEdgeID}, it can be used to move from edge to edge. For instance, to compute the centroid for a given face, we can use the following code:
|
350 |
To traverse the mesh in a way that respects connectivity, we use the \texttt{Walker} class. A \texttt{Walker} always points to a specific halfedge, but unlike a \texttt{HalfEdgeID}, it can be used to move from edge to edge. For instance, to compute the centroid for a given face, we can use the following code:
|
351 |
\begin{verbatim}
|
351 |
\begin{verbatim}
|
352 |
Vec3d p(0);
|
352 |
Vec3d p(0);
|
353 |
FaceID f = *m.faces_begin();
|
353 |
FaceID f = m.faces();
|
- |
|
354 |
Walker w = m.walker(f);
|
354 |
for(Walker w = m.walker(f); !w.full_circle(); w = w.next())
|
355 |
for(; !w.full_circle(); w = w.next())
|
355 |
p += m.pos(w.vertex());
|
356 |
p += m.pos(w.vertex());
|
356 |
p /= w.no_steps();
|
357 |
p /= w.no_steps();
|
357 |
\end{verbatim}
|
358 |
\end{verbatim}
|
358 |
The code above computes the centroid for the first face in the mesh. It does so by jumping from edge to edge until it has come full circle. For each jump, we add the vertex pointed to by the halfedge to \texttt{p}. The benefit of the \texttt{Walker} is that it allows us to move from a halfedge to all connected halfedges. The simple traversal functions are \texttt{next}, \texttt{prev}, and \texttt{opp}:
|
359 |
The code above computes the centroid for the first face in the mesh. It does so by jumping from edge to edge until it has come full circle. For each jump, we add the vertex pointed to by the halfedge to \texttt{p}. The benefit of the \texttt{Walker} is that it allows us to move from a halfedge to all connected halfedges. The simple traversal functions are \texttt{next}, \texttt{prev}, and \texttt{opp}:
|
359 |
\begin{verbatim}
|
360 |
\begin{verbatim}
|
Line 366... |
Line 367... |
366 |
That might seem inconvenient but it is more flexible since we can obtain, say, a nearby vertex just by concatenating some traversal functions. For instance \texttt{w.next().opp().next().vertex()} returns an ID to a vertex nearby -- without changing \texttt{w}. The sequence \texttt{w.next().opp().next()} creates three anonymous walkers. If, on the other hand, the functions used for walking updated the state of \texttt{w}, we would have needed to first make a named copy of \texttt{w} before updating its state, so that we could get back to the original state.
|
367 |
That might seem inconvenient but it is more flexible since we can obtain, say, a nearby vertex just by concatenating some traversal functions. For instance \texttt{w.next().opp().next().vertex()} returns an ID to a vertex nearby -- without changing \texttt{w}. The sequence \texttt{w.next().opp().next()} creates three anonymous walkers. If, on the other hand, the functions used for walking updated the state of \texttt{w}, we would have needed to first make a named copy of \texttt{w} before updating its state, so that we could get back to the original state.
|
367 |
|
368 |
|
368 |
\texttt{Walker} can be used to circulate around both faces and vertices. Below, we use vertex circulation to compute the average of the neighbors of a vertex
|
369 |
\texttt{Walker} can be used to circulate around both faces and vertices. Below, we use vertex circulation to compute the average of the neighbors of a vertex
|
369 |
\begin{verbatim}
|
370 |
\begin{verbatim}
|
370 |
Vec3d p(0);
|
371 |
Vec3d p(0);
|
371 |
VertexID v = *m.vertices_begin();
|
372 |
VertexID v = m.vertices();
|
372 |
for(Walker w = m.walker(v);!w.full_circle();
|
373 |
Walker w = m.walker(v)
|
373 |
w = w.circulate_vertex_ccw()){
|
374 |
for(;!w.full_circle(); w = w.circulate_vertex_ccw()){
|
374 |
p += m.pos(w.vertex());
|
375 |
p += m.pos(w.vertex());
|
375 |
p /= w.no_steps();
|
376 |
p /= w.no_steps();
|
376 |
\end{verbatim}
|
377 |
\end{verbatim}
|
377 |
|
378 |
|
378 |
Again, the \texttt{full\_circle()} function tests whether the walker has returned to the original halfedge whence it was created. We can also call \texttt{no\_steps() } which returns the number of steps taken. In this regard, a step of vertex circulation is considered atomic - even though \texttt{w.circulate\_vertex\_ccw()} is the same as \texttt{w.prev().opp()}.
|
379 |
Again, the \texttt{full\_circle()} function tests whether the walker has returned to the original halfedge whence it was created. We can also call \texttt{no\_steps() } which returns the number of steps taken. In this regard, a step of vertex circulation is considered atomic - even though \texttt{w.circulate\_vertex\_ccw()} is the same as \texttt{w.prev().opp()}.
|
379 |
|
380 |
|
380 |
From a walker we can easily obtain any of the mesh entities that the halfedge it refers to are incident upon. For instance \texttt{vertex()} returns the \texttt{VertexID} of the vertex which the halfedge points to, \texttt{face()} gives us the \texttt{FaceID} of the incident face and \texttt{halfedge()} simply returns the \texttt{HalfEdgeID}. There is one more option for circulation. The following code does precisely the same as the code above, i.e. it computes the average of the neighboring vertices.
|
381 |
From a walker we can easily obtain any of the mesh entities that the halfedge it refers to are incident upon. For instance \texttt{vertex()} returns the \texttt{VertexID} of the vertex which the halfedge points to, \texttt{face()} gives us the \texttt{FaceID} of the incident face and \texttt{halfedge()} simply returns the \texttt{HalfEdgeID}. There is one more option for circulation. The following code does precisely the same as the code above, i.e. it computes the average of the neighboring vertices.
|
381 |
\begin{verbatim}
|
382 |
\begin{verbatim}
|
382 |
Vec3d p(0);
|
383 |
Vec3d p(0);
|
383 |
int n = circulate_vertex_ccw(m, v,
|
384 |
int n = circulate_vertex_ccw(m, v,
|
384 |
[&](VertexID v){ p += m.pos(v); });
|
385 |
[&](VertexID v){ p += m.pos(v); });
|
385 |
return p / n;
|
386 |
return p / n;
|
386 |
\end{verbatim}
|
387 |
\end{verbatim}
|
387 |
Here the function \texttt{circulate\_vertex\_ccw} takes three arguments. The \texttt{Manifold}, \texttt{m}, and the vertex, \texttt{v}, around which we circulate, and, finally, a function that accepts a \texttt{VertexID}. This function gets called for every vertex adjacent to \texttt{v}. In this example, the function is given as a so called \textit{lambda} function. This allows us to write things compactly and to have the body of the function where it is needed. We could also have passed a function which accepts a \texttt{FaceID} or a \texttt{HalfEdgeID} or a \texttt{Walker}. Thus, we can visit incident faces and edges during circulation if that is what we need, and if we need go get some entity other than adjacent vertices, edges, or faces, we can use the \texttt{Walker} option which is the most generic.
|
388 |
Here the function \texttt{circulate\_vertex\_ccw} takes three arguments. The \texttt{Manifold}, \texttt{m}, and the vertex, \texttt{v}, around which we circulate, and, finally, a function that accepts a \texttt{VertexID}. This function gets called for every vertex adjacent to \texttt{v}. In this example, the function is given as a so called \textit{lambda} function. This allows us to write things compactly and to have the body of the function where it is needed. We could also have passed a function which accepts a \texttt{FaceID} or a \texttt{HalfEdgeID} or a \texttt{Walker}. Thus, we can visit incident faces and edges during circulation if that is what we need, and if we need go get some entity other than adjacent vertices, edges, or faces, we can use the \texttt{Walker} option which is the most generic.
|
388 |
|
389 |
|
389 |
To sum up, there are two datatypes that we use to refer to mesh entitites.
|
390 |
To summarize there are three datatypes that we use to refer to mesh entitites.
|
390 |
\begin{itemize}
|
391 |
\begin{itemize}
|
391 |
\item IDs \texttt{VertexID}, \texttt{HalfEdgeID}, and \texttt{FaceID} are simply indices. These types are what we use when calling functions that take arguments which refer to mesh entities.
|
392 |
\item IDs \texttt{VertexID}, \texttt{HalfEdgeID}, and \texttt{FaceID} are simply indices. These types are what we use when calling functions that take arguments which refer to mesh entities.
|
- |
|
393 |
\item ID iterators \texttt{VertexIDIterator}, \texttt{HalfEdgeIDIterator}, and\\ \texttt{FaceIDIterator} as their names imply can be used to iterate over the entities of a mesh. As such they reflect how the mesh is stored in memory. Dereferencing an ID iterator will give an ID.
|
392 |
\item \texttt{Walker} is used to walk from halfedge to halfedge on the mesh. It utilizes the references stored in halfedges to other halfedges in order to facilitate this traversal. Again, we can obtain an ID from a walker.
|
394 |
\item \texttt{Walker} is used to walk from halfedge to halfedge on the mesh. It utilizes the references stored in halfedges to other halfedges in order to facilitate this traversal. Again, we can obtain an ID from a walker.
|
393 |
\end{itemize}
|
395 |
\end{itemize}
|
394 |
It might seem tiresome that you have to deal with two different methods for referring to mesh entities, but they do very different jobs. The IDs are simply indices (wrapped in a class) referring to mesh elements. The walker contains functions that allow us to move from halfedge to halfedge and additionally keeps track of the starting point and number of steps taken. In fact there are also three iterator classes, but we do not mention them, because the user does not need them; they are only a tool which allows for loops that visit all faces, edges, or vertices.
|
396 |
It might seem tiresome that you have to deal with three different methods for referring to mesh entities, but they do very different jobs. The IDs are simply indices (wrapped in a class) referring to mesh elements. The iterators combine this indices with a reference to the actual data structures. The walker contains functions that allow us to move from halfedge to halfedge and additionally keeps track of the starting point and number of steps taken.
|
- |
|
397 |
|
- |
|
398 |
We could have combined two or all three of these concepts, but would have led to rather bloated datatypes.
|
395 |
|
399 |
|
396 |
\subsection{Attributes}
|
400 |
\subsection{Attributes}
|
397 |
Given a \texttt{VertexID}, \texttt{v}, and a \texttt{Manifold}, \texttt{m}, we can obtain the geometric position of the vertex with the method call \texttt{m.pos(v)}. If is fairly important to note that a reference to the position is returned. Thus, we can assign the position of a vertex:
|
401 |
Given a \texttt{VertexID}, \texttt{v}, and a \texttt{Manifold}, \texttt{m}, we can obtain the geometric position of the vertex with the method call \texttt{m.pos(v)}. If is fairly important to note that a reference to the position is returned. Thus, we can assign the position of a vertex:
|
398 |
\begin{verbatim}
|
402 |
\begin{verbatim}
|
399 |
Vec3d p;
|
403 |
Vec3d p;
|
Line 404... |
Line 408... |
404 |
|
408 |
|
405 |
Geometric position is in fact the only attribute that is stored for a vertex. That might seem strange since we would very often like to associate any number of attributes with mesh entities. For instance, we might need to associate normals, colors, curvature tensors, counters, etc. with vertices, faces, or even edges.
|
409 |
Geometric position is in fact the only attribute that is stored for a vertex. That might seem strange since we would very often like to associate any number of attributes with mesh entities. For instance, we might need to associate normals, colors, curvature tensors, counters, etc. with vertices, faces, or even edges.
|
406 |
|
410 |
|
407 |
Unfortunately, what precise attributes we need depends greatly on the precise application. To resolve this issue, we have introduces three attribute vector class templates which allow the user to create his own attribute vectors:
|
411 |
Unfortunately, what precise attributes we need depends greatly on the precise application. To resolve this issue, we have introduces three attribute vector class templates which allow the user to create his own attribute vectors:
|
408 |
\begin{verbatim}
|
412 |
\begin{verbatim}
|
409 |
VertexAttributeVector<T> vertex_attrib_vector;
|
413 |
VertexAttributeVector<T> vertex_attrib_vector;
|
410 |
FaceAttributeVector<T> face_ttrib_vector;
|
414 |
FaceAttributeVector<T> face_ttrib_vector;
|
411 |
HalfEdgeAttributeVector<T> halfedge_attrib_vector;
|
415 |
HalfEdgeAttributeVector<T> halfedge_attrib_vector;
|
412 |
\end{verbatim}
|
416 |
\end{verbatim}
|
413 |
where \texttt{T} is a typename. Attribute vectors are indexed with indices. Thus, a
|
417 |
where \texttt{T} is a typename. Attribute vectors are indexed with indices. Thus, a
|
414 |
\texttt{VertexAttributeVector} is indexed with a \texttt{VertexID} and likewise for the other types.
|
418 |
\texttt{VertexAttributeVector} is indexed with a \texttt{VertexID} and likewise for the other types. To give a simple example of how this is used consider smoothing a mesh. The simplest way of smoothing is to replace the vertex with the average of its neighbors. However, if we copy the average value back before the average value for some neighbor has been computed, then we clearly use one average to compute another average and thereby introduce an unfortunate dependence on the ordering of the vertices. Consequently, we need to first compute all of the averages and then update the original vertices. The code looks as follows:
|
415 |
\begin{verbatim}
|
419 |
\begin{verbatim}
|
416 |
VertexAttributeVector<Vec3d> my_attrib;
|
- |
|
417 |
VertexID v;
|
- |
|
418 |
// assign some vertex ID to v
|
- |
|
419 |
my_attrib[v] = Vec3d(1,2,3);
|
- |
|
420 |
\end{verbatim}
|
- |
|
421 |
To give a simple example of how this is used consider smoothing a mesh. The simplest way of smoothing is to replace the vertex with the average of its neighbors. However, if we copy the average value back before the average value for some neighbor has been computed, then we clearly use one average to compute another average and thereby introduce an unfortunate dependence on the ordering of the vertices. Consequently, we need to first compute all of the averages and then update the original vertices. What we do is to copy \textit{all} vertex positions to a new attribute vector. Then we compute a new position for each vertex storing it in the new attribute vector. Finally, we copy the positions back. Some complexity is due to the treatment of boundaries. Boundary vertices are not smoothed in this example. However, since we have copied the old vertex positions to the new attribute vector this means that boundary vertices are simply left where they are. The code looks as follows:
|
420 |
void smooth(Manifold& m, float t)
|
422 |
\begin{verbatim}
|
- |
|
423 |
void laplacian_smooth_example(Manifold& m)
|
- |
|
424 |
{
|
421 |
{
|
425 |
VertexAttributeVector<Vec3d> new_pos =
|
422 |
VertexAttributeVector<Vec3f> pos(m.total_vertices());
|
426 |
m.positions_attribute_vector();
|
- |
|
427 |
for(VertexID v : m.vertices())
|
423 |
for(VertexID v : m.vertices())
|
- |
|
424 |
{
|
428 |
if(!boundary(m, v))
|
425 |
if(!boundary(m, v))
|
429 |
{
|
426 |
{
|
430 |
Vec3d L(0);
|
427 |
Vec3f avg_pos(0);
|
- |
|
428 |
Walker w = m.walker(v)
|
431 |
int n = circulate_vertex_ccw(m,v,
|
429 |
for(; !w.full_circle(); w = w.circulate_vertex_cw())
|
432 |
[&](VertexID v){
|
430 |
avg_pos += m.pos(w.vertex());
|
433 |
L += m.pos(v);
|
431 |
pos[v] = avg_pos / w.no_steps();
|
434 |
});
|
432 |
}
|
- |
|
433 |
}
|
- |
|
434 |
|
435 |
new_pos[v] = L/n;
|
435 |
for(VertexID v : m.vertices())
|
436 |
}
|
436 |
{
|
- |
|
437 |
if(!boundary(m, v))
|
437 |
m.positions_attribute_vector() = new_pos;
|
438 |
m.pos(v) = pos[v];
|
- |
|
439 |
}
|
438 |
}
|
440 |
}
|
439 |
\end{verbatim}
|
441 |
\end{verbatim}
|
- |
|
442 |
|
440 |
What if we were to write to a position in the attribute vector which did not contain a value? For instance, what happens if we add vertices to the mesh, somehow, and then assign attribute values to the added vertices. One would hope that the attribute vector is resized automatically, and that is the case, fortunately. Thus, we never explicitly need to increase the size of an attribute vector. What if there are suddenly fewer vertices? In that case, we can clean up the attribute vector. However, we also need to clean up the manifold. The procedure looks as follows:
|
443 |
In the smoothing example the \texttt{VertexAttributeVector} containing the new positions is initialized to a size equal to the actual number of vertices. That is not strictly necessary since the attribute vectors automatically resize if the index stored in the ID is out of bounds. Thus, we never explicitly need to increase the size of an attribute vector. What if there are suddenly fewer vertices? In that case, we can clean up the attribute vector. However, we also need to clean up the manifold. The procedure looks as follows:
|
441 |
\begin{verbatim}
|
444 |
\begin{verbatim}
|
442 |
VertexAttributeVector<T> vertex_attrib_vector;
|
445 |
VertexAttributeVector<T> vertex_attrib_vector;
|
443 |
Manifold m;
|
446 |
Manifold m;
|
444 |
// ....
|
447 |
// ....
|
445 |
|
448 |
|
Line 448... |
Line 451... |
448 |
vertex_attrib_vector.cleanup(remap);
|
451 |
vertex_attrib_vector.cleanup(remap);
|
449 |
\end{verbatim}
|
452 |
\end{verbatim}
|
450 |
Calling \texttt{cleanup} on the \texttt{Manifold} removes unused mesh entities, resizes, and resize the vectors containing these entities. When we subsequently call cleanup on the attribute vector, the same reorganization is carried out on the attribute vector keeping it in sync. Strictly speaking cleanup is rarely needed, but it removes unused memory which can be a performance advantage.
|
453 |
Calling \texttt{cleanup} on the \texttt{Manifold} removes unused mesh entities, resizes, and resize the vectors containing these entities. When we subsequently call cleanup on the attribute vector, the same reorganization is carried out on the attribute vector keeping it in sync. Strictly speaking cleanup is rarely needed, but it removes unused memory which can be a performance advantage.
|
451 |
|
454 |
|
452 |
\subsection{Extended Example: Edge Flipping}
|
455 |
\subsection{Extended Example: Edge Flipping}
|
453 |
The following example demonstrates how to create a \texttt{Manifold} and add polygons (in this case triangles) and finally how to flip edges of a manifold. First, let us define some vertices. This is just a vector of 3D points:
|
456 |
The following example demonstrates how to create a \texttt{Manifold} and add polygons (in this case triangles) and finally how to flip edges of a manifold. First, let us define some vertices
|
454 |
\begin{verbatim}
|
457 |
\begin{verbatim}
|
455 |
vector<Vec3f> vertices(3);
|
458 |
vector<Vec3f> vertices(3);
|
456 |
vertices[0] = p1;
|
459 |
vertices[0] = p1;
|
457 |
vertices[1] = p2;
|
460 |
vertices[1] = p2;
|
458 |
vertices[2] = p3;
|
461 |
vertices[2] = p3;
|
Line 475... |
Line 478... |
475 |
&faces[0], &indices[0]);
|
478 |
&faces[0], &indices[0]);
|
476 |
\end{verbatim}
|
479 |
\end{verbatim}
|
477 |
The result of the above is a mesh with a single triangle.
|
480 |
The result of the above is a mesh with a single triangle.
|
478 |
Next, we try to split an edge. We grab the first halfedge and split it: Now we have two triangles. Splitting the halfedge changes the containing triangle to a quadrilateral, and we call triangulate to divide it into two triangls again.
|
481 |
Next, we try to split an edge. We grab the first halfedge and split it: Now we have two triangles. Splitting the halfedge changes the containing triangle to a quadrilateral, and we call triangulate to divide it into two triangls again.
|
479 |
\begin{verbatim}
|
482 |
\begin{verbatim}
|
480 |
HalfEdgeIDIterator h = m.halfedges_begin();
|
483 |
HalfEdgeID h = m.halfedge();
|
481 |
m.split_edge(*h);
|
484 |
m.split_edge(h);
|
482 |
triangulate_face_by_edge_split(m, m.walker(*h).face());
|
485 |
triangulate_face_by_edge_split(m, m.walker(h).face());
|
483 |
\end{verbatim}
|
486 |
\end{verbatim}
|
484 |
Now, we obtain an iterator to the first of our (two) triangles.
|
487 |
Now, we obtain an iterator to the first of our (two) triangles.
|
485 |
\begin{verbatim}
|
488 |
\begin{verbatim}
|
486 |
FaceIDIterator f1 =m.faces_begin();
|
489 |
FaceID f1 = m.faces();
|
487 |
\end{verbatim}
|
490 |
\end{verbatim}
|
488 |
We create a vertex, \texttt{v}, at centre of \texttt{f1} and insert it in that face:
|
491 |
We create a vertex, \texttt{v}, at centre of \texttt{f1} and insert it in that face:
|
489 |
\begin{verbatim}
|
492 |
\begin{verbatim}
|
490 |
m.split_face_by_vertex(*f1);
|
493 |
m.split_face_by_vertex(f1);
|
491 |
\end{verbatim}
|
494 |
\end{verbatim}
|
492 |
The code below, marks one of a pair of halfedges (with the value 1).
|
495 |
The code below, marks one of a pair of halfedges (with the value 1).
|
493 |
\begin{verbatim}
|
496 |
\begin{verbatim}
|
494 |
HalfEdgeAttributeVector<int> touched;
|
497 |
HalfEdgeAttributeVector<int> touched;
|
495 |
|
498 |
|
496 |
// ....
|
499 |
// ....
|
497 |
|
500 |
|
498 |
for(HalfEdgeIDIterator h = m.halfedges_begin();
|
501 |
for(HalfEdgeID h : m.halfedges())
|
499 |
h!=m.halfedges_end(); ++h)
|
- |
|
500 |
{
|
502 |
{
|
501 |
if(m.walker(*h).opp().halfedge() < *h)
|
503 |
if(m.walker(h).opp().halfedge() < h)
|
502 |
touched[*h] =0;
|
504 |
touched[h] =0;
|
503 |
else
|
505 |
else
|
504 |
touched[*h] =1;
|
506 |
touched[h] =1;
|
505 |
}
|
507 |
}
|
506 |
\end{verbatim}
|
508 |
\end{verbatim}
|
507 |
Next, we set the \texttt{flipper} variable to point to the first of these halfedges:
|
509 |
Next, we set the \texttt{flipper} variable to point to the first of these halfedges:
|
508 |
\begin{verbatim}
|
510 |
\begin{verbatim}
|
509 |
flipper = m.halfedges_begin();
|
511 |
flipper = m.halfedges_begin();
|
510 |
\end{verbatim}
|
512 |
\end{verbatim}
|