diff options
Diffstat (limited to 'source/blender/bmesh/tools')
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.c | 2011 | ||||
-rw-r--r-- | source/blender/bmesh/tools/bmesh_bevel.h | 7 |
2 files changed, 1393 insertions, 625 deletions
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index 797e2ca864e..6989946147e 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -38,6 +38,9 @@ #include "eigen_capi.h" +#include "BKE_curveprofile.h" +#include "DNA_curveprofile_types.h" + #include "bmesh.h" #include "bmesh_bevel.h" /* own include */ @@ -50,10 +53,22 @@ #define BEVEL_EPSILON_BIG_SQ 1e-8f #define BEVEL_EPSILON_ANG DEG2RADF(2.0f) #define BEVEL_SMALL_ANG DEG2RADF(10.0f) +/** Difference in dot products that corresponds to 10 degree difference between vectors. */ +#define BEVEL_SMALL_ANG_DOT 1 - cosf(BEVEL_SMALL_ANG) #define BEVEL_MAX_ADJUST_PCT 10.0f #define BEVEL_MAX_AUTO_ADJUST_PCT 300.0f #define BEVEL_MATCH_SPEC_WEIGHT 0.2 +//#define DEBUG_CUSTOM_PROFILE_CUTOFF +//#define DEBUG_CUSTOM_PROFILE_SAMPLE + +#if defined(DEBUG_PROFILE_ORIENTATION_DRAW) || defined(DEBUG_CUSTOM_PROFILE_PIPE) +static float debug_color_red[4] = {1.0f, 0.0f, 0.0f, 1.0f}; +static float debug_color_blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}; +extern void DRW_debug_sphere(const float center[3], const float radius, const float color[4]); +extern void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]); +#endif + /* happens far too often, uncomment for development */ // #define BEVEL_ASSERT_PROJECT @@ -64,72 +79,108 @@ typedef struct NewVert { BMVert *v; float co[3]; - // int _pad; + char _pad[4]; } NewVert; struct BoundVert; /* Data for one end of an edge involved in a bevel */ typedef struct EdgeHalf { - struct EdgeHalf *next, *prev; /* in CCW order */ - BMEdge *e; /* original mesh edge */ - BMFace *fprev; /* face between this edge and previous, if any */ - BMFace *fnext; /* face between this edge and next, if any */ - struct BoundVert *leftv; /* left boundary vert (looking along edge to end) */ - struct BoundVert *rightv; /* right boundary vert, if beveled */ - int profile_index; /* offset into profile to attach non-beveled edge */ - 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)? */ - // int _pad; + /** Other EdgeHalves connected to the same BevVert, in CCW order. */ + struct EdgeHalf *next, *prev; + /** Original mesh edge */ + BMEdge *e; + /** Face between this edge and previous, if any */ + BMFace *fprev; + /** Face between this edge and next, if any */ + BMFace *fnext; + /** Left boundary vert (looking along edge to end) */ + struct BoundVert *leftv; + /** Right boundary vert, if beveled */ + struct BoundVert *rightv; + /** Offset into profile to attach non-beveled edge */ + int profile_index; + /** How many segments for the bevel */ + int seg; + /** Offset for this edge, on left side */ + float offset_l; + /** Offset for this edge, on right side */ + float offset_r; + /** User specification for offset_l */ + float offset_l_spec; + /** User specification for offset_r */ + float offset_r_spec; + /** Is this edge beveled? */ + bool is_bev; + /** Is e->v2 the vertex at this end? */ + bool is_rev; + /** Is e a seam for custom loopdata (e.g., UVs)? */ + bool is_seam; + /** Used during the custom profile orientation pass */ + bool visited_rpo; + char _pad[4]; } EdgeHalf; -/* Profile specification. +/* Profile specification: * 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 profile is an arc with control points coa, midco, + * The profile is a path defined with start, middle, and end control points * projected onto a plane (plane_no is normal, plane_co is a point on it) * via lines in a given direction (proj_dir). * After the parameters are all set, the actual profile points are calculated - * and point in prof_co. We also may need profile points for a higher resolution - * number of segments, in order to make the vertex mesh pattern, and that goes - * in prof_co_2. + * and pointed to by prof_co. We also may need profile points for a higher resolution + * number of segments for the subdivision while making the ADJ vertex mesh pattern, + * and that goes in prof_co_2. */ typedef struct Profile { - float super_r; /* superellipse r parameter */ - float coa[3]; /* start control point for profile */ - float midco[3]; /* mid control point for profile */ - float cob[3]; /* end control point for profile */ - float plane_no[3]; /* normal of plane to project to */ - float plane_co[3]; /* coordinate on plane to project to */ - float proj_dir[3]; /* direction of projection line */ - float *prof_co; /* seg+1 profile coordinates (triples of floats) */ - float *prof_co_2; /* like prof_co, but for seg power of 2 >= seg */ + /** Superellipse r parameter */ + float super_r; + /** Height for profile cutoff face sides */ + float height; + /** Start control point for profile */ + float start[3]; + /** Mid control point for profile */ + float middle[3]; + /** End control point for profile */ + float end[3]; + /** Normal of plane to project to */ + float plane_no[3]; + /** Coordinate on plane to project to */ + float plane_co[3]; + /** Direction of projection line */ + float proj_dir[3]; + /** seg+1 profile coordinates (triples of floats) */ + float *prof_co; + /** Like prof_co, but for seg power of 2 >= seg */ + float *prof_co_2; } Profile; #define PRO_SQUARE_R 1e4f #define PRO_CIRCLE_R 2.0f #define PRO_LINE_R 1.0f #define PRO_SQUARE_IN_R 0.0f -/* Cache result of expensive calculation of u parameter values to +/** The un-transformed 2D storage of profile vertex locations. Also for non-custom profiles + * this serves as a cache for the results of the expensive calculation of u parameter values to * get even spacing on superellipse for current BevelParams seg * and pro_super_r. */ typedef struct ProfileSpacing { - double *xvals; /* seg+1 x values */ - double *xvals_2; /* seg_2+1 x values, seg_2 = power of 2 >= seg */ - double *yvals; /* seg+1 y values */ - double *yvals_2; /* seg_2+1 y values, seg_2 = power of 2 >= seg */ - int seg_2; /* the seg_2 value */ + /** The profile's seg+1 x values */ + double *xvals; + /** The profile's seg+1 y values */ + double *yvals; + /** The profile's seg_2+1 x values, (seg_2 = power of 2 >= seg) */ + double *xvals_2; + /** The profile's seg_2+1 y values, (seg_2 = power of 2 >= seg) */ + double *yvals_2; + /** The power of two greater than or equal to the number of segments. */ + int seg_2; + /** How far "out" the profile is, used at the start of subdivision */ + float fullness; } ProfileSpacing; -/* An element in a cyclic boundary of a Vertex Mesh (VMesh) */ +/** An element in a cyclic boundary of a Vertex Mesh (VMesh) */ typedef struct BoundVert { /** In CCW order. */ struct BoundVert *next, *prev; @@ -137,7 +188,7 @@ typedef struct BoundVert { /** First of edges attached here: in CCW order. */ EdgeHalf *efirst; EdgeHalf *elast; - /** The "edge between" that this is on, in offset_on_edge_between case. */ + /** The "edge between" that this boundvert on, in offset_on_edge_between case. */ EdgeHalf *eon; /** Beveled edge whose left side is attached here, if any. */ EdgeHalf *ebev; @@ -157,26 +208,35 @@ typedef struct BoundVert { bool is_arc_start; /** This boundvert begins a patch profile */ bool is_patch_start; + /** Is this boundvert the side of the custom profile's start */ + bool is_profile_start; + char _pad[3]; /** Length of seam starting from current boundvert to next boundvert with ccw ordering */ int seam_len; /** Same as seam_len but defines length of sharp edges */ int sharp_len; - // int _pad; } BoundVert; -/* Mesh structure replacing a vertex */ +/** Mesh structure replacing a vertex */ typedef struct VMesh { - NewVert *mesh; /* allocated array - size and structure depends on kind */ - BoundVert *boundstart; /* start of boundary double-linked list */ - int count; /* number of vertices in the boundary */ - int seg; /* common # of segments for segmented edges */ + /** Allocated array - size and structure depends on kind */ + NewVert *mesh; + /** Start of boundary double-linked list */ + BoundVert *boundstart; + /** Number of vertices in the boundary */ + int count; + /** Common number of segments for segmented edges (same as bp->seg) */ + int seg; + /** The kind of mesh to build at the corner vertex meshes */ enum { M_NONE, /* no polygon mesh needed */ M_POLY, /* a simple polygon */ M_ADJ, /* "adjacent edges" mesh pattern */ M_TRI_FAN, /* a simple polygon - fan filled */ + M_CUTOFF, /* A triangulated face at the end of each profile */ } mesh_kind; - // int _pad; + + int _pad; } VMesh; /* Data for a vertex involved in a bevel */ @@ -196,6 +256,7 @@ typedef struct BevVert { /** Used in graph traversal */ bool visited; /** Array of size edgecount; CCW order from vertex normal side */ + char _pad[6]; EdgeHalf *edges; /** Array of size wirecount of wire edges */ BMEdge **wire_edges; @@ -209,7 +270,7 @@ typedef enum { F_NONE, /** Original face, not touched */ F_ORIG, - /** Face for construction aroun a vert */ + /** Face for construction around a vert */ F_VERT, /** Face for a beveled edge */ F_EDGE, @@ -217,11 +278,21 @@ typedef enum { F_RECON, } FKind; +/** Helper for keeping track of angle kind. */ +enum { + /** Angle less than 180 degrees */ + ANGLE_SMALLER = -1, + /** 180 degree angle */ + ANGLE_STRAIGHT = 0, + /** Angle greater than 180 degrees */ + ANGLE_LARGER = 1, +}; + #if 0 static const char* fkind_names[] = {"F_NONE", "F_ORIG", "F_VERT", "F_EDGE", "F_RECON"}; /* DEBUG */ #endif -/* Bevel parameters and state */ +/** Bevel parameters and state */ typedef struct BevelParams { /** Records BevVerts made: key BMVert*, value BevVert* */ GHash *vert_hash; @@ -229,9 +300,10 @@ typedef struct BevelParams { GHash *face_hash; /** Use for all allocs while bevel runs, if we need to free we can switch to mempool. */ MemArena *mem_arena; - /** Parameter values for evenly spaced profiles. */ + /** Profile vertex location and spacings */ ProfileSpacing pro_spacing; - + /** Parameter values for evenly spaced profile points for the miter profiles */ + ProfileSpacing pro_spacing_miter; /** Blender units to offset each side of a beveled edge. */ float offset; /** How offset is measured; enum defined in bmesh_operators.h */ @@ -258,6 +330,11 @@ typedef struct BevelParams { bool mark_sharp; /** Should we harden normals? */ bool harden_normals; + /** Should we use the custom profiles feature? */ + bool use_custom_profile; + char _pad[3]; + /** The struct used to store the custom profile input */ + const struct CurveProfile *custom_profile; /** Vertex group array, maybe set if vertex_only. */ const struct MDeformVert *dvert; /** Vertex group index, maybe set if vertex_only. */ @@ -270,6 +347,8 @@ typedef struct BevelParams { int miter_outer; /** What kind of miter pattern to use on non-reflex angles. */ int miter_inner; + /** The method to use for vertex mesh creation */ + int vmesh_method; /** Amount to spread when doing inside miter. */ float spread; /** Mesh's smoothresh, used if hardening. */ @@ -278,8 +357,6 @@ typedef struct BevelParams { // #pragma GCC diagnostic ignored "-Wpadded" -// #include "bevdebug.c" - /* Some flags to re-enable old behavior for a while, * in case fixes broke things not caught by regression tests. */ static int bev_debug_flags = 0; @@ -367,6 +444,7 @@ static BoundVert *add_new_bound_vert(MemArena *mem_arena, VMesh *vm, const float ans->any_seam = false; ans->is_arc_start = false; ans->is_patch_start = false; + ans->is_profile_start = false; vm->count++; return ans; } @@ -628,7 +706,7 @@ static BMFace *bev_create_ngon(BMesh *bm, } if (mat_nr >= 0) { - f->mat_nr = mat_nr; + f->mat_nr = (short)mat_nr; } return f; } @@ -812,7 +890,7 @@ static void bev_merge_edge_uvs(BMesh *bm, BMEdge *bme, BMVert *v) } /* Calculate coordinates of a point a distance d from v on e->e and return it in slideco */ -static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) +static void slide_dist(EdgeHalf *e, BMVert *v, float d, float r_slideco[3]) { float dir[3], len; @@ -821,8 +899,8 @@ static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) if (d > len) { d = len - (float)(50.0 * BEVEL_EPSILON_D); } - copy_v3_v3(slideco, v->co); - madd_v3_v3fl(slideco, dir, -d); + copy_v3_v3(r_slideco, v->co); + madd_v3_v3fl(r_slideco, dir, -d); } /* Is co not on the edge e? if not, return the closer end of e in ret_closer_v */ @@ -873,13 +951,13 @@ static int edges_angle_kind(EdgeHalf *e1, EdgeHalf *e2, BMVert *v) } dot = dot_v3v3(cross, no); if (fabsf(dot) < BEVEL_EPSILON_BIG) { - return 0; + return ANGLE_STRAIGHT; } else if (dot < 0.0f) { - return 1; + return ANGLE_LARGER; } else { - return -1; + return ANGLE_SMALLER; } } @@ -917,7 +995,7 @@ static bool point_between_edges(float co[3], BMVert *v, BMFace *f, EdgeHalf *e1, /* * 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. + * 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. @@ -978,7 +1056,7 @@ static void offset_meet( normalize_v3(norm_perp1); copy_v3_v3(off1a, v->co); d = max_ff(e1->offset_r, e2->offset_l); - d = d / cos(ang / 2.0f); + d = d / cosf(ang / 2.0f); madd_v3_v3fl(off1a, norm_perp1, d); copy_v3_v3(meetco, off1a); } @@ -1260,37 +1338,37 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) negate_v3(pro->proj_dir); } normalize_v3(pro->proj_dir); - project_to_edge(e->e, co1, co2, pro->midco); + project_to_edge(e->e, co1, co2, pro->middle); if (DEBUG_OLD_PROJ_TO_PERP_PLANE) { - /* put arc endpoints on plane with normal proj_dir, containing midco */ + /* put arc endpoints on plane with normal proj_dir, containing middle */ add_v3_v3v3(co3, co1, pro->proj_dir); - if (!isect_line_plane_v3(pro->coa, co1, co3, pro->midco, pro->proj_dir)) { + if (!isect_line_plane_v3(pro->start, co1, co3, pro->middle, pro->proj_dir)) { /* shouldn't happen */ - copy_v3_v3(pro->coa, co1); + copy_v3_v3(pro->start, co1); } add_v3_v3v3(co3, co2, pro->proj_dir); - if (!isect_line_plane_v3(pro->cob, co2, co3, pro->midco, pro->proj_dir)) { + if (!isect_line_plane_v3(pro->end, co2, co3, pro->middle, pro->proj_dir)) { /* shouldn't happen */ - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->end, co2); } } else { - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); } - /* default plane to project onto is the one with triangle co1 - midco - co2 in it */ - sub_v3_v3v3(d1, pro->midco, co1); - sub_v3_v3v3(d2, pro->midco, co2); + /* default plane to project onto is the one with triangle co1 - middle - co2 in it */ + sub_v3_v3v3(d1, pro->middle, co1); + sub_v3_v3v3(d2, pro->middle, co2); normalize_v3(d1); normalize_v3(d2); cross_v3_v3v3(pro->plane_no, d1, d2); normalize_v3(pro->plane_no); if (nearly_parallel(d1, d2)) { - /* co1 - midco -co2 are collinear. + /* co1 - middle -co2 are collinear. * Should be case that beveled edge is coplanar with two boundary verts. * We want to move the profile to that common plane, if possible. * That makes the multi-segment bevels curve nicely in that plane, as users expect. - * The new midco should be either v (when neighbor edges are unbeveled) + * The new middle should be either v (when neighbor edges are unbeveled) * or the intersection of the offset lines (if they are). * If the profile is going to lead into unbeveled edges on each side * (that is, both BoundVerts are "on-edge" points on non-beveled edges) @@ -1300,14 +1378,14 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } else { if (DEBUG_OLD_PROJ_TO_PERP_PLANE) { - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); } if (DEBUG_OLD_FLAT_MID) { - copy_v3_v3(pro->midco, bv->v->co); + copy_v3_v3(pro->middle, bv->v->co); } else { - copy_v3_v3(pro->midco, bv->v->co); + copy_v3_v3(pro->middle, bv->v->co); if (e->prev->is_bev && e->next->is_bev && bv->selcount >= 3) { /* want mid at the meet point of next and prev offset edges */ float d3[3], d4[3], co4[3], meetco[3], isect2[3]; @@ -1319,7 +1397,7 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) normalize_v3(d4); if (nearly_parallel(d3, d4)) { /* offset lines are collinear - want linear interpolation */ - mid_v3_v3v3(pro->midco, co1, co2); + mid_v3_v3v3(pro->middle, co1, co2); do_linear_interp = true; } else { @@ -1327,20 +1405,20 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) add_v3_v3v3(co4, co2, d4); isect_kind = isect_line_line_v3(co1, co3, co2, co4, meetco, isect2); if (isect_kind != 0) { - copy_v3_v3(pro->midco, meetco); + copy_v3_v3(pro->middle, meetco); } else { /* offset lines don't intersect - want linear interpolation */ - mid_v3_v3v3(pro->midco, co1, co2); + mid_v3_v3v3(pro->middle, co1, co2); do_linear_interp = true; } } } } - copy_v3_v3(pro->cob, co2); - sub_v3_v3v3(d1, pro->midco, co1); + copy_v3_v3(pro->end, co2); + sub_v3_v3v3(d1, pro->middle, co1); normalize_v3(d1); - sub_v3_v3v3(d2, pro->midco, co2); + sub_v3_v3v3(d2, pro->middle, co2); normalize_v3(d2); cross_v3_v3v3(pro->plane_no, d1, d2); normalize_v3(pro->plane_no); @@ -1357,9 +1435,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) copy_v3_v3(pro->plane_co, co1); } else if (bndv->is_arc_start) { - /* assume pro->midco was already set */ - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + /* assume pro->middle was alredy set */ + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); pro->super_r = PRO_CIRCLE_R; zero_v3(pro->plane_co); zero_v3(pro->plane_no); @@ -1368,9 +1446,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } if (do_linear_interp) { pro->super_r = PRO_LINE_R; - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); - mid_v3_v3v3(pro->midco, co1, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); + mid_v3_v3v3(pro->middle, co1, co2); /* won't use projection for this line profile */ zero_v3(pro->plane_co); zero_v3(pro->plane_no); @@ -1378,22 +1456,23 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } } -/* Maybe move the profile plane for bndv->ebev to the plane its profile's coa, cob and the +/* Maybe move the profile plane for bndv->ebev to the plane its profile's start, and the * original beveled vert, bmv. This will usually be the plane containing its adjacent - * non-beveled edges, but sometimes coa and cob are not on those edges. - */ -static void move_profile_plane(BoundVert *bndv, BMVert *bmv) + * non-beveled edges, but sometimes the start and the end are not on those edges. + * + * Currently just used in build boundary terminal edge */ +static void move_profile_plane(BoundVert *bndv, BMVert *bmvert) { float d1[3], d2[3], no[3], no2[3], no3[3], dot2, dot3; Profile *pro = &bndv->profile; - /* only do this if projecting, and coa, cob, and proj_dir are not coplanar */ + /* only do this if projecting, and start, end, and proj_dir are not coplanar */ if (is_zero_v3(pro->proj_dir)) { return; } - sub_v3_v3v3(d1, bmv->co, pro->coa); + sub_v3_v3v3(d1, bmvert->co, pro->start); normalize_v3(d1); - sub_v3_v3v3(d2, bmv->co, pro->cob); + sub_v3_v3v3(d2, bmvert->co, pro->end); normalize_v3(d2); cross_v3_v3v3(no, d1, d2); cross_v3_v3v3(no2, d1, pro->proj_dir); @@ -1584,35 +1663,35 @@ static double superellipse_co(double x, float r, bool rbig) } /* Find the point on given profile at parameter i which goes from 0 to n as - * the profile is moved from pro->coa to pro->cob. + * the profile is moved from pro->start to pro->end. * We assume that n is either the global seg number or a power of 2 less than * or equal to the power of 2 >= seg. * In the latter case, we subsample the profile for seg_2, which will not necessarily * give equal spaced chords, but is in fact more what is desired by the cubic subdivision * method used to make the vmesh pattern. */ -static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n, float r_co[3]) +static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int nseg, float r_co[3]) { - int d; + int subsample_spacing; if (bp->seg == 1) { if (i == 0) { - copy_v3_v3(r_co, pro->coa); + copy_v3_v3(r_co, pro->start); } else { - copy_v3_v3(r_co, pro->cob); + copy_v3_v3(r_co, pro->end); } } else { - if (n == bp->seg) { + if (nseg == bp->seg) { BLI_assert(pro->prof_co != NULL); copy_v3_v3(r_co, pro->prof_co + 3 * i); } else { - BLI_assert(is_power_of_2_i(n) && n <= bp->pro_spacing.seg_2); - /* set d to spacing in prof_co_2 between subsamples */ - d = bp->pro_spacing.seg_2 / n; - copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * d); + BLI_assert(is_power_of_2_i(nseg) && nseg <= bp->pro_spacing.seg_2); + /* Find spacing between subsamples in prof_co_2 */ + subsample_spacing = bp->pro_spacing.seg_2 / nseg; + copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * subsample_spacing); } } } @@ -1624,15 +1703,16 @@ static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n, * the coordinate values for the power of 2 >= bp->seg, * because the ADJ pattern needs power-of-2 boundaries * during construction. */ -static void calculate_profile(BevelParams *bp, BoundVert *bndv) +static void calculate_profile(BevelParams *bp, BoundVert *bndv, bool reversed, bool miter) { int i, k, ns; const double *xvals, *yvals; - float co[3], co2[3], p[3], m[4][4]; + float co[3], co2[3], p[3], map[4][4], bottom_corner[3], top_corner[3]; float *prof_co, *prof_co_k; float r; bool need_2, map_ok; Profile *pro = &bndv->profile; + ProfileSpacing *pro_spacing = (miter) ? &bp->pro_spacing_miter : &bp->pro_spacing; if (bp->seg == 1) { return; @@ -1640,27 +1720,39 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) need_2 = bp->seg != bp->pro_spacing.seg_2; if (!pro->prof_co) { - pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena, (bp->seg + 1) * 3 * sizeof(float)); + pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena, + ((size_t)bp->seg + 1) * 3 * sizeof(float)); if (need_2) { pro->prof_co_2 = (float *)BLI_memarena_alloc( - bp->mem_arena, (bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float)); + bp->mem_arena, ((size_t)bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float)); } else { pro->prof_co_2 = pro->prof_co; } } r = pro->super_r; - if (r == PRO_LINE_R) { + if (!bp->use_custom_profile && r == PRO_LINE_R) { map_ok = false; } else { - map_ok = make_unit_square_map(pro->coa, pro->midco, pro->cob, m); - } + map_ok = make_unit_square_map(pro->start, pro->middle, pro->end, map); + } + if (bp->vmesh_method == BEVEL_VMESH_CUTOFF && map_ok) { + /* Calculate the "height" of the profile by putting the (0,0) and (1,1) corners of the + * un-transformed profile throught the 2D->3D map and calculating the distance between them */ + zero_v3(p); + mul_v3_m4v3(bottom_corner, map, p); + p[0] = 1.0f; + p[1] = 1.0f; + mul_v3_m4v3(top_corner, map, p); + pro->height = len_v3v3(bottom_corner, top_corner); + } + /* The first iteration is the nseg case, the second is the seg_2 case (if it's needed) */ for (i = 0; i < 2; i++) { if (i == 0) { ns = bp->seg; - xvals = bp->pro_spacing.xvals; - yvals = bp->pro_spacing.yvals; + xvals = pro_spacing->xvals; + yvals = pro_spacing->yvals; prof_co = pro->prof_co; } else { @@ -1668,33 +1760,42 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) break; /* shares coords with pro->prof_co */ } ns = bp->pro_spacing.seg_2; - xvals = bp->pro_spacing.xvals_2; - yvals = bp->pro_spacing.yvals_2; + xvals = pro_spacing->xvals_2; + yvals = pro_spacing->yvals_2; prof_co = pro->prof_co_2; } - BLI_assert((r == PRO_LINE_R || (xvals != NULL && yvals != NULL)) && prof_co != NULL); + + /* Iterate over the vertices along the boundary arc */ for (k = 0; k <= ns; k++) { if (k == 0) { - copy_v3_v3(co, pro->coa); + copy_v3_v3(co, pro->start); } else if (k == ns) { - copy_v3_v3(co, pro->cob); + copy_v3_v3(co, pro->end); } else { if (map_ok) { - p[0] = xvals[k]; - p[1] = yvals[k]; + if (reversed) { + p[0] = (float)yvals[ns - k]; + p[1] = (float)xvals[ns - k]; + } + else { + p[0] = (float)xvals[k]; + p[1] = (float)yvals[k]; + } p[2] = 0.0f; - mul_v3_m4v3(co, m, p); + /* Do the 2D->3D transformation of the profile coordinates */ + mul_v3_m4v3(co, map, p); } else { - interp_v3_v3v3(co, pro->coa, pro->cob, (float)k / (float)ns); + interp_v3_v3v3(co, pro->start, pro->end, (float)k / (float)ns); } } - /* project co onto final profile plane */ - prof_co_k = prof_co + 3 * k; + /* Finish the 2D->3D transformation by projecting onto the final profile plane */ + prof_co_k = prof_co + 3 * k; /* Each coord takes up 3 spaces */ if (!is_zero_v3(pro->proj_dir)) { add_v3_v3v3(co2, co, pro->proj_dir); + /* pro->plane_co and pro->plane_no are filled in "set_profile_params" */ if (!isect_line_plane_v3(prof_co_k, co, co2, pro->plane_co, pro->plane_no)) { /* shouldn't happen */ copy_v3_v3(prof_co_k, co); @@ -1708,11 +1809,12 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) } /* Snap a direction co to a superellipsoid with parameter super_r. - * For square profiles, midline says whether or not to snap to both planes. */ + * For square profiles, midline says whether or not to snap to both planes. + * + * Only currently used for the pipe and cube corner special cases */ static void snap_to_superellipsoid(float co[3], const float super_r, bool midline) { float a, b, c, x, y, z, r, rinv, dx, dy; - r = super_r; if (r == PRO_CIRCLE_R) { normalize_v3(co); @@ -1936,7 +2038,7 @@ static void bevel_extend_edge_data(BevVert *bv) } while (bcur != start); } -/* Mark edges as sharp if they are between a smooth recon face and a new face. */ +/* Mark edges as sharp if they are between a smooth reconstructed face and a new face. */ static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp) { BMIter fiter, liter; @@ -1972,7 +2074,7 @@ static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp) * And at boundaries between #F_EDGE and #F_VERT faces, the normals should match the #F_EDGE ones. * Assumes custom loop normals are in use. */ -static void bevel_harden_normals(BMesh *bm, BevelParams *bp) +static void bevel_harden_normals(BevelParams *bp, BMesh *bm) { BMIter liter, fiter; BMFace *f; @@ -2207,13 +2309,17 @@ static bool eh_on_plane(EdgeHalf *e) /* Calculate the profiles for all the BoundVerts of VMesh vm */ static void calculate_vm_profiles(BevelParams *bp, BevVert *bv, VMesh *vm) { - BoundVert *v; + BoundVert *bndv; - v = vm->boundstart; + bndv = vm->boundstart; do { - set_profile_params(bp, bv, v); - calculate_profile(bp, v); - } while ((v = v->next) != vm->boundstart); + set_profile_params(bp, bv, bndv); + /* Use the miter profile spacing struct if the default is filled with the custom profile. */ + bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start); + /* Don't bother reversing the profile if it's a miter profile */ + bool reverse_profile = !bndv->is_profile_start && !miter_profile; + calculate_profile(bp, bndv, reverse_profile, miter_profile); + } while ((bndv = bndv->next) != vm->boundstart); } /* Implements build_boundary for vertex-only case */ @@ -2263,14 +2369,15 @@ static void build_boundary_vertex_only(BevelParams *bp, BevVert *bv, bool constr static void build_boundary_terminal_edge(BevelParams *bp, BevVert *bv, EdgeHalf *efirst, - bool construct) + const bool construct) { MemArena *mem_arena = bp->mem_arena; VMesh *vm = bv->vmesh; - BoundVert *v; + BoundVert *bndv; EdgeHalf *e; const float *no; float co[3], d; + bool use_tri_fan; e = efirst; if (bv->edgecount == 2) { @@ -2278,9 +2385,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, 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; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = bndv->ebev = e; + e->leftv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2288,9 +2395,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, 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; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e; + e->rightv = bndv; } else { adjust_bound_vert(e->rightv, co); @@ -2298,11 +2405,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, /* make artificial 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; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e->next; + e->next->leftv = e->next->rightv = bndv; set_bound_vert_seams(bv, bp->mark_seam, bp->mark_sharp); } else { @@ -2316,11 +2421,11 @@ static void build_boundary_terminal_edge(BevelParams *bp, /* 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 = e->prev->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = e->prev; + bndv->elast = bndv->ebev = e; + e->leftv = bndv; + e->prev->leftv = e->prev->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2328,23 +2433,26 @@ static void build_boundary_terminal_edge(BevelParams *bp, 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 = e->rightv = v; - e->prev->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = e->prev; + bndv->elast = e; + e->leftv = e->rightv = bndv; + e->prev->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); } /* For the edges not adjacent to the beveled edge, slide the bevel amount along. */ d = efirst->offset_l_spec; + if (bp->use_custom_profile || bp->profile < 0.25f) { + d *= sqrtf(2.0f); /* Need to go further along the edge to make room for full profile area. */ + } 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 = e->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e; + e->leftv = e->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2354,11 +2462,12 @@ static void build_boundary_terminal_edge(BevelParams *bp, 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, bv->v); - calculate_profile(bp, v); + /* Special case: snap profile to plane of adjacent two edges. */ + bndv = vm->boundstart; + BLI_assert(bndv->ebev != NULL); + move_profile_plane(bndv, bv->v); + /* This step happens before the profile orientation pass so don't reverse the profile. */ + calculate_profile(bp, bndv, false, false); } if (construct) { @@ -2368,12 +2477,34 @@ static void build_boundary_terminal_edge(BevelParams *bp, vm->mesh_kind = M_NONE; } else if (vm->count == 3) { - vm->mesh_kind = M_TRI_FAN; + use_tri_fan = true; + if (bp->use_custom_profile) { + /* Use M_POLY if the extra point is planar with the profile to prevent overhanging edges */ + bndv = efirst->leftv; + float profile_plane[4]; + plane_from_point_normal_v3(profile_plane, bndv->profile.plane_co, bndv->profile.plane_no); + bndv = efirst->rightv->next; /* The added boundvert placed along the non-adjacent edge */ + if (dist_squared_to_plane_v3(bndv->nv.co, profile_plane) < BEVEL_EPSILON_BIG) { + use_tri_fan = false; + } + } + vm->mesh_kind = (use_tri_fan) ? M_TRI_FAN : M_POLY; } else { vm->mesh_kind = M_POLY; } } +#ifdef DEBUG_CUSTOM_PROFILE_WELD + if (bp->seg > 1) { + printf("Terminal Edge Profile Coordinates:\n"); + for (int k = 0; k < bp->seg; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)vm->boundstart->profile.prof_co[3 * k], + (double)vm->boundstart->profile.prof_co[3 * k + 1], + (double)vm->boundstart->profile.prof_co[3 * k + 2]); + } + } +#endif } /* Helper for build_boundary to handle special miters */ @@ -2399,7 +2530,7 @@ static void adjust_miter_coords(BevelParams *bp, BevVert *bv, EdgeHalf *emiter) v3next = v3->next; copy_v3_v3(co2, v1->nv.co); if (v1->is_arc_start) { - copy_v3_v3(v1->profile.midco, co2); + copy_v3_v3(v1->profile.middle, co2); } /* co1 is intersection of line through co2 in dir of emiter->e @@ -2477,7 +2608,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) BoundVert *v, *v1, *v2, *v3; VMesh *vm; float co[3], r; - int nip, nnip, miter_outer, miter_inner; + int in_plane, not_in_plane, miter_outer, miter_inner; int ang_kind; /* Current bevel does nothing if only one edge into a vertex */ @@ -2497,7 +2628,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) BLI_assert(e->is_bev); if (bv->selcount == 1) { - /* special case: only one beveled edge in */ + /* Special case: only one beveled edge in */ build_boundary_terminal_edge(bp, bv, efirst, construct); return; } @@ -2509,7 +2640,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) /* keep track of the first beveled edge of an outside miter (there can be at most 1 per bv */ emiter = NULL; - /* Here: there is more than one beveled edge. + /* 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; @@ -2519,26 +2650,26 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) /* 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 */ + * They are either "in plane" (preceding and subsequent faces are coplanar) or not. + * The "non-in-plane" edges affect the silhouette and we prefer to slide along one of those if + * possible. */ + in_plane = not_in_plane = 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++; + in_plane++; eip = e2; } else { - nnip++; + not_in_plane++; enip = e2; } } - if (nip == 0 && nnip == 0) { + if (in_plane == 0 && not_in_plane == 0) { offset_meet(e, e2, bv->v, e->fnext, false, co); } - else if (nnip > 0) { - if (bp->loop_slide && nnip == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) { + else if (not_in_plane > 0) { + if (bp->loop_slide && not_in_plane == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) { if (offset_on_edge_between(e, e2, enip, bv->v, co, &r)) { eon = enip; } @@ -2548,8 +2679,8 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } } else { - /* nip > 0 and nnip == 0 */ - if (bp->loop_slide && nip == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) { + /* n_in_plane > 0 and n_not_in_plane == 0 */ + if (bp->loop_slide && in_plane == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) { if (offset_on_edge_between(e, e2, eip, bv->v, co, &r)) { eon = eip; } @@ -2579,16 +2710,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) * There can only be one outer reflex angle, so only one outer miter, * and emiter will be set to the first edge of such an edge. * A miter kind of BEVEL_MITER_SHARP means no special miter */ - - if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) || - (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) { - if (ang_kind == 1) { + if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) || + (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) { + if (ang_kind == ANGLE_LARGER) { emiter = e; } /* make one or two more boundverts; for now all will have same co */ v1 = v; v1->ebev = NULL; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v2 = add_new_bound_vert(mem_arena, vm, co); } else { @@ -2600,7 +2730,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) v3->elast = e2; v3->eon = NULL; e2->leftv = v3; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v1->is_patch_start = true; v2->eon = v1->eon; v2->sinratio = v1->sinratio; @@ -2622,17 +2752,16 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } else { v1->is_arc_start = true; - copy_v3_v3(v1->profile.midco, co); + copy_v3_v3(v1->profile.middle, co); if (e->next == e2) { v1->elast = v1->efirst; } else { - int between = nip + nnip; + int between = in_plane + not_in_plane; int bet2 = between / 2; bool betodd = (between % 2) == 1; int i = 0; - /* Put first half of in-between edges at index 0, - * second half at index bp->seg. + /* Put first half of in-between edges at index 0, second half at index bp->seg. * If between is odd, put middle one at midindex */ for (e3 = e->next; e3 != e2; e3 = e3->next) { v1->elast = e3; @@ -2651,15 +2780,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } } } - else { + else { /* construct == false */ ang_kind = edges_angle_kind(e, e2, bv->v); - if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) || - (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) { - if (ang_kind == 1) { + if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) || + (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) { + if (ang_kind == ANGLE_LARGER) { emiter = e; } v1 = e->rightv; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v2 = v1->next; v3 = v2->next; } @@ -2699,7 +2828,14 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) vm->mesh_kind = M_POLY; } else { - vm->mesh_kind = M_ADJ; + switch (bp->vmesh_method) { + case BEVEL_VMESH_ADJ: + vm->mesh_kind = M_ADJ; + break; + case BEVEL_VMESH_CUTOFF: + vm->mesh_kind = M_CUTOFF; + break; + } } } } @@ -2885,6 +3021,194 @@ static bool adjust_the_cycle_or_chain_fast(BoundVert *vstart, int np, bool iscyc } #endif +/** Helper function to return the next Beveled EdgeHalf along a path. + * \param toward_bv Whether the direction to travel points toward or away from the BevVert + * connected to the current EdgeHalf + * \param r_bv The BevVert conencted to the EdgeHalf-- updated if we're travelling to the other + * EdgeHalf of an original edge + * \note This only returns the most parallel edge if it's the most parallel by + * at least 10 degrees. This is a somewhat arbitrary choice, but it makes sure that consistent + * orientation paths only continue in obvious ways. */ +static EdgeHalf *next_edgehalf_bev(BevelParams *bp, + EdgeHalf *start_edge, + bool toward_bv, + BevVert **r_bv) +{ + EdgeHalf *new_edge; + EdgeHalf *next_edge = NULL; + float dir_start_edge[3], dir_new_edge[3]; + float second_best_dot = 0.0f, best_dot = 0.0f; + float new_dot; + + /* Case 1: The next EdgeHalf is across a BevVert from the current EdgeHalf. */ + if (toward_bv) { + /* Skip all the logic if there's only one beveled edge at the vertex, we're at an end. */ + if ((*r_bv)->selcount == 1) { + return NULL; /* No other edges to go to. */ + } + + /* The case with only one other edge connected to the vertex is special too. */ + if ((*r_bv)->selcount == 2) { + /* Just find the next beveled edge, that's the only other option. */ + new_edge = start_edge; + do { + new_edge = new_edge->next; + } while (!new_edge->is_bev); + + return new_edge; + } + + /* Find the direction vector of the current edge (pointing INTO the BevVert). + * v1 and v2 don't necessarily have an order, so we need to check which is closer to bv. */ + if (start_edge->e->v1 == (*r_bv)->v) { + sub_v3_v3v3(dir_start_edge, start_edge->e->v1->co, start_edge->e->v2->co); + } + else { + sub_v3_v3v3(dir_start_edge, start_edge->e->v2->co, start_edge->e->v1->co); + } + normalize_v3(dir_start_edge); + + /* Find the beveled edge coming out of the BevVert that's most parallel to the current edge. */ + new_edge = start_edge->next; + while (new_edge != start_edge) { + if (!new_edge->is_bev) { + new_edge = new_edge->next; + continue; + } + /* Find direction vector of the possible next edge (pointing OUT of the BevVert). */ + if (new_edge->e->v2 == (*r_bv)->v) { + sub_v3_v3v3(dir_new_edge, new_edge->e->v1->co, new_edge->e->v2->co); + } + else { + sub_v3_v3v3(dir_new_edge, new_edge->e->v2->co, new_edge->e->v1->co); + } + normalize_v3(dir_new_edge); + + /* Use this edge if it is the most parallel to the orignial so far */ + new_dot = dot_v3v3(dir_new_edge, dir_start_edge); + if (new_dot > best_dot) { + second_best_dot = best_dot; /* For remembering if the choice was too close. */ + best_dot = new_dot; + next_edge = new_edge; + } + else if (new_dot > second_best_dot) { + second_best_dot = new_dot; + } + + new_edge = new_edge->next; + } + + /* Only return a new Edge if one was found and if the choice of next edge was not too close. */ + if ((next_edge != NULL) && compare_ff(best_dot, second_best_dot, BEVEL_SMALL_ANG_DOT)) { + return NULL; + } + else { + return next_edge; + } + } + else { + /* Case 2: The next EdgeHalf is the other side of the BMEdge. + * It's part of the same BMEdge, so we know the other EdgeHalf is also beveled. */ + next_edge = find_other_end_edge_half(bp, start_edge, r_bv); + return next_edge; + } +} + +/** Starting along any beveled edge, travel along the chain / cycle of beveled edges including that + * edge, marking consistent profile orientations along the way. Orientations are marked by setting + * whether the BoundVert that contains each profile's information is the side of the profile's + * start or not. */ +static void regularize_profile_orientation(BevelParams *bp, BMEdge *bme) +{ + BevVert *start_bv; + BevVert *bv; + EdgeHalf *start_edgehalf, *edgehalf; + bool toward_bv; + + start_bv = find_bevvert(bp, bme->v1); + start_edgehalf = find_edge_half(start_bv, bme); + if (!start_edgehalf->is_bev || start_edgehalf->visited_rpo) { + return; + } + + /* Pick a BoundVert on one side of the profile to use for the start of the profile. */ + start_edgehalf->leftv->is_profile_start = false; + start_edgehalf->visited_rpo = true; + + /* First loop starts in the away from BevVert direction and the second starts toward it. */ + for (int i = 0; i < 2; i++) { + edgehalf = start_edgehalf; + bv = start_bv; + toward_bv = (i == 0); + edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv); + + /* Keep traveling until there is no unvisited beveled edgehalf to visit next. */ + while (edgehalf && !edgehalf->visited_rpo) { + /* Mark the correct BoundVert as the start of the newly visited profile. + * The direction relative to the BevVert switches every step, so also switch + * the orientation every step. */ + if (i == 0) { + edgehalf->leftv->is_profile_start = toward_bv; + } + else { + /* The opposite side as the first direction because we're moving the other way. */ + edgehalf->leftv->is_profile_start = !toward_bv; + } + + /* The next jump will in the opposite direction relative to the BevVert. */ + toward_bv = !toward_bv; + + edgehalf->visited_rpo = true; + edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv); + } + } +} + +#ifdef DEBUG_PROFILE_ORIENTATION_DRAW +/** Draws markers on beveled edges showing the side that the profile starts on. A sphere shows + * the start side of the profile where it starts, and the lines connected to the sphere show which + * edge the orientation corresponds to. + * \note Only drawn while bevel is calculating, the debug geometry is not persistent. */ +static void debug_draw_profile_orientation(BevelParams *bp, BMesh *bm) +{ + BMIter iter; + BMEdge *bmedge; + float middle[3]; + + BM_ITER_MESH (bmedge, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(bmedge, BM_ELEM_TAG)) { + mid_v3_v3v3(middle, bmedge->v1->co, bmedge->v2->co); + + /* Draw the orientation for the first side of the edge. */ + EdgeHalf *edge_half = find_edge_half(find_bevvert(bp, bmedge->v1), bmedge); + if (edge_half->leftv->is_profile_start) { /* The left boundvert defines the profiles. */ + DRW_debug_sphere(edge_half->leftv->nv.co, 0.04f, debug_color_red); + DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_red); + DRW_debug_line_v3v3(bmedge->v1->co, edge_half->leftv->nv.co, debug_color_red); + } + else { + DRW_debug_sphere(edge_half->rightv->nv.co, 0.04f, debug_color_red); + DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_red); + DRW_debug_line_v3v3(bmedge->v1->co, edge_half->rightv->nv.co, debug_color_red); + } + + /* Draw the orientation for the second side of the edge. */ + edge_half = find_edge_half(find_bevvert(bp, bmedge->v2), bmedge); + if (edge_half->leftv->is_profile_start) { + DRW_debug_sphere(edge_half->leftv->nv.co, 0.05f, debug_color_blue); + DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_blue); + DRW_debug_line_v3v3(bmedge->v2->co, edge_half->leftv->nv.co, debug_color_blue); + } + else { + DRW_debug_sphere(edge_half->rightv->nv.co, 0.05f, debug_color_blue); + DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_blue); + DRW_debug_line_v3v3(bmedge->v2->co, edge_half->rightv->nv.co, debug_color_blue); + } + } + } +} +#endif + /* Adjust the offsets for a single cycle or chain. * For chains and some cycles, a fast solution exists. * Otherwise, we set up and solve a linear least squares problem @@ -3218,8 +3542,8 @@ static VMesh *new_adj_vmesh(MemArena *mem_arena, int count, int seg, BoundVert * vm->count = count; vm->seg = seg; vm->boundstart = bounds; - vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena, - count * (1 + seg / 2) * (1 + seg) * sizeof(NewVert)); + vm->mesh = (NewVert *)BLI_memarena_alloc( + mem_arena, (size_t)(count * (1 + seg / 2) * (1 + seg)) * sizeof(NewVert)); vm->mesh_kind = M_ADJ; return vm; } @@ -3261,10 +3585,10 @@ static NewVert *mesh_vert_canon(VMesh *vm, int i, int j, int k) static bool is_canon(VMesh *vm, int i, int j, int k) { int ns2 = vm->seg / 2; - if (vm->seg % 2 == 1) { + if (vm->seg % 2 == 1) { /* odd */ return (j <= ns2 && k <= ns2); } - else { + else { /* even */ return ((j < ns2 && k <= ns2) || (j == ns2 && k == ns2 && i == 0)); } } @@ -3296,6 +3620,9 @@ static void vmesh_copy_equiv_verts(VMesh *vm) /* Calculate and return in r_cent the centroid of the center poly */ static void vmesh_center(VMesh *vm, float r_cent[3]) { +#ifdef DEBUG_CUSTOM_PROFILE_ADJ + printf("VMESH CENTER\n"); +#endif int n, ns2, i; n = vm->count; @@ -3432,182 +3759,193 @@ static int interp_range(const float *frac, int n, const float f, float *r_rest) } /* Interpolate given vmesh to make one with target nseg border vertices on the profiles */ -static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm0, int nseg) +/* HANS-TODO: This puts the center mesh vert at a slightly off location sometimes, which seems to + * be associated with the rest of that ring being shifted or connected slightly incorrectly to its + * neighbors */ +static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm_in, int nseg) { - int n, ns0, nseg2, odd, i, j, k, j0, k0, k0prev, j0inc, k0inc; + int n_bndv, ns_in, nseg2, odd, i, j, k, j_in, k_in, k_in_prev, j0inc, k0inc; float *prev_frac, *frac, *new_frac, *prev_new_frac; - float f, restj, restk, restkprev; + float fraction, restj, restk, restkprev; float quad[4][3], co[3], center[3]; - VMesh *vm1; + VMesh *vm_out; BoundVert *bndv; - n = vm0->count; - ns0 = vm0->seg; + n_bndv = vm_in->count; + ns_in = vm_in->seg; nseg2 = nseg / 2; odd = nseg % 2; - vm1 = new_adj_vmesh(bp->mem_arena, n, nseg, vm0->boundstart); + vm_out = new_adj_vmesh(bp->mem_arena, n_bndv, nseg, vm_in->boundstart); - prev_frac = BLI_array_alloca(prev_frac, (ns0 + 1)); - frac = BLI_array_alloca(frac, (ns0 + 1)); + prev_frac = BLI_array_alloca(prev_frac, (ns_in + 1)); + frac = BLI_array_alloca(frac, (ns_in + 1)); new_frac = BLI_array_alloca(new_frac, (nseg + 1)); prev_new_frac = BLI_array_alloca(prev_new_frac, (nseg + 1)); - fill_vmesh_fracs(vm0, prev_frac, n - 1); - bndv = vm0->boundstart; + fill_vmesh_fracs(vm_in, prev_frac, n_bndv - 1); + bndv = vm_in->boundstart; fill_profile_fracs(bp, bndv->prev, prev_new_frac, nseg); - for (i = 0; i < n; i++) { - fill_vmesh_fracs(vm0, frac, i); + for (i = 0; i < n_bndv; i++) { + fill_vmesh_fracs(vm_in, frac, i); fill_profile_fracs(bp, bndv, new_frac, nseg); for (j = 0; j <= nseg2 - 1 + odd; j++) { for (k = 0; k <= nseg2; k++) { - f = new_frac[k]; - k0 = interp_range(frac, ns0, f, &restk); - f = prev_new_frac[nseg - j]; - k0prev = interp_range(prev_frac, ns0, f, &restkprev); - j0 = ns0 - k0prev; + /* Finding the locations where "fraction" fits into previous and current "frac" */ + fraction = new_frac[k]; + k_in = interp_range(frac, ns_in, fraction, &restk); + fraction = prev_new_frac[nseg - j]; + k_in_prev = interp_range(prev_frac, ns_in, fraction, &restkprev); + j_in = ns_in - k_in_prev; restj = -restkprev; if (restj > -BEVEL_EPSILON) { restj = 0.0f; } else { - j0 = j0 - 1; + j_in = j_in - 1; restj = 1.0f + restj; } /* Use bilinear interpolation within the source quad; could be smarter here */ if (restj < BEVEL_EPSILON && restk < BEVEL_EPSILON) { - copy_v3_v3(co, mesh_vert_canon(vm0, i, j0, k0)->co); + copy_v3_v3(co, mesh_vert_canon(vm_in, i, j_in, k_in)->co); } else { - j0inc = (restj < BEVEL_EPSILON || j0 == ns0) ? 0 : 1; - k0inc = (restk < BEVEL_EPSILON || k0 == ns0) ? 0 : 1; - copy_v3_v3(quad[0], mesh_vert_canon(vm0, i, j0, k0)->co); - copy_v3_v3(quad[1], mesh_vert_canon(vm0, i, j0, k0 + k0inc)->co); - copy_v3_v3(quad[2], mesh_vert_canon(vm0, i, j0 + j0inc, k0 + k0inc)->co); - copy_v3_v3(quad[3], mesh_vert_canon(vm0, i, j0 + j0inc, k0)->co); + j0inc = (restj < BEVEL_EPSILON || j_in == ns_in) ? 0 : 1; + k0inc = (restk < BEVEL_EPSILON || k_in == ns_in) ? 0 : 1; + copy_v3_v3(quad[0], mesh_vert_canon(vm_in, i, j_in, k_in)->co); + copy_v3_v3(quad[1], mesh_vert_canon(vm_in, i, j_in, k_in + k0inc)->co); + copy_v3_v3(quad[2], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in + k0inc)->co); + copy_v3_v3(quad[3], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in)->co); interp_bilinear_quad_v3(quad, restk, restj, co); } - copy_v3_v3(mesh_vert(vm1, i, j, k)->co, co); + copy_v3_v3(mesh_vert(vm_out, i, j, k)->co, co); } } bndv = bndv->next; - memcpy(prev_frac, frac, (ns0 + 1) * sizeof(float)); - memcpy(prev_new_frac, new_frac, (nseg + 1) * sizeof(float)); + memcpy(prev_frac, frac, (size_t)(ns_in + 1) * sizeof(float)); + memcpy(prev_new_frac, new_frac, (size_t)(nseg + 1) * sizeof(float)); } if (!odd) { - vmesh_center(vm0, center); - copy_v3_v3(mesh_vert(vm1, 0, nseg2, nseg2)->co, center); + vmesh_center(vm_in, center); + copy_v3_v3(mesh_vert(vm_out, 0, nseg2, nseg2)->co, center); } - vmesh_copy_equiv_verts(vm1); - return vm1; + vmesh_copy_equiv_verts(vm_out); + return vm_out; } /* Do one step of cubic subdivision (Catmull-Clark), with special rules at boundaries. * For now, this is written assuming vm0->nseg is even and > 0. - * We are allowed to modify vm0, as it will not be used after this call. + * We are allowed to modify vm_in, as it will not be used after this call. * See Levin 1999 paper: "Filling an N-sided hole using combined subdivision schemes". */ -static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0) +static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm_in) { - int n, ns0, ns20, ns1; + int n_boundary, ns_in, ns_in2, ns_out; int i, j, k, inext; float co[3], co1[3], co2[3], acc[3]; float beta, gamma; - VMesh *vm1; + VMesh *vm_out; BoundVert *bndv; - n = vm0->count; - ns0 = vm0->seg; - ns20 = ns0 / 2; - BLI_assert(ns0 % 2 == 0); - ns1 = 2 * ns0; - vm1 = new_adj_vmesh(bp->mem_arena, n, ns1, vm0->boundstart); + n_boundary = vm_in->count; + ns_in = vm_in->seg; + ns_in2 = ns_in / 2; + BLI_assert(ns_in % 2 == 0); + ns_out = 2 * ns_in; + vm_out = new_adj_vmesh(bp->mem_arena, n_boundary, ns_out, vm_in->boundstart); - /* First we adjust the boundary vertices of the input mesh, storing in output mesh */ - for (i = 0; i < n; i++) { - copy_v3_v3(mesh_vert(vm1, i, 0, 0)->co, mesh_vert(vm0, i, 0, 0)->co); - for (k = 1; k < ns0; k++) { - /* smooth boundary rule */ - copy_v3_v3(co, mesh_vert(vm0, i, 0, k)->co); - copy_v3_v3(co1, mesh_vert(vm0, i, 0, k - 1)->co); - copy_v3_v3(co2, mesh_vert(vm0, i, 0, k + 1)->co); + /* First we adjust the boundary vertices of the input mesh, storing in output mesh. */ + for (i = 0; i < n_boundary; i++) { + copy_v3_v3(mesh_vert(vm_out, i, 0, 0)->co, mesh_vert(vm_in, i, 0, 0)->co); + for (k = 1; k < ns_in; k++) { + copy_v3_v3(co, mesh_vert(vm_in, i, 0, k)->co); - add_v3_v3v3(acc, co1, co2); - madd_v3_v3fl(acc, co, -2.0f); - madd_v3_v3fl(co, acc, -1.0f / 6.0f); + /* Smooth boundary rule. Custom profiles shouldn't be smoothed. */ + if (!bp->use_custom_profile) { + copy_v3_v3(co1, mesh_vert(vm_in, i, 0, k - 1)->co); + copy_v3_v3(co2, mesh_vert(vm_in, i, 0, k + 1)->co); - copy_v3_v3(mesh_vert_canon(vm1, i, 0, 2 * k)->co, co); + add_v3_v3v3(acc, co1, co2); + madd_v3_v3fl(acc, co, -2.0f); + madd_v3_v3fl(co, acc, -1.0f / 6.0f); + } + + copy_v3_v3(mesh_vert_canon(vm_out, i, 0, 2 * k)->co, co); } } - /* now do odd ones in output mesh, based on even ones */ - bndv = vm1->boundstart; - for (i = 0; i < n; i++) { - for (k = 1; k < ns1; k += 2) { - get_profile_point(bp, &bndv->profile, k, ns1, co); - copy_v3_v3(co1, mesh_vert_canon(vm1, i, 0, k - 1)->co); - copy_v3_v3(co2, mesh_vert_canon(vm1, i, 0, k + 1)->co); + /* Now adjust odd boundary vertices in output mesh, based on even ones. */ + bndv = vm_out->boundstart; + for (i = 0; i < n_boundary; i++) { + for (k = 1; k < ns_out; k += 2) { + get_profile_point(bp, &bndv->profile, k, ns_out, co); - add_v3_v3v3(acc, co1, co2); - madd_v3_v3fl(acc, co, -2.0f); - madd_v3_v3fl(co, acc, -1.0f / 6.0f); + /* Smooth if using a non-custom profile. */ + if (!bp->use_custom_profile) { + copy_v3_v3(co1, mesh_vert_canon(vm_out, i, 0, k - 1)->co); + copy_v3_v3(co2, mesh_vert_canon(vm_out, i, 0, k + 1)->co); - copy_v3_v3(mesh_vert_canon(vm1, i, 0, k)->co, co); + add_v3_v3v3(acc, co1, co2); + madd_v3_v3fl(acc, co, -2.0f); + madd_v3_v3fl(co, acc, -1.0f / 6.0f); + } + + copy_v3_v3(mesh_vert_canon(vm_out, i, 0, k)->co, co); } bndv = bndv->next; } - vmesh_copy_equiv_verts(vm1); + vmesh_copy_equiv_verts(vm_out); - /* Copy adjusted verts back into vm0 */ - for (i = 0; i < n; i++) { - for (k = 0; k < ns0; k++) { - copy_v3_v3(mesh_vert(vm0, i, 0, k)->co, mesh_vert(vm1, i, 0, 2 * k)->co); + /* Copy adjusted verts back into vm_in. */ + for (i = 0; i < n_boundary; i++) { + for (k = 0; k < ns_in; k++) { + copy_v3_v3(mesh_vert(vm_in, i, 0, k)->co, mesh_vert(vm_out, i, 0, 2 * k)->co); } } - vmesh_copy_equiv_verts(vm0); + vmesh_copy_equiv_verts(vm_in); /* Now we do the internal vertices, using standard Catmull-Clark * and assuming all boundary vertices have valence 4 */ /* The new face vertices */ - for (i = 0; i < n; i++) { - for (j = 0; j < ns20; j++) { - for (k = 0; k < ns20; k++) { + for (i = 0; i < n_boundary; i++) { + for (j = 0; j < ns_in2; j++) { + for (k = 0; k < ns_in2; k++) { /* face up and right from (j, k) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j, k + 1), - mesh_vert(vm0, i, j + 1, k), - mesh_vert(vm0, i, j + 1, k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k + 1)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j, k + 1), + mesh_vert(vm_in, i, j + 1, k), + mesh_vert(vm_in, i, j + 1, k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k + 1)->co, co); } } } - /* The new vertical edge vertices */ - for (i = 0; i < n; i++) { - for (j = 0; j < ns20; j++) { - for (k = 1; k <= ns20; k++) { + /* The new vertical edge vertices */ + for (i = 0; i < n_boundary; i++) { + for (j = 0; j < ns_in2; j++) { + for (k = 1; k <= ns_in2; k++) { /* vertical edge between (j, k) and (j+1, k) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j + 1, k), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j + 1, k), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k)->co, co); } } } - /* The new horizontal edge vertices */ - for (i = 0; i < n; i++) { - for (j = 1; j < ns20; j++) { - for (k = 0; k < ns20; k++) { + /* The new horizontal edge vertices */ + for (i = 0; i < n_boundary; i++) { + for (j = 1; j < ns_in2; j++) { + for (k = 0; k < ns_in2; k++) { /* horizontal edge between (j, k) and (j, k+1) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j, k + 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k + 1)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j, k + 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k + 1)->co, co); } } } @@ -3615,66 +3953,66 @@ static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0) /* The new vertices, not on border */ gamma = 0.25f; beta = -gamma; - for (i = 0; i < n; i++) { - for (j = 1; j < ns20; j++) { - for (k = 1; k <= ns20; k++) { + for (i = 0; i < n_boundary; i++) { + for (j = 1; j < ns_in2; j++) { + for (k = 1; k <= ns_in2; k++) { /* co1 = centroid of adjacent new edge verts */ avg4(co1, - mesh_vert_canon(vm1, i, 2 * j, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k)); + mesh_vert_canon(vm_out, i, 2 * j, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k)); /* co2 = centroid of adjacent new face verts */ avg4(co2, - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); /* combine with original vert with alpha, beta, gamma factors */ copy_v3_v3(co, co1); /* alpha = 1.0 */ madd_v3_v3fl(co, co2, beta); - madd_v3_v3fl(co, mesh_vert(vm0, i, j, k)->co, gamma); - copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k)->co, co); + madd_v3_v3fl(co, mesh_vert(vm_in, i, j, k)->co, gamma); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k)->co, co); } } } - vmesh_copy_equiv_verts(vm1); + vmesh_copy_equiv_verts(vm_out); /* The center vertex is special */ - gamma = sabin_gamma(n); + gamma = sabin_gamma(n_boundary); beta = -gamma; /* accumulate edge verts in co1, face verts in co2 */ zero_v3(co1); zero_v3(co2); - for (i = 0; i < n; i++) { - add_v3_v3(co1, mesh_vert(vm1, i, ns0, ns0 - 1)->co); - add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 - 1)->co); - add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 + 1)->co); + for (i = 0; i < n_boundary; i++) { + add_v3_v3(co1, mesh_vert(vm_out, i, ns_in, ns_in - 1)->co); + add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in - 1)->co); + add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in + 1)->co); } copy_v3_v3(co, co1); - mul_v3_fl(co, 1.0f / (float)n); - madd_v3_v3fl(co, co2, beta / (2.0f * (float)n)); - madd_v3_v3fl(co, mesh_vert(vm0, 0, ns20, ns20)->co, gamma); - for (i = 0; i < n; i++) { - copy_v3_v3(mesh_vert(vm1, i, ns0, ns0)->co, co); + mul_v3_fl(co, 1.0f / (float)n_boundary); + madd_v3_v3fl(co, co2, beta / (2.0f * (float)n_boundary)); + madd_v3_v3fl(co, mesh_vert(vm_in, 0, ns_in2, ns_in2)->co, gamma); + for (i = 0; i < n_boundary; i++) { + copy_v3_v3(mesh_vert(vm_out, i, ns_in, ns_in)->co, co); } - /* Final step: sample the boundary vertices at even parameter spacing */ - bndv = vm1->boundstart; - for (i = 0; i < n; i++) { - inext = (i + 1) % n; - for (k = 0; k <= ns1; k++) { - get_profile_point(bp, &bndv->profile, k, ns1, co); - copy_v3_v3(mesh_vert(vm1, i, 0, k)->co, co); - if (k >= ns0 && k < ns1) { - copy_v3_v3(mesh_vert(vm1, inext, ns1 - k, 0)->co, co); + /* Final step: Copy the profile vertices to the VMesh's boundary */ + bndv = vm_out->boundstart; + for (i = 0; i < n_boundary; i++) { + inext = (i + 1) % n_boundary; + for (k = 0; k <= ns_out; k++) { + get_profile_point(bp, &bndv->profile, k, ns_out, co); + copy_v3_v3(mesh_vert(vm_out, i, 0, k)->co, co); + if (k >= ns_in && k < ns_out) { + copy_v3_v3(mesh_vert(vm_out, inext, ns_out - k, 0)->co, co); } } bndv = bndv->next; } - return vm1; + return vm_out; } /* Special case for cube corner, when r is PRO_SQUARE_R, meaning straight sides */ @@ -3686,7 +4024,7 @@ static VMesh *make_cube_corner_square(MemArena *mem_arena, int nseg) ns2 = nseg / 2; vm = new_adj_vmesh(mem_arena, 3, nseg, NULL); - vm->count = 0; // reset, so following loop will end up with correct count + vm->count = 0; /* Reset, so the following loop will end up with correct count. */ for (i = 0; i < 3; i++) { zero_v3(co); co[i] = 1.0f; @@ -3765,16 +4103,18 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp) int i, j, k, ns2; float co[3], coc[3]; - if (r == PRO_SQUARE_R) { - return make_cube_corner_square(mem_arena, nseg); - } - else if (r == PRO_SQUARE_IN_R) { - return make_cube_corner_square_in(mem_arena, nseg); + if (!bp->use_custom_profile) { + if (r == PRO_SQUARE_R) { + return make_cube_corner_square(mem_arena, nseg); + } + else if (r == PRO_SQUARE_IN_R) { + return make_cube_corner_square_in(mem_arena, nseg); + } } - /* initial mesh has 3 sides, 2 segments */ + /* Initial mesh has 3 sides and 2 segments on each side */ vm0 = new_adj_vmesh(mem_arena, 3, 2, NULL); - vm0->count = 0; // reset, so following loop will end up with correct count + vm0->count = 0; /* Reset, so the following loop will end up with correct count. */ for (i = 0; i < 3; i++) { zero_v3(co); co[i] = 1.0f; @@ -3787,20 +4127,24 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp) coc[(i + 1) % 3] = 1.0f; coc[(i + 2) % 3] = 0.0f; bndv->profile.super_r = r; - copy_v3_v3(bndv->profile.coa, bndv->nv.co); - copy_v3_v3(bndv->profile.cob, bndv->next->nv.co); - copy_v3_v3(bndv->profile.midco, coc); - copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.coa); - copy_v3_v3(bndv->profile.plane_co, bndv->profile.coa); - cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.coa, bndv->profile.cob); + copy_v3_v3(bndv->profile.start, bndv->nv.co); + copy_v3_v3(bndv->profile.end, bndv->next->nv.co); + copy_v3_v3(bndv->profile.middle, coc); + copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.start); + copy_v3_v3(bndv->profile.plane_co, bndv->profile.start); + cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.start, bndv->profile.end); copy_v3_v3(bndv->profile.proj_dir, bndv->profile.plane_no); - calculate_profile(bp, bndv); + /* No need to reverse the profile or use the miter profile spacing struct because this case + * isn't used with custom profiles. */ + calculate_profile(bp, bndv, false, false); + + /* Just building the boundaries here, so sample the profile halfway through */ get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co); bndv = bndv->next; } /* center vertex */ - copy_v3_fl(co, M_SQRT1_3); + copy_v3_fl(co, (float)M_SQRT1_3); if (nseg > 2) { if (r > 1.5f) { @@ -3843,7 +4187,8 @@ static int tri_corner_test(BevelParams *bp, BevVert *bv) int i; int in_plane_e = 0; - if (bp->vertex_only) { + /* The superellipse snapping of this case isn't helpful with custom profiles enabled. */ + if (bp->vertex_only || bp->use_custom_profile) { return -1; } if (bv->vmesh->count != 3) { @@ -3909,94 +4254,71 @@ static VMesh *tri_corner_adj_vmesh(BevelParams *bp, BevVert *bv) return vm; } +/* Makes the mesh that replaces the original vertex, bounded by the profiles on the sides */ static VMesh *adj_vmesh(BevelParams *bp, BevVert *bv) { - int n, ns, i; + int n_bndv, nseg, i; VMesh *vm0, *vm1; - float co[3], coa[3], cob[3], dir[3]; + float boundverts_center[3], original_vertex[3], negative_fullest[3], center_direction[3]; BoundVert *bndv; MemArena *mem_arena = bp->mem_arena; - float r, p, fullness; - /* best fullness for circles, segs = 2,4,6,8,10 */ -#define CIRCLE_FULLNESS_SEGS 11 - static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = { - 0.0f, /* nsegs ==1 */ - 0.559f, /* 2 */ - 0.642f, /* 3 */ - 0.551f, /* 4 */ - 0.646f, /* 5 */ - 0.624f, /* 6 */ - 0.646f, /* 7 */ - 0.619f, /* 8 */ - 0.647f, /* 9 */ - 0.639f, /* 10 */ - 0.647f, /* 11 */ - }; + float fullness; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; /* Same bevel as that of 3 edges of vert in a cube */ - if (n == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) { + if (n_bndv == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) { return tri_corner_adj_vmesh(bp, bv); } - /* First construct an initial control mesh, with nseg==2 */ - ns = bv->vmesh->seg; - vm0 = new_adj_vmesh(mem_arena, n, 2, bv->vmesh->boundstart); + /* First construct an initial control mesh, with nseg == 2. */ + nseg = bv->vmesh->seg; + vm0 = new_adj_vmesh(mem_arena, n_bndv, 2, bv->vmesh->boundstart); + /* Find the center of the boundverts that make up the vmesh. */ bndv = vm0->boundstart; - zero_v3(co); - for (i = 0; i < n; i++) { - /* Boundaries just divide input polygon edges into 2 even segments */ + zero_v3(boundverts_center); + for (i = 0; i < n_bndv; i++) { + /* Boundaries just divide input polygon edges into 2 even segments. */ copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->nv.co); get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co); - add_v3_v3(co, bndv->nv.co); + add_v3_v3(boundverts_center, bndv->nv.co); bndv = bndv->next; } - /* To place center vertex: - * coa is original vertex - * co is centroid of boundary corners - * cob is reflection of coa in across co. - * Calculate 'fullness' = fraction of way - * from co to coa (if positive) or to cob (if negative). - */ - copy_v3_v3(coa, bv->v->co); - mul_v3_fl(co, 1.0f / (float)n); - sub_v3_v3v3(cob, co, coa); - add_v3_v3(cob, co); - - /* An offline optimization process found fullness that let to closest fit to sphere as - * a function of r and ns (for case of cube corner) */ - r = bp->pro_super_r; - p = bp->profile; - if (r == PRO_LINE_R) { - fullness = 0.0f; - } - else if (r == PRO_CIRCLE_R && ns > 0 && ns <= CIRCLE_FULLNESS_SEGS) { - fullness = circle_fullness[ns - 1]; - } - else { - /* linear regression fit found best linear function, separately for even/odd segs */ - if (ns % 2 == 0) { - fullness = 2.4506f * p - 0.00000300f * ns - 0.6266f; + mul_v3_fl(boundverts_center, 1.0f / (float)n_bndv); + + /* To place the center vertex: + * 'negative_fullest' is the reflection of the original vertex across the boundverts' center. + * 'fullness' is the fraction of the way from the boundvert's centroid to to the original vertex + * (if positive) or to negative_fullest (if negative). */ + copy_v3_v3(original_vertex, bv->v->co); + sub_v3_v3v3(negative_fullest, boundverts_center, original_vertex); + add_v3_v3(negative_fullest, boundverts_center); + + /* Find the vertex mesh's start center with the profile's fullness */ + fullness = bp->pro_spacing.fullness; + sub_v3_v3v3(center_direction, original_vertex, boundverts_center); + if (len_squared_v3(center_direction) > BEVEL_EPSILON_SQ) { + if (bp->use_custom_profile) { + fullness *= 2.0f; + madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, negative_fullest, center_direction, fullness); } else { - fullness = 2.3635f * p + 0.000152f * ns - 0.6060f; + madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center, center_direction, fullness); } } - sub_v3_v3v3(dir, coa, co); - if (len_squared_v3(dir) > BEVEL_EPSILON_SQ) { - madd_v3_v3fl(co, dir, fullness); + else { + copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center); } - copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, co); vmesh_copy_equiv_verts(vm0); + /* Do the subdivision process to go from the two segment start mesh to the final vertex mesh. */ vm1 = vm0; do { vm1 = cubic_subdiv(bp, vm1); - } while (vm1->seg < ns); - if (vm1->seg != ns) { - vm1 = interp_vmesh(bp, vm1, ns); + } while (vm1->seg < nseg); + if (vm1->seg != nseg) { + vm1 = interp_vmesh(bp, vm1, nseg); } return vm1; } @@ -4012,15 +4334,16 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) Profile *pro = &vpipe->profile; EdgeHalf *e = vpipe->ebev; - copy_v3_v3(va, pro->coa); - copy_v3_v3(vb, pro->cob); + copy_v3_v3(va, pro->start); + copy_v3_v3(vb, pro->end); + /* Get a plane with the normal pointing along the beveled edge */ sub_v3_v3v3(edir, e->e->v1->co, e->e->v2->co); - plane_from_point_normal_v3(plane, co, edir); + closest_to_plane_v3(va0, plane, va); closest_to_plane_v3(vb0, plane, vb); - closest_to_plane_v3(vmid0, plane, pro->midco); + closest_to_plane_v3(vmid0, plane, pro->middle); if (make_unit_square_map(va0, vmid0, vb0, m)) { /* Transform co and project it onto superellipse */ if (!invert_m4_m4(minv, m)) { @@ -4030,6 +4353,7 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) } mul_v3_m4v3(p, minv, co); snap_to_superellipsoid(p, pro->super_r, midline); + mul_v3_m4v3(snap, m, p); copy_v3_v3(co, snap); } @@ -4040,38 +4364,123 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) } } -/* See pipe_test for conditions that make 'pipe'; vpipe is the return value from that. +/** See pipe_test for conditions that make 'pipe'; vpipe is the return value from that. * We want to make an ADJ mesh but then snap the vertices to the profile in a plane - * perpendicular to the pipes. - * A tricky case is for the 'square' profiles and an even nseg: we want certain vertices - * to snap to the midline on the pipe, not just to one plane or the other. */ + * perpendicular to the pipes. */ static VMesh *pipe_adj_vmesh(BevelParams *bp, BevVert *bv, BoundVert *vpipe) { - int i, j, k, n, ns, ns2, ipipe1, ipipe2; +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("PIPE ADJ VMESH\n"); + float green[4] = {0.0f, 1.0f, 0.0f, 1.0f}; + float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}; + float red[4] = {1.0f, 0.0f, 0.0f, 1.0f}; + float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + float *color; +#endif + int i, j, k, n_bndv, ns, half_ns, ipipe1, ipipe2, ring; VMesh *vm; bool even, midline; + float *profile_point_pipe1, *profile_point_pipe2, f; + /* Some unecessary overhead running this subdivision with custom profile snapping later on. */ vm = adj_vmesh(bp, bv); - /* Now snap all interior coordinates to be on the epipe profile */ - n = bv->vmesh->count; + /* Now snap all interior coordinates to be on the epipe profile. */ + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; - ns2 = ns / 2; + half_ns = ns / 2; even = (ns % 2) == 0; ipipe1 = vpipe->index; ipipe2 = vpipe->next->next->index; - for (i = 0; i < n; i++) { - for (j = 1; j <= ns2; j++) { - for (k = 0; k <= ns2; k++) { + +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("ipipe1: %d\n", ipipe1); + printf("ipipe2: %d\n", ipipe2); +#endif + + for (i = 0; i < n_bndv; i++) { + for (j = 1; j <= half_ns; j++) { + for (k = 0; k <= half_ns; k++) { if (!is_canon(vm, i, j, k)) { continue; } - midline = even && k == ns2 && ((i == 0 && j == ns2) || (i == ipipe1 || i == ipipe2)); - snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co); + if (bp->use_custom_profile) { + /* Find both profile vertices that correspond to this point. */ + if (i == ipipe1 || i == ipipe2) { + if (n_bndv == 3 && i == ipipe1) { + /* This part of the vmesh is the triangular corner between the two pipe profiles. */ + ring = max_ii(j, k); + profile_point_pipe2 = mesh_vert(vm, i, 0, ring)->co; + profile_point_pipe1 = mesh_vert(vm, i, ring, 0)->co; + /* End profile index increases with k on one side and j on the other. */ + f = ((k < j) ? min_ff(j, k) : ((2.0f * ring) - j)) / (2.0f * ring); + } + else { + /* This is part of either pipe profile boundvert area in the 4-way intersection. */ + profile_point_pipe1 = mesh_vert(vm, i, 0, k)->co; + profile_point_pipe2 = mesh_vert(vm, (i == ipipe1) ? ipipe2 : ipipe1, 0, ns - k)->co; + f = (float)j / (float)ns; /* The ring index brings us closer to the other side. */ + } + } + else { + /* The profile vertices are on both ends of each of the side profile's rings. */ + profile_point_pipe1 = mesh_vert(vm, i, j, 0)->co; + profile_point_pipe2 = mesh_vert(vm, i, j, ns)->co; + f = (float)k / (float)ns; /* Ring runs along the pipe, so segment is used here. */ + } + + /* Place the vertex by interpolatin between the two profile points using the factor. */ + interp_v3_v3v3(mesh_vert(vm, i, j, k)->co, profile_point_pipe1, profile_point_pipe2, f); +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("(%d, %d, %d)\n", i, j, k); + printf("f: %.3f\n", f); + printf("point 1: (%.3f, %.3f, %.3f)\n", + profile_point_pipe1[0], + profile_point_pipe1[1], + profile_point_pipe1[2]); + printf("point 2: (%.3f, %.3f, %.3f)\n", + profile_point_pipe2[0], + profile_point_pipe2[1], + profile_point_pipe2[2]); +#endif + } + else { + /* A tricky case is for the 'square' profiles and an even nseg: we want certain + * vertices to snap to the midline on the pipe, not just to one plane or the other. */ + midline = even && k == half_ns && + ((i == 0 && j == half_ns) || (i == ipipe1 || i == ipipe2)); + snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co); + } } } } - +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + /* Draw the locations of all the vertices after the "snapping" process */ + for (i = 0; i < n_bndv; i++) { + for (j = 1; j <= half_ns; j++) { + for (k = 1; k <= ns; k++) { + if (!is_canon(vm, i, j, k)) { + continue; + } + switch (i) { + case 0: + color = red; + break; + case 1: + color = green; + break; + case 2: + color = blue; + break; + case 3: + color = white; + break; + } + DRW_debug_sphere(mesh_vert(vm, i, j, k)->co, 0.01f, color); + } + } + } +#endif return vm; } @@ -4123,7 +4532,7 @@ static float snap_face_dist_squared(float *co, BMFace *f, BMEdge **r_snap_e, flo BMEdge *e; float closest[3]; - beste_d2 = 1e20; + beste_d2 = 1e20f; BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) { closest_to_line_segment_v3(closest, co, e->v1->co, e->v2->co); d2 = len_squared_v3v3(closest, co); @@ -4243,7 +4652,7 @@ static void closer_v3_v3v3v3(float r[3], float a[3], float b[3], float v[3]) */ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) { - int n, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, akind; + int n_bndv, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, ang_kind; float bndco[3], dir1[3], dir2[3], co1[3], co2[3], meet1[3], meet2[3], v1co[3], v2co[3]; float *on_edge_cur, *on_edge_prev, *p; float ns2inv, finalfrac, ang; @@ -4253,26 +4662,26 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) float *centerline; bool *cset, v1set, v2set; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; ns2 = ns / 2; odd = ns % 2; ns2inv = 1.0f / (float)ns2; - vm = new_adj_vmesh(bp->mem_arena, n, ns, bv->vmesh->boundstart); + vm = new_adj_vmesh(bp->mem_arena, n_bndv, ns, bv->vmesh->boundstart); clstride = 3 * (ns2 + 1); - centerline = MEM_mallocN(clstride * n * sizeof(float), "bevel"); - cset = MEM_callocN(n * sizeof(bool), "bevel"); + centerline = MEM_mallocN((size_t)(clstride * n_bndv) * sizeof(float), "bevel"); + cset = MEM_callocN((size_t)n_bndv * sizeof(bool), "bevel"); /* find on_edge, place on bndv[i]'s elast where offset line would meet, * taking min-distance-to bv->v with position where next sector's offset line would meet */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { copy_v3_v3(bndco, bndv->nv.co); e1 = bndv->efirst; e2 = bndv->elast; - akind = 0; + ang_kind = ANGLE_STRAIGHT; if (e1 && e2) { - akind = edges_angle_kind(e1, e2, bv->v); + ang_kind = edges_angle_kind(e1, e2, bv->v); } if (bndv->is_patch_start) { mid_v3_v3v3(centerline + clstride * i, bndv->nv.co, bndv->next->nv.co); @@ -4288,13 +4697,13 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) else if (bndv->is_arc_start) { e1 = bndv->efirst; e2 = bndv->next->efirst; - copy_v3_v3(centerline + clstride * i, bndv->profile.midco); + copy_v3_v3(centerline + clstride * i, bndv->profile.middle); bndv = bndv->next; cset[i] = true; i++; /* leave cset[i] where it was - probably false, unless i == n - 1 */ } - else if (akind < 0) { + else if (ang_kind == ANGLE_SMALLER) { sub_v3_v3v3(dir1, e1->e->v1->co, e1->e->v2->co); sub_v3_v3v3(dir2, e2->e->v1->co, e2->e->v2->co); add_v3_v3v3(co1, bndco, dir1); @@ -4321,7 +4730,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* want on_edge[i] to be min dist to bv->v of v2co and the v1co of next iteration */ on_edge_cur = centerline + clstride * i; - iprev = (i == 0) ? n - 1 : i - 1; + iprev = (i == 0) ? n_bndv - 1 : i - 1; on_edge_prev = centerline + clstride * iprev; if (v2set) { if (cset[i]) { @@ -4346,7 +4755,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) } /* Maybe not everything was set by the previous loop */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { if (!cset[i]) { on_edge_cur = centerline + clstride * i; e1 = bndv->next->efirst; @@ -4381,7 +4790,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* fill in rest of centerlines by interpolation */ copy_v3_v3(co2, bv->v->co); bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { if (odd) { ang = 0.5f * angle_v3v3v3(bndv->nv.co, co1, bndv->next->nv.co); if (ang > BEVEL_SMALL_ANG) { @@ -4389,7 +4798,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) * such that the base of the triangle is 1. * This is used in interpolation along centerline in odd case. * To avoid too big a drop from bv, cap finalfrac a 0.8 arbitrarily */ - finalfrac = 0.5f / sin(ang); + finalfrac = 0.5f / sinf(ang); if (finalfrac > 0.8f) { finalfrac = 0.8f; } @@ -4412,9 +4821,9 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* coords of edges and mid or near-mid line */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { copy_v3_v3(co1, bndv->nv.co); - copy_v3_v3(co2, centerline + clstride * (i == 0 ? n - 1 : i - 1)); + copy_v3_v3(co2, centerline + clstride * (i == 0 ? n_bndv - 1 : i - 1)); for (j = 0; j < ns2 + odd; j++) { interp_v3_v3v3(mesh_vert(vm, i, j, 0)->co, co1, co2, j * ns2inv); } @@ -4431,8 +4840,8 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* fill in interior points by interpolation from edges to centerlines */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { - im1 = (i == 0) ? n - 1 : i - 1; + for (i = 0; i < n_bndv; i++) { + im1 = (i == 0) ? n_bndv - 1 : i - 1; for (j = 1; j < ns2 + odd; j++) { for (k = 1; k <= ns2; k++) { ikind = isect_line_line_v3(mesh_vert(vm, i, 0, k)->co, @@ -4466,43 +4875,43 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) return vm; } -/* +/** * Given that the boundary is built and the boundary BMVerts have been made, * calculate the positions of the interior mesh points for the M_ADJ pattern, - * using cubic subdivision, then make the BMVerts and the new faces. */ -static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) + * using cubic subdivision, then make the BMVerts and the new faces. + */ +static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv, BoundVert *vpipe) { - int n, ns, ns2, odd, i, j, k, ring; + int n_bndv, ns, ns2, odd, i, j, k, ring; VMesh *vm1, *vm; - BoundVert *v; + BoundVert *bndv; BMVert *bmv1, *bmv2, *bmv3, *bmv4; BMFace *f, *f2, *r_f; BMEdge *bme, *bme1, *bme2, *bme3; EdgeHalf *e; - BoundVert *vpipe; int mat_nr = bp->mat_nr; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; ns2 = ns / 2; odd = ns % 2; - BLI_assert(n >= 3 && ns > 1); + BLI_assert(n_bndv >= 3 && ns > 1); - /* Add support for profiles in vertex only in-plane bevels */ + /* Add support for profiles in vertex only in-plane bevels. */ if (bp->vertex_only) { - v = bv->vmesh->boundstart; + bndv = bv->vmesh->boundstart; do { - Profile *pro = &v->profile; + Profile *pro = &bndv->profile; + copy_v3_v3(pro->middle, bv->v->co); pro->super_r = bp->pro_super_r; - copy_v3_v3(pro->midco, bv->v->co); - calculate_profile(bp, v); - v = v->next; - } while (v != bv->vmesh->boundstart); + bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start); + /* Orientation doesn't matter when only beveling vertices */ + calculate_profile(bp, bndv, false, miter_profile); + bndv = bndv->next; + } while (bndv != bv->vmesh->boundstart); } - vpipe = pipe_test(bv); - - if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd) { + if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd && !bp->use_custom_profile) { vm1 = square_out_adj_vmesh(bp, bv); } else if (vpipe) { @@ -4512,7 +4921,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) vm1 = tri_corner_adj_vmesh(bp, bv); /* the PRO_SQUARE_IN_R profile has boundary edges that merge * and no internal ring polys except possibly center ngon */ - if (bp->pro_super_r == PRO_SQUARE_IN_R) { + if (bp->pro_super_r == PRO_SQUARE_IN_R && !bp->use_custom_profile) { build_square_in_vmesh(bp, bm, bv, vm1); return; } @@ -4523,7 +4932,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) /* copy final vmesh into bv->vmesh, make BMVerts and BMFaces */ vm = bv->vmesh; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { for (j = 0; j <= ns2; j++) { for (k = 0; k <= ns; k++) { if (j == 0 && (k == 0 || k == ns)) { @@ -4539,16 +4948,16 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } vmesh_copy_equiv_verts(vm); /* make the polygons */ - v = vm->boundstart; + bndv = vm->boundstart; do { - i = v->index; - f = boundvert_rep_face(v, NULL); - f2 = boundvert_rep_face(v->next, NULL); + i = bndv->index; + f = boundvert_rep_face(bndv, NULL); + f2 = boundvert_rep_face(bndv->next, NULL); if (bp->vertex_only) { - e = v->efirst; + e = bndv->efirst; } else { - e = v->ebev; + e = bndv->ebev; } bme = e ? e->e : NULL; /* For odd ns, make polys with lower left corner at (i,j,k) for @@ -4576,7 +4985,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) f2, NULL, NULL, - v->next->efirst->e, + bndv->next->efirst->e, bme, mat_nr); } @@ -4617,8 +5026,8 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) else { bme1 = k == ns2 - 1 ? bme : NULL; bme3 = NULL; - if (j == ns2 - 1 && v->prev->ebev) { - bme3 = v->prev->ebev->e; + if (j == ns2 - 1 && bndv->prev->ebev) { + bme3 = bndv->prev->ebev->e; } bme2 = bme1 != NULL ? bme1 : bme3; r_f = bev_create_quad_ex( @@ -4628,14 +5037,14 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) record_face_kind(bp, r_f, F_VERT); } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); /* Fix UVs along center lines if even number of segments */ if (!odd) { - v = vm->boundstart; + bndv = vm->boundstart; do { - i = v->index; - if (!v->any_seam) { + i = bndv->index; + if (!bndv->any_seam) { for (ring = 1; ring < ns2; ring++) { BMVert *v_uv = mesh_vert(vm, i, ring, ns2)->v; if (v_uv) { @@ -4643,7 +5052,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); bmv1 = mesh_vert(vm, 0, ns2, ns2)->v; if (bp->vertex_only || count_bound_vert_seams(bv) <= 1) { bev_merge_uvs(bm, bmv1); @@ -4656,6 +5065,179 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } } +/** Builds the vertex mesh when the vertex mesh type is set to "cut off" with a face closing + * off each incoming edge's profile */ +/* HANS-TODO: Make cutoff VMesh work with outer miter != sharp. This should be possible but there + * are two problems currently: + * - Miter profiles don't have plane_no filled, so down direction is incorrect. + * - Indexing profile points of miters with (i, 0, k) seems to return zero except for the first + * and last profile point. */ +/* HANS-TODO: Use repface / edge arrays for UV interpolation properly. */ +static void bevel_build_cutoff(BevelParams *bp, BMesh *bm, BevVert *bv) +{ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("BEVEL BUILD CUTOFF\n"); + int j; +#endif + int i; + int n_bndv = bv->vmesh->count; + BoundVert *bndv; + float length; + float down_direction[3], new_vert[3]; + bool build_center_face; + /* BMFace *repface; */ + BMVert **face_bmverts = NULL; + BMEdge **bmedges = NULL; + BMFace **bmfaces = NULL; + + /* Find the locations for the corner vertices at the bottom of the cutoff faces. */ + bndv = bv->vmesh->boundstart; + do { + i = bndv->index; + + /* Find the "down" direction for this side of the cutoff face. */ + /* Find the direction along the intersection of the two adjecent profile normals. */ + cross_v3_v3v3(down_direction, bndv->profile.plane_no, bndv->prev->profile.plane_no); + if (dot_v3v3(down_direction, bv->v->no) > 0.0f) { + negate_v3(down_direction); + } + + /* Move down from the boundvert by average profile height from the two adjacent profiles. */ + length = (bndv->profile.height / sqrtf(2.0f) + bndv->prev->profile.height / sqrtf(2.0f)) / 2; + madd_v3_v3v3fl(new_vert, bndv->nv.co, down_direction, length); + + /* Use this location for this profile's first corner vert and the last profile's second. */ + copy_v3_v3(mesh_vert(bv->vmesh, i, 1, 0)->co, new_vert); + copy_v3_v3(mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->co, new_vert); + + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Corner vertices:\n"); + for (j = 0; j < n_bndv; j++) { + printf(" (%.3f, %.3f, %.3f)\n", + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[0], + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[1], + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[2]); + } +#endif + + /* Disable the center face if the corner vertices share the same location. */ + build_center_face = true; + if (n_bndv == 3) { /* Vertices only collapse with a 3-way VMesh. */ + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co, + mesh_vert(bv->vmesh, 1, 1, 0)->co) > BEVEL_EPSILON; + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co, + mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON; + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 1, 1, 0)->co, + mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON; + } +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("build_center_face: %d\n", build_center_face); +#endif + + /* Create the corner vertex BMVerts. */ + if (build_center_face) { + do { + i = bndv->index; + create_mesh_bmvert(bm, bv->vmesh, i, 1, 0, bv->v); + /* The second corner vertex for the previous profile shares this BMVert. */ + mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->v = mesh_vert(bv->vmesh, i, 1, 0)->v; + + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + } + else { + /* Use the same BMVert for all of the corner vertices. */ + create_mesh_bmvert(bm, bv->vmesh, 0, 1, 0, bv->v); + for (i = 1; i < n_bndv; i++) { + mesh_vert(bv->vmesh, i, 1, 0)->v = mesh_vert(bv->vmesh, 0, 1, 0)->v; + } + } + + /* Build the profile cutoff faces. */ + /* Extra one or two for corner vertices and one for last point along profile, or the size of the + * center face array if it's bigger. */ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Building profile cutoff faces.\n"); +#endif + face_bmverts = BLI_memarena_alloc( + bp->mem_arena, ((size_t)max_ii(bp->seg + 2 + build_center_face, n_bndv) * sizeof(BMVert *))); + bndv = bv->vmesh->boundstart; + do { + i = bndv->index; + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); + + /* Add the first corner vertex under this boundvert */ + face_bmverts[0] = mesh_vert(bv->vmesh, i, 1, 0)->v; + +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Profile Number %d:\n", i); + if (bndv->is_patch_start || bndv->is_arc_start) { + printf(" Miter profile\n"); + } + printf(" Corner 1: (%0.3f, %0.3f, %0.3f)\n", + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[0], + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[1], + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[2]); +#endif + + /* Add profile point vertices to the face, including the last one. */ + for (int k = 0; k < bp->seg + 1; k++) { + face_bmverts[k + 1] = mesh_vert(bv->vmesh, i, 0, k)->v; /* Leave room for first vert. */ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf(" Profile %d: (%0.3f, %0.3f, %0.3f)\n", + k, + (double)mesh_vert(bv->vmesh, i, 0, k)->co[0], + (double)mesh_vert(bv->vmesh, i, 0, k)->co[1], + (double)mesh_vert(bv->vmesh, i, 0, k)->co[2]); +#endif + } + + /* Add the second corner vert to complete the bottom of the face. */ + if (build_center_face) { + face_bmverts[bp->seg + 2] = mesh_vert(bv->vmesh, i, 1, 1)->v; +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf(" Corner 2: (%0.3f, %0.3f, %0.3f)\n", + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[0], + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[1], + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[2]); +#endif + } + + /* Create the profile cutoff face for this boundvert. */ + /* repface = boundvert_rep_face(bndv, NULL); */ + bev_create_ngon(bm, + face_bmverts, + bp->seg + 2 + build_center_face, + bmfaces, + NULL, + bmedges, + bp->mat_nr, + true); + + BLI_array_free(bmedges); + BLI_array_free(bmfaces); + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + + /* Create the bottom face if it should be built, reusing previous face_bmverts allocation. */ + if (build_center_face) { + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); + + /* Add all of the corner vertices to this face. */ + for (i = 0; i < n_bndv; i++) { + /* Add verts from each cutoff face. */ + face_bmverts[i] = mesh_vert(bv->vmesh, i, 1, 0)->v; + } + /* BLI_array_append(bmfaces, repface); */ + bev_create_ngon(bm, face_bmverts, n_bndv, bmfaces, NULL, bmedges, bp->mat_nr, true); + + BLI_array_free(bmedges); + BLI_array_free(bmfaces); + } +} + /* If we make a poly out of verts around bv, snapping to rep frep, will uv poly have zero area? * The uv poly is made by snapping all outside-of-frep vertices to the closest edge in frep. * Assume that this function is called when the only inside-of-frep vertex is vm->boundstart. @@ -4690,71 +5272,72 @@ static bool is_bad_uv_poly(BevVert *bv, BMFace *frep) static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv) { - BMFace *f, *frep, *frep2; + BMFace *f, *repface, *frep2; int n, k; VMesh *vm = bv->vmesh; - BoundVert *v; - BMEdge *frep_e1, *frep_e2, *frep_e; - BMVert **vv = NULL; - BMFace **vf = NULL; - BMEdge **ve = NULL; - BLI_array_staticdeclare(vv, BM_DEFAULT_NGON_STACK_SIZE); - BLI_array_staticdeclare(vf, BM_DEFAULT_NGON_STACK_SIZE); - BLI_array_staticdeclare(ve, BM_DEFAULT_NGON_STACK_SIZE); + BoundVert *bndv; + BMEdge *repface_e1, *repface_e2, *frep_e; + BMVert **bmverts = NULL; + BMEdge **bmedges = NULL; + BMFace **bmfaces = NULL; + BLI_array_staticdeclare(bmverts, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); if (bv->any_seam) { - frep = boundvert_rep_face(vm->boundstart, &frep2); - if (frep2 && frep && is_bad_uv_poly(bv, frep)) { - frep = frep2; + repface = boundvert_rep_face(vm->boundstart, &frep2); + if (frep2 && repface && is_bad_uv_poly(bv, repface)) { + repface = frep2; } - get_incident_edges(frep, bv->v, &frep_e1, &frep_e2); + get_incident_edges(repface, bv->v, &repface_e1, &repface_e2); } else { - frep = NULL; - frep_e1 = frep_e2 = NULL; + repface = NULL; + repface_e1 = repface_e2 = NULL; } - v = vm->boundstart; + bndv = vm->boundstart; n = 0; do { /* accumulate vertices for vertex ngon */ /* also accumulate faces in which uv interpolation is to happen for each */ - BLI_array_append(vv, v->nv.v); - if (frep) { - BLI_array_append(vf, frep); - frep_e = find_closer_edge(v->nv.v->co, frep_e1, frep_e2); - BLI_array_append(ve, n > 0 ? frep_e : NULL); + BLI_array_append(bmverts, bndv->nv.v); + if (repface) { + BLI_array_append(bmfaces, repface); + frep_e = find_closer_edge(bndv->nv.v->co, repface_e1, repface_e2); + BLI_array_append(bmedges, n > 0 ? frep_e : NULL); } else { - BLI_array_append(vf, boundvert_rep_face(v, NULL)); - BLI_array_append(ve, NULL); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL)); + BLI_array_append(bmedges, NULL); } n++; - if (v->ebev && v->ebev->seg > 1) { - for (k = 1; k < v->ebev->seg; k++) { - BLI_array_append(vv, mesh_vert(vm, v->index, 0, k)->v); - if (frep) { - BLI_array_append(vf, frep); - frep_e = find_closer_edge(mesh_vert(vm, v->index, 0, k)->v->co, frep_e1, frep_e2); - BLI_array_append(ve, k < v->ebev->seg / 2 ? NULL : frep_e); + if (bndv->ebev && bndv->ebev->seg > 1) { + for (k = 1; k < bndv->ebev->seg; k++) { + BLI_array_append(bmverts, mesh_vert(vm, bndv->index, 0, k)->v); + if (repface) { + BLI_array_append(bmfaces, repface); + frep_e = find_closer_edge( + mesh_vert(vm, bndv->index, 0, k)->v->co, repface_e1, repface_e2); + BLI_array_append(bmedges, k < bndv->ebev->seg / 2 ? NULL : frep_e); } else { - BLI_array_append(vf, boundvert_rep_face(v, NULL)); - BLI_array_append(ve, NULL); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL)); + BLI_array_append(bmedges, NULL); } n++; } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); if (n > 2) { - f = bev_create_ngon(bm, vv, n, vf, frep, ve, bp->mat_nr, true); + f = bev_create_ngon(bm, bmverts, n, bmfaces, repface, bmedges, bp->mat_nr, true); record_face_kind(bp, f, F_VERT); } else { f = NULL; } - BLI_array_free(vv); - BLI_array_free(vf); - BLI_array_free(ve); + BLI_array_free(bmverts); + BLI_array_free(bmedges); + BLI_array_free(bmfaces); return f; } @@ -4817,6 +5400,7 @@ static void bevel_build_trifan(BevelParams *bp, BMesh *bm, BevVert *bv) * we have to make it here. */ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) { + VMesh *vm = bv->vmesh; BMVert *v1, *v2; BMEdge *e_eg, *bme; @@ -4832,18 +5416,20 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) ns = vm->seg; if (ns > 1) { - /* Set up profile parameters */ + /* Set up profile parameters. */ bndv = vm->boundstart; pro = &bndv->profile; pro->super_r = bp->pro_super_r; - copy_v3_v3(pro->coa, v1->co); - copy_v3_v3(pro->cob, v2->co); - copy_v3_v3(pro->midco, bv->v->co); - /* don't use projection */ + copy_v3_v3(pro->start, v1->co); + copy_v3_v3(pro->end, v2->co); + copy_v3_v3(pro->middle, bv->v->co); + /* Don't use projection. */ zero_v3(pro->plane_co); zero_v3(pro->plane_no); zero_v3(pro->proj_dir); - calculate_profile(bp, bndv); + /* there's no orientation chain to continue so the orientation of the bevel doesn't matter. */ + calculate_profile(bp, bndv, false, false); + for (k = 1; k < ns; k++) { get_profile_point(bp, pro, k, ns, co); copy_v3_v3(mesh_vert(vm, 0, 0, k)->co, co); @@ -4874,84 +5460,129 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) * for the boundary and the interior of the vertex mesh. */ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv) { - MemArena *mem_arena = bp->mem_arena; VMesh *vm = bv->vmesh; - BoundVert *v, *weld1, *weld2; + BoundVert *bndv, *weld1, *weld2, *vpipe; int n, ns, ns2, i, k, weld; - float *va, *vb, co[3]; + float *v_weld1, *v_weld2, co[3]; n = vm->count; ns = vm->seg; ns2 = ns / 2; - vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena, n * (ns2 + 1) * (ns + 1) * sizeof(NewVert)); + vm->mesh = (NewVert *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(n * (ns2 + 1) * (ns + 1)) * sizeof(NewVert)); - /* special case: two beveled ends welded together */ + /* Special case: just two beveled edges welded together. */ weld = (bv->selcount == 2) && (vm->count == 2); - weld1 = weld2 = NULL; /* will hold two BoundVerts involved in weld */ + weld1 = weld2 = NULL; /* Will hold two BoundVerts involved in weld. */ - /* make (i, 0, 0) mesh verts for all i */ - v = vm->boundstart; + /* Make (i, 0, 0) mesh verts for all i boundverts. */ + bndv = vm->boundstart; do { - i = v->index; - copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, v->nv.co); - create_mesh_bmvert(bm, vm, i, 0, 0, bv->v); - v->nv.v = mesh_vert(vm, i, 0, 0)->v; - if (weld && v->ebev) { + i = bndv->index; + copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, bndv->nv.co); /* Mesh NewVert to boundary NewVert. */ + create_mesh_bmvert(bm, vm, i, 0, 0, bv->v); /* Create BMVert for that NewVert. */ + bndv->nv.v = mesh_vert(vm, i, 0, 0)->v; /* Use the BMVert for the BoundVert's NewVert. */ + + /* Find boundverts and move profile planes if this is a weld case. */ + if (weld && bndv->ebev) { if (!weld1) { - weld1 = v; + weld1 = bndv; } - else { - weld2 = v; - move_weld_profile_planes(bv, weld1, weld2); - calculate_profile(bp, weld1); - calculate_profile(bp, weld2); + else { /* Get the last of the two BoundVerts. */ + weld2 = bndv; + move_weld_profile_planes(bv, weld1, weld2); /* Profile recalculated in next loop. */ } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); - /* copy other ends to (i, 0, ns) for all i, and fill in profiles for edges */ - v = vm->boundstart; + /* Create new vertices and place them based on the profiles. */ + /* Copy other ends to (i, 0, ns) for all i, and fill in profiles for edges. */ + bndv = vm->boundstart; do { - i = v->index; - copy_mesh_vert(vm, i, 0, ns, v->next->index, 0, 0); - for (k = 1; k < ns; k++) { - if (v->ebev && vm->mesh_kind != M_ADJ) { - get_profile_point(bp, &v->profile, k, ns, co); - copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co); - if (!weld) { - create_mesh_bmvert(bm, vm, i, 0, k, bv->v); + i = bndv->index; + /* bndv's last vert along the boundary arc is the the first of the next BoundVert's arc. */ + copy_mesh_vert(vm, i, 0, ns, bndv->next->index, 0, 0); + + /* Fix the profile orientations if it's not a miter profile. */ + if (bp->use_custom_profile && !bndv->is_arc_start && !bndv->is_patch_start) { + calculate_profile(bp, bndv, !bndv->is_profile_start, false); + } + if (vm->mesh_kind != M_ADJ) { + for (k = 1; k < ns; k++) { + if (bndv->ebev) { + get_profile_point(bp, &bndv->profile, k, ns, co); + copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co); /* Get NewVert location from profile coord */ + if (!weld) { + /* This is done later with (possibly) better positions for the weld case. */ + create_mesh_bmvert(bm, vm, i, 0, k, bv->v); + } + } + else if (n == 2 && !bndv->ebev) { + /* case of one edge beveled and this is the v without ebev */ + /* want to copy the verts from other v, in reverse order */ + copy_mesh_vert(bv->vmesh, i, 0, k, 1 - i, 0, ns - k); } - } - else if (n == 2 && !v->ebev && vm->mesh_kind != M_ADJ) { - /* case of one edge beveled and this is the v without ebev */ - /* want to copy the verts from other v, in reverse order */ - copy_mesh_vert(vm, i, 0, k, 1 - i, 0, ns - k); } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); + /* Build the profile for the weld case (just a connection between the two boundverts). */ if (weld) { - vm->mesh_kind = M_NONE; + bv->vmesh->mesh_kind = M_NONE; for (k = 1; k < ns; k++) { - va = mesh_vert(vm, weld1->index, 0, k)->co; - vb = mesh_vert(vm, weld2->index, 0, ns - k)->co; - /* if one of the profiles is on a flat plane, - * just use the boundary point of the other */ - if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) { - copy_v3_v3(co, vb); - } - else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) { - copy_v3_v3(co, va); + v_weld1 = mesh_vert(bv->vmesh, weld1->index, 0, k)->co; + v_weld2 = mesh_vert(bv->vmesh, weld2->index, 0, ns - k)->co; + if (bp->use_custom_profile) { + /* Don't bother with special case profile check from below. */ + mid_v3_v3v3(co, v_weld1, v_weld2); } else { - mid_v3_v3v3(co, va, vb); + /* Use the point from the other profile if one is in a special case. */ + if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) { + copy_v3_v3(co, v_weld2); + } + else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) { + copy_v3_v3(co, v_weld1); + } + else { + /* In case the profiles aren't snapped to the same plane, use their midpoint. */ + mid_v3_v3v3(co, v_weld1, v_weld2); + } } - copy_v3_v3(mesh_vert(vm, weld1->index, 0, k)->co, co); - create_mesh_bmvert(bm, vm, weld1->index, 0, k, bv->v); + copy_v3_v3(mesh_vert(bv->vmesh, weld1->index, 0, k)->co, co); + create_mesh_bmvert(bm, bv->vmesh, weld1->index, 0, k, bv->v); } for (k = 1; k < ns; k++) { - copy_mesh_vert(vm, weld2->index, 0, ns - k, weld1->index, 0, k); + copy_mesh_vert(bv->vmesh, weld2->index, 0, ns - k, weld1->index, 0, k); + } + } +#ifdef DEBUG_CUSTOM_PROFILE_WELD + if (weld && ns > 1) { + printf("Weld1 profile coordinates:\n"); + for (k = 0; k < ns; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)weld1->profile.prof_co[3 * k], + (double)weld1->profile.prof_co[3 * k + 1], + (double)weld1->profile.prof_co[3 * k + 2]); + } + printf("Weld2 profile coordinates\n"); + for (k = 0; k < ns; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)weld2->profile.prof_co[3 * k], + (double)weld2->profile.prof_co[3 * k + 1], + (double)weld2->profile.prof_co[3 * k + 2]); + } + } +#endif + + /* Make sure the pipe case ADJ mesh is used for both the "Grid Fill" (ADJ) and cutoff options. */ + vpipe = NULL; + if ((vm->count == 3 || vm->count == 4) && bp->seg > 1) { + /* Result is passed to bevel_build_rings to avoid overhead. */ + vpipe = pipe_test(bv); + if (vpipe) { + vm->mesh_kind = M_ADJ; } } @@ -4965,11 +5596,13 @@ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv) bevel_build_poly(bp, bm, bv); break; case M_ADJ: - bevel_build_rings(bp, bm, bv); + bevel_build_rings(bp, bm, bv, vpipe); break; case M_TRI_FAN: bevel_build_trifan(bp, bm, bv); break; + case M_CUTOFF: + bevel_build_cutoff(bp, bm, bv); } } @@ -5229,9 +5862,7 @@ static void find_bevel_edge_order(BMesh *bm, BevVert *bv, BMEdge *first_bme) } } -/* - * Construction around the vertex - */ +/* Construction around the vertex */ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) { BMEdge *bme; @@ -5504,7 +6135,7 @@ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) return bv; } -/* Face f has at least one beveled vertex. Rebuild f */ +/* Face f has at least one beveled vertex. Rebuild f */ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) { BMIter liter, eiter, fiter; @@ -5849,7 +6480,7 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex } BLI_assert(bme_prev && bme_next); - /* want seams and sharp edges to cross only if that way on both sides */ + /* Want seams and sharp edges to cross only if that way on both sides. */ disable_seam = BM_elem_flag_test(bme_prev, BM_ELEM_SEAM) != BM_elem_flag_test(bme_next, BM_ELEM_SEAM); enable_smooth = BM_elem_flag_test(bme_prev, BM_ELEM_SMOOTH) != @@ -5869,8 +6500,8 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex } } -/* - * Build the polygons along the selected Edge +/** + * Build the bevel polygons along the selected Edge. */ static void bevel_build_edge_polygons(BMesh *bm, BevelParams *bp, BMEdge *bme) { @@ -6269,50 +6900,131 @@ static void find_even_superellipse_chords(int n, float r, double *xvals, double find_even_superellipse_chords_general(n, r, xvals, yvals); } -/* The superellipse used for multisegment profiles does not - * have a closed-form way to generate evenly spaced points - * along an arc. We use an expensive search procedure to find - * the parameter values that lead to bp->seg even chords. - * We also want spacing for a number of segments that is - * a power of 2 >= bp->seg (but at least 4). - * Use doubles because otherwise we cannot come close to float - * precision for final results. */ -static void set_profile_spacing(BevelParams *bp) +/** Find the profile's "fullness," which is the fraction of the space it takes up way from the + * boundvert's centroid to to the original vertex for a non-custom profile, or in the case of a + * custom profile, the average "height" of the profile points along its centerline. */ +static float find_profile_fullness(BevelParams *bp) +{ + float fullness; + int nseg = bp->seg; + + /* Precalculated fullness for circle profile radius and more common low seg values. */ +#define CIRCLE_FULLNESS_SEGS 11 + static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = { + 0.0f, /* nsegs == 1 */ + 0.559f, /* 2 */ + 0.642f, /* 3 */ + 0.551f, /* 4 */ + 0.646f, /* 5 */ + 0.624f, /* 6 */ + 0.646f, /* 7 */ + 0.619f, /* 8 */ + 0.647f, /* 9 */ + 0.639f, /* 10 */ + 0.647f, /* 11 */ + }; + + if (bp->use_custom_profile) { + /* Set fullness to the average "height" of the profile's sampled points. */ + fullness = 0.0f; + for (int i = 0; i < nseg; i++) { /* Don't use the end points. */ + fullness += (float)(bp->pro_spacing.xvals[i] + bp->pro_spacing.yvals[i]) / (2.0f * nseg); + } + } + else { + /* An offline optimization process found fullness that led to closest fit to sphere as + * a function of r and ns (for case of cube corner). */ + if (bp->pro_super_r == PRO_LINE_R) { + fullness = 0.0f; + } + else if (bp->pro_super_r == PRO_CIRCLE_R && nseg > 0 && nseg <= CIRCLE_FULLNESS_SEGS) { + fullness = circle_fullness[nseg - 1]; + } + else { + /* Linear regression fit found best linear function, separately for even/odd segs. */ + if (nseg % 2 == 0) { + fullness = 2.4506f * bp->profile - 0.00000300f * nseg - 0.6266f; + } + else { + fullness = 2.3635f * bp->profile + 0.000152f * nseg - 0.6060f; + } + } + } + return fullness; +} + +/** Fills the ProfileSpacing struct with the 2D coordinates for the profile's vertices. + * The superellipse used for multisegment profiles does not have a closed-form way + * to generate evenly spaced points along an arc. We use an expensive search procedure + * to find the parameter values that lead to bp->seg even chords. + * We also want spacing for a number of segments that is a power of 2 >= bp->seg (but at least 4). + * Use doubles because otherwise we cannot come close to float precision for final results. + * \param pro_spacing: The struct to fill. Changes depending on whether there needs + to be a separate miter profile. */ +static void set_profile_spacing(BevelParams *bp, ProfileSpacing *pro_spacing, bool custom) { int seg, seg_2; seg = bp->seg; + seg_2 = power_of_2_max_i(bp->seg); if (seg > 1) { - bp->pro_spacing.xvals = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg + 1) * sizeof(double)); - bp->pro_spacing.yvals = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg + 1) * sizeof(double)); - find_even_superellipse_chords( - seg, bp->pro_super_r, bp->pro_spacing.xvals, bp->pro_spacing.yvals); - seg_2 = power_of_2_max_i(bp->seg); + /* Sample the input number of segments. */ + pro_spacing->xvals = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg + 1) * sizeof(double)); + pro_spacing->yvals = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg + 1) * sizeof(double)); + if (custom) { + /* Make sure the curve profile's sample table is full. */ + if (bp->custom_profile->segments_len != seg || !bp->custom_profile->segments) { + BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg); + } + + /* Copy segment locations into the profile spacing struct. */ + for (int i = 0; i < seg + 1; i++) { + pro_spacing->xvals[i] = (double)bp->custom_profile->segments[i].y; + pro_spacing->yvals[i] = (double)bp->custom_profile->segments[i].x; + } + } + else { + find_even_superellipse_chords(seg, bp->pro_super_r, pro_spacing->xvals, pro_spacing->yvals); + } + + /* Sample the seg_2 segments used for subdividing the vertex meshes. */ if (seg_2 == 2) { seg_2 = 4; } bp->pro_spacing.seg_2 = seg_2; if (seg_2 == seg) { - bp->pro_spacing.xvals_2 = bp->pro_spacing.xvals; - bp->pro_spacing.yvals_2 = bp->pro_spacing.yvals; + pro_spacing->xvals_2 = pro_spacing->xvals; + pro_spacing->yvals_2 = pro_spacing->yvals; } else { - bp->pro_spacing.xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg_2 + 1) * sizeof(double)); - bp->pro_spacing.yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg_2 + 1) * sizeof(double)); - find_even_superellipse_chords( - seg_2, bp->pro_super_r, bp->pro_spacing.xvals_2, bp->pro_spacing.yvals_2); + pro_spacing->xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg_2 + 1) * sizeof(double)); + pro_spacing->yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg_2 + 1) * sizeof(double)); + if (custom) { + /* Make sure the curve profile widget's sample table is full of the seg_2 samples. */ + BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg_2); + + /* Copy segment locations into the profile spacing struct. */ + for (int i = 0; i < seg_2 + 1; i++) { + pro_spacing->xvals_2[i] = (double)bp->custom_profile->segments[i].y; + pro_spacing->yvals_2[i] = (double)bp->custom_profile->segments[i].x; + } + } + else { + find_even_superellipse_chords( + seg_2, bp->pro_super_r, pro_spacing->xvals_2, pro_spacing->yvals_2); + } } } - else { - bp->pro_spacing.xvals = NULL; - bp->pro_spacing.yvals = NULL; - bp->pro_spacing.xvals_2 = NULL; - bp->pro_spacing.yvals_2 = NULL; - bp->pro_spacing.seg_2 = 0; + else { /* Only 1 segment, we don't need any profile information */ + pro_spacing->xvals = NULL; + pro_spacing->yvals = NULL; + pro_spacing->xvals_2 = NULL; + pro_spacing->yvals_2 = NULL; + pro_spacing->seg_2 = 0; } } @@ -6320,7 +7032,7 @@ static void set_profile_spacing(BevelParams *bp) * Assume we have a situation like: * * a d - * \ / + * \ / * A \ / C * \ th1 th2/ * b---------c @@ -6566,7 +7278,10 @@ void BM_mesh_bevel(BMesh *bm, const int miter_outer, const int miter_inner, const float spread, - const float smoothresh) + const float smoothresh, + const bool use_custom_profile, + const struct CurveProfile *custom_profile, + const int vmesh_method) { BMIter iter, liter; BMVert *v, *v_next; @@ -6578,9 +7293,9 @@ void BM_mesh_bevel(BMesh *bm, bp.offset = offset; bp.offset_type = offset_type; - bp.seg = segments; + bp.seg = (int)segments; bp.profile = profile; - bp.pro_super_r = -log(2.0) / log(sqrt(profile)); /* convert to superellipse exponent */ + bp.pro_super_r = -logf(2.0) / logf(sqrtf(profile)); /* convert to superellipse exponent */ bp.vertex_only = vertex_only; bp.use_weights = use_weights; bp.loop_slide = loop_slide; @@ -6598,6 +7313,15 @@ void BM_mesh_bevel(BMesh *bm, bp.spread = spread; bp.smoothresh = smoothresh; bp.face_hash = NULL; + bp.use_custom_profile = use_custom_profile; + bp.custom_profile = custom_profile; + bp.vmesh_method = vmesh_method; + + /* Disable the miters with the cutoff vertex mesh method, this combination isn't useful anyway */ + if (bp.vmesh_method == BEVEL_VMESH_CUTOFF) { + bp.miter_outer = BEVEL_MITER_SHARP; + bp.miter_inner = BEVEL_MITER_SHARP; + } if (profile >= 0.950f) { /* r ~ 692, so PRO_SQUARE_R is 1e4 */ bp.pro_super_r = PRO_SQUARE_R; @@ -6608,7 +7332,18 @@ void BM_mesh_bevel(BMesh *bm, bp.vert_hash = BLI_ghash_ptr_new(__func__); bp.mem_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), __func__); BLI_memarena_use_calloc(bp.mem_arena); - set_profile_spacing(&bp); + + /* Get the 2D profile point locations from either the superellipse or the custom profile */ + set_profile_spacing(&bp, &bp.pro_spacing, bp.use_custom_profile); + if (bp.seg > 1) { + bp.pro_spacing.fullness = find_profile_fullness(&bp); + } + + /* Get separate non-custom profile samples for the miter profiles if they are needed */ + if (bp.use_custom_profile && + (bp.miter_inner != BEVEL_MITER_SHARP || bp.miter_outer != BEVEL_MITER_SHARP)) { + set_profile_spacing(&bp, &bp.pro_spacing_miter, false); + } bp.face_hash = BLI_ghash_ptr_new(__func__); BLI_ghash_flag_set(bp.face_hash, GHASH_FLAG_ALLOW_DUPES); @@ -6643,6 +7378,20 @@ void BM_mesh_bevel(BMesh *bm, adjust_offsets(&bp, bm); } + /* Maintain consistent orientations for the unsymmetrical custom profiles. */ + if (bp.use_custom_profile) { + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + regularize_profile_orientation(&bp, e); + } + } + } +#ifdef DEBUG_PROFILE_ORIENTATION_DRAW + if (bp.use_custom_profile) { + debug_draw_profile_orientation(&bp, bm); + } +#endif + /* 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)) { @@ -6688,14 +7437,14 @@ void BM_mesh_bevel(BMesh *bm, } if (bp.harden_normals) { - bevel_harden_normals(bm, &bp); + bevel_harden_normals(&bp, bm); } if (bp.face_strength_mode != BEVEL_FACE_STRENGTH_NONE) { bevel_set_weighted_normal_face_strength(bm, &bp); } /* When called from operator (as opposed to modifier), bm->use_toolflags - * will be set, and we to transfer the oflags to BM_ELEM_TAGs */ + * will be set, and we need 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)) { @@ -6717,6 +7466,22 @@ void BM_mesh_bevel(BMesh *bm, BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l, BM_ELEM_LONG_TAG); } + +#ifdef DEBUG_CUSTOM_PROFILE_SAMPLE + printf("Profile spacing:\n"); + printf("Seg values:\n"); + if (bp.pro_spacing.xvals != NULL) { + for (int i = 0; i < bp.seg; i++) { + printf("(%.3f, %.3f)\n", bp.pro_spacing.xvals[i], bp.pro_spacing.yvals[i]); + } + } + if (bp.pro_spacing.seg_2 != bp.seg && bp.pro_spacing.seg_2 != 0) { + printf("Seg_2 values:\n"); + for (int i = 0; i < bp.pro_spacing.seg_2; i++) { + printf("(%0.2f, %0.2f)\n", bp.pro_spacing.xvals_2[i], bp.pro_spacing.yvals_2[i]); + } + } +#endif } /* primary free */ diff --git a/source/blender/bmesh/tools/bmesh_bevel.h b/source/blender/bmesh/tools/bmesh_bevel.h index 496be9219b7..45bebbb33ce 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.h +++ b/source/blender/bmesh/tools/bmesh_bevel.h @@ -21,6 +21,7 @@ * \ingroup bmesh */ +struct CurveProfile; struct MDeformVert; void BM_mesh_bevel(BMesh *bm, @@ -42,6 +43,8 @@ void BM_mesh_bevel(BMesh *bm, const int miter_outer, const int miter_inner, const float spread, - const float smoothresh); - + const float smoothresh, + const bool use_custom_profile, + const struct CurveProfile *custom_profile, + const int vmesh_method); #endif /* __BMESH_BEVEL_H__ */ |