diff options
Diffstat (limited to 'source/blender/bmesh/tools/bmesh_bevel.c')
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.c | 223 |
1 files changed, 166 insertions, 57 deletions
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index bb3fe129e0b..5e68e468fb8 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -73,7 +73,8 @@ typedef struct EdgeHalf { struct BoundVert *leftv; /* left boundary vert (looking along edge to end) */ struct BoundVert *rightv; /* right boundary vert, if beveled */ int seg; /* how many segments for the bevel */ - float offset; /* offset for this edge */ + float offset_l; /* offset for this edge, on left side */ + float offset_r; /* offset for this edge, on right side */ 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)? */ @@ -128,9 +129,11 @@ typedef struct BevelParams { MemArena *mem_arena; /* use for all allocs while bevel runs, if we need to free we can switch to mempool */ float offset; /* blender units to offset each side of a beveled edge */ + int offset_type; /* how offset is measured; enum defined in bmesh_operators.h */ 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; @@ -206,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) @@ -223,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) @@ -449,14 +468,12 @@ static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) * Calculate the meeting point between the offset edges for e1 and e2, putting answer in meetco. * e1 and e2 share vertex v and face f (may be NULL) and viewed from the normal side of * the bevel vertex, e1 precedes e2 in CCW order. - * If on_right is true, offset edge is on right of both edges, where e1 enters v and - * e2 leave it. If on_right is false, then the offset edge is on the left. + * Offset edge is on right of both edges, where e1 enters v and e2 leave it. * 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. */ -static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, - int on_right, float meetco[3]) +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; @@ -469,7 +486,7 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, if (ang < 100.0f * BEVEL_EPSILON) { /* special case: e1 and e2 are parallel; put offset point perp to both, from v. * need to find a suitable plane. - * if offsets are different, we're out of luck: just use e1->offset */ + * if offsets are different, we're out of luck: just use e1->offset_r */ if (f) copy_v3_v3(norm_v, f->no); else @@ -477,26 +494,25 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, cross_v3_v3v3(norm_perp1, dir1, norm_v); normalize_v3(norm_perp1); copy_v3_v3(off1a, v->co); - madd_v3_v3fl(off1a, norm_perp1, e1->offset); + madd_v3_v3fl(off1a, norm_perp1, 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, meetco); + slide_dist(e2, v, e2->offset_l, meetco); } else { - /* get normal to plane where meet point should be */ + /* Get normal to plane where meet point should be, + * using cross product instead of f->no in case f is non-planar. + * If e1-v-e2 is a reflex angle (viewed from vertex normal side), need to flip*/ cross_v3_v3v3(norm_v, dir2, dir1); normalize_v3(norm_v); - if (!on_right) + if (dot_v3v3(norm_v, v->no) < 0.0f) negate_v3(norm_v); /* get vectors perp to each edge, perp to norm_v, and pointing into face */ - if (f) { - copy_v3_v3(norm_v, f->no); - } cross_v3_v3v3(norm_perp1, dir1, norm_v); cross_v3_v3v3(norm_perp2, dir2, norm_v); normalize_v3(norm_perp1); @@ -504,10 +520,10 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, /* get points that are offset distances from each line, then another point on each line */ copy_v3_v3(off1a, v->co); - madd_v3_v3fl(off1a, norm_perp1, e1->offset); + madd_v3_v3fl(off1a, norm_perp1, e1->offset_r); add_v3_v3v3(off1b, off1a, dir1); copy_v3_v3(off2a, v->co); - madd_v3_v3fl(off2a, norm_perp2, e2->offset); + madd_v3_v3fl(off2a, norm_perp2, e2->offset_l); add_v3_v3v3(off2b, off2a, dir2); /* intersect the lines; by construction they should be on the same plane and not parallel */ @@ -520,19 +536,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, 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, 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; @@ -545,6 +582,14 @@ static void offset_in_two_planes(EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid, /* calculate face normals at corner in case faces are nonplanar */ cross_v3_v3v3(f1no, dirmid, dir1); cross_v3_v3v3(f2no, dirmid, dir2); + + /* if e1-v-emid or emid-v-e2 are reflex angles, need to flip corner normals */ + if (dot_v3v3(f1no, v->no) < 0.0f) + negate_v3(f1no); + if (dot_v3v3(f2no, v->no) < 0.0f) + negate_v3(f2no); + + /* get vectors perpendicular to e1 and e2, pointing into the proper faces */ cross_v3_v3v3(norm_perp1, dir1, f1no); normalize_v3(norm_perp1); cross_v3_v3v3(norm_perp2, dir2, f2no); @@ -552,19 +597,19 @@ static void offset_in_two_planes(EdgeHalf *e1, EdgeHalf *e2, EdgeHalf *emid, /* get points that are offset distances from each line, then another point on each line */ copy_v3_v3(off1a, v->co); - madd_v3_v3fl(off1a, norm_perp1, e1->offset); + madd_v3_v3fl(off1a, norm_perp1, e1->offset_r); sub_v3_v3v3(off1b, off1a, dir1); copy_v3_v3(off2a, v->co); - madd_v3_v3fl(off2a, norm_perp2, e2->offset); + madd_v3_v3fl(off2a, norm_perp2, e2->offset_l); add_v3_v3v3(off2b, off2a, dir2); 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, meetco); + slide_dist(e2, v, e2->offset_l, meetco); } else { iret = isect_line_line_v3(off1a, off1b, off2a, off2b, meetco, isect2); @@ -573,11 +618,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 */ @@ -612,7 +656,7 @@ static void offset_in_plane(EdgeHalf *e, const float plane_no[3], int left, floa cross_v3_v3v3(fdir, no, dir); normalize_v3(fdir); copy_v3_v3(r, v->co); - madd_v3_v3fl(r, fdir, e->offset); + madd_v3_v3fl(r, fdir, left? e->offset_l : e->offset_r); } /* Calculate the point on e where line (co_a, co_b) comes closest to and return it in projco */ @@ -818,7 +862,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv) v->efirst = v->elast = e; e->rightv = v; /* make artifical extra point along unbeveled edge, and form triangle */ - slide_dist(e->next, bv->v, e->offset, co); + 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; @@ -828,14 +872,14 @@ static void build_boundary(BevelParams *bp, BevVert *bv) return; } - lastd = bp->vertex_only ? bv->offset : e->offset; + 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, TRUE, co); + 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; @@ -847,7 +891,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; @@ -857,7 +904,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv) } 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, TRUE, co); + 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; @@ -875,7 +922,7 @@ 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, TRUE, co); + 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; @@ -1921,6 +1968,19 @@ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv) } } +/* Return the angle between the two faces adjacent to e. + * If there are not two, return 0. */ +static float edge_face_angle(EdgeHalf *e) +{ + if (e->fprev && e->fnext) { + /* angle between faces is supplement of angle between face normals */ + return (float)M_PI - angle_normalized_v3v3(e->fprev->no, e->fnext->no); + } + else { + return 0.0f; + } +} + /* take care, this flag isn't cleared before use, it just so happens that its not set */ #define BM_BEVEL_EDGE_TAG_ENABLE(bme) BM_ELEM_API_FLAG_ENABLE( (bme), _FLAG_OVERLAP) #define BM_BEVEL_EDGE_TAG_DISABLE(bme) BM_ELEM_API_FLAG_DISABLE( (bme), _FLAG_OVERLAP) @@ -1936,8 +1996,8 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) BMEdge *bme2, *unflagged_bme, *first_bme; BMFace *f; BMIter iter, iter2; - EdgeHalf *e; - float weight; + EdgeHalf *e, *eother; + float weight, z; int i, found_shared_face, ccw_test_sum; int nsel = 0; int ntot = 0; @@ -2053,16 +2113,6 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) e->seg = 0; } e->is_rev = (bme->v2 == v); - if (e->is_bev) { - e->offset = bp->offset; - if (bp->use_weights) { - weight = BM_elem_float_data_get(&bm->edata, bme, CD_BWEIGHT); - e->offset *= weight; - } - } - else { - e->offset = 0.0f; - } } /* find wrap-around shared face */ BM_ITER_ELEM (f, &iter2, bme, BM_FACES_OF_EDGE) { @@ -2098,6 +2148,63 @@ static void bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) for (i = 0, e = bv->edges; i < ntot; i++, e++) { e->next = &bv->edges[(i + 1) % ntot]; e->prev = &bv->edges[(i + ntot - 1) % ntot]; + + /* set offsets */ + 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; + } + 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; + } + } + } + else { + e->offset_l = e->offset_r = 0.0f; + } + BM_BEVEL_EDGE_TAG_DISABLE(e->e); if (e->fprev && e->fnext) e->is_seam = !contig_ldata_across_edge(bm, e->e, e->fprev, e->fnext); @@ -2370,7 +2477,7 @@ static float bevel_limit_offset(BMesh *bm, BevelParams *bp) * * \warning all tagged edges _must_ be manifold. */ -void BM_mesh_bevel(BMesh *bm, const float offset, const float segments, +void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, const float segments, const bool vertex_only, const bool use_weights, const bool limit_offset, const struct MDeformVert *dvert, const int vertex_group) { @@ -2380,9 +2487,11 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const float segments, BevelParams bp = {NULL}; bp.offset = offset; + bp.offset_type = offset_type; 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; |