Sculpt: Pose Brush Scale/Transform deform mode
authorPablo Dobarro <pablodp606@gmail.com>
Mon, 18 May 2020 23:04:37 +0000 (01:04 +0200)
committerPablo Dobarro <pablodp606@gmail.com>
Mon, 25 May 2020 15:35:39 +0000 (17:35 +0200)
This is an alternative deformation brush for the Pose Brush intended
quickly change the proportions of the mesh. The regular mode scales
using the segment's origin as a pivot. The inverted mode drags the
entire segment using the grab delta.

The only difference with the regular pose brush is that it is not
compatible with IK, so the option is disabled and set to 1 segment. The
rest of the options should work as expected.

Reviewed By: sergey

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

release/scripts/startup/bl_ui/properties_paint_common.py
source/blender/blenkernel/BKE_paint.h
source/blender/editors/sculpt_paint/sculpt_pose.c
source/blender/makesdna/DNA_brush_types.h
source/blender/makesrna/intern/rna_brush.c

index 92d421f63a865fd23369f460f77ca5061260f60c..dbe3ced04b3809e38c3be256f844a2452a7fb4f3 100644 (file)
@@ -619,10 +619,12 @@ def brush_settings(layout, context, brush, popover=False):
 
         if brush.sculpt_tool == 'POSE':
             layout.separator()
+            layout.prop(brush, "pose_deform_type")
             layout.prop(brush, "pose_origin_type")
             layout.prop(brush, "pose_offset")
             layout.prop(brush, "pose_smooth_iterations")
-            layout.prop(brush, "pose_ik_segments")
+            if brush.pose_deform_type == 'ROTATE_TWIST':
+              layout.prop(brush, "pose_ik_segments")
             layout.prop(brush, "use_pose_ik_anchored")
             layout.separator()
 
index a7ece2e3167390362e925c08003d36f288d1319b..ac292432acc493f07de3e1362ced052d92c0fc50 100644 (file)
@@ -239,6 +239,7 @@ typedef struct SculptPoseIKChainSegment {
   float initial_orig[3];
   float initial_head[3];
   float len;
+  float scale;
   float rot[4];
   float *weights;
 
index c7511dfc80f48fdd361eb6a98c2bbad5eded081a..b5e2652df7c171a11d20e030a95a26c9eb02b74d 100644 (file)
@@ -131,6 +131,32 @@ static void pose_solve_roll_chain(SculptPoseIKChain *ik_chain,
   }
 }
 
+static void pose_solve_translate_chain(SculptPoseIKChain *ik_chain, const float delta[3])
+{
+  SculptPoseIKChainSegment *segments = ik_chain->segments;
+  const int tot_segments = ik_chain->tot_segments;
+
+  for (int i = 0; i < tot_segments; i++) {
+    /* Move the origin and head of each segment by delta. */
+    add_v3_v3v3(segments[i].head, segments[i].initial_head, delta);
+    add_v3_v3v3(segments[i].orig, segments[i].initial_orig, delta);
+
+    /* Reset the segment rotation. */
+    unit_qt(segments[i].rot);
+  }
+}
+
+static void pose_solve_scale_chain(SculptPoseIKChain *ik_chain, const float scale)
+{
+  SculptPoseIKChainSegment *segments = ik_chain->segments;
+  const int tot_segments = ik_chain->tot_segments;
+
+  for (int i = 0; i < tot_segments; i++) {
+    /* Assign the scale to each segment. */
+    segments[i].scale = scale;
+  }
+}
+
 static void do_pose_brush_task_cb_ex(void *__restrict userdata,
                                      const int n,
                                      const TaskParallelTLS *__restrict UNUSED(tls))
@@ -600,7 +626,19 @@ static void pose_ik_chain_origin_heads_init(SculptPoseIKChain *ik_chain,
     copy_v3_v3(ik_chain->segments[i].initial_orig, origin);
     copy_v3_v3(ik_chain->segments[i].initial_head, head);
     ik_chain->segments[i].len = len_v3v3(head, origin);
+    ik_chain->segments[i].scale = 1.0f;
+  }
+}
+
+static int pose_brush_num_effective_segments(const Brush *brush)
+{
+  /* Scaling multiple segments at the same time is not supported as the IK solver can't handle
+   * changes in the segment's length. It will also required a better weight distribution to avoid
+   * artifacts in the areas affected by multiple segments. */
+  if (brush->pose_deform_type == BRUSH_POSE_DEFORM_SCALE_TRASLATE) {
+    return 1;
   }
+  return brush->pose_ik_segments;
 }
 
 static SculptPoseIKChain *pose_ik_chain_init_topology(Sculpt *sd,
@@ -629,7 +667,8 @@ static SculptPoseIKChain *pose_ik_chain_init_topology(Sculpt *sd,
 
   pose_factor_grow[nearest_vertex_index] = 1.0f;
 
-  SculptPoseIKChain *ik_chain = pose_ik_chain_new(br->pose_ik_segments, totvert);
+  const int tot_segments = pose_brush_num_effective_segments(br);
+  SculptPoseIKChain *ik_chain = pose_ik_chain_new(tot_segments, totvert);
 
   /* Calculate the first segment in the chain using the brush radius and the pose origin offset. */
   copy_v3_v3(next_chain_segment_target, initial_location);
@@ -688,7 +727,9 @@ static SculptPoseIKChain *pose_ik_chain_init_face_sets(
 
   int totvert = SCULPT_vertex_count_get(ss);
 
-  SculptPoseIKChain *ik_chain = pose_ik_chain_new(br->pose_ik_segments, totvert);
+  const int tot_segments = pose_brush_num_effective_segments(br);
+
+  SculptPoseIKChain *ik_chain = pose_ik_chain_new(tot_segments, totvert);
 
   GSet *visited_face_sets = BLI_gset_int_new_ex("visited_face_sets", ik_chain->tot_segments);
 
@@ -802,13 +843,86 @@ void SCULPT_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br
   MEM_SAFE_FREE(nodes);
 }
 
+static void sculpt_pose_do_translate_deform(SculptSession *ss, Brush *brush)
+{
+  SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+  BKE_curvemapping_initialize(brush->curve);
+  pose_solve_translate_chain(ik_chain, ss->cache->grab_delta);
+}
+
+static void sculpt_pose_do_scale_deform(SculptSession *ss, Brush *brush)
+{
+  float ik_target[3];
+  SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+
+  copy_v3_v3(ik_target, ss->cache->true_location);
+  add_v3_v3(ik_target, ss->cache->grab_delta);
+
+  /* Solve the IK for the first segment to include rotation as part of scale. */
+  pose_solve_ik_chain(ik_chain, ik_target, brush->flag2 & BRUSH_POSE_IK_ANCHORED);
+
+  /* Calculate a scale factor based on the grab delta. */
+  float plane[4];
+  float segment_dir[3];
+  sub_v3_v3v3(segment_dir, ik_chain->segments[0].initial_head, ik_chain->segments[0].initial_orig);
+  normalize_v3(segment_dir);
+  plane_from_point_normal_v3(plane, ik_chain->segments[0].initial_head, segment_dir);
+  const float segment_len = ik_chain->segments[0].len;
+  const float scale = segment_len / (segment_len - dist_signed_to_plane_v3(ik_target, plane));
+
+  /* Write the scale into the segments. */
+  pose_solve_scale_chain(ik_chain, scale);
+}
+
+static void sculpt_pose_do_twist_deform(SculptSession *ss, Brush *brush)
+{
+  SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+
+  /* Calculate the maximum roll. 0.02 radians per pixel works fine. */
+  float roll = (ss->cache->initial_mouse[0] - ss->cache->mouse[0]) * ss->cache->bstrength * 0.02f;
+  BKE_curvemapping_initialize(brush->curve);
+  pose_solve_roll_chain(ik_chain, brush, roll);
+}
+
+static void sculpt_pose_do_rotate_deform(SculptSession *ss, Brush *brush)
+{
+  float ik_target[3];
+  SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+
+  /* Calculate the IK target. */
+  copy_v3_v3(ik_target, ss->cache->true_location);
+  add_v3_v3(ik_target, ss->cache->grab_delta);
+
+  /* Solve the IK positions. */
+  pose_solve_ik_chain(ik_chain, ik_target, brush->flag2 & BRUSH_POSE_IK_ANCHORED);
+}
+
+static void sculpt_pose_do_rotate_twist_deform(SculptSession *ss, Brush *brush)
+{
+  if (ss->cache->invert) {
+    sculpt_pose_do_twist_deform(ss, brush);
+  }
+  else {
+    sculpt_pose_do_rotate_deform(ss, brush);
+  }
+}
+
+static void sculpt_pose_do_scale_translate_deform(SculptSession *ss, Brush *brush)
+{
+  if (ss->cache->invert) {
+    sculpt_pose_do_translate_deform(ss, brush);
+  }
+  else {
+    sculpt_pose_do_scale_deform(ss, brush);
+  }
+}
+
 /* Main Brush Function. */
 void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
 {
   SculptSession *ss = ob->sculpt;
   Brush *brush = BKE_paint_brush(&sd->paint);
   float grab_delta[3];
-  float ik_target[3];
   const ePaintSymmetryFlags symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
 
   /* The pose brush applies all enabled symmetry axis in a single iteration, so the rest can be
@@ -818,26 +932,15 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
   }
 
   SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+  copy_v3_v3(grab_delta, ss->cache->grab_delta);
 
-  /* Solve the positions and rotations of the IK chain. */
-  if (ss->cache->invert) {
-    /* Roll Mode. */
-    /* Calculate the maximum roll. 0.02 radians per pixel works fine. */
-    float roll = (ss->cache->initial_mouse[0] - ss->cache->mouse[0]) * ss->cache->bstrength *
-                 0.02f;
-    BKE_curvemapping_initialize(brush->curve);
-    pose_solve_roll_chain(ik_chain, brush, roll);
-  }
-  else {
-    /* IK follow target mode. */
-    /* Calculate the IK target. */
-
-    copy_v3_v3(grab_delta, ss->cache->grab_delta);
-    copy_v3_v3(ik_target, ss->cache->true_location);
-    add_v3_v3(ik_target, ss->cache->grab_delta);
-
-    /* Solve the IK positions. */
-    pose_solve_ik_chain(ik_chain, ik_target, brush->flag2 & BRUSH_POSE_IK_ANCHORED);
+  switch (brush->pose_deform_type) {
+    case BRUSH_POSE_DEFORM_ROTATE_TWIST:
+      sculpt_pose_do_rotate_twist_deform(ss, brush);
+      break;
+    case BRUSH_POSE_DEFORM_SCALE_TRASLATE:
+      sculpt_pose_do_scale_translate_deform(ss, brush);
+      break;
   }
 
   /* Flip the segment chain in all symmetry axis and calculate the transform matrices for each
@@ -845,7 +948,7 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
   /* This can be optimized by skipping the calculation of matrices where the symmetry is not
    * enabled. */
   for (int symm_it = 0; symm_it < PAINT_SYMM_AREAS; symm_it++) {
-    for (int i = 0; i < brush->pose_ik_segments; i++) {
+    for (int i = 0; i < ik_chain->tot_segments; i++) {
       float symm_rot[4];
       float symm_orig[3];
       float symm_initial_orig[3];
@@ -865,6 +968,7 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
       /* Create the transform matrix and store it in the segment. */
       unit_m4(ik_chain->segments[i].pivot_mat[symm_it]);
       quat_to_mat4(ik_chain->segments[i].trans_mat[symm_it], symm_rot);
+      mul_m4_fl(ik_chain->segments[i].trans_mat[symm_it], ik_chain->segments[i].scale);
 
       translate_m4(ik_chain->segments[i].trans_mat[symm_it],
                    symm_orig[0] - symm_initial_orig[0],
index c143c2a442aeedf278a1f06f65978d7a09fdb994..2e449f10563bd86f9e86bc06a998e7a3aa724836 100644 (file)
@@ -331,6 +331,11 @@ typedef enum eBrushClothForceFalloffType {
   BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
 } eBrushClothForceFalloffType;
 
+typedef enum eBrushPoseDeformType {
+  BRUSH_POSE_DEFORM_ROTATE_TWIST = 0,
+  BRUSH_POSE_DEFORM_SCALE_TRASLATE = 1,
+} eBrushPoseDeformType;
+
 typedef enum eBrushPoseOriginType {
   BRUSH_POSE_ORIGIN_TOPOLOGY = 0,
   BRUSH_POSE_ORIGIN_FACE_SETS = 1,
@@ -478,7 +483,7 @@ typedef struct Brush {
   char gpencil_sculpt_tool;
   /** Active grease pencil weight tool. */
   char gpencil_weight_tool;
-  char _pad1[6];
+  char _pad1[2];
 
   float autosmooth_factor;
 
@@ -510,6 +515,7 @@ typedef struct Brush {
   float elastic_deform_volume_preservation;
 
   /* pose */
+  int pose_deform_type;
   float pose_offset;
   int pose_smooth_iterations;
   int pose_ik_segments;
index d3b607fcb76f834335fe4384c577ae77cc5c8451..9e78eec9c250ca8a4d9d5538f10856dc36c39607 100644 (file)
@@ -1960,6 +1960,12 @@ static void rna_def_brush(BlenderRNA *brna)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static const EnumPropertyItem brush_pose_deform_type_items[] = {
+      {BRUSH_POSE_DEFORM_ROTATE_TWIST, "ROTATE_TWIST", 0, "Rotate/Twist", ""},
+      {BRUSH_POSE_DEFORM_SCALE_TRASLATE, "SCALE_TRANSLATE", 0, "Scale/Translate", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   static const EnumPropertyItem brush_pose_origin_type_items[] = {
       {BRUSH_POSE_ORIGIN_TOPOLOGY,
        "TOPOLOGY",
@@ -2095,6 +2101,11 @@ static void rna_def_brush(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
   RNA_def_property_update(prop, 0, "rna_Brush_update");
 
+  prop = RNA_def_property(srna, "pose_deform_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_items(prop, brush_pose_deform_type_items);
+  RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "pose_origin_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, brush_pose_origin_type_items);
   RNA_def_property_ui_text(prop,