Integrated limits for volume preservation in stretch-to constraints.
authorLukas Tönne <lukas.toenne@gmail.com>
Mon, 13 Oct 2014 17:51:46 +0000 (19:51 +0200)
committerLukas Tönne <lukas.toenne@gmail.com>
Mon, 13 Oct 2014 17:58:53 +0000 (19:58 +0200)
Currently the volume variation feature in stretch constraints is
unlimited. This has to be compensated by riggers by adding scale limit
constraints, but these are unaware of the stretch orientation and can
lead to flipping. Also the stretch calculation itself is not working
properly and can lead to collapsing volume.

The patch fixes this with several modifications:
- Interpret the volume variation factor as exponent, which works better
  with large values for artistic purposes.
- Add integrated limits to the volume "bulge" factor, so secondary
  constraints for compensation become unnecessary
- Add a smoothness factor to make limits less visible.

Eventually a generic volume preservation constraint would be nicer,
because multiple constraints currently implement volume variation of
their own. This feature could actually work very nicely independent from
other constraint features.

Differential Revision: https://developer.blender.org/D826

release/scripts/startup/bl_ui/properties_constraint.py
source/blender/blenkernel/intern/constraint.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/intern/rna_constraint.c

index 4baf31a591ba7225329cc6c997ab5a33256d4dba..e6b62cae6ef279b7d344d7da0e290870b9abcbef 100644 (file)
@@ -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:")
index ee7bad773fafa72c2e23be2e341b6325a47332dd..e4b60c12d640be0438397fe94b690a53fccfb3a6 100644 (file)
@@ -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:
index 0277956cac5e70be11f50a3d133779416d96ba47..fb33879e2c17866bb6ecabe1a9a7aef8fc71c331 100644 (file)
@@ -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
index 5519b192ca49157bfff11aceeed56311aec1bf0d..aab2d35c0e42e1550a6a811bac8a5476a31582e8 100644 (file)
@@ -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)