diff options
-rw-r--r-- | release/scripts/startup/bl_ui/properties_paint_common.py | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_paint.h | 73 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/brush.c | 7 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_defaults.c | 8 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_cursor.c | 48 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_stroke.c | 3 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 29 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_boundary.c | 862 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 22 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_types.h | 14 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_brush.c | 15 |
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"); |