Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/bmesh/intern/bmesh_polygon.c')
-rw-r--r--source/blender/bmesh/intern/bmesh_polygon.c411
1 files changed, 224 insertions, 187 deletions
diff --git a/source/blender/bmesh/intern/bmesh_polygon.c b/source/blender/bmesh/intern/bmesh_polygon.c
index 953e7f4d20c..d235aaaa622 100644
--- a/source/blender/bmesh/intern/bmesh_polygon.c
+++ b/source/blender/bmesh/intern/bmesh_polygon.c
@@ -35,12 +35,17 @@
* degenerate faces.
*/
-#include "BLI_math.h"
-#include "BLI_array.h"
+#include "DNA_listBase.h"
#include "MEM_guardedalloc.h"
+#include "BLI_math.h"
+#include "BLI_array.h"
+#include "BLI_scanfill.h"
+#include "BLI_listbase.h"
+
#include "bmesh.h"
+
#include "intern/bmesh_private.h"
/**
@@ -50,7 +55,7 @@
* Used for tessellator
*/
-static short testedgesidef(const float v1[2], const float v2[2], const float v3[2])
+static bool testedgesidef(const float v1[2], const float v2[2], const float v3[2])
{
/* is v3 to the right of v1 - v2 ? With exception: v3 == v1 || v3 == v2 */
double inp;
@@ -59,13 +64,13 @@ static short testedgesidef(const float v1[2], const float v2[2], const float v3[
inp = (v2[0] - v1[0]) * (v1[1] - v3[1]) + (v1[1] - v2[1]) * (v1[0] - v3[0]);
if (inp < 0.0) {
- return FALSE;
+ return false;
}
else if (inp == 0) {
- if (v1[0] == v3[0] && v1[1] == v3[1]) return FALSE;
- if (v2[0] == v3[0] && v2[1] == v3[1]) return FALSE;
+ if (v1[0] == v3[0] && v1[1] == v3[1]) return false;
+ if (v2[0] == v3[0] && v2[1] == v3[1]) return false;
}
- return TRUE;
+ return true;
}
/**
@@ -151,6 +156,103 @@ static void bm_face_calc_poly_normal_vertex_cos(BMFace *f, float n[3],
}
/**
+ * For tools that insist on using triangles, ideally we would cache this data.
+ *
+ * \param r_loops Store face loop pointers, (f->len)
+ * \param r_index Store triangle triples, indicies into \a r_loops, ((f->len - 2) * 3)
+ */
+int BM_face_calc_tessellation(BMFace *f, BMLoop **r_loops, int (*_r_index)[3])
+{
+ int *r_index = (int *)_r_index;
+ BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
+ BMLoop *l_iter;
+ int totfilltri;
+
+ if (f->len == 3) {
+ *r_loops++ = (l_iter = l_first);
+ *r_loops++ = (l_iter = l_iter->next);
+ *r_loops++ = ( l_iter->next);
+
+ r_index[0] = 0;
+ r_index[1] = 1;
+ r_index[2] = 2;
+ totfilltri = 1;
+ }
+ else if (f->len == 4) {
+ *r_loops++ = (l_iter = l_first);
+ *r_loops++ = (l_iter = l_iter->next);
+ *r_loops++ = (l_iter = l_iter->next);
+ *r_loops++ = ( l_iter->next);
+
+ r_index[0] = 0;
+ r_index[1] = 1;
+ r_index[2] = 2;
+
+ r_index[3] = 0;
+ r_index[4] = 2;
+ r_index[5] = 3;
+ totfilltri = 2;
+ }
+ else {
+ int j;
+
+ ScanFillContext sf_ctx;
+ ScanFillVert *sf_vert, *sf_vert_last = NULL, *sf_vert_first = NULL;
+ /* ScanFillEdge *e; */ /* UNUSED */
+ ScanFillFace *sf_tri;
+
+ BLI_scanfill_begin(&sf_ctx);
+
+ j = 0;
+ l_iter = l_first;
+ do {
+ sf_vert = BLI_scanfill_vert_add(&sf_ctx, l_iter->v->co);
+ sf_vert->tmp.p = l_iter;
+
+ if (sf_vert_last) {
+ /* e = */ BLI_scanfill_edge_add(&sf_ctx, sf_vert_last, sf_vert);
+ }
+
+ sf_vert_last = sf_vert;
+ if (sf_vert_first == NULL) {
+ sf_vert_first = sf_vert;
+ }
+
+ r_loops[j] = l_iter;
+
+ /* mark order */
+ BM_elem_index_set(l_iter, j++); /* set_loop */
+
+ } while ((l_iter = l_iter->next) != l_first);
+
+ /* complete the loop */
+ BLI_scanfill_edge_add(&sf_ctx, sf_vert_first, sf_vert);
+
+ totfilltri = BLI_scanfill_calc_ex(&sf_ctx, 0, f->no);
+ BLI_assert(totfilltri <= f->len - 2);
+ BLI_assert(totfilltri == BLI_countlist(&sf_ctx.fillfacebase));
+
+ for (sf_tri = sf_ctx.fillfacebase.first; sf_tri; sf_tri = sf_tri->next) {
+ int i1 = BM_elem_index_get((BMLoop *)sf_tri->v1->tmp.p);
+ int i2 = BM_elem_index_get((BMLoop *)sf_tri->v2->tmp.p);
+ int i3 = BM_elem_index_get((BMLoop *)sf_tri->v3->tmp.p);
+
+ if (i1 > i2) { SWAP(int, i1, i2); }
+ if (i2 > i3) { SWAP(int, i2, i3); }
+ if (i1 > i2) { SWAP(int, i1, i2); }
+
+ *r_index++ = i1;
+ *r_index++ = i2;
+ *r_index++ = i3;
+ }
+
+ BLI_scanfill_end(&sf_ctx);
+ }
+
+ return totfilltri;
+}
+
+/**
* get the area of the face
*/
float BM_face_calc_area(BMFace *f)
@@ -158,7 +260,6 @@ float BM_face_calc_area(BMFace *f)
BMLoop *l;
BMIter iter;
float (*verts)[3] = BLI_array_alloca(verts, f->len);
- float normal[3];
float area;
int i;
@@ -173,6 +274,7 @@ float BM_face_calc_area(BMFace *f)
area = area_quad_v3(verts[0], verts[1], verts[2], verts[3]);
}
else {
+ float normal[3];
calc_poly_normal(normal, verts, f->len);
area = area_poly_v3(f->len, verts, normal);
}
@@ -297,7 +399,6 @@ static void scale_edge_v3f(float v1[3], float v2[3], const float fac)
add_v3_v3v3(v2, v2, mid);
}
-
/**
* \brief POLY ROTATE PLANE
*
@@ -306,30 +407,14 @@ static void scale_edge_v3f(float v1[3], float v2[3], const float fac)
*/
void poly_rotate_plane(const float normal[3], float (*verts)[3], const int nverts)
{
-
- float up[3] = {0.0f, 0.0f, 1.0f}, axis[3], q[4];
float mat[3][3];
- float angle;
- int i;
-
- cross_v3_v3v3(axis, normal, up);
-
- angle = saacos(dot_v3v3(normal, up));
- if (angle < FLT_EPSILON)
- return;
-
- if (len_v3(axis) < FLT_EPSILON) {
- axis[0] = 0.0f;
- axis[1] = 1.0f;
- axis[2] = 0.0f;
+ if (axis_dominant_v3_to_m3(mat, normal)) {
+ int i;
+ for (i = 0; i < nverts; i++) {
+ mul_m3_v3(mat, verts[i]);
+ }
}
-
- axis_angle_to_quat(q, axis, angle);
- quat_to_mat3(mat, q);
-
- for (i = 0; i < nverts; i++)
- mul_m3_v3(mat, verts[i]);
}
/**
@@ -498,7 +583,7 @@ void BM_face_normal_flip(BMesh *bm, BMFace *f)
/* detects if two line segments cross each other (intersects).
* note, there could be more winding cases then there needs to be. */
-static int line_crosses_v2f(const float v1[2], const float v2[2], const float v3[2], const float v4[2])
+static bool line_crosses_v2f(const float v1[2], const float v2[2], const float v3[2], const float v4[2])
{
#define GETMIN2_AXIS(a, b, ma, mb, axis) \
@@ -526,7 +611,7 @@ static int line_crosses_v2f(const float v1[2], const float v2[2], const float v3
w5 = !testedgesidef(v3, v1, v4);
if (w1 == w2 && w2 == w3 && w3 == w4 && w4 == w5) {
- return TRUE;
+ return true;
}
GETMIN2(v1, v2, mv1, mv2);
@@ -549,7 +634,7 @@ static int line_crosses_v2f(const float v1[2], const float v2[2], const float v3
return (mv4[1] >= mv1[1] && mv3[1] <= mv2[1]);
}
- return FALSE;
+ return false;
#undef GETMIN2_AXIS
#undef GETMIN2
@@ -567,7 +652,7 @@ static int line_crosses_v2f(const float v1[2], const float v2[2], const float v3
* instead of projecting co directly into f's orientation space,
* so there might be accuracy issues.
*/
-int BM_face_point_inside_test(BMFace *f, const float co[3])
+bool BM_face_point_inside_test(BMFace *f, const float co[3])
{
int ax, ay;
float co2[2], cent[2] = {0.0f, 0.0f}, out[2] = {FLT_MAX * 0.5f, FLT_MAX * 0.5f};
@@ -614,26 +699,26 @@ int BM_face_point_inside_test(BMFace *f, const float co[3])
return crosses % 2 != 0;
}
-static int bm_face_goodline(float const (*projectverts)[3], BMFace *f, int v1i, int v2i, int v3i)
+static bool bm_face_goodline(float const (*projectverts)[2], BMFace *f, int v1i, int v2i, int v3i)
{
BMLoop *l_iter;
BMLoop *l_first;
- float v1[3], v2[3], v3[3], pv1[3];
- int i;
- copy_v3_v3(v1, projectverts[v1i]);
- copy_v3_v3(v2, projectverts[v2i]);
- copy_v3_v3(v3, projectverts[v3i]);
+ float pv1[2];
+ const float *v1 = projectverts[v1i];
+ const float *v2 = projectverts[v2i];
+ const float *v3 = projectverts[v3i];
+ int i;
/* v3 must be on the left side of [v1, v2] line, else we know [v1, v3] is outside of f! */
if (testedgesidef(v1, v2, v3)) {
- return FALSE;
+ return false;
}
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
i = BM_elem_index_get(l_iter->v);
- copy_v3_v3(pv1, projectverts[i]);
+ copy_v2_v2(pv1, projectverts[i]);
if (ELEM3(i, v1i, v2i, v3i)) {
#if 0
@@ -649,23 +734,24 @@ static int bm_face_goodline(float const (*projectverts)[3], BMFace *f, int v1i,
else
printf("%d in (%d, %d, %d)\n", v1i, i, v3i, v2i);
#endif
- return FALSE;
+ return false;
}
} while ((l_iter = l_iter->next) != l_first);
- return TRUE;
+ return true;
}
/**
* \brief Find Ear
*
* Used by tessellator to find the next triangle to 'clip off' of a polygon while tessellating.
+ *
* \param f The face to search.
- * \param verts an array of face vert coords.
+ * \param projectverts an array of face vert coords.
* \param use_beauty Currently only applies to quads, can be extended later on.
* \param abscoss Must be allocated by caller, and at least f->len length
* (allow to avoid allocating a new one for each tri!).
*/
-static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, float *abscoss)
+static BMLoop *poly_find_ear(BMFace *f, float (*projectverts)[2], const bool use_beauty, float *abscoss)
{
BMLoop *bestear = NULL;
@@ -690,7 +776,7 @@ static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, floa
i = (((len_squared_v3v3(larr[0]->v->co, larr[2]->v->co) >
len_squared_v3v3(larr[1]->v->co, larr[3]->v->co) * bias)) != use_beauty);
i4 = (i + 3) % 4;
- /* Check produced tris aren’t too flat/narrow...
+ /* Check produced tris aren't too flat/narrow...
* Probably not the best test, but is quite efficient and should at least avoid null-area faces! */
cos1 = fabsf(cos_v3v3v3(larr[i4]->v->co, larr[i]->v->co, larr[i + 1]->v->co));
cos2 = fabsf(cos_v3v3v3(larr[i4]->v->co, larr[i + 2]->v->co, larr[i + 1]->v->co));
@@ -711,7 +797,7 @@ static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, floa
/* Last check we do not get overlapping triangles
* (as much as possible, there are some cases with no good solution!) */
i4 = (i + 3) % 4;
- if (!bm_face_goodline((float const (*)[3])verts, f, BM_elem_index_get(larr[i4]->v),
+ if (!bm_face_goodline((float const (*)[2])projectverts, f, BM_elem_index_get(larr[i4]->v),
BM_elem_index_get(larr[i]->v), BM_elem_index_get(larr[i + 1]->v)))
{
i = !i;
@@ -721,77 +807,44 @@ static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, floa
}
else {
- BMVert *v1, *v2, *v3;
-
/* float angle, bestangle = 180.0f; */
- float cos, tcos, bestcos = 1.0f;
- float *tcoss;
- int isear, i = 0, j, len;
+ float cos, bestcos = 1.0f;
+ int i, j, len;
/* Compute cos of all corners! */
+ i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
len = l_iter->f->len;
- tcoss = abscoss;
do {
- v1 = l_iter->prev->v;
- v2 = l_iter->v;
- v3 = l_iter->next->v;
+ const BMVert *v1 = l_iter->prev->v;
+ const BMVert *v2 = l_iter->v;
+ const BMVert *v3 = l_iter->next->v;
- *tcoss = fabsf(cos_v3v3v3(v1->co, v2->co, v3->co));
+ abscoss[i] = fabsf(cos_v3v3v3(v1->co, v2->co, v3->co));
/* printf("tcoss: %f\n", *tcoss);*/
- tcoss++;
+ i++;
} while ((l_iter = l_iter->next) != l_first);
+ i = 0;
l_iter = l_first;
- tcoss = abscoss;
do {
- isear = TRUE;
+ const BMVert *v1 = l_iter->prev->v;
+ const BMVert *v2 = l_iter->v;
+ const BMVert *v3 = l_iter->next->v;
- v1 = l_iter->prev->v;
- v2 = l_iter->v;
- v3 = l_iter->next->v;
-
- /* We may have already internal edges... */
- if (BM_edge_exists(v1, v3)) {
- isear = FALSE;
- }
- else if (!bm_face_goodline((float const (*)[3])verts, f, BM_elem_index_get(v1),
- BM_elem_index_get(v2), BM_elem_index_get(v3)))
+ if (bm_face_goodline((float const (*)[2])projectverts, f,
+ BM_elem_index_get(v1), BM_elem_index_get(v2), BM_elem_index_get(v3)))
{
-#if 0
- printf("(%d, %d, %d) would not be a valid tri!\n",
- BM_elem_index_get(v1), BM_elem_index_get(v2), BM_elem_index_get(v3));
-#endif
- isear = FALSE;
- }
-
- if (isear) {
-#if 0 /* Old, already commented code */
- /* if this code comes back, it needs to be converted to radians */
- angle = angle_v3v3v3(verts[v1->head.eflag2], verts[v2->head.eflag2], verts[v3->head.eflag2]);
- if (!bestear || ABS(angle - 45.0f) < bestangle) {
- bestear = l;
- bestangle = ABS(45.0f - angle);
- }
-
- if (angle > 20 && angle < 90) break;
- if (angle < 100 && i > 5) break;
- i += 1;
-#endif
-
/* Compute highest cos (i.e. narrowest angle) of this tri. */
- cos = *tcoss;
- tcos = fabsf(cos_v3v3v3(v2->co, v3->co, v1->co));
- if (tcos > cos)
- cos = tcos;
- tcos = fabsf(cos_v3v3v3(v3->co, v1->co, v2->co));
- if (tcos > cos)
- cos = tcos;
+ cos = max_fff(abscoss[i],
+ fabsf(cos_v3v3v3(v2->co, v3->co, v1->co)),
+ fabsf(cos_v3v3v3(v3->co, v1->co, v2->co)));
/* Compare to prev best (i.e. lowest) cos. */
if (cos < bestcos) {
/* We must check this tri would not leave a (too much) degenerated remaining face! */
- /* For now just assume if the average of cos of all "remaining face"'s corners is below a given threshold, it’s OK. */
+ /* For now just assume if the average of cos of all
+ * "remaining face"'s corners is below a given threshold, it's OK. */
float avgcos = fabsf(cos_v3v3v3(v1->co, v3->co, l_iter->next->next->v->co));
const int i_limit = (i - 1 + len) % len;
avgcos += fabsf(cos_v3v3v3(l_iter->prev->prev->v->co, v1->co, v3->co));
@@ -815,7 +868,6 @@ static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, floa
#endif
}
}
- tcoss++;
i++;
} while ((l_iter = l_iter->next) != l_first);
}
@@ -826,120 +878,71 @@ static BMLoop *find_ear(BMFace *f, float (*verts)[3], const int use_beauty, floa
/**
* \brief BMESH TRIANGULATE FACE
*
- * --- Prev description (wasn’t correct, ear clipping was currently simply picking the first tri in the loop!)
- * Triangulates a face using a simple 'ear clipping' algorithm that tries to
- * favor non-skinny triangles (angles less than 90 degrees).
- *
- * If the triangulator has bits left over (or cannot triangulate at all)
- * it uses a simple fan triangulation,
- * --- End of prev description
- *
- * Currently tries to repeatedly find the best triangle (i.e. the most "open" one), provided it does not
+ * Currently repeatedly find the best triangle (i.e. the most "open" one), provided it does not
* produces a "remaining" face with too much wide/narrow angles
* (using cos (i.e. dot product of normalized vectors) of angles).
*
- * newfaces, if non-null, must be an array of BMFace pointers,
- * with a length equal to f->len. It will be filled with the new
- * triangles, and will be NULL-terminated.
+ * \param r_faces_new if non-null, must be an array of BMFace pointers,
+ * with a length equal to (f->len - 2). It will be filled with the new
+ * triangles.
*
- * \note newedgeflag sets a flag layer flag, obviously not the header flag.
+ * \note use_tag tags new flags and edges.
*/
-void BM_face_triangulate(BMesh *bm, BMFace *f, float (*projectverts)[3], const short newedge_oflag,
- const short newface_oflag, BMFace **newfaces, const short use_beauty)
+void BM_face_triangulate(BMesh *bm, BMFace *f,
+ BMFace **r_faces_new,
+ const bool use_beauty, const bool use_tag)
{
- int i, done, nvert, nf_i = 0;
- BMLoop *newl;
+ const float f_len_orig = f->len;
+ int i, nf_i = 0;
+ BMLoop *l_new;
BMLoop *l_iter;
BMLoop *l_first;
/* BM_face_triangulate: temp absolute cosines of face corners */
- float *abscoss = BLI_array_alloca(abscoss, f->len);
+ float (*projectverts)[2] = BLI_array_alloca(projectverts, f_len_orig);
+ float *abscoss = BLI_array_alloca(abscoss, f_len_orig);
+ float mat[3][3];
+
+ axis_dominant_v3_to_m3(mat, f->no);
/* copy vertex coordinates to vertspace area */
i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
- copy_v3_v3(projectverts[i], l_iter->v->co);
- BM_elem_index_set(l_iter->v, i); /* set dirty! */
- i++;
+ mul_v2_m3v3(projectverts[i], mat, l_iter->v->co);
+ BM_elem_index_set(l_iter->v, i++); /* set dirty! */
} while ((l_iter = l_iter->next) != l_first);
bm->elem_index_dirty |= BM_VERT; /* see above */
- /* bmesh_face_normal_update(bm, f, f->no, projectverts); */
-
- calc_poly_normal(f->no, projectverts, f->len);
- poly_rotate_plane(f->no, projectverts, i);
+ while (f->len > 3) {
+ l_iter = poly_find_ear(f, projectverts, use_beauty, abscoss);
- nvert = f->len;
-
- /* calc_poly_plane(projectverts, i); */
- for (i = 0; i < nvert; i++) {
- projectverts[i][2] = 0.0f;
- }
+ /* force triangulation - if we can't find an ear the face is degenerate */
+ if (l_iter == NULL) {
+ l_iter = BM_FACE_FIRST_LOOP(f);
+ }
- done = FALSE;
- while (!done && f->len > 3) {
- done = TRUE;
- l_iter = find_ear(f, projectverts, use_beauty, abscoss);
- if (l_iter) {
- done = FALSE;
-/* printf("Subdividing face...\n");*/
- f = BM_face_split(bm, l_iter->f, l_iter->prev->v, l_iter->next->v, &newl, NULL, TRUE);
-
- if (UNLIKELY(!f)) {
- fprintf(stderr, "%s: triangulator failed to split face! (bmesh internal error)\n", __func__);
- break;
- }
+/* printf("Subdividing face...\n");*/
+ f = BM_face_split(bm, l_iter->f, l_iter->prev->v, l_iter->next->v, &l_new, NULL, true);
- copy_v3_v3(f->no, l_iter->f->no);
- BMO_elem_flag_enable(bm, newl->e, newedge_oflag);
- BMO_elem_flag_enable(bm, f, newface_oflag);
-
- if (newfaces)
- newfaces[nf_i++] = f;
+ if (UNLIKELY(!f)) {
+ fprintf(stderr, "%s: triangulator failed to split face! (bmesh internal error)\n", __func__);
+ break;
+ }
-#if 0
- l = f->loopbase;
- do {
- if (l->v == v) {
- f->loopbase = l;
- break;
- }
- l = l->next;
- } while (l != f->loopbase);
-#endif
+ copy_v3_v3(f->no, l_iter->f->no);
+ if (use_tag) {
+ BM_elem_flag_enable(l_new->e, BM_ELEM_TAG);
+ BM_elem_flag_enable(f, BM_ELEM_TAG);
}
- }
-
-#if 0 /* XXX find_ear should now always return a corner, so no more need for this piece of code... */
- if (f->len > 3) {
- l_iter = BM_FACE_FIRST_LOOP(f);
- while (l_iter->f->len > 3) {
- nextloop = l_iter->next->next;
- f = BM_face_split(bm, l_iter->f, l_iter->v, nextloop->v,
- &newl, NULL, TRUE);
- if (!f) {
- printf("triangle fan step of triangulator failed.\n");
-
- /* NULL-terminate */
- if (newfaces) newfaces[nf_i] = NULL;
- return;
- }
- if (newfaces) newfaces[nf_i++] = f;
-
- BMO_elem_flag_enable(bm, newl->e, newedge_oflag);
- BMO_elem_flag_enable(bm, f, newface_oflag);
- l_iter = nextloop;
+ if (r_faces_new) {
+ r_faces_new[nf_i++] = f;
}
}
-#endif
- /* NULL-terminate */
- if (newfaces) {
- newfaces[nf_i] = NULL;
- }
+ BLI_assert(f->len == 3);
}
/**
@@ -1078,3 +1081,37 @@ void BM_face_legal_splits(BMesh *bm, BMFace *f, BMLoop *(*loops)[2], int len)
}
}
}
+
+
+/**
+ * Small utility functions for fast access
+ *
+ * faster alternative to:
+ * BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 3);
+ */
+void BM_face_as_array_vert_tri(BMFace *f, BMVert *r_verts[3])
+{
+ BMLoop *l = BM_FACE_FIRST_LOOP(f);
+
+ BLI_assert(f->len == 3);
+
+ r_verts[0] = l->v; l = l->next;
+ r_verts[1] = l->v; l = l->next;
+ r_verts[2] = l->v;
+}
+
+/**
+ * faster alternative to:
+ * BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 4);
+ */
+void BM_face_as_array_vert_quad(BMFace *f, BMVert *r_verts[4])
+{
+ BMLoop *l = BM_FACE_FIRST_LOOP(f);
+
+ BLI_assert(f->len == 4);
+
+ r_verts[0] = l->v; l = l->next;
+ r_verts[1] = l->v; l = l->next;
+ r_verts[2] = l->v; l = l->next;
+ r_verts[3] = l->v;
+}