mirror of https://github.com/CGAL/cgal
142 lines
5.4 KiB
TeX
142 lines
5.4 KiB
TeX
|
|
The $\sqrt{3}$ subdivision scheme was introduced by
|
|
Kobbelt~\cite{sqrt3}. It is an adaptive scheme, but in our example
|
|
progra we realize only a single uniform subdivision step.
|
|
|
|
The subdivision step takes a triangle mesh as input and splits each
|
|
facet at its centroid into three triangles. We write a function that
|
|
creates the centroid for one triangle. The combinatorial part exists
|
|
already as an Euler operator for \cgalpoly, only the coordinates of
|
|
the new vertex need to be computed. We exploit the knowledge that the
|
|
facet is a triangle and access the 1-ring of the centroid directly
|
|
without any loops or branching decisions.
|
|
|
|
\begin{lstlisting}
|
|
void create_centroid( Polyhedron& P, Facet_iterator f) {
|
|
Halfedge_handle h = f->halfedge();
|
|
Vector vec = h->vertex()->point() - CGAL::ORIGIN;
|
|
vec = vec + (h->next()->vertex()->point() - CGAL::ORIGIN);
|
|
vec = vec + (h->next()->next()->vertex()->point() - CGAL::ORIGIN);
|
|
Halfedge_handle new_center = P.create_center_vertex( h);
|
|
new_center->vertex()->point() = CGAL::ORIGIN + (vec / 3.0);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
We could instead generalize the method to allow non-triangular facets
|
|
in the first step. We would need to to replace the centroid
|
|
computation with a loop based on a circulator over the halfedges
|
|
surrounding the facet. Circulators are the corresponding concept for
|
|
circular sequences as iterator are the concept for linear sequences.
|
|
Main difference are the lack of a past-the-end position for
|
|
circulators that leads to the typical ideom of a do-while-loop instead
|
|
of the usual while-loop for iterators. The example loop could like this
|
|
including the count for the facet size:
|
|
|
|
\begin{lstlisting}
|
|
Halfedge_around_facet_circulator h = f->facet_begin();
|
|
do {
|
|
vec = vec + ( h->vertex()->point() - CGAL::ORIGIN);
|
|
++ facet_size;
|
|
} while ( ++h != f->facet_begin());
|
|
\end{lstlisting}
|
|
|
|
Next, all edges of the initial mesh are flipped so that they join two
|
|
adjacent centroids. The edge flip is part of the \cgalpoly\ interface.
|
|
|
|
Finally, each initial vertex is replaced by a barycentric combination
|
|
of its neighbors. However, the mesh has already been subdivided, so
|
|
the original neighbors of a vertex are actually every other vertex in
|
|
the 1-ring. We write a function object for the smoothing step that
|
|
will be efficiently applicable with the \lstinline!std::transform!
|
|
function.
|
|
|
|
\begin{lstlisting}
|
|
struct Smooth_old_vertex {
|
|
Point operator()( const Vertex& v) const {
|
|
std::size_t degree = v.vertex_degree() / 2;
|
|
double alpha = (4.0 - 2.0 * cos( 2.0 * CGAL_PI / degree)) / 9.0;
|
|
Vector vec = (v.point() - CGAL::ORIGIN) * ( 1.0 - alpha);
|
|
Halfedge_around_vertex_const_circulator h = v.vertex_begin();
|
|
do {
|
|
vec = vec + ( h->opposite()->vertex()->point() - CGAL::ORIGIN)
|
|
* alpha / degree;
|
|
++ h; ++ h;
|
|
} while ( h != v.vertex_begin());
|
|
return (CGAL::ORIGIN + vec);
|
|
}
|
|
};
|
|
\end{lstlisting}
|
|
|
|
We are ready to assemble the subdivision program. We are exloiting
|
|
that newly created items are appended at the end, so that we can keep valid
|
|
iterators that tell us where the old items end and where the new items
|
|
start. We use this to be as economical as possible with the extra
|
|
storage needed in this method, which is an extra array for the
|
|
smoothed coordinates of original vertices. We start by creating the
|
|
centroids, then smooth the old vertices, and conclude with flipping
|
|
the old edges.
|
|
|
|
\begin{lstlisting}
|
|
void subdiv( Polyhedron& P) {
|
|
std::size_t nv = P.size_of_vertices();
|
|
Vertex_iterator last_v = P.vertices_end();
|
|
-- last_v; // the last of the old vertices
|
|
Edge_iterator last_e = P.edges_end();
|
|
-- last_e; // the last of the old edges
|
|
Facet_iterator last_f = P.facets_end();
|
|
-- last_f; // the last of the old facets
|
|
|
|
Facet_iterator f = P.facets_begin(); // centroids
|
|
do {
|
|
create_centroid( P, f);
|
|
} while ( f++ != last_f);
|
|
|
|
std::vector<Point> pts; // smooth old vertices
|
|
pts.reserve( nv); // get intermediate space for the new points
|
|
++ last_v; // make it the past-the-end position again
|
|
std::transform( P.vertices_begin(), last_v, std::back_inserter( pts),
|
|
Smooth_old_vertex());
|
|
std::copy( pts.begin(), pts.end(), P.points_begin());
|
|
|
|
++ last_e; // make it the past-the-end position again
|
|
for ( Edge_iterator e = P.edges_begin(); e != last_e; ++e)
|
|
P.flip_edge(e); // flip the old edges
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
An example of one step of
|
|
the $\sqrt{3}$ subdivision scheme is shown in
|
|
Fig.\ref{fig:sqrt3_basic}, and an example of several steps is shown in
|
|
Fig.\ref{fig:sqrt3}.
|
|
|
|
\begin{figure}[htb]
|
|
\centering{\includegraphics[width=7.0cm]{figs/sqrt3_basic}}
|
|
\caption{The $\sqrt{3}$-Subdivision scheme is decomposed as
|
|
a set of Euler operators: face splits and edge flips.}
|
|
\label{fig:sqrt3_basic}
|
|
\end{figure}
|
|
|
|
\begin{figure}[htb]
|
|
\centering{\includegraphics[width=7.0cm]{figs/sqrt3}}
|
|
\caption{$\sqrt{3}$-Subdivision of the mannequin mesh.}
|
|
\label{fig:sqrt3}
|
|
\end{figure}
|
|
|
|
\begin{tabular}{l|lll}
|
|
\textbf{$\sqrt{3}$ subdivision} & \cgal\ float & \cgal\ double &
|
|
\openmesh\ float \\\hline
|
|
Lion vase: subdiv 1 & 0.95 & 1.22 & 1.27 \\
|
|
Lion vase: subdiv 2 & 3.90 & 23.73 & 128 (swap)
|
|
\end{tabular}
|
|
|
|
\begin{tabular}{l|ll}
|
|
\textbf{load OFF file} & \cgal (binary) & \openmesh (ascii) \\\hline
|
|
Bunny & 0.7 & 0.8\\
|
|
Lion vase & 4.2 & 4.5 \\
|
|
David & 7.6 & 9.6 \\
|
|
Raptor & 22.0 7 20.6
|
|
\end{tabular}
|
|
|