Sculpt: brush input mapping improvements

* Input mappings now take a premultiply factor
  to scale the input data prior to evaluation;
* Mapping data can also now be fed through a
  (wave) function prior to evaluation.
* The UI now has seperate inputs and outputs
  sections for input mapping to avoid confusion.
* Added a distance mapping and implemented the speed
  mapping.
* Also fixed original data bug in color filter.
This commit is contained in:
Joseph Eagar 2021-10-16 15:06:36 -07:00
parent 1e194722e5
commit 6777176691
13 changed files with 251 additions and 16 deletions

View File

@ -569,6 +569,13 @@ class UnifiedPaintPanel:
col.prop(mp, "factor")
col.prop(mp, "blendmode")
col.label(text="Input Mapping")
row = col.row()
row.prop(mp, "premultiply")
row.prop(mp, "mapfunc")
col.label(text="Output Mapping")
row = col.row()
row.prop(mp, "min")
row.prop(mp, "max")

View File

@ -1144,7 +1144,12 @@ class VIEW3D_PT_sculpt_dyntopo(Panel, View3DPaintPanel):
ch = UnifiedPaintPanel.get_channel(context, brush, "dyntopo_mode")
col2.use_property_split = False
col2.prop_enum(ch, "flags_value", "CLEANUP", icon="CHECKBOX_HLT" if "CLEANUP" in ch.flags_value else "CHECKBOX_DEHLT")
row2 = col2.row()
row2.prop_enum(ch, "flags_value", "CLEANUP", icon="CHECKBOX_HLT" if "CLEANUP" in ch.flags_value else "CHECKBOX_DEHLT")
row3 = row2.row()
row3.enabled = "COLLAPSE" not in ch.flags_value
row3.prop_enum(ch, "flags_value", "LOCAL_COLLAPSE", icon="CHECKBOX_HLT" if "LOCAL_COLLAPSE" in ch.flags_value else "CHECKBOX_DEHLT")
"""
UnifiedPaintPanel.channel_unified(
@ -1250,7 +1255,20 @@ class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel):
brush,
"smooth_strength_projection", ui_editing=False, slider=True)
#col.prop(sculpt, "smooth_strength_factor")
"""
smoothbrush = None
for ts in sculpt.tool_slots:
if ts.brush and ts.brush.sculpt_tool == "SMOOTH":
smoothbrush = ts.brush
break
if smoothbrush:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
smoothbrush,
"dyntopo_disabled", ui_editing=False, text="Disable Dyntopo For Smooth")
"""
col.separator()

View File

@ -110,11 +110,11 @@ typedef struct BrushMappingDef {
typedef struct BrushMappingPreset {
// must match order of BRUSH_MAPPING_XXX enums
struct BrushMappingDef pressure, xtilt, ytilt, angle, speed, random;
struct BrushMappingDef pressure, xtilt, ytilt, angle, speed, random, stroke_t;
} BrushMappingPreset;
typedef struct BrushMappingData {
float pressure, xtilt, ytilt, angle, speed, random;
float pressure, xtilt, ytilt, angle, speed, random, stroke_t;
} BrushMappingData;
#define MAX_BRUSH_ENUM_DEF 32

View File

@ -262,7 +262,7 @@ places in rna_engine_codebase are relevent:
{DYNTOPO_COLLAPSE, "COLLAPSE", "NONE", "Collapse", ""},
{DYNTOPO_SUBDIVIDE, "SUBDIVIDE", "NONE", "Subdivide", ""},
{DYNTOPO_CLEANUP, "CLEANUP", "NONE", "Cleanup", ""},
{DYNTOPO_LOCAL_COLLAPSE, "LOCAL_COLLAPSE", "NONE", "Local Collapse", ""},
{DYNTOPO_LOCAL_COLLAPSE, "LOCAL_COLLAPSE", "NONE", "Local Collapse", "Cleanup edges based on local edge lengths if collapse is off."},
{DYNTOPO_LOCAL_SUBDIVIDE, "LOCAL_SUBDIVIDE", "NONE", "Local Subdivide", ""},
{-1}
})

View File

@ -448,6 +448,11 @@ void BKE_brush_channel_init(BrushChannel *ch, BrushChannelType *def)
mp->blendmode = !mdef->no_default ? MA_RAMP_MULT : mdef->blendmode;
mp->factor = mdef->factor == 0.0f ? 1.0f : mdef->factor;
mp->premultiply = 1.0f;
if (i == BRUSH_MAPPING_STROKE_T) {
mp->mapfunc = BRUSH_MAPFUNC_COS;
}
if (mdef->enabled) {
mp->flag |= BRUSH_MAPPING_ENABLED;
@ -946,10 +951,39 @@ double BKE_brush_channel_eval_mappings(BrushChannel *ch,
continue;
}
float inputf = ((float *)mapdata)[i];
float inputf = ((float *)mapdata)[i] * mp->premultiply;
switch ((BrushMappingFunc)mp->mapfunc) {
case BRUSH_MAPFUNC_NONE:
break;
case BRUSH_MAPFUNC_SQUARE:
inputf -= floorf(inputf);
inputf = (float)(inputf > 0.5f);
break;
case BRUSH_MAPFUNC_SAW:
inputf -= floorf(inputf);
break;
case BRUSH_MAPFUNC_TENT:
inputf -= floorf(inputf);
inputf = 1.0f - fabs(inputf - 0.5f) * 2.0f;
break;
case BRUSH_MAPFUNC_COS:
inputf = 1.0f - (cos(inputf * (float)M_PI * 2.0f) * 0.5f + 0.5f);
break;
case BRUSH_MAPFUNC_CUTOFF:
/*Cutoff is meant to create a fadeout effect,
which requires inverting the input. To avoid
user confusion we just do it here instead of making
them check the inverse checkbox.*/
inputf = 1.0f - inputf;
CLAMP(inputf, 0.0f, 1.0f);
break;
default:
break;
}
if (mp->flag & BRUSH_MAPPING_INVERT) {
inputf = 1.0 - inputf;
inputf = 1.0f - inputf;
}
double f2 = (float)BKE_curvemapping_evaluateF(mp->curve, 0, inputf);
@ -1774,6 +1808,14 @@ void BKE_brush_channelset_read(BlendDataReader *reader, BrushChannelSet *chset)
CurveMapping *curve = mp->curve;
if (mp->premultiply == 0.0f) {
mp->premultiply = 1.0f;
}
if (mp->factor == 0.0f) {
mp->factor = 1.0f;
}
if (mp->min == mp->max == 0.0f) {
mp->max = 1.0f;
}
@ -1846,6 +1888,8 @@ const char *BKE_brush_mapping_type_to_str(BrushMappingType mapping)
return "Y Tilt";
case BRUSH_MAPPING_RANDOM:
return "Random";
case BRUSH_MAPPING_STROKE_T:
return "Distance";
case BRUSH_MAPPING_MAX:
return "Error";
}
@ -1868,6 +1912,8 @@ const char *BKE_brush_mapping_type_to_typename(BrushMappingType mapping)
return "YTILT";
case BRUSH_MAPPING_RANDOM:
return "RANDOM";
case BRUSH_MAPPING_STROKE_T:
return "DISTANCE";
case BRUSH_MAPPING_MAX:
return "Error";
}

View File

@ -3858,6 +3858,11 @@ static BMVert *pbvh_bmesh_collapse_edge(PBVH *pbvh,
copy_v3_v3(v_conn->co, co);
}
if (!v_conn->e) {
printf("%s: pbvh error, v_conn->e was null\n", __func__);
return v_conn;
}
e2 = v_conn->e;
do {
BMLoop *l = e2->l;

View File

@ -1901,6 +1901,11 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh,
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
// check for currupted faceset
if (BM_ELEM_CD_GET_INT(f, pbvh->cd_faceset_offset) == 0) {
BM_ELEM_CD_SET_INT(f, pbvh->cd_faceset_offset, 1);
}
BB_reset((BB *)bbc);
do {
BB_expand((BB *)bbc, l_iter->v->co);
@ -3119,11 +3124,11 @@ static void pbvh_bmesh_balance_tree(PBVH *pbvh)
/* use higher threshold for the root node and its immediate children */
switch (BLI_array_len(stack)) {
case 0:
factor = 0.75;
factor = 0.5;
break;
case 1:
case 2:
factor = 0.5;
factor = 0.2;
break;
default:
factor = 0.2;
@ -3138,7 +3143,12 @@ static void pbvh_bmesh_balance_tree(PBVH *pbvh)
printf("factor: %.3f\n", factor);
#endif
if (overlap > volume * factor) {
bool bad = overlap > volume * factor;
bad |= child1->bm_faces && !BLI_table_gset_len(child1->bm_faces);
bad |= child2->bm_faces && !BLI_table_gset_len(child2->bm_faces);
if (bad) {
modified = true;
printf(" DELETE! %.4f %.4f %d\n", overlap, volume, BLI_array_len(stack));

View File

@ -1907,8 +1907,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
BKE_brush_mapping_reset(ch, brush->sculpt_tool, BRUSH_MAPPING_PRESSURE);
}
ch = (BrushChannel *)brush->channels->channels.first;
for (; ch; ch = ch->next) {
LISTBASE_FOREACH (BrushChannel *, ch, &brush->channels->channels) {
if (!ch->mappings[BRUSH_MAPPING_RANDOM].factor) {
ch->mappings[BRUSH_MAPPING_RANDOM].factor = 1.0f;
}
@ -1927,6 +1926,64 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 300, 38)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (!scene->toolsettings || !scene->toolsettings->sculpt ||
!scene->toolsettings->sculpt->channels) {
continue;
}
Sculpt *sd = scene->toolsettings->sculpt;
LISTBASE_FOREACH (BrushChannel *, ch, &sd->channels->channels) {
if (!ch->mappings[BRUSH_MAPPING_RANDOM].factor) {
ch->mappings[BRUSH_MAPPING_RANDOM].factor = 1.0f;
}
for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
if (ch->mappings[i].premultiply == 0.0f) {
ch->mappings[i].premultiply = 1.0f;
}
if (ch->mappings[i].blendmode == MA_RAMP_BLEND) {
ch->mappings[i].blendmode = MA_RAMP_MULT;
}
if (ch->mappings[i].min == ch->mappings[i].max) {
ch->mappings[i].min = 0.0f;
ch->mappings[i].max = 1.0f;
}
}
BrushMapping *mp = ch->mappings + BRUSH_MAPPING_STROKE_T;
mp->max = 1.0f;
mp->factor = 1.0f;
mp->blendmode = MA_RAMP_MULT;
mp->mapfunc = BRUSH_MAPFUNC_COS;
}
}
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
if (!brush->channels) {
continue;
}
LISTBASE_FOREACH (BrushChannel *, ch, &brush->channels->channels) {
for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
ch->mappings[i].premultiply = 1.0f;
}
BrushMapping *mp = ch->mappings + BRUSH_MAPPING_STROKE_T;
mp->blendmode = MA_RAMP_MULT;
mp->max = 1.0f;
mp->mapfunc = BRUSH_MAPFUNC_COS;
mp->factor = 1.0f;
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -10932,6 +10932,12 @@ static void sculpt_update_cache_invariants(
zero_v2(cache->initial_mouse);
}
/* initialize speed moving average */
for (int i = 0; i < SCULPT_SPEED_MA_SIZE; i++) {
cache->speed_avg[i] = -1.0f;
}
cache->last_speed_time = PIL_check_seconds_timer();
copy_v3_v3(cache->initial_location, ss->cursor_location);
copy_v3_v3(cache->true_initial_location, ss->cursor_location);
@ -11354,6 +11360,37 @@ static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *
}
}
static float sculpt_update_speed_average(SculptSession *ss, float speed)
{
int tot = 0.0;
bool found = false;
for (int i = 0; i < SCULPT_SPEED_MA_SIZE; i++) {
tot++;
if (ss->cache->speed_avg[i] == -1.0f) {
ss->cache->speed_avg[i] = speed;
found = true;
break;
}
}
if (!found) {
ss->cache->speed_avg[ss->cache->speed_avg_cur] = speed;
ss->cache->speed_avg_cur = (ss->cache->speed_avg_cur + 1) % SCULPT_SPEED_MA_SIZE;
}
speed = 0.0f;
tot = 0;
for (int i = 0; i < SCULPT_SPEED_MA_SIZE; i++) {
if (ss->cache->speed_avg[i] != -1.0f) {
speed += ss->cache->speed_avg[i];
tot++;
}
}
return speed / (float)tot;
}
/* Initialize the stroke cache variants from operator properties. */
static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr)
{
@ -11369,10 +11406,22 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
RNA_float_get_array(ptr, "location", cache->true_location);
}
float last_mouse[2];
copy_v2_v2(last_mouse, cache->mouse);
cache->pen_flip = RNA_boolean_get(ptr, "pen_flip");
RNA_float_get_array(ptr, "mouse", cache->mouse);
RNA_float_get_array(ptr, "mouse_event", cache->mouse_event);
float delta_mouse[2];
sub_v3_v3v3(delta_mouse, cache->mouse, cache->mouse_event);
float speed = len_v3(delta_mouse) / (800000.0f); /*get a reasonably usable value*/
speed /= PIL_check_seconds_timer() - cache->last_speed_time;
cache->input_mapping.speed = sculpt_update_speed_average(ss, speed);
cache->last_speed_time = PIL_check_seconds_timer();
/* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab,
* thumb). They depends on initial state and brush coord/pressure/etc.
* It's more an events design issue, which doesn't split coordinate/pressure/angle changing
@ -11408,7 +11457,6 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
mul_v4_m4v4(direction, cache->projection_mat, direction);
cache->input_mapping.angle = (atan2(direction[1], direction[0]) / (float)M_PI) * 0.5 + 0.5;
// cache->vc
}
@ -11481,8 +11529,10 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
}
cache->special_rotation = ups->brush_rotation;
cache->iteration_count++;
cache->input_mapping.stroke_t = cache->stroke_distance_t /
10.0f; /*scale to a more user-friendly value*/
}
/* Returns true if any of the smoothing modes are active (currently

View File

@ -299,6 +299,9 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent
const bool needs_topology_info = mode == COLOR_FILTER_SMOOTH || use_automasking;
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_topology_info, false, true);
/*flag update for original data*/
ss->stroke_id++;
if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_topology_info && !ob->sculpt->pmap) {
return OPERATOR_CANCELLED;
}

View File

@ -1187,6 +1187,7 @@ bool SCULPT_pbvh_calc_area_normal(const struct Brush *brush,
*/
#define SCULPT_CLAY_STABILIZER_LEN 10
#define SCULPT_SPEED_MA_SIZE 4
typedef struct AutomaskingSettings {
/* Flags from eAutomasking_flag. */
@ -1399,6 +1400,9 @@ typedef struct StrokeCache {
bool use_plane_trim;
struct NeighborCache *ncache;
float speed_avg[SCULPT_SPEED_MA_SIZE]; // moving average for speed
int speed_avg_cur;
double last_speed_time;
} StrokeCache;
/* Sculpt Filters */

View File

@ -40,6 +40,8 @@ typedef struct BrushMapping {
int flag, type;
float min, max;
float premultiply; // premultiply input data
int mapfunc;
} BrushMapping;
typedef struct BrushCurve {
@ -61,7 +63,7 @@ typedef struct BrushChannel {
float vector[4];
BrushCurve curve;
BrushMapping mappings[6]; // should always be BRUSH_MAPPING_MAX
BrushMapping mappings[7]; // should always be BRUSH_MAPPING_MAX
short type, ui_order;
int flag;
@ -83,6 +85,16 @@ enum {
BRUSH_MAPPING_INHERIT = 1 << 3, // inherit mapping even if channel overall is not inherited
};
// BrushMapping->mapfunc
typedef enum {
BRUSH_MAPFUNC_NONE,
BRUSH_MAPFUNC_SAW,
BRUSH_MAPFUNC_TENT,
BRUSH_MAPFUNC_COS,
BRUSH_MAPFUNC_CUTOFF,
BRUSH_MAPFUNC_SQUARE,
} BrushMappingFunc;
// mapping types
typedef enum {
BRUSH_MAPPING_PRESSURE = 0,
@ -91,7 +103,8 @@ typedef enum {
BRUSH_MAPPING_ANGLE = 3,
BRUSH_MAPPING_SPEED = 4,
BRUSH_MAPPING_RANDOM = 5,
BRUSH_MAPPING_MAX = 6 // see BrushChannel.mappings
BRUSH_MAPPING_STROKE_T = 6,
BRUSH_MAPPING_MAX = 7 // see BrushChannel.mappings
} BrushMappingType;
#ifndef __GNUC__

View File

@ -647,6 +647,7 @@ static EnumPropertyItem mapping_type_items[] = {
{BRUSH_MAPPING_ANGLE, "ANGLE", ICON_NONE, "Angle"},
{BRUSH_MAPPING_SPEED, "SPEED", ICON_NONE, "Speed"},
{BRUSH_MAPPING_RANDOM, "RANDOM", ICON_NONE, "Random"},
{BRUSH_MAPPING_STROKE_T, "DISTANCE", ICON_NONE, "Distance"},
{0, NULL, 0, NULL, NULL},
};
@ -664,6 +665,13 @@ void RNA_def_brush_mapping(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Factor", "Mapping factor");
prop = RNA_def_property(srna, "premultiply", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, "BrushMapping", "premultiply");
RNA_def_property_range(prop, -100000, 100000);
RNA_def_property_ui_range(prop, -100, 100, 0.01, 3);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Pre-Multiply", "Multiply input data by this amount");
prop = RNA_def_property(srna, "min", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, "BrushMapping", "min");
RNA_def_property_range(prop, -100000, 100000);
@ -723,6 +731,20 @@ void RNA_def_brush_mapping(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Blend Mode", "Input mapping blend mode");
static EnumPropertyItem mapfunc_items[] = {
{BRUSH_MAPFUNC_NONE, "NONE", ICON_NONE, "None", "Pass data through unmodified"},
{BRUSH_MAPFUNC_SQUARE, "SQUARE", ICON_NONE, "Square", "Square wave"},
{BRUSH_MAPFUNC_SAW, "SAW", ICON_NONE, "Saw", "Sawtooth wave"},
{BRUSH_MAPFUNC_TENT, "TENT", ICON_NONE, "Tent", "Tent wave"},
{BRUSH_MAPFUNC_COS, "COS", ICON_NONE, "Cos", "Cosine wave"},
{BRUSH_MAPFUNC_CUTOFF, "CUTOFF", ICON_NONE, "Cutoff", "Inverts data and cuts off at 1.0"},
{0, NULL, 0, NULL, NULL}};
prop = RNA_def_property(srna, "mapfunc", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, mapfunc_items);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Function", "Input data function");
prop = RNA_def_property(srna, "ui_expanded", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, "BrushMapping", "flag", BRUSH_MAPPING_UI_EXPANDED);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);