Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/mesh/CMakeLists.txt1
-rw-r--r--source/blender/editors/mesh/editmesh_fair.c169
-rw-r--r--source/blender/editors/mesh/mesh_intern.h3
-rw-r--r--source/blender/editors/mesh/mesh_ops.c2
-rw-r--r--source/blender/editors/object/object_modes.c16
-rw-r--r--source/blender/editors/object/object_relations.c87
-rw-r--r--source/blender/editors/object/object_remesh.cc11
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt5
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c116
-rw-r--r--source/blender/editors/sculpt_paint/paint_mask.c317
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c16
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c1275
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_array.c982
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_automasking.c12
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_boundary.c101
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_cloth.c160
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_detail.c2
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_expand.c112
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_face_set.c299
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_face_set_topology.c208
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_color.c2
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mask.c852
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mesh.c94
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_gradient.c253
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h136
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_mask_expand.c4
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_poly_loop.c309
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_pose.c134
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_smooth.c189
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_symmetrize.c302
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_transform.c156
31 files changed, 6203 insertions, 122 deletions
diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt
index 35bf295a678..7ca120fa95a 100644
--- a/source/blender/editors/mesh/CMakeLists.txt
+++ b/source/blender/editors/mesh/CMakeLists.txt
@@ -46,6 +46,7 @@ set(SRC
editmesh_extrude_screw.c
editmesh_extrude_spin.c
editmesh_extrude_spin_gizmo.c
+ editmesh_fair.c
editmesh_inset.c
editmesh_intersect.c
editmesh_knife.c
diff --git a/source/blender/editors/mesh/editmesh_fair.c b/source/blender/editors/mesh/editmesh_fair.c
new file mode 100644
index 00000000000..7f43043176e
--- /dev/null
+++ b/source/blender/editors/mesh/editmesh_fair.c
@@ -0,0 +1,169 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edmesh
+ *
+ * Interactive editmesh knife tool.
+ */
+
+#ifdef _MSC_VER
+# define _USE_MATH_DEFINES
+#endif
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_object_types.h"
+
+#include "BLI_alloca.h"
+#include "BLI_array.h"
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_memarena.h"
+#include "BLI_smallhash.h"
+#include "BLI_string.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_context.h"
+#include "BKE_editmesh.h"
+#include "BKE_editmesh_bvh.h"
+#include "BKE_layer.h"
+#include "BKE_mesh_fair.h"
+#include "BKE_report.h"
+
+#include "ED_mesh.h"
+#include "ED_screen.h"
+#include "ED_space_api.h"
+#include "ED_view3d.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DNA_object_types.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "mesh_intern.h" /* own include */
+
+static EnumPropertyItem prop_edit_mesh_fair_selection_mode_items[] = {
+ {
+ MESH_FAIRING_DEPTH_POSITION,
+ "POSITION",
+ 0,
+ "Position",
+ "Fair positions",
+ },
+ {
+ MESH_FAIRING_DEPTH_TANGENCY,
+ "TANGENCY",
+ 0,
+ "Tangency",
+ "Fair tangency",
+ },
+ /*
+ {
+ MESH_FAIRING_DEPTH_CURVATURE,
+ "CURVATURE",
+ 0,
+ "Curvature",
+ "Fair curvature",
+ },
+ */
+ {0, NULL, 0, NULL, NULL},
+};
+
+static int edbm_fair_vertices_exec(bContext *C, wmOperator *op)
+{
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ uint objects_len = 0;
+ Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
+ view_layer, CTX_wm_view3d(C), &objects_len);
+ for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
+ Object *obedit = objects[ob_index];
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ if ((em->bm->totvertsel == 0)) {
+ continue;
+ }
+
+ BMesh *bm = em->bm;
+ BMVert *v;
+ BMIter iter;
+ int i;
+ bool *fairing_mask = MEM_calloc_arrayN(bm->totvert, sizeof(bool), "fairing mask");
+ BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
+ if (!BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+ continue;
+ }
+ if (BM_vert_is_boundary(v)) {
+ continue;
+ }
+ if (!BM_vert_is_manifold(v)) {
+ continue;
+ }
+ fairing_mask[i] = true;
+ }
+ BKE_bmesh_prefair_and_fair_vertices(bm, fairing_mask, mode);
+ MEM_freeN(fairing_mask);
+
+ EDBM_mesh_normals_update(em);
+ EDBM_update(obedit->data,
+ &(const struct EDBMUpdate_Params){
+ .calc_looptri = false,
+ .calc_normals = true,
+ .is_destructive = true,
+ });
+ }
+
+ MEM_freeN(objects);
+ return OPERATOR_FINISHED;
+}
+
+void MESH_OT_fair_vertices(wmOperatorType *ot)
+{
+ /* description */
+ ot->name = "Fair Vertices";
+ ot->idname = "MESH_OT_fair_vertices";
+ ot->description = "Create a smooth as possible geometry patch";
+
+ /* callbacks */
+ ot->exec = edbm_fair_vertices_exec;
+ ot->poll = ED_operator_editmesh;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_enum(ot->srna,
+ "mode",
+ prop_edit_mesh_fair_selection_mode_items,
+ MESH_FAIRING_DEPTH_POSITION,
+ "Mode",
+ "");
+}
diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h
index 03c99e40d1e..8dbca8a4ea8 100644
--- a/source/blender/editors/mesh/mesh_intern.h
+++ b/source/blender/editors/mesh/mesh_intern.h
@@ -268,6 +268,9 @@ void MESH_OT_paint_mask_extract(struct wmOperatorType *ot);
void MESH_OT_face_set_extract(struct wmOperatorType *ot);
void MESH_OT_paint_mask_slice(struct wmOperatorType *ot);
+/* *** editmesh_fair.c *** */
+void MESH_OT_fair_vertices(struct wmOperatorType *ot);
+
struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf);
#if defined(WITH_FREESTYLE)
diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c
index 94823b92c44..d98e03405a4 100644
--- a/source/blender/editors/mesh/mesh_ops.c
+++ b/source/blender/editors/mesh/mesh_ops.c
@@ -175,6 +175,8 @@ void ED_operatortypes_mesh(void)
WM_operatortype_append(MESH_OT_bevel);
+ WM_operatortype_append(MESH_OT_fair_vertices);
+
WM_operatortype_append(MESH_OT_bridge_edge_loops);
WM_operatortype_append(MESH_OT_inset);
WM_operatortype_append(MESH_OT_offset_edge_loops);
diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c
index 2c58ef02486..da38f7a627c 100644
--- a/source/blender/editors/object/object_modes.c
+++ b/source/blender/editors/object/object_modes.c
@@ -21,11 +21,17 @@
* actual mode switching logic is per-object type.
*/
+#include "MEM_guardedalloc.h"
+
#include "DNA_gpencil_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_workspace_types.h"
+#include "PIL_time.h"
+
#include "BLI_kdopbvh.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@@ -38,6 +44,8 @@
#include "BKE_gpencil_modifier.h"
#include "BKE_layer.h"
#include "BKE_main.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_mesh_types.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_paint.h"
@@ -50,6 +58,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
+#include "RNA_enum_types.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -57,10 +66,13 @@
#include "ED_armature.h"
#include "ED_gpencil.h"
#include "ED_screen.h"
+#include "ED_space_api.h"
#include "ED_transform_snap_object_context.h"
#include "ED_undo.h"
#include "ED_view3d.h"
+#include "UI_resources.h"
+
#include "WM_toolsystem.h"
#include "ED_object.h" /* own include */
@@ -408,7 +420,7 @@ bool ED_object_mode_generic_has_data(struct Depsgraph *depsgraph, const struct O
/** \} */
-/* -------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------- */
/** \name Transfer Mode
*
* Enters the same mode of the current active object in another object,
@@ -588,4 +600,4 @@ void OBJECT_OT_transfer_mode(wmOperatorType *ot)
"Flash the target object when transferring the mode");
}
-/** \} */
+/** \} */ \ No newline at end of file
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 75269dffec8..2d535b12b08 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -38,6 +38,7 @@
#include "DNA_light_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
#include "DNA_meta_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
@@ -63,6 +64,7 @@
#include "BKE_constraint.h"
#include "BKE_context.h"
#include "BKE_curve.h"
+#include "BKE_customdata.h"
#include "BKE_displist.h"
#include "BKE_editmesh.h"
#include "BKE_fcurve.h"
@@ -85,9 +87,11 @@
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_object.h"
+#include "BKE_paint.h"
#include "BKE_pointcloud.h"
#include "BKE_report.h"
#include "BKE_scene.h"
+#include "BKE_screen.h"
#include "BKE_speaker.h"
#include "BKE_texture.h"
#include "BKE_volume.h"
@@ -113,6 +117,7 @@
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
+#include "ED_sculpt.h"
#include "ED_view3d.h"
#include "object_intern.h"
@@ -2753,6 +2758,63 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C,
return result;
}
+static void drop_named_material_face_set_slots_update(bContext *C,
+ Object *ob,
+ const wmEvent *event)
+{
+ Main *bmain = CTX_data_main(C);
+ Mesh *mesh = BKE_mesh_from_object(ob);
+
+ bScreen *screen = CTX_wm_screen(C);
+ ARegion *region = BKE_screen_find_main_region_at_xy(screen, SPACE_VIEW3D, event->x, event->y);
+
+ const float mval[2] = {event->x - region->winrct.xmin, event->y - region->winrct.ymin};
+ const int face_set_id = ED_sculpt_face_sets_active_update_and_get(C, ob, mval);
+
+ int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS);
+
+ short face_set_nr = -1;
+ for (int i = 0; i < mesh->totpoly; i++) {
+ if (face_sets[i] != face_set_id) {
+ continue;
+ }
+ face_set_nr = mesh->mpoly[i].mat_nr;
+ break;
+ }
+
+ bool create_new_slot = false;
+ for (int i = 0; i < mesh->totpoly; i++) {
+ if (face_sets[i] == face_set_id) {
+ if (mesh->mpoly[i].mat_nr != face_set_nr) {
+ create_new_slot = true;
+ break;
+ }
+ }
+ else {
+ if (mesh->mpoly[i].mat_nr == face_set_nr) {
+ create_new_slot = true;
+ break;
+ }
+ }
+ }
+
+ if (create_new_slot) {
+ BKE_object_material_slot_add(bmain, ob);
+ }
+ else {
+ ob->actcol = face_set_nr + 1;
+ }
+
+ const short active_mat_slot = ob->actcol;
+ const short material_nr = active_mat_slot - 1;
+ for (int i = 0; i < mesh->totpoly; i++) {
+ if (face_sets[i] != face_set_id) {
+ continue;
+ }
+ mesh->mpoly[i].mat_nr = material_nr;
+ }
+}
+
static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Main *bmain = CTX_data_main(C);
@@ -2769,6 +2831,10 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
}
+ if (ob->mode == OB_MODE_SCULPT) {
+ drop_named_material_face_set_slots_update(C, ob, event);
+ }
+
BKE_object_material_assign(CTX_data_main(C), ob, ma, mat_slot, BKE_MAT_ASSIGN_USERPREF);
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
@@ -2780,6 +2846,25 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_FINISHED;
}
+bool ED_operator_drop_material_poll(bContext *C)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *obact = CTX_data_active_object(C);
+
+ if (scene == NULL || ID_IS_LINKED(scene)) {
+ return false;
+ }
+ if (CTX_data_edit_object(C)) {
+ return false;
+ }
+
+ if (obact && !ELEM(obact->mode, OB_MODE_OBJECT, OB_MODE_SCULPT)) {
+ return false;
+ }
+
+ return true;
+}
+
/* used for dropbox */
/* assigns to object under cursor, only first material slot */
void OBJECT_OT_drop_named_material(wmOperatorType *ot)
@@ -2790,7 +2875,7 @@ void OBJECT_OT_drop_named_material(wmOperatorType *ot)
/* api callbacks */
ot->invoke = drop_named_material_invoke;
- ot->poll = ED_operator_objectmode;
+ ot->poll = ED_operator_drop_material_poll;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc
index d56cb3c7548..e859b8fa85a 100644
--- a/source/blender/editors/object/object_remesh.cc
+++ b/source/blender/editors/object/object_remesh.cc
@@ -171,7 +171,8 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
BKE_mesh_calc_normals(new_mesh);
if (mesh->flag & ME_REMESH_REPROJECT_VOLUME || mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK ||
- mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
+ mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS ||
+ mesh->flag & ME_REMESH_REPROJECT_MATERIALS) {
BKE_mesh_runtime_clear_geometry(mesh);
}
@@ -187,11 +188,19 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
BKE_remesh_reproject_sculpt_face_sets(new_mesh, mesh);
}
+ if (mesh->flag & ME_REMESH_REPROJECT_MATERIALS) {
+ BKE_remesh_reproject_materials(new_mesh, mesh);
+ }
+
if (mesh->flag & ME_REMESH_REPROJECT_VERTEX_COLORS) {
BKE_mesh_runtime_clear_geometry(mesh);
BKE_remesh_reproject_vertex_paint(new_mesh, mesh);
}
+ if (ob->mode == OB_MODE_SCULPT) {
+ BKE_mesh_remesh_sculpt_array_update(ob, new_mesh, mesh);
+ }
+
BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
if (smooth_normals) {
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index 3b668a1bd4c..c8c88e9d412 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -56,6 +56,7 @@ set(SRC
paint_vertex_weight_ops.c
paint_vertex_weight_utils.c
sculpt.c
+ sculpt_array.c
sculpt_automasking.c
sculpt_boundary.c
sculpt_cloth.c
@@ -63,15 +64,19 @@ set(SRC
sculpt_dyntopo.c
sculpt_expand.c
sculpt_face_set.c
+ sculpt_face_set_topology.c
sculpt_filter_color.c
sculpt_filter_mask.c
sculpt_filter_mesh.c
sculpt_geodesic.c
+ sculpt_gradient.c
sculpt_mask_expand.c
sculpt_mask_init.c
sculpt_multiplane_scrape.c
sculpt_paint_color.c
sculpt_pose.c
+ sculpt_poly_loop.c
+ sculpt_symmetrize.c
sculpt_smooth.c
sculpt_transform.c
sculpt_undo.c
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index ab2b2f4b16b..100ba5baf3b 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -37,6 +37,7 @@
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "DNA_view3d_types.h"
+#include "DNA_mesh_types.h"
#include "BKE_brush.h"
#include "BKE_colortools.h"
@@ -1383,7 +1384,7 @@ static void paint_cursor_sculpt_session_update_and_init(PaintCursorContext *pcon
pcontext->prev_active_vertex_index = ss->active_vertex_index;
if (!ups->stroke_active) {
pcontext->is_cursor_over_mesh = SCULPT_cursor_geometry_info_update(
- C, &gi, mouse, (pcontext->brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE));
+ C, &gi, mouse, (pcontext->brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE), false);
copy_v3_v3(pcontext->location, gi.location);
copy_v3_v3(pcontext->normal, gi.normal);
}
@@ -1464,6 +1465,103 @@ static void paint_draw_3D_view_inactive_brush_cursor(PaintCursorContext *pcontex
80);
}
+static void sculpt_cursor_draw_active_face_set_color_set(PaintCursorContext *pcontext) {
+
+ SculptSession *ss = pcontext->ss;
+
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return;
+ }
+
+ const int active_face_set = SCULPT_active_face_set_get(ss);
+ uchar color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ Object *ob = CTX_data_active_object(pcontext->C);
+ Mesh *mesh = ob->data;
+ if (active_face_set != mesh->face_sets_color_default) {
+ BKE_paint_face_set_overlay_color_get(active_face_set, mesh->face_sets_color_seed, color);
+ color[3] = UCHAR_MAX;
+ }
+ else {
+ color[3] /= 2;
+ }
+
+
+ immUniformColor4ubv(color);
+}
+
+static void sculpt_cursor_draw_3D_face_set_preview(PaintCursorContext *pcontext)
+{
+
+ SculptSession *ss = pcontext->ss;
+
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return;
+ }
+
+ GPU_line_width(1.0f);
+ sculpt_cursor_draw_active_face_set_color_set(pcontext);
+
+ MPoly *poly = &ss->mpoly[ss->active_face_index];
+ MLoop *loops = ss->mloop;
+ const int totpoints = poly->totloop;
+
+ /*
+ immBegin(GPU_PRIM_LINE_STRIP, totpoints + 1);
+ for (int i = 0; i < totpoints; i++) {
+ float co[3];
+ copy_v3_v3(co, SCULPT_vertex_co_get(ss, loops[poly->loopstart + i].v));
+ immVertex3fv(pcontext->pos, co);
+ }
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart].v));
+ immEnd();
+ */
+
+
+ /*
+ int v_in_poly = 0;
+ for (int i = 0; i < totpoints; i++) {
+ if (ss->active_vertex_index == loops[poly->loopstart + i].v) {
+ v_in_poly = i;
+ }
+ }
+ const int next_v = v_in_poly == poly->totloop - 1? 0 : v_in_poly + 1;
+ const int prev_v = v_in_poly == 0? poly->totloop - 1 : v_in_poly - 1;
+
+
+ immBegin(GPU_PRIM_LINES, 4);
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex_index));
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + next_v].v));
+
+
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex_index));
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + prev_v].v));
+
+ immEnd();
+ */
+
+ if (!ss->pmap) {
+ return;
+ }
+
+ int total = 0;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex_index, ni) {
+ total++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+
+
+
+ immBegin(GPU_PRIM_LINES, total * 2);
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex_index, ni) {
+ immVertex3fv(pcontext->pos, SCULPT_active_vertex_co_get(ss));
+ immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ni.index));
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ immEnd();
+}
+
static void paint_cursor_update_object_space_radius(PaintCursorContext *pcontext)
{
if (!BKE_brush_use_locked_size(pcontext->scene, pcontext->brush)) {
@@ -1609,6 +1707,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
}
if (len_v3v3(active_vertex_co, pcontext->location) < pcontext->radius) {
immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha);
+ sculpt_cursor_draw_active_face_set_color_set(pcontext);
cursor_draw_point_with_symmetry(pcontext->pos,
pcontext->region,
active_vertex_co,
@@ -1654,6 +1753,13 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
2);
}
+
+ /* Transform Pivot. */
+ if (pcontext->paint && pcontext->paint->flags & PAINT_SCULPT_SHOW_PIVOT) {
+ cursor_draw_point_screen_space(
+ pcontext->pos, pcontext->region, pcontext->ss->pivot_pos, pcontext->vc.obact->obmat, 2);
+ }
+
if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
paint_cursor_preview_boundary_data_update(pcontext, update_previews);
paint_cursor_preview_boundary_data_pivot_draw(pcontext);
@@ -1692,6 +1798,9 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
SCULPT_boundary_pivot_line_preview_draw(pcontext->pos, pcontext->ss);
}
+ /* Face Set Preview. */
+ sculpt_cursor_draw_3D_face_set_preview(pcontext);
+
GPU_matrix_pop();
/* Drawing Cursor overlays in Paint Cursor space (as additional info on top of the brush cursor)
@@ -1776,6 +1885,11 @@ static void paint_cursor_cursor_draw_3d_view_brush_cursor_active(PaintCursorCont
pcontext->pos, brush, ss, pcontext->outline_col, pcontext->outline_alpha);
}
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ /* Draw Array Path. */
+ SCULPT_array_path_draw(pcontext->pos, brush, ss);
+ }
+
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) {
SCULPT_cloth_plane_falloff_preview_draw(
diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c
index e65d6ce2d48..a0f4bac2e7c 100644
--- a/source/blender/editors/sculpt_paint/paint_mask.c
+++ b/source/blender/editors/sculpt_paint/paint_mask.c
@@ -44,6 +44,7 @@
#include "BKE_context.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
+#include "BKE_mesh_fair.h"
#include "BKE_multires.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
@@ -946,6 +947,24 @@ static EnumPropertyItem prop_trim_orientation_types[] = {
{0, NULL, 0, NULL, NULL},
};
+typedef enum eSculptTrimLocationType {
+ SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE,
+ SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME,
+} eSculptTrimLocationType;
+static EnumPropertyItem prop_trim_location_types[] = {
+ {SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE,
+ "DEPTH_SURFACE",
+ 0,
+ "Surface",
+ "Use the surface under the cursor to locate the trimming shape"},
+ {SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME,
+ "DEPTH_VOLUME",
+ 0,
+ "Volume",
+ "Use the volume of the mesh to locate the trimming shape in the center of the volume"},
+ {0, NULL, 0, NULL, NULL},
+};
+
typedef struct SculptGestureTrimOperation {
SculptGestureOperation op;
@@ -959,6 +978,7 @@ typedef struct SculptGestureTrimOperation {
eSculptTrimOperationType mode;
eSculptTrimOrientationType orientation;
+ eSculptTrimLocationType location;
} SculptGestureTrimOperation;
static void sculpt_gesture_trim_normals_update(SculptGestureContext *sgcontext)
@@ -1053,8 +1073,19 @@ static void sculpt_gesture_trim_calculate_depth(SculptGestureContext *sgcontext)
if (trim_operation->use_cursor_depth) {
float world_space_gesture_initial_location[3];
- mul_v3_m4v3(
- world_space_gesture_initial_location, vc->obact->obmat, ss->gesture_initial_location);
+
+ switch (trim_operation->location) {
+ case SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE: {
+ mul_v3_m4v3(
+ world_space_gesture_initial_location, vc->obact->obmat, ss->gesture_initial_location);
+
+ } break;
+ case SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME: {
+ float center_co[3];
+ mid_v3_v3v3(center_co, ss->gesture_initial_location, ss->gesture_initial_back_location);
+ mul_v3_m4v3(world_space_gesture_initial_location, vc->obact->obmat, center_co);
+ } break;
+ }
float mid_point_depth;
if (trim_operation->orientation == SCULPT_GESTURE_TRIM_ORIENTATION_VIEW) {
@@ -1067,6 +1098,17 @@ static void sculpt_gesture_trim_calculate_depth(SculptGestureContext *sgcontext)
/* When using normal orientation, if the stroke started over the mesh, position the mid point
* at 0 distance from the shape plane. This positions the trimming shape half inside of the
* surface. */
+ if (SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME) {
+ mid_point_depth = ss->gesture_initial_hit ?
+ dist_signed_to_plane_v3(world_space_gesture_initial_location,
+ shape_plane) :
+ (trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
+ }
+ else {
+ mid_point_depth = ss->gesture_initial_hit ?
+ 0.0f :
+ (trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
+ }
mid_point_depth = ss->gesture_initial_hit ?
0.0f :
(trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
@@ -1358,6 +1400,7 @@ static void sculpt_gesture_init_trim_properties(SculptGestureContext *sgcontext,
trim_operation->mode = RNA_enum_get(op->ptr, "trim_mode");
trim_operation->use_cursor_depth = RNA_boolean_get(op->ptr, "use_cursor_depth");
trim_operation->orientation = RNA_enum_get(op->ptr, "trim_orientation");
+ trim_operation->location = RNA_enum_get(op->ptr, "trim_location");
/* If the cursor was not over the mesh, force the orientation to view. */
if (!sgcontext->ss->gesture_initial_hit) {
@@ -1385,18 +1428,52 @@ static void sculpt_trim_gesture_operator_properties(wmOperatorType *ot)
SCULPT_GESTURE_TRIM_ORIENTATION_VIEW,
"Shape Orientation",
NULL);
+
+ RNA_def_enum(ot->srna,
+ "trim_location",
+ prop_trim_location_types,
+ SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE,
+ "Shape Location",
+ NULL);
}
/* Project Gesture Operation. */
+typedef enum eSculptProjectDeformationMode {
+ SCULPT_GESTURE_PROJECT_DEFORM_PROJECT,
+ SCULPT_GESTURE_PROJECT_DEFORM_FAIR,
+} eSculptProjectDeformationMode;
+
+static EnumPropertyItem prop_project_deformation_mode_types[] = {
+ {SCULPT_GESTURE_PROJECT_DEFORM_PROJECT,
+ "PROJECT",
+ 0,
+ "Project to Plane",
+ "Project the affected geometry into the gesture plane"},
+ {SCULPT_GESTURE_PROJECT_DEFORM_FAIR,
+ "FAIR",
+ 0,
+ "Fair Positions",
+ "Use position fairing in the affected area"},
+ {0, NULL, 0, NULL, NULL},
+};
typedef struct SculptGestureProjectOperation {
SculptGestureOperation operation;
+ eSculptProjectDeformationMode deformation_mode;
+ bool *fairing_mask;
} SculptGestureProjectOperation;
static void sculpt_gesture_project_begin(bContext *C, SculptGestureContext *sgcontext)
{
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
- BKE_sculpt_update_object_for_edit(depsgraph, sgcontext->vc.obact, false, false, false);
+ BKE_sculpt_update_object_for_edit(depsgraph, sgcontext->vc.obact, true, false, false);
+
+ if (project_operation->deformation_mode == SCULPT_GESTURE_PROJECT_DEFORM_FAIR) {
+ const int totvert = SCULPT_vertex_count_get(sgcontext->ss);
+ project_operation->fairing_mask = MEM_calloc_arrayN(totvert, sizeof(bool), "fairing mask");
+ }
}
static void project_line_gesture_apply_task_cb(void *__restrict userdata,
@@ -1439,27 +1516,138 @@ static void project_line_gesture_apply_task_cb(void *__restrict userdata,
}
}
+static void project_gesture_tag_fairing_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptGestureContext *sgcontext = userdata;
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
+
+ PBVHNode *node = sgcontext->nodes[i];
+ PBVHVertexIter vd;
+ bool any_updated = false;
+
+ SCULPT_undo_push_node(sgcontext->vc.obact, node, SCULPT_UNDO_COORDS);
+
+ BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_gesture_is_vertex_effected(sgcontext, &vd)) {
+ continue;
+ }
+ project_operation->fairing_mask[vd.index] = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ any_updated = true;
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (any_updated) {
+ BKE_pbvh_node_mark_update(node);
+ }
+}
+
+static void project_gesture_project_fairing_boundary_task_cb(
+ void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptGestureContext *sgcontext = userdata;
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
+ SculptSession *ss = sgcontext->ss;
+
+ PBVHNode *node = sgcontext->nodes[i];
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ bool project_vertex = false;
+ bool vertex_fairing_mask = project_operation->fairing_mask[vd.index];
+
+ if (!project_operation->fairing_mask[vd.index]) {
+ // continue;
+ }
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
+ if (project_operation->fairing_mask[ni.index] != vertex_fairing_mask) {
+ project_vertex = true;
+ break;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ if (!project_vertex) {
+ continue;
+ }
+
+ closest_to_plane_v3(vd.co, sgcontext->line.plane, vd.co);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
static void sculpt_gesture_project_apply_for_symmetry_pass(bContext *UNUSED(C),
SculptGestureContext *sgcontext)
{
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, sgcontext->totnode);
- switch (sgcontext->shape_type) {
- case SCULPT_GESTURE_SHAPE_LINE:
+ switch (project_operation->deformation_mode) {
+ case SCULPT_GESTURE_PROJECT_DEFORM_PROJECT:
+ BLI_assert(sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE);
BLI_task_parallel_range(
0, sgcontext->totnode, sgcontext, project_line_gesture_apply_task_cb, &settings);
break;
- case SCULPT_GESTURE_SHAPE_LASSO:
- case SCULPT_GESTURE_SHAPE_BOX:
- /* Gesture shape projection not implemented yet. */
- BLI_assert(false);
+ case SCULPT_GESTURE_PROJECT_DEFORM_FAIR:
+ BLI_task_parallel_range(
+ 0, sgcontext->totnode, sgcontext, project_gesture_tag_fairing_task_cb, &settings);
+ if (sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE) {
+ /* TODO: this needs to loop over all nodes to avoid artifacts. */
+ /*
+ BLI_task_parallel_range(0,
+ sgcontext->totnode,
+ sgcontext,
+ project_gesture_project_fairing_boundary_task_cb,
+ &settings);
+ */
+ }
break;
}
}
+static void sculpt_gesture_fairing_apply(SculptGestureContext *sgcontext)
+{
+ SculptSession *ss = sgcontext->vc.obact->sculpt;
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ Mesh *mesh = sgcontext->vc.obact->data;
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+ BKE_mesh_prefair_and_fair_vertices(
+ mesh, mvert, project_operation->fairing_mask, MESH_FAIRING_DEPTH_POSITION);
+ } break;
+ case PBVH_BMESH: {
+ BKE_bmesh_prefair_and_fair_vertices(
+ ss->bm, project_operation->fairing_mask, MESH_FAIRING_DEPTH_POSITION);
+ } break;
+ case PBVH_GRIDS:
+ BLI_assert(false);
+ }
+}
+
static void sculpt_gesture_project_end(bContext *C, SculptGestureContext *sgcontext)
{
+ SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
+ sgcontext->operation;
+ if (project_operation->deformation_mode == SCULPT_GESTURE_PROJECT_DEFORM_FAIR) {
+ sculpt_gesture_fairing_apply(sgcontext);
+ MEM_SAFE_FREE(project_operation->fairing_mask);
+ }
+
SculptSession *ss = sgcontext->ss;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
if (ss->deform_modifiers_active || ss->shapekey_active) {
@@ -1470,20 +1658,38 @@ static void sculpt_gesture_project_end(bContext *C, SculptGestureContext *sgcont
SCULPT_flush_update_done(C, sgcontext->vc.obact, SCULPT_UPDATE_COORDS);
}
-static void sculpt_gesture_init_project_properties(SculptGestureContext *sgcontext,
- wmOperator *UNUSED(op))
+static void sculpt_gesture_init_project_properties(SculptGestureContext *sgcontext, wmOperator *op)
{
- sgcontext->operation = MEM_callocN(sizeof(SculptGestureFaceSetOperation), "Project Operation");
+ sgcontext->operation = MEM_callocN(sizeof(SculptGestureProjectOperation), "Project Operation");
SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *)
sgcontext->operation;
+ if (sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE) {
+ project_operation->deformation_mode = RNA_enum_get(op->ptr, "deformation_mode");
+ }
+ else {
+ /* All gesture shapes that are not a line need to be deformed by fairing as they can't be
+ * projected to a plane. */
+ project_operation->deformation_mode = SCULPT_GESTURE_PROJECT_DEFORM_FAIR;
+ }
+
project_operation->operation.sculpt_gesture_begin = sculpt_gesture_project_begin;
project_operation->operation.sculpt_gesture_apply_for_symmetry_pass =
sculpt_gesture_project_apply_for_symmetry_pass;
project_operation->operation.sculpt_gesture_end = sculpt_gesture_project_end;
}
+static void sculpt_project_gesture_operator_properties(wmOperatorType *ot)
+{
+ RNA_def_enum(ot->srna,
+ "deformation_mode",
+ prop_project_deformation_mode_types,
+ SCULPT_GESTURE_PROJECT_DEFORM_PROJECT,
+ "Deformation mode",
+ NULL);
+}
+
static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op)
{
SculptGestureContext *sgcontext = sculpt_gesture_init_from_box(C, op);
@@ -1577,7 +1783,7 @@ static int sculpt_trim_gesture_box_invoke(bContext *C, wmOperator *op, const wmE
SculptCursorGeometryInfo sgi;
float mouse[2] = {event->mval[0], event->mval[1]};
SCULPT_vertex_random_access_ensure(ss);
- ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
if (ss->gesture_initial_hit) {
copy_v3_v3(ss->gesture_initial_location, sgi.location);
copy_v3_v3(ss->gesture_initial_normal, sgi.normal);
@@ -1618,9 +1824,10 @@ static int sculpt_trim_gesture_lasso_invoke(bContext *C, wmOperator *op, const w
SculptCursorGeometryInfo sgi;
float mouse[2] = {event->mval[0], event->mval[1]};
SCULPT_vertex_random_access_ensure(ss);
- ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, true);
if (ss->gesture_initial_hit) {
copy_v3_v3(ss->gesture_initial_location, sgi.location);
+ copy_v3_v3(ss->gesture_initial_back_location, sgi.back_location);
copy_v3_v3(ss->gesture_initial_normal, sgi.normal);
}
@@ -1639,6 +1846,44 @@ static int project_gesture_line_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
+static int project_gesture_lasso_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
+ /* Fairing operations are not supported in Multires. */
+ return OPERATOR_CANCELLED;
+ }
+
+ SculptGestureContext *sgcontext = sculpt_gesture_init_from_lasso(C, op);
+ if (!sgcontext) {
+ return OPERATOR_CANCELLED;
+ }
+ sculpt_gesture_init_project_properties(sgcontext, op);
+ sculpt_gesture_apply(C, sgcontext);
+ sculpt_gesture_context_free(sgcontext);
+ return OPERATOR_FINISHED;
+}
+
+static int project_gesture_box_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
+ /* Fairing operations are not supported in Multires. */
+ return OPERATOR_CANCELLED;
+ }
+
+ SculptGestureContext *sgcontext = sculpt_gesture_init_from_box(C, op);
+ if (!sgcontext) {
+ return OPERATOR_CANCELLED;
+ }
+ sculpt_gesture_init_project_properties(sgcontext, op);
+ sculpt_gesture_apply(C, sgcontext);
+ sculpt_gesture_context_free(sgcontext);
+ return OPERATOR_FINISHED;
+}
+
void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot)
{
ot->name = "Mask Lasso Gesture";
@@ -1799,4 +2044,48 @@ void SCULPT_OT_project_line_gesture(wmOperatorType *ot)
/* Properties. */
WM_operator_properties_gesture_straightline(ot, WM_CURSOR_EDIT);
sculpt_gesture_operator_properties(ot);
+
+ sculpt_project_gesture_operator_properties(ot);
+}
+
+void SCULPT_OT_project_lasso_gesture(wmOperatorType *ot)
+{
+ ot->name = "Project Lasso Gesture";
+ ot->idname = "SCULPT_OT_project_lasso_gesture";
+ ot->description = "Project by fairing the geometry to the curve defined by the lasso gesture";
+
+ ot->invoke = WM_gesture_lasso_invoke;
+ ot->modal = WM_gesture_lasso_modal;
+ ot->exec = project_gesture_lasso_exec;
+
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+
+ /* Properties. */
+ WM_operator_properties_gesture_lasso(ot);
+ sculpt_gesture_operator_properties(ot);
+
+ sculpt_project_gesture_operator_properties(ot);
+}
+
+void SCULPT_OT_project_box_gesture(wmOperatorType *ot)
+{
+ ot->name = "Project Box Gesture";
+ ot->idname = "SCULPT_OT_project_box_gesture";
+ ot->description = "Project by fairing the geometry to the box defined by the gesture";
+
+ ot->invoke = WM_gesture_box_invoke;
+ ot->modal = WM_gesture_box_modal;
+ ot->exec = project_gesture_box_exec;
+
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+
+ /* Properties. */
+ WM_operator_properties_border(ot);
+ sculpt_gesture_operator_properties(ot);
+
+ sculpt_project_gesture_operator_properties(ot);
}
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index de01bc3a474..938b7acd74b 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -227,6 +227,7 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode)
case PAINT_MODE_SCULPT:
if (ELEM(brush->sculpt_tool,
SCULPT_TOOL_GRAB,
+ SCULPT_TOOL_ARRAY,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_POSE,
SCULPT_TOOL_BOUNDARY,
@@ -269,6 +270,7 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m
case PAINT_MODE_SCULPT:
if (ELEM(brush->sculpt_tool,
SCULPT_TOOL_GRAB,
+ SCULPT_TOOL_ARRAY,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_SNAKE_HOOK,
@@ -679,7 +681,12 @@ static float paint_space_stroke_spacing(bContext *C,
ePaintMode mode = BKE_paintmode_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
float size_clamp = 0.0f;
- float size = BKE_brush_size_get(scene, stroke->brush) * size_pressure;
+ float final_size_pressure = size_pressure;
+ if (brush->pressure_size_curve) {
+ BKE_curvemapping_init(brush->pressure_size_curve);
+ final_size_pressure = BKE_curvemapping_evaluateF(brush->pressure_size_curve, 0, size_pressure);
+ }
+ float size = BKE_brush_size_get(scene, stroke->brush) * final_size_pressure;
if (paint_stroke_use_scene_spacing(brush, mode)) {
if (!BKE_brush_use_locked_size(scene, brush)) {
float last_object_space_position[3];
@@ -688,7 +695,7 @@ static float paint_space_stroke_spacing(bContext *C,
size_clamp = paint_calc_object_space_radius(&stroke->vc, last_object_space_position, size);
}
else {
- size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * size_pressure;
+ size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * final_size_pressure;
}
}
else {
@@ -788,6 +795,7 @@ static float paint_space_stroke_spacing_variable(bContext *C,
/* average spacing */
float last_spacing = paint_space_stroke_spacing(
C, scene, stroke, last_size_pressure, pressure);
+
float new_spacing = paint_space_stroke_spacing(C, scene, stroke, new_size_pressure, pressure);
return 0.5f * (last_spacing + new_spacing);
@@ -1037,6 +1045,7 @@ static bool sculpt_is_grab_tool(Brush *br)
SCULPT_TOOL_POSE,
SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_THUMB,
+ SCULPT_TOOL_ARRAY,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_SNAKE_HOOK);
}
@@ -1050,6 +1059,9 @@ bool paint_supports_dynamic_size(Brush *br, ePaintMode mode)
switch (mode) {
case PAINT_MODE_SCULPT:
+ if (br->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ return true;
+ }
if (sculpt_is_grab_tool(br)) {
return false;
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 7bde864e73f..d9afafd16d9 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -29,6 +29,7 @@
#include "BLI_ghash.h"
#include "BLI_gsqueue.h"
#include "BLI_hash.h"
+#include "BLI_linklist_stack.h"
#include "BLI_math.h"
#include "BLI_math_color_blend.h"
#include "BLI_task.h"
@@ -56,6 +57,7 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
+#include "BKE_mesh_fair.h"
#include "BKE_mesh_mapping.h"
#include "BKE_mesh_mirror.h"
#include "BKE_modifier.h"
@@ -73,9 +75,17 @@
#include "BKE_subsurf.h"
#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
#include "IMB_colormanagement.h"
+#include "GPU_batch.h"
+#include "GPU_batch_presets.h"
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
#include "WM_api.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
@@ -84,6 +94,8 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
+#include "ED_space_api.h"
+#include "ED_transform_snap_object_context.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
@@ -226,6 +238,10 @@ void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3]
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
case PBVH_BMESH:
+ if (ss->limit_surface) {
+ copy_v3_v3(r_co, ss->limit_surface[index]);
+ break;
+ }
copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, index));
break;
case PBVH_GRIDS: {
@@ -511,6 +527,31 @@ void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set)
}
}
+void SCULPT_vertex_face_set_increase(SculptSession *ss, int index, const int increase)
+{
+ 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]] += increase;
+ }
+ }
+ } break;
+ case PBVH_BMESH:
+ break;
+ case PBVH_GRIDS: {
+ const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh);
+ const int grid_index = index / key->grid_area;
+ const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index);
+ if (ss->face_sets[face_index] > 0) {
+ ss->face_sets[face_index] += increase;
+ }
+
+ } break;
+ }
+}
+
int SCULPT_vertex_face_set_get(SculptSession *ss, int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
@@ -1079,6 +1120,7 @@ void SCULPT_tag_update_overlays(bContext *C)
View3D *v3d = CTX_wm_view3d(C);
if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
}
@@ -1214,6 +1256,7 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_SMOOTH,
SCULPT_TOOL_BOUNDARY,
+ SCULPT_TOOL_FAIRING,
SCULPT_TOOL_POSE);
}
@@ -1222,12 +1265,17 @@ static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
return ELEM(sculpt_tool,
SCULPT_TOOL_SMOOTH,
SCULPT_TOOL_LAYER,
+ SCULPT_TOOL_FAIRING,
+ SCULPT_TOOL_SCENE_PROJECT,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_ARRAY,
+ SCULPT_TOOL_TWIST,
SCULPT_TOOL_DISPLACEMENT_SMEAR,
SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_PAINT,
SCULPT_TOOL_SMEAR,
+ SCULPT_TOOL_SYMMETRIZE,
SCULPT_TOOL_DRAW_FACE_SETS);
}
@@ -1250,6 +1298,7 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, const Brush *brush
SCULPT_TOOL_CREASE,
SCULPT_TOOL_DRAW,
SCULPT_TOOL_DRAW_SHARP,
+ SCULPT_TOOL_SCENE_PROJECT,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_LAYER,
SCULPT_TOOL_NUDGE,
@@ -2324,7 +2373,6 @@ static float brush_strength(const Sculpt *sd,
const float root_alpha = BKE_brush_alpha_get(scene, brush);
const float alpha = root_alpha * root_alpha;
const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f;
- const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f;
const float pen_flip = cache->pen_flip ? -1.0f : 1.0f;
const float invert = cache->invert ? -1.0f : 1.0f;
float overlap = ups->overlap_factor;
@@ -2336,20 +2384,31 @@ static float brush_strength(const Sculpt *sd,
flip = 1.0f;
}
+ float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f;
+ if (brush->pressure_strength_curve) {
+ }
+ BKE_curvemapping_init(brush->pressure_strength_curve);
+ pressure = BKE_brush_use_alpha_pressure(brush) && brush->pressure_strength_curve ?
+ BKE_curvemapping_evaluateF(brush->pressure_strength_curve, 0, cache->pressure) :
+ pressure;
/* Pressure final value after being tweaked depending on the brush. */
float final_pressure;
switch (brush->sculpt_tool) {
case SCULPT_TOOL_CLAY:
- final_pressure = pow4f(pressure);
+ // final_pressure = pow4f(pressure);
overlap = (1.0f + overlap) / 2.0f;
- return 0.25f * alpha * flip * final_pressure * overlap * feather;
+ return 0.25f * alpha * flip * pressure * overlap * feather;
case SCULPT_TOOL_DRAW:
case SCULPT_TOOL_DRAW_SHARP:
case SCULPT_TOOL_LAYER:
+ case SCULPT_TOOL_SYMMETRIZE:
return alpha * flip * pressure * overlap * feather;
case SCULPT_TOOL_DISPLACEMENT_ERASER:
return alpha * pressure * overlap * feather;
+ case SCULPT_TOOL_FAIRING:
+ case SCULPT_TOOL_SCENE_PROJECT:
+ return alpha * pressure * overlap * feather;
case SCULPT_TOOL_CLOTH:
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
/* Grab deform uses the same falloff as a regular grab brush. */
@@ -2380,11 +2439,13 @@ static float brush_strength(const Sculpt *sd,
return alpha * pressure * overlap * feather;
case SCULPT_TOOL_CLAY_STRIPS:
/* Clay Strips needs less strength to compensate the curve. */
- final_pressure = powf(pressure, 1.5f);
- return alpha * flip * final_pressure * overlap * feather * 0.3f;
+ // final_pressure = powf(pressure, 1.5f);
+ return alpha * flip * pressure * overlap * feather * 0.3f;
+ case SCULPT_TOOL_TWIST:
+ return alpha * flip * pressure * overlap * feather * 0.3f;
case SCULPT_TOOL_CLAY_THUMB:
- final_pressure = pressure * pressure;
- return alpha * flip * final_pressure * overlap * feather * 1.3f;
+ // final_pressure = pressure * pressure;
+ return alpha * flip * pressure * overlap * feather * 1.3f;
case SCULPT_TOOL_MASK:
overlap = (1.0f + overlap) / 2.0f;
@@ -2425,8 +2486,13 @@ static float brush_strength(const Sculpt *sd,
return 0.5f * alpha * flip * pressure * overlap * feather;
}
- case SCULPT_TOOL_SMOOTH:
- return flip * alpha * pressure * feather;
+ case SCULPT_TOOL_SMOOTH: {
+ const float smooth_strength_base = flip * pressure * feather;
+ if (cache->alt_smooth) {
+ return smooth_strength_base * sd->smooth_strength_factor;
+ }
+ return smooth_strength_base * alpha;
+ }
case SCULPT_TOOL_PINCH:
if (flip > 0.0f) {
@@ -2449,6 +2515,10 @@ static float brush_strength(const Sculpt *sd,
case SCULPT_TOOL_GRAB:
return root_alpha * feather;
+ case SCULPT_TOOL_ARRAY:
+ //return root_alpha * feather;
+ return alpha * pressure ;
+
case SCULPT_TOOL_ROTATE:
return alpha * pressure * feather;
@@ -2871,9 +2941,18 @@ typedef struct {
const float *ray_start;
const float *ray_normal;
bool hit;
+ int hit_count;
+ bool back_hit;
float depth;
bool original;
+ /* Depth of the second raycast hit. */
+ float back_depth;
+
+ /* When the back depth is not needed, this can be set to false to avoid traversing unnecesary
+ * nodes. */
+ bool use_back_depth;
+
int active_vertex_index;
float *face_normal;
@@ -3117,6 +3196,162 @@ static void do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **node
/** \} */
+/* -------------------------------------------------------------------- */
+/** \name Sculpt Multires Displacement Eraser Brush
+ * \{ */
+
+static void do_fairing_brush_tag_store_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+ PBVHVertexIter vd;
+
+ const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
+ const Brush *brush = data->brush;
+
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+
+ if (SCULPT_vertex_is_boundary(ss, vd.index)) {
+ continue;
+ }
+
+ const float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ ss->cache->prefairing_co[vd.index],
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ if (fade == 0.0f) {
+ continue;
+ }
+
+ ss->cache->fairing_fade[vd.index] = max_ff(fade, ss->cache->fairing_fade[vd.index]);
+ ss->cache->fairing_mask[vd.index] = true;
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_fairing_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
+ return;
+ }
+
+ if (!ss->cache->fairing_mask) {
+ ss->cache->fairing_mask = MEM_malloc_arrayN(totvert, sizeof(bool), "fairing_mask");
+ ss->cache->fairing_fade = MEM_malloc_arrayN(totvert, sizeof(float), "fairing_fade");
+ ss->cache->prefairing_co = MEM_malloc_arrayN(totvert, sizeof(float) * 3, "prefairing_co");
+ }
+
+ if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
+ for (int i = 0; i < totvert; i++) {
+ ss->cache->fairing_mask[i] = false;
+ ss->cache->fairing_fade[i] = 0.0f;
+ copy_v3_v3(ss->cache->prefairing_co[i], SCULPT_vertex_co_get(ss, i));
+ }
+ }
+
+ SCULPT_boundary_info_ensure(ob);
+
+ /* Threaded loop over nodes. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_fairing_brush_tag_store_task_cb_ex, &settings);
+}
+
+static void do_fairing_brush_displace_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!ss->cache->fairing_mask[vd.index]) {
+ continue;
+ }
+ float disp[3];
+ sub_v3_v3v3(disp, vd.co, ss->cache->prefairing_co[vd.index]);
+ mul_v3_fl(disp, ss->cache->fairing_fade[vd.index]);
+ copy_v3_v3(vd.co, ss->cache->prefairing_co[vd.index]);
+ add_v3_v3(vd.co, disp);
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void sculpt_fairing_brush_exec_fairing_for_cache(Sculpt *sd, Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ BLI_assert(BKE_pbvh_type(ss->pbvh) != PBVH_GRIDS);
+ BLI_assert(ss->cache);
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ Mesh *mesh = ob->data;
+
+ if (!ss->cache->fairing_mask) {
+ return;
+ }
+
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+ BKE_mesh_prefair_and_fair_vertices(
+ mesh, mvert, ss->cache->fairing_mask, MESH_FAIRING_DEPTH_TANGENCY);
+ } break;
+ case PBVH_BMESH: {
+ BKE_bmesh_prefair_and_fair_vertices(
+ ss->bm, ss->cache->fairing_mask, MESH_FAIRING_DEPTH_TANGENCY);
+ } break;
+ case PBVH_GRIDS:
+ BLI_assert(false);
+ }
+
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_fairing_brush_displace_task_cb_ex, &settings);
+ MEM_freeN(nodes);
+}
+
+/** \} */
+
/** \name Sculpt Multires Displacement Smear Brush
* \{ */
@@ -3414,6 +3649,237 @@ static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
}
/* -------------------------------------------------------------------- */
+/** \name Sculpt Scene Project Brush
+ * \{ */
+
+static void sculpt_stroke_cache_snap_context_init(bContext *C, Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ StrokeCache *cache = ss->cache;
+
+ if (ss->cache && ss->cache->snap_context) {
+ return;
+ }
+
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Scene *scene = CTX_data_scene(C);
+ ARegion *region = CTX_wm_region(C);
+ View3D *v3d = CTX_wm_view3d(C);
+
+ cache->snap_context = ED_transform_snap_object_context_create_view3d(scene, 0, region, v3d);
+ cache->depsgraph = depsgraph;
+}
+
+static void sculpt_scene_project_view_ray_init(Object *ob,
+ const int vertex_index,
+ float r_ray_normal[3],
+ float r_ray_origin[3])
+{
+ SculptSession *ss = ob->sculpt;
+ float world_space_vertex_co[3];
+ mul_v3_m4v3(world_space_vertex_co, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index));
+ if (ss->cache->vc->rv3d->is_persp) {
+ sub_v3_v3v3(r_ray_normal, world_space_vertex_co, ss->cache->view_origin);
+ normalize_v3(r_ray_normal);
+ copy_v3_v3(r_ray_origin, ss->cache->view_origin);
+ }
+ else {
+ mul_v3_mat3_m4v3(r_ray_normal, ob->obmat, ss->cache->view_normal);
+ sub_v3_v3v3(r_ray_origin, world_space_vertex_co, r_ray_normal);
+ }
+}
+
+static void sculpt_scene_project_vertex_normal_ray_init(Object *ob,
+ const int vertex_index,
+ const float original_normal[3],
+ float r_ray_normal[3],
+ float r_ray_origin[3])
+{
+ SculptSession *ss = ob->sculpt;
+ mul_v3_m4v3(r_ray_normal, ob->obmat, original_normal);
+ normalize_v3(r_ray_normal);
+
+ mul_v3_m4v3(r_ray_origin, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index));
+}
+
+static void sculpt_scene_project_brush_normal_ray_init(Object *ob,
+ const int vertex_index,
+ float r_ray_normal[3],
+ float r_ray_origin[3])
+{
+ SculptSession *ss = ob->sculpt;
+ mul_v3_m4v3(r_ray_origin, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index));
+ mul_v3_m4v3(r_ray_normal, ob->obmat, ss->cache->sculpt_normal);
+ normalize_v3(r_ray_normal);
+}
+
+static bool sculpt_scene_project_raycast(SculptSession *ss,
+ const float ray_normal[3],
+ const float ray_origin[3],
+ const bool use_both_directions,
+ float r_loc[3])
+{
+ float hit_co[2][3];
+ float hit_len_squared[2];
+ bool any_hit = false;
+ bool hit = false;
+ hit = ED_transform_snap_object_project_ray(ss->cache->snap_context,
+ ss->cache->depsgraph,
+ &(const struct SnapObjectParams){
+ .snap_select = SNAP_NOT_ACTIVE,
+ },
+ ray_origin,
+ ray_normal,
+ NULL,
+ hit_co[0],
+ NULL);
+ if (hit) {
+ hit_len_squared[0] = len_squared_v3v3(hit_co[0], ray_origin);
+ any_hit |= hit;
+ }
+ else {
+ hit_len_squared[0] = FLT_MAX;
+ }
+
+ if (!use_both_directions) {
+ copy_v3_v3(r_loc, hit_co[0]);
+ return any_hit;
+ }
+
+ float ray_normal_flip[3];
+ mul_v3_v3fl(ray_normal_flip, ray_normal, -1.0f);
+
+ hit = ED_transform_snap_object_project_ray(ss->cache->snap_context,
+ ss->cache->depsgraph,
+ &(const struct SnapObjectParams){
+ .snap_select = SNAP_NOT_ACTIVE,
+ },
+ ray_origin,
+ ray_normal_flip,
+ NULL,
+ hit_co[1],
+ NULL);
+ if (hit) {
+ hit_len_squared[1] = len_squared_v3v3(hit_co[1], ray_origin);
+ any_hit |= hit;
+ }
+ else {
+ hit_len_squared[1] = FLT_MAX;
+ }
+
+ if (hit_len_squared[0] <= hit_len_squared[1]) {
+ copy_v3_v3(r_loc, hit_co[0]);
+ }
+ else {
+ copy_v3_v3(r_loc, hit_co[1]);
+ }
+ return any_hit;
+}
+
+static void do_scene_project_brush_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+
+ const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
+ const Brush *brush = data->brush;
+
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+
+ const float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ if (fade == 0.0f) {
+ continue;
+ }
+
+ float ray_normal[3];
+ float ray_origin[3];
+ bool use_both_directions = false;
+ switch (brush->scene_project_direction_type) {
+ case BRUSH_SCENE_PROJECT_DIRECTION_VIEW:
+ sculpt_scene_project_view_ray_init(data->ob, vd.index, ray_normal, ray_origin);
+ break;
+ case BRUSH_SCENE_PROJECT_DIRECTION_VERTEX_NORMAL: {
+ float normal[3];
+ normal_short_to_float_v3(normal, orig_data.no);
+ sculpt_scene_project_vertex_normal_ray_init(
+ data->ob, vd.index, normal, ray_normal, ray_origin);
+ use_both_directions = true;
+ } break;
+ case BRUSH_SCENE_PROJECT_DIRECTION_BRUSH_NORMAL:
+ sculpt_scene_project_brush_normal_ray_init(data->ob, vd.index, ray_normal, ray_origin);
+ use_both_directions = true;
+ break;
+ }
+
+ float world_space_hit_co[3];
+ float hit_co[3];
+ const bool hit = sculpt_scene_project_raycast(
+ ss, ray_normal, ray_origin, use_both_directions, world_space_hit_co);
+ if (!hit) {
+ continue;
+ }
+
+ mul_v3_m4v3(hit_co, data->ob->imat, world_space_hit_co);
+
+ float disp[3];
+ sub_v3_v3v3(disp, hit_co, vd.co);
+ mul_v3_fl(disp, fade);
+ add_v3_v3(vd.co, disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_scene_project_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ SCULPT_vertex_random_access_ensure(ob->sculpt);
+
+ /* Threaded loop over nodes. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_scene_project_brush_task_cb_ex, &settings);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Sculpt Topology Brush
* \{ */
@@ -3991,6 +4457,7 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata,
const int thread_id = BLI_task_parallel_thread_id(tls);
const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE;
+ const bool use_geodesic_dists = brush->flag2 & BRUSH_USE_SURFACE_FALLOFF;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
@@ -3998,10 +4465,19 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata,
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
continue;
}
+
+ float dist;
+ if (use_geodesic_dists) {
+ dist = ss->cache->geodesic_dists[ss->cache->mirror_symmetry_pass][vd.index];
+ }
+ else {
+ dist = sqrtf(test.dist);
+ }
+
float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
orig_data.co,
- sqrtf(test.dist),
+ dist,
orig_data.no,
NULL,
vd.mask ? *vd.mask : 0.0f,
@@ -4040,6 +4516,17 @@ static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
}
+ if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) {
+ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
+ const int symm_pass = ss->cache->mirror_symmetry_pass;
+ float location[3];
+ flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), symm_pass);
+ int v = SCULPT_nearest_vertex_get(sd, ob, location, ss->cache->radius, false);
+ ss->cache->geodesic_dists[symm_pass] = SCULPT_geodesic_from_vertex(
+ ob, v, ss->cache->initial_radius);
+ }
+ }
+
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
@@ -4096,28 +4583,46 @@ static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
float final_disp[3];
+
+ float orig_co[3];
+ if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) {
+ const float geodesic_dist =
+ ss->cache->geodesic_dists[ss->cache->mirror_symmetry_pass][vd.index];
+
+ if (geodesic_dist == FLT_MAX) {
+ continue;
+ }
+
+ float disp[3];
+ sub_v3_v3v3(disp, orig_data.co, ss->cache->initial_location);
+ normalize_v3(disp);
+ mul_v3_fl(disp, geodesic_dist);
+ add_v3_v3v3(orig_co, ss->cache->initial_location, disp);
+ }
+ else {
+ copy_v3_v3(orig_co, orig_data.co);
+ }
+
switch (brush->elastic_deform_type) {
case BRUSH_ELASTIC_DEFORM_GRAB:
- BKE_kelvinlet_grab(final_disp, &params, orig_data.co, location, grab_delta);
+ BKE_kelvinlet_grab(final_disp, &params, orig_co, location, grab_delta);
mul_v3_fl(final_disp, bstrength * 20.0f);
break;
case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: {
- BKE_kelvinlet_grab_biscale(final_disp, &params, orig_data.co, location, grab_delta);
+ BKE_kelvinlet_grab_biscale(final_disp, &params, orig_co, location, grab_delta);
mul_v3_fl(final_disp, bstrength * 20.0f);
break;
}
case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: {
- BKE_kelvinlet_grab_triscale(final_disp, &params, orig_data.co, location, grab_delta);
+ BKE_kelvinlet_grab_triscale(final_disp, &params, orig_co, location, grab_delta);
mul_v3_fl(final_disp, bstrength * 20.0f);
break;
}
case BRUSH_ELASTIC_DEFORM_SCALE:
- BKE_kelvinlet_scale(
- final_disp, &params, orig_data.co, location, ss->cache->sculpt_normal_symm);
+ BKE_kelvinlet_scale(final_disp, &params, orig_co, location, ss->cache->sculpt_normal_symm);
break;
case BRUSH_ELASTIC_DEFORM_TWIST:
- BKE_kelvinlet_twist(
- final_disp, &params, orig_data.co, location, ss->cache->sculpt_normal_symm);
+ BKE_kelvinlet_twist(final_disp, &params, orig_co, location, ss->cache->sculpt_normal_symm);
break;
}
@@ -4148,6 +4653,16 @@ static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
}
+ if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) {
+ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
+ const int symm_pass = ss->cache->mirror_symmetry_pass;
+ float location[3];
+ flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), symm_pass);
+ int v = SCULPT_nearest_vertex_get(sd, ob, location, ss->cache->initial_radius, false);
+ ss->cache->geodesic_dists[symm_pass] = SCULPT_geodesic_from_vertex(ob, v, FLT_MAX);
+ }
+ }
+
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
@@ -5187,6 +5702,12 @@ static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
continue;
}
+ float vertex_no[3];
+ SCULPT_vertex_normal_get(ss, vd.index, vertex_no);
+ if (dot_v3v3(area_no_sp, vertex_no) <= -0.1f) {
+ continue;
+ }
+
float intr[3];
float val[3];
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
@@ -5225,6 +5746,8 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
const float offset = SCULPT_brush_plane_offset_get(sd, ss);
const float displace = radius * (0.18f + offset);
+ SCULPT_vertex_random_access_ensure(ss);
+
/* The sculpt-plane normal (whatever its set to). */
float area_no_sp[3];
@@ -5308,6 +5831,294 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings);
}
+
+/****** Twist Brush **********/
+
+static void do_twist_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;
+ float(*mat)[4] = data->mat;
+ const float *area_no_sp = data->area_no_sp;
+ const float *area_co = data->area_co;
+
+ PBVHVertexIter vd;
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ float stroke_direction[3];
+ float stroke_line[2][3];
+ normalize_v3_v3(stroke_direction, ss->cache->grab_delta_symmetry);
+ copy_v3_v3(stroke_line[0], ss->cache->location);
+ add_v3_v3v3(stroke_line[1], stroke_line[0], stroke_direction);
+
+
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+ const float fade = SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ if (fade == 0.0f) {
+ continue;
+ }
+
+ float local_vert_co[3];
+ float rotation_axis[3] = {0.0, 1.0, 0.0};
+ float origin[3] = {0.0, 0.0, 0.0f};
+ float vertex_in_line[3];
+ float scaled_mat[4][4];
+ float scaled_mat_inv[4][4];
+
+
+
+ copy_m4_m4(scaled_mat, mat);
+ invert_m4(scaled_mat);
+ mul_v3_fl(scaled_mat[2], 0.7f * fade * (1.0f - bstrength));
+ invert_m4(scaled_mat);
+
+ invert_m4_m4(scaled_mat_inv, scaled_mat);
+
+
+ mul_v3_m4v3(local_vert_co, scaled_mat, vd.co);
+ closest_to_line_v3(vertex_in_line, local_vert_co, rotation_axis, origin);
+ float p_to_rotate[3];
+ sub_v3_v3v3(p_to_rotate, local_vert_co, vertex_in_line);
+ float p_rotated[3];
+ rotate_v3_v3v3fl(p_rotated, p_to_rotate, rotation_axis, 2.0f * bstrength * fade);
+ add_v3_v3(p_rotated, vertex_in_line);
+ mul_v3_m4v3(p_rotated, scaled_mat_inv, p_rotated);
+
+
+ float disp[3];
+ sub_v3_v3v3(disp, p_rotated, vd.co);
+ mul_v3_fl(disp, bstrength * fade);
+ add_v3_v3(vd.co, disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_twist_brush_post_smooth_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;
+ float(*mat)[4] = data->mat;
+ const float *area_no_sp = data->area_no_sp;
+ const float *area_co = data->area_co;
+
+ PBVHVertexIter vd;
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+
+ float local_vert_co[3];
+ float scaled_mat[4][4];
+ copy_m4_m4(scaled_mat, mat);
+ invert_m4(scaled_mat);
+ invert_m4(scaled_mat);
+ mul_v3_m4v3(local_vert_co, scaled_mat, vd.co);
+
+ const float brush_fade = SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ float smooth_fade = SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ local_vert_co[0],
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ if (brush_fade == 0.0f) {
+ //continue;
+ }
+
+ if (smooth_fade == 0.0f) {
+ //continue;
+ }
+
+
+ smooth_fade = 1.0f - min_ff(fabsf(local_vert_co[0]), 1.0f);
+ smooth_fade = pow3f(smooth_fade);
+
+ float rotation_axis[3] = {0.0, 1.0, 0.0};
+ float origin[3] = {0.0, 0.0, 0.0f};
+ float vertex_in_line[3];
+ float scaled_mat_inv[4][4];
+
+ float avg[3];
+ float val[3];
+ float disp[3];
+
+/*
+ SCULPT_neighbor_coords_average(ss, avg, vd.index);
+
+ sub_v3_v3v3(disp, avg, vd.co);
+ mul_v3_fl(disp, 1.0f - smooth_fade);
+ add_v3_v3(vd.co, disp);
+ */
+
+ float final_co[3];
+ SCULPT_relax_vertex(ss, &vd, clamp_f(smooth_fade, 0.0f, 1.0f), false, final_co);
+
+ sub_v3_v3v3(disp, final_co, vd.co);
+ add_v3_v3(vd.co, disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+
+static void do_twist_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float radius = flip ? -ss->cache->radius : ss->cache->radius;
+ const float offset = SCULPT_brush_plane_offset_get(sd, ss);
+ const float displace = radius * (0.18f + offset);
+
+
+ SCULPT_vertex_random_access_ensure(ss);
+ SCULPT_boundary_info_ensure(ob);
+
+ /* The sculpt-plane normal (whatever its set to). */
+ float area_no_sp[3];
+
+ /* Geometry normal */
+ float area_no[3];
+ float area_co[3];
+
+ float temp[3];
+ float mat[4][4];
+ float scale[4][4];
+ float tmat[4][4];
+
+ SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+ SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor);
+
+ if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
+ SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
+ }
+ else {
+ copy_v3_v3(area_no, area_no_sp);
+ }
+
+ /* Delay the first daub because grab delta is not setup. */
+ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
+ return;
+ }
+
+ if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
+ return;
+ }
+
+/*
+ mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
+ mul_v3_fl(temp, displace);
+ add_v3_v3(area_co, temp);
+ */
+
+ /* Initialize brush local-space matrix. */
+ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
+ mat[0][3] = 0.0f;
+ cross_v3_v3v3(mat[1], area_no, mat[0]);
+ mat[1][3] = 0.0f;
+ copy_v3_v3(mat[2], area_no);
+ mat[2][3] = 0.0f;
+ copy_v3_v3(mat[3], area_co);
+ mat[3][3] = 1.0f;
+ normalize_m4(mat);
+
+ /* Scale brush local space matrix. */
+ scale_m4_fl(scale, ss->cache->radius * 0.5f);
+ mul_m4_m4m4(tmat, mat, scale);
+
+ /* Scale rotation space. */
+ //mul_v3_fl(tmat[2], 0.5f);
+
+ float twist_mat[4][4];
+ invert_m4_m4(twist_mat, tmat);
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .area_no_sp = area_no_sp,
+ .area_co = area_co,
+ .mat = twist_mat,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_twist_brush_task_cb_ex, &settings);
+
+ scale_m4_fl(scale, ss->cache->radius);
+ mul_m4_m4m4(tmat, mat, scale);
+ float smooth_mat[4][4];
+ invert_m4_m4(smooth_mat, tmat);
+ data.mat = smooth_mat;
+
+ for (int i = 0; i < 2; i++) {
+ BLI_task_parallel_range(0, totnode, &data, do_twist_brush_post_smooth_task_cb_ex, &settings);
+ }
+}
+
+
static void do_fill_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
@@ -5817,6 +6628,7 @@ static void sculpt_topology_update(Sculpt *sd,
/* Free index based vertex info as it will become invalid after modifying the topology during the
* stroke. */
MEM_SAFE_FREE(ss->vertex_info.boundary);
+ MEM_SAFE_FREE(ss->vertex_info.symmetrize_map);
MEM_SAFE_FREE(ss->vertex_info.connected_component);
PBVHTopologyUpdateMode mode = 0;
@@ -5878,6 +6690,9 @@ static void do_brush_action_task_cb(void *__restrict userdata,
BKE_pbvh_node_mark_update(data->nodes[n]);
}
}
+ else if (data->brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ /* Do nothing, array brush does a single geometry undo push. */
+ }
else if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) {
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK);
BKE_pbvh_node_mark_update_mask(data->nodes[n]);
@@ -5908,6 +6723,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
return;
}
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY && type != PBVH_FACES) {
+ return;
+ }
+
/* Build a list of all nodes that are potentially within the brush's area of influence */
if (SCULPT_tool_needs_all_pbvh_nodes(brush)) {
@@ -5996,7 +6815,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
if (!ss->cache->cloth_sim) {
ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create(
- ss, 1.0f, 0.0f, 0.0f, false, true);
+ ss, 1.0f, 1.0f, 0.0f, false, true);
SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
}
SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
@@ -6018,6 +6837,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode);
}
+ else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_DIRECTIONAL) {
+ SCULPT_do_directional_smooth_brush(sd, ob, nodes, totnode);
+ }
+ else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_UNIFORM_WEIGHTS) {
+ SCULPT_do_uniform_weights_smooth_brush(sd, ob, nodes, totnode);
+ }
break;
case SCULPT_TOOL_CREASE:
do_crease_brush(sd, ob, nodes, totnode);
@@ -6058,6 +6883,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_CLAY_STRIPS:
do_clay_strips_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_TWIST:
+ do_twist_brush(sd, ob, nodes, totnode);
+ break;
case SCULPT_TOOL_MULTIPLANE_SCRAPE:
SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode);
break;
@@ -6116,6 +6944,18 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_SMEAR:
SCULPT_do_smear_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_FAIRING:
+ do_fairing_brush(sd, ob, nodes, totnode);
+ break;
+ case SCULPT_TOOL_SCENE_PROJECT:
+ do_scene_project_brush(sd, ob, nodes, totnode);
+ break;
+ case SCULPT_TOOL_SYMMETRIZE:
+ SCULPT_do_symmetrize_brush(sd, ob, nodes, totnode);
+ break;
+ case SCULPT_TOOL_ARRAY:
+ SCULPT_do_array_brush(sd, ob, nodes, totnode);
+ break;
}
if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) &&
@@ -6188,15 +7028,7 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
Object *ob = data->ob;
-
- /* These brushes start from original coordinates. */
- const bool use_orco = ELEM(data->brush->sculpt_tool,
- SCULPT_TOOL_GRAB,
- SCULPT_TOOL_ROTATE,
- SCULPT_TOOL_THUMB,
- SCULPT_TOOL_ELASTIC_DEFORM,
- SCULPT_TOOL_BOUNDARY,
- SCULPT_TOOL_POSE);
+ const bool use_orco = data->use_proxies_orco;
PBVHVertexIter vd;
PBVHProxyNode *proxies;
@@ -6228,7 +7060,14 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
add_v3_v3(val, proxies[p].co[vd.i]);
}
- SCULPT_clip(sd, ss, vd.co, val);
+ if (ss->filter_cache && ss->filter_cache->cloth_sim) {
+ /* When there is a simulation running in the filter cache that was created by a tool, combine
+ * the proxies into the simulation instead of directly into the mesh. */
+ SCULPT_clip(sd, ss, ss->filter_cache->cloth_sim->pos[vd.index], val);
+ }
+ else {
+ SCULPT_clip(sd, ss, vd.co, val);
+ }
if (ss->deform_modifiers_active) {
sculpt_flush_pbvhvert_deform(ob, &vd);
@@ -6252,16 +7091,47 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
}
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
+
+ const bool use_orco = ELEM(brush->sculpt_tool,
+ SCULPT_TOOL_GRAB,
+ SCULPT_TOOL_ROTATE,
+ SCULPT_TOOL_THUMB,
+ SCULPT_TOOL_BOUNDARY,
+ SCULPT_TOOL_ELASTIC_DEFORM,
+
+ SCULPT_TOOL_POSE);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
+ .use_proxies_orco = use_orco,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings);
+ MEM_SAFE_FREE(nodes);
+}
+
+void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ PBVHNode **nodes;
+ int totnode;
+
+ BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = nodes,
+ .use_proxies_orco = false,
};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings);
+
MEM_SAFE_FREE(nodes);
}
@@ -6384,6 +7254,7 @@ void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache,
flip_v3_v3(cache->last_location, cache->true_last_location, symm);
flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm);
flip_v3_v3(cache->view_normal, cache->true_view_normal, symm);
+ flip_v3_v3(cache->view_origin, cache->true_view_origin, symm);
flip_v3_v3(cache->initial_location, cache->true_initial_location, symm);
flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm);
@@ -6674,6 +7545,16 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Paint Brush";
case SCULPT_TOOL_SMEAR:
return "Smear Brush";
+ case SCULPT_TOOL_FAIRING:
+ return "Fairing Brush";
+ case SCULPT_TOOL_SCENE_PROJECT:
+ return "Scene Project";
+ case SCULPT_TOOL_SYMMETRIZE:
+ return "Symmetrize Brush";
+ case SCULPT_TOOL_TWIST:
+ return "Clay Strips Brush";
+ case SCULPT_TOOL_ARRAY:
+ return "Array Brush";
}
return "Sculpting";
@@ -6690,9 +7571,16 @@ void SCULPT_cache_free(StrokeCache *cache)
MEM_SAFE_FREE(cache->layer_displacement_factor);
MEM_SAFE_FREE(cache->prev_colors);
MEM_SAFE_FREE(cache->detail_directions);
+ MEM_SAFE_FREE(cache->prefairing_co);
+ MEM_SAFE_FREE(cache->fairing_fade);
+ MEM_SAFE_FREE(cache->fairing_mask);
MEM_SAFE_FREE(cache->prev_displacement);
MEM_SAFE_FREE(cache->limit_surface_co);
+ if (cache->snap_context) {
+ ED_transform_snap_object_context_destroy(cache->snap_context);
+ }
+
if (cache->pose_ik_chain) {
SCULPT_pose_ik_chain_free(cache->pose_ik_chain);
}
@@ -6701,6 +7589,9 @@ void SCULPT_cache_free(StrokeCache *cache)
if (cache->boundaries[i]) {
SCULPT_boundary_data_free(cache->boundaries[i]);
}
+ if (cache->geodesic_dists[i]) {
+ MEM_SAFE_FREE(cache->geodesic_dists[i]);
+ }
}
if (cache->cloth_sim) {
@@ -6862,6 +7753,8 @@ static void sculpt_update_cache_invariants(
mul_m3_v3(mat, viewDir);
normalize_v3_v3(cache->true_view_normal, viewDir);
+ copy_v3_v3(cache->true_view_origin, cache->vc->rv3d->viewinv[3]);
+
cache->supports_gravity = (!ELEM(brush->sculpt_tool,
SCULPT_TOOL_MASK,
SCULPT_TOOL_SMOOTH,
@@ -6918,6 +7811,11 @@ static void sculpt_update_cache_invariants(
static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size)
{
+ if (brush->pressure_size_curve) {
+ return initial_size *
+ BKE_curvemapping_evaluateF(brush->pressure_size_curve, 0, cache->pressure);
+ }
+
switch (brush->sculpt_tool) {
case SCULPT_TOOL_CLAY:
return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure));
@@ -6940,6 +7838,7 @@ static bool sculpt_needs_delta_from_anchored_origin(Brush *brush)
SCULPT_TOOL_GRAB,
SCULPT_TOOL_POSE,
SCULPT_TOOL_BOUNDARY,
+ SCULPT_TOOL_ARRAY,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ELASTIC_DEFORM)) {
return true;
@@ -6960,6 +7859,7 @@ static bool sculpt_needs_delta_for_tip_orientation(Brush *brush)
}
return ELEM(brush->sculpt_tool,
SCULPT_TOOL_CLAY_STRIPS,
+ SCULPT_TOOL_TWIST,
SCULPT_TOOL_PINCH,
SCULPT_TOOL_MULTIPLANE_SCRAPE,
SCULPT_TOOL_CLAY_THUMB,
@@ -6984,12 +7884,14 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_CLAY_STRIPS,
+ SCULPT_TOOL_TWIST,
SCULPT_TOOL_PINCH,
SCULPT_TOOL_MULTIPLANE_SCRAPE,
SCULPT_TOOL_CLAY_THUMB,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_POSE,
SCULPT_TOOL_BOUNDARY,
+ SCULPT_TOOL_ARRAY,
SCULPT_TOOL_THUMB) &&
!sculpt_brush_use_topology_rake(ss, brush)) {
return;
@@ -7287,6 +8189,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd,
((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
(brush->sculpt_tool == SCULPT_TOOL_POSE) ||
(brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) ||
+ (brush->sculpt_tool == SCULPT_TOOL_FAIRING) ||
+ (brush->sculpt_tool == SCULPT_TOOL_TWIST) ||
(brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) ||
(brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) ||
(brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) ||
@@ -7309,10 +8213,11 @@ void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *b
static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin)
{
- if (BKE_pbvh_node_get_tmin(node) >= *tmin) {
+ SculptRaycastData *srd = data_v;
+ if (!srd->use_back_depth && BKE_pbvh_node_get_tmin(node) >= *tmin) {
return;
}
- SculptRaycastData *srd = data_v;
+
float(*origco)[3] = NULL;
bool use_origco = false;
@@ -7335,13 +8240,19 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin)
srd->ray_start,
srd->ray_normal,
&srd->isect_precalc,
+ &srd->hit_count,
&srd->depth,
+ &srd->back_depth,
&srd->active_vertex_index,
&srd->active_face_grid_index,
srd->face_normal)) {
srd->hit = true;
*tmin = srd->depth;
}
+
+ if (srd->hit_count >= 2) {
+ srd->back_hit = true;
+ }
}
static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin)
@@ -7421,7 +8332,8 @@ float SCULPT_raycast_init(ViewContext *vc,
bool SCULPT_cursor_geometry_info_update(bContext *C,
SculptCursorGeometryInfo *out,
const float mouse[2],
- bool use_sampled_normal)
+ bool use_sampled_normal,
+ bool use_back_depth)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Scene *scene = CTX_data_scene(C);
@@ -7451,14 +8363,19 @@ bool SCULPT_cursor_geometry_info_update(bContext *C,
/* PBVH raycast to get active vertex and face normal. */
depth = SCULPT_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original);
SCULPT_stroke_modifiers_check(C, ob, brush);
+ float back_depth = depth;
SculptRaycastData srd = {
.original = original,
.ss = ob->sculpt,
.hit = false,
+ .back_hit = false,
.ray_start = ray_start,
.ray_normal = ray_normal,
.depth = depth,
+ .back_depth = back_depth,
+ .hit_count = 0,
+ .use_back_depth = use_back_depth,
.face_normal = face_normal,
};
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
@@ -7496,6 +8413,17 @@ bool SCULPT_cursor_geometry_info_update(bContext *C,
mul_v3_fl(out->location, srd.depth);
add_v3_v3(out->location, ray_start);
+ if (use_back_depth) {
+ copy_v3_v3(out->back_location, ray_normal);
+ if (srd.back_hit) {
+ mul_v3_fl(out->back_location, srd.back_depth);
+ }
+ else {
+ mul_v3_fl(out->back_location, srd.depth);
+ }
+ add_v3_v3(out->back_location, ray_start);
+ }
+
/* Option to return the face normal directly for performance o accuracy reasons. */
if (!use_sampled_normal) {
copy_v3_v3(out->normal, srd.face_normal);
@@ -7752,6 +8680,12 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags)
/* Slow update with full dependency graph update and all that comes with it.
* Needed when there are modifiers or full shading in the 3D viewport. */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB);
+ SCULPT_update_object_bounding_box(ob);
+ }
ED_region_tag_redraw(region);
}
else {
@@ -7885,9 +8819,14 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f
sculpt_update_cache_invariants(C, sd, ss, op, mouse);
SculptCursorGeometryInfo sgi;
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
SCULPT_undo_push_begin(ob, sculpt_tool_name(sd));
+
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
+ }
return true;
}
@@ -7904,6 +8843,10 @@ static void sculpt_stroke_update_step(bContext *C,
SculptSession *ss = ob->sculpt;
const Brush *brush = BKE_paint_brush(&sd->paint);
+ if (brush->sculpt_tool == SCULPT_TOOL_SCENE_PROJECT) {
+ sculpt_stroke_cache_snap_context_init(C, ob);
+ }
+
SCULPT_stroke_modifiers_check(C, ob, brush);
sculpt_update_cache_variants(C, sd, ob, itemptr);
sculpt_restore_mesh(sd, ob);
@@ -7926,8 +8869,30 @@ static void sculpt_stroke_update_step(bContext *C,
}
do_symmetrical_brush_actions(sd, ob, do_brush_action, ups);
+
+ if (ss->needs_pbvh_rebuild) {
+ /* The mesh was modified, rebuild the PBVH. */
+ BKE_particlesystem_reset_all(ob);
+ BKE_ptcache_object_reset(CTX_data_scene(C), ob, PTCACHE_RESET_OUTDATED);
+
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ BKE_scene_graph_update_tagged(CTX_data_ensure_evaluated_depsgraph(C), CTX_data_main(C));
+ SCULPT_pbvh_clear(ob);
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
+
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ SCULPT_tag_update_overlays(C);
+ }
+ ss->needs_pbvh_rebuild = false;
+ }
+
sculpt_combine_proxies(sd, ob);
+ if (brush->sculpt_tool == SCULPT_TOOL_FAIRING) {
+ sculpt_fairing_brush_exec_fairing_for_cache(sd, ob);
+ }
+
/* Hack to fix noise texture tearing mesh. */
sculpt_fix_noise_tear(sd, ob);
@@ -8022,6 +8987,11 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str
SCULPT_cache_free(ss->cache);
ss->cache = NULL;
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
+ SCULPT_array_datalayers_free(ob);
+ }
+
SCULPT_undo_push_end();
if (brush->sculpt_tool == SCULPT_TOOL_MASK) {
@@ -8774,31 +9744,171 @@ static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
+
+#define SAMPLE_COLOR_PREVIEW_SIZE 60
+#define SAMPLE_COLOR_OFFSET_X -15
+#define SAMPLE_COLOR_OFFSET_Y -15
+typedef struct SampleColorCustomData {
+ void *draw_handle;
+ Object *active_object;
+
+ float mval[2];
+
+ float initial_color[4];
+ float sampled_color[4];
+} SampleColorCustomData;
+
+static void sculpt_sample_color_draw(const bContext *UNUSED(C),
+ ARegion *UNUSED(ar),
+ void *arg)
+{
+ SampleColorCustomData *sccd = (SampleColorCustomData *)arg;
+ GPU_line_width(2.0f);
+ GPU_line_smooth(true);
+ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+
+ const float origin_x = sccd->mval[0] + SAMPLE_COLOR_OFFSET_X;
+ const float origin_y = sccd->mval[1] + SAMPLE_COLOR_OFFSET_Y;
+
+
+ immUniformColor3fvAlpha(sccd->sampled_color, 1.0f);
+ immRectf(pos, origin_x, origin_y, origin_x - SAMPLE_COLOR_PREVIEW_SIZE, origin_y - SAMPLE_COLOR_PREVIEW_SIZE);
+
+ immUniformColor3fvAlpha(sccd->initial_color, 1.0f);
+ immRectf(pos, origin_x - SAMPLE_COLOR_PREVIEW_SIZE, origin_y, origin_x - 2.0f * SAMPLE_COLOR_PREVIEW_SIZE, origin_y - SAMPLE_COLOR_PREVIEW_SIZE);
+
+
+ immUnbindProgram();
+ GPU_line_smooth(false);
+}
+
+static bool sculpt_sample_color_update_from_base(bContext *C, const wmEvent *event, SampleColorCustomData *sccd) {
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Base *base_sample = ED_view3d_give_base_under_cursor(C, event->mval);
+ if (base_sample == NULL) {
+ return false;
+ }
+
+ Object *object_sample = base_sample->object;
+ if (object_sample->type != OB_MESH) {
+ return false;
+ }
+
+ Object *ob_eval = DEG_get_evaluated_object(depsgraph, object_sample);
+ Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ MPropCol *vcol = CustomData_get_layer(&me_eval->vdata, CD_PROP_COLOR);
+
+ if (!vcol) {
+ return false;
+ }
+
+ ARegion *region = CTX_wm_region(C);
+ Scene *scene = CTX_data_scene(C);
+ float global_loc[3];
+ if (!ED_view3d_autodist_simple(region, event->mval, global_loc, 0, NULL)) {
+ return false;
+ }
+
+ float object_loc[3];
+ mul_v3_m4v3(object_loc, ob_eval->imat, global_loc);
+
+
+ BVHTreeFromMesh bvh;
+ BKE_bvhtree_from_mesh_get(&bvh, me_eval, BVHTREE_FROM_VERTS, 2);
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ BLI_bvhtree_find_nearest(bvh.tree, object_loc, &nearest, bvh.nearest_callback, &bvh);
+ if (nearest.index == -1) {
+ return false;
+ }
+ free_bvhtree_from_mesh(&bvh);
+
+ copy_v4_v4(sccd->sampled_color, vcol[nearest.index].color);
+ IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color);
+ return true;
+}
+
+static int sculpt_sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ ARegion *region = CTX_wm_region(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ Scene *scene = CTX_data_scene(C);
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+
+ SampleColorCustomData *sccd = (SampleColorCustomData *)op->customdata;
+
+ /* Finish operation on release. */
+ if (event->val == KM_RELEASE) {
+ float color_srgb[3];
+ copy_v3_v3(color_srgb, sccd->sampled_color);
+ BKE_brush_color_set(scene, brush, sccd->sampled_color);
+ WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush);
+ ED_region_draw_cb_exit(region->type, sccd->draw_handle);
+ ED_region_tag_redraw(region);
+ MEM_freeN(sccd);
+ ss->draw_faded_cursor = false;
+ return OPERATOR_FINISHED;
+ }
+
+ SculptCursorGeometryInfo sgi;
+ sccd->mval[0] = event->mval[0];
+ sccd->mval[1] = event->mval[1];
+
+
+ const bool over_mesh = SCULPT_cursor_geometry_info_update(C, &sgi, sccd->mval, false, false);
+ if (over_mesh) {
+ const int active_vertex = SCULPT_active_vertex_get(ss);
+ copy_v4_v4(sccd->sampled_color, SCULPT_vertex_color_get(ss, active_vertex));
+ IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color);
+ }
+ else {
+ sculpt_sample_color_update_from_base(C, event, sccd);
+ }
+
+ ss->draw_faded_cursor = true;
+ ED_region_tag_redraw(region);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
static int sculpt_sample_color_invoke(bContext *C,
- wmOperator *UNUSED(op),
+ wmOperator *op,
const wmEvent *UNUSED(e))
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Brush *brush = BKE_paint_brush(&sd->paint);
+ ARegion *region = CTX_wm_region(C);
SculptSession *ss = ob->sculpt;
- int active_vertex = SCULPT_active_vertex_get(ss);
- const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex);
- if (!active_vertex_color) {
+
+ if (!ss->vcol) {
return OPERATOR_CANCELLED;
}
- float color_srgb[3];
- copy_v3_v3(color_srgb, active_vertex_color);
- IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb);
- BKE_brush_color_set(scene, brush, color_srgb);
+ SampleColorCustomData *sccd = MEM_callocN(sizeof(SampleColorCustomData), "Sample Color Custom Data");
+ const int active_vertex = SCULPT_active_vertex_get(ss);
+ copy_v4_v4(sccd->sampled_color, SCULPT_vertex_color_get(ss, active_vertex));
+ copy_v4_v4(sccd->initial_color, BKE_brush_color_get(scene, brush));
- WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush);
+ sccd->draw_handle = ED_region_draw_cb_activate(
+ region->type, sculpt_sample_color_draw, sccd, REGION_DRAW_POST_PIXEL);
- return OPERATOR_FINISHED;
+ op->customdata = sccd;
+
+ WM_event_add_modal_handler(C, op);
+ ED_region_tag_redraw(region);
+
+ return OPERATOR_RUNNING_MODAL;
}
+
+
+
static void SCULPT_OT_sample_color(wmOperatorType *ot)
{
/* identifiers */
@@ -8808,6 +9918,7 @@ static void SCULPT_OT_sample_color(wmOperatorType *ot)
/* api callbacks */
ot->invoke = sculpt_sample_color_invoke;
+ ot->modal = sculpt_sample_color_modal;
ot->poll = SCULPT_vertex_colors_poll;
ot->flag = OPTYPE_REGISTER;
@@ -9336,7 +10447,7 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven
float mouse[2];
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
SCULPT_undo_push_begin(ob, "Mask by color");
@@ -9395,11 +10506,82 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
1.0f);
}
+static int sculpt_reset_brushes_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+
+ LISTBASE_FOREACH (Brush *, br, &bmain->brushes) {
+ if (br->ob_mode != OB_MODE_SCULPT) {
+ continue;
+ }
+ BKE_brush_sculpt_reset(br);
+ WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_reset_brushes(struct wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Reset Sculpt Brushes";
+ ot->idname = "SCULPT_OT_reset_brushes";
+ ot->description = "Resets all sculpt brushes to their default value";
+
+ /* API callbacks. */
+ ot->exec = sculpt_reset_brushes_exec;
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+}
+
+static int sculpt_set_limit_surface_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Object *ob = CTX_data_active_object(C);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+ SculptSession *ss = ob->sculpt;
+
+ if (!ss) {
+ return OPERATOR_FINISHED;
+ }
+
+ SCULPT_vertex_random_access_ensure(ss);
+
+ MEM_SAFE_FREE(ss->limit_surface);
+
+ const int totvert = SCULPT_vertex_count_get(ss);
+ ss->limit_surface = MEM_mallocN(sizeof(float) * 3 * totvert,
+ "limit surface");
+
+ for (int i = 0; i < totvert; i++) {
+ SCULPT_neighbor_coords_average(ss, ss->limit_surface[i], i);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_set_limit_surface(wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Set Limit Surface";
+ ot->idname = "SCULPT_OT_set_limit_surface";
+ ot->description = "Calculates and stores a limit surface from the current mesh";
+
+ /* API callbacks. */
+ ot->exec = sculpt_set_limit_surface_exec;
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
WM_operatortype_append(SCULPT_OT_set_persistent_base);
+ WM_operatortype_append(SCULPT_OT_set_limit_surface);
WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle);
WM_operatortype_append(SCULPT_OT_optimize);
WM_operatortype_append(SCULPT_OT_symmetrize);
@@ -9414,7 +10596,6 @@ void ED_operatortypes_sculpt(void)
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);
- WM_operatortype_append(SCULPT_OT_face_sets_init);
WM_operatortype_append(SCULPT_OT_cloth_filter);
WM_operatortype_append(SCULPT_OT_face_sets_edit);
WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture);
@@ -9422,6 +10603,8 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_trim_box_gesture);
WM_operatortype_append(SCULPT_OT_trim_lasso_gesture);
WM_operatortype_append(SCULPT_OT_project_line_gesture);
+ WM_operatortype_append(SCULPT_OT_project_lasso_gesture);
+ WM_operatortype_append(SCULPT_OT_project_box_gesture);
WM_operatortype_append(SCULPT_OT_sample_color);
WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors);
@@ -9430,6 +10613,10 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_mask_by_color);
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
WM_operatortype_append(SCULPT_OT_mask_init);
+ WM_operatortype_append(SCULPT_OT_face_sets_init);
+ WM_operatortype_append(SCULPT_OT_reset_brushes);
+ WM_operatortype_append(SCULPT_OT_ipmask_filter);
+ WM_operatortype_append(SCULPT_OT_face_set_by_topology);
WM_operatortype_append(SCULPT_OT_expand);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_array.c b/source/blender/editors/sculpt_paint/sculpt_array.c
new file mode 100644
index 00000000000..d93f61168ea
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_array.c
@@ -0,0 +1,982 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_hash.h"
+#include "BLI_task.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_mesh.h"
+#include "BKE_multires.h"
+#include "BKE_lib_id.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
+#include "ED_sculpt.h"
+
+#include "bmesh.h"
+#include "bmesh_tools.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+
+static const char array_symmetry_pass_cd_name[] = "v_symmetry_pass";
+static const char array_instance_cd_name[] = "v_array_instance";
+
+#define ARRAY_INSTANCE_ORIGINAL -1
+
+static void sculpt_vertex_array_data_get(SculptArray *array, const int vertex, int *r_copy, int *r_symmetry_pass) {
+ if (!array->copy_index) {
+ printf("NO ARRAY COPY\n");
+ *r_copy = ARRAY_INSTANCE_ORIGINAL;
+ *r_symmetry_pass = 0;
+ return;
+ }
+ *r_copy = array->copy_index[vertex];
+ *r_symmetry_pass = array->symmetry_pass[vertex];
+}
+
+
+static void sculpt_array_datalayers_add(Mesh *mesh) {
+ int *v_array_instance = CustomData_add_layer_named(&mesh->vdata,
+ CD_PROP_INT32,
+ CD_CALLOC,
+ NULL,
+ mesh->totvert,
+ array_instance_cd_name);
+ for (int i = 0; i < mesh->totvert; i++) {
+ v_array_instance[i] = ARRAY_INSTANCE_ORIGINAL;
+ }
+
+ CustomData_add_layer_named(&mesh->vdata,
+ CD_PROP_INT32,
+ CD_CALLOC,
+ NULL,
+ mesh->totvert,
+ array_symmetry_pass_cd_name);
+}
+
+void SCULPT_array_datalayers_free(Object *ob) {
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+ int v_layer_index = CustomData_get_named_layer_index(&mesh->vdata, CD_PROP_INT32, array_instance_cd_name);
+ if (v_layer_index != -1) {
+ CustomData_free_layer(&mesh->vdata, CD_PROP_INT32, mesh->totvert, v_layer_index);
+ }
+
+ v_layer_index = CustomData_get_named_layer_index(&mesh->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name);
+ if (v_layer_index != -1) {
+ CustomData_free_layer(&mesh->vdata, CD_PROP_INT32, mesh->totvert, v_layer_index);
+ }
+}
+
+const float source_geometry_threshold = 0.5f;
+
+static BMesh *sculpt_array_source_build(Object *ob, Brush *brush, SculptArray *array) {
+ Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob);
+
+ BMesh *srcbm;
+ const BMAllocTemplate allocsizea = BMALLOC_TEMPLATE_FROM_ME(sculpt_mesh);
+ srcbm = BM_mesh_create(&allocsizea,
+ &((struct BMeshCreateParams){
+ .use_toolflags = true,
+ }));
+
+ BM_mesh_bm_from_me(srcbm,
+ sculpt_mesh,
+ &((struct BMeshFromMeshParams){
+ .calc_face_normal = true,
+ }));
+
+ BM_mesh_elem_table_ensure(srcbm, BM_VERT);
+ BM_mesh_elem_index_ensure(srcbm, BM_VERT);
+
+ int vert_count = 0;
+ zero_v3(array->source_origin);
+
+ SculptSession *ss = ob->sculpt;
+ for (int i = 0; i < srcbm->totvert; i++) {
+ const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, i);
+ const float mask = 1.0f - SCULPT_vertex_mask_get(ss, i);
+ const float influence = mask * automask;
+ BMVert *vert = BM_vert_at_index(srcbm, i);
+ if (influence >= source_geometry_threshold) {
+ vert_count++;
+ add_v3_v3(array->source_origin, vert->co);
+ continue;
+ }
+ BM_elem_flag_set(vert, BM_ELEM_TAG, true);
+ }
+
+
+ if (vert_count == 0) {
+ return srcbm;
+ }
+
+ mul_v3_fl(array->source_origin, 1.0f / vert_count);
+
+ /* TODO(pablodp606): Handle individual Face Sets for Face Set automasking. */
+ BM_mesh_delete_hflag_context(srcbm, BM_ELEM_TAG, DEL_VERTS);
+
+ const bool fill_holes = brush->flag2 & BRUSH_ARRAY_FILL_HOLES;
+ if (fill_holes) {
+ BM_mesh_elem_hflag_disable_all(srcbm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ BM_mesh_elem_hflag_enable_all(srcbm, BM_EDGE, BM_ELEM_TAG, false);
+ BM_mesh_edgenet(srcbm, false, true);
+ BM_mesh_normals_update(srcbm);
+ BMO_op_callf(srcbm,
+ (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
+ "triangulate faces=%hf quad_method=%i ngon_method=%i",
+ BM_ELEM_TAG,
+ 0,
+ 0);
+
+ BM_mesh_elem_hflag_enable_all(srcbm, BM_FACE, BM_ELEM_TAG, false);
+ BMO_op_callf(srcbm,
+ (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
+ "recalc_face_normals faces=%hf",
+ BM_ELEM_TAG);
+ BM_mesh_elem_hflag_disable_all(srcbm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ }
+
+ return srcbm;
+}
+
+void sculpt_array_source_datalayer_update(BMesh *bm, const int symm_pass, const int copy_index) {
+ const int cd_array_instance_index = CustomData_get_named_layer_index(
+ &bm->vdata, CD_PROP_INT32, array_instance_cd_name);
+ const int cd_array_instance_offset = CustomData_get_n_offset(
+ &bm->vdata, CD_PROP_INT32, cd_array_instance_index);
+
+ const int cd_array_symm_pass_index = CustomData_get_named_layer_index(
+ &bm->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name);
+ const int cd_array_symm_pass_offset = CustomData_get_n_offset(
+ &bm->vdata, CD_PROP_INT32, cd_array_symm_pass_index);
+
+ BM_mesh_elem_table_ensure(bm, BM_VERT);
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ BMVert *v;
+ BMIter iter;
+ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
+ BM_ELEM_CD_SET_INT(v, cd_array_instance_offset, copy_index);
+ BM_ELEM_CD_SET_INT(v, cd_array_symm_pass_offset, symm_pass);
+ }
+}
+
+static void sculpt_array_final_mesh_write(Object *ob, BMesh *final_mesh) {
+ SculptSession *ss = ob->sculpt;
+ Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob);
+ Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(final_mesh, NULL, sculpt_mesh);
+ result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
+ BKE_mesh_nomain_to_mesh(result, ob->data, ob, &CD_MASK_MESH, true);
+ BKE_id_free(NULL, result);
+ BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
+
+ const int next_face_set_id = ED_sculpt_face_sets_find_next_available_id(ob->data);
+ ED_sculpt_face_sets_initialize_none_to_id(ob->data, next_face_set_id);
+
+ ss->needs_pbvh_rebuild = true;
+}
+
+static void sculpt_array_ensure_geometry_indices(Object *ob, SculptArray *array) {
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+
+ if (array->copy_index) {
+
+ return;
+ }
+
+
+printf("ALLOCATION COPY INDEX\n");
+
+ array->copy_index = MEM_malloc_arrayN(mesh->totvert, sizeof(int), "array copy index");
+ array->symmetry_pass = MEM_malloc_arrayN(mesh->totvert, sizeof(int), "array symmetry pass index");
+
+ int *cd_array_instance = CustomData_get_layer_named(
+ &mesh->vdata, CD_PROP_INT32, array_instance_cd_name);
+
+ int *cd_array_symm_pass = CustomData_get_layer_named(
+ &mesh->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name);
+
+ if (!cd_array_instance) {
+ printf("ERROR 1");
+ }
+
+ for (int i = 0; i < mesh->totvert; i++) {
+ array->copy_index[i] = cd_array_instance[i];
+ array->symmetry_pass[i] = cd_array_symm_pass[i];
+ }
+
+ SCULPT_array_datalayers_free(ob);
+}
+
+static void sculpt_array_mesh_build(Sculpt *sd, Object *ob, SculptArray *array) {
+ Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob);
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ sculpt_array_datalayers_add(sculpt_mesh);
+
+ BMesh *srcbm = sculpt_array_source_build(ob, brush, array);
+
+ BMesh *destbm;
+ const BMAllocTemplate allocsizeb = BMALLOC_TEMPLATE_FROM_ME(sculpt_mesh);
+ destbm = BM_mesh_create(&allocsizeb,
+ &((struct BMeshCreateParams){
+ .use_toolflags = true,
+ }));
+ BM_mesh_bm_from_me(destbm,
+ sculpt_mesh,
+ &((struct BMeshFromMeshParams){
+ .calc_face_normal = true,
+ }));
+
+
+ BM_mesh_elem_toolflags_ensure(destbm);
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ for (int copy_index = 0; copy_index < array->num_copies; copy_index++) {
+ sculpt_array_source_datalayer_update(srcbm, symm_it, copy_index);
+
+ BM_mesh_copy_init_customdata(destbm, srcbm, &bm_mesh_allocsize_default);
+ const int opflag = (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE);
+ BMO_op_callf(srcbm, opflag, "duplicate geom=%avef dest=%p", destbm);
+ }
+ }
+
+ sculpt_array_final_mesh_write(ob, destbm);
+
+ BM_mesh_free(srcbm);
+ BM_mesh_free(destbm);
+
+
+}
+
+static SculptArray *sculpt_array_cache_create(Object *ob, eBrushArrayDeformType deform_type, const int num_copies) {
+
+ SculptArray *array = MEM_callocN(sizeof(SculptArray), "Sculpt Array");
+ array->num_copies = num_copies;
+
+ array->mode = deform_type;
+
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+ array->copies[symm_it] = MEM_malloc_arrayN(num_copies, sizeof(SculptArrayCopy), "Sculpt array copies");
+ }
+ return array;
+}
+
+static void sculpt_array_cache_free(SculptArray *array) {
+ return;
+ for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) {
+ MEM_SAFE_FREE(array->copies[symm_pass]);
+ }
+ MEM_freeN(array->copy_index);
+ MEM_freeN(array->symmetry_pass);
+ MEM_freeN(array);
+}
+
+static void sculpt_array_init(Object *ob, Brush *brush, SculptArray *array) {
+ SculptSession *ss = ob->sculpt;
+
+ /* TODO: add options. */
+ copy_v3_v3(array->normal, ss->cache->view_normal);
+ array->radial_angle = 2.0f * M_PI;
+
+ for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) {
+ if (array->copies[symm_pass] == NULL) {
+ continue;
+ }
+ for (int copy_index = 0; copy_index < array->num_copies; copy_index++) {
+ SculptArrayCopy *copy = &array->copies[symm_pass][copy_index];
+ unit_m4(copy->mat);
+ copy->symm_pass = symm_pass;
+ copy->index = copy_index;
+ float symm_location[3];
+ flip_v3_v3(symm_location, ss->cache->location, symm_pass);
+ copy_v3_v3(copy->origin, ss->cache->location);
+ }
+ }
+}
+
+
+static void sculpt_array_position_in_path_search(float *r_position, float *r_direction, float *r_scale, SculptArray *array, const int index) {
+ const float path_length = array->path.points[array->path.tot_points-1].length;
+ const float step_distance = path_length / (float)array->num_copies;
+ const float copy_distance = step_distance * (index + 1);
+
+
+ if (array->path.tot_points == 1) {
+ zero_v3(r_position);
+ if (r_direction) {
+ zero_v3(r_direction);
+ }
+ if (r_scale) {
+ *r_scale = 1.0f;
+ }
+ return;
+ }
+
+ for (int i = 1; i < array->path.tot_points; i++) {
+ ScultpArrayPathPoint *path_point = &array->path.points[i];
+ if (copy_distance >= path_point->length) {
+ continue;
+ }
+ ScultpArrayPathPoint *prev_path_point = &array->path.points[i - 1];
+
+ const float remaining_dist = copy_distance - prev_path_point->length;
+ const float segment_length = path_point->length - prev_path_point->length;
+ const float interp_factor = remaining_dist / segment_length;
+ interp_v3_v3v3(r_position, prev_path_point->co, path_point->co, interp_factor);
+ if (r_direction) {
+ if (i == array->path.tot_points - 1) {
+ copy_v3_v3(r_direction, prev_path_point->direction);
+ }
+ else {
+ copy_v3_v3(r_direction, path_point->direction);
+ }
+ }
+ if (r_scale) {
+ const float s = 1.0f - interp_factor;
+ *r_scale = s * prev_path_point->strength + interp_factor * path_point->strength;
+ }
+ return;
+ }
+
+ ScultpArrayPathPoint *last_path_point = &array->path.points[array->path.tot_points - 1];
+ copy_v3_v3(r_position, last_path_point->co);
+ if (r_direction) {
+ ScultpArrayPathPoint *prev_path_point = &array->path.points[array->path.tot_points - 2];
+ copy_v3_v3(r_direction, prev_path_point->direction);
+ }
+ if (r_scale) {
+ ScultpArrayPathPoint *prev_path_point = &array->path.points[array->path.tot_points - 2];
+ *r_scale = prev_path_point->strength;
+ }
+}
+
+static void scultp_array_basis_from_direction(float r_mat[4][4], SculptArray *array, const float direction[3]) {
+ float direction_normalized[3];
+ normalize_v3_v3(direction_normalized, direction);
+ copy_v3_v3(r_mat[0], direction_normalized);
+ cross_v3_v3v3(r_mat[2], r_mat[0], array->normal);
+ cross_v3_v3v3(r_mat[1], r_mat[0], r_mat[2]);
+ normalize_v3(r_mat[0]);
+ normalize_v3(r_mat[1]);
+ normalize_v3(r_mat[2]);
+}
+
+static float *sculpt_array_delta_from_path(SculptArray *array) {
+ return array->path.points[array->path.tot_points - 1].co;
+}
+
+static void sculpt_array_update_copy(StrokeCache *cache, SculptArray *array, SculptArrayCopy *copy, Brush *brush) {
+
+ float copy_position[3];
+ unit_m4(copy->mat);
+
+ float scale = 1.0f;
+ float direction[3];
+
+ eBrushArrayDeformType array_type = brush->array_deform_type;
+ float delta[3];
+ copy_v3_v3(delta, sculpt_array_delta_from_path(array));
+
+ switch (array_type)
+ {
+ case BRUSH_ARRAY_DEFORM_LINEAR: {
+ const float fade = ((float)copy->index + 1.0f) / (float)(array->num_copies);
+ mul_v3_v3fl(copy->mat[3], delta, fade);
+ normalize_v3_v3(direction, delta);
+ scale = cache->bstrength;
+ }
+ break;
+
+ case BRUSH_ARRAY_DEFORM_RADIAL: {
+ float pos[3];
+ const float fade = ((float)copy->index + 1.0f) / (float)(array->num_copies);
+ copy_v3_v3(pos, delta);
+ rotate_v3_v3v3fl(copy->mat[3], pos, array->normal, fade * array->radial_angle);
+ copy_v3_v3(direction, copy->mat[3]);
+ //sub_v3_v3v3(direction, copy->mat[3], array->source_origin);
+ scale = cache->bstrength;
+ }
+ break;
+ case BRUSH_ARRAY_DEFORM_PATH:
+ sculpt_array_position_in_path_search(copy->mat[3], direction, &scale, array, copy->index);
+ break;
+ }
+
+
+ if (!(brush->flag2 & BRUSH_ARRAY_LOCK_ORIENTATION)) {
+ scultp_array_basis_from_direction(copy->mat, array, direction);
+ }
+
+
+ /*
+ copy->mat[3][0] += (BLI_hash_int_01(copy->index) * 2.0f - 0.5f) * cache->radius;
+ copy->mat[3][1] += (BLI_hash_int_01(copy->index + 1) * 2.0f - 0.5f) * cache->radius;
+ copy->mat[3][2] += (BLI_hash_int_01(copy->index + 2) * 2.0f - 0.5f) * cache->radius;
+ */
+
+ mul_v3_fl(copy->mat[0], scale);
+ mul_v3_fl(copy->mat[1], scale);
+ mul_v3_fl(copy->mat[2], scale);
+
+ /*
+ copy->mat[0][0] = scale;
+ copy->mat[1][1] = scale;
+ copy->mat[2][2] = scale;
+ */
+
+}
+
+static void sculpt_array_update(Object *ob, Brush *brush, SculptArray *array) {
+ SculptSession *ss = ob->sculpt;
+
+ /* Main symmetry pass. */
+ for (int copy_index = 0; copy_index < array->num_copies; copy_index++) {
+ SculptArrayCopy *copy = &array->copies[0][copy_index];
+ unit_m4(copy->mat);
+ sculpt_array_update_copy(ss->cache, array, copy, brush);
+ }
+
+ for (int symm_pass = 1; symm_pass < PAINT_SYMM_AREAS; symm_pass++) {
+ if (array->copies[symm_pass] == NULL) {
+ continue;
+ }
+
+ float symm_orig[3];
+ flip_v3_v3(symm_orig, array->source_origin, symm_pass);
+
+ for (int copy_index = 0; copy_index < array->num_copies; copy_index++) {
+ SculptArrayCopy *copy = &array->copies[symm_pass][copy_index];
+ SculptArrayCopy *main_copy = &array->copies[0][copy_index];
+ unit_m4(copy->mat);
+ for (int m = 0; m < 4; m++) {
+ flip_v3_v3(copy->mat[m],main_copy->mat[m], symm_pass);
+ }
+ }
+ }
+
+ for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) {
+
+ if (array->copies[symm_pass] == NULL) {
+ continue;
+ }
+ for (int copy_index = 0; copy_index < array->num_copies; copy_index++) {
+ SculptArrayCopy *copy = &array->copies[symm_pass][copy_index];
+ invert_m4_m4(copy->imat, copy->mat);
+ }
+ }
+
+}
+
+static void do_array_deform_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ SculptArray *array = ss->array;
+
+ Mesh *mesh = BKE_object_get_original_mesh(data->ob);
+
+ bool any_modified = false;
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ int array_index = ARRAY_INSTANCE_ORIGINAL;
+ int array_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, vd.index, &array_index, &array_symm_pass);
+
+ if (array_index == ARRAY_INSTANCE_ORIGINAL) {
+ continue;
+ }
+
+ SculptArrayCopy *copy = &array->copies[array_symm_pass][array_index];
+
+ float co[3];
+ copy_v3_v3(co, array->orco[vd.index]);
+ mul_v3_m4v3(co, array->source_imat, array->orco[vd.index]);
+ mul_v3_m4v3(co, copy->mat, co);
+ float source_origin_symm[3];
+ flip_v3_v3(source_origin_symm, array->source_origin, array_symm_pass);
+ add_v3_v3v3(vd.co, co, source_origin_symm);
+
+ any_modified = true;
+
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (any_modified) {
+ BKE_pbvh_node_mark_update(data->nodes[n]);
+ }
+}
+
+static void sculpt_array_deform(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) {
+ /* Threaded loop over nodes. */
+ SculptSession *ss = ob->sculpt;
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(
+ 0, totnode, &data, do_array_deform_task_cb_ex, &settings);
+}
+
+
+static void do_array_smooth_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ SculptArray *array = ss->array;
+
+ Mesh *mesh = BKE_object_get_original_mesh(data->ob);
+
+ bool any_modified = false;
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ int array_index = ARRAY_INSTANCE_ORIGINAL;
+ int array_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, vd.index, &array_index, &array_symm_pass);
+
+ const float fade = array->smooth_strength[vd.index];
+
+ if (fade == 0.0f) {
+ continue;
+ }
+
+ float smooth_co[3];
+ SCULPT_neighbor_coords_average(ss, smooth_co, vd.index);
+ float disp[3];
+ sub_v3_v3v3(disp, smooth_co, vd.co);
+ mul_v3_fl(disp, fade);
+ add_v3_v3(vd.co, disp);
+
+
+/*
+ if (array_index == ARRAY_INSTANCE_ORIGINAL) {
+ continue;
+ }
+
+ bool do_smooth = false;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
+ int neighbor_array_index = ARRAY_INSTANCE_ORIGINAL;
+ int neighbor_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, ni.index, &neighbor_array_index,&neighbor_symm_pass);
+ if (neighbor_array_index != array_index) {
+ do_smooth = true;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ if (!do_smooth) {
+ continue;
+ }
+ */
+
+
+
+ any_modified = true;
+
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (any_modified) {
+ BKE_pbvh_node_mark_update(data->nodes[n]);
+ }
+}
+
+static void sculpt_array_smooth(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) {
+
+
+
+ /* Threaded loop over nodes. */
+ SculptSession *ss = ob->sculpt;
+ SculptArray *array = ss->array;
+
+ if (!array) {
+ return;
+ }
+
+ if (!array->smooth_strength) {
+ return;
+ }
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(
+ 0, totnode, &data, do_array_smooth_task_cb_ex, &settings);
+}
+
+static void sculpt_array_ensure_original_coordinates(Object *ob, SculptArray *array){
+ SculptSession *ss = ob->sculpt;
+ Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob);
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ if (array->orco) {
+ return;
+ }
+
+ array->orco = MEM_malloc_arrayN(sculpt_mesh->totvert, sizeof(float) * 3, "array orco");
+ for (int i = 0; i < totvert; i++) {
+ copy_v3_v3(array->orco[i], SCULPT_vertex_co_get(ss, i));
+ }
+}
+
+static void sculpt_array_ensure_base_transform(Sculpt *sd, Object *ob, SculptArray *array){
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob);
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ if (array->source_mat_valid) {
+ return;
+ }
+
+ unit_m4(array->source_mat);
+
+ if (brush->flag2 & BRUSH_ARRAY_LOCK_ORIENTATION) {
+ unit_m4(array->source_mat);
+ copy_v3_v3(array->source_mat[3], array->source_origin);
+ invert_m4_m4(array->source_imat, array->source_mat);
+ array->source_mat_valid = true;
+ return;
+ }
+
+ if (is_zero_v3(ss->cache->grab_delta)) {
+ return;
+ }
+
+ scultp_array_basis_from_direction(array->source_mat, array, ss->cache->grab_delta);
+ copy_v3_v3(array->source_mat[3], array->source_origin);
+ invert_m4_m4(array->source_imat, array->source_mat);
+
+ array->source_mat_valid = true;
+ return;
+}
+
+static void sculpt_array_path_point_update(SculptArray *array, const int path_point_index) {
+ if (path_point_index == 0) {
+ return;
+ }
+
+ const int prev_path_point_index = path_point_index - 1;
+
+ ScultpArrayPathPoint *path_point = &array->path.points[path_point_index];
+ ScultpArrayPathPoint *prev_path_point = &array->path.points[prev_path_point_index];
+
+ if (len_v3v3(prev_path_point->co, path_point->co) <= 0.0001f) {
+ return;
+ }
+ sub_v3_v3v3(prev_path_point->direction, path_point->co, prev_path_point->co);
+ path_point->length = prev_path_point->length + normalize_v3(prev_path_point->direction);
+}
+
+
+static void sculpt_array_stroke_sample_add(Object *ob, SculptArray *array) {
+ SculptSession *ss = ob->sculpt;
+
+ if (!array->path.points) {
+ array->path.points = MEM_malloc_arrayN(9999, sizeof(ScultpArrayPathPoint), "Array Path");
+ }
+
+ const int current_point_index = array->path.tot_points;
+ const int prev_point_index = current_point_index - 1;
+
+ ScultpArrayPathPoint *path_point = &array->path.points[current_point_index];
+
+ //add_v3_v3v3(path_point->co, ss->cache->orig_grab_location, ss->cache->grab_delta);
+ copy_v3_v3(path_point->co, ss->cache->grab_delta);
+ path_point->strength = ss->cache->bstrength;
+
+ if (current_point_index == 0) {
+ /* First point of the path. */
+ path_point->length = 0.0f;
+ }
+ else {
+ ScultpArrayPathPoint *prev_path_point = &array->path.points[prev_point_index];
+ if (len_v3v3(prev_path_point->co, path_point->co) <= 0.0001f) {
+ return;
+ }
+ sub_v3_v3v3(prev_path_point->direction, path_point->co, prev_path_point->co);
+ path_point->length = prev_path_point->length + normalize_v3(prev_path_point->direction);
+ }
+
+ array->path.tot_points++;
+}
+
+void SCULPT_do_array_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+
+ if (ss->cache->invert) {
+ if (!ss->array) {
+ return;
+ }
+
+ if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
+ SculptArray *array = ss->array;
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+
+ /* Rebuild smooth strength cache. */
+ MEM_SAFE_FREE(array->smooth_strength);
+ array->smooth_strength = MEM_calloc_arrayN(sizeof(float), totvert, "smooth_strength");
+
+
+ for (int i = 0; i < totvert; i++) {
+ int array_index = ARRAY_INSTANCE_ORIGINAL;
+ int array_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, i, &array_index, &array_symm_pass);
+
+ if (array_index == ARRAY_INSTANCE_ORIGINAL) {
+ continue;
+ }
+
+ /* TODO: this can be cached. */
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
+ int neighbor_array_index = ARRAY_INSTANCE_ORIGINAL;
+ int neighbor_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, ni.index, &neighbor_array_index,&neighbor_symm_pass);
+ if (neighbor_array_index != array_index) {
+ array->smooth_strength[i] = 1.0f;
+ break;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ }
+
+ for(int smooth_iterations = 0; smooth_iterations < 4; smooth_iterations++) {
+ for (int i = 0; i < totvert; i++) {
+ float avg = array->smooth_strength[i];
+ int count = 1;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
+ avg += array->smooth_strength[ni.index];
+ count++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ array->smooth_strength[i] = avg / count;
+ }
+ }
+
+
+
+
+ /* Update Array Path Orco. */
+ for (int i = 0; i < array->path.tot_points; i++) {
+ ScultpArrayPathPoint *point = &array->path.points[i];
+ copy_v3_v3(point->orco, point->co);
+ }
+ array->initial_radial_angle = array->radial_angle;
+
+ /* Update Geometry Orco. */
+ for (int i = 0; i < totvert; i++) {
+ int array_index = ARRAY_INSTANCE_ORIGINAL;
+ int array_symm_pass = 0;
+ sculpt_vertex_array_data_get(array, i, &array_index, &array_symm_pass);
+
+ if (array_index == ARRAY_INSTANCE_ORIGINAL) {
+ continue;
+ }
+ SculptArrayCopy *copy = &array->copies[array_symm_pass][array_index];
+ //sub_v3_v3v3(array->orco[i], SCULPT_vertex_co_get(ss, i), copy->mat[3]);
+ float co[3];
+ float source_origin_symm[3];
+ copy_v3_v3(co, SCULPT_vertex_co_get(ss, i));
+ flip_v3_v3(source_origin_symm, array->source_origin, array_symm_pass);
+ mul_v3_m4v3(co, copy->imat, co);
+ mul_v3_m4v3(co, array->source_imat, co);
+ //sub_v3_v3v3(co, co, source_origin_symm);
+
+ copy_v3_v3(array->orco[i], co);
+ }
+ }
+
+
+ SculptArray *array = ss->array;
+ if (array->mode == BRUSH_ARRAY_DEFORM_PATH) {
+ /* Deform path */
+ for (int i = 0; i < array->path.tot_points; i++) {
+ ScultpArrayPathPoint *point = &array->path.points[i];
+ float point_co[3];
+ add_v3_v3v3(point_co, point->orco, array->source_origin);
+ const float len = len_v3v3(ss->cache->true_location, point_co);
+ const float fade = ss->cache->bstrength * BKE_brush_curve_strength(brush, len, ss->cache->radius);
+ if (fade <= 0.0f) {
+ continue;
+ }
+ madd_v3_v3v3fl(point->co, point->orco, ss->cache->grab_delta, fade);
+ }
+ for (int i = 0; i < array->path.tot_points; i++) {
+ sculpt_array_path_point_update(array, i);
+ }
+
+ }
+ else {
+ /* Tweak radial angle. */
+ /*
+ const float factor = 1.0f - ( len_v3(ss->cache->grab_delta) / ss->cache->initial_radius);
+ array->radial_angle = array->initial_radial_angle * clamp_f(factor, 0.0f, 1.0f);
+ */
+
+ float array_disp_co[3];
+ float brush_co[3];
+ add_v3_v3v3(brush_co, ss->cache->initial_location, ss->cache->grab_delta);
+ sub_v3_v3(brush_co, array->source_origin);
+ normalize_v3(brush_co);
+ normalize_v3_v3(array_disp_co, sculpt_array_delta_from_path(array));
+ array->radial_angle = angle_signed_on_axis_v3v3_v3(brush_co, array_disp_co, array->normal);
+ }
+
+ sculpt_array_update(ob, brush, ss->array);
+ sculpt_array_deform(sd, ob, nodes, totnode);
+ for (int i = 0; i < 5; i++) {
+ sculpt_array_smooth(sd, ob, nodes, totnode);
+ }
+
+ return;
+
+ }
+
+
+ if (brush->array_count == 0) {
+ return;
+ }
+
+ if (!SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
+ /* This brush manages its own symmetry. */
+ return;
+ }
+
+ if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
+ if (ss->array) {
+ sculpt_array_cache_free(ss->array);
+ }
+
+ ss->array = sculpt_array_cache_create(ob, brush->array_deform_type, brush->array_count);
+ sculpt_array_init(ob, brush, ss->array);
+ sculpt_array_stroke_sample_add(ob, ss->array);
+ sculpt_array_mesh_build(sd, ob, ss->array);
+ /* Original coordinates can't be stored yet as the SculptSession data needs to be updated after the mesh modifications performed when building the array geometry. */
+ return;
+ }
+
+ sculpt_array_ensure_base_transform(sd, ob, ss->array);
+ sculpt_array_ensure_original_coordinates(ob, ss->array);
+ sculpt_array_ensure_geometry_indices(ob, ss->array);
+
+
+ sculpt_array_stroke_sample_add(ob, ss->array);
+
+ sculpt_array_update(ob, brush, ss->array);
+
+ sculpt_array_deform(sd, ob, nodes, totnode);
+}
+
+void SCULPT_array_path_draw(const uint gpuattr,
+ Brush *brush,
+ SculptSession *ss) {
+
+ SculptArray *array = ss->array;
+
+
+ /* Disable debug drawing. */
+ return;
+
+ if (!array) {
+ return;
+ }
+
+ if (!array->path.points) {
+ return;
+ }
+
+ if (array->path.tot_points < 2) {
+ return;
+ }
+
+ const int tot_points = array->path.tot_points;
+ immBegin(GPU_PRIM_LINE_STRIP, tot_points);
+ for (int i = 0; i < tot_points; i++) {
+ float co[3];
+ copy_v3_v3(co, array->path.points[i].co);
+ add_v3_v3(co, array->source_origin);
+ immVertex3fv(gpuattr, co);
+ }
+ immEnd();
+} \ No newline at end of file
diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.c b/source/blender/editors/sculpt_paint/sculpt_automasking.c
index 35f48400fe2..1980aa615a7 100644
--- a/source/blender/editors/sculpt_paint/sculpt_automasking.c
+++ b/source/blender/editors/sculpt_paint/sculpt_automasking.c
@@ -223,13 +223,21 @@ static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *au
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
const float radius = ss->cache ? ss->cache->radius : FLT_MAX;
- SCULPT_floodfill_add_active(sd, ob, ss, &flood, radius);
+
+ char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ symm = 0;
+ SCULPT_floodfill_add_initial(&flood, SCULPT_active_vertex_get(ss));
+ }
+ else {
+ SCULPT_floodfill_add_active(sd, ob, ss, &flood, radius);
+ }
AutomaskFloodFillData fdata = {
.automask_factor = automask_factor,
.radius = radius,
.use_radius = ss->cache && sculpt_automasking_is_constrained_by_radius(brush),
- .symm = SCULPT_mesh_symmetry_xyz_get(ob),
+ .symm = symm,
};
copy_v3_v3(fdata.location, SCULPT_active_vertex_co_get(ss));
SCULPT_floodfill_execute(ss, &flood, automask_floodfill_cb, &fdata);
diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c
index 37678ec276a..c2e8f2a8d90 100644
--- a/source/blender/editors/sculpt_paint/sculpt_boundary.c
+++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c
@@ -539,6 +539,8 @@ void SCULPT_boundary_data_free(SculptBoundary *boundary)
MEM_SAFE_FREE(boundary->bend.pivot_positions);
MEM_SAFE_FREE(boundary->bend.pivot_rotation_axis);
MEM_SAFE_FREE(boundary->slide.directions);
+ MEM_SAFE_FREE(boundary->circle.origin);
+ MEM_SAFE_FREE(boundary->circle.radius);
MEM_SAFE_FREE(boundary);
}
@@ -628,6 +630,45 @@ static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *b
MEM_freeN(poly_verts);
}
+static void sculpt_boundary_circle_data_init(SculptSession *ss, SculptBoundary *boundary)
+{
+
+ const int totvert = SCULPT_vertex_count_get(ss);
+ const int totcircles = boundary->max_propagation_steps + 1;
+
+ boundary->circle.radius = MEM_calloc_arrayN(totcircles, sizeof(float), "radius");
+ boundary->circle.origin = MEM_calloc_arrayN(totcircles, sizeof(float) * 3, "origin");
+
+ int *count = MEM_calloc_arrayN(totcircles, sizeof(int), "count");
+ for (int i = 0; i < totvert; i++) {
+ const int propagation_step_index = boundary->edit_info[i].num_propagation_steps;
+ if (propagation_step_index == -1) {
+ continue;
+ }
+ add_v3_v3(boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, i));
+ count[propagation_step_index]++;
+ }
+
+ for (int i = 0; i < totcircles; i++) {
+ mul_v3_fl(boundary->circle.origin[i], 1.0f / count[i]);
+ }
+
+ for (int i = 0; i < totvert; i++) {
+ const int propagation_step_index = boundary->edit_info[i].num_propagation_steps;
+ if (propagation_step_index == -1) {
+ continue;
+ }
+ boundary->circle.radius[propagation_step_index] += len_v3v3(
+ boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, i));
+ }
+
+ for (int i = 0; i < totcircles; i++) {
+ boundary->circle.radius[i] *= 1.0f / count[i];
+ }
+
+ MEM_freeN(count);
+}
+
static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss,
SculptBoundary *boundary)
{
@@ -946,6 +987,60 @@ static void do_boundary_brush_smooth_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_end;
}
+static void do_boundary_brush_circle_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symmetry_pass = ss->cache->mirror_symmetry_pass;
+ const SculptBoundary *boundary = ss->cache->boundaries[symmetry_pass];
+ const ePaintSymmetryFlags symm = SCULPT_mesh_symmetry_xyz_get(data->ob);
+ const Brush *brush = data->brush;
+
+ const float strength = ss->cache->bstrength;
+
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (boundary->edit_info[vd.index].num_propagation_steps == -1) {
+ continue;
+ }
+
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ if (!SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ continue;
+ }
+
+ const int propagation_steps = boundary->edit_info[vd.index].num_propagation_steps;
+ float *circle_origin = boundary->circle.origin[propagation_steps];
+ float circle_disp[3];
+ sub_v3_v3v3(circle_disp, circle_origin, orig_data.co);
+ normalize_v3(circle_disp);
+ mul_v3_fl(circle_disp, -boundary->circle.radius[propagation_steps]);
+ float target_circle_co[3];
+ add_v3_v3v3(target_circle_co, circle_origin, circle_disp);
+
+ float disp[3];
+ sub_v3_v3v3(disp, target_circle_co, vd.co);
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index);
+ madd_v3_v3v3fl(target_co,
+ vd.co,
+ disp,
+ boundary->edit_info[vd.index].strength_factor * mask * automask * strength);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
/* Main Brush Function. */
void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
@@ -981,6 +1076,9 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
case BRUSH_BOUNDARY_DEFORM_TWIST:
sculpt_boundary_twist_data_init(ss, ss->cache->boundaries[symm_area]);
break;
+ case BRUSH_BOUNDARY_DEFORM_CIRCLE:
+ sculpt_boundary_circle_data_init(ss, ss->cache->boundaries[symm_area]);
+ break;
case BRUSH_BOUNDARY_DEFORM_INFLATE:
case BRUSH_BOUNDARY_DEFORM_GRAB:
/* Do nothing. These deform modes don't need any extra data to be precomputed. */
@@ -1026,6 +1124,9 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
case BRUSH_BOUNDARY_DEFORM_SMOOTH:
BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_smooth_task_cb_ex, &settings);
break;
+ case BRUSH_BOUNDARY_DEFORM_CIRCLE:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_circle_task_cb_ex, &settings);
+ break;
}
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c
index a53a2126af4..856a2652e21 100644
--- a/source/blender/editors/sculpt_paint/sculpt_cloth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c
@@ -103,6 +103,10 @@
#include <stdlib.h>
#include <string.h>
+/* Experimental features. */
+
+#define USE_SOLVER_RIPPLE_CONSTRAINT false
+
static void cloth_brush_simulation_location_get(SculptSession *ss,
const Brush *brush,
float r_location[3])
@@ -195,8 +199,8 @@ static float cloth_brush_simulation_falloff_get(const Brush *brush,
#define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024
#define CLOTH_SIMULATION_TIME_STEP 0.01f
#define CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH 0.35f
-#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.01f
-#define CLOTH_DEFORMATION_GRAB_STRENGTH 0.1f
+#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.5f
+#define CLOTH_DEFORMATION_GRAB_STRENGTH 0.5f
static bool cloth_brush_sim_has_length_constraint(SculptClothSimulation *cloth_sim,
const int v1,
@@ -464,6 +468,17 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
BKE_pbvh_vertex_iter_end;
}
+static void cloth_brush_constraint_pos_to_line(SculptClothSimulation *cloth_sim, const int v)
+{
+ if (!USE_SOLVER_RIPPLE_CONSTRAINT) {
+ return;
+ }
+ float line_points[2][3];
+ copy_v3_v3(line_points[0], cloth_sim->init_pos[v]);
+ add_v3_v3v3(line_points[1], cloth_sim->init_pos[v], cloth_sim->init_normal[v]);
+ closest_to_line_v3(cloth_sim->pos[v], cloth_sim->pos[v], line_points[0], line_points[1]);
+}
+
static void cloth_brush_apply_force_to_vertex(SculptSession *UNUSED(ss),
SculptClothSimulation *cloth_sim,
const float force[3],
@@ -511,6 +526,17 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
plane_from_point_normal_v3(deform_plane, data->area_co, plane_normal);
}
+ KelvinletParams params;
+ const float kv_force = 1.0f;
+ const float kv_shear_modulus = 1.0f;
+ const float kv_poisson_ratio = 0.4f;
+ bool use_elastic_drag = false;
+ if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_ELASTIC_DRAG) {
+ BKE_kelvinlet_init_params(
+ &params, ss->cache->radius, kv_force, kv_shear_modulus, kv_poisson_ratio);
+ use_elastic_drag = true;
+ }
+
/* Gravity */
float gravity[3] = {0.0f};
if (ss->cache->supports_gravity) {
@@ -538,7 +564,9 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
cloth_brush_apply_force_to_vertex(ss, ss->cache->cloth_sim, vertex_gravity, vd.index);
/* When using the plane falloff mode the falloff is not constrained by the brush radius. */
- if (!sculpt_brush_test_sq_fn(&test, current_vertex_location) && !use_falloff_plane) {
+ /* Brushes that use elastic deformation are also not constrained by radius. */
+ if (!sculpt_brush_test_sq_fn(&test, current_vertex_location) && !use_falloff_plane &&
+ !use_elastic_drag) {
continue;
}
@@ -627,6 +655,24 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
cloth_sim->length_constraint_tweak[vd.index] += fade * 0.1f;
zero_v3(force);
break;
+ case BRUSH_CLOTH_DEFORM_ELASTIC_DRAG: {
+ float final_disp[3];
+ sub_v3_v3v3(brush_disp, ss->cache->location, ss->cache->last_location);
+ mul_v3_v3fl(final_disp, brush_disp, ss->cache->bstrength);
+ float location[3];
+ if (use_falloff_plane) {
+ closest_to_plane_v3(location, deform_plane, vd.co);
+ }
+ else {
+ copy_v3_v3(location, ss->cache->location);
+ }
+ BKE_kelvinlet_grab_triscale(final_disp, &params, vd.co, location, brush_disp);
+ mul_v3_fl(final_disp, 20.0f * (1.0f - fade));
+ add_v3_v3(cloth_sim->pos[vd.index], final_disp);
+ zero_v3(force);
+ }
+
+ break;
}
cloth_brush_apply_force_to_vertex(ss, ss->cache->cloth_sim, force, vd.index);
@@ -761,6 +807,17 @@ static void cloth_brush_solve_collision(Object *object,
mul_v3_m4v3(cloth_sim->pos[i], obmat_inv, cloth_sim->pos[i]);
}
}
+static void cloth_simulation_noise_get(float *r_noise,
+ SculptSession *ss,
+ const int index,
+ const float strength)
+{
+ const uint *hash_co = (const uint *)SCULPT_vertex_co_get(ss, index);
+ for (int i = 0; i < 3; i++) {
+ const uint hash = BLI_hash_int_2d(hash_co[0], hash_co[1]) ^ BLI_hash_int_2d(hash_co[2], i);
+ r_noise[i] = (hash * (1.0f / 0xFFFFFFFF) - 0.5f) * strength;
+ }
+}
static void do_cloth_brush_solve_simulation_task_cb_ex(
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
@@ -808,6 +865,16 @@ static void do_cloth_brush_solve_simulation_task_cb_ex(
madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v);
madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v);
+ /* Prevents the vertices from sliding without creating folds when all vertices and forces are
+ * in the same plane. */
+ float noise[3];
+ cloth_simulation_noise_get(noise, ss, vd.index, 0.000001f);
+ add_v3_v3(cloth_sim->pos[i], noise);
+
+ if (USE_SOLVER_RIPPLE_CONSTRAINT) {
+ cloth_brush_constraint_pos_to_line(cloth_sim, i);
+ }
+
if (cloth_sim->collider_list != NULL) {
cloth_brush_solve_collision(data->ob, cloth_sim, i);
}
@@ -920,6 +987,10 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss,
deformation_strength);
}
}
+ if (USE_SOLVER_RIPPLE_CONSTRAINT) {
+ cloth_brush_constraint_pos_to_line(cloth_sim, v1);
+ cloth_brush_constraint_pos_to_line(cloth_sim, v2);
+ }
}
}
}
@@ -1092,6 +1163,13 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss,
totverts, sizeof(float[3]), "cloth sim softbody pos");
}
+ if (USE_SOLVER_RIPPLE_CONSTRAINT) {
+ cloth_sim->init_normal = MEM_calloc_arrayN(totverts, sizeof(float) * 3, "init noramls");
+ for (int i = 0; i < totverts; i++) {
+ SCULPT_vertex_normal_get(ss, i, cloth_sim->init_normal[i]);
+ }
+ }
+
cloth_sim->mass = cloth_mass;
cloth_sim->damping = cloth_damping;
cloth_sim->softbody_strength = cloth_softbody_strength;
@@ -1264,6 +1342,7 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
MEM_SAFE_FREE(cloth_sim->init_pos);
MEM_SAFE_FREE(cloth_sim->deformation_strength);
MEM_SAFE_FREE(cloth_sim->node_state);
+ MEM_SAFE_FREE(cloth_sim->init_normal);
BLI_ghash_free(cloth_sim->node_state_index, NULL, NULL);
if (cloth_sim->collider_list) {
BKE_collider_cache_free(&cloth_sim->collider_list);
@@ -1361,6 +1440,25 @@ static EnumPropertyItem prop_cloth_filter_type[] = {
{0, NULL, 0, NULL, NULL},
};
+typedef enum eSculpClothFilterPinchOriginType {
+ CLOTH_FILTER_PINCH_ORIGIN_CURSOR,
+ CLOTH_FILTER_PINCH_ORIGIN_FACE_SET,
+} eSculptClothFilterPinchOriginType;
+
+static EnumPropertyItem prop_cloth_filter_pinch_origin_type[] = {
+ {CLOTH_FILTER_PINCH_ORIGIN_CURSOR,
+ "CURSOR",
+ 0,
+ "Cursor",
+ "Pinches to the location of the cursor"},
+ {CLOTH_FILTER_PINCH_ORIGIN_FACE_SET,
+ "FACE_SET",
+ 0,
+ "Face Set",
+ "Pinches to the average location of the Face Set"},
+ {0, NULL, 0, NULL, NULL},
+};
+
static EnumPropertyItem prop_cloth_filter_orientation_items[] = {
{SCULPT_FILTER_ORIENTATION_LOCAL,
"LOCAL",
@@ -1430,6 +1528,7 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata,
Sculpt *sd = data->sd;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
+ const ePaintSymmetryFlags symm = SCULPT_mesh_symmetry_xyz_get(data->ob);
SculptClothSimulation *cloth_sim = ss->filter_cache->cloth_sim;
@@ -1445,8 +1544,12 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata,
}
mul_v3_fl(sculpt_gravity, sd->gravity_factor * data->filter_strength);
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]);
+
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
float fade = vd.mask ? *vd.mask : 0.0f;
fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index);
fade = 1.0f - fade;
@@ -1480,11 +1583,17 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata,
cloth_sim->length_constraint_tweak[vd.index] += fade * data->filter_strength * 0.01f;
zero_v3(force);
break;
- case CLOTH_FILTER_PINCH:
- sub_v3_v3v3(force, ss->filter_cache->cloth_sim_pinch_point, vd.co);
+ case CLOTH_FILTER_PINCH: {
+ char symm_area = SCULPT_get_vertex_symm_area(orig_data.co);
+ float pinch_point[3];
+ copy_v3_v3(pinch_point, ss->filter_cache->cloth_sim_pinch_point);
+ SCULPT_flip_v3_by_symm_area(
+ pinch_point, symm, symm_area, ss->filter_cache->cloth_sim_pinch_point);
+ sub_v3_v3v3(force, pinch_point, vd.co);
normalize_v3(force);
mul_v3_fl(force, fade * data->filter_strength);
break;
+ }
case CLOTH_FILTER_SCALE:
unit_m3(transform);
scale_m3_fl(transform, 1.0f + (fade * data->filter_strength));
@@ -1569,6 +1678,28 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_RUNNING_MODAL;
}
+static void sculpt_cloth_filter_face_set_pinch_origin_calculate(float r_pinch_origin[3],
+ SculptSession *ss)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ const int active_face_set = SCULPT_active_face_set_get(ss);
+ float accum[3] = {0.0f};
+ int tot = 0;
+ for (int i = 0; i < totvert; i++) {
+ if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) {
+ continue;
+ }
+ add_v3_v3(accum, SCULPT_vertex_co_get(ss, i));
+ tot++;
+ }
+ if (tot > 0) {
+ mul_v3_v3fl(r_pinch_origin, accum, 1.0f / tot);
+ }
+ else {
+ copy_v3_v3(r_pinch_origin, SCULPT_active_vertex_co_get(ss));
+ }
+}
+
static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *ob = CTX_data_active_object(C);
@@ -1583,7 +1714,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SculptCursorGeometryInfo sgi;
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
SCULPT_vertex_random_access_ensure(ss);
@@ -1598,6 +1729,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
const float cloth_mass = RNA_float_get(op->ptr, "cloth_mass");
const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping");
const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions");
+ const int pinch_origin = RNA_enum_get(op->ptr, "pinch_origin");
ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create(
ss,
cloth_mass,
@@ -1606,7 +1738,15 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
use_collisions,
cloth_filter_is_deformation_filter(filter_type));
- copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss));
+ switch (pinch_origin) {
+ case CLOTH_FILTER_PINCH_ORIGIN_CURSOR:
+ copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss));
+ break;
+ case CLOTH_FILTER_PINCH_ORIGIN_FACE_SET:
+ sculpt_cloth_filter_face_set_pinch_origin_calculate(ss->filter_cache->cloth_sim_pinch_point,
+ ss);
+ break;
+ }
SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim);
@@ -1662,6 +1802,12 @@ void SCULPT_OT_cloth_filter(struct wmOperatorType *ot)
"Operation that is going to be applied to the mesh");
RNA_def_float(
ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter strength", -10.0f, 10.0f);
+ RNA_def_enum(ot->srna,
+ "pinch_origin",
+ prop_cloth_filter_pinch_origin_type,
+ CLOTH_FILTER_PINCH_ORIGIN_CURSOR,
+ "Pinch Origin",
+ "Location that is used to direct the pinch force");
RNA_def_enum_flag(ot->srna,
"force_axis",
prop_cloth_filter_force_axis_items,
diff --git a/source/blender/editors/sculpt_paint/sculpt_detail.c b/source/blender/editors/sculpt_paint/sculpt_detail.c
index 188bb0a88eb..12114806594 100644
--- a/source/blender/editors/sculpt_paint/sculpt_detail.c
+++ b/source/blender/editors/sculpt_paint/sculpt_detail.c
@@ -170,7 +170,7 @@ static void sample_detail_voxel(bContext *C, ViewContext *vc, int mx, int my)
/* Update the active vertex. */
const float mouse[2] = {mx, my};
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
/* Average the edge length of the connected edges to the active vertex. */
diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c
index 40874375772..08716fabe59 100644
--- a/source/blender/editors/sculpt_paint/sculpt_expand.c
+++ b/source/blender/editors/sculpt_paint/sculpt_expand.c
@@ -138,7 +138,8 @@ enum {
SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY,
SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS,
SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL,
- SCULPT_EXPAND_MODAL_SNAP_TOGGLE,
+ SCULPT_EXPAND_MODAL_SNAP_ENABLE,
+ SCULPT_EXPAND_MODAL_SNAP_DISABLE,
SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE,
SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE,
@@ -297,7 +298,7 @@ static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_
const float active_factor = fmod(expand_cache->active_falloff, loop_len);
const float falloff_factor = fmod(expand_cache->face_falloff[f], loop_len);
- enabled = falloff_factor < active_factor;
+ enabled = falloff_factor <= active_factor;
}
if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET) {
@@ -704,6 +705,71 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v)
return dists;
}
+/**
+ * Poly Loop:
+ */
+static float *sculpt_expand_poly_loop_falloff_create(Object *ob, const int v)
+{
+ SculptSession *ss = ob->sculpt;
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist");
+ BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices");
+ GSQueue *queue = BLI_gsqueue_new(sizeof(int));
+
+ printf("POLY LOOP FALLOFF\n");
+
+ /* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
+
+ BLI_bitmap *poly_loop = sculpt_poly_loop_from_cursor(ob);
+
+ for (int i = 0; i < ss->totfaces; i++) {
+ if (!BLI_BITMAP_TEST(poly_loop, i)) {
+ continue;
+ }
+ MPoly *poly = &ss->mpoly[i];
+ for (int l = 0; l < poly->totloop; l++) {
+ const int vertex = ss->mloop[poly->loopstart + l].v;
+ BLI_gsqueue_push(queue, &vertex);
+ BLI_BITMAP_ENABLE(visited_vertices, vertex);
+ }
+ }
+ MEM_freeN(poly_loop);
+ }
+
+ /* If there are no boundaries, return a falloff with all values set to 0. */
+ if (BLI_gsqueue_is_empty(queue)) {
+ return dists;
+ }
+
+ /* Propagate the values from the boundaries to the rest of the mesh. */
+ while (!BLI_gsqueue_is_empty(queue)) {
+ int v_next;
+ BLI_gsqueue_pop(queue, &v_next);
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_next, ni) {
+ if (BLI_BITMAP_TEST(visited_vertices, ni.index)) {
+ continue;
+ }
+ dists[ni.index] = dists[v_next] + 1.0f;
+ BLI_BITMAP_ENABLE(visited_vertices, ni.index);
+ BLI_gsqueue_push(queue, &ni.index);
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ }
+
+ BLI_gsqueue_free(queue);
+ MEM_freeN(visited_vertices);
+ return dists;
+}
+
/* Functions to update the max_falloff value in the #ExpandCache. These functions are called after
* initializing a new falloff to make sure that this value is always updated. */
@@ -946,7 +1012,8 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob,
BLI_BITMAP_ENABLE(enabled_vertices, i);
}
- if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ /* TODO: Default to topology. */
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && false) {
sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
}
else {
@@ -1033,6 +1100,11 @@ static void sculpt_expand_falloff_factors_from_vertex_and_symm_create(
sculpt_expand_initialize_from_face_set_boundary(
ob, expand_cache, expand_cache->initial_active_face_set, false);
break;
+ case SCULPT_EXPAND_FALLOFF_POLY_LOOP:
+ expand_cache->vert_falloff = has_topology_info ?
+ sculpt_expand_poly_loop_falloff_create(ob, v) :
+ sculpt_expand_spherical_falloff_create(ob, v);
+ break;
}
/* Update max falloff values and propagate to base mesh faces if needed. */
@@ -1471,7 +1543,7 @@ static int sculpt_expand_target_vertex_update_and_get(bContext *C,
{
SculptSession *ss = ob->sculpt;
SculptCursorGeometryInfo sgi;
- if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) {
+ if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) {
return SCULPT_active_vertex_get(ss);
}
return SCULPT_EXPAND_VERTEX_NONE;
@@ -1742,20 +1814,18 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event
}
break;
}
- case SCULPT_EXPAND_MODAL_SNAP_TOGGLE: {
- if (expand_cache->snap) {
- expand_cache->snap = false;
- if (expand_cache->snap_enabled_face_sets) {
- BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
- expand_cache->snap_enabled_face_sets = NULL;
- }
+ case SCULPT_EXPAND_MODAL_SNAP_ENABLE: {
+ expand_cache->snap = true;
+ if (!expand_cache->snap_enabled_face_sets) {
+ expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets");
}
- else {
- expand_cache->snap = true;
- if (!expand_cache->snap_enabled_face_sets) {
- expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets");
- }
- sculpt_expand_snap_initialize_from_enabled(ss, expand_cache);
+ sculpt_expand_snap_initialize_from_enabled(ss, expand_cache);
+ } break;
+ case SCULPT_EXPAND_MODAL_SNAP_DISABLE: {
+ expand_cache->snap = false;
+ if (expand_cache->snap_enabled_face_sets) {
+ BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
+ expand_cache->snap_enabled_face_sets = NULL;
}
} break;
case SCULPT_EXPAND_MODAL_MOVE_TOGGLE: {
@@ -2160,7 +2230,12 @@ void sculpt_expand_modal_keymap(wmKeyConfig *keyconf)
"Diagonals Falloff",
""},
{SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL, "FALLOFF_SPHERICAL", 0, "Spherical Falloff", ""},
- {SCULPT_EXPAND_MODAL_SNAP_TOGGLE, "SNAP_TOGGLE", 0, "Snap expand to Face Sets", ""},
+ {SCULPT_EXPAND_MODAL_SNAP_ENABLE, "SNAP_ENABLE", 0, "Snap expand to Face Sets", ""},
+ {SCULPT_EXPAND_MODAL_SNAP_DISABLE,
+ "SNAP_DISABLE",
+ 0,
+ "Disable Snap expand to Face Sets",
+ ""},
{SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
"LOOP_COUNT_INCREASE",
0,
@@ -2229,6 +2304,7 @@ void SCULPT_OT_expand(wmOperatorType *ot)
{SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY, "BOUNDARY_TOPOLOGY", 0, "Boundary Topology", ""},
{SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET, "BOUNDARY_FACE_SET", 0, "Boundary Face Set", ""},
{SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET, "ACTIVE_FACE_SET", 0, "Active Face Set", ""},
+ {SCULPT_EXPAND_FALLOFF_POLY_LOOP, "POLY_LOOP", 0, "POLY_LOOP", ""},
{0, NULL, 0, NULL, NULL},
};
diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.c b/source/blender/editors/sculpt_paint/sculpt_face_set.c
index bdbdb75732a..29c3c7f1184 100644
--- a/source/blender/editors/sculpt_paint/sculpt_face_set.c
+++ b/source/blender/editors/sculpt_paint/sculpt_face_set.c
@@ -111,7 +111,7 @@ int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const flo
}
SculptCursorGeometryInfo gi;
- if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) {
+ if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false, false)) {
return SCULPT_FACE_SET_NONE;
}
@@ -512,7 +512,6 @@ static EnumPropertyItem prop_sculpt_face_sets_init_types[] = {
"Face Sets from Face Set Boundaries",
"Create a Face Set per isolated Face Set",
},
-
{0, NULL, 0, NULL, NULL},
};
@@ -968,7 +967,7 @@ static int sculpt_face_sets_change_visibility_invoke(bContext *C,
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
SCULPT_vertex_random_access_ensure(ss);
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
return sculpt_face_sets_change_visibility_exec(C, op);
}
@@ -1052,6 +1051,10 @@ typedef enum eSculptFaceSetEditMode {
SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2,
SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3,
SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4,
+ SCULPT_FACE_SET_EDIT_FAIR_CURVATURE = 5,
+ SCULPT_FACE_SET_EDIT_FILL_COMPONENT = 6,
+ SCULPT_FACE_SET_EDIT_EXTRUDE = 7,
+ SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY = 8,
} eSculptFaceSetEditMode;
static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = {
@@ -1092,6 +1095,37 @@ static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = {
"Creates a smooth as possible geometry patch from the Face Set minimizing changes in "
"vertex tangents",
},
+ /*
+ {
+ SCULPT_FACE_SET_EDIT_FAIR_CURVATURE,
+ "FAIR_CURVATURE",
+ 0,
+ "Fair Curvature",
+ "Creates a smooth as possible geometry patch from the Face Set minimizing changes in "
+ "surface curvature",
+ },
+ */
+ {
+ SCULPT_FACE_SET_EDIT_FILL_COMPONENT,
+ "FILL_COMPONENT",
+ 0,
+ "Fill Component",
+ "Expand a Face Set to fill all affected connected components",
+ },
+ {
+ SCULPT_FACE_SET_EDIT_EXTRUDE,
+ "EXTRUDE",
+ 0,
+ "Extrude",
+ "Extrude a Face Set along the normals of the faces",
+ },
+ {
+ SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY,
+ "ALL_TANGENCY",
+ 0,
+ "All tangency",
+ "Extrude a Face Set along the normals of the faces",
+ },
{0, NULL, 0, NULL, NULL},
};
@@ -1123,6 +1157,38 @@ static void sculpt_face_set_grow(Object *ob,
}
}
+static void sculpt_face_set_fill_component(Object *ob,
+ SculptSession *ss,
+ const int active_face_set_id,
+ const bool UNUSED(modify_hidden))
+{
+ SCULPT_connected_components_ensure(ob);
+ GSet *connected_components = BLI_gset_int_new("affected_components");
+
+ const int totvert = SCULPT_vertex_count_get(ss);
+ for (int i = 0; i < totvert; i++) {
+ if (!SCULPT_vertex_has_face_set(ss, i, active_face_set_id)) {
+ continue;
+ }
+ const int vertex_connected_component = ss->vertex_info.connected_component[i];
+ if (BLI_gset_haskey(connected_components, POINTER_FROM_INT(vertex_connected_component))) {
+ continue;
+ }
+ BLI_gset_add(connected_components, POINTER_FROM_INT(vertex_connected_component));
+ }
+
+ for (int i = 0; i < totvert; i++) {
+ const int vertex_connected_component = ss->vertex_info.connected_component[i];
+ if (!BLI_gset_haskey(connected_components, POINTER_FROM_INT(vertex_connected_component))) {
+ continue;
+ }
+
+ SCULPT_vertex_face_set_set(ss, i, active_face_set_id);
+ }
+
+ BLI_gset_free(connected_components, NULL);
+}
+
static void sculpt_face_set_shrink(Object *ob,
SculptSession *ss,
const int *prev_face_sets,
@@ -1268,6 +1334,10 @@ static void sculpt_face_set_apply_edit(Object *ob,
MEM_SAFE_FREE(prev_face_sets);
break;
}
+ case SCULPT_FACE_SET_EDIT_FILL_COMPONENT: {
+ sculpt_face_set_fill_component(ob, ss, active_face_set_id, modify_hidden);
+ break;
+ }
case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden);
break;
@@ -1277,6 +1347,23 @@ static void sculpt_face_set_apply_edit(Object *ob,
case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY:
sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY);
break;
+ case SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY: {
+ GSet *face_sets_ids = BLI_gset_int_new("ids");
+ for (int i = 0; i < ss->totfaces; i++) {
+ BLI_gset_add(face_sets_ids, POINTER_FROM_INT(ss->face_sets[i]));
+ }
+
+ GSetIterator gs_iter;
+ GSET_ITER (gs_iter, face_sets_ids) {
+ const int face_set_id = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter));
+ sculpt_face_set_edit_fair_face_set(ob, face_set_id, MESH_FAIRING_DEPTH_TANGENCY);
+ }
+
+ BLI_gset_free(face_sets_ids, NULL);
+ } break;
+ case SCULPT_FACE_SET_EDIT_FAIR_CURVATURE:
+ sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_CURVATURE);
+ break;
}
}
@@ -1289,7 +1376,7 @@ static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss,
return false;
}
- if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) {
+ if (ELEM(mode, SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY, SCULPT_FACE_SET_EDIT_EXTRUDE)) {
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
/* Modification of base mesh geometry requires special remapping of multires displacement,
* which does not happen here.
@@ -1397,6 +1484,197 @@ static void sculpt_face_set_edit_modify_coordinates(bContext *C,
MEM_freeN(nodes);
}
+typedef struct FaceSetExtrudeCD {
+ int active_face_set;
+ float cursor_location[3];
+ float (*orig_co)[3];
+ float init_mval[2];
+ float (*orig_no)[3];
+} FaceSetExtrudeCD;
+
+static void sculpt_face_set_extrude_id(Object *ob, SculptSession *ss, const int active_face_set_id)
+{
+
+ Mesh *mesh = ob->data;
+ const int next_face_set_id = ED_sculpt_face_sets_find_next_available_id(mesh);
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
+ BMesh *bm = BM_mesh_create(&allocsize,
+ &((struct BMeshCreateParams){
+ .use_toolflags = true,
+ }));
+
+ BM_mesh_bm_from_me(bm,
+ mesh,
+ (&(struct BMeshFromMeshParams){
+ .calc_face_normal = true,
+ }));
+
+ BM_mesh_elem_table_init(bm, BM_FACE);
+ BM_mesh_elem_table_ensure(bm, BM_FACE);
+ BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_TAG, false);
+ BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_SELECT, false);
+ BMIter iter;
+ BMFace *f;
+ BM_mesh_select_mode_set(bm, SCE_SELECT_FACE);
+ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
+ const int face_index = BM_elem_index_get(f);
+ const int face_set_id = ss->face_sets[face_index];
+ BM_elem_select_set(bm, (BMElem *)f, face_set_id == active_face_set_id);
+ BM_elem_flag_set(f, BM_ELEM_TAG, face_set_id == active_face_set_id);
+ }
+ BM_mesh_select_flush(bm);
+ BM_mesh_select_mode_flush(bm);
+
+ BMOperator extop;
+ BMO_op_init(bm, &extop, BMO_FLAG_DEFAULTS, "extrude_face_region");
+ BMO_slot_bool_set(extop.slots_in, "use_normal_flip", true);
+ BMO_slot_bool_set(extop.slots_in, "use_dissolve_ortho_edges", true);
+ BMO_slot_bool_set(extop.slots_in, "use_select_history", true);
+ char htype = BM_ALL_NOLOOP;
+ htype &= ~(BM_VERT | BM_EDGE);
+ if (htype & BM_FACE) {
+ htype |= BM_EDGE;
+ }
+
+ BMO_slot_buffer_from_enabled_hflag(bm, &extop, extop.slots_in, "geom", htype, BM_ELEM_SELECT);
+ BMO_op_exec(bm, &extop);
+ BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_SELECT, false);
+
+ BMOIter siter;
+ BMElem *ele;
+ BMO_ITER (ele, &siter, extop.slots_out, "geom.out", BM_ALL_NOLOOP) {
+ BM_elem_flag_set(ele, BM_ELEM_TAG, true);
+ }
+ BMO_op_finish(bm, &extop);
+
+ /* Set the new Face Set ID for the extrusion. */
+ const int cd_face_sets_offset = CustomData_get_offset(&bm->pdata, CD_SCULPT_FACE_SETS);
+ BM_mesh_elem_table_ensure(bm, BM_FACE);
+ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
+ const int face_set_id = BM_ELEM_CD_GET_INT(f, cd_face_sets_offset);
+ if (face_set_id == active_face_set_id) {
+ continue;
+ }
+ BMVert *v;
+ BMIter face_iter;
+ BM_ITER_ELEM (v, &face_iter, f, BM_VERTS_OF_FACE) {
+ if (BM_elem_flag_test(v, BM_ELEM_TAG)) {
+ BM_ELEM_CD_SET_INT(f, cd_face_sets_offset, next_face_set_id);
+ break;
+ }
+ }
+ }
+
+ BM_mesh_elem_hflag_enable_all(bm, BM_FACE, BM_ELEM_TAG, false);
+ BMO_op_callf(bm,
+ (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
+ "recalc_face_normals faces=%hf",
+ BM_ELEM_TAG);
+
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
+ BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
+
+ BM_mesh_bm_to_me(NULL,
+ bm,
+ ob->data,
+ (&(struct BMeshToMeshParams){
+ .calc_object_remap = false,
+ }));
+
+ BM_mesh_free(bm);
+}
+
+static int sculpt_face_set_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
+
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ if (mode != SCULPT_FACE_SET_EDIT_EXTRUDE) {
+ return OPERATOR_FINISHED;
+ }
+
+ if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
+ FaceSetExtrudeCD *fsecd = op->customdata;
+ MEM_SAFE_FREE(fsecd->orig_co);
+ MEM_SAFE_FREE(fsecd->orig_no);
+ MEM_SAFE_FREE(op->customdata);
+ ED_sculpt_undo_geometry_end(ob);
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS);
+ return OPERATOR_FINISHED;
+ }
+
+ FaceSetExtrudeCD *fsecd = op->customdata;
+ float depth_world_space[3];
+ float new_pos[3];
+ mul_v3_m4v3(depth_world_space, ob->obmat, fsecd->cursor_location);
+ ViewContext vc;
+ ED_view3d_viewcontext_init(C, &vc, depsgraph);
+ float fmval[2] = {event->mval[0], fsecd->init_mval[1]};
+ ED_view3d_win_to_3d(vc.v3d, vc.region, depth_world_space, fmval, new_pos);
+ float extrude_disp = len_v3v3(depth_world_space, new_pos);
+
+ if (event->mval[0] <= fsecd->init_mval[0]) {
+ extrude_disp *= -1.0f;
+ }
+
+ if (!fsecd->orig_co) {
+ fsecd->orig_co = MEM_calloc_arrayN(totvert, sizeof(float) * 3, "origco");
+ fsecd->orig_no = MEM_calloc_arrayN(totvert, sizeof(float) * 3, "origno");
+ for (int i = 0; i < totvert; i++) {
+ copy_v3_v3(fsecd->orig_co[i], SCULPT_vertex_co_get(ss, i));
+ SCULPT_vertex_normal_get(ss, i, fsecd->orig_no[i]);
+ }
+ }
+
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+ for (int i = 0; i < totvert; i++) {
+ if (!SCULPT_vertex_has_face_set(ss, i, fsecd->active_face_set)) {
+ continue;
+ }
+ madd_v3_v3v3fl(mvert[i].co, fsecd->orig_co[i], fsecd->orig_no[i], extrude_disp);
+ mvert[i].flag |= ME_VERT_PBVH_UPDATE;
+ }
+
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ for (int i = 0; i < totnode; i++) {
+ BKE_pbvh_node_mark_update(nodes[i]);
+ }
+ MEM_SAFE_FREE(nodes);
+
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS);
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void sculpt_face_set_extrude_begin(bContext *C,
+ wmOperator *op,
+ const wmEvent *event,
+ Object *ob,
+ const int active_face_set,
+ const float cursor_location[3])
+{
+ FaceSetExtrudeCD *fsecd = MEM_callocN(sizeof(FaceSetExtrudeCD), "face set extrude cd");
+ fsecd->active_face_set = active_face_set;
+ copy_v3_v3(fsecd->cursor_location, cursor_location);
+ float fmval[2] = {event->mval[0], event->mval[1]};
+ copy_v2_v2(fsecd->init_mval, fmval);
+ op->customdata = fsecd;
+
+ ED_sculpt_undo_geometry_begin(ob, "face set extrude");
+
+ sculpt_face_set_extrude_id(ob, ob->sculpt, active_face_set);
+
+ BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
+}
+
static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *ob = CTX_data_active_object(C);
@@ -1416,22 +1694,32 @@ static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEven
* tool without brush cursor. */
SculptCursorGeometryInfo sgi;
const float mouse[2] = {event->mval[0], event->mval[1]};
- if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) {
+
+ if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) {
/* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */
return OPERATOR_CANCELLED;
}
+
const int active_face_set = SCULPT_active_face_set_get(ss);
switch (mode) {
+ case SCULPT_FACE_SET_EDIT_EXTRUDE:
+ sculpt_face_set_extrude_begin(C, op, event, ob, active_face_set, sgi.location);
+ SCULPT_tag_update_overlays(C);
+ WM_event_add_modal_handler(C, op);
+ return OPERATOR_RUNNING_MODAL;
case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden);
break;
case SCULPT_FACE_SET_EDIT_GROW:
case SCULPT_FACE_SET_EDIT_SHRINK:
+ case SCULPT_FACE_SET_EDIT_FILL_COMPONENT:
sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden);
break;
case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS:
case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY:
+ case SCULPT_FACE_SET_EDIT_FAIR_CURVATURE:
+ case SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY:
sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode);
break;
}
@@ -1450,6 +1738,7 @@ void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot)
/* Api callbacks. */
ot->invoke = sculpt_face_set_edit_invoke;
+ ot->modal = sculpt_face_set_edit_modal;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c
new file mode 100644
index 00000000000..808515ab723
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c
@@ -0,0 +1,208 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_hash.h"
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_customdata_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_fair.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+#include "WM_message.h"
+#include "WM_toolsystem.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_view3d.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+typedef enum eSculptFaceSetByTopologyMode {
+ SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART = 0,
+ SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP = 1,
+};
+
+static EnumPropertyItem prop_sculpt_face_set_by_topology[] = {
+ {
+ SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART,
+ "LOOSE_PART",
+ 0,
+ "Loose Part",
+ "",
+ },
+ {
+ SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP,
+ "POLY_LOOP",
+ 0,
+ "Face Loop",
+ "",
+ },
+ {0, NULL, 0, NULL, NULL},
+};
+
+static void sculpt_face_set_by_topology_poly_loop(Object *ob, const int next_face_set_id)
+{
+ SculptSession *ss = ob->sculpt;
+ BLI_bitmap *poly_loop = sculpt_poly_loop_from_cursor(ob);
+ for (int i = 0; i < ss->totfaces; i++) {
+ if (BLI_BITMAP_TEST(poly_loop, i)) {
+ ss->face_sets[i] = next_face_set_id;
+ }
+ }
+ MEM_freeN(poly_loop);
+}
+
+static int sculpt_face_set_by_topology_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ const bool repeat_previous = RNA_boolean_get(op->ptr, "repeat_previous");
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
+ printf("FACE SET TOPOLOGY\n");
+
+ /* Update the current active Face Set and Vertex as the operator can be used directly from the
+ * tool without brush cursor. */
+ SculptCursorGeometryInfo sgi;
+ const float mouse[2] = {event->mval[0], event->mval[1]};
+ if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) {
+ /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */
+ return OPERATOR_CANCELLED;
+ }
+
+ PBVHNode **nodes;
+ int totnode;
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
+ SCULPT_undo_push_begin(ob, "face set edit");
+ SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+
+ const int initial_poly = ss->active_face_index;
+ const int initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob);
+
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+ int new_face_set = SCULPT_FACE_SET_NONE;
+
+ if (repeat_previous && ss->face_set_last_created != SCULPT_FACE_SET_NONE &&
+ initial_poly != ss->face_set_last_poly && initial_edge != ss->face_set_last_edge) {
+ new_face_set = ss->face_set_last_created;
+ }
+ else {
+ new_face_set = ED_sculpt_face_sets_find_next_available_id(mesh);
+ }
+
+ switch (mode) {
+ case SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART:
+ break;
+ case SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP:
+ sculpt_face_set_by_topology_poly_loop(ob, new_face_set);
+ break;
+ }
+
+ ss->face_set_last_created = new_face_set;
+ ss->face_set_last_edge = initial_edge;
+ ss->face_set_last_poly = initial_poly;
+
+ /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */
+ SCULPT_visibility_sync_all_face_sets_to_vertices(ob);
+
+ for (int i = 0; i < totnode; i++) {
+ BKE_pbvh_node_mark_update_visibility(nodes[i]);
+ }
+
+ 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);
+ }
+
+ MEM_freeN(nodes);
+
+ SCULPT_undo_push_end();
+ SCULPT_tag_update_overlays(C);
+
+ return OPERATOR_FINISHED;
+}
+
+void SCULPT_OT_face_set_by_topology(struct wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Face Set by Topology";
+ ot->idname = "SCULPT_OT_face_set_by_topology";
+ ot->description = "Create a new Face Set following the mesh topology";
+
+ /* Api callbacks. */
+ ot->invoke = sculpt_face_set_by_topology_invoke;
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_enum(ot->srna,
+ "mode",
+ prop_sculpt_face_set_by_topology,
+ SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP,
+ "Mode",
+ "");
+
+ RNA_def_boolean(ot->srna,
+ "repeat_previous",
+ true,
+ "Repeat previous Face Set",
+ "Repeat the latest created Face Set instead of a new one");
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
index 4b49bf2cefb..859ca9b2421 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
@@ -274,7 +274,7 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SculptCursorGeometryInfo sgi;
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
}
/* Disable for multires and dyntopo for now */
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c
index 10f141e2311..97b960ff9e4 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c
@@ -316,6 +316,858 @@ void SCULPT_OT_mask_filter(struct wmOperatorType *ot)
"Use a automatic number of iterations based on the number of vertices of the sculpt");
}
+/******************************************************************************************/
+/* Interactive Preview Mask Filter */
+
+#define SCULPT_IPMASK_FILTER_MIN_MULTITHREAD 1000
+#define SCULPT_IPMASK_FILTER_GRANULARITY 100
+
+#define SCULPT_IPMASK_FILTER_QUANTIZE_STEP 0.1
+
+typedef enum eSculptIPMaskFilterType {
+ IPMASK_FILTER_SMOOTH_SHARPEN,
+ IPMASK_FILTER_GROW_SHRINK,
+ IPMASK_FILTER_HARDER_SOFTER,
+ IPMASK_FILTER_CONTRAST,
+ IPMASK_FILTER_ADD_SUBSTRACT,
+ IPMASK_FILTER_INVERT,
+ IPMASK_FILTER_QUANTIZE,
+} eSculptIPMaskFilterType;
+
+typedef enum MaskFilterStepDirectionType {
+ MASK_FILTER_STEP_DIRECTION_FORWARD,
+ MASK_FILTER_STEP_DIRECTION_BACKWARD,
+} MaskFilterStepDirectionType;
+
+/* Grown/Shrink vertex callbacks. */
+static float sculpt_ipmask_vertex_grow_cb(SculptSession *ss, const int vertex, float *current_mask)
+{
+ float max = 0.0f;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
+ float vmask_f = current_mask[ni.index];
+ if (vmask_f > max) {
+ max = vmask_f;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ return max;
+}
+
+static float sculpt_ipmask_vertex_shrink_cb(SculptSession *ss,
+ const int vertex,
+ float *current_mask)
+{
+ float min = 1.0f;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
+ float vmask_f = current_mask[ni.index];
+ if (vmask_f < min) {
+ min = vmask_f;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ return min;
+}
+
+/* Smooth/Sharpen vertex callbacks. */
+static float sculpt_ipmask_vertex_smooth_cb(SculptSession *ss,
+ const int vertex,
+ float *current_mask)
+{
+ float accum = current_mask[vertex];
+ int total = 1;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
+ accum += current_mask[ni.index];
+ total++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ return total > 0 ? accum / total : current_mask[vertex];
+}
+
+static float sculpt_ipmask_vertex_sharpen_cb(SculptSession *ss,
+ const int vertex,
+ float *current_mask)
+{
+ float accum = 0.0f;
+ int total = 0;
+ float vmask = current_mask[vertex];
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
+ accum += current_mask[ni.index];
+ total++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ const float avg = total > 0 ? accum / total : current_mask[vertex];
+ const float val = avg - vmask;
+
+ float new_mask;
+ if (vmask > 0.5f) {
+ new_mask = vmask + 0.03f;
+ }
+ else {
+ new_mask = vmask - 0.03f;
+ }
+ new_mask += val / 2.0f;
+ return clamp_f(new_mask, 0.0f, 1.0f);
+
+#ifdef SHARP_KERNEL
+ float accum = 0.0f;
+ float weight_accum = 0.0f;
+ const float neighbor_weight = -1.0f;
+ int neighbor_count = 0;
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
+ accum += neighbor_weight * current_mask[ni.index];
+ weight_accum += neighbor_weight;
+ neighbor_count++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ const float main_weight = (neighbor_count + 1) * 2.0f;
+ accum += main_weight * current_mask[vertex];
+ weight_accum += main_weight;
+
+ return clamp_f(accum / weight_accum, 0.0f, 1.0f);
+#endif
+}
+
+/* Harder/Softer callbacks. */
+#define SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP 0.01f
+static float sculpt_ipmask_vertex_harder_cb(SculptSession *UNUSED(ss),
+ const int vertex,
+ float *current_mask)
+{
+ return clamp_f(current_mask[vertex] += current_mask[vertex] *
+ SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP,
+ 0.0f,
+ 1.0f);
+}
+
+static float sculpt_ipmask_vertex_softer_cb(SculptSession *UNUSED(ss),
+ const int vertex,
+ float *current_mask)
+{
+ return clamp_f(current_mask[vertex] -= current_mask[vertex] *
+ SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP,
+ 0.0f,
+ 1.0f);
+}
+
+/* Contrast Increase/Decrease callbacks. */
+
+#define SCULPT_IPMASK_FILTER_CONTRAST_STEP 0.05f
+static float sculpt_ipmask_filter_contrast(const float mask, const float contrast)
+{
+ float offset;
+ float delta = contrast / 2.0f;
+ float gain = 1.0f - delta * 2.0f;
+ if (contrast > 0.0f) {
+ gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON);
+ offset = gain * (-delta);
+ }
+ else {
+ delta *= -1.0f;
+ offset = gain * (delta);
+ }
+ return clamp_f(gain * mask + offset, 0.0f, 1.0f);
+}
+
+static float sculpt_ipmask_vertex_contrast_increase_cb(SculptSession *UNUSED(ss),
+ const int vertex,
+ float *current_mask)
+{
+ return sculpt_ipmask_filter_contrast(current_mask[vertex], SCULPT_IPMASK_FILTER_CONTRAST_STEP);
+}
+
+static float sculpt_ipmask_vertex_contrast_decrease_cb(SculptSession *UNUSED(ss),
+ const int vertex,
+ float *current_mask)
+{
+ return sculpt_ipmask_filter_contrast(current_mask[vertex],
+ -1.0f * SCULPT_IPMASK_FILTER_CONTRAST_STEP);
+}
+
+static MaskFilterDeltaStep *sculpt_ipmask_filter_delta_create(const float *current_mask,
+ const float *next_mask,
+ const int totvert)
+{
+ int tot_modified_values = 0;
+ for (int i = 0; i < totvert; i++) {
+ if (current_mask[i] == next_mask[i]) {
+ continue;
+ }
+ tot_modified_values++;
+ }
+
+ MaskFilterDeltaStep *delta_step = MEM_callocN(sizeof(MaskFilterDeltaStep),
+ "mask filter delta step");
+ delta_step->totelem = tot_modified_values;
+ delta_step->index = MEM_malloc_arrayN(sizeof(int), tot_modified_values, "delta indices");
+ delta_step->delta = MEM_malloc_arrayN(sizeof(float), tot_modified_values, "delta values");
+
+ int delta_step_index = 0;
+ for (int i = 0; i < totvert; i++) {
+ if (current_mask[i] == next_mask[i]) {
+ continue;
+ }
+ delta_step->index[delta_step_index] = i;
+ delta_step->delta[delta_step_index] = next_mask[i] - current_mask[i];
+ delta_step_index++;
+ }
+ return delta_step;
+}
+
+typedef struct SculptIPMaskFilterTaskData {
+ SculptSession *ss;
+ float *next_mask;
+ float *current_mask;
+ MaskFilterStepDirectionType direction;
+} SculptIPMaskFilterTaskData;
+
+static void ipmask_filter_compute_step_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptIPMaskFilterTaskData *data = userdata;
+ if (data->direction == MASK_FILTER_STEP_DIRECTION_FORWARD) {
+ data->next_mask[i] = data->ss->filter_cache->mask_filter_step_forward(
+ data->ss, i, data->current_mask);
+ }
+ else {
+ data->next_mask[i] = data->ss->filter_cache->mask_filter_step_backward(
+ data->ss, i, data->current_mask);
+ }
+}
+
+static float *sculpt_ipmask_step_compute(SculptSession *ss,
+ float *current_mask,
+ MaskFilterStepDirectionType direction)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *next_mask = MEM_malloc_arrayN(sizeof(float), totvert, "delta values");
+
+ SculptIPMaskFilterTaskData data = {
+ .ss = ss,
+ .next_mask = next_mask,
+ .current_mask = current_mask,
+ .direction = direction,
+ };
+ TaskParallelSettings settings;
+ memset(&settings, 0, sizeof(TaskParallelSettings));
+ settings.use_threading = totvert > SCULPT_IPMASK_FILTER_MIN_MULTITHREAD;
+ settings.min_iter_per_thread = SCULPT_IPMASK_FILTER_GRANULARITY;
+ BLI_task_parallel_range(0, totvert, &data, ipmask_filter_compute_step_task_cb, &settings);
+
+ return next_mask;
+}
+
+static float *sculpt_ipmask_current_state_get(SculptSession *ss)
+{
+ return MEM_dupallocN(ss->filter_cache->mask_filter_ref);
+}
+
+static void sculpt_ipmask_reference_set(SculptSession *ss, float *new_mask)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ for (int i = 0; i < totvert; i++) {
+ ss->filter_cache->mask_filter_ref[i] = new_mask[i];
+ }
+}
+
+static void sculpt_ipmask_store_reference_step(SculptSession *ss)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ if (!ss->filter_cache->mask_filter_ref) {
+ ss->filter_cache->mask_filter_ref = MEM_malloc_arrayN(sizeof(float), totvert, "delta values");
+ }
+
+ for (int i = 0; i < totvert; i++) {
+ ss->filter_cache->mask_filter_ref[i] = SCULPT_vertex_mask_get(ss, i);
+ }
+}
+
+static void ipmask_filter_apply_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ss;
+ FilterCache *filter_cache = ss->filter_cache;
+ PBVHNode *node = filter_cache->nodes[i];
+ PBVHVertexIter vd;
+ bool update = false;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ if (SCULPT_automasking_factor_get(filter_cache->automasking, ss, vd.index) < 0.5f) {
+ continue;
+ }
+
+ float new_mask;
+ if (data->next_mask) {
+ new_mask = interpf(
+ data->next_mask[vd.index], data->new_mask[vd.index], data->mask_interpolation);
+ }
+ else {
+ new_mask = data->new_mask[vd.index];
+ }
+
+ if (*vd.mask == new_mask) {
+ continue;
+ }
+
+ *vd.mask = new_mask;
+ update = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (update) {
+ BKE_pbvh_node_mark_redraw(node);
+ }
+}
+
+static void sculpt_ipmask_apply_mask_data(SculptSession *ss,
+ float *new_mask,
+ float *next_mask,
+ const float interpolation)
+{
+ FilterCache *filter_cache = ss->filter_cache;
+ SculptThreadedTaskData data = {
+ .ss = ss,
+ .nodes = filter_cache->nodes,
+ .new_mask = new_mask,
+ .next_mask = next_mask,
+ .mask_interpolation = interpolation,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode);
+ BLI_task_parallel_range(0, filter_cache->totnode, &data, ipmask_filter_apply_task_cb, &settings);
+}
+
+static float *sculpt_ipmask_apply_delta_step(MaskFilterDeltaStep *delta_step,
+ const float *current_mask,
+ const MaskFilterStepDirectionType direction)
+{
+ float *next_mask = MEM_dupallocN(current_mask);
+ for (int i = 0; i < delta_step->totelem; i++) {
+ if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) {
+ next_mask[delta_step->index[i]] = current_mask[delta_step->index[i]] + delta_step->delta[i];
+ }
+ else {
+ next_mask[delta_step->index[i]] = current_mask[delta_step->index[i]] - delta_step->delta[i];
+ }
+ }
+ return next_mask;
+}
+
+static float *sculpt_ipmask_restore_state_from_delta(SculptSession *ss,
+ MaskFilterDeltaStep *delta_step,
+ MaskFilterStepDirectionType direction)
+{
+ float *current_mask = sculpt_ipmask_current_state_get(ss);
+ float *next_mask = sculpt_ipmask_apply_delta_step(delta_step, current_mask, direction);
+ MEM_freeN(current_mask);
+ return next_mask;
+}
+
+static float *sculpt_ipmask_compute_and_store_step(SculptSession *ss,
+ const int iterations,
+ const int delta_index,
+ MaskFilterStepDirectionType direction)
+{
+ BLI_assert(iterations > 0);
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float *current_mask = sculpt_ipmask_current_state_get(ss);
+ float *original_mask = MEM_dupallocN(current_mask);
+ float *next_mask = NULL;
+
+ /* Compute the filter. */
+ for (int i = 0; i < iterations; i++) {
+ MEM_SAFE_FREE(next_mask);
+ next_mask = sculpt_ipmask_step_compute(ss, current_mask, direction);
+ MEM_freeN(current_mask);
+ current_mask = MEM_dupallocN(next_mask);
+ }
+ MEM_freeN(current_mask);
+
+ /* Pack and store the delta step. */
+ MaskFilterDeltaStep *delta_step;
+ if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) {
+ delta_step = sculpt_ipmask_filter_delta_create(original_mask, next_mask, totvert);
+ }
+ else {
+ delta_step = sculpt_ipmask_filter_delta_create(next_mask, original_mask, totvert);
+ }
+ BLI_ghash_insert(ss->filter_cache->mask_delta_step, POINTER_FROM_INT(delta_index), delta_step);
+ MEM_freeN(original_mask);
+
+ return next_mask;
+}
+
+static float *sculpt_ipmask_filter_mask_for_step_get(SculptSession *ss,
+ MaskFilterStepDirectionType direction,
+ const int iteration_count)
+{
+ FilterCache *filter_cache = ss->filter_cache;
+ int next_step = filter_cache->mask_filter_current_step;
+ int delta_index = next_step;
+ /* Get the next step and the delta step index associated with it. */
+ if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) {
+ next_step = filter_cache->mask_filter_current_step + 1;
+ delta_index = filter_cache->mask_filter_current_step;
+ }
+ else {
+ next_step = filter_cache->mask_filter_current_step - 1;
+ delta_index = filter_cache->mask_filter_current_step - 1;
+ }
+
+ /* Update the data one step forward/backward. */
+ if (BLI_ghash_haskey(filter_cache->mask_delta_step, POINTER_FROM_INT(delta_index))) {
+ /* This step was already computed, restore it from the current step and a delta. */
+ MaskFilterDeltaStep *delta_step = BLI_ghash_lookup(filter_cache->mask_delta_step,
+ POINTER_FROM_INT(delta_index));
+ return sculpt_ipmask_restore_state_from_delta(ss, delta_step, direction);
+ }
+
+ /* New step that was not yet computed. Compute and store the delta. */
+ return sculpt_ipmask_compute_and_store_step(ss, iteration_count, delta_index, direction);
+}
+
+static void sculpt_ipmask_filter_update_to_target_step(SculptSession *ss,
+ const int target_step,
+ const int iteration_count,
+ const float step_interpolation)
+{
+ FilterCache *filter_cache = ss->filter_cache;
+
+ MaskFilterStepDirectionType direction;
+ /* Get the next step and the delta step index associated with it. */
+ if (target_step > filter_cache->mask_filter_current_step) {
+ direction = MASK_FILTER_STEP_DIRECTION_FORWARD;
+ }
+ else {
+ direction = MASK_FILTER_STEP_DIRECTION_BACKWARD;
+ }
+
+ while (filter_cache->mask_filter_current_step != target_step) {
+ /* Restore or compute a mask in the given direction. */
+ float *new_mask = sculpt_ipmask_filter_mask_for_step_get(ss, direction, iteration_count);
+
+ /* Store the full step. */
+ sculpt_ipmask_reference_set(ss, new_mask);
+ MEM_freeN(new_mask);
+
+ /* Update the current step count. */
+ if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) {
+ filter_cache->mask_filter_current_step += 1;
+ }
+ else {
+ filter_cache->mask_filter_current_step -= 1;
+ }
+ }
+
+ if (step_interpolation != 0.0f) {
+ float *next_mask = sculpt_ipmask_filter_mask_for_step_get(
+ ss, MASK_FILTER_STEP_DIRECTION_FORWARD, iteration_count);
+ sculpt_ipmask_apply_mask_data(
+ ss, filter_cache->mask_filter_ref, next_mask, step_interpolation);
+ MEM_freeN(next_mask);
+ }
+ else {
+ sculpt_ipmask_apply_mask_data(ss, filter_cache->mask_filter_ref, NULL, 0.0f);
+ }
+}
+
+static void ipmask_filter_apply_from_original_task_cb(
+ void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ss;
+ FilterCache *filter_cache = ss->filter_cache;
+ PBVHNode *node = filter_cache->nodes[i];
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ const eSculptIPMaskFilterType filter_type = data->filter_type;
+ bool update = false;
+
+ /* Used for quantize filter. */
+ const int steps = data->filter_strength / SCULPT_IPMASK_FILTER_QUANTIZE_STEP;
+ if (steps == 0) {
+ return;
+ }
+ const float step_size = 1.0f/steps;
+
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, node);
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ if (SCULPT_automasking_factor_get(filter_cache->automasking, ss, vd.index) < 0.5f) {
+ continue;
+ }
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ float new_mask = orig_data.mask;
+ switch (filter_type) {
+ case IPMASK_FILTER_ADD_SUBSTRACT:
+ new_mask = orig_data.mask + data->filter_strength;
+ break;
+ case IPMASK_FILTER_INVERT: {
+ const float strength = clamp_f(data->filter_strength, 0.0f, 1.0f);
+ const float mask_invert = 1.0f - orig_data.mask;
+ new_mask = interpf(mask_invert, orig_data.mask, strength);
+ break;
+ }
+ case IPMASK_FILTER_QUANTIZE: {
+ const float remainder = fmod(orig_data.mask, step_size);
+ const float total_steps = (orig_data.mask - remainder) / step_size;
+ new_mask = total_steps * step_size;
+ break;
+ }
+ default:
+ BLI_assert(false);
+ break;
+ }
+ new_mask = clamp_f(new_mask, 0.0f, 1.0f);
+ if (*vd.mask == new_mask) {
+ continue;
+ }
+
+ *vd.mask = new_mask;
+ update = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (update) {
+ BKE_pbvh_node_mark_redraw(node);
+ }
+}
+
+static void sculpt_ipmask_apply_from_original_mask_data(Object *ob,
+ eSculptIPMaskFilterType filter_type,
+ const float strength)
+{
+ SculptSession *ss = ob->sculpt;
+ FilterCache *filter_cache = ss->filter_cache;
+ SculptThreadedTaskData data = {
+ .ob = ob,
+ .ss = ss,
+ .nodes = filter_cache->nodes,
+ .filter_strength = strength,
+ .filter_type = filter_type,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode);
+ BLI_task_parallel_range(
+ 0, filter_cache->totnode, &data, ipmask_filter_apply_from_original_task_cb, &settings);
+}
+
+static bool sculpt_ipmask_filter_uses_apply_from_original(
+ const eSculptIPMaskFilterType filter_type)
+{
+ return ELEM(filter_type, IPMASK_FILTER_INVERT, IPMASK_FILTER_ADD_SUBSTRACT, IPMASK_FILTER_QUANTIZE);
+}
+
+static void ipmask_filter_restore_original_mask_task_cb(
+ void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ss;
+ PBVHNode *node = data->nodes[i];
+ SculptOrigVertData orig_data;
+ bool update = false;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, node);
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ *vd.mask = orig_data.mask;
+ update = true;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (update) {
+ BKE_pbvh_node_mark_redraw(node);
+ }
+}
+
+static void sculpt_ipmask_restore_original_mask(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ FilterCache *filter_cache = ss->filter_cache;
+ SculptThreadedTaskData data = {
+ .ob = ob,
+ .ss = ss,
+ .nodes = filter_cache->nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode);
+ BLI_task_parallel_range(
+ 0, filter_cache->totnode, &data, ipmask_filter_restore_original_mask_task_cb, &settings);
+}
+
+static void sculpt_ipmask_filter_cancel(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+
+ sculpt_ipmask_restore_original_mask(ob);
+ SCULPT_undo_push_end();
+ SCULPT_filter_cache_free(ss);
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+}
+
+#define IPMASK_FILTER_STEP_SENSITIVITY 0.05f
+#define IPMASK_FILTER_STEPS_PER_FULL_STRENGTH 20
+static int sculpt_ipmask_filter_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ SculptSession *ss = ob->sculpt;
+ FilterCache *filter_cache = ss->filter_cache;
+ const int filter_type = RNA_enum_get(op->ptr, "filter_type");
+ const bool use_step_interpolation = RNA_boolean_get(op->ptr, "use_step_interpolation");
+ const int iteration_count = RNA_int_get(op->ptr, "iterations");
+
+ if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) ||
+ (event->type == RIGHTMOUSE && event->val == KM_PRESS)) {
+ sculpt_ipmask_filter_cancel(C, op);
+ return OPERATOR_FINISHED;
+ }
+
+ if (ELEM(event->type, LEFTMOUSE, EVT_RETKEY, EVT_PADENTER)) {
+ for (int i = 0; i < filter_cache->totnode; i++) {
+ BKE_pbvh_node_mark_update_mask(filter_cache->nodes[i]);
+ }
+ SCULPT_filter_cache_free(ss);
+ SCULPT_undo_push_end();
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ return OPERATOR_FINISHED;
+ }
+
+ if (event->type != MOUSEMOVE) {
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ const float len = event->x - event->prevclickx;
+ const float target_step_fl = len * IPMASK_FILTER_STEP_SENSITIVITY * UI_DPI_FAC;
+ const int target_step = floorf(target_step_fl);
+ const float step_interpolation = use_step_interpolation ? target_step_fl - target_step : 0.0f;
+ const float full_step_strength = target_step_fl / IPMASK_FILTER_STEPS_PER_FULL_STRENGTH;
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+
+ if (sculpt_ipmask_filter_uses_apply_from_original(filter_type)) {
+ sculpt_ipmask_apply_from_original_mask_data(ob, filter_type, full_step_strength);
+ }
+ else {
+ sculpt_ipmask_filter_update_to_target_step(
+ ss, target_step, iteration_count, step_interpolation);
+ }
+
+ SCULPT_tag_update_overlays(C);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void sculpt_ipmask_store_initial_undo_step(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ for (int i = 0; i < ss->filter_cache->totnode; i++) {
+ SCULPT_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK);
+ }
+}
+
+static FilterCache *sculpt_ipmask_filter_cache_init(Object *ob,
+ Sculpt *sd,
+ const eSculptIPMaskFilterType filter_type,
+ const bool init_automasking)
+{
+ SculptSession *ss = ob->sculpt;
+ FilterCache *filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache");
+
+ filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
+ if (init_automasking) {
+ filter_cache->automasking = SCULPT_automasking_cache_init(sd, NULL, ob);
+ }
+ filter_cache->mask_filter_current_step = 0;
+
+ BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &filter_cache->nodes, &filter_cache->totnode);
+
+ filter_cache->mask_delta_step = BLI_ghash_int_new("mask filter delta steps");
+ switch (filter_type) {
+ case IPMASK_FILTER_SMOOTH_SHARPEN:
+ filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_smooth_cb;
+ filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_sharpen_cb;
+ break;
+ case IPMASK_FILTER_GROW_SHRINK:
+ filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_grow_cb;
+ filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_shrink_cb;
+ break;
+ case IPMASK_FILTER_HARDER_SOFTER:
+ filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_harder_cb;
+ filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_softer_cb;
+ break;
+ case IPMASK_FILTER_CONTRAST:
+ filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_contrast_increase_cb;
+ filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_contrast_decrease_cb;
+ break;
+ }
+
+ return filter_cache;
+}
+
+static int sculpt_ipmask_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+
+ SCULPT_undo_push_begin(ob, "mask filter");
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+
+ const int filter_type = RNA_enum_get(op->ptr, "filter_type");
+ ss->filter_cache = sculpt_ipmask_filter_cache_init(ob, sd, filter_type, true);
+ sculpt_ipmask_store_initial_undo_step(ob);
+ sculpt_ipmask_store_reference_step(ss);
+
+ WM_event_add_modal_handler(C, op);
+ return OPERATOR_RUNNING_MODAL;
+}
+static int sculpt_ipmask_filter_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+
+ const int iteration_count = RNA_int_get(op->ptr, "iterations");
+ const float strength = RNA_float_get(op->ptr, "strength");
+ const int filter_type = RNA_enum_get(op->ptr, "filter_type");
+ const int direction = RNA_enum_get(op->ptr, "direction");
+
+ SCULPT_undo_push_begin(ob, "mask filter");
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+ ss->filter_cache = sculpt_ipmask_filter_cache_init(ob, sd, filter_type, false);
+ sculpt_ipmask_store_initial_undo_step(ob);
+ sculpt_ipmask_store_reference_step(ss);
+
+ const float target_step = direction == MASK_FILTER_STEP_DIRECTION_FORWARD ? 1 : -1;
+ if (sculpt_ipmask_filter_uses_apply_from_original(filter_type)) {
+ sculpt_ipmask_apply_from_original_mask_data(ob, filter_type, strength * target_step);
+ }
+ else {
+ sculpt_ipmask_filter_update_to_target_step(ss, target_step, iteration_count, 0.0f);
+ }
+
+ SCULPT_tag_update_overlays(C);
+ SCULPT_filter_cache_free(ss);
+ SCULPT_undo_push_end();
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
+ return OPERATOR_FINISHED;
+}
+
+void SCULPT_OT_ipmask_filter(struct wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Interactive Preview Mask Filter";
+ ot->idname = "SCULPT_OT_ipmask_filter";
+ ot->description = "Applies a filter to modify the current mask";
+
+ /* API callbacks. */
+ ot->exec = sculpt_ipmask_filter_exec;
+ ot->invoke = sculpt_ipmask_filter_invoke;
+ ot->modal = sculpt_ipmask_filter_modal;
+ ot->cancel = sculpt_ipmask_filter_cancel;
+ ot->poll = SCULPT_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+
+ static EnumPropertyItem prop_ipmask_filter_types[] = {
+ {IPMASK_FILTER_SMOOTH_SHARPEN,
+ "SMOOTH_SHARPEN",
+ 0,
+ "Smooth/Sharpen",
+ "Smooth and sharpen the mask"},
+ {IPMASK_FILTER_GROW_SHRINK, "GROW_SHRINK", 0, "Grow/Shrink", "Grow and shirnk the mask"},
+ {IPMASK_FILTER_HARDER_SOFTER,
+ "HARDER_SOFTER",
+ 0,
+ "Harder/Softer",
+ "Makes the entire mask harder or softer"},
+ {IPMASK_FILTER_ADD_SUBSTRACT,
+ "ADD_SUBSTRACT",
+ 0,
+ "Add/Substract",
+ "Adds or substract a value to the mask"},
+ {IPMASK_FILTER_CONTRAST,
+ "CONTRAST",
+ 0,
+ "Contrast",
+ "Increases or decreases the contrast of the mask"},
+ {IPMASK_FILTER_INVERT, "INVERT", 0, "Invert", "Inverts the mask"},
+ {IPMASK_FILTER_QUANTIZE, "QUANTIZE", 0, "Quantize", "Quantizes the mask to intervals"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ static EnumPropertyItem prop_ipmask_filter_direction_types[] = {
+ {MASK_FILTER_STEP_DIRECTION_FORWARD,
+ "FORWARD",
+ 0,
+ "Forward",
+ "Apply the filter in the forward direction"},
+ {MASK_FILTER_STEP_DIRECTION_BACKWARD,
+ "BACKWARD",
+ 0,
+ "Backward",
+ "Apply the filter in the backward direction"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ /* RNA. */
+ RNA_def_enum(ot->srna,
+ "filter_type",
+ prop_ipmask_filter_types,
+ IPMASK_FILTER_GROW_SHRINK,
+ "Type",
+ "Filter that is going to be applied to the mask");
+ RNA_def_enum(ot->srna,
+ "direction",
+ prop_ipmask_filter_direction_types,
+ MASK_FILTER_STEP_DIRECTION_FORWARD,
+ "Direction",
+ "Direction to apply the filter step");
+ RNA_def_int(ot->srna,
+ "iterations",
+ 1,
+ 1,
+ 100,
+ "Iterations per Step",
+ "Number of times that the filter is going to be applied per step",
+ 1,
+ 100);
+ RNA_def_boolean(
+ ot->srna,
+ "use_step_interpolation",
+ true,
+ "Step Interpolation",
+ "Calculate and render intermediate values between multiple full steps of the filter");
+ RNA_def_float(
+ ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter strength", -10.0f, 10.0f);
+}
+
+/******************************************************************************************/
+
static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd)
{
int total = 0;
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
index 3fc1a7674f7..455a1d3b033 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
@@ -174,6 +174,13 @@ void SCULPT_filter_cache_init(bContext *C, Object *ob, Sculpt *sd, const int und
copy_m4_m4(ss->filter_cache->viewmat_inv, vc.rv3d->viewinv);
}
+static void mask_filter_delta_step_free(void *delta_step_free)
+{
+ MaskFilterDeltaStep *delta_step = (MaskFilterDeltaStep *)delta_step_free;
+ MEM_SAFE_FREE(delta_step->delta);
+ MEM_SAFE_FREE(delta_step->index);
+ MEM_SAFE_FREE(delta_step);
+}
void SCULPT_filter_cache_free(SculptSession *ss)
{
if (ss->filter_cache->cloth_sim) {
@@ -182,6 +189,10 @@ void SCULPT_filter_cache_free(SculptSession *ss)
if (ss->filter_cache->automasking) {
SCULPT_automasking_cache_free(ss->filter_cache->automasking);
}
+ if (ss->filter_cache->mask_delta_step) {
+ BLI_ghash_free(ss->filter_cache->mask_delta_step, NULL, mask_filter_delta_step_free);
+ }
+ MEM_SAFE_FREE(ss->filter_cache->mask_filter_ref);
MEM_SAFE_FREE(ss->filter_cache->nodes);
MEM_SAFE_FREE(ss->filter_cache->mask_update_it);
MEM_SAFE_FREE(ss->filter_cache->prev_mask);
@@ -271,6 +282,25 @@ static EnumPropertyItem prop_mesh_filter_orientation_items[] = {
{0, NULL, 0, NULL, NULL},
};
+typedef enum eMeshFilterSphereCenterType {
+ MESH_FILTER_SPHERE_CENTER_AVERAGE = 1 << 0,
+ MESH_FILTER_SPHERE_CENTER_OBJECT = 1 << 1,
+} eMeshFilterSphereCenterType;
+
+static EnumPropertyItem prop_mesh_filter_sphere_center_items[] = {
+ {MESH_FILTER_SPHERE_CENTER_AVERAGE,
+ "AVERAGE",
+ 0,
+ "Center of Mass",
+ "Use the average position of all vertices as the sphere center"},
+ {MESH_FILTER_SPHERE_CENTER_OBJECT,
+ "OBJECT",
+ 0,
+ "Object Origin",
+ "Use the object origin as the sphere center"},
+ {0, NULL, 0, NULL, NULL},
+};
+
static bool sculpt_mesh_filter_needs_pmap(eSculptMeshFilterType filter_type)
{
return ELEM(filter_type,
@@ -289,6 +319,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
+ FilterCache *filter_cache = ss->filter_cache;
const eSculptMeshFilterType filter_type = data->filter_type;
@@ -350,13 +381,15 @@ static void mesh_filter_task_cb(void *__restrict userdata,
mul_m3_v3(transform, val);
sub_v3_v3v3(disp, val, orig_co);
break;
- case MESH_FILTER_SPHERE:
- normalize_v3_v3(disp, orig_co);
+ case MESH_FILTER_SPHERE: {
+ float sphere_space_co[3];
+ sub_v3_v3v3(sphere_space_co, orig_co, filter_cache->sphere_center);
+ normalize_v3_v3(disp, sphere_space_co);
if (fade > 0.0f) {
- mul_v3_v3fl(disp, disp, fade);
+ mul_v3_v3fl(disp, disp, filter_cache->sphere_radius * fade);
}
else {
- mul_v3_v3fl(disp, disp, -fade);
+ mul_v3_v3fl(disp, disp, filter_cache->sphere_radius * -fade);
}
unit_m3(transform);
@@ -366,12 +399,11 @@ static void mesh_filter_task_cb(void *__restrict userdata,
else {
scale_m3_fl(transform, 1.0f + fade);
}
- copy_v3_v3(val, orig_co);
+ copy_v3_v3(val, sphere_space_co);
mul_m3_v3(transform, val);
- sub_v3_v3v3(disp2, val, orig_co);
-
+ sub_v3_v3v3(disp2, val, sphere_space_co);
mid_v3_v3v3(disp, disp, disp2);
- break;
+ } break;
case MESH_FILTER_RANDOM: {
normal_short_to_float_v3(normal, orig_data.no);
/* Index is not unique for multires, so hash by vertex coordinates. */
@@ -492,6 +524,36 @@ static void mesh_filter_enhance_details_init_directions(SculptSession *ss)
}
}
+static void mesh_filter_sphere_center_calculate(
+ SculptSession *ss, const eMeshFilterSphereCenterType sphere_center_mode)
+{
+ FilterCache *filter_cache = ss->filter_cache;
+ switch (sphere_center_mode) {
+ case MESH_FILTER_SPHERE_CENTER_AVERAGE: {
+ const int totvert = SCULPT_vertex_count_get(ss);
+ float center_accum[3] = {0.0f};
+ for (int i = 0; i < totvert; i++) {
+ add_v3_v3(center_accum, SCULPT_vertex_co_get(ss, i));
+ }
+ mul_v3_v3fl(filter_cache->sphere_center, center_accum, 1.0f / totvert);
+ } break;
+ case MESH_FILTER_SPHERE_CENTER_OBJECT:
+ zero_v3(filter_cache->sphere_center);
+ break;
+ }
+}
+
+static void mesh_filter_sphere_radius_calculate(SculptSession *ss)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ FilterCache *filter_cache = ss->filter_cache;
+ float accum = 0.0f;
+ for (int i = 0; i < totvert; i++) {
+ accum += len_v3v3(filter_cache->sphere_center, SCULPT_vertex_co_get(ss, i));
+ }
+ filter_cache->sphere_radius = accum / totvert;
+}
+
static void mesh_filter_surface_smooth_init(SculptSession *ss,
const float shape_preservation,
const float current_vertex_displacement)
@@ -692,7 +754,7 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SculptCursorGeometryInfo sgi;
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
}
SCULPT_vertex_random_access_ensure(ss);
@@ -731,6 +793,13 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
mesh_filter_enhance_details_init_directions(ss);
break;
}
+ case MESH_FILTER_SPHERE: {
+ const eMeshFilterSphereCenterType sphere_center_mode = RNA_enum_get(op->ptr,
+ "sphere_center");
+ mesh_filter_sphere_center_calculate(ss, sphere_center_mode);
+ mesh_filter_sphere_radius_calculate(ss);
+ break;
+ }
case MESH_FILTER_ERASE_DISPLACEMENT: {
mesh_filter_init_limit_surface_co(ss);
break;
@@ -834,4 +903,11 @@ void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
"How much smooth the resulting shape is, ignoring high frequency details",
0,
10);
+
+ RNA_def_enum(ot->srna,
+ "sphere_center",
+ prop_mesh_filter_sphere_center_items,
+ MESH_FILTER_SPHERE_CENTER_AVERAGE,
+ "Sphere Center",
+ "Position of the center of the sphere created by the filter");
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_gradient.c b/source/blender/editors/sculpt_paint/sculpt_gradient.c
new file mode 100644
index 00000000000..3b8d839af35
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_gradient.c
@@ -0,0 +1,253 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_hash.h"
+#include "BLI_math.h"
+#include "BLI_math_color_blend.h"
+#include "BLI_task.h"
+
+#include "BKE_brush.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "IMB_colormanagement.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+#include "WM_message.h"
+#include "WM_toolsystem.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_interface.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+static EnumPropertyItem prop_sculpt_gradient_type[] = {
+ {SCULPT_GRADIENT_LINEAR, "LINEAR", 0, "Linear", ""},
+ {SCULPT_GRADIENT_SPHERICAL, "SPHERICAL", 0, "Spherical", ""},
+ {SCULPT_GRADIENT_RADIAL, "RADIAL", 0, "Radial", ""},
+ {SCULPT_GRADIENT_ANGLE, "ANGLE", 0, "Angle", ""},
+ {SCULPT_GRADIENT_REFLECTED, "REFLECTED", 0, "Reflected", ""},
+ {0, NULL, 0, NULL, NULL},
+};
+
+static void sculpt_gradient_apply_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ Sculpt *sd = data->sd;
+ SculptGradientContext *gcontext = ss->filter_cache->gradient_context;
+
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ float fade = vd.mask ? *vd.mask : 0.0f;
+ fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index);
+ if (fade == 0.0f) {
+ continue;
+ }
+
+ float world_co[3];
+ float projected_co[3];
+
+ /* TODO: Implement symmetry by flipping this coordinate. */
+ float symm_co[3];
+ copy_v3_v3(symm_co, vd.co);
+
+ mul_v3_m4v3(world_co, data->ob->obmat, symm_co);
+ /* TOOD: Implement this again. */
+ /* ED_view3d_project(gcontext->vc.region, world_co, projected_co); */
+
+ float gradient_value = 0.0f;
+ switch (gcontext->gradient_type) {
+ case SCULPT_GRADIENT_LINEAR:
+
+ break;
+ case SCULPT_GRADIENT_SPHERICAL:
+
+ break;
+ case SCULPT_GRADIENT_RADIAL: {
+ const float dist = len_v2v2(projected_co, gcontext->line_points[0]);
+ gradient_value = dist / gcontext->line_length;
+ } break;
+ case SCULPT_GRADIENT_ANGLE:
+ break;
+ case SCULPT_GRADIENT_REFLECTED:
+
+ break;
+ }
+
+ gradient_value = clamp_f(gradient_value, 0.0f, 1.0f);
+ gcontext->sculpt_gradient_apply_for_element(sd, ss, &orig_data, &vd, gradient_value, fade);
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ gcontext->sculpt_gradient_node_update(data->nodes[n]);
+}
+
+static int sculpt_gradient_update_exec(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ SculptGradientContext *gcontext = ss->filter_cache->gradient_context;
+
+ if (event->type != MOUSEMOVE) {
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ gcontext->line_points[0][0] = RNA_int_get(op->ptr, "xstart");
+ gcontext->line_points[0][1] = RNA_int_get(op->ptr, "ystart");
+ gcontext->line_points[1][0] = RNA_int_get(op->ptr, "xend");
+ gcontext->line_points[1][1] = RNA_int_get(op->ptr, "yend");
+ gcontext->line_length = len_v2v2(gcontext->line_points[0], gcontext->line_points[1]);
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = ss->filter_cache->nodes,
+ };
+
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+
+ BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode);
+ BLI_task_parallel_range(
+ 0, ss->filter_cache->totnode, &data, sculpt_gradient_apply_task_cb, &settings);
+
+ SCULPT_flush_update_step(C, ss->filter_cache->gradient_context->update_type);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void sculpt_gradient_properties(wmOperatorType *ot)
+{
+ RNA_def_enum(
+ ot->srna, "type", prop_sculpt_gradient_type, SCULPT_GRADIENT_LINEAR, "Gradient Type", "");
+}
+
+static void sculpt_gradient_context_init_common(bContext *C,
+ wmOperator *op,
+ const wmEvent *event,
+ SculptGradientContext *gcontext)
+{
+ /* View Context. */
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ ED_view3d_viewcontext_init(C, &gcontext->vc, depsgraph);
+
+ /* Properties */
+ gcontext->gradient_type = RNA_enum_get(op->ptr, "type");
+ gcontext->strength = RNA_float_get(op->ptr, "strength");
+
+ /* Symmetry. */
+ Object *ob = gcontext->vc.obact;
+ gcontext->symm = SCULPT_mesh_symmetry_xyz_get(ob);
+
+ /* Depth */
+ SculptCursorGeometryInfo sgi;
+ float mouse[2] = {event->mval[0], event->mval[1]};
+ const bool hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
+ if (hit) {
+ copy_v3_v3(gcontext->depth_point, sgi.location);
+ }
+ else {
+ zero_v3(gcontext->depth_point);
+ }
+}
+
+static SculptGradientContext *sculpt_mask_gradient_context_create(Object *ob, wmOperator *op)
+{
+ SculptGradientContext *gradient_context = MEM_callocN(sizeof(SculptGradientContext),
+ "gradient context");
+ gradient_context->update_type = SCULPT_UPDATE_MASK;
+ return gradient_context;
+}
+
+static int sculpt_mask_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ SCULPT_vertex_random_access_ensure(ss);
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false);
+ SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_MASK);
+ ss->filter_cache->gradient_context = sculpt_mask_gradient_context_create(ob, op);
+ sculpt_gradient_context_init_common(C, op, event, ss->filter_cache->gradient_context);
+
+ return WM_gesture_straightline_invoke(C, op, event);
+}
+
+void SCULPT_OT_mask_gradient(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Mask Gradient";
+ ot->idname = "SCULPT_OT_mask_gradient";
+ ot->description = "Creates or modifies the mask using a gradient";
+
+ /* api callbacks */
+ /*
+ ot->invoke = WM_gesture_straightline_invoke;
+ ot->modal = WM_gesture_straightline_modal;
+ ot->exec = sculpt_gradient_update_exec;
+
+ ot->poll = SCULPT_mode_poll;
+ */
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* rna */
+ sculpt_gradient_properties(ot);
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 696c3332a2b..e5aa3ffa7de 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -33,6 +33,8 @@
#include "BLI_gsqueue.h"
#include "BLI_threads.h"
+#include "ED_view3d.h"
+
#include "BKE_paint.h"
#include "BKE_pbvh.h"
@@ -72,6 +74,7 @@ void SCULPT_tag_update_overlays(bContext *C);
typedef struct SculptCursorGeometryInfo {
float location[3];
+ float back_location[3];
float normal[3];
float active_vertex_co[3];
} SculptCursorGeometryInfo;
@@ -80,7 +83,8 @@ bool SCULPT_stroke_get_location(struct bContext *C, float out[3], const float mo
bool SCULPT_cursor_geometry_info_update(bContext *C,
SculptCursorGeometryInfo *out,
const float mouse[2],
- bool use_sampled_normal);
+ bool use_sampled_normal,
+ bool use_back_depth);
void SCULPT_geometry_preview_lines_update(bContext *C, struct SculptSession *ss, float radius);
void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush);
@@ -184,6 +188,7 @@ void SCULPT_fake_neighbors_free(struct Object *ob);
/* Vertex Info. */
void SCULPT_boundary_info_ensure(Object *object);
+void SCULPT_connected_components_ensure(Object *ob);
/* Boundary Info needs to be initialized in order to use this function. */
bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index);
@@ -202,6 +207,7 @@ void SCULPT_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss);
int SCULPT_active_face_set_get(SculptSession *ss);
int SCULPT_vertex_face_set_get(SculptSession *ss, int index);
void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set);
+void SCULPT_vertex_face_set_increase(SculptSession *ss, int index, const int increase);
bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set);
bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index);
@@ -470,6 +476,11 @@ BLI_INLINE bool SCULPT_tool_needs_all_pbvh_nodes(const Brush *brush)
return true;
}
+ if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) {
+ /* Array Brush updates and modifies the entire mesh. */
+ return true;
+ }
+
if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
/* Boundary needs all nodes because it is not possible to know where the boundary
* deformation is going to be propagated before calculating it. */
@@ -529,6 +540,13 @@ void SCULPT_boundary_edges_preview_draw(const uint gpuattr,
const float outline_alpha);
void SCULPT_boundary_pivot_line_preview_draw(const uint gpuattr, struct SculptSession *ss);
+/* Array Brush. */
+void SCULPT_do_array_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+void SCULPT_array_datalayers_free(Object *ob);
+void SCULPT_array_path_draw(const uint gpuattr,
+ Brush *brush,
+ SculptSession *ss);
+
/* Multi-plane Scrape Brush. */
void SCULPT_do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
void SCULPT_multiplane_scrape_preview_draw(const uint gpuattr,
@@ -580,6 +598,12 @@ void SCULPT_surface_smooth_displace_step(SculptSession *ss,
const float fade);
void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+/* Directional Smooth Brush. */
+void SCULPT_do_directional_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+
+/* Uniform Weights Smooth Brush. */
+void SCULPT_do_uniform_weights_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+
/* Slide/Relax */
void SCULPT_relax_vertex(struct SculptSession *ss,
struct PBVHVertexIter *vd,
@@ -587,6 +611,9 @@ void SCULPT_relax_vertex(struct SculptSession *ss,
bool filter_boundary_face_sets,
float *r_final_pos);
+/* Symmetrize Map. */
+void SCULPT_do_symmetrize_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+
/* Undo */
typedef enum {
@@ -689,6 +716,7 @@ typedef struct SculptThreadedTaskData {
struct bContext *C;
struct Sculpt *sd;
struct Object *ob;
+ struct SculptSession *ss;
const struct Brush *brush;
struct PBVHNode **nodes;
int totnode;
@@ -723,6 +751,10 @@ typedef struct SculptThreadedTaskData {
float (*mat)[4];
float (*vertCos)[3];
+ /* When true, the displacement stored in the proxies will be aplied to the original coordinates
+ * instead of to the current coordinates. */
+ bool use_proxies_orco;
+
/* X and Z vectors aligned to the stroke direction for operations where perpendicular vectors to
* the stroke direction are needed. */
float (*stroke_xz)[3];
@@ -745,6 +777,9 @@ typedef struct SculptThreadedTaskData {
float *wet_mix_sampled_color;
float *prev_mask;
+ float *new_mask;
+ float *next_mask;
+ float mask_interpolation;
float *pose_factor;
float *pose_initial_co;
@@ -766,6 +801,9 @@ typedef struct SculptThreadedTaskData {
bool mask_expand_create_face_set;
float transform_mats[8][4][4];
+ float elastic_transform_mat[4][4];
+ float elastic_transform_pivot[3];
+ float elastic_transform_radius;
/* Boundary brush */
float boundary_deform_strength;
@@ -853,6 +891,8 @@ bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]);
bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v);
bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v);
+void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob);
+
SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss,
SculptBrushTest *test,
char falloff_shape);
@@ -950,7 +990,7 @@ typedef struct StrokeCache {
/* Multires Displacement Smear. */
float (*prev_displacement)[3];
float (*limit_surface_co)[3];
-
+
/* The rest is temporary storage that isn't saved as a property */
bool first_time; /* Beginning of stroke may do some things special */
@@ -971,6 +1011,9 @@ typedef struct StrokeCache {
bool is_rake_rotation_valid;
struct SculptRakeData rake_data;
+ /* Geodesic distances. */
+ float *geodesic_dists[PAINT_SYMM_AREAS];
+
/* Face Sets */
int paint_face_set;
@@ -981,6 +1024,9 @@ typedef struct StrokeCache {
float true_view_normal[3];
float view_normal[3];
+ float view_origin[3];
+ float true_view_origin[3];
+
/* sculpt_normal gets calculated by calc_sculpt_normal(), then the
* sculpt_normal_symm gets updated quickly with the usual symmetry
* transforms */
@@ -1001,6 +1047,11 @@ typedef struct StrokeCache {
bool original;
float anchored_location[3];
+ /* Fairing. */
+ bool *fairing_mask;
+ float *fairing_fade;
+ float (*prefairing_co)[3];
+
/* Paint Brush. */
struct {
float hardness;
@@ -1048,6 +1099,10 @@ typedef struct StrokeCache {
int saved_smooth_size; /* smooth tool copies the size of the current tool */
bool alt_smooth;
+ /* Scene Project Brush */
+ struct SnapObjectContext *snap_context;
+ struct Depsgraph *depsgraph;
+
float plane_trim_squared;
bool supports_gravity;
@@ -1097,6 +1152,7 @@ typedef enum eSculptExpandFalloffType {
SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY,
SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET,
SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET,
+ SCULPT_EXPAND_FALLOFF_POLY_LOOP,
} eSculptExpandFalloffType;
typedef enum eSculptExpandTargetType {
@@ -1236,6 +1292,55 @@ typedef struct ExpandCache {
float (*original_colors)[4];
} ExpandCache;
+typedef enum eSculptGradientType {
+ SCULPT_GRADIENT_LINEAR,
+ SCULPT_GRADIENT_SPHERICAL,
+ SCULPT_GRADIENT_RADIAL,
+ SCULPT_GRADIENT_ANGLE,
+ SCULPT_GRADIENT_REFLECTED,
+} eSculptGradientType;
+typedef struct SculptGradientContext {
+
+ eSculptGradientType gradient_type;
+ ViewContext vc;
+
+ int symm;
+
+ int update_type;
+ float line_points[2][2];
+
+ float line_length;
+
+ float depth_point[3];
+
+ float gradient_plane[4];
+ float initial_location[3];
+
+ float gradient_line[3];
+ float initial_projected_location[2];
+
+ float strength;
+ void (*sculpt_gradient_begin)(struct bContext *);
+
+ void (*sculpt_gradient_apply_for_element)(struct Sculpt *,
+ struct SculptSession *,
+ SculptOrigVertData *orig_data,
+ PBVHVertexIter *vd,
+ float gradient_value,
+ float fade_value);
+ void (*sculpt_gradient_node_update)(struct PBVHNode *);
+ void (*sculpt_gradient_end)(struct bContext *);
+} SculptGradientContext;
+
+/* IPMask filter vertex callback function. */
+typedef float(SculptIPMaskFilterStepVertexCB)(struct SculptSession *, int, float *);
+
+typedef struct MaskFilterDeltaStep {
+ int totelem;
+ int *index;
+ float *delta;
+} MaskFilterDeltaStep;
+
typedef struct FilterCache {
bool enabled_axis[3];
bool enabled_force_axis[3];
@@ -1257,6 +1362,10 @@ typedef struct FilterCache {
float *sharpen_factor;
float (*detail_directions)[3];
+ /* Sphere mesh filter. */
+ float sphere_center[3];
+ float sphere_radius;
+
/* Filter orientation. */
SculptFilterOrientation orientation;
float obmat[4][4];
@@ -1292,8 +1401,20 @@ typedef struct FilterCache {
/* Transform. */
SculptTransformDisplacementMode transform_displacement_mode;
+ /* Gradient. */
+ SculptGradientContext *gradient_context;
+
/* Auto-masking. */
AutomaskingCache *automasking;
+
+ /* Mask Filter. */
+ int mask_filter_current_step;
+ float *mask_filter_ref;
+ SculptIPMaskFilterStepVertexCB *mask_filter_step_forward;
+ SculptIPMaskFilterStepVertexCB *mask_filter_step_backward;
+
+ GHash *mask_delta_step;
+
} FilterCache;
void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache,
@@ -1318,8 +1439,15 @@ bool SCULPT_get_redraw_rect(struct ARegion *region,
Object *ob,
rcti *rect);
+/* Poly Loops. */
+int sculpt_poly_loop_initial_edge_from_cursor(Object *ob);
+BLI_bitmap *sculpt_poly_loop_from_cursor(struct Object *ob);
+
/* Operators. */
+/* Face Set by Topology. */
+void SCULPT_OT_face_set_by_topology(struct wmOperatorType *ot);
+
/* Expand. */
void SCULPT_OT_expand(struct wmOperatorType *ot);
void sculpt_expand_modal_keymap(struct wmKeyConfig *keyconf);
@@ -1332,6 +1460,8 @@ void SCULPT_OT_trim_lasso_gesture(struct wmOperatorType *ot);
void SCULPT_OT_trim_box_gesture(struct wmOperatorType *ot);
void SCULPT_OT_project_line_gesture(struct wmOperatorType *ot);
+void SCULPT_OT_project_lasso_gesture(struct wmOperatorType *ot);
+void SCULPT_OT_project_box_gesture(struct wmOperatorType *ot);
/* Face Sets. */
void SCULPT_OT_face_sets_randomize_colors(struct wmOperatorType *ot);
@@ -1356,6 +1486,8 @@ void SCULPT_OT_color_filter(struct wmOperatorType *ot);
void SCULPT_OT_mask_filter(struct wmOperatorType *ot);
void SCULPT_OT_dirty_mask(struct wmOperatorType *ot);
+void SCULPT_OT_ipmask_filter(struct wmOperatorType *ot);
+
/* Mask and Face Sets Expand. */
void SCULPT_OT_mask_expand(struct wmOperatorType *ot);
diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
index 9b06b2ee5d5..7f91c5dbc6e 100644
--- a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
+++ b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c
@@ -186,7 +186,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
float mouse[2];
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
- if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) {
+ if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) {
/* The cursor is over the mesh, get the update iteration from the updated active vertex. */
mask_expand_update_it = ss->filter_cache->mask_update_it[(int)SCULPT_active_vertex_get(ss)];
}
@@ -365,7 +365,7 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
op->customdata = MEM_mallocN(sizeof(float[2]), "initial mouse position");
copy_v2_v2(op->customdata, mouse);
- SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
diff --git a/source/blender/editors/sculpt_paint/sculpt_poly_loop.c b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c
new file mode 100644
index 00000000000..461834eff0e
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c
@@ -0,0 +1,309 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_hash.h"
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_customdata_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_fair.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph.h"
+
+#include "WM_api.h"
+#include "WM_message.h"
+#include "WM_toolsystem.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_view3d.h"
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+static void sculpt_poly_loop_topology_data_ensure(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+
+ if (!ss->epmap) {
+ BKE_mesh_edge_poly_map_create(&ss->epmap,
+ &ss->epmap_mem,
+ mesh->medge,
+ mesh->totedge,
+ mesh->mpoly,
+ mesh->totpoly,
+ mesh->mloop,
+ mesh->totloop);
+ }
+ if (!ss->vemap) {
+ BKE_mesh_vert_edge_map_create(
+ &ss->vemap, &ss->vemap_mem, mesh->medge, mesh->totvert, mesh->totedge);
+ }
+}
+
+#define SCULPT_FACE_SET_LOOP_STEP_NONE -1
+static bool sculpt_poly_loop_step(SculptSession *ss,
+ const int from_poly,
+ const int edge,
+ int *r_next_poly)
+{
+ if (!ss->epmap) {
+ return false;
+ }
+
+ int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE;
+ for (int i = 0; i < ss->epmap[edge].count; i++) {
+ if (ss->epmap[edge].indices[i] != from_poly) {
+ next_poly = ss->epmap[edge].indices[i];
+ }
+ }
+
+ if (next_poly == SCULPT_FACE_SET_LOOP_STEP_NONE) {
+ return false;
+ }
+
+ *r_next_poly = next_poly;
+ return true;
+}
+
+static int sculpt_poly_loop_opposite_edge_in_quad(SculptSession *ss,
+ const int poly,
+ const int edge)
+{
+ if (ss->mpoly[poly].totloop != 4) {
+ return edge;
+ }
+
+ int edge_index_in_poly = 0;
+ for (int i = 0; i < ss->mpoly[poly].totloop; i++) {
+ if (edge == ss->mloop[ss->mpoly[poly].loopstart + i].e) {
+ edge_index_in_poly = i;
+ break;
+ }
+ }
+
+ const int next_edge_index_in_poly = (edge_index_in_poly + 2) % 4;
+ return ss->mloop[ss->mpoly[poly].loopstart + next_edge_index_in_poly].e;
+}
+
+int sculpt_poly_loop_initial_edge_from_cursor(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+
+ sculpt_poly_loop_topology_data_ensure(ob);
+
+ float *location = ss->cursor_location;
+
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+ MPoly *initial_poly = &mesh->mpoly[ss->active_face_index];
+
+ if (initial_poly->totloop != 4) {
+ return 0;
+ }
+
+ int closest_vert = mesh->mloop[initial_poly->loopstart].v;
+ for (int i = 0; i < initial_poly->totloop; i++) {
+ if (len_squared_v3v3(mvert[ss->mloop[initial_poly->loopstart + i].v].co, location) <
+ len_squared_v3v3(mvert[closest_vert].co, location)) {
+ closest_vert = ss->mloop[initial_poly->loopstart + i].v;
+ }
+ }
+
+ int initial_edge = ss->vemap[closest_vert].indices[0];
+ int closest_vert_on_initial_edge = mesh->medge[initial_edge].v1 == closest_vert ?
+ mesh->medge[initial_edge].v2 :
+ mesh->medge[initial_edge].v1;
+ for (int i = 0; i < ss->vemap[closest_vert].count; i++) {
+ const int edge_index = ss->vemap[closest_vert].indices[i];
+ const int other_vert = mesh->medge[edge_index].v1 == closest_vert ?
+ mesh->medge[edge_index].v2 :
+ mesh->medge[edge_index].v1;
+ if (dist_to_line_segment_v3(location, mvert[closest_vert].co, mvert[other_vert].co) <
+ dist_to_line_segment_v3(
+ location, mvert[closest_vert].co, mvert[closest_vert_on_initial_edge].co)) {
+ initial_edge = edge_index;
+ closest_vert_on_initial_edge = other_vert;
+ }
+ }
+ return initial_edge;
+}
+
+static void sculpt_poly_loop_iterate_and_fill(SculptSession *ss,
+ const int initial_poly,
+ const int initial_edge,
+ BLI_bitmap *poly_loop)
+{
+ int current_poly = initial_poly;
+ int current_edge = initial_edge;
+ int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE;
+ int max_steps = ss->totfaces;
+
+ BLI_BITMAP_ENABLE(poly_loop, initial_poly);
+
+ while (max_steps && sculpt_poly_loop_step(ss, current_poly, current_edge, &next_poly)) {
+ if (ss->face_sets[next_poly] == initial_poly) {
+ break;
+ }
+ if (ss->face_sets[next_poly] < 0) {
+ break;
+ }
+ if (ss->mpoly[next_poly].totloop != 4) {
+ break;
+ }
+
+ BLI_BITMAP_ENABLE(poly_loop, next_poly);
+ current_edge = sculpt_poly_loop_opposite_edge_in_quad(ss, next_poly, current_edge);
+ current_poly = next_poly;
+ max_steps--;
+ }
+}
+
+
+static void sculpt_poly_loop_symm_poly_find(Object *ob, const int poly_index, const int edge_index, const char symm_it, int *r_poly_index, int *r_edge_index) {
+ if (symm_it == 0) {
+ *r_poly_index = poly_index;
+ *r_edge_index = edge_index;
+ return;
+ }
+
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+
+ MPoly *original_poly = &mesh->mpoly[poly_index];
+ float original_poly_center[3];
+ BKE_mesh_calc_poly_center(original_poly, &mesh->mloop[original_poly->loopstart], mvert, original_poly_center);
+
+ float symm_poly_center[3];
+ flip_v3_v3(symm_poly_center, original_poly_center, symm_it);
+
+ float min_poly_dist = FLT_MAX;
+ int search_poly_index = poly_index;
+
+
+ for (int i = 0; i < mesh->totpoly; i++) {
+ MPoly *poly = &mesh->mpoly[i];
+ float poly_center[3];
+ BKE_mesh_calc_poly_center(poly, &mesh->mloop[poly->loopstart], mvert, poly_center);
+ const float dist_to_poly_squared = len_squared_v3v3(symm_poly_center, poly_center);
+ if (dist_to_poly_squared < min_poly_dist) {
+ min_poly_dist = dist_to_poly_squared;
+ search_poly_index = i;
+ }
+ }
+
+ *r_poly_index = search_poly_index;
+ MPoly *search_poly = &mesh->mpoly[search_poly_index];
+
+
+ float original_edge_center[3];
+ MEdge *original_edge = &mesh->medge[edge_index];
+ mid_v3_v3v3(original_edge_center, mvert[original_edge->v1].co, mvert[original_edge->v2].co);
+
+ float symm_edge_center[3];
+ flip_v3_v3(symm_edge_center, original_edge_center, symm_it);
+
+ float min_edge_dist = FLT_MAX;
+ int search_edge_index = edge_index;
+
+ for (int i = 0; i < search_poly->totloop; i++) {
+ MLoop *loop = &mesh->mloop[search_poly->loopstart + i];
+ MEdge *edge = &mesh->medge[loop->e];
+ float edge_center[3];
+ mid_v3_v3v3(edge_center, mvert[edge->v1].co, mvert[edge->v2].co);
+ const float dist_to_edge_squared = len_squared_v3v3(symm_edge_center, edge_center);
+ if (dist_to_edge_squared < min_edge_dist) {
+ min_edge_dist = dist_to_edge_squared;
+ search_edge_index = loop->e;
+ }
+
+ *r_edge_index = search_edge_index;
+ }
+
+
+
+}
+
+BLI_bitmap *sculpt_poly_loop_from_cursor(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+ BLI_bitmap *poly_loop = BLI_BITMAP_NEW(mesh->totpoly, "poly loop");
+
+ sculpt_poly_loop_topology_data_ensure(ob);
+ const int initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob);
+ const int initial_poly = ss->active_face_index;
+
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+ for (char symm_it = 0; symm_it <= symm; symm_it++) {
+ if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
+ continue;
+ }
+
+ int initial_poly_symm;
+ int initial_edge_symm;
+ sculpt_poly_loop_symm_poly_find(ob, initial_poly, initial_edge, symm_it, &initial_poly_symm, &initial_edge_symm);
+
+ const int initial_edge_opposite = sculpt_poly_loop_opposite_edge_in_quad(
+ ss, initial_poly_symm, initial_edge_symm);
+
+ sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_symm, poly_loop);
+ sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_opposite, poly_loop);
+
+ }
+
+ return poly_loop;
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.c b/source/blender/editors/sculpt_paint/sculpt_pose.c
index 587ce346428..3b2193c5beb 100644
--- a/source/blender/editors/sculpt_paint/sculpt_pose.c
+++ b/source/blender/editors/sculpt_paint/sculpt_pose.c
@@ -157,6 +157,75 @@ static void pose_solve_scale_chain(SculptPoseIKChain *ik_chain, const float scal
}
}
+static void do_pose_brush_bend_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+ SculptPoseIKChainSegment *segments = ik_chain->segments;
+ const Brush *brush = data->brush;
+
+ if (fabsf(ik_chain->bend_factor) <= 0.00001f) {
+ return;
+ }
+
+ float final_pos[3];
+
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+
+ const float ik_chain_weight = segments[0].weights[vd.index] *
+ (1.0f - SCULPT_vertex_mask_get(ss, vd.index));
+ if (ik_chain_weight == 0.0f) {
+ continue;
+ }
+
+ float orig_co[3];
+ mul_v3_m4v3(orig_co, ik_chain->bend_mat_inv, orig_data.co);
+
+ const float bend_factor = ik_chain->bend_factor;
+
+ if (fabsf(bend_factor) <= 0.0000001f) {
+ continue;
+ }
+
+ if (orig_co[0] < 0.0f) {
+ continue;
+ }
+
+ const float theta = orig_co[0] * bend_factor;
+ const float sint = sinf(theta);
+ const float cost = cosf(theta);
+
+ float new_co[3];
+ new_co[0] = -(orig_co[1] - 1.0f / bend_factor) * sint;
+ new_co[1] = (orig_co[1] - 1.0f / bend_factor) * cost + 1.0f / bend_factor;
+ new_co[2] = orig_co[2];
+
+ float final_co[3];
+ float disp[3];
+ mul_v3_m4v3(final_co, ik_chain->bend_mat, new_co);
+
+ sub_v3_v3v3(disp, final_co, orig_data.co);
+ mul_v3_fl(disp, ik_chain_weight);
+ add_v3_v3v3(final_pos, orig_data.co, disp);
+
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ copy_v3_v3(target_co, final_pos);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
static void do_pose_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
@@ -665,6 +734,7 @@ static int pose_brush_num_effective_segments(const Brush *brush)
* artifacts in the areas affected by multiple segments. */
if (ELEM(brush->pose_deform_type,
BRUSH_POSE_DEFORM_SCALE_TRASLATE,
+ BRUSH_POSE_DEFORM_BEND,
BRUSH_POSE_DEFORM_SQUASH_STRETCH)) {
return 1;
}
@@ -1109,6 +1179,59 @@ static void sculpt_pose_do_squash_stretch_deform(SculptSession *ss, Brush *UNUSE
pose_solve_scale_chain(ik_chain, scale);
}
+static void sculpt_pose_do_bend_deform(SculptSession *ss, Brush *UNUSED(brush))
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
+
+ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
+ sub_v3_v3v3(ik_chain->bend_mat[0],
+ ik_chain->segments[0].initial_head,
+ ik_chain->segments[0].initial_orig);
+ normalize_v3(ik_chain->bend_mat[0]);
+ copy_v3_v3(ik_chain->bend_mat[2], ss->cache->view_normal);
+ normalize_v3(ik_chain->bend_mat[2]);
+ cross_v3_v3v3(ik_chain->bend_mat[1], ik_chain->bend_mat[0], ik_chain->bend_mat[2]);
+ normalize_v3(ik_chain->bend_mat[1]);
+ copy_v3_v3(ik_chain->bend_mat[3], ik_chain->segments[0].initial_orig);
+ ik_chain->bend_mat[3][3] = 1.0f;
+ invert_m4_m4(ik_chain->bend_mat_inv, ik_chain->bend_mat);
+
+ float lower = FLT_MAX;
+ float upper = -FLT_MAX;
+
+ float smd_limit[2];
+
+ for (int i = 0; i < totvert; i++) {
+ if (ik_chain->segments[0].weights[i] == 0.0f) {
+ continue;
+ }
+ float bend_space_vert_co[3];
+ mul_v3_m4v3(bend_space_vert_co, ik_chain->bend_mat_inv, SCULPT_vertex_co_get(ss, i));
+ lower = min_ff(lower, bend_space_vert_co[0]);
+ upper = max_ff(upper, bend_space_vert_co[0]);
+ }
+
+ ik_chain->bend_upper_limit = upper;
+ smd_limit[1] = lower + (upper - lower) * 1.0f;
+ smd_limit[0] = lower + (upper - lower) * 0.0f;
+ ik_chain->bend_limit = max_ff(FLT_EPSILON, smd_limit[1] - smd_limit[0]);
+ }
+
+ float *original_dir = ik_chain->bend_mat[0];
+ float current_dir[3];
+ float brush_location[3];
+ add_v3_v3v3(brush_location, ss->cache->initial_location, ss->cache->grab_delta);
+ sub_v3_v3v3(current_dir, brush_location, ik_chain->segments[0].initial_orig);
+ ik_chain->bend_factor = angle_signed_on_axis_v3v3_v3(
+ original_dir, current_dir, ss->cache->view_normal);
+ if (ik_chain->bend_factor > M_PI) {
+ ik_chain->bend_factor = ik_chain->bend_factor - (M_PI * 2.0f);
+ }
+
+ ik_chain->bend_factor = 2.0f * (ik_chain->bend_factor / ik_chain->bend_limit);
+}
+
static void sculpt_pose_align_pivot_local_space(float r_mat[4][4],
ePaintSymmetryFlags symm,
ePaintSymmetryAreas symm_area,
@@ -1157,6 +1280,9 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
case BRUSH_POSE_DEFORM_SQUASH_STRETCH:
sculpt_pose_do_squash_stretch_deform(ss, brush);
break;
+ case BRUSH_POSE_DEFORM_BEND:
+ sculpt_pose_do_bend_deform(ss, brush);
+ break;
}
/* Flip the segment chain in all symmetry axis and calculate the transform matrices for each
@@ -1227,7 +1353,13 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
- BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings);
+
+ if (brush->pose_deform_type == BRUSH_POSE_DEFORM_BEND) {
+ BLI_task_parallel_range(0, totnode, &data, do_pose_brush_bend_task_cb_ex, &settings);
+ }
+ else {
+ BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings);
+ }
}
void SCULPT_pose_ik_chain_free(SculptPoseIKChain *ik_chain)
diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c
index 38165b7622f..4006359e0e4 100644
--- a/source/blender/editors/sculpt_paint/sculpt_smooth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c
@@ -560,3 +560,192 @@ void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in
0, totnode, &data, SCULPT_do_surface_smooth_brush_displace_task_cb_ex, &settings);
}
}
+
+static void SCULPT_do_directional_smooth_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);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+ const float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ float stroke_disp[3];
+ sub_v3_v3v3(stroke_disp, ss->cache->location, ss->cache->last_location);
+ normalize_v3(stroke_disp);
+
+ float avg[3] = {0.0f, 0.0f, 0.0f};
+ int neighbor_count = 0;
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
+ float vertex_neighbor_disp[3];
+ const float *neighbor_co = SCULPT_vertex_co_get(ss, ni.index);
+ sub_v3_v3v3(vertex_neighbor_disp, neighbor_co, vd.co);
+ normalize_v3(vertex_neighbor_disp);
+ if (fabsf(dot_v3v3(stroke_disp, vertex_neighbor_disp)) > 0.6f) {
+ neighbor_count++;
+ add_v3_v3(avg, neighbor_co);
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ /* Avoid division by 0 when there are no neighbors. */
+ if (neighbor_count == 0) {
+ continue;
+ }
+
+ float smooth_co[3];
+ mul_v3_v3fl(smooth_co, avg, 1.0f / neighbor_count);
+
+ float final_disp[3];
+ sub_v3_v3v3(final_disp, smooth_co, vd.co);
+ madd_v3_v3v3fl(final_disp, vd.co, final_disp, fade);
+ SCULPT_clip(data->sd, ss, vd.co, final_disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+void SCULPT_do_directional_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ /* Threaded loop over nodes. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ for (int i = 0; i < brush->surface_smooth_iterations; i++) {
+ BLI_task_parallel_range(0, totnode, &data, SCULPT_do_directional_smooth_task_cb_ex, &settings);
+ }
+}
+
+static void SCULPT_do_uniform_weigths_smooth_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);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+ const float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+
+
+
+ float len_accum = 0;
+ int tot_neighbors = 0;
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
+ len_accum += len_v3v3(SCULPT_vertex_co_get(ss, vd.index), SCULPT_vertex_co_get(ss, ni.index));
+ tot_neighbors++;
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ /* Avoid division by 0 when there are no neighbors. */
+ if (tot_neighbors == 0) {
+ continue;
+ }
+
+ const float len_avg = bstrength * len_accum / tot_neighbors;
+
+
+ float co_accum[3] = {0.0f};
+
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
+ const float neighbor_co[3];
+ const float neighbor_disp[3];
+ sub_v3_v3v3(neighbor_disp, SCULPT_vertex_co_get(ss, ni.index), SCULPT_vertex_co_get(ss, vd.index));
+ normalize_v3(neighbor_disp);
+ mul_v3_fl(neighbor_disp, len_avg);
+ add_v3_v3v3(neighbor_co, SCULPT_vertex_co_get(ss, vd.index), neighbor_disp);
+ add_v3_v3(co_accum, neighbor_co);
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ float smooth_co[3];
+ mul_v3_v3fl(smooth_co, co_accum, 1.0f / tot_neighbors);
+
+ float final_disp[3];
+ sub_v3_v3v3(final_disp, smooth_co, vd.co);
+ madd_v3_v3v3fl(final_disp, vd.co, final_disp, fade);
+ SCULPT_clip(data->sd, ss, vd.co, final_disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+
+
+void SCULPT_do_uniform_weights_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ /* Threaded loop over nodes. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ for (int i = 0; i < brush->surface_smooth_iterations; i++) {
+ BLI_task_parallel_range(0, totnode, &data, SCULPT_do_uniform_weigths_smooth_task_cb_ex, &settings);
+ }
+} \ No newline at end of file
diff --git a/source/blender/editors/sculpt_paint/sculpt_symmetrize.c b/source/blender/editors/sculpt_paint/sculpt_symmetrize.c
new file mode 100644
index 00000000000..ea0178e0edb
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_symmetrize.c
@@ -0,0 +1,302 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2021 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_mesh.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+typedef uint MirrTopoHash_t;
+
+typedef struct MirrTopoVert_t {
+ MirrTopoHash_t hash;
+ int v_index;
+} MirrTopoVert_t;
+
+static int mirrtopo_hash_sort(const void *l1, const void *l2)
+{
+ if ((MirrTopoHash_t)(intptr_t)l1 > (MirrTopoHash_t)(intptr_t)l2) {
+ return 1;
+ }
+ if ((MirrTopoHash_t)(intptr_t)l1 < (MirrTopoHash_t)(intptr_t)l2) {
+ return -1;
+ }
+ return 0;
+}
+
+static int mirrtopo_vert_sort(const void *v1, const void *v2)
+{
+ if (((MirrTopoVert_t *)v1)->hash > ((MirrTopoVert_t *)v2)->hash) {
+ return 1;
+ }
+ if (((MirrTopoVert_t *)v1)->hash < ((MirrTopoVert_t *)v2)->hash) {
+ return -1;
+ }
+ return 0;
+}
+
+void SCULPT_symmetrize_map_ensure(Object *ob)
+{
+ SculptSession *ss = ob->sculpt;
+ Mesh *me = BKE_object_get_original_mesh(ob);
+
+
+ if (ss->vertex_info.symmetrize_map) {
+ /* Nothing to do. */
+ return;
+ }
+
+ MEdge *medge = NULL, *med;
+
+ int a, last;
+ int totvert, totedge;
+ int tot_unique = -1, tot_unique_prev = -1;
+ int tot_unique_edges = 0, tot_unique_edges_prev;
+
+ MirrTopoHash_t *topo_hash = NULL;
+ MirrTopoHash_t *topo_hash_prev = NULL;
+ MirrTopoVert_t *topo_pairs;
+ MirrTopoHash_t topo_pass = 1;
+
+ int *index_lookup; /* direct access to mesh_topo_store->index_lookup */
+
+ totvert = me->totvert;
+ topo_hash = MEM_callocN(totvert * sizeof(MirrTopoHash_t), "TopoMirr");
+
+ /* Initialize the vert-edge-user counts used to detect unique topology */
+ totedge = me->totedge;
+ medge = me->medge;
+
+ for (a = 0, med = medge; a < totedge; a++, med++) {
+ const uint i1 = med->v1, i2 = med->v2;
+ topo_hash[i1]++;
+ topo_hash[i2]++;
+ }
+
+ topo_hash_prev = MEM_dupallocN(topo_hash);
+
+ tot_unique_prev = -1;
+ tot_unique_edges_prev = -1;
+ while (true) {
+ /* use the number of edges per vert to give verts unique topology IDs */
+
+ tot_unique_edges = 0;
+
+ /* This can make really big numbers, wrapping around here is fine */
+ for (a = 0, med = medge; a < totedge; a++, med++) {
+ const uint i1 = med->v1, i2 = med->v2;
+ topo_hash[i1] += topo_hash_prev[i2] * topo_pass;
+ topo_hash[i2] += topo_hash_prev[i1] * topo_pass;
+ tot_unique_edges += (topo_hash[i1] != topo_hash[i2]);
+ }
+ memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
+
+ /* sort so we can count unique values */
+ qsort(topo_hash_prev, totvert, sizeof(MirrTopoHash_t), mirrtopo_hash_sort);
+
+ tot_unique = 1; /* account for skipping the first value */
+ for (a = 1; a < totvert; a++) {
+ if (topo_hash_prev[a - 1] != topo_hash_prev[a]) {
+ tot_unique++;
+ }
+ }
+
+ if ((tot_unique <= tot_unique_prev) && (tot_unique_edges <= tot_unique_edges_prev)) {
+ /* Finish searching for unique values when 1 loop doesn't give a
+ * higher number of unique values compared to the previous loop. */
+ break;
+ }
+ tot_unique_prev = tot_unique;
+ tot_unique_edges_prev = tot_unique_edges;
+ /* Copy the hash calculated this iteration, so we can use them next time */
+ memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert);
+
+ topo_pass++;
+ }
+
+ /* Hash/Index pairs are needed for sorting to find index pairs */
+ topo_pairs = MEM_callocN(sizeof(MirrTopoVert_t) * totvert, "MirrTopoPairs");
+
+ /* since we are looping through verts, initialize these values here too */
+ index_lookup = MEM_mallocN(totvert * sizeof(int), "mesh_topo_lookup");
+
+ for (a = 0; a < totvert; a++) {
+ topo_pairs[a].hash = topo_hash[a];
+ topo_pairs[a].v_index = a;
+
+ /* initialize lookup */
+ index_lookup[a] = -1;
+ }
+
+ qsort(topo_pairs, totvert, sizeof(MirrTopoVert_t), mirrtopo_vert_sort);
+
+ last = 0;
+
+ /* Get the pairs out of the sorted hashes, note, totvert+1 means we can use the previous 2,
+ * but you cant ever access the last 'a' index of MirrTopoPairs */
+ for (a = 1; a <= totvert; a++) {
+ if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) {
+ const int match_count = a - last;
+ if (match_count == 2) {
+ const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index;
+ index_lookup[j] = k;
+ index_lookup[k] = j;
+ }
+ else if (match_count == 1) {
+ /* Center vertex. */
+ const int j = topo_pairs[a - 1].v_index;
+ index_lookup[j] = j;
+ }
+ last = a;
+ }
+ }
+
+ MEM_freeN(topo_pairs);
+ topo_pairs = NULL;
+
+ MEM_freeN(topo_hash);
+ MEM_freeN(topo_hash_prev);
+
+ ss->vertex_info.symmetrize_map = index_lookup;
+}
+
+
+
+static void do_shape_symmetrize_brush_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, brush->falloff_shape);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
+
+ if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
+ continue;
+ }
+
+ const int symmetrical_index = ss->vertex_info.symmetrize_map[vd.index];
+
+ if (symmetrical_index == -1) {
+ continue;
+ }
+
+ float symm_co[3];
+ copy_v3_v3(symm_co, SCULPT_vertex_co_get(ss, symmetrical_index));
+
+ symm_co[0] *= -1;
+ float new_co[3];
+ copy_v3_v3(new_co, symm_co);
+
+ const float fade = SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+
+
+ float disp[3];
+ sub_v3_v3v3(disp, new_co, vd.co);
+ madd_v3_v3v3fl(vd.co, vd.co, disp, fade);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+/* Public functions. */
+
+/* Main Brush Function. */
+void SCULPT_do_symmetrize_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return;
+ }
+
+ if (!SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
+ return;
+ }
+
+ SCULPT_symmetrize_map_ensure(ob);
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_shape_symmetrize_brush_task_cb, &settings);
+} \ No newline at end of file
diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.c b/source/blender/editors/sculpt_paint/sculpt_transform.c
index 3c0a591e8a7..efe3b8acafe 100644
--- a/source/blender/editors/sculpt_paint/sculpt_transform.c
+++ b/source/blender/editors/sculpt_paint/sculpt_transform.c
@@ -32,6 +32,7 @@
#include "BKE_brush.h"
#include "BKE_context.h"
+#include "BKE_kelvinlet.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_object.h"
@@ -73,16 +74,40 @@ void ED_sculpt_init_transform(struct bContext *C, Object *ob)
copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos);
copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot);
copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale);
-
- SCULPT_undo_push_begin(ob, "Transform");
- BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
-
ss->pivot_rot[3] = 1.0f;
+ SCULPT_undo_push_begin(ob, "Transform");
SCULPT_vertex_random_access_ensure(ss);
SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
- ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL;
+ switch (sd->transform_deform_target) {
+ case SCULPT_TRANSFORM_DEFORM_TARGET_GEOMETRY:
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
+ break;
+ case SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM:
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
+ ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create(
+ ss, 1.0f, 1.0f, 0.0f, true, false);
+ SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim);
+ SCULPT_cloth_brush_store_simulation_state(ss, ss->filter_cache->cloth_sim);
+ SCULPT_cloth_brush_ensure_nodes_constraints(sd,
+ ob,
+ ss->filter_cache->nodes,
+ ss->filter_cache->totnode,
+ ss->filter_cache->cloth_sim,
+ ss->pivot_pos,
+ FLT_MAX);
+
+ break;
+ }
+
+ if (sd->transform_mode == SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC ||
+ sd->transform_deform_target == SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM) {
+ ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL;
+ }
+ else {
+ ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL;
+ }
}
static void sculpt_transform_matrices_init(SculptSession *ss,
@@ -122,13 +147,13 @@ static void sculpt_transform_matrices_init(SculptSession *ss,
/* Translation matrix. */
sub_v3_v3v3(d_t, ss->pivot_pos, start_pivot_pos);
- SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, ss->init_pivot_pos);
+ SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, start_pivot_pos);
translate_m4(t_mat, d_t[0], d_t[1], d_t[2]);
/* Rotation matrix. */
sub_qt_qtqt(d_r, ss->pivot_rot, start_pivot_rot);
normalize_qt(d_r);
- SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, ss->init_pivot_pos);
+ SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, start_pivot_pos);
quat_to_mat4(r_mat, d_r);
/* Scale matrix. */
@@ -185,7 +210,15 @@ static void sculpt_transform_task_cb(void *__restrict userdata,
mul_m4_v3(data->transform_mats[(int)symm_area], transformed_co);
sub_v3_v3v3(disp, transformed_co, start_co);
mul_v3_fl(disp, 1.0f - fade);
- add_v3_v3v3(vd.co, start_co, disp);
+
+ switch (data->sd->transform_deform_target) {
+ case SCULPT_TRANSFORM_DEFORM_TARGET_GEOMETRY:
+ add_v3_v3v3(vd.co, start_co, disp);
+ break;
+ case SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM:
+ add_v3_v3v3(ss->filter_cache->cloth_sim->pos[vd.index], start_co, disp);
+ break;
+ }
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
@@ -218,6 +251,92 @@ static void sculpt_transform_all_vertices(Sculpt *sd, Object *ob)
0, ss->filter_cache->totnode, &data, sculpt_transform_task_cb, &settings);
}
+static void sculpt_elastic_transform_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHNode *node = data->nodes[i];
+
+ float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[i])->co;
+
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]);
+
+ KelvinletParams params;
+ /* TODO(pablodp606): These parameters can be exposed if needed as transform strength and volume
+ * preservation like in the elastic deform brushes. Setting them to the same default as elastic
+ * deform triscale grab because they work well in most cases. */
+ const float force = 1.0f;
+ const float shear_modulus = 1.0f;
+ const float poisson_ratio = 0.4f;
+ BKE_kelvinlet_init_params(
+ &params, data->elastic_transform_radius, force, shear_modulus, poisson_ratio);
+
+ SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS);
+
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ float transformed_co[3], orig_co[3], disp[3];
+ const float fade = vd.mask ? *vd.mask : 0.0f;
+ copy_v3_v3(orig_co, orig_data.co);
+
+ copy_v3_v3(transformed_co, vd.co);
+ mul_m4_v3(data->elastic_transform_mat, transformed_co);
+ sub_v3_v3v3(disp, transformed_co, vd.co);
+
+ float final_disp[3];
+ BKE_kelvinlet_grab_triscale(final_disp, &params, vd.co, data->elastic_transform_pivot, disp);
+ mul_v3_fl(final_disp, 20.0f * (1.0f - fade));
+
+ copy_v3_v3(proxy[vd.i], final_disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ BKE_pbvh_node_mark_update(node);
+}
+
+static void sculpt_transform_radius_elastic(Sculpt *sd, Object *ob, const float transform_radius)
+{
+ SculptSession *ss = ob->sculpt;
+ BLI_assert(ss->filter_cache->transform_displacement_mode ==
+ SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL);
+ const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = ss->filter_cache->nodes,
+ .elastic_transform_radius = transform_radius,
+ };
+
+ sculpt_transform_matrices_init(
+ ss, symm, ss->filter_cache->transform_displacement_mode, data.transform_mats);
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode);
+
+ /* Elastic transform needs to apply all transform matrices to all vertices and then combine the
+ * displacement proxies as all vertices are modified by all symmetry passes. */
+ for (ePaintSymmetryFlags symmpass = 0; symmpass <= symm; symmpass++) {
+ if (SCULPT_is_symmetry_iteration_valid(symmpass, symm)) {
+ flip_v3_v3(data.elastic_transform_pivot, ss->pivot_pos, symmpass);
+ const int symm_area = SCULPT_get_vertex_symm_area(data.elastic_transform_pivot);
+ copy_m4_m4(data.elastic_transform_mat, data.transform_mats[symm_area]);
+ BLI_task_parallel_range(
+ 0, ss->filter_cache->totnode, &data, sculpt_elastic_transform_task_cb, &settings);
+ }
+ }
+ SCULPT_combine_transform_proxies(sd, ob);
+}
+
void ED_sculpt_update_modal_transform(struct bContext *C, Object *ob)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
@@ -227,12 +346,31 @@ void ED_sculpt_update_modal_transform(struct bContext *C, Object *ob)
SCULPT_vertex_random_access_ensure(ss);
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
- sculpt_transform_all_vertices(sd, ob);
+ switch (sd->transform_mode) {
+ case SCULPT_TRANSFORM_MODE_ALL_VERTICES: {
+ sculpt_transform_all_vertices(sd, ob);
+ break;
+ }
+ case SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC: {
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ Scene *scene = CTX_data_scene(C);
+ const float transform_radius = BKE_brush_unprojected_radius_get(scene, brush);
+ sculpt_transform_radius_elastic(sd, ob, transform_radius);
+ break;
+ }
+ }
copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos);
copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot);
copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale);
+ if (sd->transform_deform_target == SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM) {
+ SCULPT_cloth_sim_activate_nodes(
+ ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
+ SCULPT_cloth_brush_do_simulation_step(
+ sd, ob, ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
+ }
+
if (ss->deform_modifiers_active || ss->shapekey_active) {
SCULPT_flush_stroke_deform(sd, ob, true);
}