Sculpt Face Sets
authorPablo Dobarro <pablodp606@gmail.com>
Thu, 5 Mar 2020 13:53:23 +0000 (14:53 +0100)
committerPablo Dobarro <pablodp606@gmail.com>
Thu, 5 Mar 2020 20:07:20 +0000 (21:07 +0100)
Face Sets are the new system to control the visibility state of the mesh in sculpt and paint modes. They are designed to work in modes where brushes are the primary way of interaction and they provide much more control when working with meshes with complex shapes and overlapping surfaces.

This initial commit includes:
- Sculpt Face Sets data structures and PBVH rendering.
- Face Set overlay and opacity controls.
- Sculpt Undo support.
- Remesher reprojection support. The visibility state of the mesh is also preserved when remeshing.
- Automasking and Mesh filter support.
- Mask expand operator mode to expand Face Sets (Shift + W) and flood fill areas by connectivity (press Ctrl while expanding).
- Sculpt Mode Face Sets and Visibility API.
- Sculpt Face Sets creation and visibility management operators.
- Operator to randomize the Face Sets colors.
- Draw Face Sets brush tool to create and edit the Face Sets. Drawing on the mesh creates a new Face Set. Pressing Ctrl before drawing modifies the Face Set under the brush at the beginning of the stroke.
- Updated keymap and menu to work with Face Sets from Sculpt Mode (H to toggle visibility, Alt + H to show all, Shit + H to hide).
- Pie menu on the W key with Face common Sets operations.

Know limitations:
- Multires support. The Face Sets and Visibility API needs to be implemented for Multires.

Reviewed By: jbakker, #user_interface, Severin

Differential Revision: https://developer.blender.org/D6070

38 files changed:
release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/startup/bl_ui/properties_data_mesh.py
release/scripts/startup/bl_ui/properties_paint_common.py
release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
release/scripts/startup/bl_ui/space_view3d.py
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/BKE_mesh_remesh_voxel.h
source/blender/blenkernel/BKE_paint.h
source/blender/blenkernel/BKE_pbvh.h
source/blender/blenkernel/intern/brush.c
source/blender/blenkernel/intern/customdata.c
source/blender/blenkernel/intern/mesh.c
source/blender/blenkernel/intern/mesh_remesh_voxel.c
source/blender/blenkernel/intern/paint.c
source/blender/blenkernel/intern/pbvh.c
source/blender/blenkernel/intern/pbvh_intern.h
source/blender/blenloader/intern/versioning_280.c
source/blender/blenloader/intern/writefile.c
source/blender/draw/engines/overlay/overlay_sculpt.c
source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl
source/blender/draw/intern/draw_manager_data.c
source/blender/editors/object/object_remesh.c
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_intern.h
source/blender/editors/sculpt_paint/sculpt_undo.c
source/blender/gpu/GPU_buffers.h
source/blender/gpu/intern/gpu_buffers.c
source/blender/makesdna/DNA_brush_types.h
source/blender/makesdna/DNA_customdata_types.h
source/blender/makesdna/DNA_mesh_defaults.h
source/blender/makesdna/DNA_mesh_types.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesdna/DNA_view3d_defaults.h
source/blender/makesdna/DNA_view3d_types.h
source/blender/makesrna/intern/rna_brush.c
source/blender/makesrna/intern/rna_mesh.c
source/blender/makesrna/intern/rna_sculpt_paint.c
source/blender/makesrna/intern/rna_space.c

index bd381666eda4b125858f93545266b7d1509d518d..bd986e2bc95eba934a08232861da5925e6e206b8 100644 (file)
@@ -3901,12 +3901,14 @@ def km_sculpt(params):
         ("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
          {"properties": [("mode", 'SMOOTH')]}),
         # Partial Visibility Show/hide
-        ("paint.hide_show", {"type": 'H', "value": 'PRESS', "shift": True},
-         {"properties": [("action", 'SHOW'), ("area", 'INSIDE')]}),
-        ("paint.hide_show", {"type": 'H', "value": 'PRESS'},
-         {"properties": [("action", 'HIDE'), ("area", 'INSIDE')]}),
-        ("paint.hide_show", {"type": 'H', "value": 'PRESS', "alt": True},
-         {"properties": [("action", 'SHOW'), ("area", 'ALL')]}),
+        ("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS'},
+         {"properties": [("mode", 'TOGGLE')]}),
+        ("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS', "shift": True},
+         {"properties": [("mode", 'HIDE_ACTIVE')]}),
+        ("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS', "alt": True},
+         {"properties": [("mode", 'SHOW_ALL')]}),
+        ("sculpt.mask_expand", {"type": 'W', "value": 'PRESS', "shift": True},
+         {"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", False), ("smooth_iterations", 0), ("create_face_set", True)]}),
         # Subdivision levels
         *_template_items_object_subdivision_set(),
         ("object.subdivision_set", {"type": 'PAGE_UP', "value": 'PRESS'},
@@ -3922,9 +3924,9 @@ def km_sculpt(params):
         ("wm.context_toggle", {"type": 'M', "value": 'PRESS', "ctrl": True},
          {"properties": [("data_path", 'scene.tool_settings.sculpt.show_mask')]}),
         ("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True},
-         {"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", True), ("smooth_iterations", 2)]}),
+         {"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", True), ("smooth_iterations", 2), ("create_face_set", False)]}),
         ("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True, 'alt': True},
-         {"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0)]}),
+         {"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0), ("create_face_set", False)]}),
         # Dynamic topology
         ("sculpt.dynamic_topology_toggle", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
         ("sculpt.set_detail_size", {"type": 'D', "value": 'PRESS', "shift": True}, None),
@@ -3980,6 +3982,7 @@ def km_sculpt(params):
          {"properties": [("data_path", 'tool_settings.sculpt.brush.use_smooth_stroke')]}),
         op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
         op_menu_pie("VIEW3D_MT_sculpt_mask_edit_pie", {"type" : 'A', "value": 'PRESS'}),
+        op_menu_pie("VIEW3D_MT_sculpt_face_sets_edit_pie", {"type" : 'W', "value": 'PRESS'}),
         *_template_items_context_panel("VIEW3D_PT_sculpt_context_menu", params.context_menu_event),
     ])
 
index d6aa986613de2d5018e876ecace691e09fec7a0f..347c41b23fb8f231494a7fb217a7855463da5528 100644 (file)
@@ -487,6 +487,7 @@ class DATA_PT_remesh(MeshButtonsPanel, Panel):
             col.prop(mesh, "use_remesh_smooth_normals")
             col.prop(mesh, "use_remesh_preserve_volume")
             col.prop(mesh, "use_remesh_preserve_paint_mask")
+            col.prop(mesh, "remesh_preserve_sculpt_face_sets")
             col.operator("object.voxel_remesh", text="Voxel Remesh")
         else:
             col.operator("object.quadriflow_remesh", text="QuadriFlow Remesh")
index 83455ae4af2ec9d6d143c684a950734eca0b534a..f1f6e9898b123e85ecf6a357c9b10496703d663a 100644 (file)
@@ -807,6 +807,9 @@ def brush_settings_advanced(layout, context, brush, popover=False):
         # topology automasking
         layout.prop(brush, "use_automasking_topology")
 
+        # face masks automasking
+        layout.prop(brush, "use_automasking_face_sets")
+
         # sculpt plane settings
         if capabilities.has_sculpt_plane:
             layout.prop(brush, "sculpt_plane")
index 2c79ceb5763987aa91a8161b0447f5faf1afd543..63fb72a71bdb746d168da2a1352880aa2b63a8de 100644 (file)
@@ -1030,6 +1030,7 @@ class _defs_sculpt:
             layout.prop(props, "type", expand=False)
             layout.prop(props, "strength")
             layout.prop(props, "deform_axis")
+            layout.prop(props, "use_face_sets")
 
         return dict(
             idname="builtin.mesh_filter",
index 8607c0c43b3b5ff34dff59b038dbef672ca2cdba..078ad96778979b6935fce84b1f35a72cef9af26a 100644 (file)
@@ -878,6 +878,7 @@ class VIEW3D_MT_editor_menus(Menu):
                 layout.menu("VIEW3D_MT_%s" % mode_string.lower())
             if mode_string == 'SCULPT':
                 layout.menu("VIEW3D_MT_mask")
+                layout.menu("VIEW3D_MT_face_sets")
 
         else:
             layout.menu("VIEW3D_MT_object")
@@ -2996,12 +2997,14 @@ class VIEW3D_MT_mask(Menu):
         props.keep_previous_mask = False
         props.invert = True
         props.smooth_iterations = 2
+        props.create_face_set = False
 
         props = layout.operator("sculpt.mask_expand", text="Expand Mask By Curvature")
         props.use_normals = True
         props.keep_previous_mask = True
         props.invert = False
         props.smooth_iterations = 0
+        props.create_face_set = False
 
         layout.separator()
 
@@ -3020,6 +3023,31 @@ class VIEW3D_MT_mask(Menu):
 
         props = layout.operator("sculpt.dirty_mask", text='Dirty Mask')
 
+class VIEW3D_MT_face_sets(Menu):
+    bl_label = "Face Sets"
+
+    def draw(self, _context):
+        layout = self.layout
+
+
+        op = layout.operator("sculpt.face_sets_create", text='Face Set From Masked')
+        op.mode = 'MASKED'
+
+        op = layout.operator("sculpt.face_sets_create", text='Face Set From Visible')
+        op.mode = 'VISIBLE'
+
+        layout.separator()
+
+        op = layout.operator("sculpt.face_set_change_visibility", text='Invert Visible Face Sets')
+        op.mode = 'INVERT'
+
+        op = layout.operator("sculpt.face_set_change_visibility", text='Show All Face Sets')
+        op.mode = 'SHOW_ALL'
+
+        layout.separator()
+
+        op = layout.operator("sculpt.face_sets_randomize_colors", text='Randomize Colors')
+
 
 class VIEW3D_MT_sculpt_set_pivot(Menu):
     bl_label = "Sculpt Set Pivot"
@@ -5037,6 +5065,24 @@ class VIEW3D_MT_sculpt_mask_edit_pie(Menu):
         op.filter_type = 'CONTRAST_DECREASE'
         op.auto_iteration_count = False
 
+class VIEW3D_MT_sculpt_face_sets_edit_pie(Menu):
+    bl_label = "Face Sets Edit"
+
+    def draw(self, _context):
+        layout = self.layout
+        pie = layout.menu_pie()
+
+        op = pie.operator("sculpt.face_sets_create", text='Face Set From Masked')
+        op.mode = 'MASKED'
+
+        op = pie.operator("sculpt.face_sets_create", text='Face Set From Visible')
+        op.mode = 'VISIBLE'
+
+        op = pie.operator("sculpt.face_set_change_visibility", text='Invert Visible')
+        op.mode = 'INVERT'
+
+        op = pie.operator("sculpt.face_set_change_visibility", text='Show All')
+        op.mode = 'SHOW_ALL'
 
 class VIEW3D_MT_wpaint_vgroup_lock_pie(Menu):
     bl_label = "Vertex Group Locks"
@@ -6121,6 +6167,12 @@ class VIEW3D_PT_overlay_sculpt(Panel):
         sub.active = sculpt.show_mask
         sub.prop(overlay, "sculpt_mode_mask_opacity", text="Mask")
 
+        row = layout.row(align=True)
+        row.prop(sculpt, "show_face_sets", text="")
+        sub = row.row()
+        sub.active = sculpt.show_face_sets
+        row.prop(overlay, "sculpt_mode_face_sets_opacity", text="Face Sets")
+
 
 class VIEW3D_PT_overlay_pose(Panel):
     bl_space_type = 'VIEW_3D'
@@ -7064,6 +7116,7 @@ classes = (
     VIEW3D_MT_sculpt,
     VIEW3D_MT_sculpt_set_pivot,
     VIEW3D_MT_mask,
+    VIEW3D_MT_face_sets,
     VIEW3D_MT_particle,
     VIEW3D_MT_particle_context_menu,
     VIEW3D_MT_particle_showhide,
@@ -7148,6 +7201,7 @@ classes = (
     VIEW3D_MT_proportional_editing_falloff_pie,
     VIEW3D_MT_sculpt_mask_edit_pie,
     VIEW3D_MT_wpaint_vgroup_lock_pie,
+    VIEW3D_MT_sculpt_face_sets_edit_pie,
     VIEW3D_PT_active_tool,
     VIEW3D_PT_active_tool_duplicate,
     VIEW3D_PT_view3d_properties,
index 9c8378438c7d0e64323a3a722dea97f857e285a4..1990e9a7260c4a7b9404189c8f0001ee26392509 100644 (file)
@@ -846,6 +846,7 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
         col.prop(mesh, "use_remesh_smooth_normals")
         col.prop(mesh, "use_remesh_preserve_volume")
         col.prop(mesh, "use_remesh_preserve_paint_mask")
+        col.prop(mesh, "use_remesh_preserve_sculpt_face_sets")
         col.operator("object.voxel_remesh", text="Remesh")
 
 
index 794b8dca4bcd91d398fc6e950becf7c2d9d5630a..b63f9a9814b2d4621e39d17e9c81f1addf6ba816 100644 (file)
@@ -60,6 +60,7 @@ struct Mesh *BKE_mesh_remesh_quadriflow_to_mesh_nomain(struct Mesh *mesh,
 
 /* Data reprojection functions */
 void BKE_mesh_remesh_reproject_paint_mask(struct Mesh *target, struct Mesh *source);
+void BKE_remesh_reproject_sculpt_face_sets(struct Mesh *target, struct Mesh *source);
 
 #ifdef __cplusplus
 }
index 016012d7288f5d8fc36c6861243bf6a46aec9956..ceb48783e203d05b5bf6f5bc561d5c521ca4eb42 100644 (file)
@@ -218,6 +218,8 @@ void BKE_paint_toolslots_brush_update(struct Paint *paint);
 void BKE_paint_toolslots_brush_validate(struct Main *bmain, struct Paint *paint);
 struct Brush *BKE_paint_toolslots_brush_get(struct Paint *paint, int slot_index);
 
+#define SCULPT_FACE_SET_NONE 0
+
 /* Used for both vertex color and weight paint */
 struct SculptVertexPaintGeomMap {
   int *vert_map_mem;
@@ -290,6 +292,9 @@ typedef struct SculptSession {
   struct MeshElemMap *pmap;
   int *pmap_mem;
 
+  /* Mesh Face Sets */
+  int *face_sets;
+
   /* BMesh for dynamic topology sculpting */
   struct BMesh *bm;
   int cd_vert_node_offset;
@@ -304,6 +309,7 @@ typedef struct SculptSession {
   /* PBVH acceleration structure */
   struct PBVH *pbvh;
   bool show_mask;
+  bool show_face_sets;
 
   /* Painting on deformed mesh */
   bool deform_modifiers_active; /* object is deformed with some modifiers */
index 3971b248a2e24b35b9071ec1edfc3f705cc428d3..6097fab814fb7d7b7ee6c9d97b9e06b4970d123e 100644 (file)
@@ -102,6 +102,7 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
                          int totvert,
                          struct CustomData *vdata,
                          struct CustomData *ldata,
+                         struct CustomData *pdata,
                          const struct MLoopTri *looptri,
                          int looptri_num);
 void BKE_pbvh_build_grids(PBVH *bvh,
@@ -244,10 +245,10 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *bvh,
 
 void BKE_pbvh_node_mark_update(PBVHNode *node);
 void BKE_pbvh_node_mark_update_mask(PBVHNode *node);
+void BKE_pbvh_node_mark_update_visibility(PBVHNode *node);
 void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node);
 void BKE_pbvh_node_mark_redraw(PBVHNode *node);
 void BKE_pbvh_node_mark_normals_update(PBVHNode *node);
-void BKE_pbvh_node_mark_visibility_update(PBVHNode *node);
 void BKE_pbvh_node_mark_topology_update(PBVHNode *node);
 void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden);
 void BKE_pbvh_node_fully_masked_set(PBVHNode *node, int fully_masked);
@@ -298,6 +299,8 @@ void BKE_pbvh_grids_update(PBVH *bvh,
                            struct DMFlagMat *flagmats,
                            unsigned int **grid_hidden);
 
+void BKE_pbvh_face_sets_color_seed_set(PBVH *bvh, int seed);
+
 /* Layer displacement */
 
 /* Get the node's displacement layer, creating it if necessary */
@@ -361,6 +364,7 @@ typedef struct PBVHVertexIter {
   short *no;
   float *fno;
   float *mask;
+  bool visible;
 } PBVHVertexIter;
 
 void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mode);
@@ -390,6 +394,7 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
           vi.mask = vi.key.has_mask ? CCG_elem_mask(&vi.key, vi.grid) : NULL; \
           vi.grid = CCG_elem_next(&vi.key, vi.grid); \
           vi.index++; \
+          vi.visible = true; \
           if (vi.gh) { \
             if (BLI_BITMAP_TEST(vi.gh, vi.gy * vi.gridsize + vi.gx)) \
               continue; \
@@ -397,7 +402,8 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
         } \
         else if (vi.mverts) { \
           vi.mvert = &vi.mverts[vi.vert_indices[vi.gx]]; \
-          if (mode == PBVH_ITER_UNIQUE && vi.mvert->flag & ME_HIDE) \
+          vi.visible = !(vi.mvert->flag & ME_HIDE); \
+          if (mode == PBVH_ITER_UNIQUE && !vi.visible) \
             continue; \
           vi.co = vi.mvert->co; \
           vi.no = vi.mvert->no; \
@@ -414,7 +420,8 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
             vi.bm_vert = BLI_gsetIterator_getKey(&vi.bm_other_verts); \
             BLI_gsetIterator_step(&vi.bm_other_verts); \
           } \
-          if (mode == PBVH_ITER_UNIQUE && BM_elem_flag_test(vi.bm_vert, BM_ELEM_HIDDEN)) \
+          vi.visible = !BM_elem_flag_test_bool(vi.bm_vert, BM_ELEM_HIDDEN); \
+          if (mode == PBVH_ITER_UNIQUE && !vi.visible) \
             continue; \
           vi.co = vi.bm_vert->co; \
           vi.fno = vi.bm_vert->no; \
@@ -445,6 +452,9 @@ bool BKE_pbvh_node_vert_update_check_any(PBVH *bvh, PBVHNode *node);
 bool pbvh_has_mask(PBVH *bvh);
 void pbvh_show_mask_set(PBVH *bvh, bool show_mask);
 
+bool pbvh_has_face_sets(PBVH *bvh);
+void pbvh_show_face_sets_set(PBVH *bvh, bool show_face_sets);
+
 /* Parallelization */
 typedef void (*PBVHParallelRangeFunc)(void *__restrict userdata,
                                       const int iter,
index 8abad2d541d1d91e7d4d891b9f1c75fe7ed3a701..a1725197a36e388132ac31eb29ece4ae12183f03 100644 (file)
@@ -1010,6 +1010,12 @@ void BKE_brush_sculpt_reset(Brush *br)
       br->flag &= ~BRUSH_SPACE;
       br->flag &= ~BRUSH_SPACE_ATTEN;
       break;
+    case SCULPT_TOOL_DRAW_FACE_SETS:
+      br->alpha = 0.5f;
+      br->flag &= ~BRUSH_ALPHA_PRESSURE;
+      br->flag &= ~BRUSH_SPACE;
+      br->flag &= ~BRUSH_SPACE_ATTEN;
+      break;
     case SCULPT_TOOL_GRAB:
       br->alpha = 0.4f;
       br->size = 75;
@@ -1085,6 +1091,7 @@ void BKE_brush_sculpt_reset(Brush *br)
 
     case SCULPT_TOOL_SIMPLIFY:
     case SCULPT_TOOL_MASK:
+    case SCULPT_TOOL_DRAW_FACE_SETS:
       br->add_col[0] = 0.750000;
       br->add_col[1] = 0.750000;
       br->add_col[2] = 0.750000;
index 93db44ce60ad29680d2881fb432a893c802ecbc7..33707d3f18d8fd573390767b0c236b3375ded51c 100644 (file)
@@ -1621,6 +1621,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
     {sizeof(short[4][3]), "", 0, NULL, NULL, NULL, NULL, layerSwap_flnor, NULL},
     /* 41: CD_CUSTOMLOOPNORMAL */
     {sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL},
+    /* 42: CD_SCULPT_FACE_SETS */
+    {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, NULL},
 };
 
 static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
@@ -1668,6 +1670,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
     /* 39-41 */ "CDMLoopTangent",
     "CDTessLoopNormal",
     "CDCustomLoopNormal",
+    "CDSculptFaceGroups",
 };
 
 const CustomData_MeshMasks CD_MASK_BAREMESH = {
@@ -1692,7 +1695,7 @@ const CustomData_MeshMasks CD_MASK_MESH = {
     .lmask = (CD_MASK_MLOOP | CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL |
               CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
     .pmask = (CD_MASK_MPOLY | CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE |
-              CD_MASK_GENERIC_DATA),
+              CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
 };
 const CustomData_MeshMasks CD_MASK_EDITMESH = {
     .vmask = (CD_MASK_MDEFORMVERT | CD_MASK_PAINT_MASK | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY |
@@ -1701,7 +1704,7 @@ const CustomData_MeshMasks CD_MASK_EDITMESH = {
     .fmask = 0,
     .lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL |
               CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
-    .pmask = (CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA),
+    .pmask = (CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
 };
 const CustomData_MeshMasks CD_MASK_DERIVEDMESH = {
     .vmask = (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN |
@@ -1712,7 +1715,7 @@ const CustomData_MeshMasks CD_MASK_DERIVEDMESH = {
               CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP |
               CD_MASK_GENERIC_DATA), /* XXX MISSING CD_MASK_MLOOPTANGENT ? */
     .pmask = (CD_MASK_ORIGINDEX | CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP |
-              CD_MASK_GENERIC_DATA),
+              CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
 };
 const CustomData_MeshMasks CD_MASK_BMESH = {
     .vmask = (CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY |
@@ -1721,7 +1724,8 @@ const CustomData_MeshMasks CD_MASK_BMESH = {
     .fmask = 0,
     .lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL |
               CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
-    .pmask = (CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA),
+    .pmask = (CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA |
+              CD_MASK_SCULPT_FACE_SETS),
 };
 /**
  * cover values copied by #BKE_mesh_loops_to_tessdata
@@ -1750,7 +1754,8 @@ const CustomData_MeshMasks CD_MASK_EVERYTHING = {
               CD_MASK_MLOOPTANGENT | CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP |
               CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
     .pmask = (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL |
-              CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_GENERIC_DATA),
+              CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_GENERIC_DATA |
+              CD_MASK_SCULPT_FACE_SETS),
 };
 
 static const LayerTypeInfo *layerType_getInfo(int type)
@@ -4221,7 +4226,7 @@ bool CustomData_verify_versions(struct CustomData *data, int index)
     /* 0 structnum is used in writing code to tag layer types that should not be written. */
     else if (typeInfo->structnum == 0 &&
              /* XXX Not sure why those three are exception, maybe that should be fixed? */
-             !ELEM(layer->type, CD_PAINT_MASK, CD_FACEMAP, CD_MTEXPOLY)) {
+             !ELEM(layer->type, CD_PAINT_MASK, CD_FACEMAP, CD_MTEXPOLY, CD_SCULPT_FACE_SETS)) {
       keeplayer = false;
       CLOG_WARN(&LOG, ".blend file read: removing a data layer that should not have been written");
     }
index 51f37254d8fff4dbd5cb53e7d704b014ebe57136..81ba48bd1062060eee5c21e9a3d87baf2f3ea6f5 100644 (file)
@@ -31,6 +31,8 @@
 
 #include "BLI_utildefines.h"
 #include "BLI_bitmap.h"
+#include "BLI_ghash.h"
+#include "BLI_hash.h"
 #include "BLI_math.h"
 #include "BLI_linklist.h"
 #include "BLI_memarena.h"
@@ -51,6 +53,8 @@
 #include "BKE_object.h"
 #include "BKE_editmesh.h"
 
+#include "PIL_time.h"
+
 #include "DEG_depsgraph.h"
 #include "DEG_depsgraph_query.h"
 
@@ -542,6 +546,8 @@ void BKE_mesh_init(Mesh *me)
   CustomData_reset(&me->ldata);
 
   BKE_mesh_runtime_reset(me);
+
+  me->face_sets_color_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX);
 }
 
 Mesh *BKE_mesh_add(Main *bmain, const char *name)
@@ -671,6 +677,8 @@ void BKE_mesh_copy_settings(Mesh *me_dst, const Mesh *me_src)
   me_dst->remesh_voxel_adaptivity = me_src->remesh_voxel_adaptivity;
   me_dst->remesh_mode = me_src->remesh_mode;
 
+  me_dst->face_sets_color_seed = me_src->face_sets_color_seed;
+
   /* Copy texture space. */
   me_dst->texflag = me_src->texflag;
   copy_v3_v3(me_dst->loc, me_src->loc);
index afc380fd369d7aeafb9527aa65d4b8628e8f9205..983c19857ddce01e251be83021d25aa1f253f612 100644 (file)
@@ -356,6 +356,55 @@ void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
   free_bvhtree_from_mesh(&bvhtree);
 }
 
+void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, Mesh *source)
+{
+  BVHTreeFromMesh bvhtree = {
+      .nearest_callback = NULL,
+  };
+
+  const MPoly *target_polys = CustomData_get_layer(&target->pdata, CD_MPOLY);
+  const MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
+  const MLoop *target_loops = CustomData_get_layer(&target->ldata, CD_MLOOP);
+
+  int *target_face_sets;
+  if (CustomData_has_layer(&target->pdata, CD_SCULPT_FACE_SETS)) {
+    target_face_sets = CustomData_get_layer(&target->pdata, CD_SCULPT_FACE_SETS);
+  }
+  else {
+    target_face_sets = CustomData_add_layer(
+        &target->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, target->totpoly);
+  }
+
+  int *source_face_sets;
+  if (CustomData_has_layer(&source->pdata, CD_SCULPT_FACE_SETS)) {
+    source_face_sets = CustomData_get_layer(&source->pdata, CD_SCULPT_FACE_SETS);
+  }
+  else {
+    source_face_sets = CustomData_add_layer(
+        &source->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, source->totpoly);
+  }
+
+  const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(source);
+  BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_LOOPTRI, 2);
+
+  for (int i = 0; i < target->totpoly; i++) {
+    float from_co[3];
+    BVHTreeNearest nearest;
+    nearest.index = -1;
+    nearest.dist_sq = FLT_MAX;
+    const MPoly *mpoly = &target_polys[i];
+    BKE_mesh_calc_poly_center(mpoly, &target_loops[mpoly->loopstart], target_verts, from_co);
+    BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+    if (nearest.index != -1) {
+      target_face_sets[i] = source_face_sets[looptri[nearest.index].poly];
+    }
+    else {
+      target_face_sets[i] = 1;
+    }
+  }
+  free_bvhtree_from_mesh(&bvhtree);
+}
+
 struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh)
 {
   const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
index 9f05b1656cd469108febae7123e74398eb43e698..bd585d56a5154142d946d022998ff037dc4d6809 100644 (file)
@@ -1212,6 +1212,7 @@ static void sculpt_update_object(
 
   ss->deform_modifiers_active = sculpt_modifiers_active(scene, sd, ob);
   ss->show_mask = (sd->flags & SCULPT_HIDE_MASK) == 0;
+  ss->show_face_sets = (sd->flags & SCULPT_HIDE_FACE_SETS) == 0;
 
   ss->building_vp_handle = false;
 
@@ -1251,6 +1252,16 @@ static void sculpt_update_object(
     ss->mloop = me->mloop;
     ss->multires = NULL;
     ss->vmask = CustomData_get_layer(&me->vdata, CD_PAINT_MASK);
+
+    /* Sculpt Face Sets. */
+    if (!CustomData_has_layer(&me->pdata, CD_SCULPT_FACE_SETS)) {
+      ss->face_sets = CustomData_add_layer(
+          &me->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, me->totpoly);
+      for (int i = 0; i < me->totpoly; i++) {
+        ss->face_sets[i] = 1;
+      }
+    }
+    ss->face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS);
   }
 
   ss->subdiv_ccg = me_eval->runtime.subdiv_ccg;
@@ -1265,6 +1276,7 @@ static void sculpt_update_object(
   }
 
   pbvh_show_mask_set(ss->pbvh, ss->show_mask);
+  pbvh_show_face_sets_set(ss->pbvh, ss->show_face_sets);
 
   if (ss->deform_modifiers_active) {
     if (!ss->orig_cos) {
@@ -1501,6 +1513,7 @@ static PBVH *build_pbvh_for_dynamic_topology(Object *ob)
                        ob->sculpt->cd_vert_node_offset,
                        ob->sculpt->cd_face_node_offset);
   pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+  pbvh_show_face_sets_set(pbvh, false);
   return pbvh;
 }
 
@@ -1522,10 +1535,12 @@ static PBVH *build_pbvh_from_regular_mesh(Object *ob, Mesh *me_eval_deform)
                       me->totvert,
                       &me->vdata,
                       &me->ldata,
+                      &me->pdata,
                       looptri,
                       looptris_num);
 
   pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+  pbvh_show_face_sets_set(pbvh, ob->sculpt->show_face_sets);
 
   const bool is_deformed = check_sculpt_object_deformed(ob, true);
   if (is_deformed && me_eval_deform != NULL) {
@@ -1551,6 +1566,7 @@ static PBVH *build_pbvh_from_ccg(Object *ob, SubdivCCG *subdiv_ccg)
                        subdiv_ccg->grid_flag_mats,
                        subdiv_ccg->grid_hidden);
   pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+  pbvh_show_face_sets_set(pbvh, false);
   return pbvh;
 }
 
index 95e7218a9207ad7f38724adb058e6172d59c7090..61caccccf90e873f8382eccb5de092256a3aa6a8 100644 (file)
@@ -26,6 +26,7 @@
 #include "BLI_math.h"
 #include "BLI_ghash.h"
 #include "BLI_task.h"
+#include "BLI_rand.h"
 
 #include "DNA_mesh_types.h"
 #include "DNA_meshdata_types.h"
@@ -36,6 +37,8 @@
 #include "BKE_mesh.h" /* for BKE_mesh_calc_normals */
 #include "BKE_paint.h"
 
+#include "PIL_time.h"
+
 #include "GPU_buffers.h"
 
 #include "bmesh.h"
@@ -541,6 +544,7 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
                          int totvert,
                          struct CustomData *vdata,
                          struct CustomData *ldata,
+                         struct CustomData *pdata,
                          const MLoopTri *looptri,
                          int looptri_num)
 {
@@ -558,6 +562,9 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
   bvh->leaf_limit = LEAF_LIMIT;
   bvh->vdata = vdata;
   bvh->ldata = ldata;
+  bvh->pdata = pdata;
+
+  bvh->face_sets_color_seed = mesh->face_sets_color_seed;
 
   BB_reset(&cb);
 
@@ -992,6 +999,7 @@ typedef struct PBVHUpdateData {
   float (*vnors)[3];
   int flag;
   bool show_vcol;
+  bool show_sculpt_face_sets;
 } PBVHUpdateData;
 
 static void pbvh_update_normals_accum_task_cb(void *__restrict userdata,
@@ -1155,6 +1163,44 @@ static void pbvh_update_mask_redraw(PBVH *bvh, PBVHNode **nodes, int totnode, in
   BKE_pbvh_parallel_range(0, totnode, &data, pbvh_update_mask_redraw_task_cb, &settings);
 }
 
+static void pbvh_update_visibility_redraw_task_cb(void *__restrict userdata,
+                                                  const int n,
+                                                  const TaskParallelTLS *__restrict UNUSED(tls))
+{
+
+  PBVHUpdateData *data = userdata;
+  PBVH *bvh = data->bvh;
+  PBVHNode *node = data->nodes[n];
+  if (node->flag & PBVH_UpdateVisibility) {
+    node->flag &= ~PBVH_UpdateVisibility;
+    BKE_pbvh_node_fully_hidden_set(node, true);
+    if (node->flag & PBVH_Leaf) {
+      PBVHVertexIter vd;
+      BKE_pbvh_vertex_iter_begin(bvh, node, vd, PBVH_ITER_ALL)
+      {
+        if (vd.visible) {
+          BKE_pbvh_node_fully_hidden_set(node, false);
+          return;
+        }
+      }
+      BKE_pbvh_vertex_iter_end;
+    }
+  }
+}
+
+static void pbvh_update_visibility_redraw(PBVH *bvh, PBVHNode **nodes, int totnode, int flag)
+{
+  PBVHUpdateData data = {
+      .bvh = bvh,
+      .nodes = nodes,
+      .flag = flag,
+  };
+
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+  BKE_pbvh_parallel_range(0, totnode, &data, pbvh_update_visibility_redraw_task_cb, &settings);
+}
+
 static void pbvh_update_BB_redraw_task_cb(void *__restrict userdata,
                                           const int n,
                                           const TaskParallelTLS *__restrict UNUSED(tls))
@@ -1198,6 +1244,7 @@ static int pbvh_get_buffers_update_flags(PBVH *bvh, bool show_vcol)
   int update_flags = 0;
   update_flags |= bvh->show_mask ? GPU_PBVH_BUFFERS_SHOW_MASK : 0;
   update_flags |= show_vcol ? GPU_PBVH_BUFFERS_SHOW_VCOL : 0;
+  update_flags |= bvh->show_face_sets ? GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS : 0;
   return update_flags;
 }
 
@@ -1218,14 +1265,16 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata,
         node->draw_buffers = GPU_pbvh_grid_buffers_build(node->totprim, bvh->grid_hidden);
         break;
       case PBVH_FACES:
-        node->draw_buffers = GPU_pbvh_mesh_buffers_build(node->face_vert_indices,
-                                                         bvh->mpoly,
-                                                         bvh->mloop,
-                                                         bvh->looptri,
-                                                         bvh->verts,
-                                                         node->prim_indices,
-                                                         node->totprim,
-                                                         bvh->mesh);
+        node->draw_buffers = GPU_pbvh_mesh_buffers_build(
+            node->face_vert_indices,
+            bvh->mpoly,
+            bvh->mloop,
+            bvh->looptri,
+            bvh->verts,
+            node->prim_indices,
+            CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS),
+            node->totprim,
+            bvh->mesh);
         break;
       case PBVH_BMESH:
         node->draw_buffers = GPU_pbvh_bmesh_buffers_build(bvh->flags &
@@ -1253,6 +1302,8 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata,
                                      node->uniq_verts + node->face_verts,
                                      CustomData_get_layer(bvh->vdata, CD_PAINT_MASK),
                                      CustomData_get_layer(bvh->ldata, CD_MLOOPCOL),
+                                     CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS),
+                                     bvh->face_sets_color_seed,
                                      node->face_vert_indices,
                                      update_flags);
         break;
@@ -1373,6 +1424,10 @@ void BKE_pbvh_update_vertex_data(PBVH *bvh, int flag)
     pbvh_update_mask_redraw(bvh, nodes, totnode, flag);
   }
 
+  if (flag & (PBVH_UpdateVisibility)) {
+    pbvh_update_visibility_redraw(bvh, nodes, totnode, flag);
+  }
+
   if (nodes) {
     MEM_freeN(nodes);
   }
@@ -1650,6 +1705,12 @@ void BKE_pbvh_node_mark_update_mask(PBVHNode *node)
   node->flag |= PBVH_UpdateMask | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
 }
 
+void BKE_pbvh_node_mark_update_visibility(PBVHNode *node)
+{
+  node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers |
+                PBVH_UpdateRedraw;
+}
+
 void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node)
 {
   node->flag |= PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
@@ -1665,11 +1726,6 @@ void BKE_pbvh_node_mark_normals_update(PBVHNode *node)
   node->flag |= PBVH_UpdateNormals;
 }
 
-void BKE_pbvh_node_mark_visibility_update(PBVHNode *node)
-{
-  node->flag |= PBVH_UpdateVisibility;
-}
-
 void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden)
 {
   BLI_assert(node->flag & PBVH_Leaf);
@@ -2559,6 +2615,11 @@ void BKE_pbvh_update_normals(PBVH *bvh, struct SubdivCCG *subdiv_ccg)
   MEM_SAFE_FREE(nodes);
 }
 
+void BKE_pbvh_face_sets_color_seed_set(PBVH *bvh, int seed)
+{
+  bvh->face_sets_color_seed = seed;
+}
+
 /**
  * PBVH drawing, updating draw buffers as needed and culling any nodes outside
  * the specified frustum.
@@ -2873,11 +2934,30 @@ bool pbvh_has_mask(PBVH *bvh)
   return false;
 }
 
+bool pbvh_has_face_sets(PBVH *bvh)
+{
+  switch (bvh->type) {
+    case PBVH_GRIDS:
+      return false;
+    case PBVH_FACES:
+      return (bvh->pdata && CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS));
+    case PBVH_BMESH:
+      return false;
+  }
+
+  return false;
+}
+
 void pbvh_show_mask_set(PBVH *bvh, bool show_mask)
 {
   bvh->show_mask = show_mask;
 }
 
+void pbvh_show_face_sets_set(PBVH *bvh, bool show_face_sets)
+{
+  bvh->show_face_sets = show_face_sets;
+}
+
 void BKE_pbvh_parallel_range_settings(PBVHParallelSettings *settings,
                                       bool use_threading,
                                       int totnode)
index bdee05f1aabaa9b8913f17940806cf536bfa669b..51342eb1faad565ff6d3cb6a402b4d2e4d68e1bb 100644 (file)
@@ -134,6 +134,9 @@ struct PBVH {
   const MLoopTri *looptri;
   CustomData *vdata;
   CustomData *ldata;
+  CustomData *pdata;
+
+  int face_sets_color_seed;
 
   /* Grid Data */
   CCGKey gridkey;
@@ -154,6 +157,7 @@ struct PBVH {
   /* flag are verts/faces deformed */
   bool deformed;
   bool show_mask;
+  bool show_face_sets;
 
   /* Dynamic topology */
   BMesh *bm;
index 627b38a58e81473bb89061be54de370da8d3ba8e..300880a58392820d814a2bf8975c3bbd0b255a30 100644 (file)
@@ -4514,5 +4514,18 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
    */
   {
     /* Keep this block, even when empty. */
+    if (!DNA_struct_elem_find(
+            fd->filesdna, "View3DOverlay", "float", "sculpt_mode_face_sets_opacity")) {
+      for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+        for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
+          for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
+            if (sl->spacetype == SPACE_VIEW3D) {
+              View3D *v3d = (View3D *)sl;
+              v3d->overlay.sculpt_mode_face_sets_opacity = 0.0f;
+            }
+          }
+        }
+      }
+    }
   }
 }
index 7076ed906c14463f119a33b888d5f096590b375c..e1cbdc890223100fa9e2dd91f58b507f249d548d 100644 (file)
@@ -2093,6 +2093,10 @@ static void write_customdata(WriteData *wd,
       const float *layer_data = layer->data;
       writedata(wd, DATA, sizeof(*layer_data) * count, layer_data);
     }
+    else if (layer->type == CD_SCULPT_FACE_SETS) {
+      const float *layer_data = layer->data;
+      writedata(wd, DATA, sizeof(*layer_data) * count, layer_data);
+    }
     else if (layer->type == CD_GRID_PAINT_MASK) {
       write_grid_paint_mask(wd, count, layer->data);
     }
index dc286d36e9e341fd5d2b4ec98796cc0d62954f7f..ba37f56800b61fb31d46026bf9e3c73b2d0dbfae 100644 (file)
@@ -34,12 +34,14 @@ void OVERLAY_sculpt_cache_init(OVERLAY_Data *vedata)
   OVERLAY_PrivateData *pd = vedata->stl->pd;
   DRWShadingGroup *grp;
 
-  DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_ALPHA;
+  DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_MUL;
   DRW_PASS_CREATE(psl->sculpt_mask_ps, state | pd->clipping_state);
 
   GPUShader *sh = OVERLAY_shader_sculpt_mask();
   pd->sculpt_mask_grp = grp = DRW_shgroup_create(sh, psl->sculpt_mask_ps);
   DRW_shgroup_uniform_float_copy(grp, "maskOpacity", pd->overlay.sculpt_mode_mask_opacity);
+  DRW_shgroup_uniform_float_copy(
+      grp, "faceSetsOpacity", pd->overlay.sculpt_mode_face_sets_opacity);
 }
 
 void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
@@ -51,7 +53,7 @@ void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
   const bool use_pbvh = BKE_sculptsession_use_pbvh_draw(ob, draw_ctx->v3d);
 
   if (use_pbvh || !ob->sculpt->deform_modifiers_active || ob->sculpt->shapekey_active) {
-    if (!use_pbvh || pbvh_has_mask(pbvh)) {
+    if (!use_pbvh || pbvh_has_mask(pbvh) || pbvh_has_face_sets(pbvh)) {
       DRW_shgroup_call_sculpt(pd->sculpt_mask_grp, ob, false, true, false);
     }
   }
@@ -60,10 +62,10 @@ void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
 void OVERLAY_sculpt_draw(OVERLAY_Data *vedata)
 {
   OVERLAY_PassList *psl = vedata->psl;
-  OVERLAY_FramebufferList *fbl = vedata->fbl;
+  DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
 
   if (DRW_state_is_fbo()) {
-    GPU_framebuffer_bind(fbl->overlay_default_fb);
+    GPU_framebuffer_bind(dfbl->default_fb);
   }
 
   DRW_draw_pass(psl->sculpt_mask_ps);
index 38559677706050dd3bb02b1523d309abe0242552..5d79195a40bbdc4b0d6e9aa149c1e3d49709812e 100644 (file)
@@ -1,7 +1,9 @@
 
 uniform float maskOpacity;
+uniform float faceSetsOpacity;
 
 in vec3 pos;
+in vec3 fset;
 in float msk;
 
 out vec4 finalColor;
@@ -11,7 +13,8 @@ void main()
   vec3 world_pos = point_object_to_world(pos);
   gl_Position = point_world_to_ndc(world_pos);
 
-  finalColor = vec4(0.0, 0.0, 0.0, msk * maskOpacity);
+  finalColor = vec4(mix(vec3(1.0), fset, faceSetsOpacity), 1.0);
+  finalColor.rgb *= (1.0 - (msk * maskOpacity));
 
 #ifdef USE_WORLD_CLIP_PLANES
   world_clip_planes_calc_clip_distance(world_pos);
index 6f2a9cc056f80804426e438986bc8d2c47f1f280..28d5daf011cc31e557cf293404197b4735dc6580 100644 (file)
@@ -823,6 +823,7 @@ typedef struct DRWSculptCallbackData {
   bool use_wire;
   bool use_mats;
   bool use_mask;
+  bool use_fsets;
   bool fast_mode; /* Set by draw manager. Do not init. */
 
   int debug_node_nr;
@@ -843,11 +844,6 @@ static float sculpt_debug_colors[9][4] = {
 
 static void sculpt_draw_cb(DRWSculptCallbackData *scd, GPU_PBVH_Buffers *buffers)
 {
-  /* Meh... use_mask is a bit misleading here. */
-  if (scd->use_mask && !GPU_pbvh_buffers_has_mask(buffers)) {
-    return;
-  }
-
   GPUBatch *geom = GPU_pbvh_buffers_batch_get(buffers, scd->fast_mode, scd->use_wire);
   short index = 0;
 
index 4454af5a0962c6a11b94e87669b21b7bba441d0e..8d268be5a78c8513bede4d3ecc59abe6640c4ef7 100644 (file)
@@ -138,16 +138,23 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
     BKE_mesh_calc_normals(new_mesh);
   }
 
-  if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
+  if (mesh->flag & ME_REMESH_REPROJECT_VOLUME || mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK ||
+      mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
     BKE_mesh_runtime_clear_geometry(mesh);
+  }
+
+  if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
     BKE_shrinkwrap_remesh_target_project(new_mesh, mesh, ob);
   }
 
   if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
-    BKE_mesh_runtime_clear_geometry(mesh);
     BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh);
   }
 
+  if (mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
+    BKE_remesh_reproject_sculpt_face_sets(new_mesh, mesh);
+  }
+
   BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
 
   if (mesh->flag & ME_REMESH_SMOOTH_NORMALS) {
index 390b69df7ff677ca65c6ab9ea993e5445ed5cf3e..b87a050a7c2f3e9957e3c9bb11bc7caed8bf3e93 100644 (file)
@@ -35,6 +35,8 @@
 
 #include "BLT_translation.h"
 
+#include "PIL_time.h"
+
 #include "DNA_customdata_types.h"
 #include "DNA_mesh_types.h"
 #include "DNA_meshdata_types.h"
@@ -218,6 +220,249 @@ static void sculpt_active_vertex_normal_get(SculptSession *ss, float normal[3])
   sculpt_vertex_normal_get(ss, sculpt_active_vertex_get(ss), normal);
 }
 
+/* Sculpt Face Sets and Visibility*/
+
+static void sculpt_vertex_visible_set(SculptSession *ss, int index, bool visible)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES:
+      SET_FLAG_FROM_TEST(ss->mvert[index].flag, !visible, ME_HIDE);
+      ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE;
+      break;
+    case PBVH_BMESH:
+      BM_elem_flag_set(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN, !visible);
+      break;
+    case PBVH_GRIDS:
+      break;
+  }
+}
+
+static bool sculpt_vertex_visible_get(SculptSession *ss, int index)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES:
+      return !(ss->mvert[index].flag & ME_HIDE);
+    case PBVH_BMESH:
+      return !BM_elem_flag_test(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN);
+    case PBVH_GRIDS:
+      return true;
+  }
+  return true;
+}
+
+static void sculpt_face_set_visibility_set(SculptSession *ss, int face_set, bool visible)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES:
+      for (int i = 0; i < ss->totpoly; i++) {
+        if (abs(ss->face_sets[i]) == face_set) {
+          if (visible) {
+            ss->face_sets[i] = abs(ss->face_sets[i]);
+          }
+          else {
+            ss->face_sets[i] = -abs(ss->face_sets[i]);
+          }
+        }
+      }
+      break;
+    case PBVH_BMESH:
+      break;
+    case PBVH_GRIDS:
+      break;
+  }
+}
+
+static void sculpt_face_sets_visibility_invert(SculptSession *ss)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES:
+      for (int i = 0; i < ss->totpoly; i++) {
+        ss->face_sets[i] *= -1;
+      }
+      break;
+    case PBVH_BMESH:
+      break;
+    case PBVH_GRIDS:
+      break;
+  }
+}
+
+static void sculpt_face_sets_visibility_all_set(SculptSession *ss, bool visible)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES:
+      for (int i = 0; i < ss->totpoly; i++) {
+        if (visible) {
+          ss->face_sets[i] = abs(ss->face_sets[i]);
+        }
+        else {
+          ss->face_sets[i] = -abs(ss->face_sets[i]);
+        }
+      }
+      break;
+    case PBVH_BMESH:
+      break;
+    case PBVH_GRIDS:
+      break;
+  }
+}
+
+static bool sculpt_vertex_visibility_from_face_sets_get(SculptSession *ss, int index)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES: {
+      MeshElemMap *vert_map = &ss->pmap[index];
+      for (int j = 0; j < ss->pmap[index].count; j++) {
+        if (ss->face_sets[vert_map->indices[j]] > 0) {
+          return true;
+        }
+      }
+      return false;
+    }
+    case PBVH_BMESH:
+      return true;
+    case PBVH_GRIDS:
+      return true;
+  }
+  return true;
+}
+
+static void sculpt_vertex_face_set_set(SculptSession *ss, int index, int face_set)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES: {
+      MeshElemMap *vert_map = &ss->pmap[index];
+      for (int j = 0; j < ss->pmap[index].count; j++) {
+        if (ss->face_sets[vert_map->indices[j]] > 0) {
+          ss->face_sets[vert_map->indices[j]] = abs(face_set);
+        }
+        else {
+          ss->face_sets[vert_map->indices[j]] = -abs(face_set);
+        }
+      }
+    } break;
+    case PBVH_BMESH:
+      break;
+    case PBVH_GRIDS:
+      break;
+  }
+}
+
+static int sculpt_vertex_face_set_get(SculptSession *ss, int index)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES: {
+      MeshElemMap *vert_map = &ss->pmap[index];
+      int face_set = 0;
+      for (int i = 0; i < ss->pmap[index].count; i++) {
+        if (ss->face_sets[vert_map->indices[i]] > face_set) {
+          face_set = abs(ss->face_sets[vert_map->indices[i]]);
+        }
+      }
+      return face_set;
+    }
+    case PBVH_BMESH:
+      return 0;
+    case PBVH_GRIDS:
+      return 0;
+  }
+  return 0;
+}
+
+static bool sculpt_vertex_has_face_set(SculptSession *ss, int index, int face_set)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES: {
+      MeshElemMap *vert_map = &ss->pmap[index];
+      for (int i = 0; i < ss->pmap[index].count; i++) {
+        if (ss->face_sets[vert_map->indices[i]] == face_set) {
+          return true;
+        }
+      }
+      return false;
+    }
+    case PBVH_BMESH:
+      return true;
+    case PBVH_GRIDS:
+      return true;
+  }
+  return true;
+}
+
+static void sculpt_visibility_sync_face_sets_to_vertex(SculptSession *ss, int index)
+{
+  sculpt_vertex_visible_set(ss, index, sculpt_vertex_visibility_from_face_sets_get(ss, index));
+}
+
+void sculpt_visibility_sync_all_face_sets_to_vertices(SculptSession *ss)
+{
+  for (int i = 0; i < ss->totvert; i++) {
+    sculpt_visibility_sync_face_sets_to_vertex(ss, i);
+  }
+}
+
+static void sculpt_visibility_sync_vertex_to_face_sets(SculptSession *ss, int index)
+{
+  MeshElemMap *vert_map = &ss->pmap[index];
+  const bool visible = sculpt_vertex_visible_get(ss, index);
+  for (int i = 0; i < ss->pmap[index].count; i++) {
+    if (visible) {
+      ss->face_sets[vert_map->indices[i]] = abs(ss->face_sets[vert_map->indices[i]]);
+    }
+    else {
+      ss->face_sets[vert_map->indices[i]] = -abs(ss->face_sets[vert_map->indices[i]]);
+    }
+  }
+  ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE;
+}
+
+void sculpt_visibility_sync_all_vertex_to_face_sets(SculptSession *ss)
+{
+  for (int i = 0; i < ss->totvert; i++) {
+    sculpt_visibility_sync_vertex_to_face_sets(ss, i);
+  }
+}
+
+static bool UNUSED_FUNCTION(sculpt_vertex_has_unique_face_set)(SculptSession *ss, int index)
+{
+  MeshElemMap *vert_map = &ss->pmap[index];
+  int face_set = -1;
+  for (int i = 0; i < ss->pmap[index].count; i++) {
+    if (face_set == -1) {
+      face_set = ss->face_sets[vert_map->indices[i]];
+    }
+    else {
+      if (ss->face_sets[vert_map->indices[i]] != face_set) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+static int sculpt_face_set_next_available_get(SculptSession *ss)
+{
+  switch (BKE_pbvh_type(ss->pbvh)) {
+    case PBVH_FACES: {
+      int next_face_set = 0;
+      for (int i = 0; i < ss->totpoly; i++) {
+        if (abs(ss->face_sets[i]) > next_face_set) {
+          next_face_set = abs(ss->face_sets[i]);
+        }
+      }
+      next_face_set++;
+      return next_face_set;
+    }
+    case PBVH_BMESH:
+      return 0;
+    case PBVH_GRIDS:
+      return 0;
+  }
+  return 0;
+}
+
+/* Sculpt Neighbor Iterators */
+
 #define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
 
 static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neighbor_index)
@@ -609,8 +854,12 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
 
 static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
 {
-  return ELEM(
-      sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE, SCULPT_TOOL_CLOTH);
+  return ELEM(sculpt_tool,
+              SCULPT_TOOL_SMOOTH,
+              SCULPT_TOOL_LAYER,
+              SCULPT_TOOL_POSE,
+              SCULPT_TOOL_CLOTH,
+              SCULPT_TOOL_DRAW_FACE_SETS);
 }
 
 static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush)
@@ -1219,6 +1468,9 @@ static bool sculpt_automasking_enabled(SculptSession *ss, const Brush *br)
   if (br->automasking_flags & BRUSH_AUTOMASKING_TOPOLOGY) {
     return true;
   }
+  if (br->automasking_flags & BRUSH_AUTOMASKING_FACE_SETS) {
+    return true;
+  }
   return false;
 }
 
@@ -1305,6 +1557,34 @@ static float *sculpt_topology_automasking_init(Sculpt *sd, Object *ob, float *au
   return automask_factor;
 }
 
+static float *sculpt_face_sets_automasking_init(Sculpt *sd, Object *ob, float *automask_factor)
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  if (!sculpt_automasking_enabled(ss, brush)) {
+    return NULL;
+  }
+
+  if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) {
+    BLI_assert(!"Face Sets automasking: pmap missing");
+    return NULL;
+  }
+
+  int tot_vert = sculpt_vertex_count_get(ss);
+  int active_face_set = sculpt_vertex_face_set_get(ss, sculpt_active_vertex_get(ss));
+  for (int i = 0; i < tot_vert; i++) {
+    if (sculpt_vertex_has_face_set(ss, i, active_face_set)) {
+      automask_factor[i] = 1;
+    }
+    else {
+      automask_factor[i] = 0;
+    }
+  }
+
+  return automask_factor;
+}
+
 static void sculpt_automasking_init(Sculpt *sd, Object *ob)
 {
   SculptSession *ss = ob->sculpt;
@@ -1317,6 +1597,10 @@ static void sculpt_automasking_init(Sculpt *sd, Object *ob)
     sculpt_vertex_random_access_init(ss);
     sculpt_topology_automasking_init(sd, ob, ss->cache->automask);
   }
+  if (brush->automasking_flags & BRUSH_AUTOMASKING_FACE_SETS) {
+    sculpt_vertex_random_access_init(ss);
+    sculpt_face_sets_automasking_init(sd, ob, ss->cache->automask);
+  }
 }
 
 /* ===== Sculpting =====
@@ -1796,6 +2080,8 @@ static float brush_strength(const Sculpt *sd,
          * brush and object. */
         return 10.0f * alpha * flip * pressure * overlap * feather;
       }
+    case SCULPT_TOOL_DRAW_FACE_SETS:
+      return alpha * pressure * overlap * feather;
     case SCULPT_TOOL_SLIDE_RELAX:
       return alpha * pressure * overlap * feather * 2.0f;
     case SCULPT_TOOL_CLAY_STRIPS:
@@ -2969,6 +3255,74 @@ static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
   BKE_pbvh_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings);
 }
 
+static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata,
+                                               const int n,
+                                               const TaskParallelTLS *__restrict tls)
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+  const Brush *brush = data->brush;
+  const float bstrength = ss->cache->bstrength;
+
+  PBVHVertexIter vd;
+
+  SculptBrushTest test;
+  SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+      ss, &test, data->brush->falloff_shape);
+
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+      const float fade = bstrength * tex_strength(ss,
+                                                  brush,
+                                                  vd.co,
+                                                  sqrtf(test.dist),
+                                                  vd.no,
+                                                  vd.fno,
+                                                  vd.mask ? *vd.mask : 0.0f,
+                                                  vd.index,
+                                                  tls->thread_id);
+
+      if (fade > 0.05f) {
+        sculpt_vertex_face_set_set(ss, vd.index, ss->cache->paint_face_set);
+      }
+    }
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
+static void do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+  SculptSession *ss = ob->sculpt;
+  Brush *brush = BKE_paint_brush(&sd->paint);
+
+  if (ss->cache->first_time && ss->cache->mirror_symmetry_pass == 0 &&
+      ss->cache->radial_symmetry_pass == 0) {
+    if (ss->cache->invert) {
+      /* When inverting the brush, pick the paint face mask ID from the mesh. */
+      ss->cache->paint_face_set = sculpt_vertex_face_set_get(ss, sculpt_active_vertex_get(ss));
+    }
+    else {
+      /* By default create a new Face Sets. */
+      ss->cache->paint_face_set = sculpt_face_set_next_available_get(ss);
+    }
+  }
+
+  BKE_curvemapping_initialize(brush->curve);
+
+  /* Threaded loop over nodes. */
+  SculptThreadedTaskData data = {
+      .sd = sd,
+      .ob = ob,
+      .brush = brush,
+      .nodes = nodes,
+  };
+
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+  BKE_pbvh_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings);
+}
+
 static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata,
                                            const int n,
                                            const TaskParallelTLS *__restrict tls)
@@ -5684,10 +6038,13 @@ static void do_brush_action_task_cb(void *__restrict userdata,
 {
   SculptThreadedTaskData *data = userdata;
 
-  sculpt_undo_push_node(data->ob,
-                        data->nodes[n],
-                        data->brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK :
-                                                                       SCULPT_UNDO_COORDS);
+  /* Face Sets modifications do a single undo push */
+  if (data->brush->sculpt_tool != SCULPT_TOOL_DRAW_FACE_SETS) {
+    sculpt_undo_push_node(data->ob,
+                          data->nodes[n],
+                          data->brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK :
+                                                                         SCULPT_UNDO_COORDS);
+  }
   if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) {
     BKE_pbvh_node_mark_update_mask(data->nodes[n]);
   }
@@ -5751,6 +6108,11 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
     BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
     BKE_pbvh_parallel_range(0, totnode, &task_data, do_brush_action_task_cb, &settings);
 
+    if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && ss->cache->first_time &&
+        ss->cache->mirror_symmetry_pass == 0) {
+      sculpt_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+    }
+
     if (sculpt_brush_needs_normal(ss, brush)) {
       update_sculpt_normal(sd, ob, nodes, totnode);
     }
@@ -5859,6 +6221,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
       case SCULPT_TOOL_CLOTH:
         SCULPT_do_cloth_brush(sd, ob, nodes, totnode);
         break;
+      case SCULPT_TOOL_DRAW_FACE_SETS:
+        do_draw_face_sets_brush(sd, ob, nodes, totnode);
+        break;
     }
 
     if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) &&
@@ -6383,6 +6748,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
       return "Slide/Relax Brush";
     case SCULPT_TOOL_CLOTH:
       return "Cloth Brush";
+    case SCULPT_TOOL_DRAW_FACE_SETS:
+      return "Draw Face Sets";
   }
 
   return "Sculpting";
@@ -6923,7 +7290,8 @@ static bool sculpt_needs_connectivity_info(const Brush *brush, SculptSession *ss
           ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
           (brush->sculpt_tool == SCULPT_TOOL_POSE) ||
           (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) ||
-          (brush->sculpt_tool == SCULPT_TOOL_CLOTH));
+          (brush->sculpt_tool == SCULPT_TOOL_CLOTH) ||
+          (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS));
 }
 
 static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
@@ -8931,6 +9299,9 @@ static void sculpt_filter_cache_free(SculptSession *ss)
   if (ss->filter_cache->normal_factor) {
     MEM_freeN(ss->filter_cache->normal_factor);
   }
+  if (ss->filter_cache->prev_face_set) {
+    MEM_freeN(ss->filter_cache->prev_face_set);
+  }
   MEM_freeN(ss->filter_cache);
   ss->filter_cache = NULL;
 }
@@ -8998,12 +9369,19 @@ static void mesh_filter_task_cb(void *__restrict userdata,
       continue;
     }
 
+    if (ss->filter_cache->active_face_set != SCULPT_FACE_SET_NONE) {
+      if (!sculpt_vertex_has_face_set(ss, vd.index, ss->filter_cache->active_face_set)) {
+        continue;
+      }
+    }
+
     if (filter_type == MESH_FILTER_RELAX) {
       copy_v3_v3(orig_co, vd.co);
     }
     else {
       copy_v3_v3(orig_co, orig_data.co);
     }
+
     switch (filter_type) {
       case MESH_FILTER_SMOOTH:
         CLAMP(fade, -1.0f, 1.0f);
@@ -9144,7 +9522,7 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
   return OPERATOR_RUNNING_MODAL;
 }
 
-static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
   Object *ob = CTX_data_active_object(C);
   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
@@ -9158,6 +9536,15 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
     return OPERATOR_CANCELLED;
   }
 
+  if (RNA_boolean_get(op->ptr, "use_face_sets")) {
+    /* Update the active vertex */
+    float mouse[2];
+    SculptCursorGeometryInfo sgi;
+    mouse[0] = event->mval[0];
+    mouse[1] = event->mval[1];
+    sculpt_cursor_geometry_info_update(C, &sgi, mouse, false);
+  }
+
   sculpt_vertex_random_access_init(ss);
 
   bool needs_pmap = sculpt_mesh_filter_needs_pmap(filter_type);
@@ -9171,6 +9558,14 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
 
   sculpt_filter_cache_init(ob, sd);
 
+  if (RNA_boolean_get(op->ptr, "use_face_sets")) {
+    ss->filter_cache->active_face_set = sculpt_vertex_face_set_get(ss,
+                                                                   sculpt_active_vertex_get(ss));
+  }
+  else {
+    ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
+  }
+
   ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
   ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
   ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
@@ -9208,6 +9603,11 @@ static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
                     MESH_FILTER_DEFORM_X | MESH_FILTER_DEFORM_Y | MESH_FILTER_DEFORM_Z,
                     "Deform axis",
                     "Apply the deformation in the selected axis");
+  ot->prop = RNA_def_boolean(ot->srna,
+                             "use_face_sets",
+                             false,
+                             "Use Face Sets",
+                             "Apply the filter only to the Face Mask under the cursor");
 }
 
 typedef enum eSculptMaskFilterTypes {
@@ -9617,7 +10017,7 @@ static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op)
 
   MEM_SAFE_FREE(nodes);
 
-  BKE_pbvh_update_vertex_data(pbvh, SCULPT_UPDATE_MASK);
+  BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask);
 
   sculpt_undo_push_end();
 
@@ -9705,20 +10105,29 @@ static void sculpt_expand_task_cb(void *__restrict userdata,
       }
     }
 
-    if (data->mask_expand_keep_prev_mask) {
-      final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask);
+    if (data->mask_expand_create_face_set) {
+      if (final_mask == 1.0f) {
+        sculpt_vertex_face_set_set(ss, vd.index, ss->filter_cache->new_face_set);
+      }
+      BKE_pbvh_node_mark_redraw(node);
     }
+    else {
 
-    if (data->mask_expand_invert_mask) {
-      final_mask = 1.0f - final_mask;
-    }
+      if (data->mask_expand_keep_prev_mask) {
+        final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask);
+      }
 
-    if (*vd.mask != final_mask) {
-      if (vd.mvert) {
-        vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+      if (data->mask_expand_invert_mask) {
+        final_mask = 1.0f - final_mask;
+      }
+
+      if (*vd.mask != final_mask) {
+        if (vd.mvert) {
+          vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+        }
+        *vd.mask = final_mask;
+        BKE_pbvh_node_mark_update_mask(node);
       }
-      *vd.mask = final_mask;
-      BKE_pbvh_node_mark_update_mask(node);
     }
   }
   BKE_pbvh_vertex_iter_end;
@@ -9730,6 +10139,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
   Object *ob = CTX_data_active_object(C);
   SculptSession *ss = ob->sculpt;
   Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+  ARegion *ar = CTX_wm_region(C);
   float prevclick_f[2];
   copy_v2_v2(prevclick_f, op->customdata);
   int prevclick[2] = {(int)prevclick_f[0], (int)prevclick_f[1]};
@@ -9739,6 +10149,8 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
   int mask_expand_update_it = len / mask_speed;
   mask_expand_update_it = mask_expand_update_it + 1;
 
+  const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set");
+
   if (RNA_boolean_get(op->ptr, "use_cursor")) {
     SculptCursorGeometryInfo sgi;
     float mouse[2];
@@ -9823,15 +10235,28 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
     return OPERATOR_FINISHED;
   }
 
-  if (event->type != MOUSEMOVE) {
+  /* When pressing Ctrl, expand directly to the max number of iterations. This allows to flood fill
+   * mask and face sets by connectivity directly. */
+  if (event->ctrl) {
+    mask_expand_update_it = ss->filter_cache->mask_update_last_it - 1;
+  }
+
+  if (!ELEM(event->type, MOUSEMOVE, LEFTCTRLKEY, RIGHTCTRLKEY)) {
     return OPERATOR_RUNNING_MODAL;
   }
 
   if (mask_expand_update_it == ss->filter_cache->mask_update_current_it) {
+    ED_region_tag_redraw(ar);
     return OPERATOR_RUNNING_MODAL;
   }
 
   if (mask_expand_update_it < ss->filter_cache->mask_update_last_it) {
+
+    if (create_face_set) {
+      for (int i = 0; i < ss->totpoly; i++) {
+        ss->face_sets[i] = ss->filter_cache->prev_face_set[i];
+      }
+    }
     SculptThreadedTaskData data = {
         .sd = sd,
         .ob = ob,
@@ -9840,6 +10265,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
         .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"),
         .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"),
         .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"),
+        .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"),
     };
     PBVHParallelSettings settings;
     BKE_pbvh_parallel_range_settings(
@@ -9903,7 +10329,8 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
   Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
   PBVH *pbvh = ob->sculpt->pbvh;
 
-  bool use_normals = RNA_boolean_get(op->ptr, "use_normals");
+  const bool use_normals = RNA_boolean_get(op->ptr, "use_normals");
+  const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set");
 
   SculptCursorGeometryInfo sgi;
   float mouse[2];
@@ -9927,9 +10354,17 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
 
   sculpt_undo_push_begin("Mask Expand");
 
-  for (int i = 0; i < ss->filter_cache->totnode; i++) {
-    sculpt_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK);
-    BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+  if (create_face_set) {
+    sculpt_undo_push_node(ob, ss->filter_cache->nodes[0], SCULPT_UNDO_FACE_SETS);
+    for (int i = 0; i < ss->filter_cache->totnode; i++) {
+      BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+    }
+  }
+  else {
+    for (int i = 0; i < ss->filter_cache->totnode; i++) {
+      sculpt_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK);
+      BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+    }
   }
 
   ss->filter_cache->mask_update_it = MEM_callocN(sizeof(int) * vertex_count,
@@ -9944,9 +10379,18 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
     }
   }
 
-  ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask");
-  for (int i = 0; i < vertex_count; i++) {
-    ss->filter_cache->prev_mask[i] = sculpt_vertex_mask_get(ss, i);
+  if (create_face_set) {
+    ss->filter_cache->prev_face_set = MEM_callocN(sizeof(float) * ss->totpoly, "prev face mask");
+    for (int i = 0; i < ss->totpoly; i++) {
+      ss->filter_cache->prev_face_set[i] = ss->face_sets[i];
+    }
+    ss->filter_cache->new_face_set = sculpt_face_set_next_available_get(ss);
+  }
+  else {
+    ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask");
+    for (int i = 0; i < vertex_count; i++) {
+      ss->filter_cache->prev_mask[i] = sculpt_vertex_mask_get(ss, i);
+    }
   }
 
   ss->filter_cache->mask_update_last_it = 1;
@@ -9992,6 +10436,7 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
       .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"),
       .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"),
       .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"),
+      .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"),
   };
   PBVHParallelSettings settings;
   BKE_pbvh_parallel_range_settings(
@@ -10053,6 +10498,11 @@ static void SCULPT_OT_mask_expand(wmOperatorType *ot)
                          "using normals to generate the mask",
                          0,
                          2000);
+  ot->prop = RNA_def_boolean(ot->srna,
+                             "create_face_set",
+                             false,
+                             "Expand Face Mask",
+                             "Expand a new Face Mask instead of the sculpt mask");
 }
 
 void sculpt_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius)
@@ -10437,6 +10887,356 @@ static void SCULPT_OT_set_pivot_position(wmOperatorType *ot)
                 10000.0f);
 }
 
+typedef enum eSculptFaceGroupsCreateModes {
+  SCULPT_FACE_SET_MASKED = 0,
+  SCULPT_FACE_SET_VISIBLE = 1,
+  SCULPT_FACE_SET_ALL = 2,
+} eSculptFaceGroupsCreateModes;
+
+static EnumPropertyItem prop_sculpt_face_set_create_types[] = {
+    {
+        SCULPT_FACE_SET_MASKED,
+        "MASKED",
+        0,
+        "Face Mask From Masked",
+        "Create a new Face Mask from the masked faces",
+    },
+    {
+        SCULPT_FACE_SET_VISIBLE,
+        "VISIBLE",
+        0,
+        "Face Mask From Visible",
+        "Create a new Face Mask from the visible vertices",
+    },
+    {
+        SCULPT_FACE_SET_ALL,
+        "ALL",
+        0,
+        "Face Mask Full Mesh",
+        "Create an unique Face Mask with all faces in the sculpt",
+    },
+    {0, NULL, 0, NULL, NULL},
+};
+
+static int sculpt_face_set_create_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+  Object *ob = CTX_data_active_object(C);
+  SculptSession *ss = ob->sculpt;
+  ARegion *ar = CTX_wm_region(C);
+  Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+
+  const int mode = RNA_enum_get(op->ptr, "mode");
+
+  /* Dyntopo and Multires not supported for now. */
+  if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+    return OPERATOR_CANCELLED;
+  }
+
+  BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED);
+
+  const int tot_vert = sculpt_vertex_count_get(ss);
+  float threshold = 0.5f;
+
+  PBVH *pbvh = ob->sculpt->pbvh;
+  PBVHNode **nodes;
+  int totnode;
+  BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
+
+  if (!nodes) {
+    return OPERATOR_CANCELLED;
+  }
+
+  sculpt_undo_push_begin("face mask change");
+  sculpt_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+
+  const int next_face_set = sculpt_face_set_next_available_get(ss);
+
+  if (mode == SCULPT_FACE_SET_MASKED) {
+    for (int i = 0; i < tot_vert; i++) {
+      if (sculpt_vertex_mask_get(ss, i) >= threshold) {
+        sculpt_vertex_face_set_set(ss, i, next_face_set);
+      }
+    }
+  }
+
+  if (mode == SCULPT_FACE_SET_VISIBLE) {
+    for (int i = 0; i < tot_vert; i++) {
+      if (sculpt_vertex_visible_get(ss, i)) {
+        sculpt_vertex_face_set_set(ss, i, next_face_set);
+      }
+    }
+  }
+
+  if (mode == SCULPT_FACE_SET_ALL) {
+    for (int i = 0; i < tot_vert; i++) {
+      sculpt_vertex_face_set_set(ss, i, next_face_set);
+    }
+  }
+
+  for (int i = 0; i < totnode; i++) {
+    BKE_pbvh_node_mark_redraw(nodes[i]);
+  }
+
+  MEM_SAFE_FREE(nodes);
+
+  sculpt_undo_push_end();
+
+  ED_region_tag_redraw(ar);
+  WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+
+  return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_face_sets_create(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Create Face Group";
+  ot->idname = "SCULPT_OT_face_sets_create";
+  ot->description = "Create a new Face Group";
+
+  /* api callbacks */
+  ot->invoke = sculpt_face_set_create_invoke;
+  ot->poll = sculpt_mode_poll;
+
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  RNA_def_enum(
+      ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", "");
+}
+
+typedef enum eSculptFaceGroupVisibilityModes {
+  SCULPT_FACE_SET_VISIBILITY_TOGGLE = 0,
+  SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE = 1,
+  SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE = 2,
+  SCULPT_FACE_SET_VISIBILITY_INVERT = 3,
+  SCULPT_FACE_SET_VISIBILITY_SHOW_ALL = 4,
+} eSculptFaceGroupVisibilityModes;
+
+static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = {
+    {
+        SCULPT_FACE_SET_VISIBILITY_TOGGLE,
+        "TOGGLE",
+        0,
+        "Toggle Visibility",
+        "Hide all Face Sets except for the active one",
+    },
+    {
+        SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE,
+        "SHOW_ACTIVE",
+        0,
+        "Show Active Face Mask",
+        "Show Active Face Mask",
+    },
+    {
+        SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE,
+        "HIDE_ACTIVE",
+        0,
+        "Hide Active Face Sets",
+        "Hide Active Face Sets",
+    },
+    {
+        SCULPT_FACE_SET_VISIBILITY_INVERT,
+        "INVERT",
+        0,
+        "Invert Face Mask Visibility",
+        "Invert Face Mask Visibility",
+    },
+    {
+        SCULPT_FACE_SET_VISIBILITY_SHOW_ALL,
+        "SHOW_ALL",
+        0,
+        "Show All Face Sets",
+        "Show All Face Sets",
+    },
+    {0, NULL, 0, NULL, NULL},
+};
+
+static int sculpt_face_sets_change_visibility_invoke(bContext *C,
+                                                     wmOperator *op,
+                                                     const wmEvent *UNUSED(event))
+{
+  Object *ob = CTX_data_active_object(C);
+  SculptSession *ss = ob->sculpt;
+  ARegion *ar = CTX_wm_region(C);
+  Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+
+  /* Dyntopo and Multires not supported for now. */
+  if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+    return OPERATOR_CANCELLED;
+  }
+
+  BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true);
+
+  const int tot_vert = sculpt_vertex_count_get(ss);
+  const int mode = RNA_enum_get(op->ptr, "mode");
+  int active_vertex_index = sculpt_active_vertex_get(ss);
+  int active_face_set = sculpt_vertex_face_set_get(ss, active_vertex_index);
+
+  sculpt_undo_push_begin("Hide area");
+
+  PBVH *pbvh = ob->sculpt->pbvh;
+  PBVHNode **nodes;
+  int totnode;
+
+  BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
+
+  if (totnode == 0) {
+    MEM_SAFE_FREE(nodes);
+    return OPERATOR_CANCELLED;
+  }
+
+  sculpt_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+
+  if (mode == SCULPT_FACE_SET_VISIBILITY_TOGGLE) {
+    bool hidden_vertex = false;
+    for (int i = 0; i < tot_vert; i++) {
+      if (!sculpt_vertex_visible_get(ss, i)) {
+        hidden_vertex = true;
+        break;
+      }
+    }
+
+    for (int i = 0; i < ss->totpoly; i++) {
+      if (ss->face_sets[i] < 0) {
+        hidden_vertex = true;
+        break;
+      }
+    }
+    if (hidden_vertex) {
+      sculpt_face_sets_visibility_all_set(ss, true);
+    }
+    else {
+      sculpt_face_sets_visibility_all_set(ss, false);
+      sculpt_face_set_visibility_set(ss, active_face_set, true);
+    }
+  }
+
+  if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ALL) {
+    sculpt_face_sets_visibility_all_set(ss, true);
+  }
+
+  if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE) {
+    sculpt_face_sets_visibility_all_set(ss, false);
+    sculpt_face_set_visibility_set(ss, active_face_set, true);
+    for (int i = 0; i < tot_vert; i++) {
+      sculpt_vertex_visible_set(ss,
+                                i,
+                                sculpt_vertex_visible_get(ss, i) &&
+                                    sculpt_vertex_has_face_set(ss, i, active_face_set));
+    }
+  }
+
+  if (mode == SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE) {
+    sculpt_face_set_visibility_set(ss, active_face_set, false);
+  }
+
+  if (mode == SCULPT_FACE_SET_VISIBILITY_INVERT) {
+    sculpt_face_sets_visibility_invert(ss);
+  }
+
+  /* Sync face mask visibility and vertex visibility. */
+  sculpt_visibility_sync_all_face_sets_to_vertices(ss);
+
+  sculpt_undo_push_end();
+
+  for (int i = 0; i < totnode; i++) {
+    BKE_pbvh_node_mark_update_visibility(nodes[i]);
+  }
+
+  BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
+
+  MEM_SAFE_FREE(nodes);
+
+  if (BKE_pbvh_type(pbvh) == PBVH_FACES) {
+    BKE_mesh_flush_hidden_from_verts(ob->data);
+  }
+
+  ED_region_tag_redraw(ar);
+
+  View3D *v3d = CTX_wm_view3d(C);
+  if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+    DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+    DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+  }
+  return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot)
+{
+  /* Identifiers. */
+  ot->name = "Face Mask Visibility";
+  ot->idname = "SCULPT_OT_face_set_change_visibility";
+  ot->description = "Change the visibility of the Face Sets of the sculpt";
+
+  /* Api callbacks. */
+  ot->invoke = sculpt_face_sets_change_visibility_invoke;
+  ot->poll = sculpt_mode_poll;
+
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  RNA_def_enum(ot->srna,
+               "mode",
+               prop_sculpt_face_sets_change_visibility_types,
+               SCULPT_FACE_SET_VISIBILITY_TOGGLE,
+               "Mode",
+               "");
+}
+
+static int sculpt_face_sets_randomize_colors_invoke(bContext *C,
+                                                    wmOperator *UNUSED(op),
+                                                    const wmEvent *UNUSED(event))
+{
+
+  Object *ob = CTX_data_active_object(C);
+  SculptSession *ss = ob->sculpt;
+  ARegion *ar = CTX_wm_region(C);
+
+  /* Dyntopo and Multires not supported for now. */
+  if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+    return OPERATOR_CANCELLED;
+  }
+
+  PBVH *pbvh = ob->sculpt->pbvh;
+  PBVHNode **nodes;
+  int totnode;
+  Mesh *mesh = ob->data;
+
+  int new_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX);
+  mesh->face_sets_color_seed = new_seed;
+  BKE_pbvh_face_sets_color_seed_set(pbvh, new_seed);
+
+  BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
+  for (int i = 0; i < totnode; i++) {
+    BKE_pbvh_node_mark_redraw(nodes[i]);
+  }
+
+  MEM_SAFE_FREE(nodes);
+
+  View3D *v3d = CTX_wm_view3d(C);
+  if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+    DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+  }
+
+  ED_region_tag_redraw(ar);
+  WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
+
+  return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot)
+{
+  /* Identifiers. */
+  ot->name = "Randomize Face Sets Colors";
+  ot->idname = "SCULPT_OT_face_sets_randomize_colors";
+  ot->description = "Generates a new set of random colors to render the Face Sets in the viewport";
+
+  /* Api callbacks. */
+  ot->invoke = sculpt_face_sets_randomize_colors_invoke;
+  ot->poll = sculpt_mode_poll;
+
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
 void ED_operatortypes_sculpt(void)
 {
   WM_operatortype_append(SCULPT_OT_brush_stroke);
@@ -10453,4 +11253,7 @@ void ED_operatortypes_sculpt(void)
   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);
+  WM_operatortype_append(SCULPT_OT_face_sets_change_visibility);
+  WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors);
 }
index 91656cb2ad5fd5ed393ef3177e5a0f98fd1fbdf3..a935e32cc08e244a878fa75f96262a44ef2b21a8 100644 (file)
@@ -54,6 +54,7 @@ bool sculpt_poll_view3d(struct bContext *C);
 typedef enum SculptUpdateType {
   SCULPT_UPDATE_COORDS = 1 << 0,
   SCULPT_UPDATE_MASK = 1 << 1,
+  SCULPT_UPDATE_VISIBILITY = 1 << 2,
 } SculptUpdateType;
 
 /* Stroke */
@@ -236,9 +237,17 @@ struct SculptPoseIKChain *SCULPT_pose_ik_chain_init(struct Sculpt *sd,
                                                     struct Brush *br,
                                                     const float initial_location[3],
                                                     const float radius);
-
 void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
 
+/* Sculpt Visibility API */
+void sculpt_visibility_sync_all_face_sets_to_vertices(struct SculptSession *ss);
+void sculpt_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss);
+
+/* Dynamic topology */
+void sculpt_pbvh_clear(Object *ob);
+void sculpt_dyntopo_node_layers_add(struct SculptSession *ss);
+void sculpt_dynamic_topology_disable(bContext *C, struct SculptUndoNode *unode);
+
 /* Undo */
 
 typedef enum {
@@ -249,6 +258,7 @@ typedef enum {
   SCULPT_UNDO_DYNTOPO_END,
   SCULPT_UNDO_DYNTOPO_SYMMETRIZE,
   SCULPT_UNDO_GEOMETRY,
+  SCULPT_UNDO_FACE_SETS,
 } SculptUndoType;
 
 typedef struct SculptUndoNode {
@@ -298,6 +308,9 @@ typedef struct SculptUndoNode {
   float pivot_pos[3];
   float pivot_rot[4];
 
+  /* Sculpt Face Sets */
+  int *face_sets;
+
   size_t undo_size;
 } SculptUndoNode;
 
@@ -386,6 +399,7 @@ typedef struct SculptThreadedTaskData {
   bool mask_expand_invert_mask;
   bool mask_expand_use_normals;
   bool mask_expand_keep_prev_mask;
+  bool mask_expand_create_face_set;
 
   float transform_mats[8][4][4];
 
@@ -395,6 +409,8 @@ typedef struct SculptThreadedTaskData {
   float dirty_mask_max;
   bool dirty_mask_dirty_only;
 
+  int face_set;
+
   ThreadMutex mutex;
 
 } SculptThreadedTaskData;
@@ -528,6 +544,9 @@ typedef struct StrokeCache {
   bool is_rake_rotation_valid;
   struct SculptRakeData rake_data;
 
+  /* Face Sets */
+  int paint_face_set;
+
   /* Symmetry index between 0 and 7 bit combo 0 is Brush only;
    * 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */
   int symmetry;
@@ -612,6 +631,11 @@ typedef struct FilterCache {
   float *edge_factor;
   float *prev_mask;
   float mask_expand_initial_co[3];
+
+  int new_face_set;
+  int *prev_face_set;
+
+  int active_face_set;
 } FilterCache;
 
 void sculpt_cache_calc_brushdata_symm(StrokeCache *cache,
index 044d5b3c0b0cf5867b05c51569496e850d5ab66d..d1896f47c523999cfcf6b17f411f8493fbfa4840 100644 (file)
@@ -79,8 +79,7 @@ static void update_cb(PBVHNode *node, void *rebuild)
   BKE_pbvh_node_mark_update(node);
   BKE_pbvh_node_mark_update_mask(node);
   if (*((bool *)rebuild)) {
-    BKE_pbvh_node_mark_rebuild_draw(node);
-    BKE_pbvh_node_mark_visibility_update(node);
+    BKE_pbvh_node_mark_update_visibility(node);
   }
   BKE_pbvh_node_fully_hidden_set(node, 0);
 }
@@ -332,6 +331,15 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode)
   return true;
 }
 
+static bool sculpt_undo_restore_face_sets(bContext *C, SculptUndoNode *unode)
+{
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  Object *ob = OBACT(view_layer);
+  SculptSession *ss = ob->sculpt;
+  memcpy(ss->face_sets, unode->face_sets, ss->totpoly * sizeof(int));
+  return false;
+}
+
 static void sculpt_undo_bmesh_restore_generic_task_cb(
     void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
 {
@@ -533,6 +541,30 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
       BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
       return;
     }
+    else if (unode->type == SCULPT_UNDO_FACE_SETS) {
+
+      sculpt_undo_restore_face_sets(C, unode);
+
+      rebuild = true;
+      BKE_pbvh_search_callback(ss->pbvh, NULL, NULL, update_cb, &rebuild);
+
+      BKE_sculpt_update_object_for_edit(depsgraph, ob, true, need_mask);
+
+      sculpt_visibility_sync_all_face_sets_to_vertices(ss);
+      BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
+
+      if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+        BKE_mesh_flush_hidden_from_verts(ob->data);
+      }
+
+      if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+        DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+        DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+      }
+
+      unode->applied = true;
+      return;
+    }
   }
 
   BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
@@ -584,6 +616,8 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
           update_mask = true;
         }
         break;
+      case SCULPT_UNDO_FACE_SETS:
+        break;
 
       case SCULPT_UNDO_DYNTOPO_BEGIN:
       case SCULPT_UNDO_DYNTOPO_END:
@@ -619,10 +653,16 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
     };
     BKE_pbvh_search_callback(ss->pbvh, NULL, NULL, update_cb_partial, &data);
     BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw);
+
     if (update_mask) {
       BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask);
     }
 
+    if (update_visibility) {
+      sculpt_visibility_sync_all_vertex_to_face_sets(ss);
+      BKE_pbvh_update_visibility(ss->pbvh);
+    }
+
     if (BKE_sculpt_multires_active(scene, ob)) {
       if (rebuild) {
         multires_mark_as_modified(depsgraph, ob, MULTIRES_HIDDEN_MODIFIED);
@@ -642,10 +682,6 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
       BKE_sculptsession_free_deformMats(ss);
     }
 
-    if (update_visibility) {
-      BKE_pbvh_update_visibility(ss->pbvh);
-    }
-
     if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && update_visibility) {
       Mesh *mesh = ob->data;
       BKE_mesh_flush_hidden_from_verts(mesh);
@@ -714,6 +750,10 @@ static void sculpt_undo_free_list(ListBase *lb)
       CustomData_free(&unode->geom_pdata, unode->geom_totpoly);
     }
 
+    if (unode->face_sets) {
+      MEM_freeN(unode->face_sets);
+    }
+
     MEM_freeN(unode);
 
     unode = unode_next;
@@ -828,6 +868,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt
     case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
       BLI_assert(!"Dynamic topology should've already been handled");
     case SCULPT_UNDO_GEOMETRY:
+    case SCULPT_UNDO_FACE_SETS:
       break;
   }
 
@@ -939,6 +980,27 @@ static SculptUndoNode *sculpt_undo_geometry_push(Object *ob, SculptUndoType type
   return unode;
 }
 
+static SculptUndoNode *sculpt_undo_face_sets_push(Object *ob, SculptUndoType type)
+{
+  UndoSculpt *usculpt = sculpt_undo_get_nodes();
+  SculptSession *ss = ob->sculpt;
+
+  SculptUndoNode *unode = usculpt->nodes.first;
+
+  unode = MEM_callocN(sizeof(*unode), __func__);
+
+  BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname));
+  unode->type = type;
+  unode->applied = true;
+
+  unode->face_sets = MEM_callocN(ss->totpoly * sizeof(int), "sculpt face sets");
+  memcpy(unode->face_sets, ss->face_sets, ss->totpoly * sizeof(int));
+
+  BLI_addtail(&usculpt->nodes, unode);
+
+  return unode;
+}
+
 static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, SculptUndoType type)
 {
   UndoSculpt *usculpt = sculpt_undo_get_nodes();
@@ -1022,6 +1084,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
       case SCULPT_UNDO_DYNTOPO_END:
       case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
       case SCULPT_UNDO_GEOMETRY:
+      case SCULPT_UNDO_FACE_SETS:
         break;
     }
   }
@@ -1051,6 +1114,11 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
     BLI_thread_unlock(LOCK_CUSTOM1);
     return unode;
   }
+  else if (type == SCULPT_UNDO_FACE_SETS) {
+    unode = sculpt_undo_face_sets_push(ob, type);
+    BLI_thread_unlock(LOCK_CUSTOM1);
+    return unode;
+  }
   else if ((unode = sculpt_undo_get_node(node))) {
     BLI_thread_unlock(LOCK_CUSTOM1);
     return unode;
@@ -1091,6 +1159,7 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
     case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
       BLI_assert(!"Dynamic topology should've already been handled");
     case SCULPT_UNDO_GEOMETRY:
+    case SCULPT_UNDO_FACE_SETS:
       break;
   }
 
index c4540cc1b1a4b8776fb081ea61abd575cb47b8ea..f5f7f9ee07c95d69d82762ed9c7b76a684b9a764 100644 (file)
@@ -54,6 +54,7 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
                                               const struct MLoopTri *looptri,
                                               const struct MVert *verts,
                                               const int *face_indices,
+                                              const int *sculpt_facemap,
                                               const int face_indices_len,
                                               const struct Mesh *mesh);
 
@@ -70,7 +71,8 @@ void GPU_pbvh_grid_buffers_update_free(GPU_PBVH_Buffers *buffers,
 /* Update mesh buffers without topology changes. Threaded. */
 enum {
   GPU_PBVH_BUFFERS_SHOW_MASK = (1 << 1),
-  GPU_PBVH_BUFFERS_SHOW_VCOL = (1 << 1),
+  GPU_PBVH_BUFFERS_SHOW_VCOL = (1 << 2),
+  GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS = (1 << 3),
 };
 
 void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
@@ -79,6 +81,8 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
                                   int totvert,
                                   const float *vmask,
                                   const struct MLoopCol *vcol,
+                                  const int *sculpt_face_sets,
+                                  const int face_sets_color_seed,
                                   const int (*face_vert_indices)[3],
                                   const int update_flags);
 
index bf7b19083219a8e643d6616ab27689fb24dbef7f..b4d18ba0928564a428385b8c9fae3dcf67716f7c 100644 (file)
 #include "MEM_guardedalloc.h"
 
 #include "BLI_bitmap.h"
+#include "BLI_math_color.h"
 #include "BLI_math.h"
 #include "BLI_utildefines.h"
 #include "BLI_ghash.h"
+#include "BLI_hash.h"
 
 #include "DNA_meshdata_types.h"
 
@@ -95,7 +97,7 @@ struct GPU_PBVH_Buffers {
 
 static struct {
   GPUVertFormat format;
-  uint pos, nor, msk, col;
+  uint pos, nor, msk, col, fset;
 } g_vbo_id = {{0}};
 
 /** \} */
@@ -117,6 +119,8 @@ void gpu_pbvh_init()
         &g_vbo_id.format, "msk", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
     g_vbo_id.col = GPU_vertformat_attr_add(
         &g_vbo_id.format, "ac", GPU_COMP_U16, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
+    g_vbo_id.fset = GPU_vertformat_attr_add(
+        &g_vbo_id.format, "fset", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
   }
 }
 
@@ -191,11 +195,15 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
                                   int totvert,
                                   const float *vmask,
                                   const MLoopCol *vcol,
+                                  const int *sculpt_face_sets,
+                                  const int face_sets_color_seed,
                                   const int (*face_vert_indices)[3],
                                   const int update_flags)
 {
   const bool show_mask = vmask && (update_flags & GPU_PBVH_BUFFERS_SHOW_MASK) != 0;
   const bool show_vcol = vcol && (update_flags & GPU_PBVH_BUFFERS_SHOW_VCOL) != 0;
+  const bool show_face_sets = sculpt_face_sets &&
+                              (update_flags & GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS) != 0;
   bool empty_mask = true;
 
   {
@@ -207,12 +215,12 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
       GPUVertBufRaw nor_step = {0};
       GPUVertBufRaw msk_step = {0};
       GPUVertBufRaw col_step = {0};
+      GPUVertBufRaw fset_step = {0};
 
       GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.pos, &pos_step);
       GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.nor, &nor_step);
-      if (show_mask) {
-        GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.msk, &msk_step);
-      }
+      GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.msk, &msk_step);
+      GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.fset, &fset_step);
       if (show_vcol) {
         GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.col, &col_step);
       }
@@ -227,10 +235,34 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
           copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), v->co);
           copy_v3_v3_short(GPU_vertbuf_raw_step(&nor_step), v->no);
 
+          float mask;
           if (show_mask) {
-            float mask = vmask[vidx];
-            *(float *)GPU_vertbuf_raw_step(&msk_step) = mask;
-            empty_mask = empty_mask && (mask == 0.0f);
+            mask = vmask[vidx];
+          }
+          else {
+            mask = 0.0f;
+          }
+          *(float *)GPU_vertbuf_raw_step(&msk_step) = mask;
+          empty_mask = empty_mask && (mask == 0.0f);
+        }
+
+        /* Face Sets. */
+        uchar face_set_color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+        for (uint i = 0; i < buffers->face_indices_len; i++) {
+          if (show_face_sets) {
+            const MLoopTri *lt = &buffers->looptri[buffers->face_indices[i]];
+            float rgba[4];
+            hsv_to_rgb(BLI_hash_int_01(abs(sculpt_face_sets[lt->poly]) + face_sets_color_seed),
+                       0.65f,
+                       1.0f,
+                       &rgba[0],
+                       &rgba[1],
+                       &rgba[2]);
+            rgba_float_to_uchar(face_set_color, rgba);
+          }
+          for (int j = 0; j < 3; j++) {
+            const int vidx = face_vert_indices[i][j];
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vidx, &face_set_color);
           }
         }
 
@@ -268,6 +300,10 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
             continue;
           }
 
+          if (sculpt_face_sets[lt->poly] <= 0) {
+            continue;
+          }
+
           /* Face normal and mask */
           if (lt->poly != mpoly_prev) {
             const MPoly *mp = &buffers->mpoly[lt->poly];
@@ -277,6 +313,18 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
             mpoly_prev = lt->poly;
           }
 
+          uchar face_set_color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+          if (show_face_sets) {
+            float rgba[4];
+            hsv_to_rgb(BLI_hash_int_01(abs(sculpt_face_sets[lt->poly]) + face_sets_color_seed),
+                       0.65f,
+                       1.0f,
+                       &rgba[0],
+                       &rgba[1],
+                       &rgba[2]);
+            rgba_float_to_uchar(face_set_color, rgba);
+          }
+
           float fmask = 0.0f;
           if (show_mask) {
             fmask = (vmask[vtri[0]] + vmask[vtri[1]] + vmask[vtri[2]]) / 3.0f;
@@ -287,10 +335,10 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
 
             copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), v->co);
             copy_v3_v3_short(GPU_vertbuf_raw_step(&nor_step), no);
-            if (show_mask) {
-              *(float *)GPU_vertbuf_raw_step(&msk_step) = fmask;
-              empty_mask = empty_mask && (fmask == 0.0f);
-            }
+            *(float *)GPU_vertbuf_raw_step(&msk_step) = fmask;
+            empty_mask = empty_mask && (fmask == 0.0f);
+            /* Face Sets. */
+            memcpy(GPU_vertbuf_raw_step(&fset_step), face_set_color, sizeof(uchar) * 3);
 
             if (show_vcol) {
               const uint loop_index = lt->tri[j];
@@ -326,6 +374,7 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
                                               const MLoopTri *looptri,
                                               const MVert *mvert,
                                               const int *face_indices,
+                                              const int *sculpt_face_sets,
                                               const int face_indices_len,
                                               const struct Mesh *mesh)
 {
@@ -343,7 +392,8 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
   /* Count the number of visible triangles */
   for (i = 0, tottri = 0; i < face_indices_len; i++) {
     const MLoopTri *lt = &looptri[face_indices[i]];
-    if (!paint_is_face_hidden(lt, mvert, mloop)) {
+    if (!paint_is_face_hidden(lt, mvert, mloop) && sculpt_face_sets &&
+        sculpt_face_sets[lt->poly] > 0) {
       int r_edges[3];
       BKE_mesh_looptri_get_real_edges(mesh, lt, r_edges);
       for (int j = 0; j < 3; j++) {
@@ -411,7 +461,8 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
       const MLoopTri *lt = &looptri[face_indices[i]];
 
       /* Skip hidden faces */
-      if (paint_is_face_hidden(lt, mvert, mloop)) {
+      if (paint_is_face_hidden(lt, mvert, mloop) ||
+          (sculpt_face_sets && sculpt_face_sets[lt->poly] < 0)) {
         continue;
       }
 
@@ -668,6 +719,9 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
               GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index, &vcol);
             }
 
+            uchar fsets[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index, &fsets);
+
             vbo_index += 1;
           }
         }
@@ -705,14 +759,14 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
             GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.nor, vbo_index + 3, no_short);
 
             if (has_mask && show_mask) {
-              float fmask = (*CCG_elem_mask(key, elems[0]) + *CCG_elem_mask(key, elems[1]) +
+              float fsets = (*CCG_elem_mask(key, elems[0]) + *CCG_elem_mask(key, elems[1]) +
                              *CCG_elem_mask(key, elems[2]) + *CCG_elem_mask(key, elems[3])) *
                             0.25f;
-              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 0, &fmask);
-              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 1, &fmask);
-              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 2, &fmask);
-              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 3, &fmask);
-              empty_mask = empty_mask && (fmask == 0.0f);
+              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 0, &fsets);
+              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 1, &fsets);
+              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 2, &fsets);
+              GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 3, &fsets);
+              empty_mask = empty_mask && (fsets == 0.0f);
             }
 
             ushort vcol[4] = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX};
@@ -720,6 +774,13 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
             GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 1, &vcol);
             GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 2, &vcol);
             GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 3, &vcol);
+
+            uchar fsets[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 0, &fsets);
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 1, &fsets);
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 2, &fsets);
+            GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 3, &fsets);
+
             vbo_index += 4;
           }
         }
@@ -794,6 +855,10 @@ static void gpu_bmesh_vert_to_buffer_copy(BMVert *v,
     ushort vcol[4] = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX};
     GPU_vertbuf_attr_set(vert_buf, g_vbo_id.col, v_index, &vcol);
   }
+
+  /* Add default face sets color to avoid artifacts. */
+  uchar face_set[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+  GPU_vertbuf_attr_set(vert_buf, g_vbo_id.fset, v_index, &face_set);
 }
 
 /* Return the total number of vertices that don't have BM_ELEM_HIDDEN set */
index 2b7e9246d36a5ad387194e93494a455b28d588d4..0109fba909b14d425dc402430d843d3f9280daa8 100644 (file)
@@ -227,6 +227,7 @@ typedef enum eBrushClothForceFalloffType {
 
 typedef enum eAutomasking_flag {
   BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0),
+  BRUSH_AUTOMASKING_FACE_SETS = (1 << 1),
 } eAutomasking_flag;
 
 typedef struct Brush {
@@ -540,6 +541,7 @@ typedef enum eBrushSculptTool {
   SCULPT_TOOL_SLIDE_RELAX = 24,
   SCULPT_TOOL_CLAY_THUMB = 25,
   SCULPT_TOOL_CLOTH = 26,
+  SCULPT_TOOL_DRAW_FACE_SETS = 27,
 } eBrushSculptTool;
 
 /* Brush.uv_sculpt_tool */
@@ -582,6 +584,7 @@ typedef enum eBrushUVSculptTool {
         SCULPT_TOOL_SLIDE_RELAX, \
         SCULPT_TOOL_ELASTIC_DEFORM, \
         SCULPT_TOOL_POSE, \
+        SCULPT_TOOL_DRAW_FACE_SETS, \
 \
         /* These brushes could handle dynamic topology, \ \
          * but user feedback indicates it's better not to */ \
index 552de61df8a0b31e05b0678169a3a64da625fa98..4d0d66f29fff68ac80c41968ba280a539b3bac61 100644 (file)
@@ -76,8 +76,7 @@ typedef struct CustomData {
    * MUST be >= CD_NUMTYPES, but we cant use a define here.
    * Correct size is ensured in CustomData_update_typemap assert().
    */
-  int typemap[42];
-  char _pad0[4];
+  int typemap[43];
   /** Number of layers, size of layers array. */
   int totlayer, maxlayer;
   /** In editmode, total size of all data layers. */
@@ -146,8 +145,9 @@ typedef enum CustomDataType {
   CD_MLOOPTANGENT = 39,
   CD_TESSLOOPNORMAL = 40,
   CD_CUSTOMLOOPNORMAL = 41,
+  CD_SCULPT_FACE_SETS = 42,
 
-  CD_NUMTYPES = 42,
+  CD_NUMTYPES = 43,
 } CustomDataType;
 
 /* Bits for CustomDataMask */
@@ -195,6 +195,7 @@ typedef enum CustomDataType {
 #define CD_MASK_MLOOPTANGENT (1LL << CD_MLOOPTANGENT)
 #define CD_MASK_TESSLOOPNORMAL (1LL << CD_TESSLOOPNORMAL)
 #define CD_MASK_CUSTOMLOOPNORMAL (1LL << CD_CUSTOMLOOPNORMAL)
+#define CD_MASK_SCULPT_FACE_SETS (1LL << CD_SCULPT_FACE_SETS)
 
 /** Data types that may be defined for all mesh elements types. */
 #define CD_MASK_GENERIC_DATA (CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR)
index f605827d120da4ebaba168687470d381e59f20b9..275bcc6456275211b6696ecda1bc6897b1d7b1b0 100644 (file)
@@ -35,6 +35,7 @@
     .texflag = ME_AUTOSPACE, \
     .remesh_voxel_size = 0.1f, \
     .remesh_voxel_adaptivity = 0.0f, \
+    .face_sets_color_seed = 0, \
     .flag = ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME, \
   }
 
index 86ea22690ee3fda94fd3f90db0c554dd6c34d52b..c0c7c0465bb25dbf95fbc370c7570b3428260bff 100644 (file)
@@ -196,7 +196,11 @@ typedef struct Mesh {
   float remesh_voxel_size;
   float remesh_voxel_adaptivity;
   char remesh_mode;
-  char _pad1[3];
+
+  char _pad1[7];
+
+  int face_sets_color_seed;
+
   /** Deprecated multiresolution modeling data, only keep for loading old files. */
   struct Multires *mr DNA_DEPRECATED;
 
@@ -258,6 +262,7 @@ enum {
   ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12,
   ME_REMESH_FIX_POLES = 1 << 13,
   ME_REMESH_REPROJECT_VOLUME = 1 << 14,
+  ME_REMESH_REPROJECT_SCULPT_FACE_SETS = 1 << 15,
 };
 
 /* me->cd_flag */
index 71f701b87adbda95b36ca22cbe97602dc3e4a9e6..8617f122d5596bcb7b620595dc712fb53d0c36db 100644 (file)
@@ -2211,6 +2211,9 @@ typedef enum eSculptFlags {
 
   /* Don't display mask in viewport, but still use it for strokes. */
   SCULPT_HIDE_MASK = (1 << 15),
+
+  /* Don't display face sets in viewport. */
+  SCULPT_HIDE_FACE_SETS = (1 << 16),
 } eSculptFlags;
 
 /* ImagePaintSettings.mode */
index f6c8c0b1f6d1cab5a61a2a9c5b15a4b8f3dde976..c139d4dc0d1f503f0051ed6f035b3dfe31cb4071 100644 (file)
@@ -59,6 +59,7 @@
     /* Intentionally different to vertex/paint mode, \
      * we typically want to see shading too. */ \
     .sculpt_mode_mask_opacity = 0.75f, \
+    .sculpt_mode_face_sets_opacity = 0.0f, \
  \
     .edit_flag = V3D_OVERLAY_EDIT_FACES | V3D_OVERLAY_EDIT_SEAMS | \
                              V3D_OVERLAY_EDIT_SHARP | V3D_OVERLAY_EDIT_FREESTYLE_EDGE | \
index c45a1480087e3397b4cfc5ee1c52cb18802d3b5f..ce0c68055d918629eae91c64f3c730655f8b0d89 100644 (file)
@@ -210,9 +210,9 @@ typedef struct View3DOverlay {
   float vertex_paint_mode_opacity;
   float weight_paint_mode_opacity;
   float sculpt_mode_mask_opacity;
+  float sculpt_mode_face_sets_opacity;
 
   /** Armature edit/pose mode settings. */
-  int _pad3;
   float xray_alpha_bone;
 
   /** Other settings. */
index cfaaa0cd1b350ed04897b14d1d78f288edc859f9..c2dbb2973a370e603487c5f7cf5c5d895608aaff 100644 (file)
@@ -99,6 +99,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
     {SCULPT_TOOL_CLOTH, "CLOTH", ICON_BRUSH_SCULPT_DRAW, "Cloth", ""},
     {SCULPT_TOOL_SIMPLIFY, "SIMPLIFY", ICON_BRUSH_DATA, "Simplify", ""},
     {SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
+    {SCULPT_TOOL_DRAW_FACE_SETS, "DRAW_FACE_SETS", ICON_BRUSH_MASK, "Draw Face Sets", ""},
     {0, NULL, 0, NULL, NULL},
 };
 /* clang-format on */
@@ -2152,6 +2153,13 @@ static void rna_def_brush(BlenderRNA *brna)
                            "Affect only vertices connected to the active vertex under the brush");
   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");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "use_scene_spacing", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
   RNA_def_property_enum_items(prop, brush_spacing_unit_items);
index 689b36ffea089eb31d757d19697cb6a4a8cae5e8..1721095167a96e4474aa4d03dcdae8f467efaf07 100644 (file)
@@ -3030,6 +3030,13 @@ static void rna_def_mesh(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Preserve Paint Mask", "Keep the current mask on the new mesh");
   RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
 
+  prop = RNA_def_property(srna, "use_remesh_preserve_sculpt_face_sets", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_REPROJECT_SCULPT_FACE_SETS);
+  RNA_def_property_boolean_default(prop, false);
+  RNA_def_property_ui_text(
+      prop, "Preserve Face Sets", "Keep the current Face Sets on the new mesh");
+  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
+
   prop = RNA_def_property(srna, "remesh_mode", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "remesh_mode");
   RNA_def_property_enum_items(prop, rna_enum_mesh_remesh_mode_items);
index 1457bbfd3c39d81c5f75e89e61d7ef7a71925dc6..14cfe9d29ab71472cc072776bfcaf156d7f31643 100644 (file)
@@ -805,6 +805,12 @@ 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_ShowMask_update");
 
+  prop = RNA_def_property(srna, "show_face_sets", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_negative_sdna(prop, NULL, "flags", SCULPT_HIDE_FACE_SETS);
+  RNA_def_property_ui_text(prop, "Show Face Sets", "Show Face Sets as overlay on object");
+  RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
+  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_ShowMask_update");
+
   prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_PIXEL);
   RNA_def_property_ui_range(prop, 0.5, 40.0, 10, 2);
   RNA_def_property_ui_text(
index 63817137a02ade2baccbc36f2ada37c7ccbd3cf3..cc9f4b301d51607332dab36a4cf7953dea0dc91d 100644 (file)
@@ -3763,6 +3763,12 @@ static void rna_def_space_view3d_overlay(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
 
+  prop = RNA_def_property(srna, "sculpt_mode_face_sets_opacity", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "overlay.sculpt_mode_face_sets_opacity");
+  RNA_def_property_ui_text(prop, "Sculpt Face Sets Opacity", "");
+  RNA_def_property_range(prop, 0.0f, 1.0f);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+
   /* grease pencil paper settings */
   prop = RNA_def_property(srna, "show_annotation", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag2", V3D_SHOW_ANNOTATION);