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
path: root/source
diff options
context:
space:
mode:
authorPablo Dobarro <pablodp606@gmail.com>2019-11-06 21:39:34 +0300
committerPablo Dobarro <pablodp606@gmail.com>2019-11-21 20:16:42 +0300
commitc3279be222bdca1981a8c1a8582b19a7e693009b (patch)
tree6a828ca6e1f83f2e72940d1549b83642fe1decad /source
parentc23dbcf37d9ffed4d443e0f9ce36f14854a52009 (diff)
Sculpt: Multiplane Scrape Brush
The Multiplane Scrape brush creates sharp edges with a given fixed angle by trimming the mesh with two planes in local space at the same time. When working with stylized or hard surface models, this brush produces way better results and is more predictable than any other crease/flatten brush based on curves and alphas. It is also the first brush we have than can produce hard surface concave creases. The Multiplane Scrape Brush also has a dynamic mode where it samples the surface to fit the angle and scrape planes during a stroke. With this mode enabled you can sculpt multiple times over the same edge without creating artifacts. It can also create creases that change between concave and convex during the same stroke. The behavior of this brush will improve after merging patches like D5993 and its behavior in concave creases can still be improved, so I will keep tweaking its parameters and default values once we have all brush properties available. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D6174
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/intern/brush.c9
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c8
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c82
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c348
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h6
-rw-r--r--source/blender/makesdna/DNA_brush_types.h17
-rw-r--r--source/blender/makesrna/intern/rna_brush.c22
7 files changed, 489 insertions, 3 deletions
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index eebed72103f..794876ec444 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -938,6 +938,14 @@ void BKE_brush_sculpt_reset(Brush *br)
br->curve_preset = BRUSH_CURVE_SPHERE;
br->spacing = 6;
break;
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ br->flag2 |= BRUSH_MULTIPLANE_SCRAPE_DYNAMIC | BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW;
+ br->alpha = 0.7f;
+ br->normal_radius_factor = 0.70f;
+ br->multiplane_scrape_angle = 60;
+ br->curve_preset = BRUSH_CURVE_SMOOTH;
+ br->spacing = 5;
+ break;
case SCULPT_TOOL_CREASE:
br->flag |= BRUSH_DIR_IN;
br->alpha = 0.25;
@@ -1010,6 +1018,7 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_FLATTEN:
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
br->add_col[0] = 1.0f;
br->add_col[1] = 0.39f;
br->add_col[2] = 0.39f;
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index 05758b446ad..45ec6eef813 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -509,6 +509,14 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
brush->sculpt_tool = SCULPT_TOOL_POSE;
}
+ brush_name = "Multiplane Scrape";
+ brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
+ if (!brush) {
+ brush = BKE_brush_add(bmain, brush_name, OB_MODE_SCULPT);
+ id_us_min(&brush->id);
+ brush->sculpt_tool = SCULPT_TOOL_MULTIPLANE_SCRAPE;
+ }
+
brush_name = "Simplify";
brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
if (!brush) {
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index b9af33e7ad6..ac738f326a3 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1214,6 +1214,70 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession
}
}
+static void sculpt_multiplane_scrape_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ float *outline_col,
+ float outline_alpha)
+{
+ float local_mat_inv[4][4];
+ invert_m4_m4(local_mat_inv, ss->cache->stroke_local_mat);
+ GPU_matrix_mul(local_mat_inv);
+ float angle = ss->cache->multiplane_scrape_sampled_angle;
+ if (ss->cache->pen_flip || ss->cache->invert) {
+ angle = -angle;
+ }
+
+ float offset = ss->cache->radius * 0.25f;
+
+ float p[3] = {0.0f, 0.0f, ss->cache->radius};
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float p_l[3];
+ float p_r[3];
+ float area_center[3] = {0.0f, 0.0f, 0.0f};
+ rotate_v3_v3v3fl(p_r, p, y_axis, DEG2RADF((angle + 180) * 0.5f));
+ rotate_v3_v3v3fl(p_l, p, y_axis, DEG2RADF(-(angle + 180) * 0.5f));
+
+ immBegin(GPU_PRIM_LINES, 14);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immEnd();
+
+ immUniformColor3fvAlpha(outline_col, outline_alpha * 0.1f);
+ immBegin(GPU_PRIM_TRIS, 12);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immEnd();
+}
+
static bool paint_use_2d_cursor(ePaintMode mode)
{
if (mode >= PAINT_MODE_TEXTURE_3D) {
@@ -1516,6 +1580,24 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
}
}
+ if (brush->sculpt_tool == SCULPT_TOOL_MULTIPLANE_SCRAPE &&
+ brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW && !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);
+ GPU_matrix_push();
+ GPU_matrix_mul(vc.obact->obmat);
+ sculpt_multiplane_scrape_preview_draw(pos, ss, outline_col, outline_alpha);
+ GPU_matrix_pop();
+ GPU_matrix_pop_projection();
+ }
+
wmWindowViewport(win);
}
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index fcc2a7a08b3..6cda6ccac06 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1771,6 +1771,10 @@ static float brush_strength(const Sculpt *sd,
return 0.125f * alpha * flip * pressure * overlap * feather;
}
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ overlap = (1.0f + overlap) / 2.0f;
+ return alpha * flip * pressure * overlap * feather;
+
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
case SCULPT_TOOL_FLATTEN:
@@ -4843,6 +4847,341 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
BKE_pbvh_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings);
}
+/* -------------------------------------------------------------------- */
+
+/** \name Sculpt Multiplane Scrape Brush
+ * \{ */
+
+typedef struct MultiplaneScrapeSampleData {
+ float area_cos[2][3];
+ float area_nos[2][3];
+ int area_count[2];
+} MultiplaneScrapeSampleData;
+
+static void calc_multiplane_scrape_surface_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+ MultiplaneScrapeSampleData *mssd = tls->userdata_chunk;
+ float(*mat)[4] = data->mat;
+
+ PBVHVertexIter vd;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+ ss, &test, brush->falloff_shape);
+
+ /* Apply the brush normal radius to the test before sampling */
+ float test_radius = sqrtf(test.radius_squared);
+ test_radius *= brush->normal_radius_factor;
+ test.radius_squared = test_radius * test_radius;
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ float local_co[3];
+ float normal[3];
+ if (vd.no) {
+ normal_short_to_float_v3(normal, vd.no);
+ }
+ else {
+ copy_v3_v3(normal, vd.fno);
+ }
+ mul_v3_m4v3(local_co, mat, vd.co);
+ /* Use the brush falloff to weight the sampled normals */
+ const float fade = tex_strength(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ /* Sample the normal and area of the +X and -X axis individually */
+ if (local_co[0] > 0.0f) {
+ madd_v3_v3fl(mssd->area_nos[0], normal, fade);
+ add_v3_v3(mssd->area_cos[0], vd.co);
+ mssd->area_count[0]++;
+ }
+ else {
+ madd_v3_v3fl(mssd->area_nos[1], normal, fade);
+ add_v3_v3(mssd->area_cos[1], vd.co);
+ mssd->area_count[1]++;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+static void calc_multiplane_scrape_surface_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ MultiplaneScrapeSampleData *join = chunk_join;
+ MultiplaneScrapeSampleData *mssd = chunk;
+
+ add_v3_v3(join->area_cos[0], mssd->area_cos[0]);
+ add_v3_v3(join->area_cos[1], mssd->area_cos[1]);
+
+ add_v3_v3(join->area_nos[0], mssd->area_nos[0]);
+ add_v3_v3(join->area_nos[1], mssd->area_nos[1]);
+
+ join->area_count[0] += mssd->area_count[0];
+ join->area_count[1] += mssd->area_count[1];
+}
+
+static void do_multiplane_scrape_brush_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;
+ float(*mat)[4] = data->mat;
+ float(*scrape_planes)[4] = data->multiplane_scrape_planes;
+
+ float angle = data->multiplane_scrape_angle;
+
+ PBVHVertexIter vd;
+ float(*proxy)[3];
+ const float bstrength = fabsf(ss->cache->bstrength);
+
+ proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ float local_co[3];
+ bool deform = false;
+
+ mul_v3_m4v3(local_co, mat, vd.co);
+
+ if (local_co[0] > 0.0f) {
+ deform = !plane_point_side(vd.co, scrape_planes[0]);
+ }
+ else {
+ deform = !plane_point_side(vd.co, scrape_planes[1]);
+ }
+
+ if (angle < 0.0f) {
+ deform = true;
+ }
+
+ if (deform) {
+ float intr[3];
+ float val[3];
+
+ if (local_co[0] > 0.0f) {
+ closest_to_plane_normalized_v3(intr, scrape_planes[0], vd.co);
+ }
+ else {
+ closest_to_plane_normalized_v3(intr, scrape_planes[1], vd.co);
+ }
+
+ sub_v3_v3v3(val, intr, vd.co);
+ if (plane_trim(ss->cache, brush, val)) {
+ /* Deform the local space along the Y axis to avoid artifacts on curved strokes */
+ /* This produces a not round brush tip */
+ local_co[1] *= 2.0f;
+ const float fade = bstrength * tex_strength(ss,
+ brush,
+ vd.co,
+ len_v3(local_co),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ mul_v3_v3fl(proxy[vd.i], val, fade);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ }
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float radius = flip ? -ss->cache->radius : ss->cache->radius;
+ const float offset = get_offset(sd, ss);
+ const float displace = -radius * offset;
+
+ float area_no_sp[3]; /* the sculpt-plane normal (whatever its set to) */
+ float area_no[3]; /* geometry normal */
+ float area_co[3];
+
+ float temp[3];
+ float mat[4][4];
+
+ calc_sculpt_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);
+ }
+ else {
+ copy_v3_v3(area_no, area_no_sp);
+ }
+
+ /* delay the first daub because grab delta is not setup */
+ if (ss->cache->first_time) {
+ return;
+ }
+
+ if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
+ return;
+ }
+
+ mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
+ mul_v3_fl(temp, displace);
+ add_v3_v3(area_co, temp);
+
+ /* Init brush local space matrix */
+ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
+ mat[0][3] = 0;
+ cross_v3_v3v3(mat[1], area_no, mat[0]);
+ mat[1][3] = 0;
+ copy_v3_v3(mat[2], area_no);
+ mat[2][3] = 0;
+ copy_v3_v3(mat[3], ss->cache->location);
+ mat[3][3] = 1;
+ normalize_m4(mat);
+ invert_m4(mat);
+
+ float angle = brush->multiplane_scrape_angle;
+
+ /* Update matrix for the cursor preview */
+ if (ss->cache->mirror_symmetry_pass == 0) {
+ copy_m4_m4(ss->cache->stroke_local_mat, mat);
+ }
+
+ /* Dynamic mode */
+
+ if (brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_DYNAMIC) {
+ /* Sample the individual normal and area center of the two areas at both sides of the cursor */
+ SculptThreadedTaskData sample_data = {
+ .sd = NULL,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .totnode = totnode,
+ .mat = mat,
+ };
+
+ MultiplaneScrapeSampleData mssd = {{{0}}};
+
+ PBVHParallelSettings sample_settings;
+ BKE_pbvh_parallel_range_settings(&sample_settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ sample_settings.func_reduce = calc_multiplane_scrape_surface_reduce;
+ sample_settings.userdata_chunk = &mssd;
+ sample_settings.userdata_chunk_size = sizeof(MultiplaneScrapeSampleData);
+
+ BKE_pbvh_parallel_range(
+ 0, totnode, &sample_data, calc_multiplane_scrape_surface_task_cb, &sample_settings);
+
+ float sampled_plane_normals[2][3];
+ float sampled_plane_co[2][3];
+ float sampled_cv[2][3];
+ float mid_co[3];
+
+ /* Use the area center of both planes to detect if we are sculpting along a concave or convex
+ * edge */
+ mul_v3_v3fl(sampled_plane_co[0], mssd.area_cos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_co[1], mssd.area_cos[1], 1.0f / (float)mssd.area_count[1]);
+ mid_v3_v3v3(mid_co, sampled_plane_co[0], sampled_plane_co[1]);
+
+ /* Calculate the scrape planes angle based on the sampled normals */
+ mul_v3_v3fl(sampled_plane_normals[0], mssd.area_nos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_normals[1], mssd.area_nos[1], 1.0f / (float)mssd.area_count[1]);
+ normalize_v3(sampled_plane_normals[0]);
+ normalize_v3(sampled_plane_normals[1]);
+
+ float sampled_angle = angle_v3v3(sampled_plane_normals[0], sampled_plane_normals[1]);
+ copy_v3_v3(sampled_cv[0], area_no);
+ sub_v3_v3v3(sampled_cv[1], ss->cache->location, mid_co);
+
+ sampled_angle += DEG2RADF(brush->multiplane_scrape_angle) * ss->cache->pressure;
+
+ /* Invert the angle if we are sculpting along a concave edge */
+ if (dot_v3v3(sampled_cv[0], sampled_cv[1]) < 0.0f) {
+ sampled_angle = -sampled_angle;
+ }
+
+ /* In dynamic mode, set the angle to 0 when inverting the brush, so you can trim plane surfaces
+ * without changing the brush */
+ if (flip) {
+ sampled_angle = 0.0f;
+ }
+ else {
+ copy_v3_v3(area_co, ss->cache->location);
+ }
+
+ angle = RAD2DEGF(sampled_angle);
+ }
+ else {
+
+ /* Standard mode: Scrape with the brush property fixed angle */
+ copy_v3_v3(area_co, ss->cache->location);
+ if (flip) {
+ angle = -angle;
+ }
+ }
+
+ /* Set the angle for the cursor preview */
+ ss->cache->multiplane_scrape_sampled_angle = angle;
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .mat = mat,
+ .multiplane_scrape_angle = angle,
+ };
+
+ /* Calculate the final left and right scrape planes */
+ float plane_no[3];
+ float plane_no_rot[3];
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float mat_inv[4][4];
+ invert_m4_m4(mat_inv, mat);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(-angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[1], area_co, plane_no);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[0], area_co, plane_no);
+
+ PBVHParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ BKE_pbvh_parallel_range(0, totnode, &data, do_multiplane_scrape_brush_task_cb_ex, &settings);
+}
+
/** \} */
static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
@@ -5468,6 +5807,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_CLAY_STRIPS:
do_clay_strips_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ do_multiplane_scrape_brush(sd, ob, nodes, totnode);
+ break;
case SCULPT_TOOL_FILL:
do_fill_brush(sd, ob, nodes, totnode);
break;
@@ -5997,6 +6339,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Elastic Deform Brush";
case SCULPT_TOOL_POSE:
return "Pose Brush";
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ return "Multiplane Scrape Brush";
}
return "Sculpting";
@@ -6260,6 +6604,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_CLAY_STRIPS,
+ SCULPT_TOOL_MULTIPLANE_SCRAPE,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_POSE,
SCULPT_TOOL_THUMB) ||
@@ -6295,6 +6640,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
add_v3_v3(cache->grab_delta, delta);
break;
case SCULPT_TOOL_CLAY_STRIPS:
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
case SCULPT_TOOL_NUDGE:
case SCULPT_TOOL_SNAKE_HOOK:
if (brush->flag & BRUSH_ANCHORED) {
@@ -7759,7 +8105,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *UNUSED(op))
break;
case PBVH_FACES:
/* Mesh Symmetrize */
- ED_sculpt_undo_geometry_begin(ob);
+ ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize");
Mesh *mesh = ob->data;
Mesh *mesh_mirror;
MirrorModifierData mmd = {0};
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 93e4a777569..0b25ab31ce0 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -206,6 +206,9 @@ typedef struct SculptThreadedTaskData {
float *pose_factor;
float (*transform_rot)[4], (*transform_trans)[4], (*transform_trans_inv)[4];
+ float multiplane_scrape_angle;
+ float multiplane_scrape_planes[2][4];
+
float max_distance_squared;
float nearest_vertex_search_co[3];
@@ -392,6 +395,9 @@ typedef struct StrokeCache {
float *automask;
+ float stroke_local_mat[4][4];
+ float multiplane_scrape_sampled_angle;
+
rcti previous_r; /* previous redraw rectangle */
rcti current_r; /* current redraw rectangle */
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 67bca60e7e1..c55ab81a733 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -247,7 +247,9 @@ typedef struct Brush {
int size;
/** General purpose flags. */
int flag;
+ int flag2;
int sampling_flag;
+
/** Pressure influence for mask. */
int mask_pressure;
/** Jitter the position of the brush. */
@@ -288,7 +290,8 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
- char _pad[5];
+ char _pad0;
+
/** Projection shape (sphere, circle). */
char falloff_shape;
float falloff_angle;
@@ -307,7 +310,7 @@ typedef struct Brush {
char mask_tool;
/** Active grease pencil tool. */
char gpencil_tool;
- char _pad0[1];
+ char _pad1[5];
float autosmooth_factor;
@@ -332,6 +335,9 @@ typedef struct Brush {
/* pose */
float pose_offset;
+ /* multiplane scrape */
+ float multiplane_scrape_angle;
+
/* overlay */
int texture_overlay_alpha;
int mask_overlay_alpha;
@@ -445,6 +451,12 @@ typedef enum eBrushSamplingFlags {
BRUSH_PAINT_ANTIALIASING = (1 << 0),
} eBrushSamplingFlags;
+/* Brush.flag2 */
+typedef enum eBrushFlags2 {
+ BRUSH_MULTIPLANE_SCRAPE_DYNAMIC = (1 << 0),
+ BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW = (1 << 1),
+} eBrushFlags2;
+
typedef enum {
BRUSH_MASK_PRESSURE_RAMP = (1 << 1),
BRUSH_MASK_PRESSURE_CUTOFF = (1 << 2),
@@ -488,6 +500,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_DRAW_SHARP = 20,
SCULPT_TOOL_ELASTIC_DEFORM = 21,
SCULPT_TOOL_POSE = 22,
+ SCULPT_TOOL_MULTIPLANE_SCRAPE = 23,
} eBrushSculptTool;
/* Brush.uv_sculpt_tool */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 3f269299461..86b1ed92349 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -68,6 +68,7 @@ static const EnumPropertyItem sculpt_stroke_method_items[] = {
{0, NULL, 0, NULL, NULL},
};
+/* clang-format off */
const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_DRAW, "DRAW", ICON_BRUSH_SCULPT_DRAW, "Draw", ""},
{SCULPT_TOOL_DRAW_SHARP, "DRAW_SHARP", ICON_BRUSH_SCULPT_DRAW, "Draw Sharp", ""},
@@ -82,6 +83,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_FLATTEN, "FLATTEN", ICON_BRUSH_FLATTEN, "Flatten", ""},
{SCULPT_TOOL_FILL, "FILL", ICON_BRUSH_FILL, "Fill", ""},
{SCULPT_TOOL_SCRAPE, "SCRAPE", ICON_BRUSH_SCRAPE, "Scrape", ""},
+ {SCULPT_TOOL_MULTIPLANE_SCRAPE, "MULTIPLANE_SCRAPE", ICON_BRUSH_SCRAPE, "Multiplane Scrape", ""},
{SCULPT_TOOL_PINCH, "PINCH", ICON_BRUSH_PINCH, "Pinch", ""},
{0, "", 0, NULL, NULL},
{SCULPT_TOOL_GRAB, "GRAB", ICON_BRUSH_GRAB, "Grab", ""},
@@ -96,6 +98,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
{0, NULL, 0, NULL, NULL},
};
+/* clang-format on */
const EnumPropertyItem rna_enum_brush_uv_sculpt_tool_items[] = {
{UV_SCULPT_TOOL_GRAB, "GRAB", 0, "Grab", "Grab UVs"},
@@ -1878,6 +1881,12 @@ static void rna_def_brush(BlenderRNA *brna)
prop, "Pose Origin Offset", "Offset of the pose origin in relation to the brush radius");
RNA_def_property_update(prop, 0, "rna_Brush_update");
+ prop = RNA_def_property(srna, "multiplane_scrape_angle", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "multiplane_scrape_angle");
+ RNA_def_property_range(prop, 0.0f, 160.0f);
+ RNA_def_property_ui_text(prop, "Plane Angle", "Angle between the planes of the crease");
+ 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);
@@ -2026,6 +2035,19 @@ static void rna_def_brush(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_paint_antialiasing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "sampling_flag", BRUSH_PAINT_ANTIALIASING);
RNA_def_property_ui_text(prop, "Antialasing", "Smooths the edges of the strokes");
+
+ prop = RNA_def_property(srna, "use_multiplane_scrape_dynamic", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_MULTIPLANE_SCRAPE_DYNAMIC);
+ RNA_def_property_ui_text(prop,
+ "Dynamic Mode",
+ "The angle between the planes changes during the stroke to fit the "
+ "surface under the cursor");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+ prop = RNA_def_property(srna, "show_multiplane_scrape_planes_preview", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW);
+ RNA_def_property_ui_text(
+ prop, "Show Cursor Preview", "Preview the scrape planes in the cursor during the stroke");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_pressure_strength", PROP_BOOLEAN, PROP_NONE);