Sculpt: Boundary Brush

This brush includes a set of deformation modes designed to deform and
control the shape of the mesh boundaries, which are really hard to do
with regular sculpt brushes (and even in edit mode). This is useful
for creating cloth assets and hard surface base meshes.

The brush detects the mesh boundary closest to the active vertex and
propagates the deformation using the brush falloff into the mesh.
It includes bend, expand, inflate, grab and twist deform modes.

The main use cases of this brush are the Bend and Expand deformation
modes, which depend on a grid topology to create the best results.
In order to do further adjustments and tweaks to the result of these
deformation modes, the brush also includes the Inflate, Grab and
Twist deformation modes, which do not depend that much on the topology.

Grab and Inflate are the same operation that is implemented in the
Grab and Inflate tools, they are also available in the boundary brush
as producing deformations with regular brushes in these areas is very
hard to control.

Even if this brush can produce deformations in triangle meshes and
meshes with a non-regular quad grid, the more regular and clean the
topology is, the better. Most of the assets this brush is intended to
deform are always created from a cylindrical or plane quad grid, so it
should be fine. Also, its algorithms can be improved in future versions
to handle more corner cases and topology patterns.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D8356
This commit is contained in:
Pablo Dobarro 2020-08-10 17:57:01 +02:00
parent 71639cc862
commit ed9c0464ba
12 changed files with 1082 additions and 4 deletions

View File

@ -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")

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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));

View File

@ -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();
}

View File

@ -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];

View File

@ -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 */

View File

@ -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");