From da91575206cda63966fbd526378de98a866e8e5c Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Mon, 2 Dec 2013 07:16:49 -0500 Subject: Bevel: add width consistency pass. When the desired widths (offsets) of beveled edges cannot be satisfied, often because we want them to meet on an intermediate non-beveled edge, we need to compromise on the widths somehow. This code changes the compromise to minimize the sum of squares of errors in the offsets. It also adds a global width consistency pass: starting from a vertex that needed width adjustment, it uses a breadth-first search to try to propagate the adjustments and keep the bevel widths from having to taper along the edges. Also fixed a case where a reflex angle would cause bad results. Also fixed the way the 'percentage' width method was calculated. --- source/blender/bmesh/tools/bmesh_bevel.c | 595 +++++++++++++++++++++++-------- 1 file changed, 445 insertions(+), 150 deletions(-) diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index 5e68e468fb8..7f39cbee2c5 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -35,6 +35,7 @@ #include "BLI_array.h" #include "BLI_alloca.h" +#include "BLI_gsqueue.h" #include "BLI_math.h" #include "BLI_memarena.h" @@ -75,6 +76,8 @@ typedef struct EdgeHalf { int seg; /* how many segments for the bevel */ float offset_l; /* offset for this edge, on left side */ float offset_r; /* offset for this edge, on right side */ + float offset_l_spec; /* user specification for offset_l */ + float offset_r_spec; /* user specification for offset_r */ bool is_bev; /* is this edge beveled? */ bool is_rev; /* is e->v2 the vertex at this end? */ bool is_seam; /* is e a seam for custom loopdata (e.g., UVs)? */ @@ -117,6 +120,7 @@ typedef struct BevVert { int selcount; /* number of selected edges around the vertex */ float offset; /* offset for this vertex, if vertex_only bevel */ bool any_seam; /* any seams on attached edges? */ + bool visited; /* used in graph traversal */ EdgeHalf *edges; /* array of size edgecount; CCW order from vertex normal side */ VMesh *vmesh; /* mesh structure for replacing vertex */ } BevVert; @@ -134,6 +138,7 @@ typedef struct BevelParams { 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? */ + bool limit_offset; /* should offsets be limited by collisions? */ const struct MDeformVert *dvert; /* vertex group array, maybe set if vertex_only */ int vertex_group; /* vertex group index, maybe set if vertex_only */ } BevelParams; @@ -166,6 +171,11 @@ static BoundVert *add_new_bound_vert(MemArena *mem_arena, VMesh *vm, const float return ans; } +BLI_INLINE void adjust_bound_vert(BoundVert *bv, const float co[3]) +{ + copy_v3_v3(bv->nv.co, co); +} + /* Mesh verts are indexed (i, j, k) where * i = boundvert index (0 <= i < nv) * j = ring index (0 <= j <= ns2) @@ -216,21 +226,55 @@ static BevVert *find_bevvert(BevelParams *bp, BMVert *bmv) } /* Find the EdgeHalf representing the other end of e->e. + * Return other end's BevVert in *bvother, if r_bvother is provided. * That may not have been constructed yet, in which case return NULL. */ -static EdgeHalf *find_other_end_edge_half(BevelParams *bp, EdgeHalf *e) +static EdgeHalf *find_other_end_edge_half(BevelParams *bp, EdgeHalf *e, BevVert **r_bvother) { - BevVert *bvother; + BevVert *bvo; EdgeHalf *eother; - bvother = find_bevvert(bp, e->is_rev ? e->e->v1 : e->e->v2); - if (bvother) { - eother = find_edge_half(bvother, e->e); + bvo = find_bevvert(bp, e->is_rev ? e->e->v1 : e->e->v2); + if (bvo) { + if (r_bvother) + *r_bvother = bvo; + eother = find_edge_half(bvo, e->e); BLI_assert(eother != NULL); return eother; } + else if (r_bvother) { + *r_bvother = NULL; + } return NULL; } +static bool other_edge_half_visited(BevelParams *bp, EdgeHalf *e) +{ + BevVert *bvo; + + bvo = find_bevvert(bp, e->is_rev ? e->e->v1 : e->e->v2); + if (bvo) + return bvo->visited; + else + return false; +} + +static bool edge_half_offset_changed(EdgeHalf *e) +{ + return e->offset_l != e->offset_l_spec || + e->offset_r != e->offset_r_spec; +} + +static bool any_edge_half_offset_changed(BevVert *bv) +{ + int i; + + for (i = 0; i < bv->edgecount; i++) { + if (edge_half_offset_changed(&bv->edges[i])) + return true; + } + return false; +} + /* 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) @@ -472,11 +516,14 @@ static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) * When offsets are equal, the new point is on the edge bisector, with length offset/sin(angle/2), * but if the offsets are not equal (allowing for this, as bevel modifier has edge weights that may * lead to different offsets) then meeting point can be found be intersecting offset lines. + * If making the meeting point significantly changes the left or right offset from the user spec, + * record the change in offset_l (or offset_r); later we can tell that a change has happened because + * the offset will differ from its original value in offset_l_spec (or offset_r_spec). */ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float meetco[3]) { float dir1[3], dir2[3], norm_v[3], norm_perp1[3], norm_perp2[3], - off1a[3], off1b[3], off2a[3], off2b[3], isect2[3], ang; + off1a[3], off1b[3], off2a[3], off2b[3], isect2[3], ang, d; /* get direction vectors for two offset lines */ sub_v3_v3v3(dir1, v->co, BM_edge_other_vert(e1->e, v)->co); @@ -495,13 +542,17 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float normalize_v3(norm_perp1); copy_v3_v3(off1a, v->co); madd_v3_v3fl(off1a, norm_perp1, e1->offset_r); + if (e2->offset_l != e1->offset_r) + e2->offset_l = e1->offset_r; copy_v3_v3(meetco, off1a); } else if (fabsf(ang - (float)M_PI) < 100.0f * BEVEL_EPSILON) { /* special case e1 and e2 are antiparallel, so bevel is into * a zero-area face. Just make the offset point on the * common line, at offset distance from v. */ - slide_dist(e2, v, e2->offset_l, meetco); + slide_dist(e2, v, e1->offset_r, meetco); + if (e2->offset_l != e1->offset_r) + e2->offset_l = e1->offset_r; } else { /* Get normal to plane where meet point should be, @@ -532,45 +583,122 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float BLI_assert(!"offset_meet failure"); #endif copy_v3_v3(meetco, off1a); /* just to do something */ + d = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e2->e, v)->co); + if (fabsf(d - e2->offset_l) > BEVEL_EPSILON) + e2->offset_l = d; } } } -/* 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. */ +/* Calculate the meeting point between e1 and e2 (one of which should have zero offsets), + * where e1 precedes e2 in CCW order around their common vertex v (viewed from normal side). + * If r_angle is provided, return the angle between e and emeet in *r_angle. + * If the angle is 0, or it is 180 degrees or larger, there will be no meeting point; + * return false in that case, else true */ +static bool offset_meet_edge(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, float meetco[3], float *r_angle) +{ + float dir1[3], dir2[3], fno[3], ang, sinang; + + sub_v3_v3v3(dir1, BM_edge_other_vert(e1->e, v)->co, v->co); + sub_v3_v3v3(dir2, BM_edge_other_vert(e2->e, v)->co, v->co); + normalize_v3(dir1); + normalize_v3(dir2); + + /* find angle from dir1 to dir2 as viewed from vertex normal side */ + ang = angle_normalized_v3v3(dir1, dir2); + if (ang < BEVEL_EPSILON) { + if (r_angle) + *r_angle = 0.0f; + return false; + } + cross_v3_v3v3(fno, dir1, dir2); + if (dot_v3v3(fno, v->no) < 0.0f) + ang = 2.0f * (float)M_PI - ang; /* angle is reflex */ + if (r_angle) + *r_angle = ang; + + if (ang - (float)M_PI > BEVEL_EPSILON) + return false; + + sinang = sinf(ang); + copy_v3_v3(meetco, v->co); + if (e1->offset_r == 0.0f) + madd_v3_v3fl(meetco, dir1, e2->offset_l / sinang); + else + madd_v3_v3fl(meetco, dir2, e1->offset_r / sinang); + return true; +} + +/* Calculate the best place for a meeting point for the offsets from edges e1 and e2 + * on the in-between edge emid. Viewed from the vertex normal side, the CCW + * order of these edges is e1, emid, e2. + * The offsets probably do not meet at a common point on emid, so need to pick + * one that causes the least problems. If the other end of one of e1 or e2 has been visited + * already, prefer to keep the offset the same on this end. + * Otherwise, pick a point between the two intersection points on emid that minimizes + * the sum of squares of errors from desired offset. */ static void offset_on_edge_between(BevelParams *bp, EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid, BMVert *v, float meetco[3]) { + float d, ang1, ang2, sina1, sina2, lambda; + float meet1[3], meet2[3]; + bool visited1, visited2, ok1, ok2; + 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, 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); + + visited1 = other_edge_half_visited(bp, e1); + visited2 = other_edge_half_visited(bp, e2); + + ok1 = offset_meet_edge(e1, emid, v, meet1, &ang1); + ok2 = offset_meet_edge(emid, e2, v, meet2, &ang2); + if (ok1 && ok2) { + if (visited1 && !visited2) { + copy_v3_v3(meetco, meet1); + } + else if (!visited1 && visited2) { + copy_v3_v3(meetco, meet2); + } + else { + /* find best compromise meet point */ + sina1 = sinf(ang1); + sina2 = sinf(ang2); + lambda = sina2 * sina2 / (sina1 * sina1 + sina2 * sina2); + interp_v3_v3v3(meetco, meet1, meet2, lambda); + } } - else { - offset_meet(emid, e2, v, emid->fnext, 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); + else if (ok1 && !ok2) { + copy_v3_v3(meetco, meet1); + } + else if (!ok1 && ok2) { + copy_v3_v3(meetco, meet2); } + else { + /* Neither offset line met emid. + * This should only happen if all three lines are on top of each other */ + slide_dist(emid, v, e1->offset_r, meetco); + } + + /* offsets may have changed now */ + d = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e1->e, v)->co); + if (fabsf(d - e1->offset_r) > BEVEL_EPSILON) + e1->offset_r = d; + d = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e2->e, v)->co); + if (fabsf(d - e2->offset_l) > BEVEL_EPSILON) + e2->offset_l = d; } -/* 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 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. */ +/* Calculate the best place for a meeting point for the offsets from edges e1 and e2 + * when there is an in-between edge emid, and we prefer to have a point that may not + * be on emid if that does a better job of keeping offsets at the user spec. + * Viewed from the vertex normal side, the CCW order of the edges is e1, emid, e2. + * The offset lines may not meet exactly: the lines may be angled so that they can't meet. + * In that case, pick the the offset_on_edge_between. */ static void offset_in_two_planes(BevelParams *bp, EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid, - BMVert *v, float meetco[3]) + 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], - f1no[3], f2no[3], ang; + f1no[3], f2no[3], ang, d; int iret; /* get direction vectors for two offset lines */ @@ -610,17 +738,23 @@ static void offset_in_two_planes(BevelParams *bp, EdgeHalf *e1, EdgeHalf *e2, Ed } else if (fabsf(ang - (float)M_PI) < 100.0f * BEVEL_EPSILON) { slide_dist(e2, v, e2->offset_l, meetco); + d = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e1->e, v)->co); + if (fabsf(d - e1->offset_r) > BEVEL_EPSILON) + e1->offset_r = d; } else { iret = isect_line_line_v3(off1a, off1b, off2a, off2b, meetco, isect2); if (iret == 0) { /* lines colinear: another test says they are parallel. so shouldn't happen */ copy_v3_v3(meetco, off1a); + d = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e2->e, v)->co); + if (fabsf(d - e2->offset_l) > BEVEL_EPSILON) + e2->offset_l = d; } else if (iret == 2) { /* 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 so can't preserve widths; fallback on sliding along edge between */ + /* offset lines don't meet so can't preserve widths */ offset_on_edge_between(bp, e1, e2, emid, v, meetco); } } @@ -645,7 +779,7 @@ static void offset_in_plane(EdgeHalf *e, const float plane_no[3], int left, floa } else { zero_v3(no); - if (fabs(dir[0]) < fabs(dir[1])) + if (fabsf(dir[0]) < fabsf(dir[1])) no[0] = 1.0f; else no[1] = 1.0f; @@ -828,13 +962,22 @@ static void set_bound_vert_seams(BevVert *bv) /* Make a circular list of BoundVerts for bv, each of which has the coordinates * of a vertex on the the boundary of the beveled vertex bv->v. - * Also decide on the mesh pattern that will be used inside the boundary. + * This may adjust some EdgeHalf widths, and there might have to be + * a subsequent pass to make the widths as consistent as possible. + * The first time through, construct will be true and we are making the BoundVerts + * and setting up the BoundVert and EdgeHalf pointers appropriately. + * For a width consistency path, we just recalculate the coordinates of the + * BoundVerts. If the other ends have been (re)built already, then we + * copy the offsets from there to match, else we use the ideal (user-specified) + * widths. + * Also, if construct, decide on the mesh pattern that will be used inside the boundary. * Doesn't make the actual BMVerts */ -static void build_boundary(BevelParams *bp, BevVert *bv) +static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) { MemArena *mem_arena = bp->mem_arena; - EdgeHalf *efirst, *e; + EdgeHalf *efirst, *e, *eother; BoundVert *v; + BevVert *bvother; VMesh *vm; float co[3]; const float *no; @@ -842,10 +985,26 @@ static void build_boundary(BevelParams *bp, BevVert *bv) vm = bv->vmesh; - if (bp->vertex_only) + if (bp->vertex_only) { e = efirst = &bv->edges[0]; - else + } + else { e = efirst = next_bev(bv, NULL); + do { + eother = find_other_end_edge_half(bp, e, &bvother); + if (eother && bvother->visited && bp->offset_type != BEVEL_AMT_PERCENT) { + /* try to keep bevel even by matching other end offsets */ + e->offset_l = eother->offset_r; + e->offset_r = eother->offset_l; + } + else { + /* reset to user spec */ + e->offset_l = e->offset_l_spec; + e->offset_r = e->offset_r_spec; + } + } while ((e = e->next) != efirst); + e = efirst; + } BLI_assert(bv->edgecount >= 2); /* since bevel edges incident to 2 faces */ @@ -853,38 +1012,58 @@ static void build_boundary(BevelParams *bp, BevVert *bv) /* special case: beveled edge meets non-beveled one at valence 2 vert */ no = e->fprev ? e->fprev->no : (e->fnext ? e->fnext->no : NULL); offset_in_plane(e, no, TRUE, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = v->ebev = e; - e->leftv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = v->elast = v->ebev = e; + e->leftv = v; + } + else { + adjust_bound_vert(e->leftv, co); + } no = e->fnext ? e->fnext->no : (e->fprev ? e->fprev->no : NULL); offset_in_plane(e, no, FALSE, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e; - e->rightv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = v->elast = e; + e->rightv = v; + } + else { + adjust_bound_vert(e->rightv, co); + } /* make artifical extra point along unbeveled edge, and form triangle */ slide_dist(e->next, bv->v, e->offset_l, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e->next; - e->next->leftv = e->next->rightv = v; - /* could use M_POLY too, but tri-fan looks nicer)*/ - vm->mesh_kind = M_TRI_FAN; - set_bound_vert_seams(bv); + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = v->elast = e->next; + e->next->leftv = e->next->rightv = v; + /* could use M_POLY too, but tri-fan looks nicer)*/ + vm->mesh_kind = M_TRI_FAN; + set_bound_vert_seams(bv); + } + else { + adjust_bound_vert(e->next->leftv, co); + } return; } lastd = bp->vertex_only ? bv->offset : e->offset_l; - vm->boundstart = NULL; do { if (e->is_bev) { /* handle only left side of beveled edge e here: next iteration should do right side */ if (e->prev->is_bev) { BLI_assert(e->prev != e); /* see: wire edge special case */ offset_meet(e->prev, e, bv->v, e->fprev, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = e->prev; - v->elast = v->ebev = e; - e->leftv = v; - e->prev->rightv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = e->prev; + v->elast = v->ebev = e; + e->leftv = v; + e->prev->rightv = v; + } + else { + v = e->leftv; + adjust_bound_vert(v, co); + } } else { /* e->prev is not beveled */ @@ -895,21 +1074,33 @@ static void build_boundary(BevelParams *bp, BevVert *bv) 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; - e->leftv = v; - e->prev->leftv = v; - e->prev->prev->rightv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = e->prev->prev; + v->elast = v->ebev = e; + e->leftv = v; + e->prev->leftv = v; + e->prev->prev->rightv = v; + } + else { + v = e->leftv; + adjust_bound_vert(v, co); + } } else { /* neither e->prev nor e->prev->prev are beveled: make on-edge on e->prev */ offset_meet(e->prev, e, bv->v, e->fprev, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = e->prev; - v->elast = v->ebev = e; - e->leftv = v; - e->prev->leftv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = e->prev; + v->elast = v->ebev = e; + e->leftv = v; + e->prev->leftv = v; + } + else { + v = e->leftv; + adjust_bound_vert(v, co); + } } } lastd = len_v3v3(bv->v->co, v->nv.co); @@ -923,11 +1114,16 @@ static void build_boundary(BevelParams *bp, BevVert *bv) else if (e->prev->is_bev) { /* on-edge meet between e->prev and e */ offset_meet(e->prev, e, bv->v, e->fprev, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = e->prev; - v->elast = e; - e->leftv = v; - e->prev->rightv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = e->prev; + v->elast = e; + e->leftv = v; + e->prev->rightv = v; + } + else { + adjust_bound_vert(e->leftv, co); + } } else { /* None of e->prev, e, e->next are beveled. @@ -936,41 +1132,124 @@ static void build_boundary(BevelParams *bp, BevVert *bv) * Could slide to make an even bevel plane but for now will * just use last distance a meet point moved from bv->v. */ slide_dist(e, bv->v, lastd, co); - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e; - e->leftv = v; + if (construct) { + v = add_new_bound_vert(mem_arena, vm, co); + v->efirst = v->elast = e; + e->leftv = v; + } + else { + adjust_bound_vert(e->leftv, co); + } } } } while ((e = e->next) != efirst); - set_bound_vert_seams(bv); + if (construct) { + set_bound_vert_seams(bv); - BLI_assert(vm->count >= 2); - if (bp->vertex_only) { - if (vm->count == 2) + BLI_assert(vm->count >= 2); + if (bp->vertex_only) { + if (vm->count == 2) + vm->mesh_kind = M_NONE; + else if (bp->seg > 1) + vm->mesh_kind = M_ADJ_SUBDIV; + else + vm->mesh_kind = M_POLY; + } + else if (vm->count == 2 && bv->edgecount == 3) { vm->mesh_kind = M_NONE; - else if (bp->seg > 1) - vm->mesh_kind = M_ADJ_SUBDIV; - else - vm->mesh_kind = M_POLY; - } - else if (vm->count == 2 && bv->edgecount == 3) { - vm->mesh_kind = M_NONE; - } - else if (bv->selcount == 2) { - vm->mesh_kind = M_QUAD_STRIP; - } - else if (efirst->seg == 1 || bv->selcount == 1) { - if (vm->count == 3 && bv->selcount == 1) { - vm->mesh_kind = M_TRI_FAN; + } + else if (bv->selcount == 2) { + vm->mesh_kind = M_QUAD_STRIP; + } + else if (efirst->seg == 1 || bv->selcount == 1) { + if (vm->count == 3 && bv->selcount == 1) { + vm->mesh_kind = M_TRI_FAN; + } + else { + vm->mesh_kind = M_POLY; + } } else { - vm->mesh_kind = M_POLY; + vm->mesh_kind = M_ADJ; } } - else { - vm->mesh_kind = M_ADJ; +} + +/* Do a global pass to try to make offsets as even as possible. + * Consider this graph: + * nodes = BevVerts + * edges = { (u,v) } where u and v are nodes such that u and v + * are connected by a mesh edge that has at least one end + * whose offset does not match the user spec. + * + * Do a breadth-first search on this graph, starting from nodes + * that have any_adjust=true, and changing all + * not-already-changed offsets on EdgeHalfs to match the + * corresponding ones that changed on the other end. + * The graph is dynamic in the sense that having an offset that + * doesn't meet the user spec can be added as the search proceeds. + * We want this search to be deterministic (not dependendent + * on order of processing through hash table), so as to avoid + * flicker to to different decisions made if search is different + * while dragging the offset number in the UI. So look for the + * lower vertex number when there is a choice of where to start. + * + * Note that this might not process all BevVerts, only the ones + * that need adjustment. + */ +static void adjust_offsets(BevelParams *bp) +{ + BevVert *bv, *searchbv, *bvother; + int i, searchi; + GHashIterator giter; + EdgeHalf *e, *efirst, *eother; + GSQueue *q; + + BLI_assert(!bp->vertex_only); + GHASH_ITER(giter, bp->vert_hash) { + bv = BLI_ghashIterator_getValue(&giter); + bv->visited = false; + } + + q = BLI_gsqueue_new((int)sizeof(BevVert *)); + /* the following loop terminates because at least one node is visited each time */ + for (;;) { + /* look for root of a connected component in search graph */ + searchbv = NULL; + searchi = -1; + GHASH_ITER(giter, bp->vert_hash) { + bv = BLI_ghashIterator_getValue(&giter); + if (!bv->visited && any_edge_half_offset_changed(bv)) { + i = BM_elem_index_get(bv->v); + if (!searchbv || i < searchi) { + searchbv = bv; + searchi = i; + } + } + } + if (searchbv == NULL) + break; + + BLI_gsqueue_push(q, &searchbv); + while (!BLI_gsqueue_is_empty(q)) { + BLI_gsqueue_pop(q, &bv); + /* If do this check, don't have to check for already-on-queue before push, below */ + if (bv->visited) + continue; + bv->visited = true; + build_boundary(bp, bv, false); + + e = efirst = &bv->edges[0]; + do { + eother = find_other_end_edge_half(bp, e, &bvother); + if (eother && !bvother->visited && edge_half_offset_changed(e)) { + BLI_gsqueue_push(q, &bvother); + } + } while ((e = e->next) != efirst); + } } + BLI_gsqueue_free(q); } /* @@ -1989,14 +2268,15 @@ static float edge_face_angle(EdgeHalf *e) /* * Construction around the vertex */ -static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) +static BevVert * bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) { BMEdge *bme; BevVert *bv; BMEdge *bme2, *unflagged_bme, *first_bme; BMFace *f; + BMVert *v1, *v2; BMIter iter, iter2; - EdgeHalf *e, *eother; + EdgeHalf *e; float weight, z; int i, found_shared_face, ccw_test_sum; int nsel = 0; @@ -2034,7 +2314,7 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) if ((nsel == 0 && !bp->vertex_only) || (ntot < 2 && bp->vertex_only)) { /* signal this vert isn't being beveled */ BM_elem_flag_disable(v, BM_ELEM_TAG); - return; + return NULL; } /* avoid calling BM_vert_edge_count since we loop over edges already */ @@ -2049,7 +2329,6 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) bv->edges = (EdgeHalf *)BLI_memarena_alloc(bp->mem_arena, ntot * sizeof(EdgeHalf)); bv->vmesh = (VMesh *)BLI_memarena_alloc(bp->mem_arena, sizeof(VMesh)); bv->vmesh->seg = bp->seg; - BLI_ghash_insert(bp->vert_hash, v, bv); if (bp->vertex_only) { /* if weighted, modify offset by weight */ @@ -2057,11 +2336,12 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) weight = defvert_find_weight(bp->dvert + BM_elem_index_get(v), bp->vertex_group); if (weight <= 0.0f) { BM_elem_flag_disable(v, BM_ELEM_TAG); - return; + return NULL; } bv->offset *= weight; } } + BLI_ghash_insert(bp->vert_hash, v, bv); /* add edges to bv->edges in order that keeps adjacent edges sharing * a face, if possible */ @@ -2153,57 +2433,55 @@ 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. - * - * 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; + * Except for percent method, offset will be same on each side. */ + + switch (bp->offset_type) { + case BEVEL_AMT_OFFSET: + e->offset_l_spec = bp->offset; + break; + case BEVEL_AMT_WIDTH: + z = fabsf(2.0f * sinf(edge_face_angle(e) / 2.0f)); + if (z < BEVEL_EPSILON) + e->offset_l_spec = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */ + else + e->offset_l_spec = bp->offset / z; + break; + case BEVEL_AMT_DEPTH: + z = fabsf(cosf(edge_face_angle(e) / 2.0f)); + if (z < BEVEL_EPSILON) + e->offset_l_spec = 0.01f * bp->offset; /* undefined behavior, so tiny bevel */ + else + e->offset_l_spec = bp->offset / z; + break; + case BEVEL_AMT_PERCENT: + /* offset needs to be such that it meets adjacent edges at percentage of their lengths */ + v1 = BM_edge_other_vert(e->prev->e, v); + v2 = BM_edge_other_vert(e->e, v); + z = sinf(angle_v3v3v3(v1->co, v->co, v2->co)); + e->offset_l_spec = BM_edge_calc_length(e->prev->e) * bp->offset * z / 100.0f; + v1 = BM_edge_other_vert(e->e, v); + v2 = BM_edge_other_vert(e->next->e, v); + z = sinf(angle_v3v3v3(v1->co, v->co, v2->co)); + e->offset_r_spec = BM_edge_calc_length(e->next->e) * bp->offset * z / 100.0f; + break; + default: + BLI_assert(!"bad bevel offset kind"); + e->offset_l_spec = bp->offset; + break; } - else { - switch (bp->offset_type) { - case BEVEL_AMT_OFFSET: - e->offset_l = bp->offset; - break; - case BEVEL_AMT_WIDTH: - z = fabsf(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 = fabsf(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; - break; - } - 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, e->e, CD_BWEIGHT); - e->offset_l *= weight; - e->offset_r *= weight; - } + if (bp->offset_type != BEVEL_AMT_PERCENT) + e->offset_r_spec = e->offset_l_spec; + if (bp->use_weights) { + weight = BM_elem_float_data_get(&bm->edata, e->e, CD_BWEIGHT); + e->offset_l_spec *= weight; + e->offset_r_spec *= weight; } } else { - e->offset_l = e->offset_r = 0.0f; + e->offset_l_spec = e->offset_r_spec = 0.0f; } + e->offset_l = e->offset_l_spec; + e->offset_r = e->offset_r_spec; BM_BEVEL_EDGE_TAG_DISABLE(e->e); if (e->fprev && e->fnext) @@ -2212,8 +2490,7 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) e->is_seam = true; } - build_boundary(bp, bv); - build_vmesh(bp, bm, bv); + return bv; } /* Face f has at least one beveled vertex. Rebuild f */ @@ -2484,6 +2761,7 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, const f BMIter iter; BMVert *v, *v_next; BMEdge *e; + BevVert *bv; BevelParams bp = {NULL}; bp.offset = offset; @@ -2492,6 +2770,7 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, const f bp.vertex_only = vertex_only; bp.use_weights = use_weights; bp.preserve_widths = false; + bp.limit_offset = limit_offset; bp.dvert = dvert; bp.vertex_group = vertex_group; @@ -2504,10 +2783,26 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, const f if (limit_offset) bp.offset = bevel_limit_offset(bm, &bp); - /* Analyze input vertices and build vertex meshes */ + /* Analyze input vertices, sorting edges and assigning initial new vertex positions */ + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + bv = bevel_vert_construct(bm, &bp, v); + if (bv) + build_boundary(&bp, bv, true); + } + } + + /* Perhaps do a pass to try to even out widths */ + if (!bp.vertex_only) { + adjust_offsets(&bp); + } + + /* Build the meshes around vertices, now that positions are final */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(v, BM_ELEM_TAG)) { - bevel_vert_construct(bm, &bp, v); + bv = find_bevvert(&bp, v); + if (bv) + build_vmesh(&bp, bm, bv); } } -- cgit v1.2.3