Making Simple Deform Stretch Not Terrible

I’m currently working on making the Stretch mode in the Simple Deform modifier not terrible.

So far it seems to be okay, but one thing I can’t figure out is why the Z component doesn’t normalize properly based on the size of the object. Even when removing the code that scales the Z component, objects with a radius other than 1 don’t work properly in this mode, as if the code that normalizes the Z component doesn’t execute for some reason.

What I mean by “normalization” is remapping the Z component to the range [-1,1] based on the height of the object. This seems to work fine for twist/bend/taper modes, but not for stretch mode (even when removing the Z scale code like mentioned before). I dunno why, and I’d like some insight as to where in the code I can look.

[EDIT]

Here’s a diff of what I have so far if anyone wants to take a look themselves:

diff --git a/source/blender/modifiers/intern/MOD_simpledeform.c b/source/blender/modifiers/intern/MOD_simpledeform.c
index ec89176f97e..a0eea8e0aa6 100644
--- a/source/blender/modifiers/intern/MOD_simpledeform.c
+++ b/source/blender/modifiers/intern/MOD_simpledeform.c
@@ -118,13 +118,16 @@ static void simpleDeform_stretch(const float factor,
                                  float r_co[3])
 {
   float x = r_co[0], y = r_co[1], z = r_co[2];
-  float scale;
+  float ascale, wscale;
 
-  scale = (z * z * factor - factor + 1.0f);
+  /* Scale along main axis */
+  ascale = factor+1.0f;
+  /* Scale width-wise */
+  wscale = (sqrtf(1.0f-(z*z))*(sqrtf(ascale)-1.0f))+1.0f;
 
-  r_co[0] = x * scale;
-  r_co[1] = y * scale;
-  r_co[2] = z * (1.0f + factor);
+  r_co[0] = x / wscale;
+  r_co[1] = y / wscale;
+  r_co[2] = z * ascale;
 
   add_v3_v3(r_co, dcut);
 }

So I tried to revisit this small project, and I found a few things:

  1. The scale issue does indeed apply to all modes, not just stretch. For example, the amount of rotation for the twist and bend modes scales with the size of the object, so a 45 degree rotation for a cube with radius 3 will result in a 135 degree twist at each end. However this seems like it’s possible it could be intentional, so if anyone knows if this is the intended behavior or not, let me know.

  2. Changing the limits also affects the transform in an unintuitive way. The factor value is scaled according to the limit range when it doesn’t really make sense for it to be. I have modified the code to remove this dependency.

I reworked the stretch formula a bit, this time the equation ensures that for the ideal case of a cylinder or cuboid, volume is preserved. The same squash/stretch style behavior is still present. Note that the code assumes that the first observation was in fact unintentional, so it includes code to remap the coordinates along the limit axis to the range [-1,1].

diff --git a/source/blender/modifiers/intern/MOD_simpledeform.c b/source/blender/modifiers/intern/MOD_simpledeform.c
index ec89176f97e..19ca1611db4 100644
--- a/source/blender/modifiers/intern/MOD_simpledeform.c
+++ b/source/blender/modifiers/intern/MOD_simpledeform.c
@@ -97,13 +97,23 @@ static void axis_limit(const int axis, const float limits[2], float co[3], float
   co[axis] = val;
 }
 
+/* Re-maps from:  mesh_range[0] <= co <= mesh_range[1]
+   to:  -1 <= return <= 1.
+   This prevents issues where the effecive angle/scale is affected by the object size */
+static float remap_limit_axis(const float co, const float mesh_range[2])
+{
+	return (2.0 * co - mesh_range[0] - mesh_range[1]) / (mesh_range[1] - mesh_range[0]);
+}
+
 static void simpleDeform_taper(const float factor,
                                const int UNUSED(axis),
                                const float dcut[3],
-                               float r_co[3])
+                               float r_co[3],
+			       float mesh_range[2])
 {
   float x = r_co[0], y = r_co[1], z = r_co[2];
-  float scale = z * factor;
+  float t = remap_limit_axis(z, mesh_range);
+  float scale = t * factor;
 
   r_co[0] = x + x * scale;
   r_co[1] = y + y * scale;
@@ -115,12 +125,12 @@ static void simpleDeform_taper(const float factor,
 static void simpleDeform_stretch(const float factor,
                                  const int UNUSED(axis),
                                  const float dcut[3],
-                                 float r_co[3])
+                                 float r_co[3],
+			         float mesh_range[2])
 {
   float x = r_co[0], y = r_co[1], z = r_co[2];
-  float scale;
-
-  scale = (z * z * factor - factor + 1.0f);
+  float t = remap_limit_axis(z, mesh_range);
+  float scale = 1.0f / powf(1.0f - factor * (t * t - 1.0f), 0.75f);
 
   r_co[0] = x * scale;
   r_co[1] = y * scale;
@@ -132,14 +142,14 @@ static void simpleDeform_stretch(const float factor,
 static void simpleDeform_twist(const float factor,
                                const int UNUSED(axis),
                                const float *dcut,
-                               float r_co[3])
+                               float r_co[3],
+			       float mesh_range[2])
 {
   float x = r_co[0], y = r_co[1], z = r_co[2];
-  float theta, sint, cost;
-
-  theta = z * factor;
-  sint = sinf(theta);
-  cost = cosf(theta);
+  float t = remap_limit_axis(z, mesh_range);
+  float theta = t * factor;
+  float sint = sinf(theta);
+  float cost = cosf(theta);
 
   r_co[0] = x * cost - y * sint;
   r_co[1] = x * sint + y * cost;
@@ -151,10 +161,13 @@ static void simpleDeform_twist(const float factor,
 static void simpleDeform_bend(const float factor,
                               const int axis,
                               const float dcut[3],
-                              float r_co[3])
+                              float r_co[3],
+			      float mesh_range[2])
 {
   float x = r_co[0], y = r_co[1], z = r_co[2];
-  float theta, sint, cost;
+  float tx = remap_limit_axis(x, mesh_range);
+  float tz = remap_limit_axis(z, mesh_range);
+  float theta;
 
   BLI_assert(!(fabsf(factor) < BEND_EPS));
 
@@ -162,13 +175,13 @@ static void simpleDeform_bend(const float factor,
     case 0:
       ATTR_FALLTHROUGH;
     case 1:
-      theta = z * factor;
+      theta = tz * factor;
       break;
     default:
-      theta = x * factor;
+      theta = tx * factor;
   }
-  sint = sinf(theta);
-  cost = cosf(theta);
+  float sint = sinf(theta);
+  float cost = cosf(theta);
 
   switch (axis) {
     case 0:
@@ -213,12 +226,13 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd,
 {
   const float base_limit[2] = {0.0f, 0.0f};
   int i;
-  float smd_limit[2], smd_factor;
+  float smd_limit[2], smd_limit_range[2];
   SpaceTransform *transf = NULL, tmp_transf;
   void (*simpleDeform_callback)(const float factor,
                                 const int axis,
                                 const float dcut[3],
-                                float co[3]) = NULL; /* Mode callback */
+                                float co[3],
+				float mesh_range[2]) = NULL; /* Mode callback */
   int vgroup;
   MDeformVert *dvert;
 
@@ -296,8 +310,10 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd,
     /* SMD values are normalized to the BV, calculate the absolute values */
     smd_limit[1] = lower + (upper - lower) * smd->limit[1];
     smd_limit[0] = lower + (upper - lower) * smd->limit[0];
-
-    smd_factor = smd->factor / max_ff(FLT_EPSILON, smd_limit[1] - smd_limit[0]);
+    
+    /* Needed for deform callback */
+    smd_limit_range[0] = lower;
+    smd_limit_range[1] = upper;
   }
 
   switch (smd->mode) {
@@ -318,7 +334,7 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd,
   }
 
   if (smd->mode == MOD_SIMPLEDEFORM_MODE_BEND) {
-    if (fabsf(smd_factor) < BEND_EPS) {
+    if (fabsf(smd->factor) < BEND_EPS) {
       return;
     }
   }
@@ -361,7 +377,7 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd,
       float dcut_remap[3];
       copy_v3_v3_map(co_remap, co, axis_map);
       copy_v3_v3_map(dcut_remap, dcut, axis_map);
-      simpleDeform_callback(smd_factor, deform_axis, dcut_remap, co_remap); /* apply deform */
+      simpleDeform_callback(smd->factor, deform_axis, dcut_remap, co_remap, smd_limit_range); /* apply deform */
       copy_v3_v3_unmap(co, co_remap, axis_map);
 
       /* Use vertex weight has coef of linear interpolation */