Sculpt: Cloth Simulation Dynamic area mode

This simulation area mode moves the active area with the brush. When
enabled, the cloth brush has no restrictions on stroke length, area or
mesh vertex count.

In order to work, this enables PBVH nodes dynamically for simulation as
the stroke location moves and builds the constraints for new nodes
during the stroke. When a node is not inside the simulated area, all the
constraints that were created for it and vertex collisions are not
computed. The simulation limits falloff areas and constraints tweaking
control how the simulated and no simulated nodes blend.

Reviewed By: sergey, zeddb

Differential Revision: https://developer.blender.org/D8726
This commit is contained in:
Pablo Dobarro 2020-10-01 18:50:52 +02:00 committed by Pablo Dobarro
parent cd81c38688
commit 8ef353fa50
8 changed files with 201 additions and 55 deletions

View File

@ -660,9 +660,11 @@ def brush_settings(layout, context, brush, popover=False):
elif sculpt_tool == 'CLOTH':
layout.separator()
layout.prop(brush, "cloth_simulation_area_type")
if brush.cloth_simulation_area_type == 'LOCAL':
if brush.cloth_simulation_area_type != 'GLOBAL':
layout.prop(brush, "cloth_sim_limit")
layout.prop(brush, "cloth_sim_falloff")
if brush.cloth_simulation_area_type == 'LOCAL':
layout.prop(brush, "use_cloth_pin_simulation_boundary")
layout.separator()

View File

@ -256,6 +256,18 @@ typedef struct SculptPoseIKChain {
/* Cloth Brush */
/* Cloth Simulation. */
typedef enum eSculptClothNodeSimState {
/* Constraints were not built for this node, so it can't be simulated. */
SCULPT_CLOTH_NODE_UNINITIALIZED,
/* There are constraints for the geometry in this node, but it should not be simulated. */
SCULPT_CLOTH_NODE_INACTIVE,
/* There are constraints for this node and they should be used by the solver. */
SCULPT_CLOTH_NODE_ACTIVE,
} eSculptClothNodeSimState;
typedef enum eSculptClothConstraintType {
/* Constraint that creates the structure of the cloth. */
SCULPT_CLOTH_CONSTRAINT_STRUCTURAL = 0,
@ -282,6 +294,10 @@ typedef struct SculptClothLengthConstraint {
float length;
float strength;
/* Index in SculptClothSimulation.node_state of the node from where this constraint was created.
* This constraints will only be used by the solver if the state is active. */
int node;
eSculptClothConstraintType type;
} SculptClothLengthConstraint;
@ -308,6 +324,11 @@ typedef struct SculptClothSimulation {
float (*last_iteration_pos)[3];
struct ListBase *collider_list;
int totnode;
/* PBVHNode pointer as a key, index in SculptClothSimulation.node_state as value. */
struct GHash *node_state_index;
eSculptClothNodeSimState *node_state;
} SculptClothSimulation;
typedef struct SculptPersistentBase {

View File

@ -1663,7 +1663,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
/* Cloth brush local simulation areas. */
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_LOCAL) {
brush->cloth_simulation_area_type != BRUSH_CLOTH_SIMULATION_AREA_GLOBAL) {
const float white[3] = {1.0f, 1.0f, 1.0f};
const float zero_v[3] = {0.0f};
/* This functions sets its own drawing space in order to draw the simulation limits when the

View File

@ -5646,6 +5646,17 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
};
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode);
}
if (brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_DYNAMIC) {
SculptSearchSphereData data = {
.ss = ss,
.sd = sd,
.radius_squared = square_f(ss->cache->initial_radius * (1.0 + brush->cloth_sim_limit)),
.original = false,
.ignore_fully_ineffective = false,
.center = ss->cache->location,
};
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode);
}
else {
/* Gobal simulation, get all nodes. */
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
@ -5724,10 +5735,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
if (!ss->cache->cloth_sim) {
ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create(ss, 1.0f, 0.0f, false, true);
SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
SCULPT_cloth_brush_build_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX);
}
SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
SCULPT_cloth_brush_ensure_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX);
}
bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN;
@ -5870,6 +5881,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
SCULPT_cloth_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode);
SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
}
}

View File

@ -103,6 +103,22 @@
#include <stdlib.h>
#include <string.h>
static void cloth_brush_simulation_location_get(SculptSession *ss,
const Brush *brush,
float r_location[3])
{
if (!ss->cache || !brush) {
zero_v3(r_location);
return;
}
if (brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_LOCAL) {
copy_v3_v3(r_location, ss->cache->initial_location);
return;
}
copy_v3_v3(r_location, ss->cache->location);
}
static float cloth_brush_simulation_falloff_get(const Brush *brush,
const float radius,
const float location[3],
@ -162,6 +178,7 @@ static void cloth_brush_reallocate_constraints(SculptClothSimulation *cloth_sim)
static void cloth_brush_add_length_constraint(SculptSession *ss,
SculptClothSimulation *cloth_sim,
const int node_index,
const int v1,
const int v2,
const bool use_persistent)
@ -172,6 +189,8 @@ static void cloth_brush_add_length_constraint(SculptSession *ss,
length_constraint->elem_index_a = v1;
length_constraint->elem_index_b = v2;
length_constraint->node = node_index;
length_constraint->elem_position_a = cloth_sim->pos[v1];
length_constraint->elem_position_b = cloth_sim->pos[v2];
@ -197,6 +216,7 @@ static void cloth_brush_add_length_constraint(SculptSession *ss,
}
static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim,
const int node_index,
const int v,
const float strength)
{
@ -206,6 +226,8 @@ static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim
length_constraint->elem_index_a = v;
length_constraint->elem_index_b = v;
length_constraint->node = node_index;
length_constraint->elem_position_a = cloth_sim->pos[v];
length_constraint->elem_position_b = cloth_sim->init_pos[v];
@ -221,6 +243,7 @@ static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim
}
static void cloth_brush_add_deformation_constraint(SculptClothSimulation *cloth_sim,
const int node_index,
const int v,
const float strength)
{
@ -230,6 +253,8 @@ static void cloth_brush_add_deformation_constraint(SculptClothSimulation *cloth_
length_constraint->elem_index_a = v;
length_constraint->elem_index_b = v;
length_constraint->node = node_index;
length_constraint->type = SCULPT_CLOTH_CONSTRAINT_DEFORMATION;
length_constraint->elem_position_a = cloth_sim->pos[v];
@ -250,11 +275,20 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const Brush *brush = data->brush;
PBVHNode *node = data->nodes[n];
const int node_index = (int)(BLI_ghash_lookup(data->cloth_sim->node_state_index, node));
if (data->cloth_sim->node_state[node_index] != SCULPT_CLOTH_NODE_UNINITIALIZED) {
/* The simulation already contains constraints for this node. */
return;
}
PBVHVertexIter vd;
const bool pin_simulation_boundary = ss->cache != NULL && brush != NULL &&
brush->flag2 & BRUSH_CLOTH_PIN_SIMULATION_BOUNDARY;
brush->flag2 & BRUSH_CLOTH_PIN_SIMULATION_BOUNDARY &&
brush->cloth_simulation_area_type !=
BRUSH_CLOTH_SIMULATION_AREA_DYNAMIC;
const bool use_persistent = brush != NULL && brush->flag & BRUSH_PERSISTENT;
@ -273,7 +307,7 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
data->cloth_sim_radius * data->cloth_sim_radius :
FLT_MAX;
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
{
const float len_squared = len_squared_v3v3(vd.co, data->cloth_sim_initial_location);
if (len_squared < cloth_sim_radius_squared) {
@ -291,7 +325,7 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
if (brush->cloth_constraint_softbody_strength > 0.0f) {
cloth_brush_add_softbody_constraint(
data->cloth_sim, vd.index, brush->cloth_constraint_softbody_strength);
data->cloth_sim, node_index, vd.index, brush->cloth_constraint_softbody_strength);
}
/* As we don't know the order of the neighbor vertices, we create all possible combinations
@ -303,8 +337,12 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
for (int c_j = 0; c_j < tot_indices; c_j++) {
if (c_i != c_j && !cloth_brush_sim_has_length_constraint(
data->cloth_sim, build_indices[c_i], build_indices[c_j])) {
cloth_brush_add_length_constraint(
ss, data->cloth_sim, build_indices[c_i], build_indices[c_j], use_persistent);
cloth_brush_add_length_constraint(ss,
data->cloth_sim,
node_index,
build_indices[c_i],
build_indices[c_j],
use_persistent);
}
}
}
@ -317,13 +355,13 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
/* When the grab brush brush is used as part of the cloth brush, deformation constraints
* are created with different strengths and only inside the radius of the brush. */
const float fade = BKE_brush_curve_strength(brush, sqrtf(len_squared), ss->cache->radius);
cloth_brush_add_deformation_constraint(data->cloth_sim, vd.index, fade);
cloth_brush_add_deformation_constraint(data->cloth_sim, node_index, vd.index, fade);
}
else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) {
/* Cloth Snake Hook creates deformation constraint with fixed strength because the strength
* is controlled per iteration using cloth_sim->deformation_strength. */
cloth_brush_add_deformation_constraint(
data->cloth_sim, vd.index, CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH);
data->cloth_sim,node_index, vd.index, CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH);
}
}
else if (data->cloth_sim->deformation_pos) {
@ -331,7 +369,7 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
* their own code when modifying the deformation coordinates of the simulation, so
* deformation constraints are created with a fixed strength for all vertices. */
cloth_brush_add_deformation_constraint(
data->cloth_sim, vd.index, CLOTH_DEFORMATION_TARGET_STRENGTH);
data->cloth_sim, node_index, vd.index, CLOTH_DEFORMATION_TARGET_STRENGTH);
}
if (pin_simulation_boundary) {
@ -341,7 +379,8 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
if (sim_falloff < 1.0f) {
/* Create constraints with more strength the closer the vertex is to the simulation
* boundary. */
cloth_brush_add_softbody_constraint(data->cloth_sim, vd.index, 1.0f - sim_falloff);
cloth_brush_add_softbody_constraint(
data->cloth_sim, node_index, vd.index, 1.0f - sim_falloff);
}
}
}
@ -411,8 +450,10 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
float force[3];
float sim_location[3];
cloth_brush_simulation_location_get(ss, brush, sim_location);
const float sim_factor = cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
brush, ss->cache->radius, sim_location, cloth_sim->init_pos[vd.index]);
float current_vertex_location[3];
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
@ -646,17 +687,25 @@ static void do_cloth_brush_solve_simulation_task_cb_ex(
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const Brush *brush = data->brush;
PBVHNode *node = data->nodes[n];
PBVHVertexIter vd;
SculptClothSimulation *cloth_sim = data->cloth_sim;
const float time_step = data->cloth_time_step;
const int node_index = (int)(BLI_ghash_lookup(data->cloth_sim->node_state_index, node));
if (data->cloth_sim->node_state[node_index] != SCULPT_CLOTH_NODE_ACTIVE) {
return;
}
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
const float sim_factor = ss->cache ? cloth_brush_simulation_falloff_get(
brush,
ss->cache->radius,
ss->cache->initial_location,
cloth_sim->init_pos[vd.index]) :
1.0f;
float sim_location[3];
cloth_brush_simulation_location_get(ss, brush, sim_location);
const float sim_factor =
ss->cache ? cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, sim_location, cloth_sim->init_pos[vd.index]) :
1.0f;
if (sim_factor > 0.0f) {
int i = vd.index;
float temp[3];
@ -692,6 +741,9 @@ static void do_cloth_brush_solve_simulation_task_cb_ex(
}
}
BKE_pbvh_vertex_iter_end;
/* Disable the simulation on this node, it needs to be enabled again to continue. */
cloth_sim->node_state[node_index] = SCULPT_CLOTH_NODE_INACTIVE;
}
static void cloth_brush_satisfy_constraints(SculptSession *ss,
@ -700,8 +752,13 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss,
{
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];
if (cloth_sim->node_state[constraint->node] != SCULPT_CLOTH_NODE_ACTIVE) {
/* Skip all constraints that were created for inactive nodes. */
continue;
}
const int v1 = constraint->elem_index_a;
const int v2 = constraint->elem_index_b;
@ -729,18 +786,21 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss,
const float mask_v2 = (1.0f - SCULPT_vertex_mask_get(ss, v2)) *
SCULPT_automasking_factor_get(ss, v2);
const float sim_factor_v1 = ss->cache ? cloth_brush_simulation_falloff_get(
brush,
ss->cache->radius,
ss->cache->initial_location,
cloth_sim->init_pos[v1]) :
1.0f;
const float sim_factor_v2 = ss->cache ? cloth_brush_simulation_falloff_get(
brush,
ss->cache->radius,
ss->cache->initial_location,
cloth_sim->init_pos[v2]) :
1.0f;
float sim_location[3];
cloth_brush_simulation_location_get(ss, brush, sim_location);
const float sim_factor_v1 = ss->cache ?
cloth_brush_simulation_falloff_get(brush,
ss->cache->radius,
sim_location,
cloth_sim->init_pos[v1]) :
1.0f;
const float sim_factor_v2 = ss->cache ?
cloth_brush_simulation_falloff_get(brush,
ss->cache->radius,
sim_location,
cloth_sim->init_pos[v2]) :
1.0f;
float deformation_strength = 1.0f;
if (constraint->type == SCULPT_CLOTH_CONSTRAINT_DEFORMATION) {
@ -871,6 +931,25 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod
0, totnode, &apply_forces_data, do_cloth_brush_apply_forces_task_cb_ex, &settings);
}
/* Allocates nodes state and initializes them to Uninitialized, so constraints can be created for
* them. */
static void cloth_sim_initialize_default_node_state(SculptSession *ss,
SculptClothSimulation *cloth_sim)
{
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
cloth_sim->node_state = MEM_malloc_arrayN(
totnode, sizeof(eSculptClothNodeSimState), "node sim state");
cloth_sim->node_state_index = BLI_ghash_ptr_new("node sim state indices");
for (int i = 0; i < totnode; i++) {
cloth_sim->node_state[i] = SCULPT_CLOTH_NODE_UNINITIALIZED;
BLI_ghash_insert(cloth_sim->node_state_index, nodes[i], POINTER_FROM_INT(i));
}
MEM_SAFE_FREE(nodes);
}
/* Public functions. */
SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss,
const float cloth_mass,
@ -912,10 +991,12 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss,
cloth_sim->collider_list = cloth_brush_collider_cache_create(ss->depsgraph);
}
cloth_sim_initialize_default_node_state(ss, cloth_sim);
return cloth_sim;
}
void SCULPT_cloth_brush_build_nodes_constraints(
void SCULPT_cloth_brush_ensure_nodes_constraints(
Sculpt *sd,
Object *ob,
PBVHNode **nodes,
@ -975,6 +1056,17 @@ void SCULPT_cloth_brush_store_simulation_state(SculptSession *ss, SculptClothSim
}
}
void SCULPT_cloth_sim_activate_nodes(SculptClothSimulation *cloth_sim,
PBVHNode **nodes,
int totnode)
{
/* Activate the nodes inside the simulation area. */
for (int n = 0; n < totnode; n++) {
const int node_index = (int)(BLI_ghash_lookup(cloth_sim->node_state_index, nodes[n]));
cloth_sim->node_state[node_index] = SCULPT_CLOTH_NODE_ACTIVE;
}
}
/* Main Brush Function. */
void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
@ -998,19 +1090,21 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
SCULPT_is_cloth_deform_brush(brush));
SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
}
/* Build the constraints. */
const float radius = ss->cache->initial_radius;
const float limit = radius + (radius * brush->cloth_sim_limit);
SCULPT_cloth_brush_build_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, limit);
return;
}
/* Ensure the constraints for the nodes. */
const float radius = ss->cache->initial_radius;
const float limit = radius + (radius * brush->cloth_sim_limit);
SCULPT_cloth_brush_ensure_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, limit);
/* Store the initial state in the simulation. */
SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
/* Enable the nodes that should be simulated. */
SCULPT_cloth_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode);
/* Apply forces to the vertices. */
cloth_brush_apply_brush_foces(sd, ob, nodes, totnode);
@ -1029,6 +1123,8 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
MEM_SAFE_FREE(cloth_sim->deformation_pos);
MEM_SAFE_FREE(cloth_sim->init_pos);
MEM_SAFE_FREE(cloth_sim->deformation_strength);
MEM_SAFE_FREE(cloth_sim->node_state);
BLI_ghash_free(cloth_sim->node_state_index, NULL, NULL);
if (cloth_sim->collider_list) {
BKE_collider_cache_free(&cloth_sim->collider_list);
}
@ -1313,6 +1409,10 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent
BLI_task_parallel_range(
0, ss->filter_cache->totnode, &data, cloth_filter_apply_forces_task_cb, &settings);
/* Activate all nodes. */
SCULPT_cloth_sim_activate_nodes(
ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
/* Update and write the simulation to the nodes. */
SCULPT_cloth_brush_do_simulation_step(
sd, ob, ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
@ -1363,13 +1463,13 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim);
float origin[3] = {0.0f, 0.0f, 0.0f};
SCULPT_cloth_brush_build_nodes_constraints(sd,
ob,
ss->filter_cache->nodes,
ss->filter_cache->totnode,
ss->filter_cache->cloth_sim,
origin,
FLT_MAX);
SCULPT_cloth_brush_ensure_nodes_constraints(sd,
ob,
ss->filter_cache->nodes,
ss->filter_cache->totnode,
ss->filter_cache->cloth_sim,
origin,
FLT_MAX);
const bool use_face_sets = RNA_boolean_get(op->ptr, "use_face_sets");
if (use_face_sets) {

View File

@ -372,6 +372,11 @@ struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct Sculpt
const bool needs_deform_coords);
void SCULPT_cloth_brush_simulation_init(struct SculptSession *ss,
struct SculptClothSimulation *cloth_sim);
void SCULPT_cloth_sim_activate_nodes(struct SculptClothSimulation *cloth_sim,
PBVHNode **nodes,
int totnode);
void SCULPT_cloth_brush_store_simulation_state(struct SculptSession *ss,
struct SculptClothSimulation *cloth_sim);
@ -381,13 +386,13 @@ void SCULPT_cloth_brush_do_simulation_step(struct Sculpt *sd,
struct PBVHNode **nodes,
int totnode);
void SCULPT_cloth_brush_build_nodes_constraints(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode,
struct SculptClothSimulation *cloth_sim,
float initial_location[3],
const float radius);
void SCULPT_cloth_brush_ensure_nodes_constraints(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode,
struct SculptClothSimulation *cloth_sim,
float initial_location[3],
const float radius);
void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
const struct Brush *brush,

View File

@ -359,6 +359,7 @@ typedef enum eBrushClothForceFalloffType {
typedef enum eBrushClothSimulationAreaType {
BRUSH_CLOTH_SIMULATION_AREA_LOCAL = 0,
BRUSH_CLOTH_SIMULATION_AREA_GLOBAL = 1,
BRUSH_CLOTH_SIMULATION_AREA_DYNAMIC = 2,
} eBrushClothSimulationAreaType;
typedef enum eBrushPoseDeformType {

View File

@ -2099,6 +2099,11 @@ static void rna_def_brush(BlenderRNA *brna)
"Local",
"Simulates only a specific area around the brush limited by a fixed radius"},
{BRUSH_CLOTH_SIMULATION_AREA_GLOBAL, "GLOBAL", 0, "Global", "Simulates the entire mesh"},
{BRUSH_CLOTH_SIMULATION_AREA_DYNAMIC,
"DYNAMIC",
0,
"Dynamic",
"The active simulation area moves with the brush"},
{0, NULL, 0, NULL, NULL},
};