diff options
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenkernel/BKE_paint.h | 26 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/brush.c | 17 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_cursor.c | 52 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_stroke.c | 9 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 191 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_cloth.c | 679 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 88 | ||||
-rw-r--r-- | source/blender/gpu/GPU_immediate_util.h | 1 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_immediate_util.c | 5 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_defaults.h | 4 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_types.h | 31 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_brush.c | 63 |
13 files changed, 1132 insertions, 35 deletions
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); |