diff options
Diffstat (limited to 'source/blender/blenkernel/intern/brush_engine.c')
-rw-r--r-- | source/blender/blenkernel/intern/brush_engine.c | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/brush_engine.c b/source/blender/blenkernel/intern/brush_engine.c new file mode 100644 index 00000000000..a917c92879e --- /dev/null +++ b/source/blender/blenkernel/intern/brush_engine.c @@ -0,0 +1,667 @@ +#include "MEM_guardedalloc.h" + +#include "BLI_alloca.h" +#include "BLI_array.h" +#include "BLI_bitmap.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_memarena.h" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_color_types.h" +#include "DNA_curveprofile_types.h" +#include "DNA_node_types.h" +#include "DNA_sculpt_brush_types.h" + +#include "BKE_brush.h" +#include "BKE_colorband.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_node.h" +#include "BKE_paint.h" + +#include "BKE_brush_engine.h" +#include "BKE_curveprofile.h" + +#include "BLO_read_write.h" + +#define ICON_NONE -1 + +/* +Brush command lists. + +Command lists are built dynamically from +brush flags, pen input settings, etc. + +Eventually they will be generated by node +networks. BrushCommandPreset will be +generated from the node group inputs. +*/ + +/* clang-format off */ +BrushChannelType brush_builtin_channels[] = { + { + .name = "Radius", + .idname = "RADIUS", + .min = 0.001f, + .type = BRUSH_CHANNEL_FLOAT, + .max = 2048.0f, + .soft_min = 0.1f, + .soft_max = 1024.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Strength", + .idname = "STRENGTH", + .min = -1.0f, + .type = BRUSH_CHANNEL_FLOAT, + .max = 4.0f, + .soft_min = 0.0f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = true}, + } + }, + { + .name = "Spacing", + .idname = "SPACING", + .min = 0.001f, + .type = BRUSH_CHANNEL_FLOAT, + .max = 4.0f, + .soft_min = 0.005f, + .soft_max = 2.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = true}, + } + }, + { + .name = "Autosmooth", + .idname = "AUTOSMOOTH", + .type = BRUSH_CHANNEL_FLOAT, + .min = -1.0f, + .max = 4.0f, + .soft_min = 0.0f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false, .inv = true}, + } + }, + { + .name = "Topology Rake", + .idname = "TOPOLOGY_RAKE", + .type = BRUSH_CHANNEL_FLOAT, + .min = -1.0f, + .max = 4.0f, + .soft_min = 0.0f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Autosmooth Radius Scale", + .idname = "AUTOSMOOTH_RADIUS_SCALE", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.0001f, + .max = 25.0f, + .soft_min = 0.1f, + .soft_max = 4.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Rake Radius Scale", + .idname = "TOPOLOGY_RAKE_RADIUS_SCALE", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.0001f, + .max = 25.0f, + .soft_min = 0.1f, + .soft_max = 4.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Face Set Slide", + .idname = "FSET_SLIDE", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.0001f, + .max = 1.0f, + .soft_min = 0.1f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Boundary Smooth", + .idname = "BOUNDARY_SMOOTH", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.0001f, + .max = 1.0f, + .soft_min = 0.1f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Projection", + .idname = "PROJECTION", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.0001f, + .max = 1.0f, + .soft_min = 0.1f, + .soft_max = 1.0f, + .mappings = { + .pressure = {.curve = CURVE_PRESET_SMOOTH, .min = 0.0f, .max = 1.0f, .enabled = false}, + } + }, + { + .name = "Topology Rake Mode", + .idname = "TOPOLOGY_RAKE_MODE", + .type = BRUSH_CHANNEL_ENUM, + .enumdef = {.items = { + {0, "BRUSH_DIRECTION", ICON_NONE, "Stroke", "Stroke Direction"}, + {1, "CURVATURE", ICON_NONE, "Curvature", "Follow mesh curvature"}, + {-1, 0} + }} + }, + { + .name = "Automasking", + .idname = "AUTOMASKING", + .flag = BRUSH_CHANNEL_INHERIT_IF_UNSET | BRUSH_CHANNEL_INHERIT, + .type = BRUSH_CHANNEL_BITMASK, + .enumdef = {.items = { + {BRUSH_AUTOMASKING_BOUNDARY_EDGES, "BOUNDARY_EDGE", ICON_NONE, "Boundary Edges", ""}, + {BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS, "BOUNDARY_FACE_SETS", ICON_NONE, "Boundary Face Sets", ""}, + {BRUSH_AUTOMASKING_CONCAVITY, "CONCAVITY", ICON_NONE, "Concave", ""}, + {BRUSH_AUTOMASKING_INVERT_CONCAVITY, "INVERT_CONCAVITY", ICON_NONE, "Invert Concave", "Invert Concave Map"}, + {BRUSH_AUTOMASKING_FACE_SETS, "FACE_SETS", ICON_NONE, "Face Sets", ""}, + {BRUSH_AUTOMASKING_TOPOLOGY, "TOPOLOGY", ICON_NONE, "Topology", ""} + }} + }, + { + .name = "Disable Dyntopo", + .idname = "DYNTOPO_DISABLED", + .type = BRUSH_CHANNEL_INT, + .flag = BRUSH_CHANNEL_NO_MAPPINGS, + .ivalue = 0 + }, + { + .name = "Detail Range", + .idname = "DYNTOPO_DETAIL_RANGE", + .type = BRUSH_CHANNEL_FLOAT, + .min = 0.001, + .max = 0.99, + .ivalue = 0 + }, +}; + +/* clang-format on */ +const int builtin_channel_len = ARRAY_SIZE(brush_builtin_channels); + +void BKE_brush_channel_free(BrushChannel *ch) +{ + for (int i = 0; i < BRUSH_MAPPING_MAX; i++) { + BKE_curvemapping_free_data(&ch->mappings[i].curve); + } +} + +void BKE_brush_channel_copy(BrushChannel *dst, BrushChannel *src) +{ + *dst = *src; + + for (int i = 0; i < BRUSH_MAPPING_MAX; i++) { + BKE_curvemapping_copy_data(&dst->mappings[i].curve, &src->mappings[i].curve); + } +} + +void BKE_brush_channel_init(BrushChannel *ch, BrushChannelType *def) +{ + memset(ch, 0, sizeof(*ch)); + + strcpy(ch->name, def->name); + strcpy(ch->idname, def->idname); + + ch->flag = def->flag; + ch->fvalue = def->fvalue; + ch->ivalue = def->ivalue; + + ch->def = def; + + for (int i = 0; i < BRUSH_MAPPING_MAX; i++) { + BrushMapping *map = ch->mappings + i; + CurveMapping *curve = &map->curve; + + BKE_curvemapping_init(curve); + + float min, max; + + BrushMappingDef *mdef = (&def->mappings.pressure) + i; + + if (mdef->min != mdef->max) { + min = mdef->min; + max = mdef->max; + } + else { + min = 0.0f; + max = 1.0f; + } + + if (mdef->inv) { + ch->mappings[i].flag |= BRUSH_MAPPING_INVERT; + } + + map->blendmode = mdef->blendmode; + map->factor = 1.0f; + + if (mdef->enabled) { + map->flag |= BRUSH_MAPPING_ENABLED; + } + } +} + +BrushChannelSet *BKE_brush_channelset_create() +{ + return (BrushChannelSet *)MEM_callocN(sizeof(BrushChannelSet), "BrushChannelSet"); +} + +void BKE_brush_channelset_free(BrushChannelSet *chset) +{ + if (chset->channels) { + for (int i = 0; i < chset->totchannel; i++) { + BKE_brush_channel_free(chset->channels + i); + } + + MEM_freeN(chset->channels); + } + MEM_freeN(chset); +} + +void BKE_brush_channelset_add(BrushChannelSet *chset, BrushChannel *ch) +{ + chset->totchannel++; + + if (!chset->channels) { + chset->channels = MEM_callocN(sizeof(BrushChannelSet) * chset->totchannel, "chset->channels"); + } + else { + chset->channels = MEM_recallocN(chset->channels, + sizeof(BrushChannelSet) * sizeof(chset->totchannel)); + } + + memcpy(chset->channels + chset->totchannel - 1, ch, sizeof(BrushChannelSet)); +} + +BrushChannel *BKE_brush_channelset_lookup(BrushChannelSet *chset, const char *idname) +{ + for (int i = 0; i < chset->totchannel; i++) { + if (STREQ(chset->channels[i].idname, idname)) { + return chset->channels + i; + } + } + + return NULL; +} + +bool BKE_brush_channelset_has(BrushChannelSet *chset, const char *idname) +{ + return BKE_brush_channelset_lookup(chset, idname) != NULL; +} + +void BKE_brush_channelset_add_builtin(BrushChannelSet *chset, const char *idname) +{ + BrushChannelType *def = NULL; + + for (int i = 0; i < builtin_channel_len; i++) { + BrushChannelType *def2 = builtin_channel_len + i; + + if (STREQ(def2->idname, idname)) { + def = def2; + break; + } + } + + if (!def) { + printf("%s: Could not find brush %s\n", __func__, idname); + return; + } + + BrushChannel ch; + + BKE_brush_channel_init(&ch, def); + BKE_brush_channelset_add(chset, &ch); +} + +bool BKE_brush_channelset_ensure_builtin(BrushChannelSet *chset, const char *idname) +{ + if (!BKE_brush_channelset_has(chset, idname)) { + BKE_brush_channelset_add_builtin(chset, idname); + return true; + } + + return false; +} + +#define ADDCH(name) BKE_brush_channelset_ensure_builtin(chset, name) +#define GETCH(name) BKE_brush_channelset_lookup(chset, name) + +void BKE_brush_channelset_merge(BrushChannelSet *dst, + BrushChannelSet *child, + BrushChannelSet *parent) +{ + // first add missing channels + + for (int step = 0; step < 2; step++) { + BrushChannelSet *chset = step ? parent : child; + + for (int i = 0; i < chset->totchannel; i++) { + BrushChannel *ch = chset->channels + i; + + if (BKE_brush_channelset_has(dst, ch->idname)) { + continue; + } + + BrushChannel ch2; + BKE_brush_channel_copy(&ch2, ch); + BKE_brush_channelset_add(chset, &ch2); + } + } + + for (int i = 0; i < child->totchannel; i++) { + BrushChannel *ch = child->channels + i; + BrushChannel *mch = BKE_brush_channelset_lookup(dst, ch->idname); + BrushChannel *pch = BKE_brush_channelset_lookup(parent, ch->name); + + bool ok = ch->flag & BRUSH_CHANNEL_INHERIT; + + if (ch->flag & BRUSH_CHANNEL_INHERIT) { + BKE_brush_channel_free(mch); + BKE_brush_channel_copy(mch, pch); + continue; + } + + if (ch->type == BRUSH_CHANNEL_BITMASK && (ch->flag & BRUSH_CHANNEL_INHERIT_IF_UNSET)) { + mch->ivalue = ch->ivalue | pch->ivalue; + } + } +} + +void BKE_brush_resolve_channels(Brush *brush, Sculpt *sd) +{ + if (brush->channels_final) { + BKE_brush_channelset_free(brush->channels_final); + } + + brush->channels_final = BKE_brush_channelset_create(); + + BKE_brush_channelset_merge(brush->channels_final, brush->channels, sd->channels); + + if (!brush->commandlist) { + return; + } + + BrushCommandList *cl = brush->commandlist; + + for (int i = 0; i < cl->totcommand; i++) { + BrushCommand *command = cl->commands + i; + + if (command->params_final) { + BKE_brush_channelset_free(command->params_final); + } + + command->params_final = BKE_brush_channelset_create(); + BKE_brush_channelset_merge(command->params_final, command->params, brush->channels_final); + } +} + +int BKE_brush_channel_get_int(BrushChannelSet *chset, char *idname) +{ + BrushChannel *ch = BKE_brush_channelset_lookup(chset, idname); + + if (!ch) { + printf("%s, unknown channel %s", __func__, idname); + return 0; + } + + return ch->ivalue; +} + +float BKE_brush_channel_get_float(BrushChannelSet *chset, char *idname) +{ + BrushChannel *ch = BKE_brush_channelset_lookup(chset, idname); + + if (!ch) { + printf("%s, unknown channel %s", __func__, idname); + return 0; + } + + return ch->fvalue; +} + +float BKE_brush_channel_set_float(BrushChannelSet *chset, char *idname, float val) +{ + BrushChannel *ch = BKE_brush_channelset_lookup(chset, idname); + + if (!ch) { + printf("%s, unknown channel %s", __func__, idname); + return 0; + } + + float old = ch->fvalue; + + ch->fvalue = val; + + return old; +} + +void BKE_brush_init_toolsettings(Sculpt *sd) +{ + BrushChannelSet *chset = sd->channels = BKE_brush_channelset_create(); + + ADDCH("RADIUS"); + ADDCH("STRENGTH"); + ADDCH("AUTOMASKING"); + ADDCH("DYNTOPO_DISABLED"); + ADDCH("DYNTOPO_DETAIL_RANGE"); +} + +void BKE_brush_builtin_create(Brush *brush, int tool) +{ + if (brush->channels) { + BKE_brush_channelset_free(brush->channels); + } + + BrushChannelSet *chset = brush->channels = BKE_brush_channelset_create(); + + ADDCH("RADIUS"); + ADDCH("STRENGTH"); + ADDCH("AUTOSMOOTH"); + ADDCH("TOPOLOGY_RAKE"); + ADDCH("AUTOSMOOTH_RADIUS_SCLAE"); + ADDCH("RAKE_RADIUS_SCALE"); + + ADDCH("AUTOMASKING"); + + switch (tool) { + case SCULPT_TOOL_DRAW: { + BrushChannel *ch = GETCH("STRENGTH"); + + ch->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED; + ch->flag = BRUSH_CHANNEL_INHERIT; + break; + } + } +} + +BrushCommandList *BKE_brush_commandlist_create() +{ + return MEM_callocN(sizeof(BrushCommandList), "BrushCommandList"); +} +void BKE_brush_commandlist_free(BrushCommandList *cl) +{ + for (int i = 0; i < cl->totcommand; i++) { + BrushCommand *cmd = cl->commands + i; + + if (cmd->params) { + BKE_brush_channelset_free(cmd->params); + } + + if (cmd->params_final) { + BKE_brush_channelset_free(cmd->params_final); + } + } + + MEM_SAFE_FREE(cl->commands); + + MEM_freeN(cl); +} +BrushCommand *BKE_brush_commandlist_add(BrushCommandList *cl) +{ + cl->totcommand++; + + if (!cl->commands) { + cl->commands = MEM_callocN(sizeof(BrushCommand) * cl->totcommand, "BrushCommand"); + } + else { + cl->commands = MEM_recallocN(cl->commands, sizeof(BrushCommand) * cl->totcommand); + } + + BrushCommand *cmd = cl->commands + cl->totcommand - 1; + cmd->params = BKE_brush_channelset_create(); + cmd->params_final = NULL; + + return cmd; +} + +BrushCommand *BKE_brush_command_init(BrushCommand *command, int tool) +{ + BrushChannelSet *chset = command->params; + + ADDCH("SPACING"); + + switch (tool) { + case SCULPT_TOOL_DRAW: + ADDCH("RADIUS"); + ADDCH("STRENGTH"); + break; + case SCULPT_TOOL_SMOOTH: + ADDCH("RADIUS"); + ADDCH("STRENGTH"); + ADDCH("FSET_SLIDE"); + ADDCH("BOUNDARY_SMOOTH"); + ADDCH("PROJECTION"); + break; + case SCULPT_TOOL_TOPOLOGY_RAKE: + ADDCH("RADIUS"); + ADDCH("STRENGTH"); + // ADDCH("FSET_SLIDE"); + // ADDCH("BOUNDARY_SMOOTH"); + ADDCH("PROJECTION"); + ADDCH("TOPOLOGY_RAKE_MODE"); + break; + case SCULPT_TOOL_DYNTOPO: + break; + } + + return command; +} + +void BKE_builtin_commandlist_create(BrushChannelSet *chset, BrushCommandList *cl, int tool) +{ + BrushCommand *cmd; + + cmd = BKE_brush_commandlist_add(cl); + BKE_brush_command_init(cmd, tool); + + for (int i = 0; i < cmd->totparam; i++) { + // inherit from brush channels for main tool + cmd->params->channels[i].flag |= BRUSH_CHANNEL_INHERIT; + } + + float radius = BKE_brush_channel_get_float(chset, "RADIUS"); + float autosmooth_scale = BKE_brush_channel_get_float(chset, "AUTOSMOOTH_RADIUS_SCALE"); + + float autosmooth = BKE_brush_channel_get_float(chset, "AUTOSMOOTH"); + if (autosmooth > 0.0f) { + cmd = BKE_brush_command_init(BKE_brush_commandlist_add(cl), SCULPT_TOOL_SMOOTH); + BKE_brush_channel_set_float(cmd->params, "STRENGTH", autosmooth); + BKE_brush_channel_set_float(cmd->params, "RADIUS", radius * autosmooth_scale); + BKE_brush_channel_set_float( + cmd->params, "PROJECTION", BKE_brush_channel_get_float(chset, "AUTOSMOOTH_PROJECTION")); + } +} + +void BKE_brush_channelset_read(BlendDataReader *reader, BrushChannelSet *cset) +{ + BLO_read_data_address(reader, &cset->channels); + + for (int i = 0; i < cset->totchannel; i++) { + BrushChannel *ch = cset->channels + i; + + for (int j = 0; j < BRUSH_MAPPING_MAX; j++) { + BKE_curvemapping_blend_read(reader, &ch->mappings[j].curve); + } + } +} + +void BKE_brush_channelset_write(BlendWriter *writer, BrushChannelSet *cset) +{ + BLO_write_struct(writer, BrushChannelSet, cset); + BLO_write_struct_array_by_name(writer, "BrushChannel", cset->totchannel, cset->channels); + + for (int i = 0; i < cset->totchannel; i++) { + BrushChannel *ch = cset->channels + i; + + for (int j = 0; j < BRUSH_MAPPING_MAX; j++) { + BKE_curvemapping_blend_write(writer, &ch->mappings[j].curve); + } + } +} + +/* clang-format on */ + +/* idea for building built-in preset node graphs: +from brush_builder import Builder; + +def build(input, output): + input.add("Strength", "float", "strength").range(0.0, 3.0) + input.add("Radius", "float", "radius").range(0.01, 1024.0) + input.add("Autosmooth", "float", "autosmooth").range(0.0, 4.0) + input.add("Topology Rake", "float", "topology rake").range(0.0, 4.0) + input.add("Smooth Radius Scale", "float", "autosmooth_radius_scale").range(0.01, 5.0) + input.add("Rake Radius Scale", "float", "toporake_radius_scale").range(0.01, 5.0) + + draw = input.make.tool("DRAW") + draw.radius = input.radius + draw.strength = input.strength + + smooth = input.make.tool("SMOOTH") + smooth.radius = input.radius * input.autosmooth_radius_scale + smooth.strength = input.autosmooth; + smooth.flow = draw.outflow + + rake = input.make.tool("TOPORAKE") + rake.radius = input.radius * input.toporake_radius_scale + rake.strength = input.topology; + rake.flow = smooth.outflow + + output.out = rake.outflow + +preset = Builder(build) + +*/ + +/* +bNodeType sculpt_tool_node = { + .idname = "SculptTool", + .ui_name = "SculptTool", +};*/ +/* cland-format on */ |