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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/sculpt_paint')
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c5
-rw-r--r--source/blender/editors/sculpt_paint/paint_mask.c252
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c380
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_cloth.c85
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_face_set.c215
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_undo.c8
6 files changed, 792 insertions, 153 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index f0285c8faf3..550913fc8af 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1310,6 +1310,11 @@ static bool paint_cursor_context_init(bContext *C,
Object *active_object = pcontext->vc.obact;
pcontext->ss = active_object ? active_object->sculpt : NULL;
+ if (pcontext->ss && pcontext->ss->draw_faded_cursor) {
+ pcontext->outline_alpha = 0.3f;
+ copy_v3_fl(pcontext->outline_col, 0.8f);
+ }
+
pcontext->is_stroke_active = pcontext->ups->stroke_active;
return true;
diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c
index cc0d20089b4..26abd19d5e8 100644
--- a/source/blender/editors/sculpt_paint/paint_mask.c
+++ b/source/blender/editors/sculpt_paint/paint_mask.c
@@ -246,8 +246,16 @@ typedef struct LassoGestureData {
} LassoGestureData;
typedef struct LineGestureData {
+ /* Plane aligned to the gesture line. */
float true_plane[4];
float plane[4];
+
+ /* Planes to limit the action to the length of the gesture segment at both sides of the affected
+ * area. */
+ float side_plane[2][4];
+ float true_side_plane[2][4];
+ bool use_side_planes;
+
bool flip;
} LineGestureData;
@@ -320,6 +328,13 @@ static void sculpt_gesture_operator_properties(wmOperatorType *ot)
false,
"Front Faces Only",
"Affect only faces facing towards the view");
+
+ RNA_def_boolean(ot->srna,
+ "use_limit_to_segment",
+ false,
+ "Limit to Segment",
+ "Apply the gesture action only to the area that is contained within the "
+ "segement without extending its effect to the entire line");
}
static void sculpt_gesture_context_init_common(bContext *C,
@@ -332,6 +347,7 @@ static void sculpt_gesture_context_init_common(bContext *C,
/* Operator properties. */
sgcontext->front_faces_only = RNA_boolean_get(op->ptr, "use_front_faces_only");
+ sgcontext->line.use_side_planes = RNA_boolean_get(op->ptr, "use_limit_to_segment");
/* SculptSession */
sgcontext->ss = ob->sculpt;
@@ -448,6 +464,50 @@ static SculptGestureContext *sculpt_gesture_init_from_box(bContext *C, wmOperato
return sgcontext;
}
+static void sculpt_gesture_line_plane_from_tri(float *r_plane,
+ SculptGestureContext *sgcontext,
+ const bool flip,
+ const float p1[3],
+ const float p2[3],
+ const float p3[3])
+{
+ float normal[3];
+ normal_tri_v3(normal, p1, p2, p3);
+ mul_v3_mat3_m4v3(normal, sgcontext->vc.obact->imat, normal);
+ if (flip) {
+ mul_v3_fl(normal, -1.0f);
+ }
+ float plane_point_object_space[3];
+ mul_v3_m4v3(plane_point_object_space, sgcontext->vc.obact->imat, p1);
+ plane_from_point_normal_v3(r_plane, plane_point_object_space, normal);
+}
+
+/* Creates 4 points in the plane defined by the line and 2 extra points with an offset relative to
+ * this plane. */
+static void sculpt_gesture_line_calculate_plane_points(SculptGestureContext *sgcontext,
+ float line_points[2][2],
+ float r_plane_points[4][3],
+ float r_offset_plane_points[2][3])
+{
+ float depth_point[3];
+ add_v3_v3v3(depth_point, sgcontext->true_view_origin, sgcontext->true_view_normal);
+ ED_view3d_win_to_3d(
+ sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[0], r_plane_points[0]);
+ ED_view3d_win_to_3d(
+ sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[1], r_plane_points[3]);
+
+ madd_v3_v3v3fl(depth_point, sgcontext->true_view_origin, sgcontext->true_view_normal, 10.0f);
+ ED_view3d_win_to_3d(
+ sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[0], r_plane_points[1]);
+ ED_view3d_win_to_3d(
+ sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[1], r_plane_points[2]);
+
+ float normal[3];
+ normal_tri_v3(normal, r_plane_points[0], r_plane_points[1], r_plane_points[2]);
+ add_v3_v3v3(r_offset_plane_points[0], r_plane_points[0], normal);
+ add_v3_v3v3(r_offset_plane_points[1], r_plane_points[3], normal);
+}
+
static SculptGestureContext *sculpt_gesture_init_from_line(bContext *C, wmOperator *op)
{
SculptGestureContext *sgcontext = MEM_callocN(sizeof(SculptGestureContext),
@@ -464,36 +524,33 @@ static SculptGestureContext *sculpt_gesture_init_from_line(bContext *C, wmOperat
sgcontext->line.flip = RNA_boolean_get(op->ptr, "flip");
- float depth_point[3];
- float plane_points[3][3];
-
- /* Calculate a triangle in the line's plane. */
- add_v3_v3v3(depth_point, sgcontext->true_view_origin, sgcontext->true_view_normal);
- ED_view3d_win_to_3d(
- sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[0], plane_points[0]);
-
- madd_v3_v3v3fl(depth_point, sgcontext->true_view_origin, sgcontext->true_view_normal, 10.0f);
- ED_view3d_win_to_3d(
- sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[0], plane_points[1]);
- ED_view3d_win_to_3d(
- sgcontext->vc.v3d, sgcontext->vc.region, depth_point, line_points[1], plane_points[2]);
-
- /* Calculate final line plane and normal using the triangle. */
- float normal[3];
- normal_tri_v3(normal, plane_points[0], plane_points[1], plane_points[2]);
- if (!sgcontext->vc.rv3d->is_persp) {
- mul_v3_fl(normal, -1.0f);
- }
-
- /* Apply flip. */
- if (sgcontext->line.flip) {
- mul_v3_fl(normal, -1.0f);
- }
-
- mul_v3_mat3_m4v3(normal, sgcontext->vc.obact->imat, normal);
- float plane_point_object_space[3];
- mul_v3_m4v3(plane_point_object_space, sgcontext->vc.obact->imat, plane_points[0]);
- plane_from_point_normal_v3(sgcontext->line.true_plane, plane_point_object_space, normal);
+ float plane_points[4][3];
+ float offset_plane_points[2][3];
+ sculpt_gesture_line_calculate_plane_points(
+ sgcontext, line_points, plane_points, offset_plane_points);
+
+ /* Calculate line plane and normal. */
+ const bool flip = sgcontext->line.flip ^ !sgcontext->vc.rv3d->is_persp;
+ sculpt_gesture_line_plane_from_tri(sgcontext->line.true_plane,
+ sgcontext,
+ flip,
+ plane_points[0],
+ plane_points[1],
+ plane_points[2]);
+
+ /* Calculate the side planes. */
+ sculpt_gesture_line_plane_from_tri(sgcontext->line.true_side_plane[0],
+ sgcontext,
+ false,
+ plane_points[1],
+ plane_points[0],
+ offset_plane_points[0]);
+ sculpt_gesture_line_plane_from_tri(sgcontext->line.true_side_plane[1],
+ sgcontext,
+ false,
+ plane_points[3],
+ plane_points[2],
+ offset_plane_points[1]);
return sgcontext;
}
@@ -544,14 +601,20 @@ static void sculpt_gesture_flip_for_symmetry_pass(SculptGestureContext *sgcontex
flip_v3_v3(sgcontext->view_normal, sgcontext->true_view_normal, symmpass);
flip_v3_v3(sgcontext->view_origin, sgcontext->true_view_origin, symmpass);
flip_plane(sgcontext->line.plane, sgcontext->line.true_plane, symmpass);
+ flip_plane(sgcontext->line.side_plane[0], sgcontext->line.true_side_plane[0], symmpass);
+ flip_plane(sgcontext->line.side_plane[1], sgcontext->line.true_side_plane[1], symmpass);
}
static void sculpt_gesture_update_effected_nodes_by_line_plane(SculptGestureContext *sgcontext)
{
SculptSession *ss = sgcontext->ss;
- float clip_planes[1][4];
+ float clip_planes[3][4];
copy_v4_v4(clip_planes[0], sgcontext->line.plane);
- PBVHFrustumPlanes frustum = {.planes = clip_planes, .num_planes = 1};
+ copy_v4_v4(clip_planes[1], sgcontext->line.side_plane[0]);
+ copy_v4_v4(clip_planes[2], sgcontext->line.side_plane[1]);
+
+ const int num_planes = sgcontext->line.use_side_planes ? 3 : 1;
+ PBVHFrustumPlanes frustum = {.planes = clip_planes, .num_planes = num_planes};
BKE_pbvh_search_gather(ss->pbvh,
BKE_pbvh_node_frustum_contain_AABB,
&frustum,
@@ -630,6 +693,11 @@ static bool sculpt_gesture_is_vertex_effected(SculptGestureContext *sgcontext, P
case SCULPT_GESTURE_SHAPE_LASSO:
return sculpt_gesture_is_effected_lasso(sgcontext, vd->co);
case SCULPT_GESTURE_SHAPE_LINE:
+ if (sgcontext->line.use_side_planes) {
+ return plane_point_side_v3(sgcontext->line.plane, vd->co) > 0.0f &&
+ plane_point_side_v3(sgcontext->line.side_plane[0], vd->co) > 0.0f &&
+ plane_point_side_v3(sgcontext->line.side_plane[1], vd->co) > 0.0f;
+ }
return plane_point_side_v3(sgcontext->line.plane, vd->co) > 0.0f;
}
return false;
@@ -862,6 +930,24 @@ static EnumPropertyItem prop_trim_operation_types[] = {
{0, NULL, 0, NULL, NULL},
};
+typedef enum eSculptTrimOrientationType {
+ SCULPT_GESTURE_TRIM_ORIENTATION_VIEW,
+ SCULPT_GESTURE_TRIM_ORIENTATION_SURFACE,
+} eSculptTrimOrientationType;
+static EnumPropertyItem prop_trim_orientation_types[] = {
+ {SCULPT_GESTURE_TRIM_ORIENTATION_VIEW,
+ "VIEW",
+ 0,
+ "View",
+ "Use the view to orientate the trimming shape"},
+ {SCULPT_GESTURE_TRIM_ORIENTATION_SURFACE,
+ "SURFACE",
+ 0,
+ "Surface",
+ "Use the surface normal to orientate the trimming shape"},
+ {0, NULL, 0, NULL, NULL},
+};
+
typedef struct SculptGestureTrimOperation {
SculptGestureOperation op;
@@ -874,6 +960,7 @@ typedef struct SculptGestureTrimOperation {
bool use_cursor_depth;
eSculptTrimOperationType mode;
+ eSculptTrimOrientationType orientation;
} SculptGestureTrimOperation;
static void sculpt_gesture_trim_normals_update(SculptGestureContext *sgcontext)
@@ -910,6 +997,29 @@ static void sculpt_gesture_trim_normals_update(SculptGestureContext *sgcontext)
trim_operation->mesh = result;
}
+/* Get the origin and normal that are going to be used for calculating the depth and position the
+ * trimming geometry. */
+static void sculpt_gesture_trim_shape_origin_normal_get(SculptGestureContext *sgcontext,
+ float *r_origin,
+ float *r_normal)
+{
+ SculptGestureTrimOperation *trim_operation = (SculptGestureTrimOperation *)sgcontext->operation;
+ /* Use the view origin and normal in world space. The trimming mesh coordinates are
+ * calculated in world space, aligned to the view, and then converted to object space to
+ * store them in the final trimming mesh which is going to be used in the boolean operation.
+ */
+ switch (trim_operation->orientation) {
+ case SCULPT_GESTURE_TRIM_ORIENTATION_VIEW:
+ copy_v3_v3(r_origin, sgcontext->world_space_view_origin);
+ copy_v3_v3(r_normal, sgcontext->world_space_view_normal);
+ break;
+ case SCULPT_GESTURE_TRIM_ORIENTATION_SURFACE:
+ mul_v3_m4v3(r_origin, sgcontext->vc.obact->obmat, sgcontext->ss->gesture_initial_location);
+ mul_v3_m4v3(r_normal, sgcontext->vc.obact->obmat, sgcontext->ss->gesture_initial_normal);
+ break;
+ }
+}
+
static void sculpt_gesture_trim_calculate_depth(bContext *C, SculptGestureContext *sgcontext)
{
SculptGestureTrimOperation *trim_operation = (SculptGestureTrimOperation *)sgcontext->operation;
@@ -919,11 +1029,11 @@ static void sculpt_gesture_trim_calculate_depth(bContext *C, SculptGestureContex
const int totvert = SCULPT_vertex_count_get(ss);
- float view_plane[4];
- const float *view_origin = sgcontext->world_space_view_origin;
- const float *view_normal = sgcontext->world_space_view_normal;
-
- plane_from_point_normal_v3(view_plane, view_origin, view_normal);
+ float shape_plane[4];
+ float shape_origin[3];
+ float shape_normal[3];
+ sculpt_gesture_trim_shape_origin_normal_get(sgcontext, shape_origin, shape_normal);
+ plane_from_point_normal_v3(shape_plane, shape_origin, shape_normal);
trim_operation->depth_front = FLT_MAX;
trim_operation->depth_back = -FLT_MAX;
@@ -935,7 +1045,7 @@ static void sculpt_gesture_trim_calculate_depth(bContext *C, SculptGestureContex
* store them. */
float world_space_vco[3];
mul_v3_m4v3(world_space_vco, vc->obact->obmat, vco);
- const float dist = dist_signed_to_plane_v3(world_space_vco, view_plane);
+ const float dist = dist_signed_to_plane_v3(world_space_vco, shape_plane);
trim_operation->depth_front = min_ff(dist, trim_operation->depth_front);
trim_operation->depth_back = max_ff(dist, trim_operation->depth_back);
}
@@ -944,11 +1054,22 @@ static void sculpt_gesture_trim_calculate_depth(bContext *C, SculptGestureContex
float world_space_gesture_initial_location[3];
mul_v3_m4v3(
world_space_gesture_initial_location, vc->obact->obmat, ss->gesture_initial_location);
- const float mid_point_depth = ss->gesture_initial_hit ?
- dist_signed_to_plane_v3(world_space_gesture_initial_location,
- view_plane) :
- (trim_operation->depth_back + trim_operation->depth_front) *
- 0.5f;
+
+ float mid_point_depth;
+ if (trim_operation->orientation == SCULPT_GESTURE_TRIM_ORIENTATION_VIEW) {
+ mid_point_depth = ss->gesture_initial_hit ?
+ dist_signed_to_plane_v3(world_space_gesture_initial_location,
+ shape_plane) :
+ (trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
+ }
+ else {
+ /* When using normal orientation, if the stroke started over the mesh, position the mid point
+ * at 0 distance from the shape plane. This positions the trimming shape half inside of the
+ * surface. */
+ mid_point_depth = ss->gesture_initial_hit ?
+ 0.0f :
+ (trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
+ }
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
@@ -986,30 +1107,41 @@ static void sculpt_gesture_trim_geometry_generate(SculptGestureContext *sgcontex
depth_back += 0.1f;
}
- /* Use the view origin and normal in world space. The trimming mesh coordinates are calculated in
- * world space, aligned to the view, and then converted to object space to store them in the
- * final trimming mesh which is going to be used in the boolean operation.
- */
- const float *view_origin = sgcontext->world_space_view_origin;
- const float *view_normal = sgcontext->world_space_view_normal;
+ float shape_origin[3];
+ float shape_normal[3];
+ float shape_plane[4];
+ sculpt_gesture_trim_shape_origin_normal_get(sgcontext, shape_origin, shape_normal);
+ plane_from_point_normal_v3(shape_plane, shape_origin, shape_normal);
const float(*ob_imat)[4] = vc->obact->imat;
/* Write vertices coordinates for the front face. */
float depth_point[3];
- madd_v3_v3v3fl(depth_point, view_origin, view_normal, depth_front);
+ madd_v3_v3v3fl(depth_point, shape_origin, shape_normal, depth_front);
for (int i = 0; i < tot_screen_points; i++) {
float new_point[3];
- ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
+ if (trim_operation->orientation == SCULPT_GESTURE_TRIM_ORIENTATION_VIEW) {
+ ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
+ }
+ else {
+ ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point);
+ madd_v3_v3fl(new_point, shape_normal, depth_front);
+ }
mul_v3_m4v3(trim_operation->mesh->mvert[i].co, ob_imat, new_point);
mul_v3_m4v3(trim_operation->true_mesh_co[i], ob_imat, new_point);
}
/* Write vertices coordinates for the back face. */
- madd_v3_v3v3fl(depth_point, view_origin, view_normal, depth_back);
+ madd_v3_v3v3fl(depth_point, shape_origin, shape_normal, depth_back);
for (int i = 0; i < tot_screen_points; i++) {
float new_point[3];
- ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
+ if (trim_operation->orientation == SCULPT_GESTURE_TRIM_ORIENTATION_VIEW) {
+ ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
+ }
+ else {
+ ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point);
+ madd_v3_v3fl(new_point, shape_normal, depth_back);
+ }
mul_v3_m4v3(trim_operation->mesh->mvert[i + tot_screen_points].co, ob_imat, new_point);
mul_v3_m4v3(trim_operation->true_mesh_co[i + tot_screen_points], ob_imat, new_point);
}
@@ -1224,6 +1356,12 @@ static void sculpt_gesture_init_trim_properties(SculptGestureContext *sgcontext,
trim_operation->mode = RNA_enum_get(op->ptr, "trim_mode");
trim_operation->use_cursor_depth = RNA_boolean_get(op->ptr, "use_cursor_depth");
+ trim_operation->orientation = RNA_enum_get(op->ptr, "trim_orientation");
+
+ /* If the cursor was not over the mesh, force the orientation to view. */
+ if (!sgcontext->ss->gesture_initial_hit) {
+ trim_operation->orientation = SCULPT_GESTURE_TRIM_ORIENTATION_VIEW;
+ }
}
static void sculpt_trim_gesture_operator_properties(wmOperatorType *ot)
@@ -1240,6 +1378,12 @@ static void sculpt_trim_gesture_operator_properties(wmOperatorType *ot)
false,
"Use Cursor for Depth",
"Use cursor location and radius for the dimensions and position of the trimming shape");
+ RNA_def_enum(ot->srna,
+ "trim_orientation",
+ prop_trim_orientation_types,
+ SCULPT_GESTURE_TRIM_ORIENTATION_VIEW,
+ "Shape Orientation",
+ NULL);
}
/* Project Gesture Operation. */
@@ -1425,6 +1569,7 @@ static int sculpt_trim_gesture_box_invoke(bContext *C, wmOperator *op, const wmE
ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
if (ss->gesture_initial_hit) {
copy_v3_v3(ss->gesture_initial_location, sgi.location);
+ copy_v3_v3(ss->gesture_initial_normal, sgi.normal);
}
return WM_gesture_box_invoke(C, op, event);
@@ -1460,6 +1605,7 @@ static int sculpt_trim_gesture_lasso_invoke(bContext *C, wmOperator *op, const w
ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
if (ss->gesture_initial_hit) {
copy_v3_v3(ss->gesture_initial_location, sgi.location);
+ copy_v3_v3(ss->gesture_initial_normal, sgi.normal);
}
return WM_gesture_lasso_invoke(C, op, event);
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 56b5a494df0..ecd45bfb5e9 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -76,6 +76,11 @@
#include "IMB_colormanagement.h"
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
#include "WM_api.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
@@ -84,6 +89,7 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
+#include "ED_space_api.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
@@ -1159,6 +1165,7 @@ void SCULPT_floodfill_free(SculptFloodFill *flood)
flood->queue = NULL;
}
+/* -------------------------------------------------------------------- */
/** \name Tool Capabilities
*
* Avoid duplicate checks, internal logic only,
@@ -1380,6 +1387,7 @@ static void sculpt_project_v3_normal_align(SculptSession *ss,
grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale);
}
+/* -------------------------------------------------------------------- */
/** \name SculptProjectVector
*
* Fast-path for #project_plane_v3_v3v3
@@ -1887,6 +1895,7 @@ static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache)
return 1.0f;
}
+/* -------------------------------------------------------------------- */
/** \name Calculate Normal and Center
*
* Calculate geometry surrounding the brush center.
@@ -2338,7 +2347,7 @@ static float brush_strength(const Sculpt *sd,
final_pressure = pressure * pressure;
return final_pressure * overlap * feather;
case SCULPT_TOOL_SMEAR:
- return pressure * overlap * feather;
+ return alpha * pressure * overlap * feather;
case SCULPT_TOOL_CLAY_STRIPS:
/* Clay Strips needs less strength to compensate the curve. */
final_pressure = powf(pressure, 1.5f);
@@ -3007,6 +3016,7 @@ static void do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
}
}
+/* -------------------------------------------------------------------- */
/** \name Sculpt Multires Displacement Eraser Brush
* \{ */
@@ -3229,7 +3239,6 @@ static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
}
/* -------------------------------------------------------------------- */
-
/** \name Sculpt Topology Brush
* \{ */
@@ -3805,20 +3814,33 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata,
ss, &test, data->brush->falloff_shape);
const int thread_id = BLI_task_parallel_thread_id(tls);
+ const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE;
+
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
SCULPT_orig_vert_data_update(&orig_data, &vd);
if (sculpt_brush_test_sq_fn(&test, orig_data.co)) {
- const float fade = bstrength * SCULPT_brush_strength_factor(ss,
- brush,
- orig_data.co,
- sqrtf(test.dist),
- orig_data.no,
- NULL,
- vd.mask ? *vd.mask : 0.0f,
- vd.index,
- thread_id);
+ float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ orig_data.co,
+ sqrtf(test.dist),
+ orig_data.no,
+ NULL,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ if (grab_silhouette) {
+ float silhouette_test_dir[3];
+ normalize_v3_v3(silhouette_test_dir, grab_delta);
+ if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) {
+ mul_v3_fl(silhouette_test_dir, -1.0f);
+ }
+ float vno[3];
+ normal_short_to_float_v3(vno, orig_data.no);
+ fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f);
+ }
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
@@ -4752,7 +4774,6 @@ static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totno
}
/* -------------------------------------------------------------------- */
-
/** \name Sculpt Clay Brush
* \{ */
@@ -5277,7 +5298,6 @@ static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
}
/* -------------------------------------------------------------------- */
-
/** \name Sculpt Clay Thumb Brush
* \{ */
@@ -5666,15 +5686,11 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
/* Check for unsupported features. */
PBVHType type = BKE_pbvh_type(ss->pbvh);
if (brush->sculpt_tool == SCULPT_TOOL_PAINT && type != PBVH_FACES) {
- if (!U.experimental.use_sculpt_vertex_colors) {
- return;
- }
+ return;
}
if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && type != PBVH_FACES) {
- if (!U.experimental.use_sculpt_vertex_colors) {
- return;
- }
+ return;
}
/* Build a list of all nodes that are potentially within the brush's area of influence */
@@ -8126,6 +8142,7 @@ static void SCULPT_OT_symmetrize(wmOperatorType *ot)
/**** Toggle operator for turning sculpt mode on or off ****/
+/** \warning Expects a fully evaluated depsgraph. */
static void sculpt_init_session(Depsgraph *depsgraph, Scene *scene, Object *ob)
{
/* Create persistent sculpt mode data. */
@@ -8190,6 +8207,7 @@ void ED_object_sculptmode_enter_ex(Main *bmain,
if (flush_recalc) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
}
/* Create sculpt mode session data. */
@@ -8197,10 +8215,6 @@ void ED_object_sculptmode_enter_ex(Main *bmain,
BKE_sculptsession_free(ob);
}
- /* Make sure derived final from original object does not reference possibly
- * freed memory. */
- BKE_object_free_derived_caches(ob);
-
/* Copy the current mesh visibility to the Face Sets. */
BKE_sculpt_face_sets_ensure_from_base_mesh_visibility(me);
@@ -9231,6 +9245,325 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
1.0f);
}
+/* -------------------------------------------------------------------- */
+/** \name Dyntopo Detail Size Edit Operator
+ * \{ */
+
+/* Defines how much the mouse movement will modify the detail size value. */
+#define DETAIL_SIZE_DELTA_SPEED 0.08f
+#define DETAIL_SIZE_DELTA_ACCURATE_SPEED 0.004f
+
+typedef struct DyntopoDetailSizeEditCustomData {
+ void *draw_handle;
+ Object *active_object;
+
+ float init_mval[2];
+ float accurate_mval[2];
+
+ float outline_col[4];
+
+ bool accurate_mode;
+ bool sample_mode;
+
+ float init_detail_size;
+ float accurate_detail_size;
+ float detail_size;
+ float radius;
+
+ float preview_tri[3][3];
+ float gizmo_mat[4][4];
+} DyntopoDetailSizeEditCustomData;
+
+static void dyntopo_detail_size_parallel_lines_draw(uint pos3d,
+ DyntopoDetailSizeEditCustomData *cd,
+ const float start_co[3],
+ const float end_co[3],
+ bool flip,
+ const float angle)
+{
+ float object_space_constant_detail = 1.0f /
+ (cd->detail_size * mat4_to_scale(cd->active_object->obmat));
+
+ /* The constant detail represents the maximum edge length allowed before subdividing it. If the
+ * triangle grid preview is created with this value it will represent an ideal mesh density where
+ * all edges have the exact maximum length, which never happens in practice. As the minimum edge
+ * length for dyntopo is 0.4 * max_edge_length, this adjust the detail size to the average
+ * between max and min edge length so the preview is more accurate. */
+ object_space_constant_detail *= 0.7f;
+
+ const float total_len = len_v3v3(cd->preview_tri[0], cd->preview_tri[1]);
+ const int tot_lines = (int)(total_len / object_space_constant_detail) + 1;
+ const float tot_lines_fl = total_len / object_space_constant_detail;
+ float spacing_disp[3];
+ sub_v3_v3v3(spacing_disp, end_co, start_co);
+ normalize_v3(spacing_disp);
+
+ float line_disp[3];
+ rotate_v2_v2fl(line_disp, spacing_disp, DEG2RAD(angle));
+ mul_v3_fl(spacing_disp, total_len / tot_lines_fl);
+
+ immBegin(GPU_PRIM_LINES, (uint)tot_lines * 2);
+ for (int i = 0; i < tot_lines; i++) {
+ float line_length;
+ if (flip) {
+ line_length = total_len * ((float)i / (float)tot_lines_fl);
+ }
+ else {
+ line_length = total_len * (1.0f - ((float)i / (float)tot_lines_fl));
+ }
+ float line_start[3];
+ copy_v3_v3(line_start, start_co);
+ madd_v3_v3v3fl(line_start, line_start, spacing_disp, i);
+ float line_end[3];
+ madd_v3_v3v3fl(line_end, line_start, line_disp, line_length);
+ immVertex3fv(pos3d, line_start);
+ immVertex3fv(pos3d, line_end);
+ }
+ immEnd();
+}
+
+static void dyntopo_detail_size_edit_draw(const bContext *UNUSED(C),
+ ARegion *UNUSED(ar),
+ void *arg)
+{
+ DyntopoDetailSizeEditCustomData *cd = arg;
+ GPU_blend(GPU_BLEND_ALPHA);
+ GPU_line_smooth(true);
+
+ uint pos3d = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
+ GPU_matrix_push();
+ GPU_matrix_mul(cd->gizmo_mat);
+
+ /* Draw Cursor */
+ immUniformColor4fv(cd->outline_col);
+ GPU_line_width(3.0f);
+
+ imm_draw_circle_wire_3d(pos3d, 0, 0, cd->radius, 80);
+
+ /* Draw Triangle. */
+ immUniformColor4f(0.9f, 0.9f, 0.9f, 0.8f);
+ immBegin(GPU_PRIM_LINES, 6);
+ immVertex3fv(pos3d, cd->preview_tri[0]);
+ immVertex3fv(pos3d, cd->preview_tri[1]);
+
+ immVertex3fv(pos3d, cd->preview_tri[1]);
+ immVertex3fv(pos3d, cd->preview_tri[2]);
+
+ immVertex3fv(pos3d, cd->preview_tri[2]);
+ immVertex3fv(pos3d, cd->preview_tri[0]);
+ immEnd();
+
+ /* Draw Grid */
+ GPU_line_width(1.0f);
+ dyntopo_detail_size_parallel_lines_draw(
+ pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], false, 60.0f);
+ dyntopo_detail_size_parallel_lines_draw(
+ pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], true, 120.0f);
+ dyntopo_detail_size_parallel_lines_draw(
+ pos3d, cd, cd->preview_tri[0], cd->preview_tri[2], false, -60.0f);
+ GPU_matrix_pop();
+
+ GPU_blend(GPU_BLEND_NONE);
+ GPU_line_smooth(false);
+}
+
+static void dyntopo_detail_size_edit_cancel(bContext *C, wmOperator *op)
+{
+ Object *active_object = CTX_data_active_object(C);
+ SculptSession *ss = active_object->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+ DyntopoDetailSizeEditCustomData *cd = op->customdata;
+ ED_region_draw_cb_exit(ar->type, cd->draw_handle);
+ ss->draw_faded_cursor = false;
+ MEM_freeN(op->customdata);
+ ED_workspace_status_text(C, NULL);
+}
+
+static void dyntopo_detail_size_sample_from_surface(Object *ob,
+ DyntopoDetailSizeEditCustomData *cd)
+{
+ SculptSession *ss = ob->sculpt;
+ const int active_vertex = SCULPT_active_vertex_get(ss);
+
+ float len_accum = 0;
+ int num_neighbors = 0;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) {
+ len_accum += len_v3v3(SCULPT_vertex_co_get(ss, active_vertex),
+ SCULPT_vertex_co_get(ss, ni.index));
+ num_neighbors++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ if (num_neighbors > 0) {
+ const float avg_edge_len = len_accum / num_neighbors;
+ /* Use 0.7 as the average of min and max dyntopo edge length. */
+ const float detail_size = 0.7f / (avg_edge_len * mat4_to_scale(cd->active_object->obmat));
+ cd->detail_size = clamp_f(detail_size, 1.0f, 500.0f);
+ }
+}
+
+static void dyntopo_detail_size_update_from_mouse_delta(DyntopoDetailSizeEditCustomData *cd,
+ const wmEvent *event)
+{
+ const float mval[2] = {event->mval[0], event->mval[1]};
+
+ float detail_size_delta;
+ if (cd->accurate_mode) {
+ detail_size_delta = mval[0] - cd->accurate_mval[0];
+ cd->detail_size = cd->accurate_detail_size +
+ detail_size_delta * DETAIL_SIZE_DELTA_ACCURATE_SPEED;
+ }
+ else {
+ detail_size_delta = mval[0] - cd->init_mval[0];
+ cd->detail_size = cd->init_detail_size + detail_size_delta * DETAIL_SIZE_DELTA_SPEED;
+ }
+
+ if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_PRESS) {
+ cd->accurate_mode = true;
+ copy_v2_v2(cd->accurate_mval, mval);
+ cd->accurate_detail_size = cd->detail_size;
+ }
+ if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_RELEASE) {
+ cd->accurate_mode = false;
+ cd->accurate_detail_size = 0.0f;
+ }
+
+ cd->detail_size = clamp_f(cd->detail_size, 1.0f, 500.0f);
+}
+
+static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *active_object = CTX_data_active_object(C);
+ SculptSession *ss = active_object->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+ DyntopoDetailSizeEditCustomData *cd = op->customdata;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ /* Cancel modal operator */
+ if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) ||
+ (event->type == RIGHTMOUSE && event->val == KM_PRESS)) {
+ dyntopo_detail_size_edit_cancel(C, op);
+ ED_region_tag_redraw(ar);
+ return OPERATOR_FINISHED;
+ }
+
+ /* Finish modal operator */
+ if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) ||
+ (event->type == EVT_RETKEY && event->val == KM_PRESS) ||
+ (event->type == EVT_PADENTER && event->val == KM_PRESS)) {
+ ED_region_draw_cb_exit(ar->type, cd->draw_handle);
+ sd->constant_detail = cd->detail_size;
+ ss->draw_faded_cursor = false;
+ MEM_freeN(op->customdata);
+ ED_region_tag_redraw(ar);
+ ED_workspace_status_text(C, NULL);
+ return OPERATOR_FINISHED;
+ }
+
+ ED_region_tag_redraw(ar);
+
+ if (event->type == EVT_LEFTCTRLKEY && event->val == KM_PRESS) {
+ cd->sample_mode = true;
+ }
+ if (event->type == EVT_LEFTCTRLKEY && event->val == KM_RELEASE) {
+ cd->sample_mode = false;
+ }
+
+ /* Sample mode sets the detail size sampling the average edge length under the surface. */
+ if (cd->sample_mode) {
+ dyntopo_detail_size_sample_from_surface(active_object, cd);
+ return OPERATOR_RUNNING_MODAL;
+ }
+ /* Regular mode, changes the detail size by moving the cursor. */
+ dyntopo_detail_size_update_from_mouse_delta(cd, event);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int dyntopo_detail_size_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ ARegion *ar = CTX_wm_region(C);
+ Object *active_object = CTX_data_active_object(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ DyntopoDetailSizeEditCustomData *cd = MEM_callocN(sizeof(DyntopoDetailSizeEditCustomData),
+ "Dyntopo Detail Size Edit OP Custom Data");
+
+ /* Initial operator Custom Data setup. */
+ cd->draw_handle = ED_region_draw_cb_activate(
+ ar->type, dyntopo_detail_size_edit_draw, cd, REGION_DRAW_POST_VIEW);
+ cd->active_object = active_object;
+ cd->init_mval[0] = event->mval[0];
+ cd->init_mval[1] = event->mval[1];
+ cd->detail_size = sd->constant_detail;
+ cd->init_detail_size = sd->constant_detail;
+ copy_v4_v4(cd->outline_col, brush->add_col);
+ op->customdata = cd;
+
+ SculptSession *ss = active_object->sculpt;
+ cd->radius = ss->cursor_radius;
+
+ /* Generates the matrix to position the gizmo in the surface of the mesh using the same location
+ * and orientation as the brush cursor. */
+ float cursor_trans[4][4], cursor_rot[4][4];
+ const float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f};
+ float quat[4];
+ copy_m4_m4(cursor_trans, active_object->obmat);
+ translate_m4(
+ cursor_trans, ss->cursor_location[0], ss->cursor_location[1], ss->cursor_location[2]);
+ rotation_between_vecs_to_quat(quat, z_axis, ss->cursor_normal);
+ quat_to_mat4(cursor_rot, quat);
+ copy_m4_m4(cd->gizmo_mat, cursor_trans);
+ mul_m4_m4_post(cd->gizmo_mat, cursor_rot);
+
+ /* Initize the position of the triangle vertices. */
+ const float y_axis[3] = {0.0f, cd->radius, 0.0f};
+ for (int i = 0; i < 3; i++) {
+ zero_v3(cd->preview_tri[i]);
+ rotate_v2_v2fl(cd->preview_tri[i], y_axis, DEG2RAD(120.0f * i));
+ }
+
+ SCULPT_vertex_random_access_ensure(ss);
+
+ WM_event_add_modal_handler(C, op);
+ ED_region_tag_redraw(ar);
+
+ ss->draw_faded_cursor = true;
+
+ const char *status_str = TIP_(
+ "Move the mouse to change the dyntopo detail size. LMB: confirm size, ESC/RMB: cancel");
+ ED_workspace_status_text(C, status_str);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static bool dyntopo_detail_size_edit_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ return SCULPT_mode_poll(C) && ob->sculpt->bm && (sd->flags & SCULPT_DYNTOPO_DETAIL_CONSTANT);
+}
+
+static void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Edit Dyntopo Detail Size";
+ ot->description = "Modify the constant detail size of dyntopo interactively";
+ ot->idname = "SCULPT_OT_dyntopo_detail_size_edit";
+
+ /* api callbacks */
+ ot->poll = dyntopo_detail_size_edit_poll;
+ ot->invoke = dyntopo_detail_size_edit_invoke;
+ ot->modal = dyntopo_detail_size_edit_modal;
+ ot->cancel = dyntopo_detail_size_edit_cancel;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@@ -9264,4 +9597,5 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors);
WM_operatortype_append(SCULPT_OT_color_filter);
WM_operatortype_append(SCULPT_OT_mask_by_color);
+ WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c
index 67129d5da50..20b164fa80c 100644
--- a/source/blender/editors/sculpt_paint/sculpt_cloth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c
@@ -139,14 +139,14 @@ 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;
}
@@ -214,7 +214,7 @@ 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);
}
@@ -326,12 +326,15 @@ 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 :
@@ -380,12 +383,21 @@ 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, node_index, vd.index, fade * CLOTH_DEFORMATION_GRAB_STRENGTH);
+ 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
@@ -436,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;
@@ -448,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) {
@@ -470,12 +481,6 @@ 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];
@@ -486,8 +491,7 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
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);
@@ -504,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 *
@@ -539,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:
@@ -920,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);
@@ -930,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);
@@ -944,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]);
@@ -965,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++) {
@@ -1058,7 +1068,7 @@ void SCULPT_cloth_brush_ensure_nodes_constraints(
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)
@@ -1178,7 +1188,7 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
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 alignement. */
+ * for brush tip alignment. */
return;
}
@@ -1253,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;
@@ -1299,7 +1314,7 @@ static EnumPropertyItem prop_cloth_filter_type[] = {
"SCALE",
0,
"Scale",
- "Scales the mesh as a softbody using the origin of the object as scale"},
+ "Scales the mesh as a soft-body using the origin of the object as scale"},
{0, NULL, 0, NULL, NULL},
};
diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.c b/source/blender/editors/sculpt_paint/sculpt_face_set.c
index af6a06caf69..3c87407b2db 100644
--- a/source/blender/editors/sculpt_paint/sculpt_face_set.c
+++ b/source/blender/editors/sculpt_paint/sculpt_face_set.c
@@ -1018,6 +1018,7 @@ void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot)
typedef enum eSculptFaceSetEditMode {
SCULPT_FACE_SET_EDIT_GROW = 0,
SCULPT_FACE_SET_EDIT_SHRINK = 1,
+ SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2,
} eSculptFaceSetEditMode;
static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = {
@@ -1035,6 +1036,13 @@ static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = {
"Shrink Face Set",
"Shrinks the Face Sets boundary by one face based on mesh topology",
},
+ {
+ SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY,
+ "DELETE_GEOMETRY",
+ 0,
+ "Delete Geometry",
+ "Deletes the faces that are assigned to the Face Set",
+ },
{0, NULL, 0, NULL, NULL},
};
@@ -1096,6 +1104,78 @@ static void sculpt_face_set_shrink(Object *ob,
}
}
+static bool check_single_face_set(SculptSession *ss, int *face_sets, const bool check_visible_only)
+{
+
+ int first_face_set = SCULPT_FACE_SET_NONE;
+ if (check_visible_only) {
+ for (int f = 0; f < ss->totfaces; f++) {
+ if (face_sets[f] > 0) {
+ first_face_set = face_sets[f];
+ break;
+ }
+ }
+ }
+ else {
+ first_face_set = abs(face_sets[0]);
+ }
+
+ if (first_face_set == SCULPT_FACE_SET_NONE) {
+ return true;
+ }
+
+ for (int f = 0; f < ss->totfaces; f++) {
+ const int face_set_id = check_visible_only ? face_sets[f] : abs(face_sets[f]);
+ if (face_set_id != first_face_set) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void sculpt_face_set_delete_geometry(Object *ob,
+ SculptSession *ss,
+ const int active_face_set_id,
+ const bool modify_hidden)
+{
+
+ Mesh *mesh = ob->data;
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
+ BMesh *bm = BM_mesh_create(&allocsize,
+ &((struct BMeshCreateParams){
+ .use_toolflags = true,
+ }));
+
+ BM_mesh_bm_from_me(bm,
+ mesh,
+ (&(struct BMeshFromMeshParams){
+ .calc_face_normal = true,
+ }));
+
+ BM_mesh_elem_table_init(bm, BM_FACE);
+ BM_mesh_elem_table_ensure(bm, BM_FACE);
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ BMIter iter;
+ BMFace *f;
+ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
+ const int face_index = BM_elem_index_get(f);
+ const int face_set_id = modify_hidden ? abs(ss->face_sets[face_index]) :
+ ss->face_sets[face_index];
+ BM_elem_flag_set(f, BM_ELEM_TAG, face_set_id == active_face_set_id);
+ }
+ BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES);
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+
+ BM_mesh_bm_to_me(NULL,
+ bm,
+ ob->data,
+ (&(struct BMeshToMeshParams){
+ .calc_object_remap = false,
+ }));
+
+ BM_mesh_free(bm);
+}
+
static void sculpt_face_set_apply_edit(Object *ob,
const int active_face_set_id,
const int mode,
@@ -1103,80 +1183,135 @@ static void sculpt_face_set_apply_edit(Object *ob,
{
SculptSession *ss = ob->sculpt;
- int *prev_face_sets = MEM_dupallocN(ss->face_sets);
-
switch (mode) {
- case SCULPT_FACE_SET_EDIT_GROW:
+ case SCULPT_FACE_SET_EDIT_GROW: {
+ int *prev_face_sets = MEM_dupallocN(ss->face_sets);
sculpt_face_set_grow(ob, ss, prev_face_sets, active_face_set_id, modify_hidden);
+ MEM_SAFE_FREE(prev_face_sets);
break;
- case SCULPT_FACE_SET_EDIT_SHRINK:
+ }
+ case SCULPT_FACE_SET_EDIT_SHRINK: {
+ int *prev_face_sets = MEM_dupallocN(ss->face_sets);
sculpt_face_set_shrink(ob, ss, prev_face_sets, active_face_set_id, modify_hidden);
+ MEM_SAFE_FREE(prev_face_sets);
+ break;
+ }
+ case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
+ sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden);
break;
}
-
- MEM_SAFE_FREE(prev_face_sets);
}
-static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss,
+ const eSculptFaceSetEditMode mode,
+ const bool modify_hidden)
{
- Object *ob = CTX_data_active_object(C);
- SculptSession *ss = ob->sculpt;
- Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
-
- const int mode = RNA_enum_get(op->ptr, "mode");
-
- /* Dyntopo not supported. */
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
+ /* Dyntopo is not supported. */
return OPERATOR_CANCELLED;
}
- /* Ignore other events to avoid repeated operations. */
- if (event->val != KM_PRESS) {
- return OPERATOR_CANCELLED;
+ if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) {
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
+ /* Modification of base mesh geometry requires special remapping of multires displacement,
+ * which does not happen here.
+ * Disable delete operation. It can be supported in the future by doing similar displacement
+ * data remapping as what happens in the mesh edit mode. */
+ return false;
+ }
+ if (check_single_face_set(ss, ss->face_sets, !modify_hidden)) {
+ /* Cancel the operator if the mesh only contains one Face Set to avoid deleting the
+ * entire object. */
+ return false;
+ }
}
+ return true;
+}
- BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
+static void sculpt_face_set_edit_modify_geometry(bContext *C,
+ Object *ob,
+ const int active_face_set,
+ const eSculptFaceSetEditMode mode,
+ const bool modify_hidden)
+{
+ ED_sculpt_undo_geometry_begin(ob, "edit face set delete geometry");
+ sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden);
+ ED_sculpt_undo_geometry_end(ob);
+ BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
+}
- /* Update the current active Face Set and Vertex as the operator can be used directly from the
- * tool without brush cursor. */
- SculptCursorGeometryInfo sgi;
- float mouse[2];
- mouse[0] = event->mval[0];
- mouse[1] = event->mval[1];
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ PBVH *pbvh = ss->pbvh;
+
+ /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */
+ SCULPT_visibility_sync_all_face_sets_to_vertices(ob);
+
+ for (int i = 0; i < totnode; i++) {
+ BKE_pbvh_node_mark_update_visibility(nodes[i]);
+ }
+
+ BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
+
+ if (BKE_pbvh_type(pbvh) == PBVH_FACES) {
+ BKE_mesh_flush_hidden_from_verts(ob->data);
+ }
+}
+static void sculpt_face_set_edit_modify_face_sets(Object *ob,
+ const int active_face_set,
+ const eSculptFaceSetEditMode mode,
+ const bool modify_hidden)
+{
PBVH *pbvh = ob->sculpt->pbvh;
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
if (!nodes) {
- return OPERATOR_CANCELLED;
+ return;
}
-
SCULPT_undo_push_begin("face set edit");
SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
-
- const int active_face_set = SCULPT_active_face_set_get(ss);
- const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden");
-
sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden);
-
SCULPT_undo_push_end();
+ face_set_edit_do_post_visibility_updates(ob, nodes, totnode);
+ MEM_freeN(nodes);
+}
- /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */
- SCULPT_visibility_sync_all_face_sets_to_vertices(ob);
+static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
- for (int i = 0; i < totnode; i++) {
- BKE_pbvh_node_mark_update_visibility(nodes[i]);
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden");
+
+ if (!sculpt_face_set_edit_is_operation_valid(ss, mode, modify_hidden)) {
+ return OPERATOR_CANCELLED;
}
- BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
- MEM_SAFE_FREE(nodes);
+ /* Update the current active Face Set and Vertex as the operator can be used directly from the
+ * tool without brush cursor. */
+ SculptCursorGeometryInfo sgi;
+ const float mouse[2] = {event->mval[0], event->mval[1]};
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ const int active_face_set = SCULPT_active_face_set_get(ss);
- if (BKE_pbvh_type(pbvh) == PBVH_FACES) {
- BKE_mesh_flush_hidden_from_verts(ob->data);
+ switch (mode) {
+ case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
+ sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden);
+ break;
+ case SCULPT_FACE_SET_EDIT_GROW:
+ case SCULPT_FACE_SET_EDIT_SHRINK:
+ sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden);
+ break;
}
SCULPT_tag_update_overlays(C);
diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c
index f1410ffad1b..f4f30c903aa 100644
--- a/source/blender/editors/sculpt_paint/sculpt_undo.c
+++ b/source/blender/editors/sculpt_paint/sculpt_undo.c
@@ -1477,8 +1477,6 @@ static void sculpt_undosys_step_decode(
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
- /* Sculpt needs evaluated state. */
- BKE_scene_view_layer_graph_evaluated_ensure(bmain, scene, view_layer);
Object *ob = OBACT(view_layer);
if (ob && (ob->type == OB_MESH)) {
if (ob->mode & OB_MODE_SCULPT) {
@@ -1486,6 +1484,12 @@ static void sculpt_undosys_step_decode(
}
else {
ED_object_mode_generic_exit(bmain, depsgraph, scene, ob);
+
+ /* Sculpt needs evaluated state.
+ * Note: needs to be done here, as #ED_object_mode_generic_exit will usually invalidate
+ * (some) evaluated data. */
+ BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
+
Mesh *me = ob->data;
/* Don't add sculpt topology undo steps when reading back undo state.
* The undo steps must enter/exit for us. */