BMesh: improved smooth subdivision

Instead of offsetting along normals, smooth positions are now
calculated on a sphere defined by the vertices and their normals.

This removes visible seams along original edges, which were common previously.
This commit is contained in:
Campbell Barton 2015-10-04 23:20:48 +11:00
parent 52f74923e6
commit 123b64f818
3 changed files with 141 additions and 22 deletions

@ -163,6 +163,85 @@ static BMEdge *connect_smallest_face(BMesh *bm, BMVert *v_a, BMVert *v_b, BMFace
return NULL;
}
/**
* Specialized slerp that uses a sphere defined by each points normal.
*/
static void interp_slerp_co_no_v3(
const float co_a[3], const float no_a[3],
const float co_b[3], const float no_b[3],
const float no_dir[3], /* caller already knows, avoid normalize */
float fac,
float r_co[3])
{
/* center of the sphere defined by both normals */
float center[3];
BLI_assert(len_squared_v3v3(no_a, no_b) != 0);
/* calculate sphere 'center' */
{
/* use point on plane to */
float plane_a[4], plane_b[4], plane_c[4];
float no_mid[3], no_ortho[3];
/* pass this as an arg instead */
#if 0
float no_dir[3];
#endif
float v_a_no_ortho[3], v_b_no_ortho[3];
add_v3_v3v3(no_mid, no_a, no_b);
normalize_v3(no_mid);
#if 0
sub_v3_v3v3(no_dir, co_a, co_b);
normalize_v3(no_dir);
#endif
/* axis of slerp */
cross_v3_v3v3(no_ortho, no_mid, no_dir);
normalize_v3(no_ortho);
/* creater planes */
cross_v3_v3v3(v_a_no_ortho, no_ortho, no_a);
cross_v3_v3v3(v_b_no_ortho, no_ortho, no_b);
project_v3_plane(v_a_no_ortho, no_ortho, v_a_no_ortho);
project_v3_plane(v_b_no_ortho, no_ortho, v_b_no_ortho);
plane_from_point_normal_v3(plane_a, co_a, v_a_no_ortho);
plane_from_point_normal_v3(plane_b, co_b, v_b_no_ortho);
plane_from_point_normal_v3(plane_c, co_b, no_ortho);
/* find the sphere center from 3 planes */
if (isect_plane_plane_plane_v3(plane_a, plane_b, plane_c, center)) {
/* pass */
}
else {
mid_v3_v3v3(center, co_a, co_b);
}
}
/* calculate the final output 'r_co' */
{
float ofs_a[3], ofs_b[3], ofs_slerp[3];
float dist_a, dist_b;
sub_v3_v3v3(ofs_a, co_a, center);
sub_v3_v3v3(ofs_b, co_b, center);
dist_a = normalize_v3(ofs_a);
dist_b = normalize_v3(ofs_b);
if (interp_v3_v3v3_slerp(ofs_slerp, ofs_a, ofs_b, fac)) {
madd_v3_v3v3fl(r_co, center, ofs_slerp, interpf(dist_b, dist_a, fac));
}
else {
interp_v3_v3v3(r_co, co_a, co_b, fac);
}
}
}
/* calculates offset for co, based on fractal, sphere or smooth settings */
static void alter_co(
BMVert *v, BMEdge *UNUSED(e_orig),
@ -179,32 +258,72 @@ static void alter_co(
mul_v3_fl(co, params->smooth);
}
else if (params->use_smooth) {
/* we calculate an offset vector vec1[], to be added to *co */
float dir[3], tvec[3];
float fac, len, val;
/* calculating twice and blending gives smoother results,
* removing visible seams. */
#define USE_SPHERE_DUAL_BLEND
sub_v3_v3v3(dir, v_a->co, v_b->co);
len = (float)M_SQRT1_2 * normalize_v3(dir);
const float eps_unit_vec = 1e-5f;
float smooth;
float no_dir[3];
/* cosine angle */
fac = dot_v3v3(dir, v_a->no);
mul_v3_v3fl(tvec, v_a->no, fac);
#ifdef USE_SPHERE_DUAL_BLEND
float no_reflect[3], co_a[3], co_b[3];
#endif
/* cosine angle */
fac = -dot_v3v3(dir, v_b->no);
madd_v3_v3fl(tvec, v_b->no, fac);
sub_v3_v3v3(no_dir, v_a->co, v_b->co);
normalize_v3(no_dir);
/* falloff for multi subdivide */
val = fabsf(1.0f - 2.0f * fabsf(0.5f - perc));
val = bmesh_subd_falloff_calc(params->smooth_falloff, val);
if (params->use_smooth_even) {
val *= shell_v3v3_mid_normalized_to_dist(v_a->no, v_b->no);
#ifndef USE_SPHERE_DUAL_BLEND
if (len_squared_v3v3(v_a->no, v_b->no) < eps_unit_vec) {
interp_v3_v3v3(co, v_a->co, v_b->co, perc);
}
else {
interp_slerp_co_no_v3(v_a->co, v_a->no, v_b->co, v_b->no, no_dir, perc, co);
}
#else
/* sphere-a */
reflect_v3_v3v3(no_reflect, v_a->no, no_dir);
if (len_squared_v3v3(v_a->no, no_reflect) < eps_unit_vec) {
interp_v3_v3v3(co_a, v_a->co, v_b->co, perc);
}
else {
interp_slerp_co_no_v3(v_a->co, v_a->no, v_b->co, no_reflect, no_dir, perc, co_a);
}
mul_v3_fl(tvec, params->smooth * val * len);
/* sphere-b */
reflect_v3_v3v3(no_reflect, v_b->no, no_dir);
if (len_squared_v3v3(v_b->no, no_reflect) < eps_unit_vec) {
interp_v3_v3v3(co_b, v_a->co, v_b->co, perc);
}
else {
interp_slerp_co_no_v3(v_a->co, no_reflect, v_b->co, v_b->no, no_dir, perc, co_b);
}
add_v3_v3(co, tvec);
/* blend both spheres */
interp_v3_v3v3(co, co_a, co_b, perc);
#endif /* USE_SPHERE_DUAL_BLEND */
/* apply falloff */
if (params->smooth_falloff == SUBD_FALLOFF_LIN) {
smooth = 1.0f;
}
else {
smooth = fabsf(1.0f - 2.0f * fabsf(0.5f - perc));
smooth = 1.0f + bmesh_subd_falloff_calc(params->smooth_falloff, smooth);
}
if (params->use_smooth_even) {
smooth *= shell_v3v3_mid_normalized_to_dist(v_a->no, v_b->no);
}
smooth *= params->smooth;
if (smooth != 1.0f) {
float co_flat[3];
interp_v3_v3v3(co_flat, v_a->co, v_b->co, perc);
interp_v3_v3v3(co, co_flat, co, smooth);
}
#undef USE_SPHERE_DUAL_BLEND
}
if (params->use_fractal) {

@ -401,7 +401,7 @@ static void ringsel_finish(bContext *C, wmOperator *op)
{
RingSelOpData *lcd = op->customdata;
const int cuts = RNA_int_get(op->ptr, "number_cuts");
const float smoothness = 0.292f * RNA_float_get(op->ptr, "smoothness");
const float smoothness = RNA_float_get(op->ptr, "smoothness");
const int smooth_falloff = RNA_enum_get(op->ptr, "falloff");
#ifdef BMW_EDGERING_NGON
const bool use_only_quads = false;

@ -85,7 +85,7 @@ static int edbm_subdivide_exec(bContext *C, wmOperator *op)
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const int cuts = RNA_int_get(op->ptr, "number_cuts");
float smooth = 0.292f * RNA_float_get(op->ptr, "smoothness");
float smooth = RNA_float_get(op->ptr, "smoothness");
const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");
@ -96,7 +96,7 @@ static int edbm_subdivide_exec(bContext *C, wmOperator *op)
}
BM_mesh_esubdivide(em->bm, BM_ELEM_SELECT,
smooth, SUBD_FALLOFF_INVSQUARE, false,
smooth, SUBD_FALLOFF_LIN, false,
fractal, along_normal,
cuts,
SUBDIV_SELECT_ORIG, RNA_enum_get(op->ptr, "quadcorner"),