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/sculpt_paint/sculpt_boundary.c')
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_boundary.c865
1 files changed, 865 insertions, 0 deletions
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..a368214cb0b
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c
@@ -0,0 +1,865 @@
+/*
+ * 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 function 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 decide which boundary should be active. The flood fill
+ * should also stop at corners. */
+ if (neighbor_count <= 2) {
+ return false;
+ }
+
+ /* Non manifold geometry 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 calculate 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 assigns 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();
+}