Sculpt: New Cavity Automasking Mode

Add new cavity automasking mode based on local mesh
curvature.  Cavity masking is a great way to quickly add
detail in crevices and the like.  It's meant to be used
with the Paint brush in color attribute mode.  It does
work with other brushes but the results can be unpredictable.

{F13131497}

The old "dirty mask" operator has been replace with a new
"mask from cavity" operator that shares the same code with
cavity automasking.

Differences from the sculpt-dev implementation:
    * It uses the word "cavity."  When I first implemented
this I wasn't aware
      this feature existed in other software (and other
paint modes in Blender),
      and for reasons that escape me today I initially
decided to call it a concave or
      concavity mask.
    * The cavity factor works a bit differently.  It's
      no longer non-linear and functions as a simple
      scale around 0.5f.
    * Supports custom curves.
    * Supports blurring.

Reviewed By: Julian Kaspar, Jeroen Bakker and Campbell Barton
Differential Revision: https://developer.blender.org/D15122
Ref D15122
This commit is contained in:
Joseph Eagar 2022-09-28 16:22:34 -07:00
parent 53ac3192ba
commit 0156a677c7
Notes: blender-bot 2023-02-14 10:14:07 +01:00
Referenced by issue #103948, Flickering when using sculpt brushes with cavity option
Referenced by issue #101718, Crash when cavity automasking in enabled in sculpt/paint mode
Referenced by issue #101718, Crash when cavity automasking in enabled in sculpt/paint mode
Referenced by issue #98383, Sculpt Mode Cavity Masking
26 changed files with 955 additions and 236 deletions

View File

@ -943,8 +943,22 @@ def brush_settings_advanced(layout, context, brush, popover=False):
# boundary edges/face sets automasking
col.prop(brush, "use_automasking_boundary_edges", text="Mesh Boundary")
col.prop(brush, "use_automasking_boundary_face_sets", text="Face Sets Boundary")
col.prop(brush, "use_automasking_cavity", text="Cavity")
col.prop(brush, "use_automasking_cavity_inverted", text="Cavity (Inverted)")
col.separator()
col.prop(brush, "automasking_boundary_edges_propagation_steps")
if brush.use_automasking_cavity or brush.use_automasking_cavity_inverted:
col.separator()
col.prop(brush, "automasking_cavity_factor", text="Cavity Factor")
col.prop(brush, "automasking_cavity_blur_steps", text="Cavity Blur")
col.prop(brush, "use_automasking_custom_cavity_curve", text="Use Curve")
if brush.use_automasking_custom_cavity_curve:
col.template_curve_mapping(brush, "automasking_cavity_curve")
layout.separator()
# sculpt plane settings

View File

@ -3298,7 +3298,8 @@ class VIEW3D_MT_mask(Menu):
layout.separator()
props = layout.operator("sculpt.dirty_mask", text='Dirty Mask')
props = layout.operator("sculpt.mask_from_cavity", text="Mask From Cavity")
props.use_automask_settings = False
layout.separator()
@ -5494,6 +5495,8 @@ class VIEW3D_MT_sculpt_automasking_pie(Menu):
pie.prop(sculpt, "use_automasking_face_sets", text="Face Sets")
pie.prop(sculpt, "use_automasking_boundary_edges", text="Mesh Boundary")
pie.prop(sculpt, "use_automasking_boundary_face_sets", text="Face Sets Boundary")
pie.prop(sculpt, "use_automasking_cavity", text="Cavity")
pie.prop(sculpt, "use_automasking_cavity_inverted", text="Cavity (Inverted)")
class VIEW3D_MT_sculpt_face_sets_edit_pie(Menu):

View File

@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import Menu, Panel, UIList
from bpy.types import Menu, Panel, UIList, WindowManager
from bl_ui.properties_grease_pencil_common import (
GreasePencilSculptAdvancedPanel,
GreasePencilDisplayPanel,
@ -939,7 +939,6 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
layout.operator("object.voxel_remesh", text="Remesh")
# TODO, move to space_view3d.py
class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel):
bl_context = ".sculpt_mode" # dot on purpose (access from topbar)
@ -967,10 +966,33 @@ class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel):
col.separator()
col = layout.column(heading="Auto-Masking", align=True)
col.prop(sculpt, "use_automasking_topology", text="Topology")
col.prop(sculpt, "use_automasking_face_sets", text="Face Sets")
col.prop(sculpt, "use_automasking_boundary_edges", text="Mesh Boundary")
col.prop(sculpt, "use_automasking_boundary_face_sets", text="Face Sets Boundary")
col.prop(sculpt, "use_automasking_cavity", text="Cavity")
col.prop(sculpt, "use_automasking_cavity_inverted", text="Cavity (Inverted)")
col.separator()
col.prop(sculpt.brush, "automasking_boundary_edges_propagation_steps")
if sculpt.use_automasking_cavity or sculpt.use_automasking_cavity_inverted:
col.separator()
col2 = col.column()
props = col2.operator("sculpt.mask_from_cavity", text="Mask From Cavity")
props.use_automask_settings = True
col2 = col.column()
col2.prop(sculpt, "automasking_cavity_factor", text="Cavity Factor")
col2.prop(sculpt, "automasking_cavity_blur_steps", text="Cavity Blur")
col2.prop(sculpt, "use_automasking_custom_cavity_curve", text="Use Curve")
if sculpt.use_automasking_custom_cavity_curve:
col2.template_curve_mapping(sculpt, "automasking_cavity_curve")
class VIEW3D_PT_sculpt_options_gravity(Panel, View3DPaintPanel):

View File

@ -19,13 +19,13 @@ extern "C" {
/* Blender major and minor version. */
#define BLENDER_VERSION 304
/* Blender patch version for bugfix releases. */
#define BLENDER_VERSION_PATCH 0
#define BLENDER_VERSION_PATCH 1
/** Blender release cycle stage: alpha/beta/rc/release. */
#define BLENDER_VERSION_CYCLE alpha
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 1
#define BLENDER_FILE_SUBVERSION 2
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -52,6 +52,7 @@ struct Palette;
struct PaletteColor;
struct Scene;
struct StrokeCache;
struct Sculpt;
struct SubdivCCG;
struct Tex;
struct ToolSettings;
@ -473,6 +474,11 @@ typedef struct SculptBoundary {
} twist;
} SculptBoundary;
typedef struct CavityMaskData {
float factor;
int stroke_id;
} CavityMaskData;
typedef struct SculptFakeNeighbors {
bool use_fake_neighbors;
@ -552,6 +558,9 @@ typedef struct SculptAttributePointers {
/* BMesh */
SculptAttribute *dyntopo_node_id_vertex;
SculptAttribute *dyntopo_node_id_face;
SculptAttribute *stroke_id;
SculptAttribute *cavity;
} SculptAttributePointers;
typedef struct SculptSession {
@ -743,6 +752,9 @@ typedef struct SculptSession {
*/
char *last_paint_canvas_key;
uchar stroke_id;
int last_automasking_settings_hash;
uchar last_cavity_stroke_id;
} SculptSession;
void BKE_sculptsession_free(struct Object *ob);
@ -900,6 +912,8 @@ bool BKE_paint_canvas_image_get(struct PaintModeSettings *settings,
struct ImageUser **r_image_user);
int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings,
struct Object *ob);
void BKE_sculpt_check_cavity_curves(struct Sculpt *sd);
struct CurveMapping *BKE_sculpt_default_cavity_curve(void);
#ifdef __cplusplus
}

View File

@ -72,6 +72,8 @@ static void brush_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
}
brush_dst->curve = BKE_curvemapping_copy(brush_src->curve);
brush_dst->automasking_cavity_curve = BKE_curvemapping_copy(brush_src->automasking_cavity_curve);
if (brush_src->gpencil_settings != nullptr) {
brush_dst->gpencil_settings = MEM_cnew(__func__, *(brush_src->gpencil_settings));
brush_dst->gpencil_settings->curve_sensitivity = BKE_curvemapping_copy(
@ -109,6 +111,7 @@ static void brush_free_data(ID *id)
IMB_freeImBuf(brush->icon_imbuf);
}
BKE_curvemapping_free(brush->curve);
BKE_curvemapping_free(brush->automasking_cavity_curve);
if (brush->gpencil_settings != nullptr) {
BKE_curvemapping_free(brush->gpencil_settings->curve_sensitivity);
@ -212,6 +215,10 @@ static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_addres
BKE_curvemapping_blend_write(writer, brush->curve);
}
if (brush->automasking_cavity_curve) {
BKE_curvemapping_blend_write(writer, brush->automasking_cavity_curve);
}
if (brush->gpencil_settings) {
BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings);
@ -267,6 +274,14 @@ static void brush_blend_read_data(BlendDataReader *reader, ID *id)
BKE_brush_curve_preset(brush, CURVE_PRESET_SHARP);
}
BLO_read_data_address(reader, &brush->automasking_cavity_curve);
if (brush->automasking_cavity_curve) {
BKE_curvemapping_blend_read(reader, brush->automasking_cavity_curve);
}
else {
brush->automasking_cavity_curve = BKE_sculpt_default_cavity_curve();
}
/* grease pencil */
BLO_read_data_address(reader, &brush->gpencil_settings);
if (brush->gpencil_settings != nullptr) {

View File

@ -2089,6 +2089,9 @@ void BKE_sculpt_toolsettings_data_ensure(Scene *scene)
if (!sd->paint.tile_offset[2]) {
sd->paint.tile_offset[2] = 1.0f;
}
if (!sd->automasking_cavity_curve || !sd->automasking_cavity_curve_op) {
BKE_sculpt_check_cavity_curves(sd);
}
}
static bool check_sculpt_object_deformed(Object *object, const bool for_construction)

View File

@ -114,6 +114,32 @@
#include "bmesh.h"
CurveMapping *BKE_sculpt_default_cavity_curve()
{
CurveMapping *cumap = BKE_curvemapping_add(1, 0, 0, 1, 1);
cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE;
cumap->preset = CURVE_PRESET_LINE;
BKE_curvemap_reset(cumap->cm, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_POSITIVE);
BKE_curvemapping_changed(cumap, false);
BKE_curvemapping_init(cumap);
return cumap;
}
void BKE_sculpt_check_cavity_curves(Sculpt *sd)
{
if (!sd->automasking_cavity_curve) {
sd->automasking_cavity_curve = BKE_sculpt_default_cavity_curve();
}
if (!sd->automasking_cavity_curve_op) {
sd->automasking_cavity_curve_op = BKE_sculpt_default_cavity_curve();
}
}
static void scene_init_data(ID *id)
{
Scene *scene = (Scene *)id;
@ -940,6 +966,13 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
}
if (tos->sculpt) {
BLO_write_struct(writer, Sculpt, tos->sculpt);
if (tos->sculpt->automasking_cavity_curve) {
BKE_curvemapping_blend_write(writer, tos->sculpt->automasking_cavity_curve);
}
if (tos->sculpt->automasking_cavity_curve_op) {
BKE_curvemapping_blend_write(writer, tos->sculpt->automasking_cavity_curve_op);
}
BKE_paint_blend_write(writer, &tos->sculpt->paint);
}
if (tos->uvsculpt) {
@ -1153,6 +1186,24 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
sce->toolsettings->particle.object = nullptr;
sce->toolsettings->gp_sculpt.paintcursor = nullptr;
if (sce->toolsettings->sculpt) {
BLO_read_data_address(reader, &sce->toolsettings->sculpt->automasking_cavity_curve);
BLO_read_data_address(reader, &sce->toolsettings->sculpt->automasking_cavity_curve_op);
if (sce->toolsettings->sculpt->automasking_cavity_curve) {
BKE_curvemapping_blend_read(reader, sce->toolsettings->sculpt->automasking_cavity_curve);
BKE_curvemapping_init(sce->toolsettings->sculpt->automasking_cavity_curve);
}
if (sce->toolsettings->sculpt->automasking_cavity_curve_op) {
BKE_curvemapping_blend_read(reader,
sce->toolsettings->sculpt->automasking_cavity_curve_op);
BKE_curvemapping_init(sce->toolsettings->sculpt->automasking_cavity_curve_op);
}
BKE_sculpt_check_cavity_curves(sce->toolsettings->sculpt);
}
/* Relink grease pencil interpolation curves. */
BLO_read_data_address(reader, &sce->toolsettings->gp_interpolate.custom_ipo);
if (sce->toolsettings->gp_interpolate.custom_ipo) {
@ -1740,6 +1791,18 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag)
if (ts->sculpt) {
ts->sculpt = static_cast<Sculpt *>(MEM_dupallocN(ts->sculpt));
BKE_paint_copy(&ts->sculpt->paint, &ts->sculpt->paint, flag);
if (ts->sculpt->automasking_cavity_curve) {
ts->sculpt->automasking_cavity_curve = BKE_curvemapping_copy(
ts->sculpt->automasking_cavity_curve);
BKE_curvemapping_init(ts->sculpt->automasking_cavity_curve);
}
if (ts->sculpt->automasking_cavity_curve_op) {
ts->sculpt->automasking_cavity_curve_op = BKE_curvemapping_copy(
ts->sculpt->automasking_cavity_curve_op);
BKE_curvemapping_init(ts->sculpt->automasking_cavity_curve_op);
}
}
if (ts->uvsculpt) {
ts->uvsculpt = static_cast<UvSculpt *>(MEM_dupallocN(ts->uvsculpt));
@ -1797,6 +1860,13 @@ void BKE_toolsettings_free(ToolSettings *toolsettings)
MEM_freeN(toolsettings->wpaint);
}
if (toolsettings->sculpt) {
if (toolsettings->sculpt->automasking_cavity_curve) {
BKE_curvemapping_free(toolsettings->sculpt->automasking_cavity_curve);
}
if (toolsettings->sculpt->automasking_cavity_curve_op) {
BKE_curvemapping_free(toolsettings->sculpt->automasking_cavity_curve_op);
}
BKE_paint_free(&toolsettings->sculpt->paint);
MEM_freeN(toolsettings->sculpt);
}

View File

@ -3337,6 +3337,14 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
if (!DNA_struct_elem_find(fd->filesdna, "Sculpt", "float", "automasking_cavity_factor")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->toolsettings && scene->toolsettings->sculpt) {
scene->toolsettings->sculpt->automasking_cavity_factor = 0.5f;
}
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 302, 14)) {
/* Compensate for previously wrong squared distance. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
@ -3519,6 +3527,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
BKE_main_namemap_validate_and_fix(bmain);
}
if (!MAIN_VERSION_ATLEAST(bmain, 304, 1)) {
/* Image generation information transferred to tiles. */
if (!DNA_struct_elem_find(fd->filesdna, "ImageTile", "int", "gen_x")) {
@ -3575,6 +3584,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_ATLEAST(bmain, 304, 2)) {
/* Initialize brush curves sculpt settings. */
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
brush->automasking_cavity_factor = 0.5f;
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -3364,6 +3364,8 @@ static void do_brush_action(Sculpt *sd,
/* Initialize auto-masking cache. */
if (SCULPT_is_automasking_enabled(sd, ss, brush)) {
ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob);
ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash(
ob, ss->cache->automasking);
}
/* Initialize surface smooth cache. */
if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) &&
@ -3546,6 +3548,11 @@ static void do_brush_action(Sculpt *sd,
SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor);
}
if (!SCULPT_tool_can_reuse_cavity_mask(brush->sculpt_tool) || (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) {
/* Clear cavity mask cache. */
ss->last_automasking_settings_hash = 0;
}
/* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool,
SCULPT_TOOL_CLOTH,
@ -4234,6 +4241,8 @@ static void sculpt_update_cache_invariants(
ss->cache = cache;
cache->stroke_id = ss->stroke_id;
/* Set scaling adjustment. */
max_scale = 0.0f;
for (int i = 0; i < 3; i++) {
@ -4339,6 +4348,10 @@ static void sculpt_update_cache_invariants(
cache->original = true;
}
if (SCULPT_automasking_needs_original(sd, brush)) {
cache->original = true;
}
/* Draw sharp does not need the original coordinates to produce the accumulate effect, so it
* should work the opposite way. */
if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) {
@ -5398,6 +5411,8 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f
SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd));
}
SCULPT_stroke_id_next(ob);
return true;
}
return false;
@ -5996,4 +6011,24 @@ void SCULPT_fake_neighbors_free(Object *ob)
sculpt_pose_fake_neighbors_free(ss);
}
void SCULPT_stroke_id_next(Object *ob)
{
/* Manually wrap in int32 space to avoid tripping up undefined behavior
* sanitizers.
*/
ob->sculpt->stroke_id = (uchar)(((int)ob->sculpt->stroke_id + 1) & 255);
}
void SCULPT_stroke_id_ensure(Object *ob)
{
SculptSession *ss = ob->sculpt;
if (!ss->attrs.stroke_id) {
SculptAttributeParams params = {0};
ss->attrs.stroke_id = BKE_sculpt_attribute_ensure(
ob, ATTR_DOMAIN_POINT, CD_PROP_INT8, SCULPT_ATTRIBUTE_NAME(stroke_id), &params);
}
}
/** \} */

View File

@ -7,17 +7,22 @@
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_blenlib.h"
#include "BLI_hash.h"
#include "BLI_index_range.hh"
#include "BLI_math.h"
#include "BLI_math_vec_types.hh"
#include "BLI_set.hh"
#include "BLI_task.h"
#include "BLI_vector.hh"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_brush.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
@ -47,7 +52,10 @@
#include <cmath>
#include <cstdlib>
using blender::float3;
using blender::IndexRange;
using blender::Set;
using blender::Vector;
AutomaskingCache *SCULPT_automasking_active_cache_get(SculptSession *ss)
{
@ -64,10 +72,13 @@ bool SCULPT_is_automasking_mode_enabled(const Sculpt *sd,
const Brush *br,
const eAutomasking_flag mode)
{
int automasking = sd->automasking_flags;
if (br) {
return br->automasking_flags & mode || sd->automasking_flags & mode;
automasking |= br->automasking_flags;
}
return sd->automasking_flags & mode;
return (eAutomasking_flag)automasking & mode;
}
bool SCULPT_is_automasking_enabled(const Sculpt *sd, const SculptSession *ss, const Brush *br)
@ -87,13 +98,31 @@ bool SCULPT_is_automasking_enabled(const Sculpt *sd, const SculptSession *ss, co
if (SCULPT_is_automasking_mode_enabled(sd, br, BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS)) {
return true;
}
if (SCULPT_is_automasking_mode_enabled(sd, br, BRUSH_AUTOMASKING_CAVITY_ALL)) {
return true;
}
return false;
}
static int sculpt_automasking_mode_effective_bits(const Sculpt *sculpt, const Brush *brush)
{
if (brush) {
return sculpt->automasking_flags | brush->automasking_flags;
int flags = sculpt->automasking_flags | brush->automasking_flags;
/* Check if we are using brush cavity settings. */
if (brush->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL) {
flags &= ~(BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE |
BRUSH_AUTOMASKING_CAVITY_NORMAL);
flags |= brush->automasking_flags;
}
else if (sculpt->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL) {
flags &= ~(BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE |
BRUSH_AUTOMASKING_CAVITY_NORMAL);
flags |= sculpt->automasking_flags;
}
return flags;
}
return sculpt->automasking_flags;
}
@ -105,15 +134,239 @@ static bool SCULPT_automasking_needs_factors_cache(const Sculpt *sd, const Brush
if (automasking_flags & BRUSH_AUTOMASKING_TOPOLOGY) {
return true;
}
if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_EDGES) {
return brush && brush->automasking_boundary_edges_propagation_steps != 1;
}
if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS) {
if (automasking_flags &
(BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS | BRUSH_AUTOMASKING_BOUNDARY_EDGES)) {
return brush && brush->automasking_boundary_edges_propagation_steps != 1;
}
return false;
}
static float sculpt_cavity_calc_factor(AutomaskingCache *automasking,
float factor)
{
float sign = signf(factor);
factor = fabsf(factor) * automasking->settings.cavity_factor * 50.0f;
factor = factor * sign * 0.5f + 0.5f;
CLAMP(factor, 0.0f, 1.0f);
return (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_INVERTED) ? 1.0f - factor :
factor;
}
struct CavityBlurVert {
PBVHVertRef vertex;
float dist;
int depth;
CavityBlurVert(PBVHVertRef vertex_, float dist_, int depth_)
: vertex(vertex_), dist(dist_), depth(depth_)
{
}
CavityBlurVert()
{
}
CavityBlurVert(const CavityBlurVert &b)
{
vertex = b.vertex;
dist = b.dist;
depth = b.depth;
}
};
static void sculpt_calc_blurred_cavity(SculptSession *ss,
AutomaskingCache *automasking,
int steps,
PBVHVertRef vertex)
{
float3 sno1(0.0f);
float3 sno2(0.0f);
float3 sco1(0.0f);
float3 sco2(0.0f);
float len1_sum = 0.0f, len2_sum = 0.0f;
int sco1_len = 0, sco2_len = 0;
/* Steps starts at 1, but API and user interface
* are zero-based.
*/
steps++;
Vector<CavityBlurVert, 64> queue;
Set<int64_t, 64> visit;
int start = 0, end = 0;
queue.resize(64);
CavityBlurVert initial(vertex, 0.0f, 0);
visit.add_new(vertex.i);
queue[0] = initial;
end = 1;
const float *co1 = SCULPT_vertex_co_get(ss, vertex);
while (start != end) {
CavityBlurVert &blurvert = queue[start];
PBVHVertRef v = blurvert.vertex;
start = (start + 1) % queue.size();
float3 no;
const float *co = SCULPT_vertex_co_get(ss, v);
SCULPT_vertex_normal_get(ss, v, no);
float centdist = len_v3v3(co, co1);
sco1 += co;
sno1 += no;
len1_sum += centdist;
sco1_len++;
if (blurvert.depth < steps) {
sco2 += co;
sno2 += no;
len2_sum += centdist;
sco2_len++;
}
if (blurvert.depth >= steps) {
continue;
}
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v, ni) {
PBVHVertRef v2 = ni.vertex;
if (visit.contains(v2.i)) {
continue;
}
float dist = len_v3v3(SCULPT_vertex_co_get(ss, v2), SCULPT_vertex_co_get(ss, v));
visit.add_new(v2.i);
CavityBlurVert blurvert2(v2, dist, blurvert.depth + 1);
int nextend = (end + 1) % queue.size();
if (nextend == start) {
int oldsize = queue.size();
queue.resize(queue.size() << 1);
if (end < start) {
int n = oldsize - start;
for (int i = 0; i < n; i++) {
queue[queue.size() - n + i] = queue[i + start];
}
start = queue.size() - n;
}
}
queue[end] = blurvert2;
end = (end + 1) % queue.size();
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
}
BLI_assert(sco1_len != sco2_len);
if (!sco1_len) {
sco1 = SCULPT_vertex_co_get(ss, vertex);
}
else {
sco1 /= (float)sco1_len;
len1_sum /= sco1_len;
}
if (!sco2_len) {
sco2 = SCULPT_vertex_co_get(ss, vertex);
}
else {
sco2 /= (float)sco2_len;
len2_sum /= sco2_len;
}
normalize_v3(sno1);
if (dot_v3v3(sno1, sno1) == 0.0f) {
SCULPT_vertex_normal_get(ss, vertex, sno1);
}
normalize_v3(sno2);
if (dot_v3v3(sno2, sno2) == 0.0f) {
SCULPT_vertex_normal_get(ss, vertex, sno2);
}
float3 vec = sco1 - sco2;
float factor_sum = dot_v3v3(vec, sno2) / len1_sum;
factor_sum = sculpt_cavity_calc_factor(automasking, factor_sum);
*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.cavity) = factor_sum;
*(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.stroke_id) = automasking->cavity_stroke_id;
}
int SCULPT_automasking_settings_hash(Object *ob, AutomaskingCache *automasking)
{
SculptSession *ss = ob->sculpt;
int hash;
int totvert = SCULPT_vertex_count_get(ss);
hash = BLI_hash_int(automasking->settings.flags);
hash = BLI_hash_int_2d(hash, totvert);
if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) {
hash = BLI_hash_int_2d(hash, automasking->settings.cavity_blur_steps);
hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&automasking->settings.cavity_factor));
if (automasking->settings.cavity_curve) {
CurveMap *cm = automasking->settings.cavity_curve->cm;
for (int i = 0; i < cm->totpoint; i++) {
hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&cm->curve[i].x));
hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&cm->curve[i].y));
hash = BLI_hash_int_2d(hash, (uint)cm->curve[i].flag);
hash = BLI_hash_int_2d(hash, (uint)cm->curve[i].shorty);
}
}
}
if (automasking->settings.flags & BRUSH_AUTOMASKING_FACE_SETS) {
hash = BLI_hash_int_2d(hash, automasking->settings.initial_face_set);
}
return hash;
}
static float sculpt_automasking_cavity_factor(AutomaskingCache *automasking,
SculptSession *ss,
PBVHVertRef vertex)
{
uchar stroke_id = *(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.stroke_id);
if (stroke_id != automasking->cavity_stroke_id) {
sculpt_calc_blurred_cavity(ss, automasking, automasking->settings.cavity_blur_steps, vertex);
}
float factor = *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.cavity);
bool inverted = automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_INVERTED;
if ((automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) &&
(automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_USE_CURVE)) {
factor = inverted ? 1.0f - factor : factor;
factor = BKE_curvemapping_evaluateF(automasking->settings.cavity_curve, 0, factor);
factor = inverted ? 1.0f - factor : factor;
}
return factor;
}
float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
SculptSession *ss,
PBVHVertRef vert)
@ -126,7 +379,13 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
* automasking information can't be computed in real time per vertex and needs to be
* initialized for the whole mesh when the stroke starts. */
if (ss->attrs.automasking_factor) {
return *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor);
float factor = *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor);
if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) {
factor *= sculpt_automasking_cavity_factor(automasking, ss, vert);
}
return factor;
}
if (automasking->settings.flags & BRUSH_AUTOMASKING_FACE_SETS) {
@ -147,6 +406,10 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
}
}
if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) {
return sculpt_automasking_cavity_factor(automasking, ss, vert);
}
return 1.0f;
}
@ -200,7 +463,7 @@ static void SCULPT_topology_automasking_init(Sculpt *sd, Object *ob)
Brush *brush = BKE_paint_brush(&sd->paint);
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) {
BLI_assert_msg(0, "Topology masking: pmap missing");
BLI_assert_unreachable();
return;
}
@ -328,6 +591,26 @@ static void SCULPT_automasking_cache_settings_update(AutomaskingCache *automaski
{
automasking->settings.flags = sculpt_automasking_mode_effective_bits(sd, brush);
automasking->settings.initial_face_set = SCULPT_active_face_set_get(ss);
if (brush && (brush->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL)) {
automasking->settings.cavity_curve = brush->automasking_cavity_curve;
automasking->settings.cavity_factor = brush->automasking_cavity_factor;
automasking->settings.cavity_blur_steps = brush->automasking_cavity_blur_steps;
}
else {
automasking->settings.cavity_curve = sd->automasking_cavity_curve;
automasking->settings.cavity_factor = sd->automasking_cavity_factor;
automasking->settings.cavity_blur_steps = sd->automasking_cavity_blur_steps;
}
}
bool SCULPT_tool_can_reuse_cavity_mask(int sculpt_tool)
{
return ELEM(sculpt_tool,
SCULPT_TOOL_PAINT,
SCULPT_TOOL_SMEAR,
SCULPT_TOOL_MASK,
SCULPT_TOOL_DRAW_FACE_SETS);
}
AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object *ob)
@ -344,6 +627,35 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object
SCULPT_automasking_cache_settings_update(automasking, ss, sd, brush);
SCULPT_boundary_info_ensure(ob);
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_CAVITY_ALL)) {
if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_CAVITY_USE_CURVE)) {
BKE_curvemapping_init(brush->automasking_cavity_curve);
BKE_curvemapping_init(sd->automasking_cavity_curve);
}
SCULPT_stroke_id_ensure(ob);
automasking->cavity_stroke_id = ss->stroke_id;
if (!ss->attrs.cavity) {
SculptAttributeParams params = {0};
ss->attrs.cavity = BKE_sculpt_attribute_ensure(
ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(cavity), &params);
}
/* Can we reuse the previous stroke's cavity mask? */
else if (brush && SCULPT_tool_can_reuse_cavity_mask(brush->sculpt_tool)) {
int hash = SCULPT_automasking_settings_hash(ob, automasking);
if (hash == ss->last_automasking_settings_hash) {
automasking->cavity_stroke_id = ss->last_automasking_settings_hash;
automasking->can_reuse_cavity = true;
}
}
if (!automasking->can_reuse_cavity) {
ss->last_cavity_stroke_id = ss->stroke_id;
}
}
if (!SCULPT_automasking_needs_factors_cache(sd, brush)) {
return automasking;
}
@ -385,3 +697,8 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object
return automasking;
}
bool SCULPT_automasking_needs_original(const Sculpt *sd, const Brush *brush)
{
return sculpt_automasking_mode_effective_bits(sd, brush) & BRUSH_AUTOMASKING_CAVITY_ALL;
}

View File

@ -2102,6 +2102,8 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SCULPT_stroke_id_next(ob);
/* Create and configure the Expand Cache. */
ss->expand_cache = MEM_callocN(sizeof(ExpandCache), "expand cache");
sculpt_expand_cache_initial_config_set(C, op, ss->expand_cache);

View File

@ -334,6 +334,9 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent
const bool use_automasking = SCULPT_is_automasking_enabled(sd, ss, NULL);
if (use_automasking) {
/* Increment stroke id for automasking system. */
SCULPT_stroke_id_next(ob);
/* Update the active face set manually as the paint cursor is not enabled when using the Mesh
* Filter Tool. */
float mval_fl[2] = {UNPACK2(event->mval)};

View File

@ -303,173 +303,3 @@ void SCULPT_OT_mask_filter(struct wmOperatorType *ot)
"Auto Iteration Count",
"Use a automatic number of iterations based on the number of vertices of the sculpt");
}
static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd)
{
int total = 0;
float avg[3];
zero_v3(avg);
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) {
float normalized[3];
sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.vertex), vd->co);
normalize_v3(normalized);
add_v3_v3(avg, normalized);
total++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (total > 0) {
mul_v3_fl(avg, 1.0f / total);
float dot = dot_v3v3(avg, vd->no ? vd->no : vd->fno);
float angle = max_ff(saacosf(dot), 0.0f);
return angle;
}
return 0.0f;
}
typedef struct DirtyMaskRangeData {
float min, max;
} DirtyMaskRangeData;
static void dirty_mask_compute_range_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict tls)
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
DirtyMaskRangeData *range = tls->userdata_chunk;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
float dirty_mask = neighbor_dirty_mask(ss, &vd);
range->min = min_ff(dirty_mask, range->min);
range->max = max_ff(dirty_mask, range->max);
}
BKE_pbvh_vertex_iter_end;
}
static void dirty_mask_compute_range_reduce(const void *__restrict UNUSED(userdata),
void *__restrict chunk_join,
void *__restrict chunk)
{
DirtyMaskRangeData *join = chunk_join;
DirtyMaskRangeData *range = chunk;
join->min = min_ff(range->min, join->min);
join->max = max_ff(range->max, join->max);
}
static void dirty_mask_apply_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
PBVHVertexIter vd;
const bool dirty_only = data->dirty_mask_dirty_only;
const float min = data->dirty_mask_min;
const float max = data->dirty_mask_max;
float range = max - min;
if (range < 0.0001f) {
range = 0.0f;
}
else {
range = 1.0f / range;
}
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
float dirty_mask = neighbor_dirty_mask(ss, &vd);
float mask = *vd.mask + (1.0f - ((dirty_mask - min) * range));
if (dirty_only) {
mask = fminf(mask, 0.5f) * 2.0f;
}
*vd.mask = CLAMPIS(mask, 0.0f, 1.0f);
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_update_mask(node);
}
static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op)
{
ARegion *region = CTX_wm_region(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
PBVH *pbvh = ob->sculpt->pbvh;
PBVHNode **nodes;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
int totnode;
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_vertex_random_access_ensure(ss);
if (!ob->sculpt->pmap) {
return OPERATOR_CANCELLED;
}
BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
SCULPT_undo_push_begin(ob, op);
for (int i = 0; i < totnode; i++) {
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK);
}
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = nodes,
.dirty_mask_dirty_only = RNA_boolean_get(op->ptr, "dirty_only"),
};
DirtyMaskRangeData range = {
.min = FLT_MAX,
.max = -FLT_MAX,
};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
settings.func_reduce = dirty_mask_compute_range_reduce;
settings.userdata_chunk = &range;
settings.userdata_chunk_size = sizeof(DirtyMaskRangeData);
BLI_task_parallel_range(0, totnode, &data, dirty_mask_compute_range_task_cb, &settings);
data.dirty_mask_min = range.min;
data.dirty_mask_max = range.max;
BLI_task_parallel_range(0, totnode, &data, dirty_mask_apply_task_cb, &settings);
MEM_SAFE_FREE(nodes);
BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask);
SCULPT_undo_push_end(ob);
ED_region_tag_redraw(region);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
void SCULPT_OT_dirty_mask(struct wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Dirty Mask";
ot->idname = "SCULPT_OT_dirty_mask";
ot->description = "Generates a mask based on the geometry cavity and pointiness";
/* API callbacks. */
ot->exec = sculpt_dirty_mask_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER;
/* RNA. */
RNA_def_boolean(
ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas");
}

View File

@ -681,6 +681,9 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
}
if (use_automasking) {
/* Increment stroke id for automasking system. */
SCULPT_stroke_id_next(ob);
/* Update the active face set manually as the paint cursor is not enabled when using the Mesh
* Filter Tool. */
float mval_fl[2] = {UNPACK2(event->mval)};

View File

@ -311,10 +311,6 @@ typedef struct SculptThreadedTaskData {
float *cloth_sim_initial_location;
float cloth_sim_radius;
float dirty_mask_min;
float dirty_mask_max;
bool dirty_mask_dirty_only;
/* Mask By Color Tool */
float mask_by_color_threshold;
@ -398,10 +394,16 @@ typedef struct AutomaskingSettings {
/* Flags from eAutomasking_flag. */
int flags;
int initial_face_set;
float cavity_factor;
int cavity_blur_steps;
struct CurveMapping *cavity_curve;
} AutomaskingSettings;
typedef struct AutomaskingCache {
AutomaskingSettings settings;
bool can_reuse_cavity;
uchar cavity_stroke_id;
} AutomaskingCache;
typedef struct FilterCache {
@ -633,6 +635,7 @@ typedef struct StrokeCache {
rcti previous_r; /* previous redraw rectangle */
rcti current_r; /* current redraw rectangle */
int stroke_id;
} StrokeCache;
/* -------------------------------------------------------------------- */
@ -1297,6 +1300,9 @@ float *SCULPT_boundary_automasking_init(Object *ob,
eBoundaryAutomaskMode mode,
int propagation_steps,
float *automask_factor);
bool SCULPT_automasking_needs_original(const struct Sculpt *sd, const struct Brush *brush);
int SCULPT_automasking_settings_hash(Object *ob, AutomaskingCache *automasking);
/** \} */
/* -------------------------------------------------------------------- */
@ -1587,7 +1593,6 @@ void SCULPT_OT_color_filter(struct wmOperatorType *ot);
/* Mask filter and Dirty Mask. */
void SCULPT_OT_mask_filter(struct wmOperatorType *ot);
void SCULPT_OT_dirty_mask(struct wmOperatorType *ot);
/* Mask and Face Sets Expand. */
@ -1842,6 +1847,10 @@ BLI_INLINE bool SCULPT_tool_is_face_sets(int tool)
return ELEM(tool, SCULPT_TOOL_DRAW_FACE_SETS);
}
void SCULPT_stroke_id_ensure(struct Object *ob);
void SCULPT_stroke_id_next(struct Object *ob);
bool SCULPT_tool_can_reuse_cavity_mask(int sculpt_tool);
#ifdef __cplusplus
}
#endif

View File

@ -98,6 +98,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_path.h"
#include "UI_interface.h"
#include "UI_resources.h"
@ -1002,6 +1003,252 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
1.0f);
}
typedef enum {
AUTOMASK_BAKE_MIX,
AUTOMASK_BAKE_MULTIPLY,
AUTOMASK_BAKE_DIVIDE,
AUTOMASK_BAKE_ADD,
AUTOMASK_BAKE_SUBTRACT,
} CavityBakeMixMode;
typedef struct AutomaskBakeTaskData {
SculptSession *ss;
AutomaskingCache *automasking;
PBVHNode **nodes;
CavityBakeMixMode mode;
float factor;
Object *ob;
} AutomaskBakeTaskData;
static void sculpt_bake_cavity_exec_task_cb(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
AutomaskBakeTaskData *tdata = userdata;
SculptSession *ss = tdata->ss;
PBVHNode *node = tdata->nodes[n];
PBVHVertexIter vd;
const CavityBakeMixMode mode = tdata->mode;
const float factor = tdata->factor;
SCULPT_undo_push_node(tdata->ob, node, SCULPT_UNDO_MASK);
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
float automask = SCULPT_automasking_factor_get(tdata->automasking, ss, vd.vertex);
float mask;
switch (mode) {
case AUTOMASK_BAKE_MIX:
mask = automask;
break;
case AUTOMASK_BAKE_MULTIPLY:
mask = *vd.mask * automask;
break;
break;
case AUTOMASK_BAKE_DIVIDE:
mask = automask > 0.00001f ? *vd.mask / automask : 0.0f;
break;
break;
case AUTOMASK_BAKE_ADD:
mask = *vd.mask + automask;
break;
case AUTOMASK_BAKE_SUBTRACT:
mask = *vd.mask - automask;
break;
}
mask = *vd.mask + (mask - *vd.mask) * factor;
CLAMP(mask, 0.0f, 1.0f);
*vd.mask = mask;
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_update_mask(node);
}
static int sculpt_bake_cavity_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_vertex_random_access_ensure(ss);
MultiresModifierData *mmd = BKE_sculpt_multires_active(CTX_data_scene(C), ob);
BKE_sculpt_mask_layers_ensure(ob, mmd);
SCULPT_undo_push_begin(ob, op);
CavityBakeMixMode mode = RNA_enum_get(op->ptr, "mix_mode");
float factor = RNA_float_get(op->ptr, "mix_factor");
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
AutomaskBakeTaskData tdata;
/* Set up automasking settings.
*/
Sculpt sd2 = *sd;
/* Override cavity mask settings if use_automask_settings is false. */
if (!RNA_boolean_get(op->ptr, "use_automask_settings")) {
if (RNA_boolean_get(op->ptr, "invert")) {
sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
else {
sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
if (RNA_boolean_get(op->ptr, "use_curve")) {
sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_USE_CURVE;
}
sd2.automasking_cavity_blur_steps = RNA_int_get(op->ptr, "blur_steps");
sd2.automasking_cavity_factor = RNA_float_get(op->ptr, "factor");
sd2.automasking_cavity_curve = sd->automasking_cavity_curve_op;
}
else {
sd2.automasking_flags &= BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE;
/* Ensure cavity mask is actually enabled. */
if (!(sd2.automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL)) {
sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
}
/* Create copy of brush with cleared automasking settings. */
Brush brush2 = *brush;
brush2.automasking_flags = 0;
brush2.automasking_boundary_edges_propagation_steps = 1;
brush2.automasking_cavity_curve = sd2.automasking_cavity_curve;
tdata.ob = ob;
tdata.mode = mode;
tdata.factor = factor;
tdata.ss = ss;
tdata.nodes = nodes;
tdata.automasking = SCULPT_automasking_cache_init(&sd2, &brush2, ob);
SCULPT_stroke_id_next(ob);
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &tdata, sculpt_bake_cavity_exec_task_cb, &settings);
MEM_SAFE_FREE(nodes);
SCULPT_automasking_cache_free(tdata.automasking);
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask);
SCULPT_undo_push_end(ob);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
/* Unlike other operators we do not tag the ID for update here;
* it triggers a PBVH rebuild which is too slow and ruins
* the interactivity of the tool. */
return OPERATOR_FINISHED;
}
static void cavity_bake_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
Scene *scene = CTX_data_scene(C);
Sculpt *sd = scene->toolsettings ? scene->toolsettings->sculpt : NULL;
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
bool use_curve;
if (!sd || !RNA_boolean_get(op->ptr, "use_automask_settings")) {
uiItemR(layout, op->ptr, "mix_mode", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "mix_factor", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "use_automask_settings", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "factor", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "blur_steps", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "invert", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "use_curve", 0, NULL, ICON_NONE);
use_curve = RNA_boolean_get(op->ptr, "use_curve");
}
else {
PointerRNA sculpt_ptr;
RNA_pointer_create(&scene->id, &RNA_Sculpt, sd, &sculpt_ptr);
uiItemR(layout, op->ptr, "mix_mode", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "mix_factor", 0, NULL, ICON_NONE);
uiItemR(layout, op->ptr, "use_automask_settings", 0, NULL, ICON_NONE);
use_curve = RNA_boolean_get(&sculpt_ptr, "use_automasking_custom_cavity_curve");
}
if (use_curve) {
Scene *scene = CTX_data_scene(C);
PointerRNA sculpt_ptr;
const char *curve_prop;
if (RNA_boolean_get(op->ptr, "use_automask_settings")) {
curve_prop = "automasking_cavity_curve";
}
else {
curve_prop = "automasking_cavity_curve_op";
}
if (scene->toolsettings && scene->toolsettings->sculpt) {
RNA_pointer_create(&scene->id, &RNA_Sculpt, scene->toolsettings->sculpt, &sculpt_ptr);
uiTemplateCurveMapping(layout, &sculpt_ptr, curve_prop, 'v', false, false, false, false);
}
}
}
static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Mask From Cavity";
ot->idname = "SCULPT_OT_mask_from_cavity";
ot->description = "Creates a mask based on the curvature of the surface";
ot->ui = cavity_bake_ui;
static EnumPropertyItem mix_modes[] = {
{AUTOMASK_BAKE_MIX, "MIX", ICON_NONE, "Mix", ""},
{AUTOMASK_BAKE_MULTIPLY, "MULTIPLY", ICON_NONE, "Multiply", ""},
{AUTOMASK_BAKE_DIVIDE, "DIVIDE", ICON_NONE, "Divide", ""},
{AUTOMASK_BAKE_ADD, "ADD", ICON_NONE, "Add", ""},
{AUTOMASK_BAKE_SUBTRACT, "SUBTRACT", ICON_NONE, "Subtract", ""},
{0, NULL, 0, NULL, NULL},
};
/* api callbacks */
ot->exec = sculpt_bake_cavity_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna, "mix_mode", mix_modes, AUTOMASK_BAKE_MIX, "Mode", "Mix mode");
RNA_def_float(ot->srna, "mix_factor", 1.0f, 0.0f, 5.0f, "Mix Factor", "", 0.0f, 1.0f);
RNA_def_boolean(ot->srna,
"use_automask_settings",
false,
"Use Automask Settings",
"Use default settings from Options panel in sculpt mode.");
RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 5.0f, "Cavity Factor", "The contrast of the cavity mask", 0.0f, 1.0f);
RNA_def_int(ot->srna, "blur_steps", 2, 0, 25, "Cavity Blur", "The number of times the cavity mask is blurred", 0, 25);
RNA_def_boolean(ot->srna, "use_curve", false, "Use Curve", "");
RNA_def_boolean(ot->srna, "invert", false, "Cavity (Inverted)", "");
}
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@ -1015,7 +1262,6 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_set_detail_size);
WM_operatortype_append(SCULPT_OT_mesh_filter);
WM_operatortype_append(SCULPT_OT_mask_filter);
WM_operatortype_append(SCULPT_OT_dirty_mask);
WM_operatortype_append(SCULPT_OT_mask_expand);
WM_operatortype_append(SCULPT_OT_set_pivot_position);
WM_operatortype_append(SCULPT_OT_face_sets_create);
@ -1037,4 +1283,5 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_mask_init);
WM_operatortype_append(SCULPT_OT_expand);
WM_operatortype_append(SCULPT_OT_mask_from_cavity);
}

View File

@ -195,8 +195,11 @@ static void do_paint_brush_task_cb_ex(void *__restrict userdata,
paint_color, paint_color, wet_mix_color, ss->cache->paint_brush.wet_mix);
blend_color_mix_float(color_buffer->color[vd.i], color_buffer->color[vd.i], paint_color);
/* Final mix over the original color using brush alpha. */
mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], brush->alpha);
/* Final mix over the original color using brush alpha. We apply automaking again
* at this point to avoid washing out non-binary masking modes like cavity masking.
*/
float automasking = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex);
mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], brush->alpha * automasking);
float col[4];
SCULPT_vertex_color_get(ss, vd.vertex, col);

View File

@ -91,6 +91,8 @@
.pose_ik_segments = 1, \
.hardness = 0.0f, \
.automasking_boundary_edges_propagation_steps = 1, \
.automasking_cavity_blur_steps = 0,\
.automasking_cavity_factor = 0.5f,\
\
/* A kernel radius of 1 has almost no effect (T63233). */ \
.blur_kernel_radius = 2, \

View File

@ -330,6 +330,14 @@ typedef enum eAutomasking_flag {
BRUSH_AUTOMASKING_FACE_SETS = (1 << 1),
BRUSH_AUTOMASKING_BOUNDARY_EDGES = (1 << 2),
BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS = (1 << 3),
BRUSH_AUTOMASKING_CAVITY_NORMAL = (1 << 4),
/* Note: normal and inverted are mutually exclusive,
* inverted has priority if both bits are set.
*/
BRUSH_AUTOMASKING_CAVITY_INVERTED = (1 << 5),
BRUSH_AUTOMASKING_CAVITY_ALL = (1 << 4) | (1 << 5),
BRUSH_AUTOMASKING_CAVITY_USE_CURVE = (1 << 6),
} eAutomasking_flag;
typedef enum ePaintBrush_flag {

View File

@ -388,6 +388,11 @@ typedef struct Brush {
struct BrushGpencilSettings *gpencil_settings;
struct BrushCurvesSculptSettings *curves_sculpt_settings;
int automasking_cavity_blur_steps;
float automasking_cavity_factor;
struct CurveMapping *automasking_cavity_curve;
} Brush;
/* Struct to hold palette colors for sorting. */

View File

@ -1021,8 +1021,12 @@ typedef struct Sculpt {
float constant_detail;
float detail_percent;
int automasking_cavity_blur_steps;
float automasking_cavity_factor;
char _pad[4];
struct CurveMapping *automasking_cavity_curve;
struct CurveMapping *automasking_cavity_curve_op; /* For use by operators */
struct Object *gravity_object;
} Sculpt;

View File

@ -92,6 +92,19 @@ static const EnumPropertyItem rna_enum_brush_texture_slot_map_texture_mode_items
#endif
/* clang-format off */
/* Note: we don't actually turn these into a single enum bitmask property,
* instead we construct individual boolean properties. */
const EnumPropertyItem RNA_automasking_flags[] = {
{BRUSH_AUTOMASKING_TOPOLOGY, "use_automasking_topology", 0,"Topology", "Affect only vertices connected to the active vertex under the brush"},
{BRUSH_AUTOMASKING_FACE_SETS, "use_automasking_face_sets", 0,"Face Sets", "Affect only vertices that share Face Sets with the active vertex"},
{BRUSH_AUTOMASKING_BOUNDARY_EDGES, "use_automasking_boundary_edges", 0,"Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges"},
{BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS, "use_automasking_boundary_face_sets", 0,"Face Sets Boundary Automasking", "Do not affect vertices that belong to a Face Set boundary"},
{BRUSH_AUTOMASKING_CAVITY_NORMAL, "use_automasking_cavity", 0,"Cavity Mask", "Do not affect vertices on peaks, based on the surface curvature"},
{BRUSH_AUTOMASKING_CAVITY_INVERTED, "use_automasking_cavity_inverted", 0,"Inverted Cavity Mask", "Do not affect vertices within crevices, based on the surface curvature"},
{BRUSH_AUTOMASKING_CAVITY_USE_CURVE, "use_automasking_custom_cavity_curve", 0,"Custom Cavity Curve", "Use custom curve"},
{0, NULL, 0, NULL, NULL}
};
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", ""},
@ -1084,6 +1097,32 @@ static const EnumPropertyItem *rna_BrushTextureSlot_map_mode_itemf(bContext *C,
# undef rna_enum_brush_texture_slot_map_sculpt_mode_items
}
static void rna_Brush_automasking_invert_cavity_set(PointerRNA *ptr, bool val)
{
Brush *brush = (Brush *)ptr->data;
if (val) {
brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL;
brush->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
else {
brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
}
static void rna_Brush_automasking_cavity_set(PointerRNA *ptr, bool val)
{
Brush *brush = (Brush *)ptr->data;
if (val) {
brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED;
brush->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
else {
brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
}
#else
static void rna_def_brush_texture_slot(BlenderRNA *brna)
@ -3192,32 +3231,44 @@ static void rna_def_brush(BlenderRNA *brna)
"When locked keep using the plane origin of surface where stroke was initiated");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_automasking_topology", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_TOPOLOGY);
RNA_def_property_ui_text(prop,
"Topology Auto-Masking",
"Affect only vertices connected to the active vertex under the brush");
const EnumPropertyItem *entry = RNA_automasking_flags;
do {
prop = RNA_def_property(srna, entry->identifier, PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", entry->value);
RNA_def_property_ui_text(prop, entry->name, entry->description);
if (entry->value == BRUSH_AUTOMASKING_CAVITY_NORMAL) {
RNA_def_property_boolean_funcs(prop, NULL, "rna_Brush_automasking_cavity_set");
}
else if (entry->value == BRUSH_AUTOMASKING_CAVITY_INVERTED) {
RNA_def_property_boolean_funcs(prop, NULL, "rna_Brush_automasking_invert_cavity_set");
}
RNA_def_property_update(prop, 0, "rna_Brush_update");
} while ((++entry)->identifier);
prop = RNA_def_property(srna, "automasking_cavity_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "automasking_cavity_factor");
RNA_def_property_ui_text(prop, "Cavity Factor", "The contrast of the cavity mask");
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_range(prop, 0.0f, 5.0f);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_automasking_face_sets", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_FACE_SETS);
RNA_def_property_ui_text(prop,
"Face Sets Auto-Masking",
"Affect only vertices that share Face Sets with the active vertex");
prop = RNA_def_property(srna, "automasking_cavity_blur_steps", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "automasking_cavity_blur_steps");
RNA_def_property_ui_text(prop, "Blur Steps", "The number of times the cavity mask is blurred.");
RNA_def_property_range(prop, 0.0f, 25.0f);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_automasking_boundary_edges", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_EDGES);
RNA_def_property_ui_text(
prop, "Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_automasking_boundary_face_sets", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS);
RNA_def_property_ui_text(prop,
"Face Sets Boundary Automasking",
"Do not affect vertices that belong to a Face Set boundary");
prop = RNA_def_property(srna, "automasking_cavity_curve", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve");
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_scene_spacing", PROP_ENUM, PROP_NONE);

View File

@ -24,7 +24,7 @@ struct AssetLibraryReference;
struct FreestyleSettings;
struct ID;
struct IDOverrideLibrary;
struct IDOverrideLibraryPropertyOperation;
struct IDOverrideLibraryenOperation;
struct IDProperty;
struct Main;
struct Object;

View File

@ -9,6 +9,7 @@
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
@ -33,6 +34,8 @@
#include "bmesh.h"
extern const EnumPropertyItem RNA_automasking_flags[];
const EnumPropertyItem rna_enum_particle_edit_hair_brush_items[] = {
{PE_BRUSH_COMB, "COMB", 0, "Comb", "Comb hairs"},
{PE_BRUSH_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth hairs"},
@ -598,6 +601,31 @@ static char *rna_GPencilSculptGuide_path(const PointerRNA *UNUSED(ptr))
return BLI_strdup("tool_settings.gpencil_sculpt.guide");
}
static void rna_Sculpt_automasking_invert_cavity_set(PointerRNA *ptr, bool val)
{
Sculpt *sd = (Sculpt *)ptr->data;
if (val) {
sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL;
sd->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
else {
sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
}
static void rna_Sculpt_automasking_cavity_set(PointerRNA *ptr, bool val)
{
Sculpt *sd = (Sculpt *)ptr->data;
if (val) {
sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED;
sd->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
else {
sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
}
#else
static void rna_def_paint_curve(BlenderRNA *brna)
@ -883,32 +911,47 @@ static void rna_def_sculpt(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_update");
prop = RNA_def_property(srna, "use_automasking_topology", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_TOPOLOGY);
RNA_def_property_ui_text(prop,
"Topology Auto-Masking",
"Affect only vertices connected to the active vertex under the brush");
const EnumPropertyItem *entry = RNA_automasking_flags;
do {
prop = RNA_def_property(srna, entry->identifier, PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", entry->value);
RNA_def_property_ui_text(prop, entry->name, entry->description);
if (entry->value == BRUSH_AUTOMASKING_CAVITY_NORMAL) {
RNA_def_property_boolean_funcs(prop, NULL, "rna_Sculpt_automasking_cavity_set");
}
else if (entry->value == BRUSH_AUTOMASKING_CAVITY_INVERTED) {
RNA_def_property_boolean_funcs(prop, NULL, "rna_Sculpt_automasking_invert_cavity_set");
}
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
} while ((++entry)->identifier);
prop = RNA_def_property(srna, "automasking_cavity_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "automasking_cavity_factor");
RNA_def_property_ui_text(prop, "Cavity Factor", "The contrast of the cavity mask");
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_range(prop, 0.0f, 5.0f);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "use_automasking_face_sets", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_FACE_SETS);
RNA_def_property_ui_text(prop,
"Face Sets Auto-Masking",
"Affect only vertices that share Face Sets with the active vertex");
prop = RNA_def_property(srna, "automasking_cavity_blur_steps", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "automasking_cavity_blur_steps");
RNA_def_property_ui_text(prop, "Blur Steps", "The number of times the cavity mask is blurred");
RNA_def_property_range(prop, 0.0f, 25.0f);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "use_automasking_boundary_edges", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_EDGES);
RNA_def_property_ui_text(
prop, "Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges");
prop = RNA_def_property(srna, "automasking_cavity_curve", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve");
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "use_automasking_boundary_face_sets", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS);
RNA_def_property_ui_text(prop,
"Face Sets Boundary Auto-Masking",
"Do not affect vertices that belong to a Face Set boundary");
prop = RNA_def_property(srna, "automasking_cavity_curve_op", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve_op");
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "symmetrize_direction", PROP_ENUM, PROP_NONE);

View File

@ -83,7 +83,7 @@ op_blacklist = (
"object.voxel_remesh",
"mesh.paint_mask_slice",
"paint.mask_flood_fill",
"sculpt.dirty_mask",
"sculpt.mask_from_cavity",
# TODO: use empty temp dir to avoid behavior depending on local setup.
"view3d.pastebuffer",
# Needs active window.