Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py12
-rw-r--r--source/blender/blenkernel/BKE_paint.h26
-rw-r--r--source/blender/blenkernel/intern/brush.c17
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c52
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c9
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c191
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_cloth.c679
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h88
-rw-r--r--source/blender/gpu/GPU_immediate_util.h1
-rw-r--r--source/blender/gpu/intern/gpu_immediate_util.c5
-rw-r--r--source/blender/makesdna/DNA_brush_defaults.h4
-rw-r--r--source/blender/makesdna/DNA_brush_types.h31
-rw-r--r--source/blender/makesrna/intern/rna_brush.c63
14 files changed, 1144 insertions, 35 deletions
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index 24016061194..704af1ae307 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -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()
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 28e564f0fe2..4c274804a07 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -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 */
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index 15ef6479007..09b3ad89e73 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -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;
}
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index a5cc262ddcd..2522bc0f5cc 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -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
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index d5bb552b470..cabf17e8534 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -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);
}
}
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index 372ea954630..4abeb937758 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -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,
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 55ddc82c3f8..9a01be9d7b3 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -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
index 00000000000..7474a7de629
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c
@@ -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();
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 8302e119ddb..c67096c2dff 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -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;
diff --git a/source/blender/gpu/GPU_immediate_util.h b/source/blender/gpu/GPU_immediate_util.h
index 370c576006f..419c84c803e 100644
--- a/source/blender/gpu/GPU_immediate_util.h
+++ b/source/blender/gpu/GPU_immediate_util.h
@@ -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. */
diff --git a/source/blender/gpu/intern/gpu_immediate_util.c b/source/blender/gpu/intern/gpu_immediate_util.c
index bb3c4344bd4..45d9c40b3e6 100644
--- a/source/blender/gpu/intern/gpu_immediate_util.c
+++ b/source/blender/gpu/intern/gpu_immediate_util.c
@@ -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);
diff --git a/source/blender/makesdna/DNA_brush_defaults.h b/source/blender/makesdna/DNA_brush_defaults.h
index 03129bf6734..3c18df076f6 100644
--- a/source/blender/makesdna/DNA_brush_defaults.h
+++ b/source/blender/makesdna/DNA_brush_defaults.h
@@ -48,6 +48,10 @@
.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, \
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index a5baa7a5c75..e14732ee77a 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -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, \
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index a219bbcfc8e..5f30e299a12 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -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);