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:
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py4
-rw-r--r--source/blender/blenkernel/BKE_paint.h73
-rw-r--r--source/blender/blenkernel/intern/brush.c7
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c8
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c48
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c3
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c29
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_boundary.c862
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h22
-rw-r--r--source/blender/makesdna/DNA_brush_types.h14
-rw-r--r--source/blender/makesrna/intern/rna_brush.c15
12 files changed, 1082 insertions, 4 deletions
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index 12f02acedc2..f1004358418 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -719,6 +719,10 @@ def brush_settings(layout, context, brush, popover=False):
col = layout.column()
col.prop(brush, "smear_deform_type")
+ elif sculpt_tool == 'BOUNDARY':
+ col = layout.column()
+ col.prop(brush, "boundary_deform_type")
+
elif sculpt_tool == 'TOPOLOGY':
col = layout.column()
col.prop(brush, "slide_deform_type")
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 4e520948bcb..58687858a9e 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -314,6 +314,76 @@ typedef struct SculptVertexInfo {
BLI_bitmap *boundary;
} SculptVertexInfo;
+typedef struct SculptBoundaryEditInfo {
+ /* Vertex index from where the topology propagation reached this vertex. */
+ int original_vertex;
+
+ /* How many steps were needed to reach this vertex from the boundary. */
+ int num_propagation_steps;
+
+ /* Stregth that is used to deform this vertex. */
+ float strength_factor;
+} SculptBoundaryEditInfo;
+
+/* Edge for drawing the boundary preview in the cursor. */
+typedef struct SculptBoundaryPreviewEdge {
+ int v1;
+ int v2;
+} SculptBoundaryPreviewEdge;
+
+typedef struct SculptBoundary {
+ /* Vertex indices of the active boundary. */
+ int *vertices;
+ int vertices_capacity;
+ int num_vertices;
+
+ /* Data for drawing the preview. */
+ SculptBoundaryPreviewEdge *edges;
+ int edges_capacity;
+ int num_edges;
+
+ /* True if the boundary loops into itself. */
+ bool forms_loop;
+
+ /* Initial vertex in the boundary which is closest to the current sculpt active vertex. */
+ int initial_vertex;
+
+ /* Vertex that at max_propagation_steps from the boundary and closest to the original active
+ * vertex that was used to initialize the boundary. This is used as a reference to check how much
+ * the deformation will go into the mesh and to calculate the strength of the brushes. */
+ int pivot_vertex;
+
+ /* Stores the initial positions of the pivot and boundary initial vertex as they may be deformed
+ * during the brush action. This allows to use them as a reference positions and vectors for some
+ * brush effects. */
+ float initial_vertex_position[3];
+ float initial_pivot_position[3];
+
+ /* Maximum number of topology steps that were calculated from the boundary. */
+ int max_propagation_steps;
+
+ /* Indexed by vertex index, contains the topology information needed for boundary deformations.
+ */
+ struct SculptBoundaryEditInfo *edit_info;
+
+ /* Bend Deform type. */
+ struct {
+ float (*pivot_rotation_axis)[3];
+ float (*pivot_positions)[3];
+ } bend;
+
+ /* Slide Deform type. */
+ struct {
+ float (*directions)[3];
+ } slide;
+
+ /* Twist Deform type. */
+ struct {
+ float rotation_axis[3];
+ float pivot_position[3];
+ } twist;
+} SculptBoundary;
+
typedef struct SculptFakeNeighbors {
bool use_fake_neighbors;
@@ -415,6 +485,9 @@ typedef struct SculptSession {
float pose_origin[3];
SculptPoseIKChain *pose_ik_chain_preview;
+ /* Boundary Brush Preview */
+ SculptBoundary *boundary_preview;
+
/* Mesh State Persistence */
/* This is freed with the PBVH, so it is always in sync with the mesh. */
SculptPersistentBase *persistent_base;
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index ce0249c71ce..af186dc4940 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -1566,6 +1566,12 @@ void BKE_brush_sculpt_reset(Brush *br)
br->flag &= ~BRUSH_SPACE;
br->flag &= ~BRUSH_SPACE_ATTEN;
break;
+ case SCULPT_TOOL_BOUNDARY:
+ br->flag &= ~BRUSH_ALPHA_PRESSURE;
+ br->flag &= ~BRUSH_SPACE;
+ br->flag &= ~BRUSH_SPACE_ATTEN;
+ br->curve_preset = BRUSH_CURVE_CONSTANT;
+ break;
case SCULPT_TOOL_DRAW_FACE_SETS:
br->alpha = 0.5f;
br->flag &= ~BRUSH_ALPHA_PRESSURE;
@@ -1660,6 +1666,7 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_ROTATE:
case SCULPT_TOOL_ELASTIC_DEFORM:
case SCULPT_TOOL_POSE:
+ case SCULPT_TOOL_BOUNDARY:
case SCULPT_TOOL_SLIDE_RELAX:
br->add_col[0] = 1.0f;
br->add_col[1] = 0.95f;
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index ae6d3a58091..631961e342d 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -698,6 +698,14 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
brush->sculpt_tool = SCULPT_TOOL_SMEAR;
}
+ brush_name = "Boundary";
+ brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
+ if (!brush) {
+ brush = BKE_brush_add(bmain, brush_name, OB_MODE_SCULPT);
+ id_us_min(&brush->id);
+ brush->sculpt_tool = SCULPT_TOOL_BOUNDARY;
+ }
+
brush_name = "Simplify";
brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
if (!brush) {
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index ed87d524627..51cfb912722 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SRC
paint_vertex_weight_utils.c
sculpt.c
sculpt_automasking.c
+ sculpt_boundary.c
sculpt_cloth.c
sculpt_detail.c
sculpt_dyntopo.c
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index b8af33fb7cc..a4101bdd89f 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1508,6 +1508,43 @@ static void paint_cursor_pose_brush_origins_draw(PaintCursorContext *pcontext)
}
}
+static void paint_cursor_preview_boundary_data_pivot_draw(PaintCursorContext *pcontext)
+{
+
+ if (!pcontext->ss->boundary_preview) {
+ /* There is no guarantee that a boundary preview exists as there may be no boundaries
+ * inside the brush radius. */
+ return;
+ }
+ immUniformColor4f(1.0f, 1.0f, 1.0f, 0.8f);
+ cursor_draw_point_screen_space(
+ pcontext->pos,
+ pcontext->region,
+ SCULPT_vertex_co_get(pcontext->ss, pcontext->ss->boundary_preview->pivot_vertex),
+ pcontext->vc.obact->obmat,
+ 3);
+}
+
+static void paint_cursor_preview_boundary_data_update(PaintCursorContext *pcontext,
+ const bool update_previews)
+{
+ SculptSession *ss = pcontext->ss;
+ if (!(update_previews || !ss->boundary_preview)) {
+ return;
+ }
+
+ /* Needed for updating the necessary SculptSession data in order to initialize the
+ * boundary data for the preview. */
+ BKE_sculpt_update_object_for_edit(pcontext->depsgraph, pcontext->vc.obact, true, false, false);
+
+ if (ss->boundary_preview) {
+ SCULPT_boundary_data_free(ss->boundary_preview);
+ }
+
+ ss->boundary_preview = SCULPT_boundary_data_init(
+ pcontext->vc.obact, ss->active_vertex_index, pcontext->radius);
+}
+
static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *pcontext)
{
Brush *brush = pcontext->brush;
@@ -1577,6 +1614,11 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
paint_cursor_pose_brush_origins_draw(pcontext);
}
+ if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
+ paint_cursor_preview_boundary_data_update(pcontext, update_previews);
+ paint_cursor_preview_boundary_data_pivot_draw(pcontext);
+ }
+
/* Setup 3D perspective drawing. */
GPU_matrix_push_projection();
ED_view3d_draw_setup_view(pcontext->wm,
@@ -1603,6 +1645,12 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *
paint_cursor_pose_brush_segments_draw(pcontext);
}
+ if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
+ SCULPT_boundary_edges_preview_draw(
+ pcontext->pos, pcontext->ss, pcontext->outline_col, pcontext->outline_alpha);
+ SCULPT_boundary_pivot_line_preview_draw(pcontext->pos, pcontext->ss);
+ }
+
GPU_matrix_pop();
/* Drawing Cursor overlays in Paint Cursor space (as additional info on top of the brush cursor)
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index 38a09cd97bd..90b0f017bd6 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -225,6 +225,7 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode)
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_THUMB)) {
@@ -265,6 +266,7 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_CLOTH,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE)) {
return false;
}
@@ -1008,6 +1010,7 @@ static bool sculpt_is_grab_tool(Brush *br)
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_SNAKE_HOOK);
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index d0b834a3dc0..c87189997f2 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1126,6 +1126,7 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
SCULPT_TOOL_DRAW_SHARP,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_SMOOTH,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE);
}
@@ -1135,6 +1136,7 @@ static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
SCULPT_TOOL_SMOOTH,
SCULPT_TOOL_LAYER,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_PAINT,
SCULPT_TOOL_SMEAR,
@@ -2338,6 +2340,7 @@ static float brush_strength(const Sculpt *sd,
case SCULPT_TOOL_ELASTIC_DEFORM:
case SCULPT_TOOL_POSE:
+ case SCULPT_TOOL_BOUNDARY:
return root_alpha * feather;
default:
@@ -5510,7 +5513,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
/* Pose needs all nodes because it applies all symmetry iterations at the same time and the IK
* chain can grow to any area of the model. */
/* This can be optimized by filtering the nodes after calculating the chain. */
- if (ELEM(brush->sculpt_tool, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE)) {
+ if (ELEM(brush->sculpt_tool,
+ SCULPT_TOOL_ELASTIC_DEFORM,
+ SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY)) {
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
}
else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
@@ -5690,6 +5696,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_SLIDE_RELAX:
do_slide_relax_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_BOUNDARY:
+ SCULPT_do_boundary_brush(sd, ob, nodes, totnode);
+ break;
case SCULPT_TOOL_CLOTH:
SCULPT_do_cloth_brush(sd, ob, nodes, totnode);
break;
@@ -5724,8 +5733,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
}
/* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
- if (ss->cache->supports_gravity &&
- !ELEM(brush->sculpt_tool, SCULPT_TOOL_CLOTH, SCULPT_TOOL_DRAW_FACE_SETS)) {
+ if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool,
+ SCULPT_TOOL_CLOTH,
+ SCULPT_TOOL_DRAW_FACE_SETS,
+ SCULPT_TOOL_BOUNDARY)) {
do_gravity(sd, ob, nodes, totnode, sd->gravity_factor);
}
@@ -5777,6 +5788,7 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ELASTIC_DEFORM,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE);
PBVHVertexIter vd;
@@ -6238,6 +6250,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Multi-plane Scrape Brush";
case SCULPT_TOOL_SLIDE_RELAX:
return "Slide/Relax Brush";
+ case SCULPT_TOOL_BOUNDARY:
+ return "Boundary Brush";
case SCULPT_TOOL_CLOTH:
return "Cloth Brush";
case SCULPT_TOOL_DRAW_FACE_SETS:
@@ -6266,6 +6280,12 @@ void SCULPT_cache_free(StrokeCache *cache)
SCULPT_pose_ik_chain_free(cache->pose_ik_chain);
}
+ for (int i = 0; i < PAINT_SYMM_AREAS; i++) {
+ if (cache->bdata[i]) {
+ SCULPT_boundary_data_free(cache->bdata[i]);
+ }
+ }
+
if (cache->cloth_sim) {
SCULPT_cloth_simulation_free(cache->cloth_sim);
}
@@ -6494,6 +6514,7 @@ static bool sculpt_needs_delta_from_anchored_origin(Brush *brush)
return ELEM(brush->sculpt_tool,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ELASTIC_DEFORM) ||
SCULPT_is_cloth_deform_brush(brush);
@@ -6537,6 +6558,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
SCULPT_TOOL_CLAY_THUMB,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_THUMB) ||
sculpt_brush_use_topology_rake(ss, brush)) {
float grab_location[3], imat[4][4], delta[3], loc[3];
@@ -6825,6 +6847,7 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd,
(brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) ||
((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_SLIDE_RELAX) ||
(brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) ||
(brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS));
diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c
new file mode 100644
index 00000000000..c6ed0701704
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c
@@ -0,0 +1,862 @@
+/*
+ * 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_edgehash.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>
+
+#define BOUNDARY_VERTEX_NONE -1
+#define BOUNDARY_STEPS_NONE -1
+
+typedef struct BoundaryInitialVertexFloodFillData {
+ int initial_vertex;
+ int boundary_initial_vertex_steps;
+ int boundary_initial_vertex;
+ int *floodfill_steps;
+ float radius_sq;
+} BoundaryInitialVertexFloodFillData;
+
+static bool boundary_initial_vertex_floodfill_cb(
+ SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
+{
+ BoundaryInitialVertexFloodFillData *data = userdata;
+
+ if (!is_duplicate) {
+ data->floodfill_steps[to_v] = data->floodfill_steps[from_v] + 1;
+ }
+ else {
+ data->floodfill_steps[to_v] = data->floodfill_steps[from_v];
+ }
+
+ if (SCULPT_vertex_is_boundary(ss, to_v)) {
+ if (data->floodfill_steps[to_v] < data->boundary_initial_vertex_steps) {
+ data->boundary_initial_vertex_steps = data->floodfill_steps[to_v];
+ data->boundary_initial_vertex = to_v;
+ }
+ }
+
+ const float len_sq = len_squared_v3v3(SCULPT_vertex_co_get(ss, data->initial_vertex),
+ SCULPT_vertex_co_get(ss, to_v));
+ return len_sq < data->radius_sq;
+}
+
+/* From a vertex index anywhere in the mesh, returns the closest vertex in a mesh boundary inside
+ * the given radius, if it exists. */
+static int sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss,
+ const int initial_vertex,
+ const float radius)
+{
+
+ if (SCULPT_vertex_is_boundary(ss, initial_vertex)) {
+ return initial_vertex;
+ }
+
+ SculptFloodFill flood;
+ SCULPT_floodfill_init(ss, &flood);
+ SCULPT_floodfill_add_initial(&flood, initial_vertex);
+
+ BoundaryInitialVertexFloodFillData fdata = {
+ .initial_vertex = initial_vertex,
+ .boundary_initial_vertex = BOUNDARY_VERTEX_NONE,
+ .boundary_initial_vertex_steps = INT_MAX,
+ .radius_sq = radius * radius,
+ };
+
+ fdata.floodfill_steps = MEM_calloc_arrayN(
+ SCULPT_vertex_count_get(ss), sizeof(int), "floodfill steps");
+
+ SCULPT_floodfill_execute(ss, &flood, boundary_initial_vertex_floodfill_cb, &fdata);
+ SCULPT_floodfill_free(&flood);
+
+ MEM_freeN(fdata.floodfill_steps);
+ return fdata.boundary_initial_vertex;
+}
+
+/* Used to allocate the memory of the boundary index arrays. This was decided considered the most
+ * common use cases for the brush deformers, taking into account how many vertices those
+ * deformations usually need in the boundary. */
+static int BOUNDARY_INDICES_BLOCK_SIZE = 300;
+
+static void sculpt_boundary_index_add(SculptBoundary *bdata,
+ const int new_index,
+ GSet *included_vertices)
+{
+
+ bdata->vertices[bdata->num_vertices] = new_index;
+ if (included_vertices) {
+ BLI_gset_add(included_vertices, POINTER_FROM_INT(new_index));
+ }
+ bdata->num_vertices++;
+ if (bdata->num_vertices >= bdata->vertices_capacity) {
+ bdata->vertices_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
+ bdata->vertices = MEM_reallocN_id(
+ bdata->vertices, bdata->vertices_capacity * sizeof(int), "boundary indices");
+ }
+};
+
+static void sculpt_boundary_preview_edge_add(SculptBoundary *bdata, const int v1, const int v2)
+{
+
+ bdata->edges[bdata->num_edges].v1 = v1;
+ bdata->edges[bdata->num_edges].v2 = v2;
+ bdata->num_edges++;
+
+ if (bdata->num_edges >= bdata->edges_capacity) {
+ bdata->edges_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
+ bdata->edges = MEM_reallocN_id(
+ bdata->edges, bdata->edges_capacity * sizeof(SculptBoundaryPreviewEdge), "boundary edges");
+ }
+};
+
+/* This funcion is used to check where the propagation should stop when calculating the boundary,
+ * as well as to check if the initial vertex is valid. */
+static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss,
+ const int initial_vertex)
+{
+
+ int neighbor_count = 0;
+ int boundary_vertex_count = 0;
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, initial_vertex, ni) {
+ neighbor_count++;
+ if (SCULPT_vertex_is_boundary(ss, ni.index)) {
+ boundary_vertex_count++;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+
+ /* Corners are ambiguous as it can't be decied which boundary should be active. The flood fill
+ * should also stop at corners. */
+ if (neighbor_count <= 2) {
+ return false;
+ }
+
+ /* Non manifold geomery in the mesh boundary. The deformation result will be unpredictable and
+ * not very useful. */
+ if (boundary_vertex_count > 2) {
+ return false;
+ }
+
+ return true;
+}
+
+/* Flood fill that adds to the boundary data all the vertices from a boundary and its duplicates.
+ */
+
+typedef struct BoundaryFloodFillData {
+ SculptBoundary *bdata;
+ GSet *included_vertices;
+ EdgeSet *preview_edges;
+
+ int last_visited_vertex;
+
+} BoundaryFloodFillData;
+
+static bool boundary_floodfill_cb(
+ SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
+{
+ BoundaryFloodFillData *data = userdata;
+ SculptBoundary *bdata = data->bdata;
+ if (SCULPT_vertex_is_boundary(ss, to_v)) {
+ sculpt_boundary_index_add(bdata, to_v, data->included_vertices);
+ if (!is_duplicate) {
+ sculpt_boundary_preview_edge_add(bdata, from_v, to_v);
+ }
+ return sculpt_boundary_is_vertex_in_editable_boundary(ss, to_v);
+ }
+ return false;
+}
+
+static void sculpt_boundary_indices_init(SculptSession *ss,
+ SculptBoundary *bdata,
+ const int initial_boundary_index)
+{
+
+ bdata->vertices = MEM_malloc_arrayN(
+ BOUNDARY_INDICES_BLOCK_SIZE, sizeof(int), "boundary indices");
+ bdata->edges = MEM_malloc_arrayN(
+ BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge), "boundary edges");
+
+ GSet *included_vertices = BLI_gset_int_new_ex("included vertices", BOUNDARY_INDICES_BLOCK_SIZE);
+ SculptFloodFill flood;
+ SCULPT_floodfill_init(ss, &flood);
+
+ bdata->initial_vertex = initial_boundary_index;
+ copy_v3_v3(bdata->initial_vertex_position, SCULPT_vertex_co_get(ss, bdata->initial_vertex));
+ sculpt_boundary_index_add(bdata, initial_boundary_index, included_vertices);
+ SCULPT_floodfill_add_initial(&flood, initial_boundary_index);
+
+ BoundaryFloodFillData fdata = {
+ .bdata = bdata,
+ .included_vertices = included_vertices,
+ .last_visited_vertex = BOUNDARY_VERTEX_NONE,
+
+ };
+
+ SCULPT_floodfill_execute(ss, &flood, boundary_floodfill_cb, &fdata);
+ SCULPT_floodfill_free(&flood);
+
+ /* Check if the boundary loops into itself and add the extra preview edge to close the loop. */
+ if (fdata.last_visited_vertex != BOUNDARY_VERTEX_NONE &&
+ sculpt_boundary_is_vertex_in_editable_boundary(ss, fdata.last_visited_vertex)) {
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, fdata.last_visited_vertex, ni) {
+ if (BLI_gset_haskey(included_vertices, POINTER_FROM_INT(ni.index)) &&
+ sculpt_boundary_is_vertex_in_editable_boundary(ss, ni.index)) {
+ sculpt_boundary_preview_edge_add(bdata, fdata.last_visited_vertex, ni.index);
+ bdata->forms_loop = true;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ }
+
+ BLI_gset_free(included_vertices, NULL);
+}
+
+/* This functions initializes all data needed to calcualte falloffs and deformation from the
+ * boundary into the mesh into a SculptBoundaryEditInfo array. This includes how many steps are
+ * needed to go from a boundary vertex to an interior vertex and which vertex of the boundary is
+ * the closest one. */
+
+static void sculpt_boundary_edit_data_init(SculptSession *ss,
+ SculptBoundary *bdata,
+ const int initial_vertex,
+ const float radius)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+
+ const bool has_duplicates = BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS;
+
+ bdata->edit_info = MEM_malloc_arrayN(
+ totvert, sizeof(SculptBoundaryEditInfo), "Boundary edit info");
+
+ for (int i = 0; i < totvert; i++) {
+ bdata->edit_info[i].original_vertex = BOUNDARY_VERTEX_NONE;
+ bdata->edit_info[i].num_propagation_steps = BOUNDARY_STEPS_NONE;
+ }
+
+ GSQueue *current_iteration = BLI_gsqueue_new(sizeof(int));
+ GSQueue *next_iteration = BLI_gsqueue_new(sizeof(int));
+
+ /* Initialized the first iteration with the vertices already in the boundary. This is propagation
+ * step 0. */
+ BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices");
+ for (int i = 0; i < bdata->num_vertices; i++) {
+ bdata->edit_info[bdata->vertices[i]].original_vertex = bdata->vertices[i];
+ bdata->edit_info[bdata->vertices[i]].num_propagation_steps = 0;
+
+ /* This ensures that all duplicate vertices in the boundary have the same original_vertex
+ * index, so the deformation for them will be the same. */
+ if (has_duplicates) {
+ SculptVertexNeighborIter ni_duplis;
+ SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, bdata->vertices[i], ni_duplis) {
+ if (ni_duplis.is_duplicate) {
+ bdata->edit_info[ni_duplis.index].original_vertex = bdata->vertices[i];
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis);
+ }
+
+ BLI_gsqueue_push(current_iteration, &bdata->vertices[i]);
+ }
+
+ int num_propagation_steps = 0;
+ float accum_distance = 0.0f;
+
+ while (true) {
+ /* This steps is further away from the boundary than the brush radius, so stop adding more
+ * steps. */
+ if (accum_distance > radius) {
+ bdata->max_propagation_steps = num_propagation_steps;
+ break;
+ }
+
+ while (!BLI_gsqueue_is_empty(current_iteration)) {
+ int from_v;
+ BLI_gsqueue_pop(current_iteration, &from_v);
+
+ SculptVertexNeighborIter ni;
+ SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) {
+ if (bdata->edit_info[ni.index].num_propagation_steps == BOUNDARY_STEPS_NONE) {
+ bdata->edit_info[ni.index].original_vertex = bdata->edit_info[from_v].original_vertex;
+
+ BLI_BITMAP_ENABLE(visited_vertices, ni.index);
+
+ if (ni.is_duplicate) {
+ /* Grids duplicates handling. */
+ bdata->edit_info[ni.index].num_propagation_steps =
+ bdata->edit_info[from_v].num_propagation_steps;
+ }
+ else {
+ bdata->edit_info[ni.index].num_propagation_steps =
+ bdata->edit_info[from_v].num_propagation_steps + 1;
+
+ BLI_gsqueue_push(next_iteration, &ni.index);
+
+ /* When copying the data to the neighbor for the next iteration, it has to be copied to
+ * all its duplicates too. This is because it is not possible to know if the updated
+ * neighbor or one if its uninitialized duplicates is going to come first in order to
+ * copy the data in the from_v neighbor iterator. */
+ if (has_duplicates) {
+ SculptVertexNeighborIter ni_duplis;
+ SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, ni.index, ni_duplis) {
+ if (ni_duplis.is_duplicate) {
+ bdata->edit_info[ni_duplis.index].original_vertex =
+ bdata->edit_info[from_v].original_vertex;
+ bdata->edit_info[ni_duplis.index].num_propagation_steps =
+ bdata->edit_info[from_v].num_propagation_steps + 1;
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis);
+ }
+
+ /* Check the distance using the vertex that was propagated from the initial vertex that
+ * was used to initialize the boundary. */
+ if (bdata->edit_info[from_v].original_vertex == initial_vertex) {
+ bdata->pivot_vertex = ni.index;
+ copy_v3_v3(bdata->initial_pivot_position, SCULPT_vertex_co_get(ss, ni.index));
+ accum_distance += len_v3v3(SCULPT_vertex_co_get(ss, from_v),
+ SCULPT_vertex_co_get(ss, ni.index));
+ }
+ }
+ }
+ }
+ SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
+ }
+
+ /* Copy the new vertices to the queue to be processed in the next iteration. */
+ while (!BLI_gsqueue_is_empty(next_iteration)) {
+ int next_v;
+ BLI_gsqueue_pop(next_iteration, &next_v);
+ BLI_gsqueue_push(current_iteration, &next_v);
+ }
+
+ /* Stop if no vertices were added in this iteration. At this point, all the mesh should have
+ * been initialized with the edit data. */
+ if (BLI_gsqueue_is_empty(current_iteration)) {
+ break;
+ }
+
+ num_propagation_steps++;
+ }
+
+ MEM_SAFE_FREE(visited_vertices);
+
+ BLI_gsqueue_free(current_iteration);
+ BLI_gsqueue_free(next_iteration);
+}
+
+/* This functions assings a falloff factor to each one of the SculptBoundaryEditInfo structs based
+ * on the brush curve and its propagation steps. The falloff goes from the boundary into the mesh.
+ */
+static void sculpt_boundary_falloff_factor_init(SculptSession *ss,
+ SculptBoundary *bdata,
+ Brush *brush)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ BKE_curvemapping_init(brush->curve);
+
+ for (int i = 0; i < totvert; i++) {
+ if (bdata->edit_info[i].num_propagation_steps != -1) {
+ bdata->edit_info[i].strength_factor = BKE_brush_curve_strength(
+ brush, bdata->edit_info[i].num_propagation_steps, bdata->max_propagation_steps);
+ }
+ }
+}
+
+/* Main function to get SculptBoundary data both for brush deformation and viewport preview. Can
+ * return NULL if there is no boundary from the given vertex using the given radius. */
+SculptBoundary *SCULPT_boundary_data_init(Object *object,
+ const int initial_vertex,
+ const float radius)
+{
+ SculptSession *ss = object->sculpt;
+
+ SCULPT_vertex_random_access_init(ss);
+ SCULPT_boundary_info_ensure(object);
+
+ const int boundary_initial_vertex = sculpt_boundary_get_closest_boundary_vertex(
+ ss, initial_vertex, radius);
+
+ if (boundary_initial_vertex == BOUNDARY_VERTEX_NONE) {
+ return NULL;
+ }
+
+ /* Starting from a vertex that is the limit of a boundary is ambiguous, so return NULL instead of
+ * forcing a random active boundary from a corner. */
+ if (!sculpt_boundary_is_vertex_in_editable_boundary(ss, initial_vertex)) {
+ return NULL;
+ }
+
+ SculptBoundary *bdata = MEM_callocN(sizeof(SculptBoundary), "Boundary edit data");
+
+ sculpt_boundary_indices_init(ss, bdata, boundary_initial_vertex);
+ sculpt_boundary_edit_data_init(ss, bdata, boundary_initial_vertex, radius);
+
+ return bdata;
+}
+
+void SCULPT_boundary_data_free(SculptBoundary *bdata)
+{
+ MEM_SAFE_FREE(bdata->vertices);
+ MEM_SAFE_FREE(bdata->edit_info);
+ MEM_SAFE_FREE(bdata->bend.pivot_positions);
+ MEM_SAFE_FREE(bdata->bend.pivot_rotation_axis);
+ MEM_SAFE_FREE(bdata->slide.directions);
+ MEM_SAFE_FREE(bdata);
+}
+
+/* These functions initialize the required vectors for the desired deformation using the
+ * SculptBoundaryEditInfo. They calculate the data using the vertices that have the
+ * max_propagation_steps value and them this data is copied to the rest of the vertices using the
+ * original vertex index. */
+static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *bdata)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ bdata->bend.pivot_rotation_axis = MEM_calloc_arrayN(
+ totvert, 3 * sizeof(float), "pivot rotation axis");
+ bdata->bend.pivot_positions = MEM_calloc_arrayN(totvert, 3 * sizeof(float), "pivot positions");
+
+ for (int i = 0; i < totvert; i++) {
+ if (bdata->edit_info[i].num_propagation_steps == bdata->max_propagation_steps) {
+ float dir[3];
+ float normal[3];
+ SCULPT_vertex_normal_get(ss, i, normal);
+ sub_v3_v3v3(dir,
+ SCULPT_vertex_co_get(ss, bdata->edit_info[i].original_vertex),
+ SCULPT_vertex_co_get(ss, i));
+ cross_v3_v3v3(
+ bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex], dir, normal);
+ normalize_v3(bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex]);
+ copy_v3_v3(bdata->bend.pivot_positions[bdata->edit_info[i].original_vertex],
+ SCULPT_vertex_co_get(ss, i));
+ }
+ }
+
+ for (int i = 0; i < totvert; i++) {
+ if (bdata->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
+ copy_v3_v3(bdata->bend.pivot_positions[i],
+ bdata->bend.pivot_positions[bdata->edit_info[i].original_vertex]);
+ copy_v3_v3(bdata->bend.pivot_rotation_axis[i],
+ bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex]);
+ }
+ }
+}
+
+static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *bdata)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ bdata->slide.directions = MEM_calloc_arrayN(totvert, 3 * sizeof(float), "slide directions");
+
+ for (int i = 0; i < totvert; i++) {
+ if (bdata->edit_info[i].num_propagation_steps == bdata->max_propagation_steps) {
+ sub_v3_v3v3(bdata->slide.directions[bdata->edit_info[i].original_vertex],
+ SCULPT_vertex_co_get(ss, bdata->edit_info[i].original_vertex),
+ SCULPT_vertex_co_get(ss, i));
+ normalize_v3(bdata->slide.directions[bdata->edit_info[i].original_vertex]);
+ }
+ }
+
+ for (int i = 0; i < totvert; i++) {
+ if (bdata->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
+ copy_v3_v3(bdata->slide.directions[i],
+ bdata->slide.directions[bdata->edit_info[i].original_vertex]);
+ }
+ }
+}
+
+static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *bdata)
+{
+ zero_v3(bdata->twist.pivot_position);
+ float(*poly_verts)[3] = MEM_malloc_arrayN(bdata->num_vertices, sizeof(float) * 3, "poly verts");
+ for (int i = 0; i < bdata->num_vertices; i++) {
+ add_v3_v3(bdata->twist.pivot_position, SCULPT_vertex_co_get(ss, bdata->vertices[i]));
+ copy_v3_v3(poly_verts[i], SCULPT_vertex_co_get(ss, bdata->vertices[i]));
+ }
+ mul_v3_fl(bdata->twist.pivot_position, 1.0f / bdata->num_vertices);
+ if (bdata->forms_loop) {
+ normal_poly_v3(bdata->twist.rotation_axis, poly_verts, bdata->num_vertices);
+ }
+ else {
+ sub_v3_v3v3(bdata->twist.rotation_axis,
+ SCULPT_vertex_co_get(ss, bdata->pivot_vertex),
+ SCULPT_vertex_co_get(ss, bdata->initial_vertex));
+ normalize_v3(bdata->twist.rotation_axis);
+ }
+ MEM_freeN(poly_verts);
+}
+
+static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss,
+ SculptBoundary *bdata)
+{
+ float plane[4];
+ float pos[3];
+ float normal[3];
+ sub_v3_v3v3(normal, ss->cache->initial_location, bdata->initial_pivot_position);
+ normalize_v3(normal);
+ plane_from_point_normal_v3(plane, ss->cache->initial_location, normal);
+ add_v3_v3v3(pos, ss->cache->initial_location, ss->cache->grab_delta_symmetry);
+ return dist_signed_to_plane_v3(pos, plane);
+}
+
+/* Deformation tasks callbacks. */
+static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ SculptBoundary *bdata = ss->cache->bdata[symm_area];
+
+ const float strength = ss->cache->bstrength;
+
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ float angle_factor = disp / ss->cache->radius;
+ /* Angle Snapping when inverting the brush. */
+ if (ss->cache->invert) {
+ angle_factor = floorf(angle_factor * 10) / 10.0f;
+ }
+ const float angle = angle_factor * M_PI;
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float t_orig_co[3];
+ sub_v3_v3v3(t_orig_co, orig_data.co, bdata->bend.pivot_positions[vd.index]);
+ rotate_v3_v3v3fl(vd.co,
+ t_orig_co,
+ bdata->bend.pivot_rotation_axis[vd.index],
+ angle * bdata->edit_info[vd.index].strength_factor * mask);
+ add_v3_v3(vd.co, bdata->bend.pivot_positions[vd.index]);
+ }
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ SculptBoundary *bdata = ss->cache->bdata[symm_area];
+
+ const float strength = ss->cache->bstrength;
+
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ madd_v3_v3v3fl(vd.co,
+ orig_data.co,
+ bdata->slide.directions[vd.index],
+ bdata->edit_info[vd.index].strength_factor * disp * mask * strength);
+ }
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ SculptBoundary *bdata = ss->cache->bdata[symm_area];
+
+ const float strength = ss->cache->bstrength;
+
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float normal[3];
+ normal_short_to_float_v3(normal, orig_data.no);
+ madd_v3_v3v3fl(vd.co,
+ orig_data.co,
+ normal,
+ bdata->edit_info[vd.index].strength_factor * disp * mask * strength);
+ }
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ SculptBoundary *bdata = ss->cache->bdata[symm_area];
+
+ 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 (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ madd_v3_v3v3fl(vd.co,
+ orig_data.co,
+ ss->cache->grab_delta_symmetry,
+ bdata->edit_info[vd.index].strength_factor * mask * strength);
+ }
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ SculptBoundary *bdata = ss->cache->bdata[symm_area];
+
+ const float strength = ss->cache->bstrength;
+
+ PBVHVertexIter vd;
+ SculptOrigVertData orig_data;
+ SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+ const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ float angle_factor = disp / ss->cache->radius;
+ /* Angle Snapping when inverting the brush. */
+ if (ss->cache->invert) {
+ angle_factor = floorf(angle_factor * 10) / 10.0f;
+ }
+ const float angle = angle_factor * M_PI;
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ SCULPT_orig_vert_data_update(&orig_data, &vd);
+ float t_orig_co[3];
+ sub_v3_v3v3(t_orig_co, orig_data.co, bdata->twist.pivot_position);
+ rotate_v3_v3v3fl(vd.co,
+ t_orig_co,
+ bdata->twist.rotation_axis,
+ angle * mask * bdata->edit_info[vd.index].strength_factor);
+ add_v3_v3(vd.co, bdata->twist.pivot_position);
+ }
+
+ 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)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ const int symm_area = ss->cache->mirror_symmetry_pass;
+ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
+
+ int initial_vertex;
+ if (ss->cache->mirror_symmetry_pass == 0) {
+ initial_vertex = SCULPT_active_vertex_get(ss);
+ }
+ else {
+ float location[3];
+ flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), symm_area);
+ initial_vertex = SCULPT_nearest_vertex_get(
+ sd, ob, location, ss->cache->radius_squared, false);
+ }
+
+ ss->cache->bdata[symm_area] = SCULPT_boundary_data_init(
+ ob, initial_vertex, ss->cache->initial_radius);
+
+ if (ss->cache->bdata[symm_area]) {
+
+ switch (brush->boundary_deform_type) {
+ case BRUSH_BOUNDARY_DEFORM_BEND:
+ sculpt_boundary_bend_data_init(ss, ss->cache->bdata[symm_area]);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_EXPAND:
+ sculpt_boundary_slide_data_init(ss, ss->cache->bdata[symm_area]);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_TWIST:
+ sculpt_boundary_twist_data_init(ss, ss->cache->bdata[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. */
+ break;
+ }
+
+ sculpt_boundary_falloff_factor_init(ss, ss->cache->bdata[symm_area], brush);
+ }
+ }
+
+ /* No active boundary under the cursor. */
+ if (!ss->cache->bdata[symm_area]) {
+ return;
+ }
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+
+ switch (brush->boundary_deform_type) {
+ case BRUSH_BOUNDARY_DEFORM_BEND:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_bend_task_cb_ex, &settings);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_EXPAND:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_slide_task_cb_ex, &settings);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_INFLATE:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_inflate_task_cb_ex, &settings);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_GRAB:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_grab_task_cb_ex, &settings);
+ break;
+ case BRUSH_BOUNDARY_DEFORM_TWIST:
+ BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_twist_task_cb_ex, &settings);
+ break;
+ }
+}
+
+void SCULPT_boundary_edges_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ const float outline_col[3],
+ const float outline_alpha)
+{
+ if (!ss->boundary_preview) {
+ return;
+ }
+ immUniformColor3fvAlpha(outline_col, outline_alpha);
+ GPU_line_width(2.0f);
+ immBegin(GPU_PRIM_LINES, ss->boundary_preview->num_edges * 2);
+ for (int i = 0; i < ss->boundary_preview->num_edges; i++) {
+ immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->edges[i].v1));
+ immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->edges[i].v2));
+ }
+ immEnd();
+}
+
+void SCULPT_boundary_pivot_line_preview_draw(const uint gpuattr, SculptSession *ss)
+{
+ if (!ss->boundary_preview) {
+ return;
+ }
+ immUniformColor4f(1.0f, 1.0f, 1.0f, 0.8f);
+ GPU_line_width(2.0f);
+ immBegin(GPU_PRIM_LINES, 2);
+ immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->pivot_vertex));
+ immVertex3fv(gpuattr, SCULPT_vertex_co_get(ss, ss->boundary_preview->initial_vertex));
+ immEnd();
+}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 74feaa8ca00..79b1a5bde7b 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -392,6 +392,22 @@ struct SculptPoseIKChain *SCULPT_pose_ik_chain_init(struct Sculpt *sd,
const float radius);
void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
+/* Boundary Brush. */
+struct SculptBoundary *SCULPT_boundary_data_init(Object *object,
+ const int initial_vertex,
+ const float radius);
+void SCULPT_boundary_data_free(struct SculptBoundary *bdata);
+void SCULPT_do_boundary_brush(struct Sculpt *sd,
+ struct Object *ob,
+ struct PBVHNode **nodes,
+ int totnode);
+
+void SCULPT_boundary_edges_preview_draw(const uint gpuattr,
+ struct SculptSession *ss,
+ const float outline_col[3],
+ const float outline_alpha);
+void SCULPT_boundary_pivot_line_preview_draw(const uint gpuattr, struct SculptSession *ss);
+
/* Multiplane 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,
@@ -630,6 +646,9 @@ typedef struct SculptThreadedTaskData {
float transform_mats[8][4][4];
+ /* Boundary brush */
+ float boundary_deform_strength;
+
float cloth_time_step;
SculptClothSimulation *cloth_sim;
float *cloth_sim_initial_location;
@@ -855,6 +874,9 @@ typedef struct StrokeCache {
float initial_normal[3];
float true_initial_normal[3];
+ /* Boundary brush */
+ struct SculptBoundary *bdata[PAINT_SYMM_AREAS];
+
/* Surface Smooth Brush */
/* Stores the displacement produced by the laplacian step of HC smooth. */
float (*surface_smooth_laplacian_disp)[3];
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 036c6c9c9d6..503dba776a7 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -379,6 +379,14 @@ typedef enum eBrushSlideDeformType {
BRUSH_SLIDE_DEFORM_EXPAND = 2,
} eBrushSlideDeformType;
+typedef enum eBrushBoundaryDeformType {
+ BRUSH_BOUNDARY_DEFORM_BEND = 0,
+ BRUSH_BOUNDARY_DEFORM_EXPAND = 1,
+ BRUSH_BOUNDARY_DEFORM_INFLATE = 2,
+ BRUSH_BOUNDARY_DEFORM_GRAB = 3,
+ BRUSH_BOUNDARY_DEFORM_TWIST = 4,
+} eBrushBushBoundaryDeformType;
+
/* Gpencilsettings.Vertex_mode */
typedef enum eGp_Vertex_Mode {
/* Affect to Stroke only. */
@@ -524,7 +532,7 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
- char _pad0[5];
+ char _pad0[1];
/** Projection shape (sphere, circle). */
char falloff_shape;
@@ -586,6 +594,9 @@ typedef struct Brush {
int pose_ik_segments;
int pose_origin_type;
+ /* boundary */
+ int boundary_deform_type;
+
/* cloth */
int cloth_deform_type;
int cloth_force_falloff_type;
@@ -798,6 +809,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_DRAW_FACE_SETS = 27,
SCULPT_TOOL_PAINT = 28,
SCULPT_TOOL_SMEAR = 29,
+ SCULPT_TOOL_BOUNDARY = 30,
} eBrushSculptTool;
/* Brush.uv_sculpt_tool */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index ca90483926f..852ddf32607 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -95,6 +95,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_NUDGE, "NUDGE", ICON_BRUSH_NUDGE, "Nudge", ""},
{SCULPT_TOOL_ROTATE, "ROTATE", ICON_BRUSH_ROTATE, "Rotate", ""},
{SCULPT_TOOL_SLIDE_RELAX, "TOPOLOGY", ICON_BRUSH_GRAB, "Slide Relax", ""},
+ {SCULPT_TOOL_BOUNDARY, "BOUNDARY", ICON_BRUSH_GRAB, "Boundary", ""},
{0, "", 0, NULL, NULL},
{SCULPT_TOOL_CLOTH, "CLOTH", ICON_BRUSH_SCULPT_DRAW, "Cloth", ""},
{SCULPT_TOOL_SIMPLIFY, "SIMPLIFY", ICON_BRUSH_DATA, "Simplify", ""},
@@ -2060,6 +2061,15 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
+ static const EnumPropertyItem brush_boundary_deform_type_items[] = {
+ {BRUSH_BOUNDARY_DEFORM_BEND, "BEND", 0, "Bend", ""},
+ {BRUSH_BOUNDARY_DEFORM_EXPAND, "EXPAND", 0, "Expand", ""},
+ {BRUSH_BOUNDARY_DEFORM_INFLATE, "INFLATE", 0, "Inflate", ""},
+ {BRUSH_BOUNDARY_DEFORM_GRAB, "GRAB", 0, "Grab", ""},
+ {BRUSH_BOUNDARY_DEFORM_TWIST, "TWIST", 0, "Twist", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
srna = RNA_def_struct(brna, "Brush", "ID");
RNA_def_struct_ui_text(
srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
@@ -2198,6 +2208,11 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
+ prop = RNA_def_property(srna, "boundary_deform_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, brush_boundary_deform_type_items);
+ RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+
prop = RNA_def_property(srna, "pose_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_pose_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");