diff --git a/release/scripts/startup/bl_ui/properties_constraint.py b/release/scripts/startup/bl_ui/properties_constraint.py index 4baf31a591b..e6b62cae6ef 100644 --- a/release/scripts/startup/bl_ui/properties_constraint.py +++ b/release/scripts/startup/bl_ui/properties_constraint.py @@ -502,6 +502,20 @@ class ConstraintButtonsPanel(): row.operator("constraint.stretchto_reset", text="Reset") layout.prop(con, "bulge", text="Volume Variation") + split = layout.split() + col = split.column(align=True) + col.prop(con, "use_bulge_min", text="Volume Min") + sub = col.column() + sub.active = con.use_bulge_min + sub.prop(con, "bulge_min", text="") + col = split.column(align=True) + col.prop(con, "use_bulge_max", text="Volume Max") + sub = col.column() + sub.active = con.use_bulge_max + sub.prop(con, "bulge_max", text="") + col = layout.column() + col.active = con.use_bulge_min or con.use_bulge_max + col.prop(con, "bulge_smooth", text="Smooth") row = layout.row() row.label(text="Volume:") diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index ee7bad773fa..e4b60c12d64 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -2658,7 +2658,7 @@ static void stretchto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t if (VALID_CONS_TARGET(ct)) { float size[3], scale[3], vec[3], xx[3], zz[3], orth[3]; float totmat[3][3]; - float dist; + float dist, bulge; /* store scaling before destroying obmat */ mat4_to_size(size, cob->matrix); @@ -2676,7 +2676,7 @@ static void stretchto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t /* vec[2] /= size[2];*/ /* dist = normalize_v3(vec);*/ - + dist = len_v3v3(cob->matrix[3], ct->matrix[3]); /* Only Y constrained object axis scale should be used, to keep same length when scaling it. */ dist /= size[1]; @@ -2684,23 +2684,60 @@ static void stretchto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t /* data->orglength==0 occurs on first run, and after 'R' button is clicked */ if (data->orglength == 0) data->orglength = dist; - if (data->bulge == 0) - data->bulge = 1.0; - + scale[1] = dist / data->orglength; + + bulge = powf(data->orglength / dist, data->bulge); + + if (data->flag & STRETCHTOCON_USE_BULGE_MAX) { + const float bulge_median = ((data->flag & STRETCHTOCON_USE_BULGE_MIN) ? + 0.5f * (data->bulge_min + data->bulge_max) : 0.0f); + const float bulge_range = data->bulge_max - bulge_median; + float x, bulge_smoothed; + + x = bulge_range != 0.0f ? (bulge - bulge_median) / bulge_range : 0.0f; + CLAMP(x, -1.0f, 1.0f); + bulge_smoothed = bulge_median + bulge_range * sinf(0.5f*M_PI * x); + + if (data->flag & STRETCHTOCON_USE_BULGE_MIN) { + CLAMP_MIN(bulge, data->bulge_min); + } + CLAMP_MAX(bulge, data->bulge_max); + + bulge = interpf(bulge_smoothed, bulge, data->bulge_smooth); + } + else if (data->flag & STRETCHTOCON_USE_BULGE_MIN) { + /* The quadratic formula below creates a smooth asymptote + * of the clamped bulge value. By scaling x with the inverse smooth factor + * the smoothed area becomes smaller ("less smoothing"). + * For very small smoothing factor below epsilon it is replaced + * by the clamped version to avoid floating point precision issues. + */ + const float epsilon = 0.000001f; + if (data->bulge_smooth < epsilon) { + CLAMP_MIN(bulge, data->bulge_min); + } + else { + float scale = data->bulge_smooth; + float inv_scale = 1.0f / scale; + float x = (bulge - data->bulge_min) * 0.5f * inv_scale; + bulge = scale * (x + sqrtf((x * x) + 1.0f)) + data->bulge_min; + } + } + switch (data->volmode) { /* volume preserving scaling */ case VOLUME_XZ: - scale[0] = 1.0f - sqrtf(data->bulge) + sqrtf(data->bulge * (data->orglength / dist)); + scale[0] = sqrtf(bulge); scale[2] = scale[0]; break; case VOLUME_X: - scale[0] = 1.0f + data->bulge * (data->orglength / dist - 1); + scale[0] = bulge; scale[2] = 1.0; break; case VOLUME_Z: scale[0] = 1.0; - scale[2] = 1.0f + data->bulge * (data->orglength / dist - 1); + scale[2] = bulge; break; /* don't care for volume */ case NO_VOLUME: diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 0277956cac5..fb33879e2c1 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -284,10 +284,14 @@ typedef struct bFollowPathConstraint { /* Stretch to constraint */ typedef struct bStretchToConstraint { struct Object *tar; + int flag; int volmode; - int plane; + int plane; float orglength; float bulge; + float bulge_min; + float bulge_max; + float bulge_smooth; char subtarget[64]; /* MAX_ID_NAME-2 */ } bStretchToConstraint; @@ -820,6 +824,12 @@ typedef enum eObjectSolver_Flags { #define CONSTRAINT_DRAW_PIVOT 0x40 #define CONSTRAINT_DISABLE_LINKED_COLLISION 0x80 +/* ObjectSolver Constraint -> flag */ +typedef enum eStretchTo_Flags { + STRETCHTOCON_USE_BULGE_MIN = (1 << 0), + STRETCHTOCON_USE_BULGE_MAX = (1 << 1), +} eStretchTo_Flags; + /* important: these defines need to match up with PHY_DynamicTypes headerfile */ #define CONSTRAINT_RB_BALL 1 #define CONSTRAINT_RB_HINGE 2 diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index 5519b192ca4..aab2d35c0e4 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -1365,6 +1365,31 @@ static void rna_def_constraint_stretch_to(BlenderRNA *brna) RNA_def_property_range(prop, 0.0, 100.f); RNA_def_property_ui_text(prop, "Volume Variation", "Factor between volume variation and stretching"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + + prop = RNA_def_property(srna, "use_bulge_min", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", STRETCHTOCON_USE_BULGE_MIN); + RNA_def_property_ui_text(prop, "Use Volume Variation Minimum", "Use lower limit for volume variation"); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + + prop = RNA_def_property(srna, "use_bulge_max", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", STRETCHTOCON_USE_BULGE_MAX); + RNA_def_property_ui_text(prop, "Use Volume Variation Maximum", "Use upper limit for volume variation"); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + + prop = RNA_def_property(srna, "bulge_min", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0.0, 100.f); + RNA_def_property_ui_text(prop, "Volume Variation Minimum", "Minimum volume stretching factor"); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + + prop = RNA_def_property(srna, "bulge_max", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0.0, 100.f); + RNA_def_property_ui_text(prop, "Volume Variation Maximum", "Maximum volume stretching factor"); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); + + prop = RNA_def_property(srna, "bulge_smooth", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0, 1.0f); + RNA_def_property_ui_text(prop, "Volume Variation Smoothness", ""); + RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); } static void rna_def_constraint_rigid_body_joint(BlenderRNA *brna)