diff options
author | Howard Trickey <howard.trickey@gmail.com> | 2014-01-08 16:40:01 +0400 |
---|---|---|
committer | Howard Trickey <howard.trickey@gmail.com> | 2014-01-08 16:40:01 +0400 |
commit | 49aa701645e97ea7a6f698e4063a5327f6c79815 (patch) | |
tree | 3469853a0ec2ca2c58c4c50184d05d66ff978d85 /source/blender/bmesh | |
parent | 8094ac91944f080c53943c8319eeaa9c3d1b9720 (diff) |
Add profile control parameter to Bevel.
Parameter controls concavity / convexity.
<.25 means: concave inward
.25 means: straight slanted
>.25 means: concave outward
.5 means: circular (the default)
1 means: straight along original sides
For now, there is a hard lower limit of .15
because more work is needed to get decent
results in the range below that.
The profile is actually a superellipse, and the
parameter is 1/4 of the exponent in the implicit equation
for a superellipse, except at the extreme values of 0 and 1.
Diffstat (limited to 'source/blender/bmesh')
-rw-r--r-- | source/blender/bmesh/intern/bmesh_opdefines.c | 1 | ||||
-rw-r--r-- | source/blender/bmesh/operators/bmo_bevel.c | 3 | ||||
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.c | 192 | ||||
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.h | 4 |
4 files changed, 159 insertions, 41 deletions
diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c index 97624ec64bb..b4697935da5 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.c +++ b/source/blender/bmesh/intern/bmesh_opdefines.c @@ -1559,6 +1559,7 @@ static BMOpDefine bmo_bevel_def = { {"offset", BMO_OP_SLOT_FLT}, /* amount to offset beveled edge */ {"offset_type", BMO_OP_SLOT_INT}, /* how to measure offset (enum) */ {"segments", BMO_OP_SLOT_INT}, /* number of segments in bevel */ + {"profile", BMO_OP_SLOT_FLT}, /* profile shape, 0->1 (.5=>round) */ {"vertex_only", BMO_OP_SLOT_BOOL}, /* only bevel vertices, not edges */ {{'\0'}}, }, diff --git a/source/blender/bmesh/operators/bmo_bevel.c b/source/blender/bmesh/operators/bmo_bevel.c index 4eec15d4d7e..07a2e674863 100644 --- a/source/blender/bmesh/operators/bmo_bevel.c +++ b/source/blender/bmesh/operators/bmo_bevel.c @@ -39,6 +39,7 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) const int offset_type = BMO_slot_int_get(op->slots_in, "offset_type"); const int seg = BMO_slot_int_get(op->slots_in, "segments"); const bool vonly = BMO_slot_bool_get(op->slots_in, "vertex_only"); + const float profile = BMO_slot_float_get(op->slots_in, "profile"); if (offset > 0) { BMOIter siter; @@ -59,7 +60,7 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) } } - BM_mesh_bevel(bm, offset, offset_type, seg, vonly, false, false, NULL, -1); + BM_mesh_bevel(bm, offset, offset_type, seg, profile, vonly, false, false, NULL, -1); BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG); } diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index 984632d807d..4a6032a1281 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -89,14 +89,20 @@ typedef struct EdgeHalf { } EdgeHalf; /* Profile specification. - * For now, only have round profiles and straight profiles, so only need midpoint. + * Many interesting profiles are in family of superellipses: + * (abs(x/a))^r + abs(y/b))^r = 1 + * r==2 => ellipse; r==1 => line; r < 1 => concave; r > 1 => bulging out. + * Special cases: let r==0 mean straight-inward, and r==4 mean straight outward * The start and end points of the profile are stored separately. - * TODO: generalize to superellipse profiles. */ typedef struct Profile { - bool flat; + float super_r; /* superellipse r parameter */ float midco[3]; /* mid control point for profile */ } Profile; +#define PRO_SQUARE_R 4.0f +#define PRO_CIRCLE_R 2.0f +#define PRO_LINE_R 1.0f +#define PRO_SQUARE_IN_R 0.0f /* An element in a cyclic boundary of a Vertex Mesh (VMesh) */ typedef struct BoundVert { @@ -150,6 +156,7 @@ typedef struct BevelParams { 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 */ + float pro_super_r; /* superellipse parameter for 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? */ @@ -182,7 +189,7 @@ static BoundVert *add_new_bound_vert(MemArena *mem_arena, VMesh *vm, const float tail->next = ans; vm->boundstart->prev = ans; } - ans->profile.flat = true; + ans->profile.super_r = PRO_LINE_R; vm->count++; return ans; } @@ -825,13 +832,13 @@ static void project_to_edge(BMEdge *e, const float co_a[3], const float co_b[3], /* If there is a bndv->ebev edge, find the mid control point if necessary. * It is the closest point on the beveled edge to the line segment between * bndv and bndv->next. */ -static void set_profile_params(BoundVert *bndv) +static void set_profile_params(BevelParams *bp, BoundVert *bndv) { EdgeHalf *e; e = bndv->ebev; if (e) { - bndv->profile.flat = false; + bndv->profile.super_r = bp->pro_super_r; project_to_edge(e->e, bndv->nv.co, bndv->next->nv.co, bndv->profile.midco); } @@ -1001,23 +1008,46 @@ static void get_point_on_round_edge(EdgeHalf *e, int k, /* Find the point on given profile at parameter u which goes from 0 to 2 as * the profile is moved from va to vb. */ static void get_profile_point(const Profile *pro, const float va[3], const float vb[3], float u, float r_co[3]) - { - float p[3], angle; +{ + float p[3], vo[3], angle, r, w; float m[4][4]; if (u <= 0.0f) copy_v3_v3(r_co, va); else if (u >= 2.0f) copy_v3_v3(r_co, vb); - else if (pro->flat || !make_unit_square_map(va, pro->midco, vb, m)) { - interp_v3_v3v3(r_co, va, vb, u / 2.0f); - } else { - angle = u * (float)M_PI / 4.0f; /* angle from y axis */ - p[0] = sinf(angle); - p[1] = cosf(angle); - p[2] = 0.0f; - mul_v3_m4v3(r_co, m, p); + r = pro->super_r; + if (r == 1.0f || !make_unit_square_map(va, pro->midco, vb, m)) { + interp_v3_v3v3(r_co, va, vb, u / 2.0f); + } + else if (r == PRO_SQUARE_IN_R) { + /* square inward concave */ + zero_v3(p); + mul_v3_m4v3(vo, m, p); + if (u <= 1.0f) + interp_v3_v3v3(r_co, va, vo, u); + else + interp_v3_v3v3(r_co, vo, vb, u - 1.0f); + } + else if (r >= PRO_SQUARE_R) { + /* square outward convex */ + if (u <= 1.0f) + interp_v3_v3v3(r_co, va, pro->midco, u); + else + interp_v3_v3v3(r_co, pro->midco, vb, u - 1.0f); + } + else { + angle = u * (float)M_PI / 4.0f; /* angle from y axis */ + p[0] = sinf(angle); + p[1] = cosf(angle); + p[2] = 0.0f; + if (r != PRO_CIRCLE_R) { + w = powf(powf(p[0], r) + pow(p[1], r), -1.0f / r); + mul_v2_fl(p, w); + } + mul_v3_m4v3(r_co, m, p); + } } } @@ -1065,11 +1095,43 @@ static void snap_to_edge_profile(EdgeHalf *e, const float va[3], const float vb[ } #endif -/* Snapping a direction co to a unit radius sphere is just normalizing co. - * TODO: generalize to superellipsoid */ -static void snap_to_sphere(float co[3]) +/* Snap a direction co to a superellipsoid with parameter super_r */ +static void snap_to_superellipsoid(float co[3], const float super_r) { - normalize_v3(co); + float a, b, c, x, y, z, r, rinv; + + r = super_r; + if (r == PRO_CIRCLE_R) { + normalize_v3(co); + return; + } + + x = a = max_ff(0.0f, co[0]); + y = b = max_ff(0.0f, co[1]); + z = c = max_ff(0.0f, co[2]); + if (r <= 0.0f) + r = 0.1f; + rinv = 1.0f / r; + if (a == 0.0f) { + if (b == 0.0f) { + x = 0.0f; + y = 0.0f; + z = powf(c, rinv); + } + else { + x = 0.0f; + y = powf(1.0f / (1.0f + powf(c / b, r)), rinv); + z = c * y / b; + } + } + else { + x = powf(1.0f / (1.0f + powf(b / a, r) + powf(c / a, r)), rinv); + y = b * x / a; + z = c * x / a; + } + co[0] = x; + co[1] = y; + co[2] = z; } static void snap_to_profile(BoundVert *bndv, EdgeHalf *e, float co[3]) @@ -1087,14 +1149,14 @@ static void snap_to_profile(BoundVert *bndv, EdgeHalf *e, float co[3]) closest_to_plane_v3(vb0, plane, vb); closest_to_plane_v3(vmid0, plane, bndv->profile.midco); if (make_unit_square_map(va0, vmid0, vb0, m)) { - /* Transform co and project it onto sphere */ + /* Transform co and project it onto superellipse */ if (!invert_m4_m4(minv, m)) { /* shouldn't happen */ BLI_assert(!"failed inverse during profile snap"); return; } mul_v3_m4v3(p, minv, co); - snap_to_sphere(p); + snap_to_superellipsoid(p, bndv->profile.super_r); mul_v3_m4v3(snap, m, p); copy_v3_v3(co, snap); } @@ -1207,7 +1269,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) else { adjust_bound_vert(e->next->leftv, co); } - set_profile_params(vm->boundstart); + set_profile_params(bp, vm->boundstart); return; } @@ -1311,7 +1373,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) v = vm->boundstart; do { - set_profile_params(v); + set_profile_params(bp, v); } while ((v = v->next) != vm->boundstart); if (bv->selcount == 1 && bv->edgecount == 3) { @@ -2247,12 +2309,44 @@ static VMesh *cubic_subdiv(MemArena *mem_arena, VMesh *vm0) return vm1; } +/* Special case for cube corner, when r is PRO_SQUARE_R, + * meaning straight sides */ +static VMesh *make_cube_corner_straight(MemArena *mem_arena, int nseg) +{ + VMesh *vm; + float co[3]; + int i, j, k, ns2; + + ns2 = nseg / 2; + vm = new_adj_subdiv_vmesh(mem_arena, 3, nseg, NULL); + vm->count = 0; // reset, so following loop will end up with correct count + for (i = 0; i < 3; i++) { + zero_v3(co); + co[i] = 1.0f; + add_new_bound_vert(mem_arena, vm, co); + } + for (i = 0; i < 3; i++) { + for (j = 0; j <= ns2; j++) { + for (k = 0; k <= ns2; k++) { + if (!is_canon(vm, i, j, k)) + continue; + co[i] = 1.0f; + co[(i + 1) % 3] = (float) k * 2.0 / (float) nseg; + co[(i + 2) % 3] = (float) j * 2.0 / (float) nseg; + copy_v3_v3(mesh_vert(vm, i, j, k)->co, co); + } + } + } + vmesh_copy_equiv_verts(vm); + return vm; +} + /* Make a VMesh with nseg segments that covers the unit radius sphere octant * with center at (0,0,0). * This has BoundVerts at (1,0,0), (0,1,0) and (0,0,1), with quarter circle arcs * on the faces for the orthogonal planes through the origin. */ -static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) +static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg, float r) { VMesh *vm0, *vm1; BoundVert *bndv; @@ -2260,6 +2354,9 @@ static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) float co[3], coa[3], cob[3], coc[3]; float w; + if (r == PRO_SQUARE_R) + return make_cube_corner_straight(mem_arena, nseg); + /* initial mesh has 3 sides, 2 segments */ vm0 = new_adj_subdiv_vmesh(mem_arena, 3, 2, NULL); vm0->count = 0; // reset, so following loop will end up with correct count @@ -2276,6 +2373,7 @@ static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) coc[i] = 1.0f; coc[(i + 1) % 3] = 1.0f; coc[(i + 2) % 3] = 0.0f; + bndv->profile.super_r = r; copy_v3_v3(bndv->profile.midco, coc); copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, coa); get_profile_point(&bndv->profile, coa, cob, 1.0f, co); @@ -2289,7 +2387,10 @@ static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) co[1] = w; co[2] = w; if (nseg > 2) { - mul_v3_fl(co, 1.4f); + if (r > 1.5f) + mul_v3_fl(co, 1.4f); + else if (r < 0.75f) + mul_v3_fl(co, 0.6f); } copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, co); @@ -2307,7 +2408,7 @@ static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) for (i = 0; i < 3; i++) { for (j = 0; j <= ns2; j++) { for (k = 0; k <= nseg; k++) { - snap_to_sphere(mesh_vert(vm1, i, j, k)->co); + snap_to_superellipsoid(mesh_vert(vm1, i, j, k)->co, r); } } } @@ -2315,7 +2416,7 @@ static VMesh *make_cube_corner_adj_vmesh(MemArena *mem_arena, int nseg) } /* Is this a good candidate for using tri_corner_adj_vmesh? */ -static bool tri_corner_test(BevVert *bv) +static bool tri_corner_test(BevelParams *bp, BevVert *bv) { float ang, totang, angdiff; EdgeHalf *e; @@ -2332,7 +2433,8 @@ static bool tri_corner_test(BevVert *bv) totang += ang; } angdiff = fabsf(totang - 3.0f * (float)M_PI_2); - if (angdiff > (float)M_PI_4) + if ((bp->pro_super_r == PRO_SQUARE_R && angdiff > (float)M_PI/16.0f) || + angdiff > (float)M_PI_4) return false; return true; } @@ -2355,7 +2457,7 @@ static VMesh *tri_corner_adj_vmesh(BevelParams *bp, BevVert *bv) make_unit_cube_map(co0, co1, co2, bv->v->co, mat); ns = bp->seg; ns2 = ns / 2; - vm = make_cube_corner_adj_vmesh(bp->mem_arena, bp->seg); + vm = make_cube_corner_adj_vmesh(bp->mem_arena, bp->seg, bp->pro_super_r); for (i = 0; i < 3; i++) { for (j = 0; j <= ns2; j++) { for (k = 0; k <= ns; k++) { @@ -2377,7 +2479,7 @@ static VMesh *adj_vmesh(BevelParams *bp, BevVert *bv) float co[3], coa[3], cob[3], dir[3]; BoundVert *bndv; MemArena *mem_arena = bp->mem_arena; - float fac, fullness; + float r, fac, fullness; /* First construct an initial control mesh, with nseg==2 */ n = bv->vmesh->count; @@ -2404,11 +2506,21 @@ static VMesh *adj_vmesh(BevelParams *bp, BevVert *bv) mul_v3_fl(co, 1.0f / (float)n); sub_v3_v3v3(cob, co, coa); add_v3_v3(cob, co); - if (bp->vertex_only) - fac = 0.25f; - else - fac = 0.5f; - fullness = 1.0f - fac / 2.0f; + r = bp->pro_super_r; + if (r == 1.0) + fullness = 0.0f; + else if (r > 1.0) { + if (bp->vertex_only) + fac = 0.25f; + else if (r == PRO_SQUARE_R) + fac = -2.0; + else + fac = 0.5f; + fullness = 1.0f - fac / r; + } + else { + fullness = r - 1.0f; + } sub_v3_v3v3(dir, coa, co); if (len_squared_v3(dir) > BEVEL_EPSILON_SQ) madd_v3_v3fl(co, dir, fullness); @@ -2478,7 +2590,7 @@ static void bevel_build_rings_subdiv(BevelParams *bp, BMesh *bm, BevVert *bv) if (epipe) vm1 = pipe_adj_vmesh(bp, bv, epipe); - else if (tri_corner_test(bv)) + else if (tri_corner_test(bp, bv)) vm1 = tri_corner_adj_vmesh(bp, bv); else vm1 = adj_vmesh(bp, bv); @@ -3293,7 +3405,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 int offset_type, - const float segments, + const float segments, const float profile, const bool vertex_only, const bool use_weights, const bool limit_offset, const struct MDeformVert *dvert, const int vertex_group) { @@ -3306,6 +3418,7 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, bp.offset = offset; bp.offset_type = offset_type; bp.seg = segments; + bp.pro_super_r = 4.0f * profile; /* convert to superellipse exponent */ bp.vertex_only = vertex_only; bp.use_weights = use_weights; bp.preserve_widths = false; @@ -3313,6 +3426,9 @@ void BM_mesh_bevel(BMesh *bm, const float offset, const int offset_type, bp.dvert = dvert; bp.vertex_group = vertex_group; + if (bp.pro_super_r < 0.15f) + bp.pro_super_r = 0.15f; /* TODO: implement 0 case properly */ + if (bp.offset > 0) { /* primary alloc */ bp.vert_hash = BLI_ghash_ptr_new(__func__); diff --git a/source/blender/bmesh/tools/bmesh_bevel.h b/source/blender/bmesh/tools/bmesh_bevel.h index 9897b6b070c..0c088ce0238 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.h +++ b/source/blender/bmesh/tools/bmesh_bevel.h @@ -30,7 +30,7 @@ struct MDeformVert; 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); + const float profile, const bool vertex_only, const bool use_weights, + const bool limit_offset, const struct MDeformVert *dvert, const int vertex_group); #endif /* __BMESH_BEVEL_H__ */ |