diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_cloth.c')
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_cloth.c | 484 |
1 files changed, 371 insertions, 113 deletions
diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c index a95e4f7008c..20b164fa80c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.c +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c @@ -103,11 +103,32 @@ #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], const float co[3]) { + if (brush->sculpt_tool != SCULPT_TOOL_CLOTH) { + /* All brushes that are not the cloth brush do not use simulation areas. */ + return 1.0f; + } + /* Global simulation does not have any falloff as the entire mesh is being simulated. */ if (brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_GLOBAL) { return 1.0f; @@ -118,23 +139,27 @@ static float cloth_brush_simulation_falloff_get(const Brush *brush, const float falloff = radius + (radius * brush->cloth_sim_limit * brush->cloth_sim_falloff); if (distance > limit) { - /* Outiside the limits. */ + /* Outside the limits. */ return 0.0f; } if (distance < falloff) { /* Before the falloff area. */ return 1.0f; } - /* Do a smoothstep transition inside the falloff area. */ + /* Do a smooth-step transition inside the falloff area. */ float p = 1.0f - ((distance - falloff) / (limit - falloff)); return 3.0f * p * p - 2.0f * p * p * p; } #define CLOTH_LENGTH_CONSTRAINTS_BLOCK 100000 #define CLOTH_SIMULATION_ITERATIONS 5 + +#define CLOTH_SOLVER_DISPLACEMENT_FACTOR 0.6f #define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024 #define CLOTH_SIMULATION_TIME_STEP 0.01f -#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.35f +#define CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH 0.35f +#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.01f +#define CLOTH_DEFORMATION_GRAB_STRENGTH 0.1f static bool cloth_brush_sim_has_length_constraint(SculptClothSimulation *cloth_sim, const int v1, @@ -156,6 +181,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) @@ -166,6 +192,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]; @@ -186,11 +214,12 @@ static void cloth_brush_add_length_constraint(SculptSession *ss, /* Reallocation if the array capacity is exceeded. */ cloth_brush_reallocate_constraints(cloth_sim); - /* Add the constraint to the GSet to avoid creating it again. */ + /* Add the constraint to the #GSet to avoid creating it again. */ BLI_edgeset_add(cloth_sim->created_length_constraints, v1, v2); } static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim, + const int node_index, const int v, const float strength) { @@ -200,8 +229,10 @@ 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]; + length_constraint->elem_position_b = cloth_sim->softbody_pos[v]; length_constraint->type = SCULPT_CLOTH_CONSTRAINT_SOFTBODY; @@ -214,7 +245,35 @@ static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim cloth_brush_reallocate_constraints(cloth_sim); } +static void cloth_brush_add_pin_constraint(SculptClothSimulation *cloth_sim, + const int node_index, + const int v, + const float strength) +{ + SculptClothLengthConstraint *length_constraint = + &cloth_sim->length_constraints[cloth_sim->tot_length_constraints]; + + 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]; + + length_constraint->type = SCULPT_CLOTH_CONSTRAINT_PIN; + + length_constraint->length = 0.0f; + length_constraint->strength = strength; + + cloth_sim->tot_length_constraints++; + + /* Reallocation if the array capacity is exceeded. */ + cloth_brush_reallocate_constraints(cloth_sim); +} + static void cloth_brush_add_deformation_constraint(SculptClothSimulation *cloth_sim, + const int node_index, const int v, const float strength) { @@ -224,6 +283,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]; @@ -244,11 +305,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 = POINTER_AS_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; @@ -256,18 +326,21 @@ static void do_cloth_brush_build_constraints_task_cb_ex( * positions. */ const bool cloth_is_deform_brush = ss->cache != NULL && brush != NULL && SCULPT_is_cloth_deform_brush(brush); + + const bool use_falloff_plane = brush->cloth_force_falloff_type == + BRUSH_CLOTH_FORCE_FALLOFF_PLANE; float radius_squared = 0.0f; if (cloth_is_deform_brush) { radius_squared = ss->cache->initial_radius * ss->cache->initial_radius; } - /* Only limit the contraint creation to a radius when the simulation is local. */ + /* Only limit the constraint creation to a radius when the simulation is local. */ const float cloth_sim_radius_squared = brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_LOCAL ? 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) { @@ -283,9 +356,8 @@ static void do_cloth_brush_build_constraints_task_cb_ex( } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - if (brush->cloth_constraint_softbody_strength > 0.0f) { - cloth_brush_add_softbody_constraint( - data->cloth_sim, vd.index, brush->cloth_constraint_softbody_strength); + if (data->cloth_sim->softbody_strength > 0.0f) { + cloth_brush_add_softbody_constraint(data->cloth_sim, node_index, vd.index, 1.0f); } /* As we don't know the order of the neighbor vertices, we create all possible combinations @@ -297,8 +369,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); } } } @@ -307,17 +383,27 @@ static void do_cloth_brush_build_constraints_task_cb_ex( if (brush && brush->sculpt_tool == SCULPT_TOOL_CLOTH) { /* The cloth brush works by applying forces in most of its modes, but some of them require * deformation coordinates to make the simulation stable. */ - if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB && len_squared < radius_squared) { - /* 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); + if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { + if (use_falloff_plane) { + /* With plane falloff the strength of the constraints is set when applying the + * deformation forces. */ + cloth_brush_add_deformation_constraint( + data->cloth_sim, node_index, vd.index, CLOTH_DEFORMATION_GRAB_STRENGTH); + } + else if (len_squared < radius_squared) { + /* With radial falloff 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, node_index, vd.index, fade * CLOTH_DEFORMATION_GRAB_STRENGTH); + } } 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_TARGET_STRENGTH); + data->cloth_sim, node_index, vd.index, CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH); } } else if (data->cloth_sim->deformation_pos) { @@ -325,7 +411,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) { @@ -335,7 +421,7 @@ 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_pin_constraint(data->cloth_sim, node_index, vd.index, 1.0f - sim_falloff); } } } @@ -362,9 +448,8 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, const float *grab_delta = data->grab_delta; float(*imat)[4] = data->mat; - const bool use_falloff_plane = !SCULPT_is_cloth_deform_brush(brush) && - brush->cloth_force_falloff_type == - BRUSH_CLOTH_FORCE_FALLOFF_PLANE; + const bool use_falloff_plane = brush->cloth_force_falloff_type == + BRUSH_CLOTH_FORCE_FALLOFF_PLANE; PBVHVertexIter vd; const float bstrength = ss->cache->bstrength; @@ -374,7 +459,7 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); - /* For Pich Perpendicular Deform Type. */ + /* For Pinch Perpendicular Deform Type. */ float x_object_space[3]; float z_object_space[3]; if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR) { @@ -396,22 +481,17 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, madd_v3_v3fl(gravity, ss->cache->gravity_direction, -data->sd->gravity_factor); } - /* Original data for deform brushes. */ - SculptOrigVertData orig_data; - if (SCULPT_is_cloth_deform_brush(brush)) { - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - } - 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) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - copy_v3_v3(current_vertex_location, orig_data.co); + copy_v3_v3(current_vertex_location, ss->cache->cloth_sim->init_pos[vd.index]); } else { copy_v3_v3(current_vertex_location, vd.co); @@ -428,7 +508,7 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, float dist = sqrtf(test.dist); if (use_falloff_plane) { - dist = dist_to_plane_v3(vd.co, deform_plane); + dist = dist_to_plane_v3(current_vertex_location, deform_plane); } const float fade = sim_factor * bstrength * @@ -463,9 +543,15 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, break; case BRUSH_CLOTH_DEFORM_GRAB: madd_v3_v3v3fl(cloth_sim->deformation_pos[vd.index], - orig_data.co, + cloth_sim->init_pos[vd.index], ss->cache->grab_delta_symmetry, fade); + if (use_falloff_plane) { + cloth_sim->deformation_strength[vd.index] = clamp_f(fade, 0.0f, 1.0f); + } + else { + cloth_sim->deformation_strength[vd.index] = 1.0f; + } zero_v3(force); break; case BRUSH_CLOTH_DEFORM_SNAKE_HOOK: @@ -640,17 +726,27 @@ 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 = POINTER_AS_INT(BLI_ghash_lookup(data->cloth_sim->node_state_index, node)); + if (data->cloth_sim->node_state[node_index] != SCULPT_CLOTH_NODE_ACTIVE) { + return; + } + + AutomaskingCache *automasking = SCULPT_automasking_active_cache_get(ss); + 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]; @@ -660,10 +756,10 @@ static void do_cloth_brush_solve_simulation_task_cb_ex( 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)); + mul_v3_fl(pos_diff, (1.0f - cloth_sim->damping) * sim_factor); const float mask_v = (1.0f - (vd.mask ? *vd.mask : 0.0f)) * - SCULPT_automasking_factor_get(ss, vd.index); + SCULPT_automasking_factor_get(automasking, ss, vd.index); madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v); madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v); @@ -686,16 +782,27 @@ 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, Brush *brush, SculptClothSimulation *cloth_sim) { + + AutomaskingCache *automasking = SCULPT_automasking_active_cache_get(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; @@ -710,31 +817,37 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss, (cloth_sim->length_constraint_tweak[v2] * 0.5f); if (current_distance > 0.0f) { - mul_v3_v3fl(correction_vector, v1_to_v2, 1.0f - (constraint_distance / current_distance)); + mul_v3_v3fl(correction_vector, + v1_to_v2, + CLOTH_SOLVER_DISPLACEMENT_FACTOR * + (1.0f - (constraint_distance / current_distance))); } else { - copy_v3_v3(correction_vector, v1_to_v2); + mul_v3_v3fl(correction_vector, v1_to_v2, CLOTH_SOLVER_DISPLACEMENT_FACTOR); } mul_v3_v3fl(correction_vector_half, correction_vector, 0.5f); const float mask_v1 = (1.0f - SCULPT_vertex_mask_get(ss, v1)) * - SCULPT_automasking_factor_get(ss, v1); + SCULPT_automasking_factor_get(automasking, ss, v1); 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; + SCULPT_automasking_factor_get(automasking, ss, v2); + + 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) { @@ -743,14 +856,26 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss, 0.5f; } - madd_v3_v3fl(cloth_sim->pos[v1], - correction_vector_half, - 1.0f * mask_v1 * sim_factor_v1 * constraint->strength * deformation_strength); - if (v1 != v2) { - madd_v3_v3fl(cloth_sim->pos[v2], + if (constraint->type == SCULPT_CLOTH_CONSTRAINT_SOFTBODY) { + const float softbody_plasticity = brush ? brush->cloth_constraint_softbody_strength : 0.0f; + madd_v3_v3fl(cloth_sim->pos[v1], correction_vector_half, - -1.0f * mask_v2 * sim_factor_v2 * constraint->strength * - deformation_strength); + 1.0f * mask_v1 * sim_factor_v1 * constraint->strength * softbody_plasticity); + madd_v3_v3fl(cloth_sim->softbody_pos[v1], + correction_vector_half, + -1.0f * mask_v1 * sim_factor_v1 * constraint->strength * + (1.0f - softbody_plasticity)); + } + else { + madd_v3_v3fl(cloth_sim->pos[v1], + correction_vector_half, + 1.0f * mask_v1 * sim_factor_v1 * constraint->strength * deformation_strength); + if (v1 != v2) { + madd_v3_v3fl(cloth_sim->pos[v2], + correction_vector_half, + -1.0f * mask_v2 * sim_factor_v2 * constraint->strength * + deformation_strength); + } } } } @@ -805,7 +930,7 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod BKE_curvemapping_init(brush->curve); - /* Init the grab delta. */ + /* Initialize the grab delta. */ copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); normalize_v3(grab_delta); @@ -815,7 +940,7 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod return; } - /* Calcuate push offset. */ + /* Calculate push offset. */ if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PUSH) { mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius); @@ -829,7 +954,7 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod 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. */ + /* Initialize 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]); @@ -850,8 +975,8 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod } } - if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) { - /* Set the deformation strength to 0. Snake hook will initialize the strength in the required + if (ELEM(brush->cloth_deform_type, BRUSH_CLOTH_DEFORM_SNAKE_HOOK, BRUSH_CLOTH_DEFORM_GRAB)) { + /* Set the deformation strength to 0. Brushes will initialize the strength in the required * area. */ const int totverts = SCULPT_vertex_count_get(ss); for (int i = 0; i < totverts; i++) { @@ -865,12 +990,32 @@ 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, - Brush *brush, const float cloth_mass, const float cloth_damping, - const bool use_collisions) + const float cloth_softbody_strength, + const bool use_collisions, + const bool needs_deform_coords) { const int totverts = SCULPT_vertex_count_get(ss); SculptClothSimulation *cloth_sim; @@ -892,32 +1037,38 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, cloth_sim->length_constraint_tweak = MEM_calloc_arrayN( totverts, sizeof(float), "cloth sim length tweak"); - /* Brush can be NULL for tools that need the solver but don't rely on constraint to deformation - * positions. */ - if (brush && SCULPT_is_cloth_deform_brush(brush)) { + if (needs_deform_coords) { cloth_sim->deformation_pos = MEM_calloc_arrayN( totverts, sizeof(float[3]), "cloth sim deformation positions"); cloth_sim->deformation_strength = MEM_calloc_arrayN( totverts, sizeof(float), "cloth sim deformation strength"); } + if (cloth_softbody_strength > 0.0f) { + cloth_sim->softbody_pos = MEM_calloc_arrayN( + totverts, sizeof(float[3]), "cloth sim softbody pos"); + } + cloth_sim->mass = cloth_mass; cloth_sim->damping = cloth_damping; + cloth_sim->softbody_strength = cloth_softbody_strength; if (use_collisions) { 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, int totnode, SculptClothSimulation *cloth_sim, - /* Cannot be const, because it is assigned to a non-const variable. + /* Cannot be `const`, because it is assigned to a `non-const` variable. * NOLINTNEXTLINE: readability-non-const-parameter. */ float initial_location[3], const float radius) @@ -952,6 +1103,7 @@ void SCULPT_cloth_brush_simulation_init(SculptSession *ss, SculptClothSimulation { const int totverts = SCULPT_vertex_count_get(ss); const bool has_deformation_pos = cloth_sim->deformation_pos != NULL; + const bool has_softbody_pos = cloth_sim->softbody_pos != NULL; for (int i = 0; i < totverts; i++) { copy_v3_v3(cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, i)); copy_v3_v3(cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, i)); @@ -960,6 +1112,9 @@ void SCULPT_cloth_brush_simulation_init(SculptSession *ss, SculptClothSimulation copy_v3_v3(cloth_sim->deformation_pos[i], SCULPT_vertex_co_get(ss, i)); cloth_sim->deformation_strength[i] = 1.0f; } + if (has_softbody_pos) { + copy_v3_v3(cloth_sim->softbody_pos[i], SCULPT_vertex_co_get(ss, i)); + } } } @@ -971,14 +1126,38 @@ 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 = POINTER_AS_INT(BLI_ghash_lookup(cloth_sim->node_state_index, nodes[n])); + cloth_sim->node_state[node_index] = SCULPT_CLOTH_NODE_ACTIVE; + } +} + +static void sculpt_cloth_ensure_constraints_in_simulation_area(Sculpt *sd, + Object *ob, + PBVHNode **nodes, + int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + const float radius = ss->cache->initial_radius; + const float limit = radius + (radius * brush->cloth_sim_limit); + float sim_location[3]; + cloth_brush_simulation_location_get(ss, brush, sim_location); + SCULPT_cloth_brush_ensure_nodes_constraints( + sd, ob, nodes, totnode, ss->cache->cloth_sim, sim_location, limit); +} + /* 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); - /* In the first brush step of each symmetry pass, build the constraints for the vertices in all - * nodes inside the simulation's limits. */ /* Brushes that use anchored strokes and restore the mesh can't rely on symmetry passes and steps * count as it is always the first step, so the simulation needs to be created when it does not * exist for this stroke. */ @@ -988,25 +1167,40 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode if (SCULPT_stroke_is_first_brush_step(ss->cache) || !ss->cache->cloth_sim) { ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( ss, - brush, brush->cloth_mass, brush->cloth_damping, - (brush->flag2 & BRUSH_CLOTH_USE_COLLISION)); + brush->cloth_constraint_softbody_strength, + (brush->flag2 & BRUSH_CLOTH_USE_COLLISION), + 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); - + if (brush->cloth_simulation_area_type == BRUSH_CLOTH_SIMULATION_AREA_LOCAL) { + /* When using simulation a fixed local simulation area, constraints are created only using + * the initial stroke position and initial radius (per symmetry pass) instead of per node. + * This allows to skip unnecessary constraints that will never be simulated, making the + * solver faster. When the simulation starts for a node, the node gets activated and all its + * constraints are considered final. As the same node can be included inside the brush radius + * from multiple symmetry passes, the cloth brush can't activate the node for simulation yet + * as this will cause the ensure constraints function to skip the node in the next symmetry + * passes. It needs to build the constraints here and skip simulating the first step, so all + * passes can add their constraints to all affected nodes. */ + sculpt_cloth_ensure_constraints_in_simulation_area(sd, ob, nodes, totnode); + } + /* The first step of a symmetry pass is never simulated as deformation modes need valid delta + * for brush tip alignment. */ return; } + /* Ensure the constraints for the nodes. */ + sculpt_cloth_ensure_constraints_in_simulation_area(sd, ob, nodes, totnode); + /* 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); @@ -1023,8 +1217,11 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim) MEM_SAFE_FREE(cloth_sim->length_constraints); MEM_SAFE_FREE(cloth_sim->length_constraint_tweak); MEM_SAFE_FREE(cloth_sim->deformation_pos); + MEM_SAFE_FREE(cloth_sim->softbody_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); } @@ -1066,9 +1263,14 @@ void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr, 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); + float local_mat[4][4]; + copy_m4_m4(local_mat, ss->cache->stroke_local_mat); + + if (ss->cache->brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { + add_v3_v3v3(local_mat[3], ss->cache->true_location, ss->cache->grab_delta); + } + + GPU_matrix_mul(local_mat); const float dist = ss->cache->radius; const float arrow_x = ss->cache->radius * 0.2f; @@ -1100,6 +1302,7 @@ typedef enum eSculpClothFilterType { CLOTH_FILTER_INFLATE, CLOTH_FILTER_EXPAND, CLOTH_FILTER_PINCH, + CLOTH_FILTER_SCALE, } eSculptClothFilterType; static EnumPropertyItem prop_cloth_filter_type[] = { @@ -1107,6 +1310,11 @@ static EnumPropertyItem prop_cloth_filter_type[] = { {CLOTH_FILTER_INFLATE, "INFLATE", 0, "Inflate", "Inflates the cloth"}, {CLOTH_FILTER_EXPAND, "EXPAND", 0, "Expand", "Expands the cloth's dimensions"}, {CLOTH_FILTER_PINCH, "PINCH", 0, "Pinch", "Pulls the cloth to the cursor's start position"}, + {CLOTH_FILTER_SCALE, + "SCALE", + 0, + "Scale", + "Scales the mesh as a soft-body using the origin of the object as scale"}, {0, NULL, 0, NULL, NULL}, }; @@ -1142,6 +1350,35 @@ static EnumPropertyItem prop_cloth_filter_force_axis_items[] = { {0, NULL, 0, NULL, NULL}, }; +static bool cloth_filter_is_deformation_filter(eSculptClothFilterType filter_type) +{ + return ELEM(filter_type, CLOTH_FILTER_SCALE); +} + +static void cloth_filter_apply_displacement_to_deform_co(const int v_index, + const float disp[3], + FilterCache *filter_cache) +{ + float final_disp[3]; + copy_v3_v3(final_disp, disp); + SCULPT_filter_zero_disabled_axis_components(final_disp, filter_cache); + add_v3_v3v3(filter_cache->cloth_sim->deformation_pos[v_index], + filter_cache->cloth_sim->init_pos[v_index], + final_disp); +} + +static void cloth_filter_apply_forces_to_vertices(const int v_index, + const float force[3], + const float gravity[3], + FilterCache *filter_cache) +{ + float final_force[3]; + copy_v3_v3(final_force, force); + SCULPT_filter_zero_disabled_axis_components(final_force, filter_cache); + add_v3_v3(final_force, gravity); + cloth_brush_apply_force_to_vertex(NULL, filter_cache->cloth_sim, final_force, v_index); +} + static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls)) @@ -1152,7 +1389,9 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, PBVHNode *node = data->nodes[i]; SculptClothSimulation *cloth_sim = ss->filter_cache->cloth_sim; - const int filter_type = data->filter_type; + + const eSculptClothFilterType filter_type = data->filter_type; + const bool is_deformation_filter = cloth_filter_is_deformation_filter(filter_type); float sculpt_gravity[3] = {0.0f}; if (sd->gravity_object) { @@ -1167,8 +1406,10 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { float fade = vd.mask ? *vd.mask : 0.0f; + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); fade = 1.0f - fade; float force[3] = {0.0f, 0.0f, 0.0f}; + float disp[3], temp[3], transform[3][3]; if (ss->filter_cache->active_face_set != SCULPT_FACE_SET_NONE) { if (!SCULPT_vertex_has_face_set(ss, vd.index, ss->filter_cache->active_face_set)) { @@ -1202,19 +1443,23 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, normalize_v3(force); mul_v3_fl(force, fade * data->filter_strength); break; - } + case CLOTH_FILTER_SCALE: + unit_m3(transform); + scale_m3_fl(transform, 1.0f + (fade * data->filter_strength)); + copy_v3_v3(temp, cloth_sim->init_pos[vd.index]); + mul_m3_v3(transform, temp); + sub_v3_v3v3(disp, temp, cloth_sim->init_pos[vd.index]); + zero_v3(force); - SCULPT_filter_to_orientation_space(force, ss->filter_cache); - for (int axis = 0; axis < 3; axis++) { - if (!ss->filter_cache->enabled_force_axis[axis]) { - force[axis] = 0.0f; - } + break; } - SCULPT_filter_to_object_space(force, ss->filter_cache); - add_v3_v3(force, sculpt_gravity); - - cloth_brush_apply_force_to_vertex(ss, cloth_sim, force, vd.index); + if (is_deformation_filter) { + cloth_filter_apply_displacement_to_deform_co(vd.index, disp, ss->filter_cache); + } + else { + cloth_filter_apply_forces_to_vertices(vd.index, force, sculpt_gravity, ss->filter_cache); + } } BKE_pbvh_vertex_iter_end; @@ -1267,6 +1512,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); @@ -1285,6 +1534,8 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent Sculpt *sd = CTX_data_tool_settings(C)->sculpt; SculptSession *ss = ob->sculpt; + const eSculptClothFilterType filter_type = RNA_enum_get(op->ptr, "type"); + /* Update the active vertex */ float mouse[2]; SculptCursorGeometryInfo sgi; @@ -1300,24 +1551,31 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent SCULPT_undo_push_begin("Cloth filter"); SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS); + ss->filter_cache->automasking = SCULPT_automasking_cache_init(sd, NULL, ob); + const float cloth_mass = RNA_float_get(op->ptr, "cloth_mass"); const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping"); const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions"); ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, NULL, cloth_mass, cloth_damping, use_collisions); + ss, + cloth_mass, + cloth_damping, + 0.0f, + use_collisions, + cloth_filter_is_deformation_filter(filter_type)); copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss)); 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) { |