diff options
Diffstat (limited to 'source/blender/bmesh/tools/bmesh_bevel.c')
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.c | 313 |
1 files changed, 263 insertions, 50 deletions
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index 74841dc2756..51a0fa4b2cc 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -205,13 +205,36 @@ static int bev_debug_flags = 0; #define DEBUG_OLD_PROJ_TO_PERP_PLANE (bev_debug_flags & 2) #define DEBUG_OLD_FLAT_MID (bev_debug_flags & 4) +/* this flag values will get set on geom we want to return in 'out' slots for edges and verts */ +#define EDGE_OUT 4 +#define VERT_OUT 8 + +/* If we're called from the modifier, tool flags aren't available, but don't need output geometry */ +static void flag_out_edge(BMesh *bm, BMEdge *bme) +{ + if (bm->use_toolflags) + BMO_edge_flag_enable(bm, bme, EDGE_OUT); +} + +static void flag_out_vert(BMesh *bm, BMVert *bmv) +{ + if (bm->use_toolflags) + BMO_vert_flag_enable(bm, bmv, VERT_OUT); +} + +static void disable_flag_out_edge(BMesh *bm, BMEdge *bme) +{ + if (bm->use_toolflags) + BMO_edge_flag_disable(bm, bme, EDGE_OUT); +} + /* Are d1 and d2 parallel or nearly so? */ static bool nearly_parallel(const float d1[3], const float d2[3]) { float ang; ang = angle_v3v3(d1, d2); - return (fabsf(ang) < BEVEL_EPSILON_ANG) || (fabsf(ang - M_PI) < BEVEL_EPSILON_ANG); + return (fabsf(ang) < BEVEL_EPSILON_ANG) || (fabsf(ang - (float)M_PI) < BEVEL_EPSILON_ANG); } /* Make a new BoundVert of the given kind, insert it at the end of the circular linked @@ -262,6 +285,7 @@ static void create_mesh_bmvert(BMesh *bm, VMesh *vm, int i, int j, int k, BMVert NewVert *nv = mesh_vert(vm, i, j, k); nv->v = BM_vert_create(bm, nv->co, eg, BM_CREATE_NOP); BM_elem_flag_disable(nv->v, BM_ELEM_TAG); + flag_out_vert(bm, nv->v); } static void copy_mesh_vert( @@ -504,9 +528,12 @@ static BMFace *bev_create_ngon( } /* not essential for bevels own internal logic, - * this is done so the operator can select newly created faces */ + * this is done so the operator can select newly created geometry */ if (f) { BM_elem_flag_enable(f, BM_ELEM_TAG); + BM_ITER_ELEM(bme, &iter, f, BM_EDGES_OF_FACE) { + flag_out_edge(bm, bme); + } } if (mat_nr >= 0) @@ -3213,6 +3240,7 @@ static void bevel_build_trifan(BevelParams *bp, BMesh *bm, BevVert *bv) BMFace *f_new; BLI_assert(v_fan == l_fan->v); f_new = BM_face_split(bm, f, l_fan, l_fan->next->next, &l_new, NULL, false); + flag_out_edge(bm, l_new->e); if (f_new->len > f->len) { f = f_new; @@ -3259,6 +3287,7 @@ static void bevel_build_quadstrip(BevelParams *bp, BMesh *bm, BevVert *bv) else { BM_face_split(bm, f, l_a, l_b, &l_new, NULL, false); f = l_new->f; + flag_out_edge(bm, l_new->e); /* walk around the new face to get the next verts to split */ l_a = l_new->prev; @@ -3278,7 +3307,7 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) { VMesh *vm = bv->vmesh; BMVert *v1, *v2; - BMEdge *e_eg; + BMEdge *e_eg, *bme; Profile *pro; float co[3]; BoundVert *bndv; @@ -3320,7 +3349,9 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) v1 = mesh_vert(vm, 0, 0, k)->v; v2 = mesh_vert(vm, 0, 0, k + 1)->v; BLI_assert(v1 != NULL && v2 != NULL); - BM_edge_create(bm, v1, v2, e_eg, BM_CREATE_NO_DOUBLE); + bme = BM_edge_create(bm, v1, v2, e_eg, BM_CREATE_NO_DOUBLE); + if (bme) + flag_out_edge(bm, bme); } } } @@ -3901,7 +3932,7 @@ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) /* Face f has at least one beveled vertex. Rebuild f */ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) { - BMIter liter; + BMIter liter, eiter, fiter; BMLoop *l, *lprev; BevVert *bv; BoundVert *v, *vstart, *vend; @@ -3909,10 +3940,10 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) VMesh *vm; int i, k, n; bool do_rebuild = false; - bool go_ccw, corner3special; + bool go_ccw, corner3special, keep; BMVert *bmv; BMEdge *bme, *bme_new, *bme_prev; - BMFace *f_new; + BMFace *f_new, *f_other; BMVert **vv = NULL; BMVert **vv_fix = NULL; BMEdge **ee = NULL; @@ -4050,9 +4081,21 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) } } - /* don't select newly created boundary faces... */ + /* don't select newly or return created boundary faces... */ if (f_new) { BM_elem_flag_disable(f_new, BM_ELEM_TAG); + /* Also don't want new edges that aren't part of a new bevel face */ + BM_ITER_ELEM(bme, &eiter, f_new, BM_EDGES_OF_FACE) { + keep = false; + BM_ITER_ELEM(f_other, &fiter, bme, BM_FACES_OF_EDGE) { + if (BM_elem_flag_test(f_other, BM_ELEM_TAG)) { + keep = true; + break; + } + } + if (!keep) + disable_flag_out_edge(bm, bme); + } } } @@ -4134,8 +4177,9 @@ static void bevel_reattach_wires(BMesh *bm, BevelParams *bp, BMVert *v) } } } while ((bndv = bndv->next) != bv->vmesh->boundstart); - if (vclosest) + if (vclosest) { BM_edge_create(bm, vclosest, votherclosest, e, BM_CREATE_NO_DOUBLE); + } } } @@ -4487,61 +4531,206 @@ static void set_profile_spacing(BevelParams *bp) } /* - * Calculate and return an offset that is the lesser of the current + * Assume we have a situation like: + * + * a d + * \ / + * A \ / C + * \ th1 th2/ + * b---------c + * B + * + * where edges are A, B, and C, + * following a face around vertices a, b, c, d; + * th1 is angle abc and th2 is angle bcd; + * and the argument EdgeHalf eb is B, going from b to c. + * In general case, edge offset specs for A, B, C have + * the form ka*t, kb*t, kc*t where ka, kb, kc are some factors + * (may be 0) and t is the current bp->offset. + * We want to calculate t at which the clone of B parallel + * to it collapses. This can be calculated using trig. + * Another case of geometry collision that can happen is + * When B slides along A because A is unbeveled. + * Then it might collide with a. Similarly for B sliding along C. + */ +static float geometry_collide_offset(BevelParams *bp, EdgeHalf *eb) +{ + EdgeHalf *ea, *ec, *ebother; + BevVert *bvc; + BMLoop *lb; + BMVert *va, *vb, *vc, *vd; + float ka, kb, kc, g, h, t, den, no_collide_offset, th1, th2, sin1, sin2, tan1, tan2, limit; + + limit = no_collide_offset = bp->offset + 1e6; + if (bp->offset == 0.0f) + return no_collide_offset; + kb = eb->offset_l_spec; + ea = eb->next; /* note: this is in direction b --> a */ + ka = ea->offset_r_spec; + if (eb->is_rev) { + vc = eb->e->v1; + vb = eb->e->v2; + } + else { + vb = eb->e->v1; + vc = eb->e->v2; + } + va = ea->is_rev ? ea->e->v1 : ea->e->v2; + bvc = NULL; + ebother = find_other_end_edge_half(bp, eb, &bvc); + if (ebother != NULL) { + ec = ebother->prev; /* note: this is in direction c --> d*/ + vc = bvc->v; + kc = ec->offset_l_spec; + vd = ec->is_rev ? ec->e->v1 : ec->e->v2; + } + else { + /* No bevvert for w, so C can't be beveled */ + kc = 0.0f; + ec = NULL; + /* Find an edge from c that has same face */ + lb = BM_face_edge_share_loop(eb->fnext, eb->e); + if (!lb) { + return no_collide_offset; + } + if (lb->next->v == vc) + vd = lb->next->next->v; + else if (lb->v == vc) + vd = lb->prev->v; + else { + return no_collide_offset; + } + } + if (ea->e == eb->e || (ec && ec->e == eb->e)) + return no_collide_offset; + ka = ka / bp->offset; + kb = kb / bp->offset; + kc = kc / bp->offset; + th1 = angle_v3v3v3(va->co, vb->co, vc->co); + th2 = angle_v3v3v3(vb->co, vc->co, vd->co); + + /* First calculate offset at which edge B collapses, which happens + * when advancing clones of A, B, C all meet at a point. + * This only happens if at least two of those three edges have non-zero k's */ + sin1 = sinf(th1); + sin2 = sinf(th2); + if ((ka > 0.0f) + (kb > 0.0f) + (kc > 0.0f) >= 2) { + tan1 = tanf(th1); + tan2 = tanf(th2); + g = tan1 * tan2; + h = sin1 * sin2; + den = g * (ka * sin2 + kc * sin1) + kb * h * (tan1 + tan2); + if (den != 0.0f) { + t = BM_edge_calc_length(eb->e); + t *= g * h / den; + if (t >= 0.0f) + limit = t; + } + } + + /* Now check edge slide cases */ + if (kb > 0.0f && ka == 0.0f /*&& bvb->selcount == 1 && bvb->edgecount > 2*/) { + t = BM_edge_calc_length(ea->e); + t *= sin1 / kb; + if (t >= 0.0f && t < limit) + limit = t; + } + if (kb > 0.0f && kc == 0.0f /* && bvc && ec && bvc->selcount == 1 && bvc->edgecount > 2 */) { + t = BM_edge_calc_length(ec->e); + t *= sin2 / kb; + if (t >= 0.0f && t < limit) + limit = t; + } + return limit; +} + +/* + * We have an edge A between vertices a and b, + * where EdgeHalf ea is the half of A that starts at a. + * For vertex-only bevels, the new vertices slide from a at a rate ka*t + * and from b at a rate kb*t. + * We want to calculate the t at which the two meet. + */ +static float vertex_collide_offset(BevelParams *bp, EdgeHalf *ea) +{ + float limit, ka, kb, no_collide_offset, la, kab; + EdgeHalf *eb; + + limit = no_collide_offset = bp->offset + 1e6; + if (bp->offset == 0.0f) + return no_collide_offset; + ka = ea->offset_l_spec / bp->offset; + eb = find_other_end_edge_half(bp, ea, NULL); + kb = eb ? eb->offset_l_spec / bp->offset : 0.0f; + kab = ka + kb; + la = BM_edge_calc_length(ea->e); + if (kab <= 0.0f) + return no_collide_offset; + limit = la / kab; + return limit; +} + +/* + * Calculate an offset that is the lesser of the current * bp.offset and the maximum possible offset before geometry * collisions happen. - * Currently this is a quick and dirty estimate of the max - * possible: half the minimum edge length of any vertex involved - * in a bevel. This is usually conservative. - * The correct calculation is quite complicated. - * TODO: implement this correctly. + * If the offset changes as a result of this, adjust the + * current edge offset specs to reflect this clamping, + * and store the new offset in bp.offset. */ -static float bevel_limit_offset(BMesh *bm, BevelParams *bp) +static void bevel_limit_offset(BevelParams *bp) { - BMVert *v; - BMEdge *e; - BMIter v_iter, e_iter; - float limited_offset, half_elen; - bool vbeveled; + BevVert *bv; + EdgeHalf *eh; + GHashIterator giter; + float limited_offset, offset_factor, collision_offset; + int i; limited_offset = bp->offset; - if (bp->offset_type == BEVEL_AMT_PERCENT) { - if (limited_offset > 50.0f) - limited_offset = 50.0f; - return limited_offset; - } - BM_ITER_MESH (v, &v_iter, bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + GHASH_ITER(giter, bp->vert_hash) { + bv = BLI_ghashIterator_getValue(&giter); + for (i = 0; i < bv->edgecount; i++) { + eh = &bv->edges[i]; if (bp->vertex_only) { - vbeveled = true; + collision_offset = vertex_collide_offset(bp, eh); + if (collision_offset < limited_offset) + limited_offset = collision_offset; } else { - vbeveled = false; - BM_ITER_ELEM (e, &e_iter, v, BM_EDGES_OF_VERT) { - if (BM_elem_flag_test(BM_edge_other_vert(e, v), BM_ELEM_TAG)) { - vbeveled = true; - break; - } - } + collision_offset = geometry_collide_offset(bp, eh); + if (collision_offset < limited_offset) + limited_offset = collision_offset; } - if (vbeveled) { - BM_ITER_ELEM (e, &e_iter, v, BM_EDGES_OF_VERT) { - half_elen = 0.5f * BM_edge_calc_length(e); - if (half_elen < limited_offset) - limited_offset = half_elen; - } + } + } + + if (limited_offset < bp->offset) { + /* All current offset specs have some number times bp->offset, + * so we can just multiply them all by the reduction factor + * of the offset to have the effect of recalculating the specs + * with the new limited_offset. + */ + offset_factor = limited_offset / bp->offset; + GHASH_ITER(giter, bp->vert_hash) { + bv = BLI_ghashIterator_getValue(&giter); + for (i = 0; i < bv->edgecount; i++) { + eh = &bv->edges[i]; + eh->offset_l_spec *= offset_factor; + eh->offset_r_spec *= offset_factor; + eh->offset_l *= offset_factor; + eh->offset_r *= offset_factor; } } + bp->offset = limited_offset; } - return limited_offset; } /** * - Currently only bevels BM_ELEM_TAG'd verts and edges. * - * - Newly created faces are BM_ELEM_TAG'd too, - * the caller needs to ensure this is cleared before calling - * if its going to use this face tag. + * - Newly created faces, edges, and verts are BM_ELEM_TAG'd too, + * the caller needs to ensure these are cleared before calling + * if its going to use this tag. * * - If limit_offset is set, adjusts offset down if necessary * to avoid geometry collisions. @@ -4560,6 +4749,7 @@ void BM_mesh_bevel( BMEdge *e; BevVert *bv; BevelParams bp = {NULL}; + GHashIterator giter; bp.offset = offset; bp.offset_type = offset_type; @@ -4583,24 +4773,33 @@ void BM_mesh_bevel( BLI_memarena_use_calloc(bp.mem_arena); set_profile_spacing(&bp); - if (limit_offset) - bp.offset = bevel_limit_offset(bm, &bp); - /* 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) + if (!limit_offset && bv) build_boundary(&bp, bv, true); } } + /* Perhaps clamp offset to avoid geometry colliisions */ + if (limit_offset) { + bevel_limit_offset(&bp); + + /* Assign initial new vertex positions */ + GHASH_ITER(giter, bp.vert_hash) { + bv = BLI_ghashIterator_getValue(&giter); + 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 */ + /* Note: could use GHASH_ITER over bp.vert_hash when backward compatibility no longer matters */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(v, BM_ELEM_TAG)) { bv = find_bevvert(&bp, v); @@ -4633,6 +4832,20 @@ void BM_mesh_bevel( } } + /* When called from operator (as opposed to modifier), bm->use_toolflags + * will be set, and we to transfer the oflags to BM_ELEM_TAGs */ + if (bm->use_toolflags) { + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BMO_vert_flag_test(bm, v, VERT_OUT)) + BM_elem_flag_enable(v, BM_ELEM_TAG); + } + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BMO_edge_flag_test(bm, e, EDGE_OUT)) { + BM_elem_flag_enable(e, BM_ELEM_TAG); + } + } + } + /* primary free */ BLI_ghash_free(bp.vert_hash, NULL, NULL); BLI_memarena_free(bp.mem_arena); |