Sculpt: Cloth brush
authorPablo Dobarro <pablodp606@gmail.com>
Fri, 28 Feb 2020 13:40:40 +0000 (14:40 +0100)
committerPablo Dobarro <pablodp606@gmail.com>
Fri, 28 Feb 2020 16:03:20 +0000 (17:03 +0100)
This brush has a simple physics solver that helps when sculpting cloth.

- The mass and the damping properties of the simulation are properties of the brush.
- It has two additional radius control to limit the influence and falloff of the simulation.
- Masked vertices are pinned in the simulation, and it applies the sculpt gravity directly in the solver.
- The Cloth Brush has 7 deformation modes with 2 falloff types (radial and plane).

The brush can create the constraints only on the required PBVH nodes, so the simulation is isolated on high poly meshes. As long
as the brush size is not too big it should be possible to keep it real time.

Known issues:
- The way constraints are created is extremely basic and it creates repeated constraints. Maybe there is another way to create fewer constraints while keeping the simulation quality decent. This part can also be multithreaded. (As it is it works ok, but it could be better)

Reviewed By: jbakker

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

14 files changed:
release/scripts/startup/bl_ui/properties_paint_common.py
source/blender/blenkernel/BKE_paint.h
source/blender/blenkernel/intern/brush.c
source/blender/editors/sculpt_paint/CMakeLists.txt
source/blender/editors/sculpt_paint/paint_cursor.c
source/blender/editors/sculpt_paint/paint_stroke.c
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_cloth.c [new file with mode: 0644]
source/blender/editors/sculpt_paint/sculpt_intern.h
source/blender/gpu/GPU_immediate_util.h
source/blender/gpu/intern/gpu_immediate_util.c
source/blender/makesdna/DNA_brush_defaults.h
source/blender/makesdna/DNA_brush_types.h
source/blender/makesrna/intern/rna_brush.c

index 24016061194b6d7b4036cbf26f5dd2b8baa09eb0..704af1ae30724645eeca97472a7979c3d25a0cb2 100644 (file)
@@ -626,6 +626,18 @@ def brush_settings(layout, context, brush, popover=False):
             layout.prop(brush, "pose_ik_segments")
             layout.prop(brush, "use_pose_ik_anchored")
             layout.separator()
+
+        if brush.sculpt_tool == 'CLOTH':
+            layout.separator()
+            layout.prop(brush, "cloth_sim_limit")
+            layout.prop(brush, "cloth_sim_falloff")
+            layout.separator()
+            layout.prop(brush, "cloth_deform_type")
+            layout.prop(brush, "cloth_force_falloff_type")
+            layout.separator()
+            layout.prop(brush, "cloth_mass")
+            layout.prop(brush, "cloth_damping")
+            layout.separator()
         
         if brush.sculpt_tool == 'SCRAPE':
             row = layout.row()
index 28e564f0fe2cc1b7ab7113b3b631123b9b9caa6b..4c274804a0747147f64d8806cd083276e1349e39 100644 (file)
@@ -245,6 +245,31 @@ typedef struct SculptPoseIKChain {
   int tot_segments;
 } SculptPoseIKChain;
 
+/* Cloth Brush */
+
+typedef struct SculptClothLengthConstraint {
+  int v1;
+  int v2;
+
+  float length;
+} SculptClothLengthConstraint;
+
+typedef struct SculptClothSimulation {
+  SculptClothLengthConstraint *length_constraints;
+  int tot_length_constraints;
+  int capacity_length_constraints;
+  float *length_constraint_tweak;
+
+  float mass;
+  float damping;
+
+  float (*acceleration)[3];
+  float (*pos)[3];
+  float (*init_pos)[3];
+  float (*prev_pos)[3];
+
+} SculptClothSimulation;
+
 /* Session data (mode-specific) */
 
 typedef struct SculptSession {
@@ -298,6 +323,7 @@ typedef struct SculptSession {
   float cursor_radius;
   float cursor_location[3];
   float cursor_normal[3];
+  float cursor_sampled_normal[3];
   float cursor_view_normal[3];
 
   /* TODO(jbakker): Replace rv3d adn v3d with ViewContext */
index 15ef647900772c50bdb803797e5593c565552059..09b3ad89e73c7d3259b9cbfc7e206a0ad15a28c6 100644 (file)
@@ -1014,6 +1014,14 @@ void BKE_brush_sculpt_reset(Brush *br)
       br->flag &= ~BRUSH_SPACE;
       br->flag &= ~BRUSH_SPACE_ATTEN;
       break;
+    case SCULPT_TOOL_CLOTH:
+      br->cloth_mass = 1.0f;
+      br->cloth_damping = 0.01f;
+      br->cloth_sim_limit = 2.5f;
+      br->cloth_sim_falloff = 0.75f;
+      br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG;
+      br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE);
+      break;
     default:
       break;
   }
@@ -1081,6 +1089,15 @@ void BKE_brush_sculpt_reset(Brush *br)
       br->sub_col[1] = 0.750000;
       br->sub_col[2] = 0.750000;
       break;
+
+    case SCULPT_TOOL_CLOTH:
+      br->add_col[0] = 1.0f;
+      br->add_col[1] = 0.5f;
+      br->add_col[2] = 0.1f;
+      br->sub_col[0] = 1.0f;
+      br->sub_col[1] = 0.5f;
+      br->sub_col[2] = 0.1f;
+      break;
     default:
       break;
   }
index a5cc262ddcd3a32a611ef0be64c45bfbef4ca6a0..2522bc0f5cc518227c8878e0878bf1841f7ebb27 100644 (file)
@@ -59,6 +59,7 @@ set(SRC
   paint_vertex_weight_ops.c
   paint_vertex_weight_utils.c
   sculpt.c
+  sculpt_cloth.c
   sculpt_undo.c
   sculpt_uv.c
 
index d5bb552b470127e633b3d018e1dd261ef39f4ff4..cabf17e8534775a2e7bbf41a30bd5551d5e8ea4b 100644 (file)
@@ -1526,11 +1526,21 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
           immUniformColor3fvAlpha(outline_col, outline_alpha);
           GPU_line_width(2.0f);
           imm_draw_circle_wire_3d(pos, 0, 0, rds, 80);
+
           GPU_line_width(1.0f);
           immUniformColor3fvAlpha(outline_col, outline_alpha * 0.5f);
           imm_draw_circle_wire_3d(pos, 0, 0, rds * clamp_f(brush->alpha, 0.0f, 1.0f), 80);
           GPU_matrix_pop();
 
+          /* Cloth brush simulation areas. */
+          if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
+            GPU_matrix_push();
+            const float white[3] = {1.0f, 1.0f, 1.0f};
+            SCULPT_cloth_simulation_limits_draw(
+                pos, brush, vc.obact->obmat, gi.location, gi.normal, rds, 1.0f, white, 0.25f);
+            GPU_matrix_pop();
+          }
+
           /* Update and draw dynamic mesh preview lines. */
           GPU_matrix_push();
           GPU_matrix_mul(vc.obact->obmat);
@@ -1620,6 +1630,48 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
             GPU_matrix_pop_projection();
           }
 
+          if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && !ss->cache->first_time) {
+            GPU_matrix_push_projection();
+            ED_view3d_draw_setup_view(CTX_wm_window(C),
+                                      CTX_data_depsgraph_pointer(C),
+                                      CTX_data_scene(C),
+                                      ar,
+                                      CTX_wm_view3d(C),
+                                      NULL,
+                                      NULL,
+                                      NULL);
+
+            /* Plane falloff preview */
+            if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) {
+              GPU_matrix_push();
+              GPU_matrix_mul(vc.obact->obmat);
+              SCULPT_cloth_plane_falloff_preview_draw(pos, ss, outline_col, outline_alpha);
+              GPU_matrix_pop();
+            }
+
+            /* Display the simulation limits if sculpting outside them. */
+            /* This does not makes much sense of plane fallof as the fallof is infinte. */
+            else if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_RADIAL) {
+              if (len_v3v3(ss->cache->true_location, ss->cache->true_initial_location) >
+                  ss->cache->radius * (1.0f + brush->cloth_sim_limit)) {
+                const float red[3] = {1.0f, 0.2f, 0.2f};
+                GPU_matrix_push();
+                SCULPT_cloth_simulation_limits_draw(pos,
+                                                    brush,
+                                                    vc.obact->obmat,
+                                                    ss->cache->true_initial_location,
+                                                    ss->cache->true_initial_normal,
+                                                    ss->cache->radius,
+                                                    2.0f,
+                                                    red,
+                                                    0.8f);
+                GPU_matrix_pop();
+              }
+            }
+
+            GPU_matrix_pop_projection();
+          }
+
           wmWindowViewport(win);
         }
       }
index 372ea9546301b0c05660f8d2166111934e674c64..4abeb937758e4a7c2d49e0732fb96b3a3990871e 100644 (file)
@@ -228,6 +228,10 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode)
                SCULPT_TOOL_THUMB)) {
         return false;
       }
+      else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
+               brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
+        return false;
+      }
       else {
         return true;
       }
@@ -259,6 +263,7 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m
                SCULPT_TOOL_THUMB,
                SCULPT_TOOL_SNAKE_HOOK,
                SCULPT_TOOL_ELASTIC_DEFORM,
+               SCULPT_TOOL_CLOTH,
                SCULPT_TOOL_POSE)) {
         return false;
       }
@@ -999,6 +1004,10 @@ bool paint_space_stroke_enabled(Brush *br, ePaintMode mode)
 
 static bool sculpt_is_grab_tool(Brush *br)
 {
+
+  if (br->sculpt_tool == SCULPT_TOOL_CLOTH && br->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
+    return true;
+  }
   return ELEM(br->sculpt_tool,
               SCULPT_TOOL_GRAB,
               SCULPT_TOOL_ELASTIC_DEFORM,
index 55ddc82c3f8d880a8d1d37d65466378e174bcacb..9a01be9d7b3d3cdb5e8cebabd207d2478a111a13 100644 (file)
@@ -112,7 +112,7 @@ static void sculpt_vertex_random_access_init(SculptSession *ss)
   }
 }
 
-static int sculpt_vertex_count_get(SculptSession *ss)
+int sculpt_vertex_count_get(SculptSession *ss)
 {
   switch (BKE_pbvh_type(ss->pbvh)) {
     case PBVH_FACES:
@@ -171,7 +171,7 @@ static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3])
   }
 }
 
-static float sculpt_vertex_mask_get(SculptSession *ss, int index)
+float sculpt_vertex_mask_get(SculptSession *ss, int index)
 {
   BMVert *v;
   float *mask;
@@ -220,22 +220,6 @@ static void sculpt_active_vertex_normal_get(SculptSession *ss, float normal[3])
 
 #define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
 
-typedef struct SculptVertexNeighborIter {
-  /* Storage */
-  int *neighbors;
-  int size;
-  int capacity;
-  int neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY];
-
-  /* Internal iterator. */
-  int num_duplicates;
-  int i;
-
-  /* Public */
-  int index;
-  bool is_duplicate;
-} SculptVertexNeighborIter;
-
 static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neighbor_index)
 {
   for (int i = 0; i < iter->size; i++) {
@@ -342,10 +326,10 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
   }
 }
 
-static void sculpt_vertex_neighbors_get(SculptSession *ss,
-                                        const int index,
-                                        const bool include_duplicates,
-                                        SculptVertexNeighborIter *iter)
+void sculpt_vertex_neighbors_get(SculptSession *ss,
+                                 const int index,
+                                 const bool include_duplicates,
+                                 SculptVertexNeighborIter *iter)
 {
   switch (BKE_pbvh_type(ss->pbvh)) {
     case PBVH_FACES:
@@ -630,7 +614,8 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
 
 static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
 {
-  return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE);
+  return ELEM(
+      sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE, SCULPT_TOOL_CLOTH);
 }
 
 static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush)
@@ -652,6 +637,7 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, const Brush *brush
                SCULPT_TOOL_CREASE,
                SCULPT_TOOL_DRAW,
                SCULPT_TOOL_DRAW_SHARP,
+               SCULPT_TOOL_CLOTH,
                SCULPT_TOOL_LAYER,
                SCULPT_TOOL_NUDGE,
                SCULPT_TOOL_ROTATE,
@@ -1820,6 +1806,17 @@ static float brush_strength(const Sculpt *sd,
     case SCULPT_TOOL_DRAW_SHARP:
     case SCULPT_TOOL_LAYER:
       return alpha * flip * pressure * overlap * feather;
+    case SCULPT_TOOL_CLOTH:
+      /* Ex/pand is more sensible to strength as it keeps expanding the cloth when sculpting over
+       * the same vertices. */
+      if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) {
+        return 0.1f * alpha * flip * pressure * overlap * feather;
+      }
+      else {
+        /* Multiply by 10 by default to get a larger range of strength depending on the size of the
+         * brush and object. */
+        return 10.0f * alpha * flip * pressure * overlap * feather;
+      }
     case SCULPT_TOOL_SLIDE_RELAX:
       return alpha * pressure * overlap * feather * 2.0f;
     case SCULPT_TOOL_CLAY_STRIPS:
@@ -4416,6 +4413,94 @@ static void sculpt_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Br
   MEM_SAFE_FREE(nodes);
 }
 
+void SCULPT_calc_brush_plane(
+    Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3])
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  zero_v3(r_area_co);
+  zero_v3(r_area_no);
+
+  if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_symmetry_pass == 0 &&
+      ss->cache->tile_pass == 0 &&
+      (ss->cache->first_time || !(brush->flag & BRUSH_ORIGINAL_PLANE) ||
+       !(brush->flag & BRUSH_ORIGINAL_NORMAL))) {
+    switch (brush->sculpt_plane) {
+      case SCULPT_DISP_DIR_VIEW:
+        copy_v3_v3(r_area_no, ss->cache->true_view_normal);
+        break;
+
+      case SCULPT_DISP_DIR_X:
+        ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f);
+        break;
+
+      case SCULPT_DISP_DIR_Y:
+        ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f);
+        break;
+
+      case SCULPT_DISP_DIR_Z:
+        ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f);
+        break;
+
+      case SCULPT_DISP_DIR_AREA:
+        calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
+        if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
+          project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal);
+          normalize_v3(r_area_no);
+        }
+        break;
+
+      default:
+        break;
+    }
+
+    /* For flatten center. */
+    /* fFlatten center has not been calculated yet if we are not using the area normal. */
+    if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) {
+      calc_area_center(sd, ob, nodes, totnode, r_area_co);
+    }
+
+    /* For area normal. */
+    if ((!ss->cache->first_time) && (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
+      copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
+    }
+    else {
+      copy_v3_v3(ss->cache->sculpt_normal, r_area_no);
+    }
+
+    /* For flatten center. */
+    if ((!ss->cache->first_time) && (brush->flag & BRUSH_ORIGINAL_PLANE)) {
+      copy_v3_v3(r_area_co, ss->cache->last_center);
+    }
+    else {
+      copy_v3_v3(ss->cache->last_center, r_area_co);
+    }
+  }
+  else {
+    /* For area normal. */
+    copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
+
+    /* For flatten center. */
+    copy_v3_v3(r_area_co, ss->cache->last_center);
+
+    /* For area normal. */
+    flip_v3(r_area_no, ss->cache->mirror_symmetry_pass);
+
+    /* For flatten center. */
+    flip_v3(r_area_co, ss->cache->mirror_symmetry_pass);
+
+    /* For area normal. */
+    mul_m4_v3(ss->cache->symm_rot_mat, r_area_no);
+
+    /* For flatten center. */
+    mul_m4_v3(ss->cache->symm_rot_mat, r_area_co);
+
+    /* Shift the plane for the current tile. */
+    add_v3_v3(r_area_co, ss->cache->plane_offset);
+  }
+}
+
 static void do_nudge_brush_task_cb_ex(void *__restrict userdata,
                                       const int n,
                                       const TaskParallelTLS *__restrict tls)
@@ -5016,7 +5101,7 @@ static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totno
   float displace;
   float temp[3];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
 
   displace = radius * offset;
 
@@ -5175,7 +5260,7 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
   float area_co[3];
   float temp[3];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
 
   SculptThreadedTaskData sample_data = {
       .sd = NULL,
@@ -5414,7 +5499,7 @@ static void do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes,
   float temp[3];
   float mat[4][4];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
 
   if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
     calc_area_normal(sd, ob, nodes, totnode, area_no);
@@ -5645,7 +5730,7 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
   float scale[4][4];
   float tmat[4][4];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
 
   if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
     calc_area_normal(sd, ob, nodes, totnode, area_no);
@@ -5769,7 +5854,7 @@ static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
 
   float temp[3];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
 
   displace = radius * offset;
 
@@ -5861,7 +5946,7 @@ static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
 
   float temp[3];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
 
   displace = -radius * offset;
 
@@ -5996,7 +6081,7 @@ static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
   float scale[4][4];
   float tmat[4][4];
 
-  calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+  SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
 
   if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
     calc_area_normal(sd, ob, nodes, totnode, area_no);
@@ -6275,6 +6360,17 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
   if (ELEM(brush->sculpt_tool, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE)) {
     BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
   }
+  else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
+    SculptSearchSphereData data = {
+        .ss = ss,
+        .sd = sd,
+        .radius_squared = SQUARE(ss->cache->radius * (1.0 + brush->cloth_sim_limit)),
+        .original = false,
+        .ignore_fully_masked = false,
+        .center = ss->cache->initial_location,
+    };
+    BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode);
+  }
   else {
     const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
                                                                                ss->cache->original;
@@ -6407,6 +6503,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
       case SCULPT_TOOL_SLIDE_RELAX:
         do_slide_relax_brush(sd, ob, nodes, totnode);
         break;
+      case SCULPT_TOOL_CLOTH:
+        SCULPT_do_cloth_brush(sd, ob, nodes, totnode);
+        break;
     }
 
     if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) &&
@@ -6428,7 +6527,8 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
       bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor);
     }
 
-    if (ss->cache->supports_gravity) {
+    /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
+    if (ss->cache->supports_gravity && brush->sculpt_tool != SCULPT_TOOL_CLOTH) {
       do_gravity(sd, ob, nodes, totnode, sd->gravity_factor);
     }
 
@@ -6663,6 +6763,9 @@ void sculpt_cache_calc_brushdata_symm(StrokeCache *cache,
   flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm);
   flip_v3_v3(cache->view_normal, cache->true_view_normal, symm);
 
+  flip_v3_v3(cache->initial_location, cache->true_initial_location, symm);
+  flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm);
+
   /* XXX This reduces the length of the grab delta if it approaches the line of symmetry
    * XXX However, a different approach appears to be needed. */
 #if 0
@@ -6925,6 +7028,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
       return "Multi-plane Scrape Brush";
     case SCULPT_TOOL_SLIDE_RELAX:
       return "Slide/Relax Brush";
+    case SCULPT_TOOL_CLOTH:
+      return "Cloth Brush";
   }
 
   return "Sculpting";
@@ -6942,6 +7047,11 @@ void sculpt_cache_free(StrokeCache *cache)
   if (cache->pose_ik_chain) {
     sculpt_pose_ik_chain_free(cache->pose_ik_chain);
   }
+
+  if (cache->cloth_sim) {
+    SCULPT_cloth_simulation_free(cache->cloth_sim);
+  }
+
   MEM_freeN(cache);
 }
 
@@ -7018,6 +7128,12 @@ static void sculpt_update_cache_invariants(
     zero_v2(cache->initial_mouse);
   }
 
+  copy_v3_v3(cache->initial_location, ss->cursor_location);
+  copy_v3_v3(cache->true_initial_location, ss->cursor_location);
+
+  copy_v3_v3(cache->initial_normal, ss->cursor_normal);
+  copy_v3_v3(cache->true_initial_normal, ss->cursor_normal);
+
   mode = RNA_enum_get(op->ptr, "mode");
   cache->invert = mode == BRUSH_STROKE_INVERT;
   cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH;
@@ -7193,6 +7309,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
   if (ELEM(tool,
            SCULPT_TOOL_GRAB,
            SCULPT_TOOL_ELASTIC_DEFORM,
+           SCULPT_TOOL_CLOTH,
            SCULPT_TOOL_NUDGE,
            SCULPT_TOOL_CLAY_STRIPS,
            SCULPT_TOOL_PINCH,
@@ -7234,6 +7351,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
           break;
         case SCULPT_TOOL_CLAY_STRIPS:
         case SCULPT_TOOL_PINCH:
+        case SCULPT_TOOL_CLOTH:
         case SCULPT_TOOL_MULTIPLANE_SCRAPE:
         case SCULPT_TOOL_CLAY_THUMB:
         case SCULPT_TOOL_NUDGE:
@@ -7448,7 +7566,8 @@ static bool sculpt_needs_connectivity_info(const Brush *brush, SculptSession *ss
           (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) ||
           ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
           (brush->sculpt_tool == SCULPT_TOOL_POSE) ||
-          (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX));
+          (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) ||
+          (brush->sculpt_tool == SCULPT_TOOL_CLOTH));
 }
 
 static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
@@ -7687,6 +7806,7 @@ bool sculpt_cursor_geometry_info_update(bContext *C,
   /* Calculate the sampled normal. */
   if (sculpt_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) {
     copy_v3_v3(out->normal, sampled_normal);
+    copy_v3_v3(ss->cursor_sampled_normal, sampled_normal);
   }
   else {
     /* Use face normal when there are no vertices to sample inside the cursor radius. */
@@ -7806,6 +7926,10 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
     need_mask = true;
   }
 
+  if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
+    need_mask = true;
+  }
+
   view3d_operator_needs_opengl(C);
   sculpt_brush_init_tex(scene, sd, ss);
 
@@ -7820,7 +7944,8 @@ static void sculpt_restore_mesh(Sculpt *sd, Object *ob)
   /* Restore the mesh before continuing with anchored stroke. */
   if ((brush->flag & BRUSH_ANCHORED) ||
       ((brush->sculpt_tool == SCULPT_TOOL_GRAB ||
-        brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) &&
+        brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM ||
+        brush->sculpt_tool == SCULPT_TOOL_CLOTH) &&
        BKE_brush_use_size_pressure(brush)) ||
       (brush->flag & BRUSH_DRAG_DOT)) {
     paint_mesh_restore_co(sd, ob);
diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c
new file mode 100644 (file)
index 0000000..7474a7d
--- /dev/null
@@ -0,0 +1,679 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software  Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+#include "BLI_dial_2d.h"
+#include "BLI_gsqueue.h"
+#include "BLI_ghash.h"
+#include "BLI_hash.h"
+#include "BLI_task.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_customdata_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_brush_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_image.h"
+#include "BKE_kelvinlet.h"
+#include "BKE_key.h"
+#include "BKE_lib_id.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_mesh_mirror.h"
+#include "BKE_modifier.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_particle.h"
+#include "BKE_pbvh.h"
+#include "BKE_pointcache.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_screen.h"
+#include "BKE_subdiv_ccg.h"
+#include "BKE_subsurf.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+#include "WM_message.h"
+#include "WM_toolsystem.h"
+
+#include "ED_sculpt.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "GPU_draw.h"
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "bmesh.h"
+#include "bmesh_tools.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CLOTH_LENGTH_CONSTRAINTS_BLOCK 100000
+#define CLOTH_SIMULATION_ITERATIONS 5
+#define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024
+#define CLOTH_SIMULATION_TIME_STEP 0.01f
+
+static void cloth_brush_add_length_constraint(SculptSession *ss, const int v1, const int v2)
+{
+  SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
+  cloth_sim->length_constraints[cloth_sim->tot_length_constraints].v1 = v1;
+  cloth_sim->length_constraints[cloth_sim->tot_length_constraints].v2 = v2;
+  cloth_sim->length_constraints[cloth_sim->tot_length_constraints].length = len_v3v3(
+      sculpt_vertex_co_get(ss, v1), sculpt_vertex_co_get(ss, v2));
+
+  cloth_sim->tot_length_constraints++;
+
+  /* Reallocation if the array capacity is exceeded. */
+  if (cloth_sim->tot_length_constraints >= cloth_sim->capacity_length_constraints) {
+    cloth_sim->capacity_length_constraints += CLOTH_LENGTH_CONSTRAINTS_BLOCK;
+    cloth_sim->length_constraints = MEM_reallocN_id(cloth_sim->length_constraints,
+                                                    cloth_sim->capacity_length_constraints *
+                                                        sizeof(SculptClothLengthConstraint),
+                                                    "length constraints");
+  }
+}
+
+static void do_cloth_brush_build_constraints_task_cb_ex(
+    void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+
+  PBVHVertexIter vd;
+  const float radius = ss->cache->initial_radius;
+  const float limit = radius + (radius * data->brush->cloth_sim_limit);
+
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    if (len_squared_v3v3(vd.co, ss->cache->initial_location) < limit * limit) {
+
+      SculptVertexNeighborIter ni;
+      int build_indices[CLOTH_MAX_CONSTRAINTS_PER_VERTEX];
+      int tot_indices = 0;
+      build_indices[tot_indices] = vd.index;
+      tot_indices++;
+      sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
+      {
+        build_indices[tot_indices] = ni.index;
+        tot_indices++;
+      }
+      sculpt_vertex_neighbors_iter_end(ni);
+
+      /* As we don't know the order of the neighbor vertices, we create all possible combinations
+       * between the neighbor and the original vertex as length constraints. */
+      /* This results on a pattern that contains structural, shear and bending constraints for all
+       * vertices, but constraints are repeated taking more memory than necessary. */
+
+      for (int c_i = 0; c_i < tot_indices; c_i++) {
+        for (int c_j = 0; c_j < tot_indices; c_j++) {
+          if (c_i != c_j) {
+            cloth_brush_add_length_constraint(ss, build_indices[c_i], build_indices[c_j]);
+          }
+        }
+      }
+    }
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
+static float cloth_brush_simulation_falloff_get(const Brush *brush,
+                                                const float radius,
+                                                const float location[3],
+                                                const float co[3])
+{
+  const float distance = len_v3v3(location, co);
+  const float limit = radius + (radius * brush->cloth_sim_limit);
+  const float falloff = radius + (radius * brush->cloth_sim_limit * brush->cloth_sim_falloff);
+
+  if (distance > limit) {
+    /* Outiside the limits. */
+    return 0.0f;
+  }
+  else if (distance < falloff) {
+    /* Before the falloff area. */
+    return 1.0f;
+  }
+  else {
+    /* Do a smoothstep transition inside the falloff area. */
+    float p = 1.0f - ((distance - falloff) / (limit - falloff));
+    return 3.0f * p * p - 2.0f * p * p * p;
+  }
+}
+
+static void cloth_brush_apply_force_to_vertex(SculptSession *ss,
+                                              const float force[3],
+                                              const int vertex_index)
+{
+  SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
+  madd_v3_v3fl(cloth_sim->acceleration[vertex_index], force, 1.0f / cloth_sim->mass);
+}
+
+static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
+                                                   const int n,
+                                                   const TaskParallelTLS *__restrict tls)
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+  const Brush *brush = data->brush;
+  SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
+  const float *offset = data->offset;
+  const float *grab_delta = data->grab_delta;
+  float(*imat)[4] = data->mat;
+
+  const bool use_falloff_plane = brush->cloth_force_falloff_type ==
+                                 BRUSH_CLOTH_FORCE_FALLOFF_PLANE;
+
+  PBVHVertexIter vd;
+  const float bstrength = ss->cache->bstrength;
+
+  SculptBrushTest test;
+  SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+      ss, &test, data->brush->falloff_shape);
+
+  /* For Pich Perpendicular Deform Type. */
+  float x_object_space[3];
+  float z_object_space[3];
+  if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR) {
+    normalize_v3_v3(x_object_space, imat[0]);
+    normalize_v3_v3(z_object_space, imat[2]);
+  }
+
+  /* For Plane Force Falloff. */
+  float deform_plane[4];
+  float plane_normal[3];
+  if (use_falloff_plane) {
+    normalize_v3_v3(plane_normal, grab_delta);
+    plane_from_point_normal_v3(deform_plane, data->area_co, plane_normal);
+  }
+
+  /* Gravity */
+  float gravity[3] = {0.0f};
+  if (ss->cache->supports_gravity) {
+    madd_v3_v3fl(
+        gravity, ss->cache->gravity_direction, -ss->cache->radius * data->sd->gravity_factor);
+  }
+
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    float force[3];
+    const float sim_factor = cloth_brush_simulation_falloff_get(
+        brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
+
+    /* When using the plane falloff mode the falloff is not constrained by the brush radius. */
+    if (sculpt_brush_test_sq_fn(&test, vd.co) || use_falloff_plane) {
+
+      float dist = sqrtf(test.dist);
+
+      if (use_falloff_plane) {
+        dist = dist_to_plane_v3(vd.co, deform_plane);
+      }
+
+      const float fade = sim_factor * bstrength *
+                         tex_strength(ss,
+                                      brush,
+                                      vd.co,
+                                      dist,
+                                      vd.no,
+                                      vd.fno,
+                                      vd.mask ? *vd.mask : 0.0f,
+                                      vd.index,
+                                      tls->thread_id);
+
+      float brush_disp[3];
+      float normal[3];
+
+      if (vd.no) {
+        normal_short_to_float_v3(normal, vd.no);
+      }
+      else {
+        copy_v3_v3(normal, vd.fno);
+      }
+
+      switch (brush->cloth_deform_type) {
+        case BRUSH_CLOTH_DEFORM_DRAG:
+          sub_v3_v3v3(brush_disp, ss->cache->location, ss->cache->last_location);
+          normalize_v3(brush_disp);
+          mul_v3_v3fl(force, brush_disp, fade);
+          break;
+        case BRUSH_CLOTH_DEFORM_PUSH:
+          /* Invert the fade to push inwards. */
+          mul_v3_v3fl(force, offset, -fade);
+          break;
+        case BRUSH_CLOTH_DEFORM_GRAB:
+          mul_v3_v3fl(force, grab_delta, fade);
+          break;
+        case BRUSH_CLOTH_DEFORM_PINCH_POINT:
+          if (use_falloff_plane) {
+            float distance = dist_signed_to_plane_v3(vd.co, deform_plane);
+            copy_v3_v3(brush_disp, plane_normal);
+            mul_v3_fl(brush_disp, -distance);
+          }
+          else {
+            sub_v3_v3v3(brush_disp, ss->cache->location, vd.co);
+          }
+          normalize_v3(brush_disp);
+          mul_v3_v3fl(force, brush_disp, fade);
+          break;
+        case BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR: {
+          float disp_center[3];
+          float x_disp[3];
+          float z_disp[3];
+          sub_v3_v3v3(disp_center, ss->cache->location, vd.co);
+          normalize_v3(disp_center);
+          mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space));
+          mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space));
+          add_v3_v3v3(disp_center, x_disp, z_disp);
+          mul_v3_v3fl(force, disp_center, fade);
+        } break;
+        case BRUSH_CLOTH_DEFORM_INFLATE:
+          mul_v3_v3fl(force, normal, fade);
+          break;
+        case BRUSH_CLOTH_DEFORM_EXPAND:
+          cloth_sim->length_constraint_tweak[vd.index] += fade * 0.1f;
+          zero_v3(force);
+          break;
+      }
+
+      madd_v3_v3fl(force, gravity, fade);
+
+      cloth_brush_apply_force_to_vertex(ss, force, vd.index);
+    }
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
+static SculptClothSimulation *cloth_brush_simulation_create(SculptSession *ss, Brush *brush)
+{
+  const int totverts = sculpt_vertex_count_get(ss);
+  SculptClothSimulation *cloth_sim;
+
+  cloth_sim = MEM_callocN(sizeof(SculptClothSimulation), "cloth constraints");
+
+  cloth_sim->length_constraints = MEM_callocN(sizeof(SculptClothLengthConstraint) *
+                                                  CLOTH_LENGTH_CONSTRAINTS_BLOCK,
+                                              "cloth length constraints");
+  cloth_sim->capacity_length_constraints = CLOTH_LENGTH_CONSTRAINTS_BLOCK;
+
+  cloth_sim->acceleration = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim acceleration");
+  cloth_sim->pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim pos");
+  cloth_sim->prev_pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim prev pos");
+  cloth_sim->init_pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim init pos");
+  cloth_sim->length_constraint_tweak = MEM_callocN(sizeof(float) * totverts,
+                                                   "cloth sim length tweak");
+
+  cloth_sim->mass = brush->cloth_mass;
+  cloth_sim->damping = brush->cloth_damping;
+
+  return cloth_sim;
+}
+
+static void do_cloth_brush_solve_simulation_task_cb_ex(
+    void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+  const Brush *brush = data->brush;
+  PBVHVertexIter vd;
+  SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
+  const float time_step = data->cloth_time_step;
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    const float sim_factor = cloth_brush_simulation_falloff_get(
+        brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
+    if (sim_factor > 0.0f) {
+      int i = vd.index;
+      float temp[3];
+      copy_v3_v3(temp, cloth_sim->pos[i]);
+
+      mul_v3_fl(cloth_sim->acceleration[i], time_step);
+
+      float pos_diff[3];
+      sub_v3_v3v3(pos_diff, cloth_sim->pos[i], cloth_sim->prev_pos[i]);
+      mul_v3_fl(pos_diff, (1.0f - cloth_sim->damping));
+
+      const float mask_v = (1.0f - (vd.mask ? *vd.mask : 0.0f));
+      madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v);
+      madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v);
+
+      copy_v3_v3(cloth_sim->prev_pos[i], temp);
+
+      copy_v3_fl(cloth_sim->acceleration[i], 0.0f);
+
+      copy_v3_v3(vd.co, ss->cache->cloth_sim->pos[vd.index]);
+      if (vd.mvert) {
+        vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+      }
+    }
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
+static void cloth_brush_build_nodes_constraints(Sculpt *sd,
+                                                Object *ob,
+                                                PBVHNode **nodes,
+                                                int totnode)
+{
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  /* TODO: Multithreaded needs to be disabled for this task until implementing the optimization of
+   * storing the constraints per node. */
+  /* Currently all constrains are added to the same global array which can't be accesed from
+   * different threads. */
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, false, totnode);
+
+  SculptThreadedTaskData build_constraints_data = {
+      .sd = sd,
+      .ob = ob,
+      .brush = brush,
+      .nodes = nodes,
+  };
+  BKE_pbvh_parallel_range(
+      0, totnode, &build_constraints_data, do_cloth_brush_build_constraints_task_cb_ex, &settings);
+}
+
+static void cloth_brush_satisfy_constraints(SculptSession *ss,
+                                            Brush *brush,
+                                            SculptClothSimulation *cloth_sim)
+{
+  for (int constraint_it = 0; constraint_it < CLOTH_SIMULATION_ITERATIONS; constraint_it++) {
+    for (int i = 0; i < cloth_sim->tot_length_constraints; i++) {
+
+      const SculptClothLengthConstraint *constraint = &cloth_sim->length_constraints[i];
+      const int v1 = constraint->v1;
+      const int v2 = constraint->v2;
+
+      float v1_to_v2[3];
+      sub_v3_v3v3(v1_to_v2, cloth_sim->pos[v2], cloth_sim->pos[v1]);
+      const float current_distance = len_v3(v1_to_v2);
+      float correction_vector[3];
+      float correction_vector_half[3];
+
+      const float constraint_distance = constraint->length +
+                                        (cloth_sim->length_constraint_tweak[v1] * 0.5f) +
+                                        (cloth_sim->length_constraint_tweak[v2] * 0.5f);
+      mul_v3_v3fl(correction_vector, v1_to_v2, 1.0f - (constraint_distance / current_distance));
+      mul_v3_v3fl(correction_vector_half, correction_vector, 0.5f);
+
+      const float mask_v1 = (1.0f - sculpt_vertex_mask_get(ss, v1));
+      const float mask_v2 = (1.0f - sculpt_vertex_mask_get(ss, v2));
+
+      const float sim_factor_v1 = cloth_brush_simulation_falloff_get(
+          brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[v1]);
+      const float sim_factor_v2 = cloth_brush_simulation_falloff_get(
+          brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[v2]);
+
+      madd_v3_v3fl(cloth_sim->pos[v1], correction_vector_half, 1.0f * mask_v1 * sim_factor_v1);
+      madd_v3_v3fl(cloth_sim->pos[v2], correction_vector_half, -1.0f * mask_v2 * sim_factor_v2);
+    }
+  }
+}
+
+static void cloth_brush_do_simulation_step(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
+
+  /* Update the constraints. */
+  cloth_brush_satisfy_constraints(ss, brush, cloth_sim);
+
+  /* Solve the simulation and write the final step to the mesh. */
+  SculptThreadedTaskData solve_simulation_data = {
+      .sd = sd,
+      .ob = ob,
+      .brush = brush,
+      .nodes = nodes,
+      .cloth_time_step = CLOTH_SIMULATION_TIME_STEP,
+  };
+
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+  BKE_pbvh_parallel_range(
+      0, totnode, &solve_simulation_data, do_cloth_brush_solve_simulation_task_cb_ex, &settings);
+}
+
+static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  float grab_delta[3];
+  float mat[4][4];
+  float area_no[3];
+  float area_co[3];
+  float imat[4][4];
+  float offset[3];
+
+  SculptThreadedTaskData apply_forces_data = {
+      .sd = sd,
+      .ob = ob,
+      .brush = brush,
+      .nodes = nodes,
+      .area_no = area_no,
+      .area_co = area_co,
+      .mat = imat,
+  };
+
+  BKE_curvemapping_initialize(brush->curve);
+
+  /* Init the grab delta. */
+  copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
+  normalize_v3(grab_delta);
+
+  apply_forces_data.grab_delta = grab_delta;
+
+  if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
+    return;
+  }
+
+  /* Calcuate push offset. */
+
+  if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PUSH) {
+    mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius);
+    mul_v3_v3(offset, ss->cache->scale);
+    mul_v3_fl(offset, 2.0f);
+
+    apply_forces_data.offset = offset;
+  }
+
+  if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR ||
+      brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) {
+    SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
+
+    /* Init stroke local space matrix. */
+    cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
+    mat[0][3] = 0.0f;
+    cross_v3_v3v3(mat[1], area_no, mat[0]);
+    mat[1][3] = 0.0f;
+    copy_v3_v3(mat[2], area_no);
+    mat[2][3] = 0.0f;
+    copy_v3_v3(mat[3], ss->cache->location);
+    mat[3][3] = 1.0f;
+    normalize_m4(mat);
+
+    apply_forces_data.area_co = area_co;
+    apply_forces_data.area_no = area_no;
+    apply_forces_data.mat = mat;
+
+    /* Update matrix for the cursor preview. */
+    if (ss->cache->mirror_symmetry_pass == 0) {
+      copy_m4_m4(ss->cache->stroke_local_mat, mat);
+    }
+  }
+
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+  BKE_pbvh_parallel_range(
+      0, totnode, &apply_forces_data, do_cloth_brush_apply_forces_task_cb_ex, &settings);
+}
+
+/* Public functions. */
+
+/* Main Brush Function. */
+void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+  const int totverts = sculpt_vertex_count_get(ss);
+
+  /* In the first brush step of each symmetry pass, build the constraints for the vertices in all
+   * nodes inside the simulation's limits. */
+  /* Brush stroke types that restore the mesh on each brush step also need the cloth sim data to be
+   * created on each step. */
+  if (ss->cache->first_time || !ss->cache->cloth_sim) {
+
+    /* The simulation structure only needs to be created on the first symmetry pass. */
+    if (ss->cache->mirror_symmetry_pass == 0) {
+      ss->cache->cloth_sim = cloth_brush_simulation_create(ss, brush);
+      for (int i = 0; i < totverts; i++) {
+        copy_v3_v3(ss->cache->cloth_sim->prev_pos[i], sculpt_vertex_co_get(ss, i));
+        copy_v3_v3(ss->cache->cloth_sim->init_pos[i], sculpt_vertex_co_get(ss, i));
+      }
+    }
+
+    /* Build the constraints. */
+    cloth_brush_build_nodes_constraints(sd, ob, nodes, totnode);
+
+    return;
+  }
+
+  /* Store the initial state in the simulation. */
+  for (int i = 0; i < totverts; i++) {
+    copy_v3_v3(ss->cache->cloth_sim->pos[i], sculpt_vertex_co_get(ss, i));
+  }
+
+  /* Apply forces to the vertices. */
+  cloth_brush_apply_brush_foces(sd, ob, nodes, totnode);
+
+  /* Update and write the simulation to the nodes. */
+  cloth_brush_do_simulation_step(sd, ob, nodes, totnode);
+
+  return;
+}
+
+void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
+{
+  MEM_SAFE_FREE(cloth_sim->pos);
+  MEM_SAFE_FREE(cloth_sim->prev_pos);
+  MEM_SAFE_FREE(cloth_sim->acceleration);
+  MEM_SAFE_FREE(cloth_sim->length_constraints);
+  MEM_SAFE_FREE(cloth_sim->length_constraint_tweak);
+  MEM_SAFE_FREE(cloth_sim->init_pos);
+  MEM_SAFE_FREE(cloth_sim);
+}
+
+/* Cursor drawing function. */
+void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
+                                         const Brush *brush,
+                                         const float obmat[4][4],
+                                         const float location[3],
+                                         const float normal[3],
+                                         const float rds,
+                                         const float line_width,
+                                         const float outline_col[3],
+                                         const float alpha)
+{
+  float cursor_trans[4][4], cursor_rot[4][4];
+  float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f};
+  float quat[4];
+  copy_m4_m4(cursor_trans, obmat);
+  translate_m4(cursor_trans, location[0], location[1], location[2]);
+  rotation_between_vecs_to_quat(quat, z_axis, normal);
+  quat_to_mat4(cursor_rot, quat);
+  GPU_matrix_mul(cursor_trans);
+  GPU_matrix_mul(cursor_rot);
+
+  GPU_line_width(line_width);
+  immUniformColor3fvAlpha(outline_col, alpha * 0.5f);
+  imm_draw_circle_dashed_3d(
+      gpuattr, 0, 0, rds + (rds * brush->cloth_sim_limit * brush->cloth_sim_falloff), 320);
+  immUniformColor3fvAlpha(outline_col, alpha * 0.7f);
+  imm_draw_circle_wire_3d(gpuattr, 0, 0, rds + rds * brush->cloth_sim_limit, 80);
+}
+
+void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
+                                             SculptSession *ss,
+                                             const float outline_col[3],
+                                             float outline_alpha)
+{
+  float local_mat_inv[4][4];
+  invert_m4_m4(local_mat_inv, ss->cache->stroke_local_mat);
+  GPU_matrix_mul(ss->cache->stroke_local_mat);
+
+  const float dist = ss->cache->radius;
+  const float arrow_x = ss->cache->radius * 0.2f;
+  const float arrow_y = ss->cache->radius * 0.1f;
+
+  immUniformColor3fvAlpha(outline_col, outline_alpha);
+  GPU_line_width(2.0f);
+  immBegin(GPU_PRIM_LINES, 2);
+  immVertex3f(gpuattr, dist, 0.0f, 0.0f);
+  immVertex3f(gpuattr, -dist, 0.0f, 0.0f);
+  immEnd();
+
+  immBegin(GPU_PRIM_TRIS, 6);
+  immVertex3f(gpuattr, dist, 0.0f, 0.0f);
+  immVertex3f(gpuattr, dist - arrow_x, arrow_y, 0.0f);
+  immVertex3f(gpuattr, dist - arrow_x, -arrow_y, 0.0f);
+
+  immVertex3f(gpuattr, -dist, 0.0f, 0.0f);
+  immVertex3f(gpuattr, -dist + arrow_x, arrow_y, 0.0f);
+  immVertex3f(gpuattr, -dist + arrow_x, -arrow_y, 0.0f);
+
+  immEnd();
+}
index 8302e119ddb4387bcb00271f501e34fe71512fa9..c67096c2dfff58085678573fe29de29bf4881de3 100644 (file)
@@ -85,13 +85,92 @@ struct SculptPoseIKChain *sculpt_pose_ik_chain_init(struct Sculpt *sd,
 void sculpt_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
 
 /* Sculpt PBVH abstraction API */
+int sculpt_vertex_count_get(struct SculptSession *ss);
 const float *sculpt_vertex_co_get(struct SculptSession *ss, int index);
+float sculpt_vertex_mask_get(struct SculptSession *ss, int index);
+
+#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
+typedef struct SculptVertexNeighborIter {
+  /* Storage */
+  int *neighbors;
+  int size;
+  int capacity;
+  int neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY];
+
+  /* Internal iterator. */
+  int num_duplicates;
+  int i;
+
+  /* Public */
+  int index;
+  bool is_duplicate;
+} SculptVertexNeighborIter;
+
+void sculpt_vertex_neighbors_get(struct SculptSession *ss,
+                                 const int index,
+                                 const bool include_duplicates,
+                                 SculptVertexNeighborIter *iter);
+
+/* Iterator over neighboring vertices. */
+#define sculpt_vertex_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
+  sculpt_vertex_neighbors_get(ss, v_index, false, &neighbor_iterator); \
+  for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \
+       neighbor_iterator.i++) { \
+    neighbor_iterator.index = ni.neighbors[ni.i];
+
+/* Iterate over neighboring and duplicate vertices (for PBVH_GRIDS). Duplicates come
+ * first since they are nearest for floodfill. */
+#define sculpt_vertex_duplicates_and_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
+  sculpt_vertex_neighbors_get(ss, v_index, true, &neighbor_iterator); \
+  for (neighbor_iterator.i = neighbor_iterator.size - 1; neighbor_iterator.i >= 0; \
+       neighbor_iterator.i--) { \
+    neighbor_iterator.index = ni.neighbors[ni.i]; \
+    neighbor_iterator.is_duplicate = (ni.i >= \
+                                      neighbor_iterator.size - neighbor_iterator.num_duplicates);
+
+#define sculpt_vertex_neighbors_iter_end(neighbor_iterator) \
+  } \
+  if (neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
+    MEM_freeN(neighbor_iterator.neighbors); \
+  } \
+  ((void)0)
 
 /* Dynamic topology */
 void sculpt_pbvh_clear(Object *ob);
 void sculpt_dyntopo_node_layers_add(struct SculptSession *ss);
 void sculpt_dynamic_topology_disable(bContext *C, struct SculptUndoNode *unode);
 
+/* Utils. */
+void SCULPT_calc_brush_plane(struct Sculpt *sd,
+                             struct Object *ob,
+                             struct PBVHNode **nodes,
+                             int totnode,
+                             float r_area_no[3],
+                             float r_area_co[3]);
+
+/* Brushes. */
+
+/* Cloth Brush. */
+void SCULPT_do_cloth_brush(struct Sculpt *sd,
+                           struct Object *ob,
+                           struct PBVHNode **nodes,
+                           int totnode);
+void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim);
+
+void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
+                                         const struct Brush *brush,
+                                         const float obmat[4][4],
+                                         const float location[3],
+                                         const float normal[3],
+                                         const float rds,
+                                         const float line_width,
+                                         const float outline_col[3],
+                                         const float alpha);
+void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
+                                             struct SculptSession *ss,
+                                             const float outline_col[3],
+                                             float outline_alpha);
+
 /* Undo */
 
 typedef enum {
@@ -242,6 +321,8 @@ typedef struct SculptThreadedTaskData {
 
   float transform_mats[8][4][4];
 
+  float cloth_time_step;
+
   float dirty_mask_min;
   float dirty_mask_max;
   bool dirty_mask_dirty_only;
@@ -416,6 +497,13 @@ typedef struct StrokeCache {
   float clay_pressure_stabilizer[CLAY_STABILIZER_LEN];
   int clay_pressure_stabilizer_index;
 
+  /* Cloth brush */
+  struct SculptClothSimulation *cloth_sim;
+  float initial_location[3];
+  float true_initial_location[3];
+  float initial_normal[3];
+  float true_initial_normal[3];
+
   float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
   struct Dial *dial;
 
index 370c576006fe2d95324fdb47112f4baaa285ed93..419c84c803e243e7302fe38dcf0fb12c14a56235 100644 (file)
@@ -47,6 +47,7 @@ void imm_draw_circle_fill_aspect_2d(
 
 /* use this version when GPUVertFormat has a vec3 position */
 void imm_draw_circle_wire_3d(uint pos, float x, float y, float radius, int nsegments);
+void imm_draw_circle_dashed_3d(uint pos, float x, float y, float radius, int nsegments);
 void imm_draw_circle_fill_3d(uint pos, float x, float y, float radius, int nsegments);
 
 /* same as 'imm_draw_disk_partial_fill_2d', except it draws a wire arc. */
index bb3c4344bd4549cc44c56a82f6ce36f1eab04e34..45d9c40b3e69aa694bdd60686bec0e29fa3b2e2f 100644 (file)
@@ -316,6 +316,11 @@ void imm_draw_circle_wire_3d(uint pos, float x, float y, float rad, int nsegment
   imm_draw_circle_3D(GPU_PRIM_LINE_LOOP, pos, x, y, rad, nsegments);
 }
 
+void imm_draw_circle_dashed_3d(uint pos, float x, float y, float rad, int nsegments)
+{
+  imm_draw_circle_3D(GPU_PRIM_LINES, pos, x, y, rad, nsegments / 2);
+}
+
 void imm_draw_circle_fill_3d(uint pos, float x, float y, float rad, int nsegments)
 {
   imm_draw_circle_3D(GPU_PRIM_TRI_FAN, pos, x, y, rad, nsegments);
index 03129bf673441f03268259f04591ae93a1452a6b..3c18df076f6bc7d2a581880d32ef604e59ee1267 100644 (file)
     .normal_radius_factor = 0.5f, \
     .area_radius_factor = 0.5f, \
     .sculpt_plane = SCULPT_DISP_DIR_AREA, \
+    .cloth_damping = 0.01, \
+    .cloth_mass = 1, \
+    .cloth_sim_limit = 2.5f, \
+    .cloth_sim_falloff = 0.75f, \
     /* How far above or below the plane that is found by averaging the faces. */ \
     .plane_offset = 0.0f, \
     .plane_trim = 0.5f, \
index a5baa7a5c756187c0ac6495e751d1422c7a9a2c6..e14732ee77a0b09d484510d6144529e9c2834e95 100644 (file)
@@ -210,6 +210,21 @@ typedef enum eBrushElasticDeformType {
   BRUSH_ELASTIC_DEFORM_TWIST = 4,
 } eBrushElasticDeformType;
 
+typedef enum eBrushClothDeformType {
+  BRUSH_CLOTH_DEFORM_DRAG = 0,
+  BRUSH_CLOTH_DEFORM_PUSH = 1,
+  BRUSH_CLOTH_DEFORM_GRAB = 2,
+  BRUSH_CLOTH_DEFORM_PINCH_POINT = 3,
+  BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR = 4,
+  BRUSH_CLOTH_DEFORM_INFLATE = 5,
+  BRUSH_CLOTH_DEFORM_EXPAND = 6,
+} eBrushClothDeformType;
+
+typedef enum eBrushClothForceFalloffType {
+  BRUSH_CLOTH_FORCE_FALLOFF_RADIAL = 0,
+  BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
+} eBrushClothForceFalloffType;
+
 typedef enum eAutomasking_flag {
   BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0),
 } eAutomasking_flag;
@@ -291,7 +306,7 @@ typedef struct Brush {
   /** Source for fill tool color gradient application. */
   char gradient_fill_mode;
 
-  char _pad0;
+  char _pad0[5];
 
   /** Projection shape (sphere, circle). */
   char falloff_shape;
@@ -311,7 +326,7 @@ typedef struct Brush {
   char mask_tool;
   /** Active grease pencil tool. */
   char gpencil_tool;
-  char _pad1[5];
+  char _pad1[1];
 
   float autosmooth_factor;
 
@@ -343,6 +358,16 @@ typedef struct Brush {
   int pose_smooth_iterations;
   int pose_ik_segments;
 
+  /* cloth */
+  int cloth_deform_type;
+  int cloth_force_falloff_type;
+
+  float cloth_mass;
+  float cloth_damping;
+
+  float cloth_sim_limit;
+  float cloth_sim_falloff;
+
   /* multiplane scrape */
   float multiplane_scrape_angle;
 
@@ -512,6 +537,7 @@ typedef enum eBrushSculptTool {
   SCULPT_TOOL_MULTIPLANE_SCRAPE = 23,
   SCULPT_TOOL_SLIDE_RELAX = 24,
   SCULPT_TOOL_CLAY_THUMB = 25,
+  SCULPT_TOOL_CLOTH = 26,
 } eBrushSculptTool;
 
 /* Brush.uv_sculpt_tool */
@@ -547,6 +573,7 @@ typedef enum eBrushUVSculptTool {
   (ELEM(t, /* These brushes, as currently coded, cannot support dynamic topology */ \
         SCULPT_TOOL_GRAB, \
         SCULPT_TOOL_ROTATE, \
+        SCULPT_TOOL_CLOTH, \
         SCULPT_TOOL_THUMB, \
         SCULPT_TOOL_LAYER, \
         SCULPT_TOOL_DRAW_SHARP, \
index a219bbcfc8e8d655d3a2777cdf57139cce8cacfb..5f30e299a128c1e31934a8552955b4ac0d1befb1 100644 (file)
@@ -96,6 +96,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
     {SCULPT_TOOL_ROTATE, "ROTATE", ICON_BRUSH_ROTATE, "Rotate", ""},
     {SCULPT_TOOL_SLIDE_RELAX, "TOPOLOGY", ICON_BRUSH_GRAB, "Slide Relax", ""},
     {0, "", 0, NULL, NULL},
+    {SCULPT_TOOL_CLOTH, "CLOTH", ICON_BRUSH_SCULPT_DRAW, "Cloth", ""},
     {SCULPT_TOOL_SIMPLIFY, "SIMPLIFY", ICON_BRUSH_DATA, "Simplify", ""},
     {SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
     {0, NULL, 0, NULL, NULL},
@@ -1641,6 +1642,27 @@ static void rna_def_brush(BlenderRNA *brna)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static const EnumPropertyItem brush_cloth_deform_type_items[] = {
+      {BRUSH_CLOTH_DEFORM_DRAG, "DRAG", 0, "Drag", ""},
+      {BRUSH_CLOTH_DEFORM_PUSH, "PUSH", 0, "Push", ""},
+      {BRUSH_CLOTH_DEFORM_PINCH_POINT, "PINCH_POINT", 0, "Pinch Point", ""},
+      {BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR,
+       "PINCH_PERPENDICULAR",
+       0,
+       "Pinch Perpendicular",
+       ""},
+      {BRUSH_CLOTH_DEFORM_INFLATE, "INFLATE", 0, "Inflate", ""},
+      {BRUSH_CLOTH_DEFORM_GRAB, "GRAB", 0, "Grab", ""},
+      {BRUSH_CLOTH_DEFORM_EXPAND, "EXPAND", 0, "Expand", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  static const EnumPropertyItem brush_cloth_force_falloff_type_items[] = {
+      {BRUSH_CLOTH_FORCE_FALLOFF_RADIAL, "RADIAL", 0, "Radial", ""},
+      {BRUSH_CLOTH_FORCE_FALLOFF_PLANE, "PLANE", 0, "Plane", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   srna = RNA_def_struct(brna, "Brush", "ID");
   RNA_def_struct_ui_text(
       srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
@@ -1726,6 +1748,17 @@ 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, "cloth_deform_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_items(prop, brush_cloth_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, "cloth_force_falloff_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_items(prop, brush_cloth_force_falloff_type_items);
+  RNA_def_property_ui_text(
+      prop, "Force Falloff", "Shape used in the brush to apply force to the cloth");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "jitter_unit", PROP_ENUM, PROP_NONE); /* as an enum */
   RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
   RNA_def_property_enum_items(prop, brush_jitter_unit_items);
@@ -1949,6 +1982,36 @@ static void rna_def_brush(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Tip Roundness", "Roundness of the brush tip");
   RNA_def_property_update(prop, 0, "rna_Brush_update");
 
+  prop = RNA_def_property(srna, "cloth_mass", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "cloth_mass");
+  RNA_def_property_range(prop, 0.01f, 2.0f);
+  RNA_def_property_ui_text(prop, "Cloth mass", "Mass of each simulation particle");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+  prop = RNA_def_property(srna, "cloth_damping", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "cloth_damping");
+  RNA_def_property_range(prop, 0.01f, 1.0f);
+  RNA_def_property_ui_text(
+      prop, "Cloth Damping", "How much the applied forces are propagated through the cloth");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+  prop = RNA_def_property(srna, "cloth_sim_limit", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "cloth_sim_limit");
+  RNA_def_property_range(prop, 0.1f, 10.0f);
+  RNA_def_property_ui_text(
+      prop,
+      "Simulation Limit",
+      "Factor added relative to the size of the radius to limit the cloth simulation effects");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+  prop = RNA_def_property(srna, "cloth_sim_falloff", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "cloth_sim_falloff");
+  RNA_def_property_range(prop, 0.0f, 1.0f);
+  RNA_def_property_ui_text(prop,
+                           "Simulation Falloff",
+                           "Area to apply deformation falloff to the effects of the simulation");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "auto_smooth_factor", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "autosmooth_factor");
   RNA_def_property_float_default(prop, 0);