|
|
|
@ -30,6 +30,7 @@
|
|
|
|
|
#include "BLI_hash.h"
|
|
|
|
|
#include "BLI_gsqueue.h"
|
|
|
|
|
#include "BLI_stack.h"
|
|
|
|
|
#include "BLI_gsqueue.h"
|
|
|
|
|
#include "BLI_task.h"
|
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
|
#include "BLI_ghash.h"
|
|
|
|
@ -343,6 +344,25 @@ static void sculpt_vertex_mask_clamp(SculptSession *ss, int index, float min, fl
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm)
|
|
|
|
|
{
|
|
|
|
|
bool is_in_symmetry_area = true;
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
char symm_it = 1 << i;
|
|
|
|
|
if (symm & symm_it) {
|
|
|
|
|
if (pco[i] == 0.0f) {
|
|
|
|
|
if (vco[i] > 0.0f) {
|
|
|
|
|
is_in_symmetry_area = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (vco[i] * pco[i] < 0.0f) {
|
|
|
|
|
is_in_symmetry_area = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return is_in_symmetry_area;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void do_nearest_vertex_get_task_cb(void *__restrict userdata,
|
|
|
|
|
const int n,
|
|
|
|
|
const TaskParallelTLS *__restrict UNUSED(tls))
|
|
|
|
@ -401,6 +421,7 @@ int sculpt_nearest_vertex_get(
|
|
|
|
|
.max_distance_squared = max_distance * max_distance,
|
|
|
|
|
.nearest_vertex_index = -1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(task_data.nearest_vertex_search_co, co);
|
|
|
|
|
|
|
|
|
|
BLI_mutex_init(&task_data.mutex);
|
|
|
|
@ -470,12 +491,13 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
|
|
|
|
|
SCULPT_TOOL_THUMB,
|
|
|
|
|
SCULPT_TOOL_LAYER,
|
|
|
|
|
SCULPT_TOOL_DRAW_SHARP,
|
|
|
|
|
SCULPT_TOOL_ELASTIC_DEFORM);
|
|
|
|
|
SCULPT_TOOL_ELASTIC_DEFORM,
|
|
|
|
|
SCULPT_TOOL_POSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
|
|
|
|
|
{
|
|
|
|
|
return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER);
|
|
|
|
|
return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush)
|
|
|
|
@ -1698,6 +1720,7 @@ static float brush_strength(const Sculpt *sd,
|
|
|
|
|
return alpha * pressure * feather;
|
|
|
|
|
|
|
|
|
|
case SCULPT_TOOL_ELASTIC_DEFORM:
|
|
|
|
|
case SCULPT_TOOL_POSE:
|
|
|
|
|
return root_alpha * feather;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
@ -3446,6 +3469,240 @@ static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in
|
|
|
|
|
BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void do_pose_brush_task_cb_ex(void *__restrict userdata,
|
|
|
|
|
const int n,
|
|
|
|
|
const TaskParallelTLS *__restrict tls)
|
|
|
|
|
{
|
|
|
|
|
SculptThreadedTaskData *data = userdata;
|
|
|
|
|
SculptSession *ss = data->ob->sculpt;
|
|
|
|
|
|
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
|
float disp[3], val[3];
|
|
|
|
|
float final_pos[3];
|
|
|
|
|
|
|
|
|
|
SculptOrigVertData orig_data;
|
|
|
|
|
sculpt_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
|
|
|
|
|
|
|
|
|
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
sculpt_orig_vert_data_update(&orig_data, &vd);
|
|
|
|
|
if (check_vertex_pivot_symmetry(
|
|
|
|
|
orig_data.co, data->pose_initial_co, ss->cache->mirror_symmetry_pass)) {
|
|
|
|
|
copy_v3_v3(val, orig_data.co);
|
|
|
|
|
mul_m4_v3(data->transform_trans_inv, val);
|
|
|
|
|
mul_m4_v3(data->transform_rot, val);
|
|
|
|
|
mul_m4_v3(data->transform_trans, val);
|
|
|
|
|
sub_v3_v3v3(disp, val, orig_data.co);
|
|
|
|
|
|
|
|
|
|
mul_v3_fl(disp, ss->cache->pose_factor[vd.index]);
|
|
|
|
|
float mask = vd.mask ? *vd.mask : 0.0f;
|
|
|
|
|
mul_v3_fl(disp, 1.0f - mask);
|
|
|
|
|
add_v3_v3v3(final_pos, orig_data.co, disp);
|
|
|
|
|
copy_v3_v3(vd.co, final_pos);
|
|
|
|
|
|
|
|
|
|
if (vd.mvert) {
|
|
|
|
|
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BKE_pbvh_vertex_iter_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
|
|
|
|
{
|
|
|
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
|
float grab_delta[3], rot_quat[4], initial_v[3], current_v[3], temp[3];
|
|
|
|
|
float pose_origin[3];
|
|
|
|
|
float pose_initial_co[3];
|
|
|
|
|
float transform_rot[4][4], transform_trans[4][4], transform_trans_inv[4][4];
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(pose_origin, ss->cache->pose_origin);
|
|
|
|
|
flip_v3(pose_origin, (char)ss->cache->mirror_symmetry_pass);
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(pose_initial_co, ss->cache->pose_initial_co);
|
|
|
|
|
flip_v3(pose_initial_co, (char)ss->cache->mirror_symmetry_pass);
|
|
|
|
|
|
|
|
|
|
sub_v3_v3v3(initial_v, pose_initial_co, pose_origin);
|
|
|
|
|
normalize_v3(initial_v);
|
|
|
|
|
|
|
|
|
|
add_v3_v3v3(temp, pose_initial_co, grab_delta);
|
|
|
|
|
sub_v3_v3v3(current_v, temp, pose_origin);
|
|
|
|
|
normalize_v3(current_v);
|
|
|
|
|
|
|
|
|
|
rotation_between_vecs_to_quat(rot_quat, initial_v, current_v);
|
|
|
|
|
unit_m4(transform_rot);
|
|
|
|
|
unit_m4(transform_trans);
|
|
|
|
|
quat_to_mat4(transform_rot, rot_quat);
|
|
|
|
|
translate_m4(transform_trans, pose_origin[0], pose_origin[1], pose_origin[2]);
|
|
|
|
|
invert_m4_m4(transform_trans_inv, transform_trans);
|
|
|
|
|
|
|
|
|
|
SculptThreadedTaskData data = {
|
|
|
|
|
.sd = sd,
|
|
|
|
|
.ob = ob,
|
|
|
|
|
.brush = brush,
|
|
|
|
|
.nodes = nodes,
|
|
|
|
|
.grab_delta = grab_delta,
|
|
|
|
|
.pose_origin = pose_origin,
|
|
|
|
|
.pose_initial_co = pose_initial_co,
|
|
|
|
|
.transform_rot = transform_rot,
|
|
|
|
|
.transform_trans = transform_trans,
|
|
|
|
|
.transform_trans_inv = transform_trans_inv,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TaskParallelSettings settings;
|
|
|
|
|
BLI_parallel_range_settings_defaults(&settings);
|
|
|
|
|
settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
|
|
|
|
|
BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void pose_brush_init_task_cb_ex(void *__restrict userdata,
|
|
|
|
|
const int n,
|
|
|
|
|
const TaskParallelTLS *__restrict UNUSED(tls))
|
|
|
|
|
{
|
|
|
|
|
SculptThreadedTaskData *data = userdata;
|
|
|
|
|
SculptSession *ss = data->ob->sculpt;
|
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
|
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
|
|
|
|
|
{
|
|
|
|
|
SculptVertexNeighborIter ni;
|
|
|
|
|
float avg = 0;
|
|
|
|
|
int total = 0;
|
|
|
|
|
sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
|
|
|
|
|
{
|
|
|
|
|
avg += ss->cache->pose_factor[ni.index];
|
|
|
|
|
total++;
|
|
|
|
|
}
|
|
|
|
|
sculpt_vertex_neighbors_iter_end(ni);
|
|
|
|
|
|
|
|
|
|
if (total > 0) {
|
|
|
|
|
ss->cache->pose_factor[vd.index] = avg / (float)total;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BKE_pbvh_vertex_iter_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool sculpt_pose_brush_is_vertex_inside_brush_radius(float vertex[3],
|
|
|
|
|
float br_co[3],
|
|
|
|
|
float radius,
|
|
|
|
|
char symm)
|
|
|
|
|
{
|
|
|
|
|
for (char i = 0; i <= symm; ++i) {
|
|
|
|
|
if (is_symmetry_iteration_valid(i, symm)) {
|
|
|
|
|
float location[3];
|
|
|
|
|
flip_v3_v3(location, br_co, (char)i);
|
|
|
|
|
if (len_v3v3(location, vertex) < radius) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sculpt_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br)
|
|
|
|
|
{
|
|
|
|
|
sculpt_vertex_random_access_init(ss);
|
|
|
|
|
|
|
|
|
|
ss->cache->pose_factor = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(float), "Pose factor");
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(ss->cache->pose_initial_co, ss->cache->location);
|
|
|
|
|
|
|
|
|
|
char *visited_vertices = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(char),
|
|
|
|
|
"Visited vertices");
|
|
|
|
|
BLI_Stack *not_visited_vertices = BLI_stack_new(sizeof(VertexTopologyIterator),
|
|
|
|
|
"not visited vertices stack");
|
|
|
|
|
|
|
|
|
|
float tot_co = 0;
|
|
|
|
|
zero_v3(ss->cache->pose_origin);
|
|
|
|
|
|
|
|
|
|
VertexTopologyIterator mevit;
|
|
|
|
|
|
|
|
|
|
/* Add active vertex and symmetric vertices to the stack. */
|
|
|
|
|
const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
|
|
|
|
|
for (char i = 0; i <= symm; ++i) {
|
|
|
|
|
if (is_symmetry_iteration_valid(i, symm)) {
|
|
|
|
|
float location[3];
|
|
|
|
|
flip_v3_v3(location, sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss)), (char)i);
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
mevit.v = sculpt_active_vertex_get(ss);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mevit.v = sculpt_nearest_vertex_get(
|
|
|
|
|
sd, ob, location, ss->cache->radius * ss->cache->radius, false);
|
|
|
|
|
}
|
|
|
|
|
if (mevit.v != -1) {
|
|
|
|
|
mevit.it = 1;
|
|
|
|
|
BLI_stack_push(not_visited_vertices, &mevit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Flood fill the internal pose brush factor. Calculate the pose rotation point based on the
|
|
|
|
|
* boundaries of the brush factor*/
|
|
|
|
|
while (!BLI_stack_is_empty(not_visited_vertices)) {
|
|
|
|
|
VertexTopologyIterator c_mevit;
|
|
|
|
|
BLI_stack_pop(not_visited_vertices, &c_mevit);
|
|
|
|
|
SculptVertexNeighborIter ni;
|
|
|
|
|
sculpt_vertex_neighbors_iter_begin(ss, c_mevit.v, ni)
|
|
|
|
|
{
|
|
|
|
|
if (visited_vertices[(int)ni.index] == 0) {
|
|
|
|
|
VertexTopologyIterator new_entry;
|
|
|
|
|
new_entry.v = ni.index;
|
|
|
|
|
new_entry.it = c_mevit.it + 1;
|
|
|
|
|
ss->cache->pose_factor[new_entry.v] = 1.0f;
|
|
|
|
|
visited_vertices[(int)ni.index] = 1;
|
|
|
|
|
if (sculpt_pose_brush_is_vertex_inside_brush_radius(sculpt_vertex_co_get(ss, new_entry.v),
|
|
|
|
|
ss->cache->pose_initial_co,
|
|
|
|
|
ss->cache->radius,
|
|
|
|
|
symm)) {
|
|
|
|
|
BLI_stack_push(not_visited_vertices, &new_entry);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (check_vertex_pivot_symmetry(
|
|
|
|
|
sculpt_vertex_co_get(ss, new_entry.v), ss->cache->pose_initial_co, symm)) {
|
|
|
|
|
tot_co++;
|
|
|
|
|
add_v3_v3(ss->cache->pose_origin, sculpt_vertex_co_get(ss, new_entry.v));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sculpt_vertex_neighbors_iter_end(ni)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_stack_free(not_visited_vertices);
|
|
|
|
|
|
|
|
|
|
MEM_freeN(visited_vertices);
|
|
|
|
|
|
|
|
|
|
if (tot_co > 0) {
|
|
|
|
|
mul_v3_fl(ss->cache->pose_origin, 1.0f / (float)tot_co);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Smooth the pose brush factor for cleaner deformation */
|
|
|
|
|
|
|
|
|
|
PBVHNode **nodes;
|
|
|
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
|
|
|
int totnode;
|
|
|
|
|
|
|
|
|
|
BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
|
|
|
|
|
|
|
|
|
|
SculptThreadedTaskData data = {
|
|
|
|
|
.sd = sd,
|
|
|
|
|
.ob = ob,
|
|
|
|
|
.brush = br,
|
|
|
|
|
.nodes = nodes,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
TaskParallelSettings settings;
|
|
|
|
|
BLI_parallel_range_settings_defaults(&settings);
|
|
|
|
|
settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
|
|
|
|
|
BLI_task_parallel_range(0, totnode, &data, pose_brush_init_task_cb_ex, &settings);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void do_nudge_brush_task_cb_ex(void *__restrict userdata,
|
|
|
|
|
const int n,
|
|
|
|
|
const TaskParallelTLS *__restrict tls)
|
|
|
|
@ -4810,6 +5067,11 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (brush->sculpt_tool == SCULPT_TOOL_POSE && ss->cache->first_time &&
|
|
|
|
|
ss->cache->mirror_symmetry_pass == 0) {
|
|
|
|
|
sculpt_pose_brush_init(sd, ob, ss, brush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Apply one type of brush action */
|
|
|
|
|
switch (brush->sculpt_tool) {
|
|
|
|
|
case SCULPT_TOOL_DRAW:
|
|
|
|
@ -4865,6 +5127,8 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
|
|
|
|
break;
|
|
|
|
|
case SCULPT_TOOL_MASK:
|
|
|
|
|
do_mask_brush(sd, ob, nodes, totnode);
|
|
|
|
|
case SCULPT_TOOL_POSE:
|
|
|
|
|
do_pose_brush(sd, ob, nodes, totnode);
|
|
|
|
|
break;
|
|
|
|
|
case SCULPT_TOOL_DRAW_SHARP:
|
|
|
|
|
do_draw_sharp_brush(sd, ob, nodes, totnode);
|
|
|
|
@ -4940,7 +5204,8 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
|
|
|
|
|
SCULPT_TOOL_GRAB,
|
|
|
|
|
SCULPT_TOOL_ROTATE,
|
|
|
|
|
SCULPT_TOOL_THUMB,
|
|
|
|
|
SCULPT_TOOL_ELASTIC_DEFORM);
|
|
|
|
|
SCULPT_TOOL_ELASTIC_DEFORM,
|
|
|
|
|
SCULPT_TOOL_POSE);
|
|
|
|
|
|
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
|
PBVHProxyNode *proxies;
|
|
|
|
@ -5384,6 +5649,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
|
|
|
|
|
return "Draw Sharp Brush";
|
|
|
|
|
case SCULPT_TOOL_ELASTIC_DEFORM:
|
|
|
|
|
return "Elastic Deform Brush";
|
|
|
|
|
case SCULPT_TOOL_POSE:
|
|
|
|
|
return "Pose Brush";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "Sculpting";
|
|
|
|
@ -5626,6 +5893,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
|
|
|
|
|
SCULPT_TOOL_NUDGE,
|
|
|
|
|
SCULPT_TOOL_CLAY_STRIPS,
|
|
|
|
|
SCULPT_TOOL_SNAKE_HOOK,
|
|
|
|
|
SCULPT_TOOL_POSE,
|
|
|
|
|
SCULPT_TOOL_THUMB) ||
|
|
|
|
|
sculpt_brush_use_topology_rake(ss, brush)) {
|
|
|
|
|
float grab_location[3], imat[4][4], delta[3], loc[3];
|
|
|
|
@ -5645,6 +5913,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
|
|
|
|
|
if (!cache->first_time) {
|
|
|
|
|
switch (tool) {
|
|
|
|
|
case SCULPT_TOOL_GRAB:
|
|
|
|
|
case SCULPT_TOOL_POSE:
|
|
|
|
|
case SCULPT_TOOL_THUMB:
|
|
|
|
|
case SCULPT_TOOL_ELASTIC_DEFORM:
|
|
|
|
|
sub_v3_v3v3(delta, grab_location, cache->old_grab_location);
|
|
|
|
@ -5692,7 +5961,11 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
|
|
|
|
|
copy_v3_v3(cache->anchored_location, cache->orig_grab_location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ELEM(tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_THUMB, SCULPT_TOOL_ELASTIC_DEFORM)) {
|
|
|
|
|
if (ELEM(tool,
|
|
|
|
|
SCULPT_TOOL_GRAB,
|
|
|
|
|
SCULPT_TOOL_THUMB,
|
|
|
|
|
SCULPT_TOOL_ELASTIC_DEFORM,
|
|
|
|
|
SCULPT_TOOL_POSE)) {
|
|
|
|
|
/* location stays the same for finding vertices in brush radius */
|
|
|
|
|
copy_v3_v3(cache->true_location, cache->orig_grab_location);
|
|
|
|
|
|
|
|
|
@ -5837,7 +6110,8 @@ static bool sculpt_needs_conectivity_info(const Brush *brush, SculptSession *ss,
|
|
|
|
|
}
|
|
|
|
|
return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) ||
|
|
|
|
|
(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_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
|
|
|
|
|
(brush->sculpt_tool == SCULPT_TOOL_POSE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
|
|
|
|
|