Fix Bevel artifacts bug T37053
authorhowardt <howard.trickey@gmail.com>
Fri, 15 Nov 2013 15:19:38 +0000 (10:19 -0500)
committerhowardt <howard.trickey@gmail.com>
Fri, 15 Nov 2013 15:47:45 +0000 (10:47 -0500)
In the case that there are two beveled edges with one unbeveled
one in between, and the widths don't allow them to magically
line up, it is better to slide along unbeveled edge.

Sometimes bevel widths are uneven (this was the case before)
and it is a followup TODO to do a width cleanup pass afterwards
to even the edges up as much as possible.

source/blender/bmesh/tools/bmesh_bevel.c

index 7293e40095115eb15d9e774e99bbd0062f7eabc7..a1f08583e2c74e911ac83005b4d599df0d601c1f 100644 (file)
@@ -133,6 +133,7 @@ typedef struct BevelParams {
        int seg;                /* number of segments in beveled edge profile */
        bool vertex_only;       /* bevel vertices only */
        bool use_weights;       /* bevel amount affected by weights on edges or verts */
+       bool preserve_widths;   /* should bevel prefer widths over angles, if forced to choose? */
        const struct MDeformVert *dvert; /* vertex group array, maybe set if vertex_only */
        int vertex_group;       /* vertex group index, maybe set if vertex_only */
 } BevelParams;
@@ -208,6 +209,28 @@ static EdgeHalf *find_edge_half(BevVert *bv, BMEdge *bme)
        return NULL;
 }
 
+/* find the BevVert corresponding to BMVert bmv */
+static BevVert *find_bevvert(BevelParams *bp, BMVert *bmv)
+{
+       return BLI_ghash_lookup(bp->vert_hash, bmv);
+}
+
+/* Find the EdgeHalf representing the other end of e->e.
+ * That may not have been constructed yet, in which case return NULL. */
+static EdgeHalf *find_other_end_edge_half(BevelParams *bp, EdgeHalf *e)
+{
+       BevVert *bvother;
+       EdgeHalf *eother;
+
+       bvother = find_bevvert(bp, e->is_rev ? e->e->v1 : e->e->v2);
+       if (bvother) {
+               eother = find_edge_half(bvother, e->e);
+               BLI_assert(eother != NULL);
+               return eother;
+       }
+       return NULL;
+}
+
 /* Return the next EdgeHalf after from_e that is beveled.
  * If from_e is NULL, find the first beveled edge. */
 static EdgeHalf *next_bev(BevVert *bv, EdgeHalf *from_e)
@@ -225,12 +248,6 @@ static EdgeHalf *next_bev(BevVert *bv, EdgeHalf *from_e)
        return NULL;
 }
 
-/* find the BevVert corresponding to BMVert bmv */
-static BevVert *find_bevvert(BevelParams *bp, BMVert *bmv)
-{
-       return BLI_ghash_lookup(bp->vert_hash, bmv);
-}
-
 /* Return a good representative face (for materials, etc.) for faces
  * created around/near BoundVert v */
 static BMFace *boundvert_rep_face(BoundVert *v)
@@ -522,19 +539,40 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f,
        }
 }
 
+/* Like offset_in_two planes, but for the case where we prefer to solve the problem
+ * of not meeting at the same point by choosing to change the bevel offset on one
+ * of the appropriate side of either e1 or e2, in order that the lines can meet on emid. */
+static void offset_on_edge_between(BevelParams *bp, EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid,
+                                  BMVert *v, float meetco[3])
+{
+       BLI_assert(e1->is_bev && e2->is_bev && !emid->is_bev);
+       
+       /* If have to change offset of e1 or e2, which one?
+        * Prefer the one whose other end hasn't been constructed yet.
+        * Following will choose to change e2 if both have already been constructed. */
+       if (find_other_end_edge_half(bp, e1)) {
+               offset_meet(e1, emid, v, e1->fnext, TRUE, meetco);
+               /* now e2's left offset is probably different */
+               e2->offset_l = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e2->e, v)->co);
+       }
+       else {
+               offset_meet(emid, e2, v, emid->fnext, TRUE, meetco);
+               /* now e1's right offset is probably different */
+               e1->offset_r = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e1->e, v)->co);
+       }
+}
+
 /* Like offset_meet, but with a mid edge between them that is used
  * to calculate the planes in which to run the offset lines.
- * They may not meet exactly: the offsets for the edges may be different
- * or both the planes and the lines may be angled so that they can't meet.
+ * They may not meet exactly: the lines may be angled so that they can't meet,
+ * probably because one or both faces is non-planar.
  * In that case, pick a close point on emid, which should be the dividing
- * edge between the two planes.
- * TODO: should have a global 'offset consistency' prepass to adjust offset
- * widths so that all edges have the same offset at both ends. */
-static void offset_in_two_planes(EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid,
+ * edge between the two planes. */
+static void offset_in_two_planes(BevelParams *bp, EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid,
                                  BMVert *v, float meetco[3])
 {
        float dir1[3], dir2[3], dirmid[3], norm_perp1[3], norm_perp2[3],
-             off1a[3], off1b[3], off2a[3], off2b[3], isect2[3], co[3],
+             off1a[3], off1b[3], off2a[3], off2b[3], isect2[3],
              f1no[3], f2no[3], ang;
        int iret;
 
@@ -562,8 +600,8 @@ static void offset_in_two_planes(EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid,
 
        ang = angle_v3v3(dir1, dir2);
        if (ang < 100.0f * BEVEL_EPSILON) {
-               /* lines are parallel; off1a is a good meet point */
-               copy_v3_v3(meetco, off1a);
+               /* lines are parallel; put intersection on emid */
+               offset_on_edge_between(bp, e1, e2, emid, v, meetco);
        }
        else if (fabsf(ang - (float)M_PI) < 100.0f * BEVEL_EPSILON) {
                slide_dist(e2, v, e2->offset_l, meetco);
@@ -575,11 +613,10 @@ static void offset_in_two_planes(EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid,
                        copy_v3_v3(meetco, off1a);
                }
                else if (iret == 2) {
-                       /* lines are not coplanar; meetco and isect2 are nearest to first and second lines */
+                       /* lines are not coplanar and don't meet; meetco and isect2 are nearest to first and second lines */
                        if (len_v3v3(meetco, isect2) > 100.0f * BEVEL_EPSILON) {
-                               /* offset lines don't meet: project average onto emid; this is not ideal (see TODO above) */
-                               mid_v3_v3v3(co, meetco, isect2);
-                               closest_to_line_v3(meetco, co, v->co, BM_edge_other_vert(emid->e, v)->co);
+                               /* offset lines don't meet so can't preserve widths; fallback on sliding along edge between */
+                               offset_on_edge_between(bp, e1, e2, emid, v, meetco);
                        }
                }
                /* else iret == 1 and the lines are coplanar so meetco has the intersection */
@@ -849,7 +886,10 @@ static void build_boundary(BevelParams *bp, BevVert *bv)
                                if (e->prev->prev->is_bev) {
                                        BLI_assert(e->prev->prev != e); /* see: edgecount 2, selcount 1 case */
                                        /* find meet point between e->prev->prev and e and attach e->prev there */
-                                       offset_in_two_planes(e->prev->prev, e, e->prev, bv->v, co);
+                                       if (bp->preserve_widths)
+                                               offset_in_two_planes(bp, e->prev->prev, e, e->prev, bv->v, co);
+                                       else
+                                               offset_on_edge_between(bp, e->prev->prev, e, e->prev, bv->v, co);
                                        v = add_new_bound_vert(mem_arena, vm, co);
                                        v->efirst = e->prev->prev;
                                        v->elast = v->ebev = e;
@@ -1950,7 +1990,7 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v)
        BMEdge *bme2, *unflagged_bme, *first_bme;
        BMFace *f;
        BMIter iter, iter2;
-       EdgeHalf *e;
+       EdgeHalf *e, *eother;
        float weight, z;
        int i, found_shared_face, ccw_test_sum;
        int nsel = 0;
@@ -2107,39 +2147,51 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v)
                if (e->is_bev) {
                        /* Convert distance as specified by user into offsets along
                         * faces on left side and right side of this edgehalf.
-                        * Except for percent method, offset will be same on each side. */
-                       switch (bp->offset_type) {
-                       case BEVEL_AMT_OFFSET:
-                               e->offset_l = bp->offset;
-                               break;
-                       case BEVEL_AMT_WIDTH:
-                               z = fabs(2.0f * sinf(edge_face_angle(e) / 2.0f));
-                               if (z < BEVEL_EPSILON)
-                                       e->offset_l = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */
-                               else
-                                       e->offset_l = bp->offset / z;
-                               break;
-                       case BEVEL_AMT_DEPTH:
-                               z = fabs(cosf(edge_face_angle(e) / 2.0f));
-                               if (z < BEVEL_EPSILON)
-                                       e->offset_l = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */
-                               else
-                                       e->offset_l = bp->offset / z;
-                               break;
-                       case BEVEL_AMT_PERCENT:
-                               e->offset_l = BM_edge_calc_length(e->prev->e) * bp->offset / 100.0f;
-                               e->offset_r = BM_edge_calc_length(e->next->e) * bp->offset / 100.0f;
-                               break;
-                       default:
-                               BLI_assert(!"bad bevel offset kind");
-                               e->offset_l = bp->offset;
+                        * Except for percent method, offset will be same on each side.
+                        *
+                        * First check to see if other end has had construction made,
+                        * because offset may have been forced to another number
+                        * (but for percent method all 4 widths can be different). */
+
+                       eother = find_other_end_edge_half(bp, e);
+                       if (eother && bp->offset_type != BEVEL_AMT_PERCENT) {
+                               e->offset_l = eother->offset_r;
+                               e->offset_r = eother->offset_l;
                        }
-                       if (bp->offset_type != BEVEL_AMT_PERCENT)
-                               e->offset_r = e->offset_l;
-                       if (bp->use_weights) {
-                               weight = BM_elem_float_data_get(&bm->edata, bme, CD_BWEIGHT);
-                               e->offset_l *= weight;
-                               e->offset_r *= weight;
+                       else {
+                               switch (bp->offset_type) {
+                               case BEVEL_AMT_OFFSET:
+                                       e->offset_l = bp->offset;
+                                       break;
+                               case BEVEL_AMT_WIDTH:
+                                       z = fabs(2.0f * sinf(edge_face_angle(e) / 2.0f));
+                                       if (z < BEVEL_EPSILON)
+                                               e->offset_l = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */
+                                       else
+                                               e->offset_l = bp->offset / z;
+                                       break;
+                               case BEVEL_AMT_DEPTH:
+                                       z = fabs(cosf(edge_face_angle(e) / 2.0f));
+                                       if (z < BEVEL_EPSILON)
+                                               e->offset_l = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */
+                                       else
+                                               e->offset_l = bp->offset / z;
+                                       break;
+                               case BEVEL_AMT_PERCENT:
+                                       e->offset_l = BM_edge_calc_length(e->prev->e) * bp->offset / 100.0f;
+                                       e->offset_r = BM_edge_calc_length(e->next->e) * bp->offset / 100.0f;
+                                       break;
+                               default:
+                                       BLI_assert(!"bad bevel offset kind");
+                                       e->offset_l = bp->offset;
+                               }
+                               if (bp->offset_type != BEVEL_AMT_PERCENT)
+                                       e->offset_r = e->offset_l;
+                               if (bp->use_weights) {
+                                       weight = BM_elem_float_data_get(&bm->edata, bme, CD_BWEIGHT);
+                                       e->offset_l *= weight;
+                                       e->offset_r *= weight;
+                               }
                        }
                }
                else {
@@ -2432,6 +2484,7 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, const f
        bp.seg    = segments;
        bp.vertex_only = vertex_only;
        bp.use_weights = use_weights;
+       bp.preserve_widths = false;
        bp.dvert = dvert;
        bp.vertex_group = vertex_group;