Sculpt: split sculpt.c into three files
Sculpt.c is now three files: * Sculpt.c: main API methods and the brush stroke operator * Sculpt_brushes.c: Code for individual brushes. * Sculpt_ops.c: Sculpt operators other than the brush stroke operator. TODO: split brush stroke operator into a new file (sculpt_stroke.c?).
This commit is contained in:
@@ -59,6 +59,7 @@ set(SRC
|
||||
sculpt.c
|
||||
sculpt_automasking.c
|
||||
sculpt_boundary.c
|
||||
sculpt_brushes.c
|
||||
sculpt_cloth.c
|
||||
sculpt_detail.c
|
||||
sculpt_dyntopo.c
|
||||
@@ -71,6 +72,7 @@ set(SRC
|
||||
sculpt_mask_expand.c
|
||||
sculpt_mask_init.c
|
||||
sculpt_multiplane_scrape.c
|
||||
sculpt_ops.c
|
||||
sculpt_paint_color.c
|
||||
sculpt_pose.c
|
||||
sculpt_smooth.c
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "BKE_paint.h"
|
||||
|
||||
#include "BLI_rect.h"
|
||||
#include "BLI_compiler_compat.h"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
@@ -404,8 +406,61 @@ bool facemask_paint_poll(struct bContext *C);
|
||||
/**
|
||||
* Uses symm to selectively flip any axis of a coordinate.
|
||||
*/
|
||||
void flip_v3_v3(float out[3], const float in[3], const enum ePaintSymmetryFlags symm);
|
||||
void flip_qt_qt(float out[4], const float in[4], const enum ePaintSymmetryFlags symm);
|
||||
|
||||
BLI_INLINE void flip_v3_v3(float out[3], const float in[3], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
if (symm & PAINT_SYMM_X) {
|
||||
out[0] = -in[0];
|
||||
}
|
||||
else {
|
||||
out[0] = in[0];
|
||||
}
|
||||
if (symm & PAINT_SYMM_Y) {
|
||||
out[1] = -in[1];
|
||||
}
|
||||
else {
|
||||
out[1] = in[1];
|
||||
}
|
||||
if (symm & PAINT_SYMM_Z) {
|
||||
out[2] = -in[2];
|
||||
}
|
||||
else {
|
||||
out[2] = in[2];
|
||||
}
|
||||
}
|
||||
|
||||
BLI_INLINE void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
float axis[3], angle;
|
||||
|
||||
quat_to_axis_angle(axis, &angle, in);
|
||||
normalize_v3(axis);
|
||||
|
||||
if (symm & PAINT_SYMM_X) {
|
||||
axis[0] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
if (symm & PAINT_SYMM_Y) {
|
||||
axis[1] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
if (symm & PAINT_SYMM_Z) {
|
||||
axis[2] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
|
||||
axis_angle_normalized_to_quat(out, axis, angle);
|
||||
}
|
||||
|
||||
BLI_INLINE void flip_v3(float v[3], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
flip_v3_v3(v, v, symm);
|
||||
}
|
||||
|
||||
BLI_INLINE void flip_qt(float quat[4], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
flip_qt_qt(quat, quat, symm);
|
||||
}
|
||||
|
||||
/* stroke operator */
|
||||
typedef enum BrushStrokeMode {
|
||||
|
||||
@@ -397,51 +397,6 @@ static Image *imapaint_face_image(Object *ob, Mesh *me, int face_index)
|
||||
return ima;
|
||||
}
|
||||
|
||||
void flip_v3_v3(float out[3], const float in[3], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
if (symm & PAINT_SYMM_X) {
|
||||
out[0] = -in[0];
|
||||
}
|
||||
else {
|
||||
out[0] = in[0];
|
||||
}
|
||||
if (symm & PAINT_SYMM_Y) {
|
||||
out[1] = -in[1];
|
||||
}
|
||||
else {
|
||||
out[1] = in[1];
|
||||
}
|
||||
if (symm & PAINT_SYMM_Z) {
|
||||
out[2] = -in[2];
|
||||
}
|
||||
else {
|
||||
out[2] = in[2];
|
||||
}
|
||||
}
|
||||
|
||||
void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
float axis[3], angle;
|
||||
|
||||
quat_to_axis_angle(axis, &angle, in);
|
||||
normalize_v3(axis);
|
||||
|
||||
if (symm & PAINT_SYMM_X) {
|
||||
axis[0] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
if (symm & PAINT_SYMM_Y) {
|
||||
axis[1] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
if (symm & PAINT_SYMM_Z) {
|
||||
axis[2] *= -1.0f;
|
||||
angle *= -1.0f;
|
||||
}
|
||||
|
||||
axis_angle_normalized_to_quat(out, axis, angle);
|
||||
}
|
||||
|
||||
void paint_sample_color(
|
||||
bContext *C, ARegion *region, int x, int y, bool texpaint_proj, bool use_palette)
|
||||
{
|
||||
|
||||
@@ -1329,103 +1329,6 @@ static void sculpt_rake_data_update(struct SculptRakeData *srd, const float co[3
|
||||
}
|
||||
}
|
||||
|
||||
static void sculpt_rake_rotate(const SculptSession *ss,
|
||||
const float sculpt_co[3],
|
||||
const float v_co[3],
|
||||
float factor,
|
||||
float r_delta[3])
|
||||
{
|
||||
float vec_rot[3];
|
||||
|
||||
#if 0
|
||||
/* lerp */
|
||||
sub_v3_v3v3(vec_rot, v_co, sculpt_co);
|
||||
mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot);
|
||||
add_v3_v3(vec_rot, sculpt_co);
|
||||
sub_v3_v3v3(r_delta, vec_rot, v_co);
|
||||
mul_v3_fl(r_delta, factor);
|
||||
#else
|
||||
/* slerp */
|
||||
float q_interp[4];
|
||||
sub_v3_v3v3(vec_rot, v_co, sculpt_co);
|
||||
|
||||
copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry);
|
||||
pow_qt_fl_normalized(q_interp, factor);
|
||||
mul_qt_v3(q_interp, vec_rot);
|
||||
|
||||
add_v3_v3(vec_rot, sculpt_co);
|
||||
sub_v3_v3v3(r_delta, vec_rot, v_co);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Align the grab delta to the brush normal.
|
||||
*
|
||||
* \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`.
|
||||
*/
|
||||
static void sculpt_project_v3_normal_align(SculptSession *ss,
|
||||
const float normal_weight,
|
||||
float grab_delta[3])
|
||||
{
|
||||
/* Signed to support grabbing in (to make a hole) as well as out. */
|
||||
const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta);
|
||||
|
||||
/* This scale effectively projects the offset so dragging follows the cursor,
|
||||
* as the normal points towards the view, the scale increases. */
|
||||
float len_view_scale;
|
||||
{
|
||||
float view_aligned_normal[3];
|
||||
project_plane_v3_v3v3(
|
||||
view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal);
|
||||
len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm));
|
||||
len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f;
|
||||
}
|
||||
|
||||
mul_v3_fl(grab_delta, 1.0f - normal_weight);
|
||||
madd_v3_v3fl(
|
||||
grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name SculptProjectVector
|
||||
*
|
||||
* Fast-path for #project_plane_v3_v3v3
|
||||
* \{ */
|
||||
|
||||
typedef struct SculptProjectVector {
|
||||
float plane[3];
|
||||
float len_sq;
|
||||
float len_sq_inv_neg;
|
||||
bool is_valid;
|
||||
|
||||
} SculptProjectVector;
|
||||
|
||||
/**
|
||||
* \param plane: Direction, can be any length.
|
||||
*/
|
||||
static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3])
|
||||
{
|
||||
copy_v3_v3(spvc->plane, plane);
|
||||
spvc->len_sq = len_squared_v3(spvc->plane);
|
||||
spvc->is_valid = (spvc->len_sq > FLT_EPSILON);
|
||||
spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the projection.
|
||||
*/
|
||||
static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3])
|
||||
{
|
||||
#if 0
|
||||
project_plane_v3_v3v3(r_vec, vec, spvc->plane);
|
||||
#else
|
||||
/* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */
|
||||
madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -1835,15 +1738,6 @@ static bool sculpt_brush_test_cyl(SculptBrushTest *test,
|
||||
|
||||
/* ===== Sculpting =====
|
||||
*/
|
||||
static void flip_v3(float v[3], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
flip_v3_v3(v, v, symm);
|
||||
}
|
||||
|
||||
static void flip_qt(float quat[4], const ePaintSymmetryFlags symm)
|
||||
{
|
||||
flip_qt_qt(quat, quat, symm);
|
||||
}
|
||||
|
||||
static float calc_overlap(StrokeCache *cache, const char symm, const char axis, const float angle)
|
||||
{
|
||||
@@ -1913,9 +1807,9 @@ static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache)
|
||||
* (optionally using original coordinates).
|
||||
*
|
||||
* Functions are:
|
||||
* - #calc_area_center
|
||||
* - #calc_area_normal
|
||||
* - #calc_area_normal_and_center
|
||||
* - #SCULPT_calc_area_center
|
||||
* - #SCULPT_calc_area_normal
|
||||
* - #SCULPT_calc_area_normal_and_center
|
||||
*
|
||||
* \note These are all _very_ similar, when changing one, check others.
|
||||
* \{ */
|
||||
@@ -2137,7 +2031,7 @@ static void calc_area_normal_and_center_reduce(const void *__restrict UNUSED(use
|
||||
add_v2_v2_int(join->count_co, anctd->count_co);
|
||||
}
|
||||
|
||||
static void calc_area_center(
|
||||
void SCULPT_calc_area_center(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3])
|
||||
{
|
||||
const Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
@@ -2238,7 +2132,7 @@ bool SCULPT_pbvh_calc_area_normal(const Brush *brush,
|
||||
* This calculates flatten center and area normal together,
|
||||
* amortizing the memory bandwidth and loop overhead to calculate both at the same time.
|
||||
*/
|
||||
static void calc_area_normal_and_center(
|
||||
void SCULPT_calc_area_normal_and_center(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3])
|
||||
{
|
||||
const Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
@@ -2856,10 +2750,6 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Topology Rake (Shared Utility)
|
||||
* \{ */
|
||||
|
||||
typedef struct {
|
||||
SculptSession *ss;
|
||||
const float *ray_start;
|
||||
@@ -2885,1294 +2775,6 @@ typedef struct {
|
||||
bool original;
|
||||
} SculptFindNearestToRayData;
|
||||
|
||||
static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
Sculpt *sd = data->sd;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
float direction[3];
|
||||
copy_v3_v3(direction, ss->cache->grab_delta_symmetry);
|
||||
|
||||
float tmp[3];
|
||||
mul_v3_v3fl(
|
||||
tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction));
|
||||
sub_v3_v3(direction, tmp);
|
||||
normalize_v3(direction);
|
||||
|
||||
/* Cancel if there's no grab data. */
|
||||
if (is_zero_v3(direction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float bstrength = clamp_f(data->strength, 0.0f, 1.0f);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade =
|
||||
bstrength *
|
||||
SCULPT_brush_strength_factor(
|
||||
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) *
|
||||
ss->cache->pressure;
|
||||
|
||||
float avg[3], val[3];
|
||||
|
||||
SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert);
|
||||
|
||||
sub_v3_v3v3(val, avg, vd.co);
|
||||
|
||||
madd_v3_v3v3fl(val, vd.co, val, fade);
|
||||
|
||||
SCULPT_clip(sd, ss, vd.co, val);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void bmesh_topology_rake(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
const float strength = clamp_f(bstrength, 0.0f, 1.0f);
|
||||
|
||||
/* Interactions increase both strength and quality. */
|
||||
const int iterations = 3;
|
||||
|
||||
int iteration;
|
||||
const int count = iterations * strength + 1;
|
||||
const float factor = iterations * strength / count;
|
||||
|
||||
for (iteration = 0; iteration <= count; iteration++) {
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.strength = factor,
|
||||
};
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Mask Brush
|
||||
* \{ */
|
||||
|
||||
static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = SCULPT_brush_strength_factor(
|
||||
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id);
|
||||
|
||||
if (bstrength > 0.0f) {
|
||||
(*vd.mask) += fade * bstrength * (1.0f - *vd.mask);
|
||||
}
|
||||
else {
|
||||
(*vd.mask) += fade * bstrength * (*vd.mask);
|
||||
}
|
||||
*vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
}
|
||||
|
||||
static void do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_mask_brush_draw_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
switch ((BrushMaskTool)brush->mask_tool) {
|
||||
case BRUSH_MASK_DRAW:
|
||||
do_mask_brush_draw(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case BRUSH_MASK_SMOOTH:
|
||||
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Multires Displacement Eraser Brush
|
||||
* \{ */
|
||||
|
||||
static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
|
||||
|
||||
float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
float limit_co[3];
|
||||
float disp[3];
|
||||
SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co);
|
||||
sub_v3_v3v3(disp, limit_co, vd.co);
|
||||
mul_v3_v3fl(proxy[vd.i], disp, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_displacement_eraser_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Multires Displacement Smear Brush
|
||||
* \{ */
|
||||
|
||||
static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
float current_disp[3];
|
||||
float current_disp_norm[3];
|
||||
float interp_limit_surface_disp[3];
|
||||
|
||||
copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]);
|
||||
|
||||
switch (brush->smear_deform_type) {
|
||||
case BRUSH_SMEAR_DEFORM_DRAG:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location);
|
||||
break;
|
||||
case BRUSH_SMEAR_DEFORM_PINCH:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, vd.co);
|
||||
break;
|
||||
case BRUSH_SMEAR_DEFORM_EXPAND:
|
||||
sub_v3_v3v3(current_disp, vd.co, ss->cache->location);
|
||||
break;
|
||||
}
|
||||
|
||||
normalize_v3_v3(current_disp_norm, current_disp);
|
||||
mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength);
|
||||
|
||||
float weights_accum = 1.0f;
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
|
||||
float vertex_disp[3];
|
||||
float vertex_disp_norm[3];
|
||||
float neighbor_limit_co[3];
|
||||
SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co);
|
||||
sub_v3_v3v3(vertex_disp,
|
||||
ss->cache->limit_surface_co[ni.index],
|
||||
ss->cache->limit_surface_co[vd.index]);
|
||||
const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index];
|
||||
normalize_v3_v3(vertex_disp_norm, vertex_disp);
|
||||
|
||||
if (dot_v3v3(current_disp_norm, vertex_disp_norm) >= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float disp_interp = clamp_f(
|
||||
-dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f);
|
||||
madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp);
|
||||
weights_accum += disp_interp;
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum);
|
||||
|
||||
float new_co[3];
|
||||
add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp);
|
||||
interp_v3_v3v3(vd.co, vd.co, new_co, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_displacement_smear_store_prev_disp_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) {
|
||||
sub_v3_v3v3(ss->cache->prev_displacement[vd.index],
|
||||
SCULPT_vertex_co_get(ss, vd.index),
|
||||
ss->cache->limit_surface_co[vd.index]);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
if (!ss->cache->prev_displacement) {
|
||||
ss->cache->prev_displacement = MEM_malloc_arrayN(
|
||||
totvert, sizeof(float[3]), "prev displacement");
|
||||
ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co");
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]);
|
||||
sub_v3_v3v3(ss->cache->prev_displacement[i],
|
||||
SCULPT_vertex_co_get(ss, i),
|
||||
ss->cache->limit_surface_co[i]);
|
||||
}
|
||||
}
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(
|
||||
0, totnode, &data, do_displacement_smear_store_prev_disp_task_cb_ex, &settings);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Draw Brush
|
||||
* \{ */
|
||||
|
||||
static void do_draw_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], offset, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float effective_normal[3];
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
mul_v3_v3fl(offset, effective_normal, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise
|
||||
* initialize before threads so they can do curve mapping. */
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.offset = offset,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], offset, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float effective_normal[3];
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
mul_v3_v3fl(offset, effective_normal, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise
|
||||
* initialize before threads so they can do curve mapping. */
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.offset = offset,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Topology Brush
|
||||
* \{ */
|
||||
|
||||
static void do_topology_slide_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float current_disp[3];
|
||||
float current_disp_norm[3];
|
||||
float final_disp[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
switch (brush->slide_deform_type) {
|
||||
case BRUSH_SLIDE_DEFORM_DRAG:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location);
|
||||
break;
|
||||
case BRUSH_SLIDE_DEFORM_PINCH:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, vd.co);
|
||||
break;
|
||||
case BRUSH_SLIDE_DEFORM_EXPAND:
|
||||
sub_v3_v3v3(current_disp, vd.co, ss->cache->location);
|
||||
break;
|
||||
}
|
||||
|
||||
normalize_v3_v3(current_disp_norm, current_disp);
|
||||
mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength);
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
|
||||
float vertex_disp[3];
|
||||
float vertex_disp_norm[3];
|
||||
sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co);
|
||||
normalize_v3_v3(vertex_disp_norm, vertex_disp);
|
||||
if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) {
|
||||
madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp));
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], final_disp, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_relax_vertex(SculptSession *ss,
|
||||
PBVHVertexIter *vd,
|
||||
float factor,
|
||||
bool filter_boundary_face_sets,
|
||||
float *r_final_pos)
|
||||
{
|
||||
float smooth_pos[3];
|
||||
float final_disp[3];
|
||||
float boundary_normal[3];
|
||||
int avg_count = 0;
|
||||
int neighbor_count = 0;
|
||||
zero_v3(smooth_pos);
|
||||
zero_v3(boundary_normal);
|
||||
const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index);
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) {
|
||||
neighbor_count++;
|
||||
if (!filter_boundary_face_sets ||
|
||||
(filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) {
|
||||
|
||||
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
|
||||
* position. */
|
||||
if (is_boundary) {
|
||||
if (!SCULPT_vertex_is_boundary(ss, ni.index)) {
|
||||
continue;
|
||||
}
|
||||
add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index));
|
||||
avg_count++;
|
||||
|
||||
/* Calculate a normal for the constraint plane using the edges of the boundary. */
|
||||
float to_neighbor[3];
|
||||
sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co);
|
||||
normalize_v3(to_neighbor);
|
||||
add_v3_v3(boundary_normal, to_neighbor);
|
||||
}
|
||||
else {
|
||||
add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index));
|
||||
avg_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
/* Don't modify corner vertices. */
|
||||
if (neighbor_count <= 2) {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avg_count > 0) {
|
||||
mul_v3_fl(smooth_pos, 1.0f / avg_count);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
float plane[4];
|
||||
float smooth_closest_plane[3];
|
||||
float vno[3];
|
||||
|
||||
if (is_boundary && avg_count == 2) {
|
||||
normalize_v3_v3(vno, boundary_normal);
|
||||
}
|
||||
else {
|
||||
SCULPT_vertex_normal_get(ss, vd->index, vno);
|
||||
}
|
||||
|
||||
if (is_zero_v3(vno)) {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
plane_from_point_normal_v3(plane, vd->co, vno);
|
||||
closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos);
|
||||
sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co);
|
||||
|
||||
mul_v3_fl(final_disp, factor);
|
||||
add_v3_v3v3(r_final_pos, vd->co, final_disp);
|
||||
}
|
||||
|
||||
static void do_topology_relax_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co);
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
if (ss->cache->alt_smooth) {
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_sculpt_plane(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3])
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) &&
|
||||
(SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) ||
|
||||
!(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) {
|
||||
switch (brush->sculpt_plane) {
|
||||
case SCULPT_DISP_DIR_VIEW:
|
||||
copy_v3_v3(r_area_no, ss->cache->true_view_normal);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_X:
|
||||
ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_Y:
|
||||
ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_Z:
|
||||
ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_AREA:
|
||||
calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal);
|
||||
normalize_v3(r_area_no);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* For flatten center. */
|
||||
/* Flatten center has not been calculated yet if we are not using the area normal. */
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) {
|
||||
calc_area_center(sd, ob, nodes, totnode, r_area_co);
|
||||
}
|
||||
|
||||
/* For area normal. */
|
||||
if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) &&
|
||||
(brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(ss->cache->sculpt_normal, r_area_no);
|
||||
}
|
||||
|
||||
/* For flatten center. */
|
||||
if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) &&
|
||||
(brush->flag & BRUSH_ORIGINAL_PLANE)) {
|
||||
copy_v3_v3(r_area_co, ss->cache->last_center);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(ss->cache->last_center, r_area_co);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* For area normal. */
|
||||
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
|
||||
|
||||
/* For flatten center. */
|
||||
copy_v3_v3(r_area_co, ss->cache->last_center);
|
||||
|
||||
/* For area normal. */
|
||||
flip_v3(r_area_no, ss->cache->mirror_symmetry_pass);
|
||||
|
||||
/* For flatten center. */
|
||||
flip_v3(r_area_co, ss->cache->mirror_symmetry_pass);
|
||||
|
||||
/* For area normal. */
|
||||
mul_m4_v3(ss->cache->symm_rot_mat, r_area_no);
|
||||
|
||||
/* For flatten center. */
|
||||
mul_m4_v3(ss->cache->symm_rot_mat, r_area_co);
|
||||
|
||||
/* Shift the plane for the current tile. */
|
||||
add_v3_v3(r_area_co, ss->cache->plane_offset);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Crease & Blob Brush
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB'
|
||||
*/
|
||||
static void do_crease_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
SculptProjectVector *spvc = data->spvc;
|
||||
const float flippedbstrength = data->flippedbstrength;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float val1[3];
|
||||
float val2[3];
|
||||
|
||||
/* First we pinch. */
|
||||
sub_v3_v3v3(val1, test.location, vd.co);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(val1, val1, ss->cache->view_normal);
|
||||
}
|
||||
|
||||
mul_v3_fl(val1, fade * flippedbstrength);
|
||||
|
||||
sculpt_project_v3(spvc, val1, val1);
|
||||
|
||||
/* Then we draw. */
|
||||
mul_v3_v3fl(val2, offset, fade);
|
||||
|
||||
add_v3_v3v3(proxy[vd.i], val1, val2);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
const Scene *scene = ss->cache->vc->scene;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
float bstrength = ss->cache->bstrength;
|
||||
float flippedbstrength, crease_correction;
|
||||
float brush_alpha;
|
||||
|
||||
SculptProjectVector spvc;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* We divide out the squared alpha and multiply by the squared crease
|
||||
* to give us the pinch strength. */
|
||||
crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor;
|
||||
brush_alpha = BKE_brush_alpha_get(scene, brush);
|
||||
if (brush_alpha > 0.0f) {
|
||||
crease_correction /= brush_alpha * brush_alpha;
|
||||
}
|
||||
|
||||
/* We always want crease to pinch or blob to relax even when draw is negative. */
|
||||
flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength :
|
||||
crease_correction * bstrength;
|
||||
|
||||
if (brush->sculpt_tool == SCULPT_TOOL_BLOB) {
|
||||
flippedbstrength *= -1.0f;
|
||||
}
|
||||
|
||||
/* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single
|
||||
* point. Without this we get a 'flat' surface surrounding the pinch. */
|
||||
sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.spvc = &spvc,
|
||||
.offset = offset,
|
||||
.flippedbstrength = flippedbstrength,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_pinch_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*stroke_xz)[3] = data->stroke_xz;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
float x_object_space[3];
|
||||
float z_object_space[3];
|
||||
copy_v3_v3(x_object_space, stroke_xz[0]);
|
||||
copy_v3_v3(z_object_space, stroke_xz[1]);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float disp_center[3];
|
||||
float x_disp[3];
|
||||
float z_disp[3];
|
||||
/* Calculate displacement from the vertex to the brush center. */
|
||||
sub_v3_v3v3(disp_center, test.location, vd.co);
|
||||
|
||||
/* Project the displacement into the X vector (aligned to the stroke). */
|
||||
mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space));
|
||||
|
||||
/* Project the displacement into the Z vector (aligned to the surface normal). */
|
||||
mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space));
|
||||
|
||||
/* Add the two projected vectors to calculate the final displacement.
|
||||
* The Y component is removed. */
|
||||
add_v3_v3v3(disp_center, x_disp, z_disp);
|
||||
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal);
|
||||
}
|
||||
mul_v3_v3fl(proxy[vd.i], disp_center, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float mat[4][4];
|
||||
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
/* delay the first daub because grab delta is not setup */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize `mat`. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], ss->cache->location);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
float stroke_xz[2][3];
|
||||
normalize_v3_v3(stroke_xz[0], mat[0]);
|
||||
normalize_v3_v3(stroke_xz[1], mat[2]);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.stroke_xz = stroke_xz,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_grab_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE;
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
if (grab_silhouette) {
|
||||
float silhouette_test_dir[3];
|
||||
normalize_v3_v3(silhouette_test_dir, grab_delta);
|
||||
if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) {
|
||||
mul_v3_fl(silhouette_test_dir, -1.0f);
|
||||
}
|
||||
float vno[3];
|
||||
normal_short_to_float_v3(vno, orig_data.no);
|
||||
fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f);
|
||||
}
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
const float *location = ss->cache->location;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
float dir;
|
||||
if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) {
|
||||
dir = 1.0f;
|
||||
}
|
||||
else {
|
||||
dir = -1.0f;
|
||||
}
|
||||
|
||||
if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) {
|
||||
int symm = ss->cache->mirror_symmetry_pass;
|
||||
if (ELEM(symm, 1, 2, 4, 7)) {
|
||||
dir = -dir;
|
||||
}
|
||||
}
|
||||
|
||||
KelvinletParams params;
|
||||
float force = len_v3(grab_delta) * dir * bstrength;
|
||||
BKE_kelvinlet_init_params(
|
||||
¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
float final_disp[3];
|
||||
switch (brush->elastic_deform_type) {
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB:
|
||||
BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: {
|
||||
BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
}
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: {
|
||||
BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
}
|
||||
case BRUSH_ELASTIC_DEFORM_SCALE:
|
||||
BKE_kelvinlet_scale(
|
||||
final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm);
|
||||
break;
|
||||
case BRUSH_ELASTIC_DEFORM_TWIST:
|
||||
BKE_kelvinlet_twist(
|
||||
final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm);
|
||||
break;
|
||||
}
|
||||
|
||||
if (vd.mask) {
|
||||
mul_v3_fl(final_disp, 1.0f - *vd.mask);
|
||||
}
|
||||
|
||||
mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index));
|
||||
|
||||
copy_v3_v3(proxy[vd.i], final_disp);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3])
|
||||
{
|
||||
@@ -4257,7 +2859,7 @@ void SCULPT_calc_brush_plane(
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_AREA:
|
||||
calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
|
||||
SCULPT_calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal);
|
||||
normalize_v3(r_area_no);
|
||||
@@ -4271,7 +2873,7 @@ void SCULPT_calc_brush_plane(
|
||||
/* For flatten center. */
|
||||
/* Flatten center has not been calculated yet if we are not using the area normal. */
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) {
|
||||
calc_area_center(sd, ob, nodes, totnode, r_area_co);
|
||||
SCULPT_calc_area_center(sd, ob, nodes, totnode, r_area_co);
|
||||
}
|
||||
|
||||
/* For area normal. */
|
||||
@@ -4316,564 +2918,12 @@ void SCULPT_calc_brush_plane(
|
||||
}
|
||||
}
|
||||
|
||||
static void do_nudge_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *cono = data->cono;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], cono, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
float tmp[3], cono[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta);
|
||||
cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.cono = cono,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
SculptProjectVector *spvc = data->spvc;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
const bool do_rake_rotation = ss->cache->is_rake_rotation_valid;
|
||||
const bool do_pinch = (brush->crease_pinch_factor != 0.5f);
|
||||
const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) *
|
||||
(len_v3(grab_delta) / ss->cache->radius)) :
|
||||
0.0f;
|
||||
|
||||
const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
KelvinletParams params;
|
||||
BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!do_elastic && !sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float fade;
|
||||
if (do_elastic) {
|
||||
fade = 1.0f;
|
||||
}
|
||||
else {
|
||||
fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
}
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
|
||||
|
||||
/* Negative pinch will inflate, helps maintain volume. */
|
||||
if (do_pinch) {
|
||||
float delta_pinch_init[3], delta_pinch[3];
|
||||
|
||||
sub_v3_v3v3(delta_pinch, vd.co, test.location);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal);
|
||||
}
|
||||
|
||||
/* Important to calculate based on the grabbed location
|
||||
* (intentionally ignore fade here). */
|
||||
add_v3_v3(delta_pinch, grab_delta);
|
||||
|
||||
sculpt_project_v3(spvc, delta_pinch, delta_pinch);
|
||||
|
||||
copy_v3_v3(delta_pinch_init, delta_pinch);
|
||||
|
||||
float pinch_fade = pinch * fade;
|
||||
/* When reducing, scale reduction back by how close to the center we are,
|
||||
* so we don't pinch into nothingness. */
|
||||
if (pinch > 0.0f) {
|
||||
/* Square to have even less impact for close vertices. */
|
||||
pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius));
|
||||
}
|
||||
mul_v3_fl(delta_pinch, 1.0f + pinch_fade);
|
||||
sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch);
|
||||
add_v3_v3(proxy[vd.i], delta_pinch);
|
||||
}
|
||||
|
||||
if (do_rake_rotation) {
|
||||
float delta_rotate[3];
|
||||
sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate);
|
||||
add_v3_v3(proxy[vd.i], delta_rotate);
|
||||
}
|
||||
|
||||
if (do_elastic) {
|
||||
float disp[3];
|
||||
BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]);
|
||||
mul_v3_fl(disp, bstrength * 20.0f);
|
||||
if (vd.mask) {
|
||||
mul_v3_fl(disp, 1.0f - *vd.mask);
|
||||
}
|
||||
mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index));
|
||||
copy_v3_v3(proxy[vd.i], disp);
|
||||
}
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
float grab_delta[3];
|
||||
|
||||
SculptProjectVector spvc;
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (bstrength < 0.0f) {
|
||||
negate_v3(grab_delta);
|
||||
}
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
/* Optionally pinch while painting. */
|
||||
if (brush->crease_pinch_factor != 0.5f) {
|
||||
sculpt_project_v3_cache_init(&spvc, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.spvc = &spvc,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_thumb_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *cono = data->cono;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], cono, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
float tmp[3], cono[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta);
|
||||
cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.cono = cono,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_rotate_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float angle = data->angle;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
float vec[3], rot[3][3];
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
sub_v3_v3v3(vec, orig_data.co, ss->cache->location);
|
||||
axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade);
|
||||
mul_v3_m3v3(proxy[vd.i], rot, vec);
|
||||
add_v3_v3(proxy[vd.i], ss->cache->location);
|
||||
sub_v3_v3(proxy[vd.i], orig_data.co);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1};
|
||||
const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass];
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.angle = angle,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_layer_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
Sculpt *sd = data->sd;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
const int vi = vd.index;
|
||||
float *disp_factor;
|
||||
if (use_persistent_base) {
|
||||
disp_factor = &ss->persistent_base[vi].disp;
|
||||
}
|
||||
else {
|
||||
disp_factor = &ss->cache->layer_displacement_factor[vi];
|
||||
}
|
||||
|
||||
/* When using persistent base, the layer brush (holding Control) invert mode resets the
|
||||
* height of the layer to 0. This makes possible to clean edges of previously added layers
|
||||
* on top of the base. */
|
||||
/* The main direction of the layers is inverted using the regular brush strength with the
|
||||
* brush direction property. */
|
||||
if (use_persistent_base && ss->cache->invert) {
|
||||
(*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) *
|
||||
((*disp_factor) > 0.0f ? -1.0f : 1.0f);
|
||||
}
|
||||
else {
|
||||
(*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor));
|
||||
}
|
||||
if (vd.mask) {
|
||||
const float clamp_mask = 1.0f - *vd.mask;
|
||||
*disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask);
|
||||
}
|
||||
else {
|
||||
*disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
float final_co[3];
|
||||
float normal[3];
|
||||
|
||||
if (use_persistent_base) {
|
||||
SCULPT_vertex_persistent_normal_get(ss, vi, normal);
|
||||
mul_v3_fl(normal, brush->height);
|
||||
madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor);
|
||||
}
|
||||
else {
|
||||
normal_short_to_float_v3(normal, orig_data.no);
|
||||
mul_v3_fl(normal, brush->height);
|
||||
madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor);
|
||||
}
|
||||
|
||||
float vdisp[3];
|
||||
sub_v3_v3v3(vdisp, final_co, vd.co);
|
||||
mul_v3_fl(vdisp, fabsf(fade));
|
||||
add_v3_v3v3(final_co, vd.co, vdisp);
|
||||
|
||||
SCULPT_clip(sd, ss, vd.co, final_co);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (ss->cache->layer_displacement_factor == NULL) {
|
||||
ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss),
|
||||
"layer displacement factor");
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_inflate_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float val[3];
|
||||
|
||||
if (vd.fno) {
|
||||
copy_v3_v3(val, vd.fno);
|
||||
}
|
||||
else {
|
||||
normal_short_to_float_v3(val, vd.no);
|
||||
}
|
||||
|
||||
mul_v3_fl(val, fade * ss->cache->radius);
|
||||
mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3])
|
||||
{
|
||||
return (!(brush->flag & BRUSH_PLANE_TRIM) ||
|
||||
((dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared)));
|
||||
}
|
||||
|
||||
static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip)
|
||||
{
|
||||
float d = plane_point_side_v3(plane, co);
|
||||
if (flip) {
|
||||
d = -d;
|
||||
}
|
||||
return d <= 0.0f;
|
||||
}
|
||||
|
||||
int SCULPT_plane_point_side(const float co[3], const float plane[4])
|
||||
{
|
||||
float d = plane_point_side_v3(plane, co);
|
||||
@@ -4893,807 +2943,6 @@ float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss)
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void do_flatten_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
float intr[3];
|
||||
float val[3];
|
||||
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
float displace;
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Clay Brush
|
||||
* \{ */
|
||||
|
||||
typedef struct ClaySampleData {
|
||||
float plane_dist[2];
|
||||
} ClaySampleData;
|
||||
|
||||
static void calc_clay_surface_task_cb(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
ClaySampleData *csd = tls->userdata_chunk;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
float plane[4];
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, brush->falloff_shape);
|
||||
|
||||
/* Apply the brush normal radius to the test before sampling. */
|
||||
float test_radius = sqrtf(test.radius_squared);
|
||||
test_radius *= brush->normal_radius_factor;
|
||||
test.radius_squared = test_radius * test_radius;
|
||||
plane_from_point_normal_v3(plane, area_co, area_no);
|
||||
|
||||
if (is_zero_v4(plane)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float plane_dist = dist_signed_to_plane_v3(vd.co, plane);
|
||||
float plane_dist_abs = fabsf(plane_dist);
|
||||
if (plane_dist > 0.0f) {
|
||||
csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs);
|
||||
}
|
||||
else {
|
||||
csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata),
|
||||
void *__restrict chunk_join,
|
||||
void *__restrict chunk)
|
||||
{
|
||||
ClaySampleData *join = chunk_join;
|
||||
ClaySampleData *csd = chunk;
|
||||
join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]);
|
||||
join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]);
|
||||
}
|
||||
|
||||
static void do_clay_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = fabsf(ss->cache->bstrength);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = fabsf(ss->cache->radius);
|
||||
const float initial_radius = fabsf(ss->cache->initial_radius);
|
||||
bool flip = ss->cache->bstrength < 0.0f;
|
||||
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
float displace;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SculptThreadedTaskData sample_data = {
|
||||
.sd = NULL,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.totnode = totnode,
|
||||
.area_no = area_no,
|
||||
.area_co = ss->cache->location,
|
||||
};
|
||||
|
||||
ClaySampleData csd = {{0}};
|
||||
|
||||
TaskParallelSettings sample_settings;
|
||||
BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode);
|
||||
sample_settings.func_reduce = calc_clay_surface_reduce;
|
||||
sample_settings.userdata_chunk = &csd;
|
||||
sample_settings.userdata_chunk_size = sizeof(ClaySampleData);
|
||||
|
||||
BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings);
|
||||
|
||||
float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]);
|
||||
d_offset = min_ff(radius, d_offset);
|
||||
d_offset = d_offset / radius;
|
||||
d_offset = 1.0f - d_offset;
|
||||
displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f)));
|
||||
if (flip) {
|
||||
displace = -displace;
|
||||
}
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
copy_v3_v3(area_co, ss->cache->location);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*mat)[4] = data->mat;
|
||||
const float *area_no_sp = data->area_no_sp;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptBrushTest test;
|
||||
float(*proxy)[3];
|
||||
const bool flip = (ss->cache->bstrength < 0.0f);
|
||||
const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SCULPT_brush_test_init(ss, &test);
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!plane_point_side_flip(vd.co, test.plane_tool, flip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
/* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
ss->cache->radius * test.dist,
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const bool flip = (ss->cache->bstrength < 0.0f);
|
||||
const float radius = flip ? -ss->cache->radius : ss->cache->radius;
|
||||
const float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
const float displace = radius * (0.18f + offset);
|
||||
|
||||
/* The sculpt-plane normal (whatever its set to). */
|
||||
float area_no_sp[3];
|
||||
|
||||
/* Geometry normal */
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float temp[3];
|
||||
float mat[4][4];
|
||||
float scale[4][4];
|
||||
float tmat[4][4];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
|
||||
SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(area_no, area_no_sp);
|
||||
}
|
||||
|
||||
/* Delay the first daub because grab delta is not setup. */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
/* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform the
|
||||
* vertices. When in Add mode, vertices that are below the plane and inside the cube are move
|
||||
* towards the plane. In this situation, there may be cases where a vertex is outside the cube
|
||||
* but below the plane, so won't be deformed, causing artifacts. In order to prevent these
|
||||
* artifacts, this displaces the test cube space in relation to the plane in order to
|
||||
* deform more vertices that may be below it. */
|
||||
/* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were set
|
||||
* by doing multiple tests using the default "Clay Strips" brush preset. */
|
||||
float area_co_displaced[3];
|
||||
madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f);
|
||||
|
||||
/* Initialize brush local-space matrix. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], area_co_displaced);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
/* Scale brush local space matrix. */
|
||||
scale_m4_fl(scale, ss->cache->radius);
|
||||
mul_m4_m4m4(tmat, mat, scale);
|
||||
|
||||
/* Deform the local space in Z to scale the test cube. As the test cube does not have falloff in
|
||||
* Z this does not produce artifacts in the falloff cube and allows to deform extra vertices
|
||||
* during big deformation while keeping the surface as uniform as possible. */
|
||||
mul_v3_fl(tmat[2], 1.25f);
|
||||
|
||||
invert_m4_m4(mat, tmat);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no_sp = area_no_sp,
|
||||
.area_co = area_co,
|
||||
.mat = mat,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_fill_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
|
||||
float displace;
|
||||
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_scrape_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SCULPT_plane_point_side(vd.co, test.plane_tool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
|
||||
float displace;
|
||||
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = -radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Clay Thumb Brush
|
||||
* \{ */
|
||||
|
||||
static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*mat)[4] = data->mat;
|
||||
const float *area_no_sp = data->area_no_sp;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = data->clay_strength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
float plane_tilt[4];
|
||||
float normal_tilt[3];
|
||||
float imat[4][4];
|
||||
|
||||
invert_m4_m4(imat, mat);
|
||||
rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle));
|
||||
|
||||
/* Plane aligned to the geometry normal (back part of the brush). */
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp);
|
||||
/* Tilted plane (front part of the brush). */
|
||||
plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
float local_co[3];
|
||||
mul_v3_m4v3(local_co, mat, vd.co);
|
||||
float intr[3], intr_tilt[3];
|
||||
float val[3];
|
||||
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co);
|
||||
|
||||
/* Mix the deformation of the aligned and the tilted plane based on the brush space vertex
|
||||
* coordinates. */
|
||||
/* We can also control the mix with a curve if it produces noticeable artifacts in the center
|
||||
* of the brush. */
|
||||
const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f;
|
||||
interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix);
|
||||
sub_v3_v3v3(val, intr_tilt, vd.co);
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static float sculpt_clay_thumb_get_stabilized_pressure(StrokeCache *cache)
|
||||
{
|
||||
float final_pressure = 0.0f;
|
||||
for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) {
|
||||
final_pressure += cache->clay_pressure_stabilizer[i];
|
||||
}
|
||||
return final_pressure / SCULPT_CLAY_STABILIZER_LEN;
|
||||
}
|
||||
|
||||
static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
const float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
const float displace = radius * (0.25f + offset);
|
||||
|
||||
/* Sampled geometry normal and area center. */
|
||||
float area_no_sp[3];
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float temp[3];
|
||||
float mat[4][4];
|
||||
float scale[4][4];
|
||||
float tmat[4][4];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
|
||||
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(area_no, area_no_sp);
|
||||
}
|
||||
|
||||
/* Delay the first daub because grab delta is not setup. */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
ss->cache->clay_thumb_front_angle = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to the
|
||||
* stroke. */
|
||||
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
|
||||
ss->cache->clay_thumb_front_angle += 0.8f;
|
||||
ss->cache->clay_thumb_front_angle = clamp_f(ss->cache->clay_thumb_front_angle, 0.0f, 60.0f);
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Displace the brush planes. */
|
||||
copy_v3_v3(area_co, ss->cache->location);
|
||||
mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
/* Initialize brush local-space matrix. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], ss->cache->location);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
/* Scale brush local space matrix. */
|
||||
scale_m4_fl(scale, ss->cache->radius);
|
||||
mul_m4_m4m4(tmat, mat, scale);
|
||||
invert_m4_m4(mat, tmat);
|
||||
|
||||
float clay_strength = ss->cache->bstrength *
|
||||
sculpt_clay_thumb_get_stabilized_pressure(ss->cache);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no_sp = area_no_sp,
|
||||
.area_co = ss->cache->location,
|
||||
.mat = mat,
|
||||
.clay_strength = clay_strength,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -6036,7 +3285,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
||||
/* Apply one type of brush action. */
|
||||
switch (brush->sculpt_tool) {
|
||||
case SCULPT_TOOL_DRAW:
|
||||
do_draw_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_draw_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_SMOOTH:
|
||||
if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
|
||||
@@ -6047,80 +3296,80 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
||||
}
|
||||
break;
|
||||
case SCULPT_TOOL_CREASE:
|
||||
do_crease_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_crease_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_BLOB:
|
||||
do_crease_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_crease_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_PINCH:
|
||||
do_pinch_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_pinch_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_INFLATE:
|
||||
do_inflate_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_inflate_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_GRAB:
|
||||
do_grab_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_grab_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_ROTATE:
|
||||
do_rotate_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_rotate_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_SNAKE_HOOK:
|
||||
do_snake_hook_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_snake_hook_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_NUDGE:
|
||||
do_nudge_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_nudge_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_THUMB:
|
||||
do_thumb_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_thumb_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_LAYER:
|
||||
do_layer_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_layer_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_FLATTEN:
|
||||
do_flatten_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_flatten_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_CLAY:
|
||||
do_clay_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_clay_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_CLAY_STRIPS:
|
||||
do_clay_strips_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_clay_strips_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_MULTIPLANE_SCRAPE:
|
||||
SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_CLAY_THUMB:
|
||||
do_clay_thumb_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_clay_thumb_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_FILL:
|
||||
if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) {
|
||||
do_scrape_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_scrape_brush(sd, ob, nodes, totnode);
|
||||
}
|
||||
else {
|
||||
do_fill_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_fill_brush(sd, ob, nodes, totnode);
|
||||
}
|
||||
break;
|
||||
case SCULPT_TOOL_SCRAPE:
|
||||
if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) {
|
||||
do_fill_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_fill_brush(sd, ob, nodes, totnode);
|
||||
}
|
||||
else {
|
||||
do_scrape_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_scrape_brush(sd, ob, nodes, totnode);
|
||||
}
|
||||
break;
|
||||
case SCULPT_TOOL_MASK:
|
||||
do_mask_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_mask_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_POSE:
|
||||
SCULPT_do_pose_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_DRAW_SHARP:
|
||||
do_draw_sharp_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_draw_sharp_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_ELASTIC_DEFORM:
|
||||
do_elastic_deform_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_elastic_deform_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_SLIDE_RELAX:
|
||||
do_slide_relax_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_slide_relax_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_BOUNDARY:
|
||||
SCULPT_do_boundary_brush(sd, ob, nodes, totnode);
|
||||
@@ -6132,10 +3381,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
||||
SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_DISPLACEMENT_ERASER:
|
||||
do_displacement_eraser_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_displacement_eraser_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_DISPLACEMENT_SMEAR:
|
||||
do_displacement_smear_brush(sd, ob, nodes, totnode);
|
||||
SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case SCULPT_TOOL_PAINT:
|
||||
SCULPT_do_paint_brush(sd, ob, nodes, totnode);
|
||||
@@ -6157,7 +3406,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
|
||||
}
|
||||
|
||||
if (sculpt_brush_use_topology_rake(ss, brush)) {
|
||||
bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor);
|
||||
SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor);
|
||||
}
|
||||
|
||||
/* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
|
||||
@@ -6946,7 +4195,7 @@ static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, flo
|
||||
case SCULPT_TOOL_CLAY_STRIPS:
|
||||
return max_ff(initial_size * 0.30f, initial_size * powf(cache->pressure, 1.5f));
|
||||
case SCULPT_TOOL_CLAY_THUMB: {
|
||||
float clay_stabilized_pressure = sculpt_clay_thumb_get_stabilized_pressure(cache);
|
||||
float clay_stabilized_pressure = SCULPT_clay_thumb_get_stabilized_pressure(cache);
|
||||
return initial_size * clay_stabilized_pressure;
|
||||
}
|
||||
default:
|
||||
@@ -8131,7 +5380,7 @@ static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op)
|
||||
sculpt_brush_exit_tex(sd);
|
||||
}
|
||||
|
||||
static void SCULPT_OT_brush_stroke(wmOperatorType *ot)
|
||||
void SCULPT_OT_brush_stroke(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Sculpt";
|
||||
@@ -8159,677 +5408,6 @@ static void SCULPT_OT_brush_stroke(wmOperatorType *ot)
|
||||
"Clicks on the background do not start the stroke");
|
||||
}
|
||||
|
||||
/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */
|
||||
|
||||
static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
if (!ss) {
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
|
||||
|
||||
MEM_SAFE_FREE(ss->persistent_base);
|
||||
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert,
|
||||
"layer persistent base");
|
||||
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i));
|
||||
SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no);
|
||||
ss->persistent_base[i].disp = 0.0f;
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_set_persistent_base(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Set Persistent Base";
|
||||
ot->idname = "SCULPT_OT_set_persistent_base";
|
||||
ot->description = "Reset the copy of the mesh that is being sculpted on";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_set_persistent_base_exec;
|
||||
ot->poll = SCULPT_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/************************* SCULPT_OT_optimize *************************/
|
||||
|
||||
static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
SCULPT_pbvh_clear(ob);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
/* The BVH gets less optimal more quickly with dynamic topology than
|
||||
* regular sculpting. There is no doubt more clever stuff we can do to
|
||||
* optimize it on the fly, but for now this gives the user a nicer way
|
||||
* to recalculate it than toggling modes. */
|
||||
static void SCULPT_OT_optimize(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Rebuild BVH";
|
||||
ot->idname = "SCULPT_OT_optimize";
|
||||
ot->description = "Recalculate the sculpt BVH to improve performance";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_optimize_exec;
|
||||
ot->poll = SCULPT_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/********************* Dynamic topology symmetrize ********************/
|
||||
|
||||
static bool sculpt_no_multires_poll(bContext *C)
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) {
|
||||
return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
const Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
||||
SculptSession *ss = ob->sculpt;
|
||||
PBVH *pbvh = ss->pbvh;
|
||||
const float dist = RNA_float_get(op->ptr, "merge_tolerance");
|
||||
|
||||
if (!pbvh) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
switch (BKE_pbvh_type(pbvh)) {
|
||||
case PBVH_BMESH:
|
||||
/* Dyntopo Symmetrize. */
|
||||
|
||||
/* To simplify undo for symmetrize, all BMesh elements are logged
|
||||
* as deleted, then after symmetrize operation all BMesh elements
|
||||
* are logged as added (as opposed to attempting to store just the
|
||||
* parts that symmetrize modifies). */
|
||||
SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize");
|
||||
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE);
|
||||
BM_log_before_all_removed(ss->bm, ss->bm_log);
|
||||
|
||||
BM_mesh_toolflags_set(ss->bm, true);
|
||||
|
||||
/* Symmetrize and re-triangulate. */
|
||||
BMO_op_callf(ss->bm,
|
||||
(BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
|
||||
"symmetrize input=%avef direction=%i dist=%f use_shapekey=%b",
|
||||
sd->symmetrize_direction,
|
||||
dist,
|
||||
true);
|
||||
SCULPT_dynamic_topology_triangulate(ss->bm);
|
||||
|
||||
/* Bisect operator flags edges (keep tags clean for edge queue). */
|
||||
BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false);
|
||||
|
||||
BM_mesh_toolflags_set(ss->bm, false);
|
||||
|
||||
/* Finish undo. */
|
||||
BM_log_all_added(ss->bm, ss->bm_log);
|
||||
SCULPT_undo_push_end();
|
||||
|
||||
break;
|
||||
case PBVH_FACES:
|
||||
/* Mesh Symmetrize. */
|
||||
ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize");
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist);
|
||||
|
||||
ED_sculpt_undo_geometry_end(ob);
|
||||
BKE_mesh_calc_normals(ob->data);
|
||||
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
|
||||
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Redraw. */
|
||||
SCULPT_pbvh_clear(ob);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_symmetrize(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Symmetrize";
|
||||
ot->idname = "SCULPT_OT_symmetrize";
|
||||
ot->description = "Symmetrize the topology modifications";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_symmetrize_exec;
|
||||
ot->poll = sculpt_no_multires_poll;
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"merge_tolerance",
|
||||
0.001f,
|
||||
0.0f,
|
||||
FLT_MAX,
|
||||
"Merge Distance",
|
||||
"Distance within which symmetrical vertices are merged",
|
||||
0.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
/**** Toggle operator for turning sculpt mode on or off ****/
|
||||
|
||||
static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
|
||||
{
|
||||
/* Create persistent sculpt mode data. */
|
||||
BKE_sculpt_toolsettings_data_ensure(scene);
|
||||
|
||||
/* Create sculpt mode session data. */
|
||||
if (ob->sculpt != NULL) {
|
||||
BKE_sculptsession_free(ob);
|
||||
}
|
||||
ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session");
|
||||
ob->sculpt->mode_type = OB_MODE_SCULPT;
|
||||
|
||||
BKE_sculpt_ensure_orig_mesh_data(scene, ob);
|
||||
|
||||
BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
|
||||
|
||||
/* This function expects a fully evaluated depsgraph. */
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
|
||||
|
||||
/* Here we can detect geometry that was just added to Sculpt Mode as it has the
|
||||
* SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */
|
||||
/* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not
|
||||
* initialized, which is used is some operators that modify the mesh topology to perform certain
|
||||
* actions in the new polys. After these operations are finished, all polys should have a valid
|
||||
* face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility
|
||||
* correctly. */
|
||||
/* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new
|
||||
* objects, like moving the transform pivot position to the new area or masking existing
|
||||
* geometry. */
|
||||
SculptSession *ss = ob->sculpt;
|
||||
const int new_face_set = SCULPT_face_set_next_available_get(ss);
|
||||
for (int i = 0; i < ss->totfaces; i++) {
|
||||
if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) {
|
||||
ss->face_sets[i] = new_face_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_enter_ex(Main *bmain,
|
||||
Depsgraph *depsgraph,
|
||||
Scene *scene,
|
||||
Object *ob,
|
||||
const bool force_dyntopo,
|
||||
ReportList *reports)
|
||||
{
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *me = BKE_mesh_from_object(ob);
|
||||
|
||||
/* Enter sculpt mode. */
|
||||
ob->mode |= mode_flag;
|
||||
|
||||
sculpt_init_session(bmain, depsgraph, scene, ob);
|
||||
|
||||
if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f &&
|
||||
fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f)) {
|
||||
BKE_report(
|
||||
reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable");
|
||||
}
|
||||
else if (is_negative_m4(ob->obmat)) {
|
||||
BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable");
|
||||
}
|
||||
|
||||
Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT);
|
||||
BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT);
|
||||
|
||||
paint_cursor_start(paint, SCULPT_mode_poll_view3d);
|
||||
|
||||
/* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes,
|
||||
* As long as no data was added that is not supported. */
|
||||
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
|
||||
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
|
||||
|
||||
const char *message_unsupported = NULL;
|
||||
if (me->totloop != me->totpoly * 3) {
|
||||
message_unsupported = TIP_("non-triangle face");
|
||||
}
|
||||
else if (mmd != NULL) {
|
||||
message_unsupported = TIP_("multi-res modifier");
|
||||
}
|
||||
else {
|
||||
enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob);
|
||||
if (flag == 0) {
|
||||
/* pass */
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_VDATA) {
|
||||
message_unsupported = TIP_("vertex data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_EDATA) {
|
||||
message_unsupported = TIP_("edge data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_LDATA) {
|
||||
message_unsupported = TIP_("face data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_MODIFIER) {
|
||||
message_unsupported = TIP_("constructive modifier");
|
||||
}
|
||||
else {
|
||||
BLI_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if ((message_unsupported == NULL) || force_dyntopo) {
|
||||
/* Needed because we may be entering this mode before the undo system loads. */
|
||||
wmWindowManager *wm = bmain->wm.first;
|
||||
bool has_undo = wm->undo_stack != NULL;
|
||||
/* Undo push is needed to prevent memory leak. */
|
||||
if (has_undo) {
|
||||
SCULPT_undo_push_begin(ob, "Dynamic topology enable");
|
||||
}
|
||||
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob);
|
||||
if (has_undo) {
|
||||
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN);
|
||||
SCULPT_undo_push_end();
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_reportf(
|
||||
reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported);
|
||||
me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush object mode. */
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, ReportList *reports)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
|
||||
{
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *me = BKE_mesh_from_object(ob);
|
||||
|
||||
multires_flush_sculpt_updates(ob);
|
||||
|
||||
/* Not needed for now. */
|
||||
#if 0
|
||||
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
|
||||
const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd);
|
||||
#endif
|
||||
|
||||
/* Always for now, so leaving sculpt mode always ensures scene is in
|
||||
* a consistent state. */
|
||||
if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) {
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
|
||||
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
|
||||
/* Dynamic topology must be disabled before exiting sculpt
|
||||
* mode to ensure the undo stack stays in a consistent
|
||||
* state. */
|
||||
sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob);
|
||||
|
||||
/* Store so we know to re-enable when entering sculpt mode. */
|
||||
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
|
||||
}
|
||||
|
||||
/* Leave sculpt mode. */
|
||||
ob->mode &= ~mode_flag;
|
||||
|
||||
BKE_sculptsession_free(ob);
|
||||
|
||||
paint_cursor_delete_textures();
|
||||
|
||||
/* Never leave derived meshes behind. */
|
||||
BKE_object_free_derived_caches(ob);
|
||||
|
||||
/* Flush object mode. */
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
|
||||
}
|
||||
|
||||
static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
struct wmMsgBus *mbus = CTX_wm_message_bus(C);
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *ts = scene->toolsettings;
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
const bool is_mode_set = (ob->mode & mode_flag) != 0;
|
||||
|
||||
if (!is_mode_set) {
|
||||
if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_mode_set) {
|
||||
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
|
||||
}
|
||||
else {
|
||||
if (depsgraph) {
|
||||
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
||||
}
|
||||
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports);
|
||||
BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint);
|
||||
|
||||
if (ob->mode & mode_flag) {
|
||||
Mesh *me = ob->data;
|
||||
/* Dyntopo adds its own undo step. */
|
||||
if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) {
|
||||
/* Without this the memfile undo step is used,
|
||||
* while it works it causes lag when undoing the first undo step, see T71564. */
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
if (wm->op_undo_depth <= 1) {
|
||||
SCULPT_undo_push_begin(ob, op->type->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene);
|
||||
|
||||
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
|
||||
|
||||
WM_toolsystem_update_from_context_view3d(C);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Sculpt Mode";
|
||||
ot->idname = "SCULPT_OT_sculptmode_toggle";
|
||||
ot->description = "Toggle sculpt mode in 3D view";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_mode_toggle_exec;
|
||||
ot->poll = ED_operator_object_active_editable_mesh;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ss->preview_vert_index_count = 0;
|
||||
int totpoints = 0;
|
||||
|
||||
/* This function is called from the cursor drawing code, so the PBVH may not be build yet. */
|
||||
if (!ss->pbvh) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ss->deform_modifiers_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
|
||||
|
||||
if (!ss->pmap) {
|
||||
return;
|
||||
}
|
||||
|
||||
float brush_co[3];
|
||||
copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss));
|
||||
|
||||
BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices");
|
||||
|
||||
/* Assuming an average of 6 edges per vertex in a triangulated mesh. */
|
||||
const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2;
|
||||
|
||||
if (ss->preview_vert_index_list == NULL) {
|
||||
ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines");
|
||||
}
|
||||
|
||||
GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int));
|
||||
int active_v = SCULPT_active_vertex_get(ss);
|
||||
BLI_gsqueue_push(not_visited_vertices, &active_v);
|
||||
|
||||
while (!BLI_gsqueue_is_empty(not_visited_vertices)) {
|
||||
int from_v;
|
||||
BLI_gsqueue_pop(not_visited_vertices, &from_v);
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) {
|
||||
if (totpoints + (ni.size * 2) < max_preview_vertices) {
|
||||
int to_v = ni.index;
|
||||
ss->preview_vert_index_list[totpoints] = from_v;
|
||||
totpoints++;
|
||||
ss->preview_vert_index_list[totpoints] = to_v;
|
||||
totpoints++;
|
||||
if (BLI_BITMAP_TEST(visited_vertices, to_v)) {
|
||||
continue;
|
||||
}
|
||||
BLI_BITMAP_ENABLE(visited_vertices, to_v);
|
||||
const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v);
|
||||
if (len_squared_v3v3(brush_co, co) < radius * radius) {
|
||||
BLI_gsqueue_push(not_visited_vertices, &to_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
}
|
||||
|
||||
BLI_gsqueue_free(not_visited_vertices);
|
||||
|
||||
MEM_freeN(visited_vertices);
|
||||
|
||||
ss->preview_vert_index_count = totpoints;
|
||||
}
|
||||
|
||||
static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ID *data;
|
||||
data = ob->data;
|
||||
if (data && ID_IS_LINKED(data)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (ob->type != OB_MESH) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL);
|
||||
if (mloopcol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n);
|
||||
|
||||
const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR);
|
||||
if (MPropCol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n);
|
||||
|
||||
MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP);
|
||||
MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY);
|
||||
|
||||
for (int i = 0; i < mesh->totpoly; i++) {
|
||||
MPoly *c_poly = &polys[i];
|
||||
for (int j = 0; j < c_poly->totloop; j++) {
|
||||
int loop_index = c_poly->loopstart + j;
|
||||
MLoop *c_loop = &loops[c_poly->loopstart + j];
|
||||
float srgb_color[4];
|
||||
linearrgb_to_srgb_v4(srgb_color, vertcols[c_loop->v].color);
|
||||
loopcols[loop_index].r = (char)(srgb_color[0] * 255);
|
||||
loopcols[loop_index].g = (char)(srgb_color[1] * 255);
|
||||
loopcols[loop_index].b = (char)(srgb_color[2] * 255);
|
||||
loopcols[loop_index].a = (char)(srgb_color[3] * 255);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Sculpt Vertex Color to Vertex Color";
|
||||
ot->description = "Copy the Sculpt Vertex Color to a regular color layer";
|
||||
ot->idname = "SCULPT_OT_vertex_to_loop_colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
ot->exec = vertex_to_loop_colors_exec;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ID *data;
|
||||
data = ob->data;
|
||||
if (data && ID_IS_LINKED(data)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (ob->type != OB_MESH) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL);
|
||||
if (mloopcol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n);
|
||||
|
||||
const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR);
|
||||
if (MPropCol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n);
|
||||
|
||||
MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP);
|
||||
MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY);
|
||||
|
||||
for (int i = 0; i < mesh->totpoly; i++) {
|
||||
MPoly *c_poly = &polys[i];
|
||||
for (int j = 0; j < c_poly->totloop; j++) {
|
||||
int loop_index = c_poly->loopstart + j;
|
||||
MLoop *c_loop = &loops[c_poly->loopstart + j];
|
||||
vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f);
|
||||
vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f);
|
||||
vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f);
|
||||
vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f);
|
||||
srgb_to_linearrgb_v4(vertcols[c_loop->v].color, vertcols[c_loop->v].color);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Vertex Color to Sculpt Vertex Color";
|
||||
ot->description = "Copy the active loop color layer to the vertex color";
|
||||
ot->idname = "SCULPT_OT_loop_to_vertex_colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
ot->exec = loop_to_vertex_colors_exec;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int sculpt_sample_color_invoke(bContext *C,
|
||||
wmOperator *UNUSED(op),
|
||||
const wmEvent *UNUSED(e))
|
||||
{
|
||||
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
int active_vertex = SCULPT_active_vertex_get(ss);
|
||||
const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex);
|
||||
if (!active_vertex_color) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
float color_srgb[3];
|
||||
copy_v3_v3(color_srgb, active_vertex_color);
|
||||
IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb);
|
||||
BKE_brush_color_set(scene, brush, color_srgb);
|
||||
|
||||
WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_sample_color(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Sample Color";
|
||||
ot->idname = "SCULPT_OT_sample_color";
|
||||
ot->description = "Sample the vertex color of the active vertex";
|
||||
|
||||
/* api callbacks */
|
||||
ot->invoke = sculpt_sample_color_invoke;
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
}
|
||||
|
||||
/* Fake Neighbors. */
|
||||
/* This allows the sculpt tools to work on meshes with multiple connected components as they had
|
||||
* only one connected component. When initialized and enabled, the sculpt API will return extra
|
||||
@@ -9105,356 +5683,10 @@ void SCULPT_fake_neighbors_free(Object *ob)
|
||||
sculpt_pose_fake_neighbors_free(ss);
|
||||
}
|
||||
|
||||
/**
|
||||
* #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the
|
||||
* mask based on the difference between two colors (the active color and the color of any other
|
||||
* vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active
|
||||
* color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer
|
||||
* falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between
|
||||
* masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going
|
||||
* to be.
|
||||
*/
|
||||
#define MASK_BY_COLOR_SLOPE 0.25f
|
||||
|
||||
static float sculpt_mask_by_color_delta_get(const float *color_a,
|
||||
const float *color_b,
|
||||
const float threshold,
|
||||
const bool invert)
|
||||
{
|
||||
float len = len_v3v3(color_a, color_b);
|
||||
/* Normalize len to the (0, 1) range. */
|
||||
len = len / M_SQRT3;
|
||||
|
||||
if (len < threshold - MASK_BY_COLOR_SLOPE) {
|
||||
len = 1.0f;
|
||||
}
|
||||
else if (len >= threshold) {
|
||||
len = 0.0f;
|
||||
}
|
||||
else {
|
||||
len = (-len + threshold) / MASK_BY_COLOR_SLOPE;
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
return 1.0f - len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static float sculpt_mask_by_color_final_mask_get(const float current_mask,
|
||||
const float new_mask,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
if (preserve_mask) {
|
||||
if (invert) {
|
||||
return min_ff(current_mask, new_mask);
|
||||
}
|
||||
return max_ff(current_mask, new_mask);
|
||||
}
|
||||
return new_mask;
|
||||
}
|
||||
|
||||
typedef struct MaskByColorContiguousFloodFillData {
|
||||
float threshold;
|
||||
bool invert;
|
||||
float *new_mask;
|
||||
float initial_color[3];
|
||||
} MaskByColorContiguousFloodFillData;
|
||||
|
||||
static void do_mask_by_color_contiguous_update_nodes_cb(
|
||||
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
|
||||
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK);
|
||||
bool update_node = false;
|
||||
|
||||
const bool invert = data->mask_by_color_invert;
|
||||
const bool preserve_mask = data->mask_by_color_preserve_mask;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
const float current_mask = *vd.mask;
|
||||
const float new_mask = data->mask_by_color_floodfill[vd.index];
|
||||
*vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask);
|
||||
if (current_mask == *vd.mask) {
|
||||
continue;
|
||||
}
|
||||
update_node = true;
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
if (update_node) {
|
||||
BKE_pbvh_node_mark_redraw(data->nodes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool sculpt_mask_by_color_contiguous_floodfill_cb(
|
||||
SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
|
||||
{
|
||||
MaskByColorContiguousFloodFillData *data = userdata;
|
||||
const float *current_color = SCULPT_vertex_color_get(ss, to_v);
|
||||
float new_vertex_mask = sculpt_mask_by_color_delta_get(
|
||||
current_color, data->initial_color, data->threshold, data->invert);
|
||||
data->new_mask[to_v] = new_vertex_mask;
|
||||
|
||||
if (is_duplicate) {
|
||||
data->new_mask[to_v] = data->new_mask[from_v];
|
||||
}
|
||||
|
||||
float len = len_v3v3(current_color, data->initial_color);
|
||||
len = len / M_SQRT3;
|
||||
return len <= data->threshold;
|
||||
}
|
||||
|
||||
static void sculpt_mask_by_color_contiguous(Object *object,
|
||||
const int vertex,
|
||||
const float threshold,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
SculptSession *ss = object->sculpt;
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
|
||||
float *new_mask = MEM_calloc_arrayN(totvert, sizeof(float), "new mask");
|
||||
|
||||
if (invert) {
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
new_mask[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
SculptFloodFill flood;
|
||||
SCULPT_floodfill_init(ss, &flood);
|
||||
SCULPT_floodfill_add_initial(&flood, vertex);
|
||||
|
||||
MaskByColorContiguousFloodFillData ffd;
|
||||
ffd.threshold = threshold;
|
||||
ffd.invert = invert;
|
||||
ffd.new_mask = new_mask;
|
||||
copy_v3_v3(ffd.initial_color, SCULPT_vertex_color_get(ss, vertex));
|
||||
|
||||
SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill_cb, &ffd);
|
||||
SCULPT_floodfill_free(&flood);
|
||||
|
||||
int totnode;
|
||||
PBVHNode **nodes;
|
||||
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.ob = object,
|
||||
.nodes = nodes,
|
||||
.mask_by_color_floodfill = new_mask,
|
||||
.mask_by_color_vertex = vertex,
|
||||
.mask_by_color_threshold = threshold,
|
||||
.mask_by_color_invert = invert,
|
||||
.mask_by_color_preserve_mask = preserve_mask,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(
|
||||
0, totnode, &data, do_mask_by_color_contiguous_update_nodes_cb, &settings);
|
||||
|
||||
MEM_SAFE_FREE(nodes);
|
||||
|
||||
MEM_freeN(new_mask);
|
||||
}
|
||||
|
||||
static void do_mask_by_color_task_cb(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
|
||||
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK);
|
||||
bool update_node = false;
|
||||
|
||||
const float threshold = data->mask_by_color_threshold;
|
||||
const bool invert = data->mask_by_color_invert;
|
||||
const bool preserve_mask = data->mask_by_color_preserve_mask;
|
||||
const float *active_color = SCULPT_vertex_color_get(ss, data->mask_by_color_vertex);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
const float current_mask = *vd.mask;
|
||||
const float new_mask = sculpt_mask_by_color_delta_get(active_color, vd.col, threshold, invert);
|
||||
*vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask);
|
||||
|
||||
if (current_mask == *vd.mask) {
|
||||
continue;
|
||||
}
|
||||
update_node = true;
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
if (update_node) {
|
||||
BKE_pbvh_node_mark_redraw(data->nodes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static void sculpt_mask_by_color_full_mesh(Object *object,
|
||||
const int vertex,
|
||||
const float threshold,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
SculptSession *ss = object->sculpt;
|
||||
|
||||
int totnode;
|
||||
PBVHNode **nodes;
|
||||
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.ob = object,
|
||||
.nodes = nodes,
|
||||
.mask_by_color_vertex = vertex,
|
||||
.mask_by_color_threshold = threshold,
|
||||
.mask_by_color_invert = invert,
|
||||
.mask_by_color_preserve_mask = preserve_mask,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_mask_by_color_task_cb, &settings);
|
||||
|
||||
MEM_SAFE_FREE(nodes);
|
||||
}
|
||||
|
||||
static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
|
||||
|
||||
/* Color data is not available in Multires. */
|
||||
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (!ss->vcol) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
|
||||
/* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move,
|
||||
* so it needs to be updated here. */
|
||||
SculptCursorGeometryInfo sgi;
|
||||
float mouse[2];
|
||||
mouse[0] = event->mval[0];
|
||||
mouse[1] = event->mval[1];
|
||||
SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
|
||||
|
||||
SCULPT_undo_push_begin(ob, "Mask by color");
|
||||
|
||||
const int active_vertex = SCULPT_active_vertex_get(ss);
|
||||
const float threshold = RNA_float_get(op->ptr, "threshold");
|
||||
const bool invert = RNA_boolean_get(op->ptr, "invert");
|
||||
const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask");
|
||||
|
||||
if (RNA_boolean_get(op->ptr, "contiguous")) {
|
||||
sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask);
|
||||
}
|
||||
else {
|
||||
sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask);
|
||||
}
|
||||
|
||||
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask);
|
||||
SCULPT_undo_push_end();
|
||||
|
||||
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Mask by Color";
|
||||
ot->idname = "SCULPT_OT_mask_by_color";
|
||||
ot->description = "Creates a mask based on the sculpt vertex colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->invoke = sculpt_mask_by_color_invoke;
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
|
||||
ot->prop = RNA_def_boolean(
|
||||
ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas");
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask");
|
||||
ot->prop = RNA_def_boolean(
|
||||
ot->srna,
|
||||
"preserve_previous_mask",
|
||||
false,
|
||||
"Preserve Previous Mask",
|
||||
"Preserve the previous mask and add or subtract the new one generated by the colors");
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"threshold",
|
||||
0.35f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Threshold",
|
||||
"How much changes in color affect the mask generation",
|
||||
0.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator Registration
|
||||
* \{ */
|
||||
|
||||
void ED_operatortypes_sculpt(void)
|
||||
{
|
||||
WM_operatortype_append(SCULPT_OT_brush_stroke);
|
||||
WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
|
||||
WM_operatortype_append(SCULPT_OT_set_persistent_base);
|
||||
WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle);
|
||||
WM_operatortype_append(SCULPT_OT_optimize);
|
||||
WM_operatortype_append(SCULPT_OT_symmetrize);
|
||||
WM_operatortype_append(SCULPT_OT_detail_flood_fill);
|
||||
WM_operatortype_append(SCULPT_OT_sample_detail_size);
|
||||
WM_operatortype_append(SCULPT_OT_set_detail_size);
|
||||
WM_operatortype_append(SCULPT_OT_mesh_filter);
|
||||
WM_operatortype_append(SCULPT_OT_mask_filter);
|
||||
WM_operatortype_append(SCULPT_OT_dirty_mask);
|
||||
WM_operatortype_append(SCULPT_OT_mask_expand);
|
||||
WM_operatortype_append(SCULPT_OT_set_pivot_position);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_create);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_change_visibility);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_init);
|
||||
WM_operatortype_append(SCULPT_OT_cloth_filter);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_edit);
|
||||
WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_face_set_box_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_trim_box_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_trim_lasso_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_project_line_gesture);
|
||||
|
||||
WM_operatortype_append(SCULPT_OT_sample_color);
|
||||
WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors);
|
||||
WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors);
|
||||
WM_operatortype_append(SCULPT_OT_color_filter);
|
||||
WM_operatortype_append(SCULPT_OT_mask_by_color);
|
||||
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
|
||||
WM_operatortype_append(SCULPT_OT_mask_init);
|
||||
|
||||
WM_operatortype_append(SCULPT_OT_expand);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
2849
source/blender/editors/sculpt_paint/sculpt_brushes.c
Normal file
2849
source/blender/editors/sculpt_paint/sculpt_brushes.c
Normal file
@@ -0,0 +1,2849 @@
|
||||
/*
|
||||
* 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) 2006 by Nicholas Bishop
|
||||
* All rights reserved.
|
||||
* Implements the Sculpt Mode tools
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup edsculpt
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_dial_2d.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_gsqueue.h"
|
||||
#include "BLI_hash.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_color_blend.h"
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "PIL_time.h"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_ccg.h"
|
||||
#include "BKE_colortools.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_kelvinlet.h"
|
||||
#include "BKE_key.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
#include "BKE_mesh_mirror.h"
|
||||
#include "BKE_modifier.h"
|
||||
#include "BKE_multires.h"
|
||||
#include "BKE_node.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_paint.h"
|
||||
#include "BKE_particle.h"
|
||||
#include "BKE_pbvh.h"
|
||||
#include "BKE_pointcache.h"
|
||||
#include "BKE_report.h"
|
||||
#include "BKE_scene.h"
|
||||
#include "BKE_screen.h"
|
||||
#include "BKE_subdiv_ccg.h"
|
||||
#include "BKE_subsurf.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "IMB_colormanagement.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_message.h"
|
||||
#include "WM_toolsystem.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "ED_object.h"
|
||||
#include "ED_screen.h"
|
||||
#include "ED_sculpt.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "paint_intern.h"
|
||||
#include "sculpt_intern.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_define.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "bmesh.h"
|
||||
#include "bmesh_tools.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name SculptProjectVector
|
||||
*
|
||||
* Fast-path for #project_plane_v3_v3v3
|
||||
* \{ */
|
||||
|
||||
typedef struct SculptProjectVector {
|
||||
float plane[3];
|
||||
float len_sq;
|
||||
float len_sq_inv_neg;
|
||||
bool is_valid;
|
||||
|
||||
} SculptProjectVector;
|
||||
|
||||
static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip)
|
||||
{
|
||||
float d = plane_point_side_v3(plane, co);
|
||||
if (flip) {
|
||||
d = -d;
|
||||
}
|
||||
return d <= 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* \param plane: Direction, can be any length.
|
||||
*/
|
||||
static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3])
|
||||
{
|
||||
copy_v3_v3(spvc->plane, plane);
|
||||
spvc->len_sq = len_squared_v3(spvc->plane);
|
||||
spvc->is_valid = (spvc->len_sq > FLT_EPSILON);
|
||||
spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the projection.
|
||||
*/
|
||||
static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3])
|
||||
{
|
||||
#if 0
|
||||
project_plane_v3_v3v3(r_vec, vec, spvc->plane);
|
||||
#else
|
||||
/* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */
|
||||
madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void calc_sculpt_plane(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3])
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) &&
|
||||
(SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) ||
|
||||
!(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) {
|
||||
switch (brush->sculpt_plane) {
|
||||
case SCULPT_DISP_DIR_VIEW:
|
||||
copy_v3_v3(r_area_no, ss->cache->true_view_normal);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_X:
|
||||
ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_Y:
|
||||
ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_Z:
|
||||
ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f);
|
||||
break;
|
||||
|
||||
case SCULPT_DISP_DIR_AREA:
|
||||
SCULPT_calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal);
|
||||
normalize_v3(r_area_no);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* For flatten center. */
|
||||
/* Flatten center has not been calculated yet if we are not using the area normal. */
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) {
|
||||
SCULPT_calc_area_center(sd, ob, nodes, totnode, r_area_co);
|
||||
}
|
||||
|
||||
/* For area normal. */
|
||||
if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) &&
|
||||
(brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(ss->cache->sculpt_normal, r_area_no);
|
||||
}
|
||||
|
||||
/* For flatten center. */
|
||||
if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) &&
|
||||
(brush->flag & BRUSH_ORIGINAL_PLANE)) {
|
||||
copy_v3_v3(r_area_co, ss->cache->last_center);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(ss->cache->last_center, r_area_co);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* For area normal. */
|
||||
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
|
||||
|
||||
/* For flatten center. */
|
||||
copy_v3_v3(r_area_co, ss->cache->last_center);
|
||||
|
||||
/* For area normal. */
|
||||
flip_v3(r_area_no, ss->cache->mirror_symmetry_pass);
|
||||
|
||||
/* For flatten center. */
|
||||
flip_v3(r_area_co, ss->cache->mirror_symmetry_pass);
|
||||
|
||||
/* For area normal. */
|
||||
mul_m4_v3(ss->cache->symm_rot_mat, r_area_no);
|
||||
|
||||
/* For flatten center. */
|
||||
mul_m4_v3(ss->cache->symm_rot_mat, r_area_co);
|
||||
|
||||
/* Shift the plane for the current tile. */
|
||||
add_v3_v3(r_area_co, ss->cache->plane_offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void sculpt_rake_rotate(const SculptSession *ss,
|
||||
const float sculpt_co[3],
|
||||
const float v_co[3],
|
||||
float factor,
|
||||
float r_delta[3])
|
||||
{
|
||||
float vec_rot[3];
|
||||
|
||||
#if 0
|
||||
/* lerp */
|
||||
sub_v3_v3v3(vec_rot, v_co, sculpt_co);
|
||||
mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot);
|
||||
add_v3_v3(vec_rot, sculpt_co);
|
||||
sub_v3_v3v3(r_delta, vec_rot, v_co);
|
||||
mul_v3_fl(r_delta, factor);
|
||||
#else
|
||||
/* slerp */
|
||||
float q_interp[4];
|
||||
sub_v3_v3v3(vec_rot, v_co, sculpt_co);
|
||||
|
||||
copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry);
|
||||
pow_qt_fl_normalized(q_interp, factor);
|
||||
mul_qt_v3(q_interp, vec_rot);
|
||||
|
||||
add_v3_v3(vec_rot, sculpt_co);
|
||||
sub_v3_v3v3(r_delta, vec_rot, v_co);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Align the grab delta to the brush normal.
|
||||
*
|
||||
* \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`.
|
||||
*/
|
||||
static void sculpt_project_v3_normal_align(SculptSession *ss,
|
||||
const float normal_weight,
|
||||
float grab_delta[3])
|
||||
{
|
||||
/* Signed to support grabbing in (to make a hole) as well as out. */
|
||||
const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta);
|
||||
|
||||
/* This scale effectively projects the offset so dragging follows the cursor,
|
||||
* as the normal points towards the view, the scale increases. */
|
||||
float len_view_scale;
|
||||
{
|
||||
float view_aligned_normal[3];
|
||||
project_plane_v3_v3v3(
|
||||
view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal);
|
||||
len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm));
|
||||
len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f;
|
||||
}
|
||||
|
||||
mul_v3_fl(grab_delta, 1.0f - normal_weight);
|
||||
madd_v3_v3fl(
|
||||
grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale);
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Draw Brush
|
||||
* \{ */
|
||||
|
||||
static void do_draw_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], offset, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float effective_normal[3];
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
mul_v3_v3fl(offset, effective_normal, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise
|
||||
* initialize before threads so they can do curve mapping. */
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.offset = offset,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static void do_fill_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
|
||||
float displace;
|
||||
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_scrape_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SCULPT_plane_point_side(vd.co, test.plane_tool)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
|
||||
float displace;
|
||||
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = -radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Clay Thumb Brush
|
||||
* \{ */
|
||||
|
||||
static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*mat)[4] = data->mat;
|
||||
const float *area_no_sp = data->area_no_sp;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = data->clay_strength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
float plane_tilt[4];
|
||||
float normal_tilt[3];
|
||||
float imat[4][4];
|
||||
|
||||
invert_m4_m4(imat, mat);
|
||||
rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle));
|
||||
|
||||
/* Plane aligned to the geometry normal (back part of the brush). */
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp);
|
||||
/* Tilted plane (front part of the brush). */
|
||||
plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
float local_co[3];
|
||||
mul_v3_m4v3(local_co, mat, vd.co);
|
||||
float intr[3], intr_tilt[3];
|
||||
float val[3];
|
||||
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co);
|
||||
|
||||
/* Mix the deformation of the aligned and the tilted plane based on the brush space vertex
|
||||
* coordinates. */
|
||||
/* We can also control the mix with a curve if it produces noticeable artifacts in the center
|
||||
* of the brush. */
|
||||
const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f;
|
||||
interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix);
|
||||
sub_v3_v3v3(val, intr_tilt, vd.co);
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
float SCULPT_clay_thumb_get_stabilized_pressure(StrokeCache *cache)
|
||||
{
|
||||
float final_pressure = 0.0f;
|
||||
for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) {
|
||||
final_pressure += cache->clay_pressure_stabilizer[i];
|
||||
}
|
||||
return final_pressure / SCULPT_CLAY_STABILIZER_LEN;
|
||||
}
|
||||
|
||||
void SCULPT_do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
const float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
const float displace = radius * (0.25f + offset);
|
||||
|
||||
/* Sampled geometry normal and area center. */
|
||||
float area_no_sp[3];
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float temp[3];
|
||||
float mat[4][4];
|
||||
float scale[4][4];
|
||||
float tmat[4][4];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
|
||||
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(area_no, area_no_sp);
|
||||
}
|
||||
|
||||
/* Delay the first daub because grab delta is not setup. */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
ss->cache->clay_thumb_front_angle = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to the
|
||||
* stroke. */
|
||||
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
|
||||
ss->cache->clay_thumb_front_angle += 0.8f;
|
||||
ss->cache->clay_thumb_front_angle = clamp_f(ss->cache->clay_thumb_front_angle, 0.0f, 60.0f);
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Displace the brush planes. */
|
||||
copy_v3_v3(area_co, ss->cache->location);
|
||||
mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
/* Initialize brush local-space matrix. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], ss->cache->location);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
/* Scale brush local space matrix. */
|
||||
scale_m4_fl(scale, ss->cache->radius);
|
||||
mul_m4_m4m4(tmat, mat, scale);
|
||||
invert_m4_m4(mat, tmat);
|
||||
|
||||
float clay_strength = ss->cache->bstrength *
|
||||
SCULPT_clay_thumb_get_stabilized_pressure(ss->cache);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no_sp = area_no_sp,
|
||||
.area_co = ss->cache->location,
|
||||
.mat = mat,
|
||||
.clay_strength = clay_strength,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static void do_flatten_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
float intr[3];
|
||||
float val[3];
|
||||
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = ss->cache->radius;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
float displace;
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
displace = radius * offset;
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Clay Brush
|
||||
* \{ */
|
||||
|
||||
typedef struct ClaySampleData {
|
||||
float plane_dist[2];
|
||||
} ClaySampleData;
|
||||
|
||||
static void calc_clay_surface_task_cb(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
ClaySampleData *csd = tls->userdata_chunk;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
float plane[4];
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, brush->falloff_shape);
|
||||
|
||||
/* Apply the brush normal radius to the test before sampling. */
|
||||
float test_radius = sqrtf(test.radius_squared);
|
||||
test_radius *= brush->normal_radius_factor;
|
||||
test.radius_squared = test_radius * test_radius;
|
||||
plane_from_point_normal_v3(plane, area_co, area_no);
|
||||
|
||||
if (is_zero_v4(plane)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float plane_dist = dist_signed_to_plane_v3(vd.co, plane);
|
||||
float plane_dist_abs = fabsf(plane_dist);
|
||||
if (plane_dist > 0.0f) {
|
||||
csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs);
|
||||
}
|
||||
else {
|
||||
csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata),
|
||||
void *__restrict chunk_join,
|
||||
void *__restrict chunk)
|
||||
{
|
||||
ClaySampleData *join = chunk_join;
|
||||
ClaySampleData *csd = chunk;
|
||||
join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]);
|
||||
join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]);
|
||||
}
|
||||
|
||||
static void do_clay_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *area_no = data->area_no;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = fabsf(ss->cache->bstrength);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const float radius = fabsf(ss->cache->radius);
|
||||
const float initial_radius = fabsf(ss->cache->initial_radius);
|
||||
bool flip = ss->cache->bstrength < 0.0f;
|
||||
|
||||
float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
float displace;
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
float temp[3];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
SculptThreadedTaskData sample_data = {
|
||||
.sd = NULL,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.totnode = totnode,
|
||||
.area_no = area_no,
|
||||
.area_co = ss->cache->location,
|
||||
};
|
||||
|
||||
ClaySampleData csd = {{0}};
|
||||
|
||||
TaskParallelSettings sample_settings;
|
||||
BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode);
|
||||
sample_settings.func_reduce = calc_clay_surface_reduce;
|
||||
sample_settings.userdata_chunk = &csd;
|
||||
sample_settings.userdata_chunk_size = sizeof(ClaySampleData);
|
||||
|
||||
BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings);
|
||||
|
||||
float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]);
|
||||
d_offset = min_ff(radius, d_offset);
|
||||
d_offset = d_offset / radius;
|
||||
d_offset = 1.0f - d_offset;
|
||||
displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f)));
|
||||
if (flip) {
|
||||
displace = -displace;
|
||||
}
|
||||
|
||||
mul_v3_v3v3(temp, area_no, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
copy_v3_v3(area_co, ss->cache->location);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no = area_no,
|
||||
.area_co = area_co,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*mat)[4] = data->mat;
|
||||
const float *area_no_sp = data->area_no_sp;
|
||||
const float *area_co = data->area_co;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptBrushTest test;
|
||||
float(*proxy)[3];
|
||||
const bool flip = (ss->cache->bstrength < 0.0f);
|
||||
const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SCULPT_brush_test_init(ss, &test);
|
||||
plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!plane_point_side_flip(vd.co, test.plane_tool, flip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float intr[3];
|
||||
float val[3];
|
||||
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
|
||||
sub_v3_v3v3(val, intr, vd.co);
|
||||
|
||||
if (!SCULPT_plane_trim(ss->cache, brush, val)) {
|
||||
continue;
|
||||
}
|
||||
/* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
ss->cache->radius * test.dist,
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], val, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
const bool flip = (ss->cache->bstrength < 0.0f);
|
||||
const float radius = flip ? -ss->cache->radius : ss->cache->radius;
|
||||
const float offset = SCULPT_brush_plane_offset_get(sd, ss);
|
||||
const float displace = radius * (0.18f + offset);
|
||||
|
||||
/* The sculpt-plane normal (whatever its set to). */
|
||||
float area_no_sp[3];
|
||||
|
||||
/* Geometry normal */
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float temp[3];
|
||||
float mat[4][4];
|
||||
float scale[4][4];
|
||||
float tmat[4][4];
|
||||
|
||||
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
|
||||
SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor);
|
||||
|
||||
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
|
||||
SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(area_no, area_no_sp);
|
||||
}
|
||||
|
||||
/* Delay the first daub because grab delta is not setup. */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
|
||||
mul_v3_fl(temp, displace);
|
||||
add_v3_v3(area_co, temp);
|
||||
|
||||
/* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform the
|
||||
* vertices. When in Add mode, vertices that are below the plane and inside the cube are move
|
||||
* towards the plane. In this situation, there may be cases where a vertex is outside the cube
|
||||
* but below the plane, so won't be deformed, causing artifacts. In order to prevent these
|
||||
* artifacts, this displaces the test cube space in relation to the plane in order to
|
||||
* deform more vertices that may be below it. */
|
||||
/* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were set
|
||||
* by doing multiple tests using the default "Clay Strips" brush preset. */
|
||||
float area_co_displaced[3];
|
||||
madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f);
|
||||
|
||||
/* Initialize brush local-space matrix. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], area_co_displaced);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
/* Scale brush local space matrix. */
|
||||
scale_m4_fl(scale, ss->cache->radius);
|
||||
mul_m4_m4m4(tmat, mat, scale);
|
||||
|
||||
/* Deform the local space in Z to scale the test cube. As the test cube does not have falloff in
|
||||
* Z this does not produce artifacts in the falloff cube and allows to deform extra vertices
|
||||
* during big deformation while keeping the surface as uniform as possible. */
|
||||
mul_v3_fl(tmat[2], 1.25f);
|
||||
|
||||
invert_m4_m4(mat, tmat);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.area_no_sp = area_no_sp,
|
||||
.area_co = area_co,
|
||||
.mat = mat,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
SculptProjectVector *spvc = data->spvc;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
const bool do_rake_rotation = ss->cache->is_rake_rotation_valid;
|
||||
const bool do_pinch = (brush->crease_pinch_factor != 0.5f);
|
||||
const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) *
|
||||
(len_v3(grab_delta) / ss->cache->radius)) :
|
||||
0.0f;
|
||||
|
||||
const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
KelvinletParams params;
|
||||
BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!do_elastic && !sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float fade;
|
||||
if (do_elastic) {
|
||||
fade = 1.0f;
|
||||
}
|
||||
else {
|
||||
fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
}
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
|
||||
|
||||
/* Negative pinch will inflate, helps maintain volume. */
|
||||
if (do_pinch) {
|
||||
float delta_pinch_init[3], delta_pinch[3];
|
||||
|
||||
sub_v3_v3v3(delta_pinch, vd.co, test.location);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal);
|
||||
}
|
||||
|
||||
/* Important to calculate based on the grabbed location
|
||||
* (intentionally ignore fade here). */
|
||||
add_v3_v3(delta_pinch, grab_delta);
|
||||
|
||||
sculpt_project_v3(spvc, delta_pinch, delta_pinch);
|
||||
|
||||
copy_v3_v3(delta_pinch_init, delta_pinch);
|
||||
|
||||
float pinch_fade = pinch * fade;
|
||||
/* When reducing, scale reduction back by how close to the center we are,
|
||||
* so we don't pinch into nothingness. */
|
||||
if (pinch > 0.0f) {
|
||||
/* Square to have even less impact for close vertices. */
|
||||
pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius));
|
||||
}
|
||||
mul_v3_fl(delta_pinch, 1.0f + pinch_fade);
|
||||
sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch);
|
||||
add_v3_v3(proxy[vd.i], delta_pinch);
|
||||
}
|
||||
|
||||
if (do_rake_rotation) {
|
||||
float delta_rotate[3];
|
||||
sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate);
|
||||
add_v3_v3(proxy[vd.i], delta_rotate);
|
||||
}
|
||||
|
||||
if (do_elastic) {
|
||||
float disp[3];
|
||||
BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]);
|
||||
mul_v3_fl(disp, bstrength * 20.0f);
|
||||
if (vd.mask) {
|
||||
mul_v3_fl(disp, 1.0f - *vd.mask);
|
||||
}
|
||||
mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index));
|
||||
copy_v3_v3(proxy[vd.i], disp);
|
||||
}
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
float grab_delta[3];
|
||||
|
||||
SculptProjectVector spvc;
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (bstrength < 0.0f) {
|
||||
negate_v3(grab_delta);
|
||||
}
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
/* Optionally pinch while painting. */
|
||||
if (brush->crease_pinch_factor != 0.5f) {
|
||||
sculpt_project_v3_cache_init(&spvc, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.spvc = &spvc,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_thumb_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *cono = data->cono;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], cono, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
float tmp[3], cono[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta);
|
||||
cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.cono = cono,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_rotate_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float angle = data->angle;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
float vec[3], rot[3][3];
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
sub_v3_v3v3(vec, orig_data.co, ss->cache->location);
|
||||
axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade);
|
||||
mul_v3_m3v3(proxy[vd.i], rot, vec);
|
||||
add_v3_v3(proxy[vd.i], ss->cache->location);
|
||||
sub_v3_v3(proxy[vd.i], orig_data.co);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1};
|
||||
const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass];
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.angle = angle,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_layer_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
Sculpt *sd = data->sd;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
const int vi = vd.index;
|
||||
float *disp_factor;
|
||||
if (use_persistent_base) {
|
||||
disp_factor = &ss->persistent_base[vi].disp;
|
||||
}
|
||||
else {
|
||||
disp_factor = &ss->cache->layer_displacement_factor[vi];
|
||||
}
|
||||
|
||||
/* When using persistent base, the layer brush (holding Control) invert mode resets the
|
||||
* height of the layer to 0. This makes possible to clean edges of previously added layers
|
||||
* on top of the base. */
|
||||
/* The main direction of the layers is inverted using the regular brush strength with the
|
||||
* brush direction property. */
|
||||
if (use_persistent_base && ss->cache->invert) {
|
||||
(*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) *
|
||||
((*disp_factor) > 0.0f ? -1.0f : 1.0f);
|
||||
}
|
||||
else {
|
||||
(*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor));
|
||||
}
|
||||
if (vd.mask) {
|
||||
const float clamp_mask = 1.0f - *vd.mask;
|
||||
*disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask);
|
||||
}
|
||||
else {
|
||||
*disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
float final_co[3];
|
||||
float normal[3];
|
||||
|
||||
if (use_persistent_base) {
|
||||
SCULPT_vertex_persistent_normal_get(ss, vi, normal);
|
||||
mul_v3_fl(normal, brush->height);
|
||||
madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor);
|
||||
}
|
||||
else {
|
||||
normal_short_to_float_v3(normal, orig_data.no);
|
||||
mul_v3_fl(normal, brush->height);
|
||||
madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor);
|
||||
}
|
||||
|
||||
float vdisp[3];
|
||||
sub_v3_v3v3(vdisp, final_co, vd.co);
|
||||
mul_v3_fl(vdisp, fabsf(fade));
|
||||
add_v3_v3v3(final_co, vd.co, vdisp);
|
||||
|
||||
SCULPT_clip(sd, ss, vd.co, final_co);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (ss->cache->layer_displacement_factor == NULL) {
|
||||
ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss),
|
||||
"layer displacement factor");
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_inflate_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float val[3];
|
||||
|
||||
if (vd.fno) {
|
||||
copy_v3_v3(val, vd.fno);
|
||||
}
|
||||
else {
|
||||
normal_short_to_float_v3(val, vd.no);
|
||||
}
|
||||
|
||||
mul_v3_fl(val, fade * ss->cache->radius);
|
||||
mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_nudge_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *cono = data->cono;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], cono, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
float tmp[3], cono[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta);
|
||||
cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.cono = cono,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Crease & Blob Brush
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB'
|
||||
*/
|
||||
static void do_crease_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
SculptProjectVector *spvc = data->spvc;
|
||||
const float flippedbstrength = data->flippedbstrength;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float val1[3];
|
||||
float val2[3];
|
||||
|
||||
/* First we pinch. */
|
||||
sub_v3_v3v3(val1, test.location, vd.co);
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(val1, val1, ss->cache->view_normal);
|
||||
}
|
||||
|
||||
mul_v3_fl(val1, fade * flippedbstrength);
|
||||
|
||||
sculpt_project_v3(spvc, val1, val1);
|
||||
|
||||
/* Then we draw. */
|
||||
mul_v3_v3fl(val2, offset, fade);
|
||||
|
||||
add_v3_v3v3(proxy[vd.i], val1, val2);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
const Scene *scene = ss->cache->vc->scene;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
float bstrength = ss->cache->bstrength;
|
||||
float flippedbstrength, crease_correction;
|
||||
float brush_alpha;
|
||||
|
||||
SculptProjectVector spvc;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* We divide out the squared alpha and multiply by the squared crease
|
||||
* to give us the pinch strength. */
|
||||
crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor;
|
||||
brush_alpha = BKE_brush_alpha_get(scene, brush);
|
||||
if (brush_alpha > 0.0f) {
|
||||
crease_correction /= brush_alpha * brush_alpha;
|
||||
}
|
||||
|
||||
/* We always want crease to pinch or blob to relax even when draw is negative. */
|
||||
flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength :
|
||||
crease_correction * bstrength;
|
||||
|
||||
if (brush->sculpt_tool == SCULPT_TOOL_BLOB) {
|
||||
flippedbstrength *= -1.0f;
|
||||
}
|
||||
|
||||
/* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single
|
||||
* point. Without this we get a 'flat' surface surrounding the pinch. */
|
||||
sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.spvc = &spvc,
|
||||
.offset = offset,
|
||||
.flippedbstrength = flippedbstrength,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_pinch_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
float(*stroke_xz)[3] = data->stroke_xz;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
float x_object_space[3];
|
||||
float z_object_space[3];
|
||||
copy_v3_v3(x_object_space, stroke_xz[0]);
|
||||
copy_v3_v3(z_object_space, stroke_xz[1]);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float disp_center[3];
|
||||
float x_disp[3];
|
||||
float z_disp[3];
|
||||
/* Calculate displacement from the vertex to the brush center. */
|
||||
sub_v3_v3v3(disp_center, test.location, vd.co);
|
||||
|
||||
/* Project the displacement into the X vector (aligned to the stroke). */
|
||||
mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space));
|
||||
|
||||
/* Project the displacement into the Z vector (aligned to the surface normal). */
|
||||
mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space));
|
||||
|
||||
/* Add the two projected vectors to calculate the final displacement.
|
||||
* The Y component is removed. */
|
||||
add_v3_v3v3(disp_center, x_disp, z_disp);
|
||||
|
||||
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal);
|
||||
}
|
||||
mul_v3_v3fl(proxy[vd.i], disp_center, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
float area_no[3];
|
||||
float area_co[3];
|
||||
|
||||
float mat[4][4];
|
||||
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
|
||||
|
||||
/* delay the first daub because grab delta is not setup */
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize `mat`. */
|
||||
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
|
||||
mat[0][3] = 0.0f;
|
||||
cross_v3_v3v3(mat[1], area_no, mat[0]);
|
||||
mat[1][3] = 0.0f;
|
||||
copy_v3_v3(mat[2], area_no);
|
||||
mat[2][3] = 0.0f;
|
||||
copy_v3_v3(mat[3], ss->cache->location);
|
||||
mat[3][3] = 1.0f;
|
||||
normalize_m4(mat);
|
||||
|
||||
float stroke_xz[2][3];
|
||||
normalize_v3_v3(stroke_xz[0], mat[0]);
|
||||
normalize_v3_v3(stroke_xz[1], mat[2]);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.stroke_xz = stroke_xz,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_grab_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE;
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
if (grab_silhouette) {
|
||||
float silhouette_test_dir[3];
|
||||
normalize_v3_v3(silhouette_test_dir, grab_delta);
|
||||
if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) {
|
||||
mul_v3_fl(silhouette_test_dir, -1.0f);
|
||||
}
|
||||
float vno[3];
|
||||
normal_short_to_float_v3(vno, orig_data.no);
|
||||
fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f);
|
||||
}
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *grab_delta = data->grab_delta;
|
||||
const float *location = ss->cache->location;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
float dir;
|
||||
if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) {
|
||||
dir = 1.0f;
|
||||
}
|
||||
else {
|
||||
dir = -1.0f;
|
||||
}
|
||||
|
||||
if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) {
|
||||
int symm = ss->cache->mirror_symmetry_pass;
|
||||
if (ELEM(symm, 1, 2, 4, 7)) {
|
||||
dir = -dir;
|
||||
}
|
||||
}
|
||||
|
||||
KelvinletParams params;
|
||||
float force = len_v3(grab_delta) * dir * bstrength;
|
||||
BKE_kelvinlet_init_params(
|
||||
¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
float final_disp[3];
|
||||
switch (brush->elastic_deform_type) {
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB:
|
||||
BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: {
|
||||
BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
}
|
||||
case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: {
|
||||
BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta);
|
||||
mul_v3_fl(final_disp, bstrength * 20.0f);
|
||||
break;
|
||||
}
|
||||
case BRUSH_ELASTIC_DEFORM_SCALE:
|
||||
BKE_kelvinlet_scale(
|
||||
final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm);
|
||||
break;
|
||||
case BRUSH_ELASTIC_DEFORM_TWIST:
|
||||
BKE_kelvinlet_twist(
|
||||
final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm);
|
||||
break;
|
||||
}
|
||||
|
||||
if (vd.mask) {
|
||||
mul_v3_fl(final_disp, 1.0f - *vd.mask);
|
||||
}
|
||||
|
||||
mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index));
|
||||
|
||||
copy_v3_v3(proxy[vd.i], final_disp);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float grab_delta[3];
|
||||
|
||||
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
|
||||
|
||||
if (ss->cache->normal_weight > 0.0f) {
|
||||
sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.grab_delta = grab_delta,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings);
|
||||
}
|
||||
/** \} */
|
||||
|
||||
static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float *offset = data->offset;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
/* Offset vertex. */
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], offset, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
float offset[3];
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float effective_normal[3];
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
mul_v3_v3fl(offset, effective_normal, ss->cache->radius);
|
||||
mul_v3_v3(offset, ss->cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise
|
||||
* initialize before threads so they can do curve mapping. */
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.offset = offset,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Topology Brush
|
||||
* \{ */
|
||||
|
||||
static void do_topology_slide_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
float(*proxy)[3];
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
float current_disp[3];
|
||||
float current_disp_norm[3];
|
||||
float final_disp[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
switch (brush->slide_deform_type) {
|
||||
case BRUSH_SLIDE_DEFORM_DRAG:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location);
|
||||
break;
|
||||
case BRUSH_SLIDE_DEFORM_PINCH:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, vd.co);
|
||||
break;
|
||||
case BRUSH_SLIDE_DEFORM_EXPAND:
|
||||
sub_v3_v3v3(current_disp, vd.co, ss->cache->location);
|
||||
break;
|
||||
}
|
||||
|
||||
normalize_v3_v3(current_disp_norm, current_disp);
|
||||
mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength);
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
|
||||
float vertex_disp[3];
|
||||
float vertex_disp_norm[3];
|
||||
sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co);
|
||||
normalize_v3_v3(vertex_disp_norm, vertex_disp);
|
||||
if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) {
|
||||
madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp));
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
mul_v3_v3fl(proxy[vd.i], final_disp, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_relax_vertex(SculptSession *ss,
|
||||
PBVHVertexIter *vd,
|
||||
float factor,
|
||||
bool filter_boundary_face_sets,
|
||||
float *r_final_pos)
|
||||
{
|
||||
float smooth_pos[3];
|
||||
float final_disp[3];
|
||||
float boundary_normal[3];
|
||||
int avg_count = 0;
|
||||
int neighbor_count = 0;
|
||||
zero_v3(smooth_pos);
|
||||
zero_v3(boundary_normal);
|
||||
const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index);
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) {
|
||||
neighbor_count++;
|
||||
if (!filter_boundary_face_sets ||
|
||||
(filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) {
|
||||
|
||||
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
|
||||
* position. */
|
||||
if (is_boundary) {
|
||||
if (!SCULPT_vertex_is_boundary(ss, ni.index)) {
|
||||
continue;
|
||||
}
|
||||
add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index));
|
||||
avg_count++;
|
||||
|
||||
/* Calculate a normal for the constraint plane using the edges of the boundary. */
|
||||
float to_neighbor[3];
|
||||
sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co);
|
||||
normalize_v3(to_neighbor);
|
||||
add_v3_v3(boundary_normal, to_neighbor);
|
||||
}
|
||||
else {
|
||||
add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index));
|
||||
avg_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
/* Don't modify corner vertices. */
|
||||
if (neighbor_count <= 2) {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avg_count > 0) {
|
||||
mul_v3_fl(smooth_pos, 1.0f / avg_count);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
float plane[4];
|
||||
float smooth_closest_plane[3];
|
||||
float vno[3];
|
||||
|
||||
if (is_boundary && avg_count == 2) {
|
||||
normalize_v3_v3(vno, boundary_normal);
|
||||
}
|
||||
else {
|
||||
SCULPT_vertex_normal_get(ss, vd->index, vno);
|
||||
}
|
||||
|
||||
if (is_zero_v3(vno)) {
|
||||
copy_v3_v3(r_final_pos, vd->co);
|
||||
return;
|
||||
}
|
||||
|
||||
plane_from_point_normal_v3(plane, vd->co, vno);
|
||||
closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos);
|
||||
sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co);
|
||||
|
||||
mul_v3_fl(final_disp, factor);
|
||||
add_v3_v3v3(r_final_pos, vd->co, final_disp);
|
||||
}
|
||||
|
||||
static void do_topology_relax_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
SculptOrigVertData orig_data;
|
||||
|
||||
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
||||
|
||||
BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
||||
if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
orig_data.co,
|
||||
sqrtf(test.dist),
|
||||
orig_data.no,
|
||||
NULL,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co);
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
if (ss->cache->alt_smooth) {
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Multires Displacement Eraser Brush
|
||||
* \{ */
|
||||
|
||||
static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
|
||||
|
||||
float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
float limit_co[3];
|
||||
float disp[3];
|
||||
SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co);
|
||||
sub_v3_v3v3(disp, limit_co, vd.co);
|
||||
mul_v3_v3fl(proxy[vd.i], disp, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_displacement_eraser_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Multires Displacement Smear Brush
|
||||
* \{ */
|
||||
|
||||
static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask ? *vd.mask : 0.0f,
|
||||
vd.index,
|
||||
thread_id);
|
||||
|
||||
float current_disp[3];
|
||||
float current_disp_norm[3];
|
||||
float interp_limit_surface_disp[3];
|
||||
|
||||
copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]);
|
||||
|
||||
switch (brush->smear_deform_type) {
|
||||
case BRUSH_SMEAR_DEFORM_DRAG:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location);
|
||||
break;
|
||||
case BRUSH_SMEAR_DEFORM_PINCH:
|
||||
sub_v3_v3v3(current_disp, ss->cache->location, vd.co);
|
||||
break;
|
||||
case BRUSH_SMEAR_DEFORM_EXPAND:
|
||||
sub_v3_v3v3(current_disp, vd.co, ss->cache->location);
|
||||
break;
|
||||
}
|
||||
|
||||
normalize_v3_v3(current_disp_norm, current_disp);
|
||||
mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength);
|
||||
|
||||
float weights_accum = 1.0f;
|
||||
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) {
|
||||
float vertex_disp[3];
|
||||
float vertex_disp_norm[3];
|
||||
float neighbor_limit_co[3];
|
||||
SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co);
|
||||
sub_v3_v3v3(vertex_disp,
|
||||
ss->cache->limit_surface_co[ni.index],
|
||||
ss->cache->limit_surface_co[vd.index]);
|
||||
const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index];
|
||||
normalize_v3_v3(vertex_disp_norm, vertex_disp);
|
||||
|
||||
if (dot_v3v3(current_disp_norm, vertex_disp_norm) >= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float disp_interp = clamp_f(
|
||||
-dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f);
|
||||
madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp);
|
||||
weights_accum += disp_interp;
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
|
||||
mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum);
|
||||
|
||||
float new_co[3];
|
||||
add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp);
|
||||
interp_v3_v3v3(vd.co, vd.co, new_co, fade);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void do_displacement_smear_store_prev_disp_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) {
|
||||
sub_v3_v3v3(ss->cache->prev_displacement[vd.index],
|
||||
SCULPT_vertex_co_get(ss, vd.index),
|
||||
ss->cache->limit_surface_co[vd.index]);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
BKE_curvemapping_init(brush->curve);
|
||||
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
if (!ss->cache->prev_displacement) {
|
||||
ss->cache->prev_displacement = MEM_malloc_arrayN(
|
||||
totvert, sizeof(float[3]), "prev displacement");
|
||||
ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co");
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]);
|
||||
sub_v3_v3v3(ss->cache->prev_displacement[i],
|
||||
SCULPT_vertex_co_get(ss, i),
|
||||
ss->cache->limit_surface_co[i]);
|
||||
}
|
||||
}
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(
|
||||
0, totnode, &data, do_displacement_smear_store_prev_disp_task_cb_ex, &settings);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Topology Rake (Shared Utility)
|
||||
* \{ */
|
||||
|
||||
static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
Sculpt *sd = data->sd;
|
||||
const Brush *brush = data->brush;
|
||||
|
||||
float direction[3];
|
||||
copy_v3_v3(direction, ss->cache->grab_delta_symmetry);
|
||||
|
||||
float tmp[3];
|
||||
mul_v3_v3fl(
|
||||
tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction));
|
||||
sub_v3_v3(direction, tmp);
|
||||
normalize_v3(direction);
|
||||
|
||||
/* Cancel if there's no grab data. */
|
||||
if (is_zero_v3(direction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float bstrength = clamp_f(data->strength, 0.0f, 1.0f);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade =
|
||||
bstrength *
|
||||
SCULPT_brush_strength_factor(
|
||||
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) *
|
||||
ss->cache->pressure;
|
||||
|
||||
float avg[3], val[3];
|
||||
|
||||
SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert);
|
||||
|
||||
sub_v3_v3v3(val, avg, vd.co);
|
||||
|
||||
madd_v3_v3v3fl(val, vd.co, val, fade);
|
||||
|
||||
SCULPT_clip(sd, ss, vd.co, val);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_bmesh_topology_rake(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
const float strength = clamp_f(bstrength, 0.0f, 1.0f);
|
||||
|
||||
/* Interactions increase both strength and quality. */
|
||||
const int iterations = 3;
|
||||
|
||||
int iteration;
|
||||
const int count = iterations * strength + 1;
|
||||
const float factor = iterations * strength / count;
|
||||
|
||||
for (iteration = 0; iteration <= count; iteration++) {
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
.strength = factor,
|
||||
};
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
|
||||
BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Mask Brush
|
||||
* \{ */
|
||||
|
||||
static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict tls)
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
const Brush *brush = data->brush;
|
||||
const float bstrength = ss->cache->bstrength;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, &test, data->brush->falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(tls);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float fade = SCULPT_brush_strength_factor(
|
||||
ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id);
|
||||
|
||||
if (bstrength > 0.0f) {
|
||||
(*vd.mask) += fade * bstrength * (1.0f - *vd.mask);
|
||||
}
|
||||
else {
|
||||
(*vd.mask) += fade * bstrength * (*vd.mask);
|
||||
}
|
||||
*vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f);
|
||||
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
}
|
||||
|
||||
void SCULPT_do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
/* Threaded loop over nodes. */
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
.brush = brush,
|
||||
.nodes = nodes,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_mask_brush_draw_task_cb_ex, &settings);
|
||||
}
|
||||
|
||||
void SCULPT_do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
||||
{
|
||||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
|
||||
switch ((BrushMaskTool)brush->mask_tool) {
|
||||
case BRUSH_MASK_DRAW:
|
||||
SCULPT_do_mask_brush_draw(sd, ob, nodes, totnode);
|
||||
break;
|
||||
case BRUSH_MASK_SMOOTH:
|
||||
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "BLI_bitmap.h"
|
||||
#include "BLI_gsqueue.h"
|
||||
#include "BLI_threads.h"
|
||||
|
||||
#include "BKE_paint.h"
|
||||
#include "BKE_pbvh.h"
|
||||
#include "BLI_bitmap.h"
|
||||
#include "BLI_compiler_compat.h"
|
||||
#include "BLI_gsqueue.h"
|
||||
#include "BLI_threads.h"
|
||||
|
||||
struct AutomaskingCache;
|
||||
struct KeyBlock;
|
||||
@@ -300,6 +300,10 @@ void SCULPT_calc_brush_plane(struct Sculpt *sd,
|
||||
|
||||
void SCULPT_calc_area_normal(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]);
|
||||
void SCULPT_calc_area_normal_and_center(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]);
|
||||
void SCULPT_calc_area_center(
|
||||
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]);
|
||||
|
||||
int SCULPT_nearest_vertex_get(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
@@ -1506,3 +1510,115 @@ void SCULPT_OT_dyntopo_detail_size_edit(struct wmOperatorType *ot);
|
||||
/* Dyntopo. */
|
||||
|
||||
void SCULPT_OT_dynamic_topology_toggle(struct wmOperatorType *ot);
|
||||
|
||||
/* sculpt_brushes.c */
|
||||
|
||||
float SCULPT_clay_thumb_get_stabilized_pressure(struct StrokeCache *cache);
|
||||
|
||||
void SCULPT_do_draw_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
|
||||
void SCULPT_do_fill_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_scrape_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_clay_thumb_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_flatten_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_clay_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_clay_strips_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_snake_hook_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_thumb_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_rotate_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_layer_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_inflate_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_nudge_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_crease_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_pinch_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_grab_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_elastic_deform_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_draw_sharp_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_slide_relax_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
|
||||
void SCULPT_do_displacement_smear_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_displacement_eraser_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_mask_brush_draw(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
void SCULPT_do_mask_brush(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
int totnode);
|
||||
|
||||
void SCULPT_bmesh_topology_rake(struct Sculpt *sd,
|
||||
struct Object *ob,
|
||||
struct PBVHNode **nodes,
|
||||
const int totnode,
|
||||
float bstrength);
|
||||
|
||||
/* end sculpt_brushes.c */
|
||||
|
||||
/* sculpt_ops.c */
|
||||
void SCULPT_OT_brush_stroke(struct wmOperatorType *ot);
|
||||
|
||||
/* end sculpt_ops.c */
|
||||
|
||||
1141
source/blender/editors/sculpt_paint/sculpt_ops.c
Normal file
1141
source/blender/editors/sculpt_paint/sculpt_ops.c
Normal file
@@ -0,0 +1,1141 @@
|
||||
/*
|
||||
* 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) 2006 by Nicholas Bishop
|
||||
* All rights reserved.
|
||||
* Implements the Sculpt Mode tools
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup edsculpt
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_array.h"
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_dial_2d.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_gsqueue.h"
|
||||
#include "BLI_hash.h"
|
||||
#include "BLI_link_utils.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_linklist_stack.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_math_color_blend.h"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_rand.h"
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "atomic_ops.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "PIL_time.h"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_ccg.h"
|
||||
#include "BKE_colortools.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_kelvinlet.h"
|
||||
#include "BKE_key.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_fair.h"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
#include "BKE_mesh_mirror.h"
|
||||
#include "BKE_modifier.h"
|
||||
#include "BKE_multires.h"
|
||||
#include "BKE_node.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_paint.h"
|
||||
#include "BKE_particle.h"
|
||||
#include "BKE_pbvh.h"
|
||||
#include "BKE_pointcache.h"
|
||||
#include "BKE_report.h"
|
||||
#include "BKE_scene.h"
|
||||
#include "BKE_screen.h"
|
||||
#include "BKE_subdiv_ccg.h"
|
||||
#include "BKE_subsurf.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "IMB_colormanagement.h"
|
||||
|
||||
#include "GPU_batch.h"
|
||||
#include "GPU_batch_presets.h"
|
||||
#include "GPU_immediate.h"
|
||||
#include "GPU_immediate_util.h"
|
||||
#include "GPU_matrix.h"
|
||||
#include "GPU_state.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_message.h"
|
||||
#include "WM_toolsystem.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "ED_object.h"
|
||||
#include "ED_screen.h"
|
||||
#include "ED_sculpt.h"
|
||||
#include "ED_space_api.h"
|
||||
#include "ED_transform_snap_object_context.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "paint_intern.h"
|
||||
#include "sculpt_intern.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_define.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "bmesh.h"
|
||||
#include "bmesh_tools.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */
|
||||
|
||||
static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
if (!ss) {
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
|
||||
|
||||
MEM_SAFE_FREE(ss->persistent_base);
|
||||
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert,
|
||||
"layer persistent base");
|
||||
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i));
|
||||
SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no);
|
||||
ss->persistent_base[i].disp = 0.0f;
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_set_persistent_base(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Set Persistent Base";
|
||||
ot->idname = "SCULPT_OT_set_persistent_base";
|
||||
ot->description = "Reset the copy of the mesh that is being sculpted on";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_set_persistent_base_exec;
|
||||
ot->poll = SCULPT_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/************************* SCULPT_OT_optimize *************************/
|
||||
|
||||
static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
SCULPT_pbvh_clear(ob);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
/* The BVH gets less optimal more quickly with dynamic topology than
|
||||
* regular sculpting. There is no doubt more clever stuff we can do to
|
||||
* optimize it on the fly, but for now this gives the user a nicer way
|
||||
* to recalculate it than toggling modes. */
|
||||
static void SCULPT_OT_optimize(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Rebuild BVH";
|
||||
ot->idname = "SCULPT_OT_optimize";
|
||||
ot->description = "Recalculate the sculpt BVH to improve performance";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_optimize_exec;
|
||||
ot->poll = SCULPT_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/********************* Dynamic topology symmetrize ********************/
|
||||
|
||||
static bool sculpt_no_multires_poll(bContext *C)
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) {
|
||||
return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
const Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
||||
SculptSession *ss = ob->sculpt;
|
||||
PBVH *pbvh = ss->pbvh;
|
||||
const float dist = RNA_float_get(op->ptr, "merge_tolerance");
|
||||
|
||||
if (!pbvh) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
switch (BKE_pbvh_type(pbvh)) {
|
||||
case PBVH_BMESH:
|
||||
/* Dyntopo Symmetrize. */
|
||||
|
||||
/* To simplify undo for symmetrize, all BMesh elements are logged
|
||||
* as deleted, then after symmetrize operation all BMesh elements
|
||||
* are logged as added (as opposed to attempting to store just the
|
||||
* parts that symmetrize modifies). */
|
||||
SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize");
|
||||
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE);
|
||||
BM_log_before_all_removed(ss->bm, ss->bm_log);
|
||||
|
||||
BM_mesh_toolflags_set(ss->bm, true);
|
||||
|
||||
/* Symmetrize and re-triangulate. */
|
||||
BMO_op_callf(ss->bm,
|
||||
(BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
|
||||
"symmetrize input=%avef direction=%i dist=%f use_shapekey=%b",
|
||||
sd->symmetrize_direction,
|
||||
dist,
|
||||
true);
|
||||
SCULPT_dynamic_topology_triangulate(ss->bm);
|
||||
|
||||
/* Bisect operator flags edges (keep tags clean for edge queue). */
|
||||
BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false);
|
||||
|
||||
BM_mesh_toolflags_set(ss->bm, false);
|
||||
|
||||
/* Finish undo. */
|
||||
BM_log_all_added(ss->bm, ss->bm_log);
|
||||
SCULPT_undo_push_end();
|
||||
|
||||
break;
|
||||
case PBVH_FACES:
|
||||
/* Mesh Symmetrize. */
|
||||
ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize");
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist);
|
||||
|
||||
ED_sculpt_undo_geometry_end(ob);
|
||||
BKE_mesh_calc_normals(ob->data);
|
||||
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
|
||||
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Redraw. */
|
||||
SCULPT_pbvh_clear(ob);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_symmetrize(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Symmetrize";
|
||||
ot->idname = "SCULPT_OT_symmetrize";
|
||||
ot->description = "Symmetrize the topology modifications";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_symmetrize_exec;
|
||||
ot->poll = sculpt_no_multires_poll;
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"merge_tolerance",
|
||||
0.001f,
|
||||
0.0f,
|
||||
FLT_MAX,
|
||||
"Merge Distance",
|
||||
"Distance within which symmetrical vertices are merged",
|
||||
0.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
/**** Toggle operator for turning sculpt mode on or off ****/
|
||||
|
||||
static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
|
||||
{
|
||||
/* Create persistent sculpt mode data. */
|
||||
BKE_sculpt_toolsettings_data_ensure(scene);
|
||||
|
||||
/* Create sculpt mode session data. */
|
||||
if (ob->sculpt != NULL) {
|
||||
BKE_sculptsession_free(ob);
|
||||
}
|
||||
ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session");
|
||||
ob->sculpt->mode_type = OB_MODE_SCULPT;
|
||||
|
||||
BKE_sculpt_ensure_orig_mesh_data(scene, ob);
|
||||
|
||||
BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
|
||||
|
||||
/* This function expects a fully evaluated depsgraph. */
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
|
||||
|
||||
/* Here we can detect geometry that was just added to Sculpt Mode as it has the
|
||||
* SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */
|
||||
/* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not
|
||||
* initialized, which is used is some operators that modify the mesh topology to perform certain
|
||||
* actions in the new polys. After these operations are finished, all polys should have a valid
|
||||
* face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility
|
||||
* correctly. */
|
||||
/* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new
|
||||
* objects, like moving the transform pivot position to the new area or masking existing
|
||||
* geometry. */
|
||||
SculptSession *ss = ob->sculpt;
|
||||
const int new_face_set = SCULPT_face_set_next_available_get(ss);
|
||||
for (int i = 0; i < ss->totfaces; i++) {
|
||||
if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) {
|
||||
ss->face_sets[i] = new_face_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_enter_ex(Main *bmain,
|
||||
Depsgraph *depsgraph,
|
||||
Scene *scene,
|
||||
Object *ob,
|
||||
const bool force_dyntopo,
|
||||
ReportList *reports)
|
||||
{
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *me = BKE_mesh_from_object(ob);
|
||||
|
||||
/* Enter sculpt mode. */
|
||||
ob->mode |= mode_flag;
|
||||
|
||||
sculpt_init_session(bmain, depsgraph, scene, ob);
|
||||
|
||||
if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f &&
|
||||
fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f)) {
|
||||
BKE_report(
|
||||
reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable");
|
||||
}
|
||||
else if (is_negative_m4(ob->obmat)) {
|
||||
BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable");
|
||||
}
|
||||
|
||||
Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT);
|
||||
BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT);
|
||||
|
||||
paint_cursor_start(paint, SCULPT_mode_poll_view3d);
|
||||
|
||||
/* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes,
|
||||
* As long as no data was added that is not supported. */
|
||||
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
|
||||
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
|
||||
|
||||
const char *message_unsupported = NULL;
|
||||
if (me->totloop != me->totpoly * 3) {
|
||||
message_unsupported = TIP_("non-triangle face");
|
||||
}
|
||||
else if (mmd != NULL) {
|
||||
message_unsupported = TIP_("multi-res modifier");
|
||||
}
|
||||
else {
|
||||
enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob);
|
||||
if (flag == 0) {
|
||||
/* pass */
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_VDATA) {
|
||||
message_unsupported = TIP_("vertex data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_EDATA) {
|
||||
message_unsupported = TIP_("edge data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_LDATA) {
|
||||
message_unsupported = TIP_("face data");
|
||||
}
|
||||
else if (flag & DYNTOPO_WARN_MODIFIER) {
|
||||
message_unsupported = TIP_("constructive modifier");
|
||||
}
|
||||
else {
|
||||
BLI_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if ((message_unsupported == NULL) || force_dyntopo) {
|
||||
/* Needed because we may be entering this mode before the undo system loads. */
|
||||
wmWindowManager *wm = bmain->wm.first;
|
||||
bool has_undo = wm->undo_stack != NULL;
|
||||
/* Undo push is needed to prevent memory leak. */
|
||||
if (has_undo) {
|
||||
SCULPT_undo_push_begin(ob, "Dynamic topology enable");
|
||||
}
|
||||
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob);
|
||||
if (has_undo) {
|
||||
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN);
|
||||
SCULPT_undo_push_end();
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_reportf(
|
||||
reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported);
|
||||
me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush object mode. */
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, ReportList *reports)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
|
||||
{
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *me = BKE_mesh_from_object(ob);
|
||||
|
||||
multires_flush_sculpt_updates(ob);
|
||||
|
||||
/* Not needed for now. */
|
||||
#if 0
|
||||
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
|
||||
const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd);
|
||||
#endif
|
||||
|
||||
/* Always for now, so leaving sculpt mode always ensures scene is in
|
||||
* a consistent state. */
|
||||
if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) {
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
|
||||
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
|
||||
/* Dynamic topology must be disabled before exiting sculpt
|
||||
* mode to ensure the undo stack stays in a consistent
|
||||
* state. */
|
||||
sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob);
|
||||
|
||||
/* Store so we know to re-enable when entering sculpt mode. */
|
||||
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
|
||||
}
|
||||
|
||||
/* Leave sculpt mode. */
|
||||
ob->mode &= ~mode_flag;
|
||||
|
||||
BKE_sculptsession_free(ob);
|
||||
|
||||
paint_cursor_delete_textures();
|
||||
|
||||
/* Never leave derived meshes behind. */
|
||||
BKE_object_free_derived_caches(ob);
|
||||
|
||||
/* Flush object mode. */
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
|
||||
}
|
||||
|
||||
void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
|
||||
}
|
||||
|
||||
static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
struct wmMsgBus *mbus = CTX_wm_message_bus(C);
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *ts = scene->toolsettings;
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *ob = OBACT(view_layer);
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
const bool is_mode_set = (ob->mode & mode_flag) != 0;
|
||||
|
||||
if (!is_mode_set) {
|
||||
if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_mode_set) {
|
||||
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
|
||||
}
|
||||
else {
|
||||
if (depsgraph) {
|
||||
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
||||
}
|
||||
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports);
|
||||
BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint);
|
||||
|
||||
if (ob->mode & mode_flag) {
|
||||
Mesh *me = ob->data;
|
||||
/* Dyntopo adds its own undo step. */
|
||||
if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) {
|
||||
/* Without this the memfile undo step is used,
|
||||
* while it works it causes lag when undoing the first undo step, see T71564. */
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
if (wm->op_undo_depth <= 1) {
|
||||
SCULPT_undo_push_begin(ob, op->type->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene);
|
||||
|
||||
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
|
||||
|
||||
WM_toolsystem_update_from_context_view3d(C);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Sculpt Mode";
|
||||
ot->idname = "SCULPT_OT_sculptmode_toggle";
|
||||
ot->description = "Toggle sculpt mode in 3D view";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->exec = sculpt_mode_toggle_exec;
|
||||
ot->poll = ED_operator_object_active_editable_mesh;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ss->preview_vert_index_count = 0;
|
||||
int totpoints = 0;
|
||||
|
||||
/* This function is called from the cursor drawing code, so the PBVH may not be build yet. */
|
||||
if (!ss->pbvh) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ss->deform_modifiers_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
|
||||
|
||||
if (!ss->pmap) {
|
||||
return;
|
||||
}
|
||||
|
||||
float brush_co[3];
|
||||
copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss));
|
||||
|
||||
BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices");
|
||||
|
||||
/* Assuming an average of 6 edges per vertex in a triangulated mesh. */
|
||||
const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2;
|
||||
|
||||
if (ss->preview_vert_index_list == NULL) {
|
||||
ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines");
|
||||
}
|
||||
|
||||
GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int));
|
||||
int active_v = SCULPT_active_vertex_get(ss);
|
||||
BLI_gsqueue_push(not_visited_vertices, &active_v);
|
||||
|
||||
while (!BLI_gsqueue_is_empty(not_visited_vertices)) {
|
||||
int from_v;
|
||||
BLI_gsqueue_pop(not_visited_vertices, &from_v);
|
||||
SculptVertexNeighborIter ni;
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) {
|
||||
if (totpoints + (ni.size * 2) < max_preview_vertices) {
|
||||
int to_v = ni.index;
|
||||
ss->preview_vert_index_list[totpoints] = from_v;
|
||||
totpoints++;
|
||||
ss->preview_vert_index_list[totpoints] = to_v;
|
||||
totpoints++;
|
||||
if (BLI_BITMAP_TEST(visited_vertices, to_v)) {
|
||||
continue;
|
||||
}
|
||||
BLI_BITMAP_ENABLE(visited_vertices, to_v);
|
||||
const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v);
|
||||
if (len_squared_v3v3(brush_co, co) < radius * radius) {
|
||||
BLI_gsqueue_push(not_visited_vertices, &to_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
||||
}
|
||||
|
||||
BLI_gsqueue_free(not_visited_vertices);
|
||||
|
||||
MEM_freeN(visited_vertices);
|
||||
|
||||
ss->preview_vert_index_count = totpoints;
|
||||
}
|
||||
|
||||
static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ID *data;
|
||||
data = ob->data;
|
||||
if (data && ID_IS_LINKED(data)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (ob->type != OB_MESH) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL);
|
||||
if (mloopcol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n);
|
||||
|
||||
const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR);
|
||||
if (MPropCol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n);
|
||||
|
||||
MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP);
|
||||
MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY);
|
||||
|
||||
for (int i = 0; i < mesh->totpoly; i++) {
|
||||
MPoly *c_poly = &polys[i];
|
||||
for (int j = 0; j < c_poly->totloop; j++) {
|
||||
int loop_index = c_poly->loopstart + j;
|
||||
MLoop *c_loop = &loops[c_poly->loopstart + j];
|
||||
float srgb_color[4];
|
||||
linearrgb_to_srgb_v4(srgb_color, vertcols[c_loop->v].color);
|
||||
loopcols[loop_index].r = (char)(srgb_color[0] * 255);
|
||||
loopcols[loop_index].g = (char)(srgb_color[1] * 255);
|
||||
loopcols[loop_index].b = (char)(srgb_color[2] * 255);
|
||||
loopcols[loop_index].a = (char)(srgb_color[3] * 255);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Sculpt Vertex Color to Vertex Color";
|
||||
ot->description = "Copy the Sculpt Vertex Color to a regular color layer";
|
||||
ot->idname = "SCULPT_OT_vertex_to_loop_colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
ot->exec = vertex_to_loop_colors_exec;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op))
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
ID *data;
|
||||
data = ob->data;
|
||||
if (data && ID_IS_LINKED(data)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (ob->type != OB_MESH) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Mesh *mesh = ob->data;
|
||||
|
||||
const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL);
|
||||
if (mloopcol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n);
|
||||
|
||||
const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR);
|
||||
if (MPropCol_layer_n == -1) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n);
|
||||
|
||||
MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP);
|
||||
MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY);
|
||||
|
||||
for (int i = 0; i < mesh->totpoly; i++) {
|
||||
MPoly *c_poly = &polys[i];
|
||||
for (int j = 0; j < c_poly->totloop; j++) {
|
||||
int loop_index = c_poly->loopstart + j;
|
||||
MLoop *c_loop = &loops[c_poly->loopstart + j];
|
||||
vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f);
|
||||
vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f);
|
||||
vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f);
|
||||
vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f);
|
||||
srgb_to_linearrgb_v4(vertcols[c_loop->v].color, vertcols[c_loop->v].color);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Vertex Color to Sculpt Vertex Color";
|
||||
ot->description = "Copy the active loop color layer to the vertex color";
|
||||
ot->idname = "SCULPT_OT_loop_to_vertex_colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
ot->exec = loop_to_vertex_colors_exec;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int sculpt_sample_color_invoke(bContext *C,
|
||||
wmOperator *UNUSED(op),
|
||||
const wmEvent *UNUSED(e))
|
||||
{
|
||||
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
Brush *brush = BKE_paint_brush(&sd->paint);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
int active_vertex = SCULPT_active_vertex_get(ss);
|
||||
const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex);
|
||||
if (!active_vertex_color) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
float color_srgb[3];
|
||||
copy_v3_v3(color_srgb, active_vertex_color);
|
||||
IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb);
|
||||
BKE_brush_color_set(scene, brush, color_srgb);
|
||||
|
||||
WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_sample_color(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Sample Color";
|
||||
ot->idname = "SCULPT_OT_sample_color";
|
||||
ot->description = "Sample the vertex color of the active vertex";
|
||||
|
||||
/* api callbacks */
|
||||
ot->invoke = sculpt_sample_color_invoke;
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the
|
||||
* mask based on the difference between two colors (the active color and the color of any other
|
||||
* vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active
|
||||
* color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer
|
||||
* falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between
|
||||
* masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going
|
||||
* to be.
|
||||
*/
|
||||
#define MASK_BY_COLOR_SLOPE 0.25f
|
||||
|
||||
static float sculpt_mask_by_color_delta_get(const float *color_a,
|
||||
const float *color_b,
|
||||
const float threshold,
|
||||
const bool invert)
|
||||
{
|
||||
float len = len_v3v3(color_a, color_b);
|
||||
/* Normalize len to the (0, 1) range. */
|
||||
len = len / M_SQRT3;
|
||||
|
||||
if (len < threshold - MASK_BY_COLOR_SLOPE) {
|
||||
len = 1.0f;
|
||||
}
|
||||
else if (len >= threshold) {
|
||||
len = 0.0f;
|
||||
}
|
||||
else {
|
||||
len = (-len + threshold) / MASK_BY_COLOR_SLOPE;
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
return 1.0f - len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static float sculpt_mask_by_color_final_mask_get(const float current_mask,
|
||||
const float new_mask,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
if (preserve_mask) {
|
||||
if (invert) {
|
||||
return min_ff(current_mask, new_mask);
|
||||
}
|
||||
return max_ff(current_mask, new_mask);
|
||||
}
|
||||
return new_mask;
|
||||
}
|
||||
|
||||
typedef struct MaskByColorContiguousFloodFillData {
|
||||
float threshold;
|
||||
bool invert;
|
||||
float *new_mask;
|
||||
float initial_color[3];
|
||||
} MaskByColorContiguousFloodFillData;
|
||||
|
||||
static void do_mask_by_color_contiguous_update_nodes_cb(
|
||||
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
|
||||
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK);
|
||||
bool update_node = false;
|
||||
|
||||
const bool invert = data->mask_by_color_invert;
|
||||
const bool preserve_mask = data->mask_by_color_preserve_mask;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
const float current_mask = *vd.mask;
|
||||
const float new_mask = data->mask_by_color_floodfill[vd.index];
|
||||
*vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask);
|
||||
if (current_mask == *vd.mask) {
|
||||
continue;
|
||||
}
|
||||
update_node = true;
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
if (update_node) {
|
||||
BKE_pbvh_node_mark_redraw(data->nodes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool sculpt_mask_by_color_contiguous_floodfill_cb(
|
||||
SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
|
||||
{
|
||||
MaskByColorContiguousFloodFillData *data = userdata;
|
||||
const float *current_color = SCULPT_vertex_color_get(ss, to_v);
|
||||
float new_vertex_mask = sculpt_mask_by_color_delta_get(
|
||||
current_color, data->initial_color, data->threshold, data->invert);
|
||||
data->new_mask[to_v] = new_vertex_mask;
|
||||
|
||||
if (is_duplicate) {
|
||||
data->new_mask[to_v] = data->new_mask[from_v];
|
||||
}
|
||||
|
||||
float len = len_v3v3(current_color, data->initial_color);
|
||||
len = len / M_SQRT3;
|
||||
return len <= data->threshold;
|
||||
}
|
||||
|
||||
static void sculpt_mask_by_color_contiguous(Object *object,
|
||||
const int vertex,
|
||||
const float threshold,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
SculptSession *ss = object->sculpt;
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
|
||||
float *new_mask = MEM_calloc_arrayN(totvert, sizeof(float), "new mask");
|
||||
|
||||
if (invert) {
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
new_mask[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
SculptFloodFill flood;
|
||||
SCULPT_floodfill_init(ss, &flood);
|
||||
SCULPT_floodfill_add_initial(&flood, vertex);
|
||||
|
||||
MaskByColorContiguousFloodFillData ffd;
|
||||
ffd.threshold = threshold;
|
||||
ffd.invert = invert;
|
||||
ffd.new_mask = new_mask;
|
||||
copy_v3_v3(ffd.initial_color, SCULPT_vertex_color_get(ss, vertex));
|
||||
|
||||
SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill_cb, &ffd);
|
||||
SCULPT_floodfill_free(&flood);
|
||||
|
||||
int totnode;
|
||||
PBVHNode **nodes;
|
||||
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.ob = object,
|
||||
.nodes = nodes,
|
||||
.mask_by_color_floodfill = new_mask,
|
||||
.mask_by_color_vertex = vertex,
|
||||
.mask_by_color_threshold = threshold,
|
||||
.mask_by_color_invert = invert,
|
||||
.mask_by_color_preserve_mask = preserve_mask,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(
|
||||
0, totnode, &data, do_mask_by_color_contiguous_update_nodes_cb, &settings);
|
||||
|
||||
MEM_SAFE_FREE(nodes);
|
||||
|
||||
MEM_freeN(new_mask);
|
||||
}
|
||||
|
||||
static void do_mask_by_color_task_cb(void *__restrict userdata,
|
||||
const int n,
|
||||
const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
|
||||
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK);
|
||||
bool update_node = false;
|
||||
|
||||
const float threshold = data->mask_by_color_threshold;
|
||||
const bool invert = data->mask_by_color_invert;
|
||||
const bool preserve_mask = data->mask_by_color_preserve_mask;
|
||||
const float *active_color = SCULPT_vertex_color_get(ss, data->mask_by_color_vertex);
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
const float current_mask = *vd.mask;
|
||||
const float new_mask = sculpt_mask_by_color_delta_get(active_color, vd.col, threshold, invert);
|
||||
*vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask);
|
||||
|
||||
if (current_mask == *vd.mask) {
|
||||
continue;
|
||||
}
|
||||
update_node = true;
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
if (update_node) {
|
||||
BKE_pbvh_node_mark_redraw(data->nodes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static void sculpt_mask_by_color_full_mesh(Object *object,
|
||||
const int vertex,
|
||||
const float threshold,
|
||||
const bool invert,
|
||||
const bool preserve_mask)
|
||||
{
|
||||
SculptSession *ss = object->sculpt;
|
||||
|
||||
int totnode;
|
||||
PBVHNode **nodes;
|
||||
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.ob = object,
|
||||
.nodes = nodes,
|
||||
.mask_by_color_vertex = vertex,
|
||||
.mask_by_color_threshold = threshold,
|
||||
.mask_by_color_invert = invert,
|
||||
.mask_by_color_preserve_mask = preserve_mask,
|
||||
};
|
||||
|
||||
TaskParallelSettings settings;
|
||||
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
||||
BLI_task_parallel_range(0, totnode, &data, do_mask_by_color_task_cb, &settings);
|
||||
|
||||
MEM_SAFE_FREE(nodes);
|
||||
}
|
||||
|
||||
static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
SculptSession *ss = ob->sculpt;
|
||||
|
||||
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
|
||||
|
||||
/* Color data is not available in Multires. */
|
||||
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (!ss->vcol) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
|
||||
/* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move,
|
||||
* so it needs to be updated here. */
|
||||
SculptCursorGeometryInfo sgi;
|
||||
float mouse[2];
|
||||
mouse[0] = event->mval[0];
|
||||
mouse[1] = event->mval[1];
|
||||
SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
|
||||
|
||||
SCULPT_undo_push_begin(ob, "Mask by color");
|
||||
|
||||
const int active_vertex = SCULPT_active_vertex_get(ss);
|
||||
const float threshold = RNA_float_get(op->ptr, "threshold");
|
||||
const bool invert = RNA_boolean_get(op->ptr, "invert");
|
||||
const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask");
|
||||
|
||||
if (RNA_boolean_get(op->ptr, "contiguous")) {
|
||||
sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask);
|
||||
}
|
||||
else {
|
||||
sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask);
|
||||
}
|
||||
|
||||
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask);
|
||||
SCULPT_undo_push_end();
|
||||
|
||||
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Mask by Color";
|
||||
ot->idname = "SCULPT_OT_mask_by_color";
|
||||
ot->description = "Creates a mask based on the sculpt vertex colors";
|
||||
|
||||
/* api callbacks */
|
||||
ot->invoke = sculpt_mask_by_color_invoke;
|
||||
ot->poll = SCULPT_vertex_colors_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
|
||||
ot->prop = RNA_def_boolean(
|
||||
ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas");
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask");
|
||||
ot->prop = RNA_def_boolean(
|
||||
ot->srna,
|
||||
"preserve_previous_mask",
|
||||
false,
|
||||
"Preserve Previous Mask",
|
||||
"Preserve the previous mask and add or subtract the new one generated by the colors");
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"threshold",
|
||||
0.35f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Threshold",
|
||||
"How much changes in color affect the mask generation",
|
||||
0.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
void ED_operatortypes_sculpt(void)
|
||||
{
|
||||
WM_operatortype_append(SCULPT_OT_brush_stroke);
|
||||
WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
|
||||
WM_operatortype_append(SCULPT_OT_set_persistent_base);
|
||||
WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle);
|
||||
WM_operatortype_append(SCULPT_OT_optimize);
|
||||
WM_operatortype_append(SCULPT_OT_symmetrize);
|
||||
WM_operatortype_append(SCULPT_OT_detail_flood_fill);
|
||||
WM_operatortype_append(SCULPT_OT_sample_detail_size);
|
||||
WM_operatortype_append(SCULPT_OT_set_detail_size);
|
||||
WM_operatortype_append(SCULPT_OT_mesh_filter);
|
||||
WM_operatortype_append(SCULPT_OT_mask_filter);
|
||||
WM_operatortype_append(SCULPT_OT_dirty_mask);
|
||||
WM_operatortype_append(SCULPT_OT_mask_expand);
|
||||
WM_operatortype_append(SCULPT_OT_set_pivot_position);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_create);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_change_visibility);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_init);
|
||||
WM_operatortype_append(SCULPT_OT_cloth_filter);
|
||||
WM_operatortype_append(SCULPT_OT_face_sets_edit);
|
||||
WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_face_set_box_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_trim_box_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_trim_lasso_gesture);
|
||||
WM_operatortype_append(SCULPT_OT_project_line_gesture);
|
||||
|
||||
WM_operatortype_append(SCULPT_OT_sample_color);
|
||||
WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors);
|
||||
WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors);
|
||||
WM_operatortype_append(SCULPT_OT_color_filter);
|
||||
WM_operatortype_append(SCULPT_OT_mask_by_color);
|
||||
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
|
||||
WM_operatortype_append(SCULPT_OT_mask_init);
|
||||
|
||||
WM_operatortype_append(SCULPT_OT_expand);
|
||||
}
|
||||
Reference in New Issue
Block a user