Sculpt/Paint: Brush curve presets

This patch introduces the same presets that are used for proportional editing in the brush falloff menu. The user can select any of these presets or use the regular custom falloff curve. The presets are hardcoded formulas, so the falloff curve is not used when they are active.

This change improves the general feeling of the brushes and it is more convenient and simpler to use. The CUSTOM curve option should now be used in the case that an unusual deformation is needed, in other cases, the hardcoded curve presets should be the default.

The smooth curve presets is a must in the grab brush, as it fixes the deformation issue with the current custom curve setting. The user may try to adjust the deformation by tweaking the curve, but it is nearly impossible to replicate this desired behavior.

{F7636217}

Other brushes that are included in the sculpt branch also rely on this as they need specific hardcoded falloffs to produce the desired effect.

Reviewers: brecht, billreynish

Reviewed By: brecht

Subscribers: JulienKaspar

Differential Revision: https://developer.blender.org/D5367
This commit is contained in:
Pablo Dobarro 2019-07-31 12:58:50 +02:00
parent 03f652b2c1
commit 4bb9fbd3a8
7 changed files with 104 additions and 28 deletions

View File

@ -1438,16 +1438,21 @@ class IMAGE_PT_paint_curve(BrushButtonsPanel, Panel):
tool_settings = context.tool_settings.image_paint
brush = tool_settings.brush
layout.template_curve_mapping(brush, "curve")
col = layout.column(align=True)
row = col.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
row.prop(brush, "curve_preset", text="")
if brush.curve_preset == 'CUSTOM':
layout.template_curve_mapping(brush, "curve")
col = layout.column(align=True)
row = col.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
class IMAGE_PT_tools_imagepaint_symmetry(BrushButtonsPanel, Panel):
@ -1538,15 +1543,20 @@ class IMAGE_PT_uv_sculpt_curve(Panel):
brush = uvsculpt.brush
if brush is not None:
layout.template_curve_mapping(brush, "curve")
col = layout.column(align=True)
row = col.row(align=True)
row.prop(brush, "curve_preset", text="")
row = layout.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
if brush.curve_preset == 'CUSTOM':
layout.template_curve_mapping(brush, "curve")
row = layout.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
class ImageScopesPanel:

View File

@ -2687,7 +2687,7 @@ class VIEW3D_MT_brush(Menu):
sculpt_tool = brush.sculpt_tool
layout.separator()
layout.operator_menu_enum("brush.curve_preset", "shape", text="Curve Preset")
layout.prop_menu_enum(brush, "curve_preset")
layout.separator()
if sculpt_tool != 'GRAB':

View File

@ -958,16 +958,21 @@ class VIEW3D_PT_tools_brush_falloff(Panel, View3DPaintPanel):
settings = self.paint_settings(context)
brush = settings.brush
layout.template_curve_mapping(brush, "curve", brush=True)
col = layout.column(align=True)
row = col.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
row.prop(brush, "curve_preset", text="")
if brush.curve_preset == 'CUSTOM':
layout.template_curve_mapping(brush, "curve", brush=True)
col = layout.column(align=True)
row = col.row(align=True)
row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
class VIEW3D_PT_tools_brush_falloff_frontface(View3DPaintPanel, Panel):

View File

@ -1400,16 +1400,45 @@ void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask)
/* Uses the brush curve control to find a strength value */
float BKE_brush_curve_strength(const Brush *br, float p, const float len)
{
float strength;
float strength = 1.0f;
if (p >= len) {
return 0;
}
else {
p = p / len;
p = 1.0f - p;
}
strength = curvemapping_evaluateF(br->curve, 0, p);
switch (br->curve_preset) {
case BRUSH_CURVE_CUSTOM:
strength = curvemapping_evaluateF(br->curve, 0, 1.0f - p);
break;
case BRUSH_CURVE_SHARP:
strength = p * p;
break;
case BRUSH_CURVE_SMOOTH:
strength = 3.0f * p * p - 2.0f * p * p * p;
break;
case BRUSH_CURVE_ROOT:
strength = sqrtf(p);
break;
case BRUSH_CURVE_LIN:
strength = p;
break;
case BRUSH_CURVE_CONSTANT:
strength = 1.0f;
break;
case BRUSH_CURVE_SPHERE:
strength = sqrtf(2 * p - p * p);
break;
case BRUSH_CURVE_POW4:
strength = p * p * p * p;
break;
case BRUSH_CURVE_INVSQUARE:
strength = p * (2.0f - p);
break;
}
return strength;
}

View File

@ -1262,7 +1262,6 @@ float tex_strength(SculptSession *ss,
/* Falloff curve */
avg *= BKE_brush_curve_strength(br, len, cache->radius);
avg *= frontface(br, cache->view_normal, vno, fno);
/* Paint mask */

View File

@ -186,6 +186,18 @@ typedef enum eGP_BrushIcons {
GP_BRUSH_ICON_ERASE_STROKE = 10,
} eGP_BrushIcons;
typedef enum eBrushCurvePreset {
BRUSH_CURVE_CUSTOM = 0,
BRUSH_CURVE_SMOOTH = 1,
BRUSH_CURVE_SPHERE = 2,
BRUSH_CURVE_ROOT = 3,
BRUSH_CURVE_SHARP = 4,
BRUSH_CURVE_LIN = 5,
BRUSH_CURVE_POW4 = 6,
BRUSH_CURVE_INVSQUARE = 7,
BRUSH_CURVE_CONSTANT = 8,
} eBrushCurvePreset;
typedef struct Brush {
ID id;
@ -289,6 +301,9 @@ typedef struct Brush {
float texture_sample_bias;
int curve_preset;
char _pad1[4];
/* overlay */
int texture_overlay_alpha;
int mask_overlay_alpha;

View File

@ -1565,6 +1565,19 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_curve_preset_items[] = {
{BRUSH_CURVE_CUSTOM, "CUSTOM", ICON_RNDCURVE, "Custom", ""},
{BRUSH_CURVE_SMOOTH, "SMOOTH", ICON_SMOOTHCURVE, "Smooth", ""},
{BRUSH_CURVE_SPHERE, "SPHERE", ICON_SPHERECURVE, "Sphere", ""},
{BRUSH_CURVE_ROOT, "ROOT", ICON_ROOTCURVE, "Root", ""},
{BRUSH_CURVE_SHARP, "SHARP", ICON_SHARPCURVE, "Sharp", ""},
{BRUSH_CURVE_LIN, "LIN", ICON_LINCURVE, "Linear", ""},
{BRUSH_CURVE_POW4, "POW4", ICON_SHARPCURVE, "Sharper", ""},
{BRUSH_CURVE_INVSQUARE, "INVSQUARE", ICON_INVERSESQUARECURVE, "Inverse square", ""},
{BRUSH_CURVE_CONSTANT, "CONSTANT", ICON_NOCURVE, "Constant", ""},
{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");
@ -1640,6 +1653,11 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Mask Tool", "");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "curve_preset", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_curve_preset_items);
RNA_def_property_ui_text(prop, "Curve Preset", "");
RNA_def_property_update(prop, 0, "rna_Brush_update");
/* number values */
prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL);
RNA_def_property_int_funcs(prop, NULL, "rna_Brush_set_size", NULL);