Fix T44742. Bevel now avoids vertex meshes when only two edges are beveled.

Also, changed the algorithm for generating the vertex meshes when not all
edges into a vertex are beveled. Now it tries to slide along edges that
form part of the silhouette when possible; when not possible, it tries
to snap to the best plane in between the beveled edges.
This commit is contained in:
Howard Trickey 2015-06-02 09:25:05 -04:00
parent 9454377c71
commit fd1ea5e3db
Notes: blender-bot 2023-02-14 10:37:49 +01:00
Referenced by issue #44742, Bevel should not create vertex meshes when only two edges at a vertex are beveled.
1 changed files with 421 additions and 20 deletions

View File

@ -57,6 +57,9 @@
/* happens far too often, uncomment for development */
// #define BEVEL_ASSERT_PROJECT
/* will likely remove the code enabled by this soon, when sure that it is not needed */
// #define PRE_275_ALGORITHM
/* for testing */
// #pragma GCC diagnostic error "-Wpadded"
@ -187,7 +190,7 @@ typedef struct BevelParams {
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 */
int mat_nr; /* if >= 0, material number for bevel; else material comes from adjacent faces */
int mat_nr; /* if >= 0, material number for bevel; else material comes from adjacent faces */
} BevelParams;
// #pragma GCC diagnostic ignored "-Wpadded"
@ -484,8 +487,7 @@ static bool contig_ldata_across_edge(BMesh *bm, BMEdge *e, BMFace *f1, BMFace *f
}
/**
* Like #bev_create_quad_tri, but when verts straddle an old edge.
*
* Like bev_create_quad_tri, but when verts straddle an old edge.
* <pre>
* e
* |
@ -593,10 +595,42 @@ static bool is_outside_edge(EdgeHalf *e, const float co[3], BMVert **ret_closer_
}
}
/* co should be approximately on the plane between e1 and e2, which share common vert v
* and common face f (which cannot be NULL).
* Is it between those edges, sweeping CCW? */
static bool point_between_edges(float co[3], BMVert *v, BMFace *f, EdgeHalf *e1, EdgeHalf *e2)
{
BMVert *v1, *v2;
float dir1[3], dir2[3], dirco[3], no[3];
float ang11, ang1co;
v1 = BM_edge_other_vert(e1->e, v);
v2 = BM_edge_other_vert(e2->e, v);
sub_v3_v3v3(dir1, v->co, v1->co);
sub_v3_v3v3(dir2, v->co, v2->co);
sub_v3_v3v3(dirco, v->co, co);
normalize_v3(dir1);
normalize_v3(dir2);
normalize_v3(dirco);
ang11 = angle_normalized_v3v3(dir1, dir2);
ang1co = angle_normalized_v3v3(dir1, dirco);
/* angles are in [0,pi]. need to compare cross product with normal to see if they are reflex */
cross_v3_v3v3(no, dir1, dir2);
if (dot_v3v3(no, f->no) < 0.0f)
ang11 = (float)(M_PI * 2.0) - ang11;
cross_v3_v3v3(no, dir1, dirco);
if (dot_v3v3(no, f->no) < 0.0f)
ang1co = (float)(M_PI * 2.0) - ang1co;
return (ang11 - ang1co > -BEVEL_EPSILON_BIG);
}
/*
* 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.
* Except: if edges_between is true, there are edges between e1 and e2 in CCW order so they
* don't share a common face. We want the meeting point to be on an existing face so it
* should be dropped onto one of the intermediate faces, if possible.
* 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
@ -605,16 +639,27 @@ static bool is_outside_edge(EdgeHalf *e, const float co[3], BMVert **ret_closer_
* 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])
static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, bool edges_between, 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, d;
float dir1[3], dir2[3], dir1n[3], dir2p[3], norm_v[3], norm_v1[3], norm_v2[3],
norm_perp1[3], norm_perp2[3], off1a[3], off1b[3], off2a[3], off2b[3],
isect2[3], dropco[3], plane[4], ang, d;
BMVert *closer_v;
EdgeHalf *e, *e1next, *e2prev;
BMFace *ff;
int isect_kind;
/* get direction vectors for two offset lines */
sub_v3_v3v3(dir1, v->co, BM_edge_other_vert(e1->e, v)->co);
sub_v3_v3v3(dir2, BM_edge_other_vert(e2->e, v)->co, v->co);
if (edges_between) {
e1next = e1->next;
e2prev = e2->prev;
sub_v3_v3v3(dir1n, BM_edge_other_vert(e1next->e, v)->co, v->co);
sub_v3_v3v3(dir2p, v->co, BM_edge_other_vert(e2prev->e, v)->co);
}
ang = angle_v3v3(dir1, dir2);
if (ang < BEVEL_EPSILON_BIG) {
/* special case: e1 and e2 are parallel; put offset point perp to both, from v.
@ -654,14 +699,31 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float
* If e1-v-e2 is a reflex angle (viewed from vertex normal side), need to flip.
* Use f->no to figure out which side to look at angle from, as even if
* f is non-planar, will be more accurate than vertex normal */
cross_v3_v3v3(norm_v, dir2, dir1);
normalize_v3(norm_v);
if (dot_v3v3(norm_v, f ? f->no : v->no) < 0.0f)
negate_v3(norm_v);
if (!edges_between) {
cross_v3_v3v3(norm_v1, dir2, dir1);
normalize_v3(norm_v1);
if (dot_v3v3(norm_v1, f ? f->no : v->no) < 0.0f)
negate_v3(norm_v1);
copy_v3_v3(norm_v2, norm_v1);
}
else {
/* separate faces; get face norms at corners for each separately */
cross_v3_v3v3(norm_v1, dir1n, dir1);
normalize_v3(norm_v1);
f = e1->fnext;
if (dot_v3v3(norm_v1, f ? f->no : v->no) < 0.0f)
negate_v3(norm_v1);
cross_v3_v3v3(norm_v2, dir2, dir2p);
normalize_v3(norm_v2);
f = e2->fprev;
if (dot_v3v3(norm_v2, f ? f->no : v->no) < 0.0f)
negate_v3(norm_v2);
}
/* get vectors perp to each edge, perp to norm_v, and pointing into face */
cross_v3_v3v3(norm_perp1, dir1, norm_v);
cross_v3_v3v3(norm_perp2, dir2, norm_v);
cross_v3_v3v3(norm_perp1, dir1, norm_v1);
cross_v3_v3v3(norm_perp2, dir2, norm_v2);
normalize_v3(norm_perp1);
normalize_v3(norm_perp2);
@ -673,11 +735,10 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float
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 */
if (!isect_line_line_v3(off1a, off1b, off2a, off2b, meetco, isect2)) {
#ifdef BEVEL_ASSERT_PROJECT
BLI_assert(!"offset_meet failure");
#endif
/* intersect the lines */
isect_kind = isect_line_line_v3(off1a, off1b, off2a, off2b, meetco, isect2);
if (isect_kind == 0) {
/* lines are colinear: we already tested for this, but this used a different epsilon */
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)
@ -697,6 +758,26 @@ static void offset_meet(EdgeHalf *e1, EdgeHalf *e2, BMVert *v, BMFace *f, float
copy_v3_v3(meetco, closer_v->co);
e1->offset_r = len_v3v3(meetco, v->co);
}
if (edges_between && e1->offset_r > 0.0 && e2->offset_l > 0.0) {
/* Try to drop meetco to a face between e1 and e2 */
if (isect_kind == 2) {
/* lines didn't meet in 3d: get average of meetco and isect2 */
mid_v3_v3v3(meetco, meetco, isect2);
}
for (e = e1; e != e2; e = e->next) {
ff = e->fnext;
if (!ff)
continue;
plane_from_point_normal_v3(plane, v->co, ff->no);
closest_to_plane_v3(dropco, plane, meetco);
if (point_between_edges(dropco, v, ff, e, e->next)) {
copy_v3_v3(meetco, dropco);
break;
}
}
e1->offset_r = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e1->e, v)->co);
e2->offset_l = dist_to_line_v3(meetco, v->co, BM_edge_other_vert(e2->e, v)->co);
}
}
}
}
@ -799,6 +880,7 @@ static void offset_on_edge_between(
e2->offset_l = d;
}
#ifdef PRE_275_ALGORITHM
/* 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.
@ -874,6 +956,7 @@ static void offset_in_two_planes(
/* else iret == 1 and the lines are coplanar so meetco has the intersection */
}
}
#endif
/* Offset by e->offset in plane with normal plane_no, on left if left==true,
* else on right. If no is NULL, choose an arbitrary plane different
@ -1425,6 +1508,323 @@ static void set_bound_vert_seams(BevVert *bv)
} while ((v = v->next) != bv->vmesh->boundstart);
}
#ifndef PRE_275_ALGORITHM
/* Is e between two planes where angle between is 180? */
static bool eh_on_plane(EdgeHalf *e)
{
float dot;
if (e->fprev && e->fnext) {
dot = dot_v3v3(e->fprev->no, e->fnext->no);
if (fabsf(dot) <= BEVEL_EPSILON ||
fabsf(dot - 1.0f) <= BEVEL_EPSILON)
return true;
}
return false;
}
/* Calculate the profiles for all the BoundVerts of VMesh vm */
static void calculate_vm_profiles(BevelParams *bp, BevVert *bv, VMesh *vm)
{
BoundVert *v;
v = vm->boundstart;
do {
set_profile_params(bp, bv, v);
calculate_profile(bp, v);
} while ((v = v->next) != vm->boundstart);
}
/* Implements build_boundary for vertex-only case */
static void build_boundary_vertex_only(BevelParams *bp, BevVert *bv, bool construct)
{
VMesh *vm = bv->vmesh;
EdgeHalf *efirst, *e;
BoundVert *v;
float co[3];
BLI_assert(bp->vertex_only);
e = efirst = &bv->edges[0];
do {
slide_dist(e, bv->v, e->offset_l, co);
if (construct) {
v = add_new_bound_vert(bp->mem_arena, vm, co);
v->efirst = v->elast = e;
e->leftv = v;
}
else {
adjust_bound_vert(e->leftv, co);
}
} while ((e = e->next) != efirst);
calculate_vm_profiles(bp, bv, vm);
if (construct) {
set_bound_vert_seams(bv);
if (vm->count == 2)
vm->mesh_kind = M_NONE;
else if (bp->seg == 1)
vm->mesh_kind = M_POLY;
else
vm->mesh_kind = M_ADJ;
}
}
/* Special case of build_boundary when a single edge is beveled.
* The 'width adjust' part of build_boundary has been done already, and
* efirst is the first beveled edge at vertex bv. */
static void build_boundary_terminal_edge(BevelParams *bp, BevVert *bv, EdgeHalf *efirst, bool construct)
{
MemArena *mem_arena = bp->mem_arena;
VMesh *vm = bv->vmesh;
BoundVert *v;
EdgeHalf *e;
const float *no;
float co[3], d;
e = efirst;
if (bv->edgecount == 2) {
/* only 2 edges in, so terminate the edge with an artificial vertex on the unbeveled edge */
no = e->fprev ? e->fprev->no : (e->fnext ? e->fnext->no : NULL);
offset_in_plane(e, no, true, co);
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);
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);
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);
}
}
else {
/* More than 2 edges in. Put on-edge verts on all the other edges
* and join with the beveled edge to make a poly or adj mesh,
* Because e->prev has offset 0, offset_meet will put co on that edge */
/* TODO: should do something else if angle between e and e->prev > 180 */
offset_meet(e->prev, e, bv->v, e->fprev, false, co);
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 {
adjust_bound_vert(e->leftv, co);
}
e = e->next;
offset_meet(e->prev, e, bv->v, e->fprev, false, co);
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);
}
d = len_v3v3(bv->v->co, co);
for (e = e->next; e->next != efirst; e = e->next) {
slide_dist(e, bv->v, d, co);
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);
}
}
}
calculate_vm_profiles(bp, bv, vm);
if (bv->edgecount >= 3) {
/* special case: snap profile to plane of adjacent two edges */
v = vm->boundstart;
BLI_assert(v->ebev != NULL);
move_profile_plane(v, v->efirst, v->next->elast);
calculate_profile(bp, v);
}
if (construct) {
set_bound_vert_seams(bv);
if (vm->count == 2 && bv->edgecount == 3) {
vm->mesh_kind = M_NONE;
}
else if (vm->count == 3) {
vm->mesh_kind = M_TRI_FAN;
}
else {
vm->mesh_kind = M_POLY;
}
}
}
/* Make a circular list of BoundVerts for bv, each of which has the coordinates
* of a vertex on the boundary of the beveled vertex bv->v.
* 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, bool construct)
{
MemArena *mem_arena = bp->mem_arena;
EdgeHalf *efirst, *e, *e2, *e3, *enip, *eip, *eother;
BoundVert *v;
BevVert *bvother;
VMesh *vm;
float co[3];
int nip, nnip;
/* Current bevel does nothing if only one edge into a vertex */
if (bv->edgecount <= 1)
return;
if (bp->vertex_only) {
build_boundary_vertex_only(bp, bv, construct);
return;
}
vm = bv->vmesh;
/* Find a beveled edge to be efirst. Then for each edge, try matching widths to other end. */
e = efirst = next_bev(bv, NULL);
BLI_assert(e->is_bev);
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);
if (bv->selcount == 1) {
/* special case: only one beveled edge in */
build_boundary_terminal_edge(bp, bv, efirst, construct);
return;
}
/* Here: there is more than one beveled edge.
* We make BoundVerts to connect the sides of the beveled edges.
* Non-beveled edges in between will just join to the appropriate juncture point. */
e = efirst;
do {
BLI_assert(e->is_bev);
/* Make the BoundVert for the right side of e; other side will be made
* when the beveled edge to the left of e is handled.
* Analyze edges until next beveled edge.
* They are either "in plane" (preceding and subsequent faces are coplanar)
* or not. The "non-in-plane" edges effect silhouette and we prefer to slide
* along one of those if possible. */
nip = nnip = 0; /* counts of in-plane / not-in-plane */
enip = eip = NULL; /* representatives of each */
for (e2 = e->next; !e2->is_bev; e2 = e2->next) {
if (eh_on_plane(e2)) {
nip++;
eip = e2;
}
else {
nnip++;
enip = e2;
}
}
if (nip == 0 && nnip == 0) {
offset_meet(e, e2, bv->v, e->fnext, false, co);
}
else if (nnip > 0) {
if (nnip == 1) {
offset_on_edge_between(bp, e, e2, enip, bv->v, co);
}
else {
offset_meet(e, e2, bv->v, NULL, true, co);
}
}
else {
/* nip > 0 and nnip == 0 */
if (nip == 1) {
offset_on_edge_between(bp, e, e2, eip, bv->v, co);
}
else {
offset_meet(e, e2, bv->v, NULL, true, co);
}
}
if (construct) {
v = add_new_bound_vert(mem_arena, vm, co);
v->efirst = e;
v->elast = e2;
v->ebev = e2;
e->rightv = v;
e2->leftv = v;
for (e3 = e->next; e3 != e2; e3 = e3->next) {
e3->leftv = e3->rightv = v;
}
}
else {
adjust_bound_vert(e->rightv, co);
}
e = e2;
} while (e != efirst);
calculate_vm_profiles(bp, bv, vm);
if (construct) {
set_bound_vert_seams(bv);
if (vm->count == 2) {
vm->mesh_kind = M_NONE;
}
else if (efirst->seg == 1) {
vm->mesh_kind = M_POLY;
}
else {
vm->mesh_kind = M_ADJ;
}
}
}
#endif
#ifdef PRE_275_ALGORITHM
/* This code was used prior to just before the 2.75 Blender release.
* It treated multiple non-beveled edges between beveled ones differently */
/* Make a circular list of BoundVerts for bv, each of which has the coordinates
* of a vertex on the boundary of the beveled vertex bv->v.
* This may adjust some EdgeHalf widths, and there might have to be
@ -1519,7 +1919,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
/* 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);
offset_meet(e->prev, e, bv->v, e->fprev, false, co);
if (construct) {
v = add_new_bound_vert(mem_arena, vm, co);
v->efirst = e->prev;
@ -1556,7 +1956,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
}
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);
offset_meet(e->prev, e, bv->v, e->fprev, false, co);
if (construct) {
v = add_new_bound_vert(mem_arena, vm, co);
v->efirst = e->prev;
@ -1580,7 +1980,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
}
else if (e->prev->is_bev) {
/* on-edge meet between e->prev and e */
offset_meet(e->prev, e, bv->v, e->fprev, co);
offset_meet(e->prev, e, bv->v, e->fprev, false, co);
if (construct) {
v = add_new_bound_vert(mem_arena, vm, co);
v->efirst = e->prev;
@ -1657,6 +2057,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
}
}
}
#endif
/* Do a global pass to try to make offsets as even as possible.
* Consider this graph: