Sculpt: Pose Brush with Inverse Kinematics

This commits introduces the pose_ik_segments brush property in the Pose Brush. When increasing the IK segments count, the brush generates more segments and weights associations following the topology of the mesh. When moving the brush, these segments are transformed using an IK solver and they are used to deform the mesh.

When pressing Ctrl, the brush controls the segments' roll rotation instead of using the IK solver. The brush falloff controls how much rotation is propagated from the first to the last segment in the chain.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D6389
This commit is contained in:
Pablo Dobarro 2020-01-07 16:46:56 +01:00
parent bd766f8f06
commit fdf89acc86
12 changed files with 547 additions and 198 deletions

View File

@ -616,9 +616,12 @@ def brush_settings(layout, context, brush, popover=False):
layout.separator()
if brush.sculpt_tool == 'POSE':
row = layout.row()
row.prop(brush, "pose_offset")
layout.separator()
layout.prop(brush, "pose_offset")
layout.prop(brush, "pose_smooth_iterations")
layout.prop(brush, "pose_ik_segments")
layout.separator()
if brush.sculpt_tool == 'SCRAPE':
row = layout.row()
row.prop(brush, "invert_to_scrape_fill", text="Invert to Fill")

View File

@ -98,6 +98,20 @@ typedef enum eOverlayControlFlags {
(PAINT_OVERLAY_OVERRIDE_SECONDARY | PAINT_OVERLAY_OVERRIDE_PRIMARY | \
PAINT_OVERLAY_OVERRIDE_CURSOR)
/* Defines 8 areas resulting of splitting the object space by the XYZ axis planes. This is used to
* flip or mirror transform values depending on where the vertex is and where the transform
* operation started to support XYZ symmetry on those operations in a predictable way. */
#define AREA_SYMM_DEFAULT 0
typedef enum ePaintSymmetryAreas {
AREA_SYMM_X = (1 << 0),
AREA_SYMM_Y = (1 << 1),
AREA_SYMM_Z = (1 << 2),
} ePaintSymmetryAreas;
#define PAINT_SYMM_AREAS 8
void BKE_paint_invalidate_overlay_tex(struct Scene *scene,
struct ViewLayer *view_layer,
const struct Tex *tex);
@ -211,6 +225,29 @@ struct SculptVertexPaintGeomMap {
struct MeshElemMap *vert_to_poly;
};
/* Pose Brush IK Chain */
typedef struct PoseIKChainSegment {
float orig[3];
float head[3];
float initial_orig[3];
float initial_head[3];
float len;
float rot[4];
float *weights;
/* Store a 4x4 transform matrix for each of the possible combinations of enabled XYZ symmetry
* axis. */
float trans_mat[PAINT_SYMM_AREAS][4][4];
float pivot_mat[PAINT_SYMM_AREAS][4][4];
float pivot_mat_inv[PAINT_SYMM_AREAS][4][4];
} PoseIKChainSegment;
typedef struct PoseIKChain {
PoseIKChainSegment *segments;
int tot_segments;
} PoseIKChain;
/* Session data (mode-specific) */
typedef struct SculptSession {
@ -273,7 +310,10 @@ typedef struct SculptSession {
/* Dynamic mesh preview */
int *preview_vert_index_list;
int preview_vert_index_count;
/* Pose Brush Preview */
float pose_origin[3];
PoseIKChain *pose_ik_chain_preview;
/* Transform operator */
float pivot_pos[3];

View File

@ -992,6 +992,7 @@ void BKE_brush_sculpt_reset(Brush *br)
break;
case SCULPT_TOOL_POSE:
br->pose_smooth_iterations = 4;
br->pose_ik_segments = 1;
br->flag &= ~BRUSH_ALPHA_PRESSURE;
br->flag &= ~BRUSH_SPACE;
br->flag &= ~BRUSH_SPACE_ATTEN;

View File

@ -1096,6 +1096,14 @@ void BKE_sculptsession_free(Object *ob)
MEM_freeN(ss->preview_vert_index_list);
}
if (ss->pose_ik_chain_preview) {
for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) {
MEM_SAFE_FREE(ss->pose_ik_chain_preview->segments[i].weights);
}
MEM_SAFE_FREE(ss->pose_ik_chain_preview->segments);
MEM_SAFE_FREE(ss->pose_ik_chain_preview);
}
BKE_sculptsession_free_vwpaint_data(ob->sculpt);
MEM_freeN(ss);

View File

@ -4323,5 +4323,12 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
br->add_col[3] = 0.9f;
br->sub_col[3] = 0.9f;
}
/* Pose brush IK segments. */
if (!DNA_struct_elem_find(fd->filesdna, "Brush", "int", "pose_ik_segments")) {
for (Brush *br = bmain->brushes.first; br; br = br->id.next) {
br->pose_ik_segments = 1;
}
}
}
}

View File

@ -1461,15 +1461,30 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
cursor_draw_point_with_symmetry(pos, ar, gi.active_vertex_co, sd, vc.obact, rds);
}
/* Draw pose brush origin */
/* Draw pose brush origins. */
if (brush->sculpt_tool == SCULPT_TOOL_POSE) {
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.8f);
if (update_previews) {
BKE_sculpt_update_object_for_edit(depsgraph, vc.obact, true, false);
sculpt_pose_calc_pose_data(
sd, vc.obact, ss, gi.location, rds, brush->pose_offset, ss->pose_origin, NULL);
/* Free the previous pose brush preview. */
if (ss->pose_ik_chain_preview) {
sculpt_pose_ik_chain_free(ss->pose_ik_chain_preview);
}
/* Generate a new pose brush preview from the current cursor location. */
ss->pose_ik_chain_preview = sculpt_pose_ik_chain_init(
sd, vc.obact, ss, brush, gi.location, rds);
}
/* Draw the pose brush rotation origins. */
for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) {
cursor_draw_point_screen_space(pos,
ar,
ss->pose_ik_chain_preview->segments[i].initial_orig,
vc.obact->obmat,
3);
}
cursor_draw_point_screen_space(pos, ar, ss->pose_origin, vc.obact->obmat, 5);
}
/* Draw 3D brush cursor */
@ -1518,9 +1533,13 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
if (brush->sculpt_tool == SCULPT_TOOL_POSE) {
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.8f);
GPU_line_width(2.0f);
immBegin(GPU_PRIM_LINES, 2);
immVertex3fv(pos, ss->pose_origin);
immVertex3fv(pos, gi.location);
immBegin(GPU_PRIM_LINES, ss->pose_ik_chain_preview->tot_segments * 2);
for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) {
immVertex3fv(pos, ss->pose_ik_chain_preview->segments[i].initial_orig);
immVertex3fv(pos, ss->pose_ik_chain_preview->segments[i].initial_head);
}
immEnd();
}

View File

@ -45,6 +45,7 @@ struct wmOperator;
struct wmOperatorType;
struct wmWindowManager;
enum ePaintMode;
enum ePaintSymmetryFlags;
typedef struct CoNo {
float co[3];
@ -306,8 +307,8 @@ bool mask_paint_poll(struct bContext *C);
bool paint_curve_poll(struct bContext *C);
bool facemask_paint_poll(struct bContext *C);
void flip_v3_v3(float out[3], const float in[3], const char symm);
void flip_qt_qt(float out[3], const float in[3], const char symm);
void flip_v3_v3(float out[3], const float in[3], const enum ePaintSymmetryFlags symm);
void flip_qt_qt(float out[3], const float in[3], const enum ePaintSymmetryFlags symm);
/* stroke operator */
typedef enum BrushStrokeMode {

View File

@ -412,7 +412,7 @@ static Image *imapaint_face_image(Object *ob, Mesh *me, int face_index)
}
/* Uses symm to selectively flip any axis of a coordinate. */
void flip_v3_v3(float out[3], const float in[3], const char symm)
void flip_v3_v3(float out[3], const float in[3], const ePaintSymmetryFlags symm)
{
if (symm & PAINT_SYMM_X) {
out[0] = -in[0];
@ -434,7 +434,7 @@ void flip_v3_v3(float out[3], const float in[3], const char symm)
}
}
void flip_qt_qt(float out[4], const float in[4], const char symm)
void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm)
{
float axis[3], angle;

View File

@ -443,7 +443,7 @@ static void nearest_vertex_get_reduce(const void *__restrict UNUSED(userdata),
}
static int sculpt_nearest_vertex_get(
Sculpt *sd, Object *ob, float co[3], float max_distance, bool use_original)
Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original)
{
SculptSession *ss = ob->sculpt;
PBVHNode **nodes = NULL;
@ -1330,11 +1330,16 @@ static void sculpt_automasking_init(Sculpt *sd, Object *ob)
/* ===== Sculpting =====
*/
static void flip_v3(float v[3], const char symm)
static void flip_v3(float v[3], const ePaintSymmetryFlags symm)
{
flip_v3_v3(v, v, symm);
}
static void flip_qt(float quat[3], const ePaintSymmetryFlags symm)
{
flip_qt_qt(quat, quat, symm);
}
static float calc_overlap(StrokeCache *cache, const char symm, const char axis, const float angle)
{
float mirror[3];
@ -1921,7 +1926,8 @@ float tex_strength(SculptSession *ss,
bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v)
{
SculptSearchSphereData *data = data_v;
float *center, nearest[3];
const float *center;
float nearest[3];
if (data->center) {
center = data->center;
}
@ -3527,15 +3533,141 @@ static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in
BKE_pbvh_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings);
}
static ePaintSymmetryAreas sculpt_get_vertex_symm_area(const float co[3])
{
ePaintSymmetryAreas symm_area = AREA_SYMM_DEFAULT;
if (co[0] < 0.0f) {
symm_area |= AREA_SYMM_X;
}
if (co[1] < 0.0f) {
symm_area |= AREA_SYMM_Y;
}
if (co[2] < 0.0f) {
symm_area |= AREA_SYMM_Z;
}
return symm_area;
}
static void sculpt_flip_v3_by_symm_area(float v[3],
const ePaintSymmetryFlags symm,
const ePaintSymmetryAreas symmarea,
const float pivot[3])
{
for (char i = 0; i < 3; i++) {
ePaintSymmetryFlags symm_it = 1 << i;
if (symm & symm_it) {
if (symmarea & symm_it) {
flip_v3(v, symm_it);
}
if (pivot[0] < 0) {
flip_v3(v, symm_it);
}
}
}
}
static void sculpt_flip_quat_by_symm_area(float quat[3],
const ePaintSymmetryFlags symm,
const ePaintSymmetryAreas symmarea,
const float pivot[3])
{
for (char i = 0; i < 3; i++) {
ePaintSymmetryFlags symm_it = 1 << i;
if (symm & symm_it) {
if (symmarea & symm_it) {
flip_qt(quat, symm_it);
}
if (pivot[0] < 0) {
flip_qt(quat, symm_it);
}
}
}
}
static void pose_solve_ik_chain(PoseIKChain *ik_chain, const float initial_target[3])
{
PoseIKChainSegment *segments = ik_chain->segments;
int tot_segments = ik_chain->tot_segments;
float target[3];
/* Set the initial target. */
copy_v3_v3(target, initial_target);
/* Solve the positions and rotations of all segments in the chain. */
for (int i = 0; i < tot_segments; i++) {
float initial_orientation[3];
float current_orientation[3];
float current_head_position[3];
float current_origin_position[3];
/* Calculate the rotation to orientate the segment to the target from its initial state. */
sub_v3_v3v3(current_orientation, target, segments[i].orig);
normalize_v3(current_orientation);
sub_v3_v3v3(initial_orientation, segments[i].initial_head, segments[i].initial_orig);
normalize_v3(initial_orientation);
rotation_between_vecs_to_quat(segments[i].rot, initial_orientation, current_orientation);
/* Rotate the segment by calculating a new head position. */
madd_v3_v3v3fl(current_head_position, segments[i].orig, current_orientation, segments[i].len);
/* Move the origin of the segment towards the target. */
sub_v3_v3v3(current_origin_position, target, current_head_position);
/* Store the new head and origin positions to the segment. */
copy_v3_v3(segments[i].head, current_head_position);
add_v3_v3(segments[i].orig, current_origin_position);
/* Use the origin of this segment as target for the next segment in the chain. */
copy_v3_v3(target, segments[i].orig);
}
/* Move back the whole chain to preserve the anchor point. */
float anchor_diff[3];
sub_v3_v3v3(
anchor_diff, segments[tot_segments - 1].initial_orig, segments[tot_segments - 1].orig);
for (int i = 0; i < tot_segments; i++) {
add_v3_v3(segments[i].orig, anchor_diff);
add_v3_v3(segments[i].head, anchor_diff);
}
}
static void pose_solve_roll_chain(PoseIKChain *ik_chain, const Brush *brush, const float roll)
{
PoseIKChainSegment *segments = ik_chain->segments;
int tot_segments = ik_chain->tot_segments;
for (int i = 0; i < tot_segments; i++) {
float initial_orientation[3];
float initial_rotation[4];
float current_rotation[4];
sub_v3_v3v3(initial_orientation, segments[i].initial_head, segments[i].initial_orig);
normalize_v3(initial_orientation);
/* Calculate the current roll angle using the brush curve. */
float current_roll = roll * BKE_brush_curve_strength(brush, i, tot_segments);
axis_angle_normalized_to_quat(initial_rotation, initial_orientation, 0.0f);
axis_angle_normalized_to_quat(current_rotation, initial_orientation, current_roll);
/* Store the difference of the rotations in the segment rotation. */
rotation_between_quats_to_quat(segments[i].rot, current_rotation, initial_rotation);
}
}
static void do_pose_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PoseIKChain *ik_chain = ss->cache->pose_ik_chain;
PoseIKChainSegment *segments = ik_chain->segments;
PBVHVertexIter vd;
float disp[3], val[3];
float disp[3], new_co[3];
float final_pos[3];
SculptOrigVertData orig_data;
@ -3543,25 +3675,41 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
sculpt_orig_vert_data_update(&orig_data, &vd);
if (check_vertex_pivot_symmetry(
orig_data.co, data->pose_initial_co, ss->cache->mirror_symmetry_pass)) {
copy_v3_v3(val, orig_data.co);
mul_m4_v3(data->transform_trans_inv, val);
mul_m4_v3(data->transform_rot, val);
mul_m4_v3(data->transform_trans, val);
sub_v3_v3v3(disp, val, orig_data.co);
mul_v3_fl(disp, ss->cache->pose_factor[vd.index]);
float total_disp[3];
zero_v3(total_disp);
ePaintSymmetryAreas symm_area = sculpt_get_vertex_symm_area(orig_data.co);
/* Calculate the displacement of each vertex for all the segments in the chain. */
for (int ik = 0; ik < ik_chain->tot_segments; ik++) {
copy_v3_v3(new_co, orig_data.co);
/* Get the transform matrix for the vertex symmetry area to calculate a displacement in the
* vertex. */
mul_m4_v3(segments[ik].pivot_mat_inv[(int)symm_area], new_co);
mul_m4_v3(segments[ik].trans_mat[(int)symm_area], new_co);
mul_m4_v3(segments[ik].pivot_mat[(int)symm_area], new_co);
/* Apply the segment weight of the vertex to the displacement. */
sub_v3_v3v3(disp, new_co, orig_data.co);
mul_v3_fl(disp, segments[ik].weights[vd.index]);
/* Apply the vertex mask to the displacement. */
float mask = vd.mask ? *vd.mask : 0.0f;
mul_v3_fl(disp, 1.0f - mask);
add_v3_v3v3(final_pos, orig_data.co, disp);
copy_v3_v3(vd.co, final_pos);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
/* Accumulate the displacement. */
add_v3_v3(total_disp, disp);
}
/* Apply the accumulated displacement to the vertex. */
add_v3_v3v3(final_pos, orig_data.co, total_disp);
copy_v3_v3(vd.co, final_pos);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BKE_pbvh_vertex_iter_end;
@ -3571,32 +3719,75 @@ static void do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
float grab_delta[3], rot_quat[4], initial_v[3], current_v[3], temp[3];
float pose_origin[3];
float pose_initial_co[3];
float transform_rot[4][4], transform_trans[4][4], transform_trans_inv[4][4];
float grab_delta[3];
float ik_target[3];
const ePaintSymmetryFlags symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
/* The pose brush applies all enabled symmetry axis in a sigle iteration, so the rest can be
* ignored. */
if (ss->cache->mirror_symmetry_pass != 0) {
return;
}
copy_v3_v3(pose_origin, ss->cache->pose_origin);
flip_v3(pose_origin, (char)ss->cache->mirror_symmetry_pass);
PoseIKChain *ik_chain = ss->cache->pose_ik_chain;
copy_v3_v3(pose_initial_co, ss->cache->pose_initial_co);
flip_v3(pose_initial_co, (char)ss->cache->mirror_symmetry_pass);
/* Solve the positions and rotations of the IK chain. */
if (ss->cache->invert) {
/* Roll Mode */
/* Calculate the maximum roll. 0.02 radians per pixel works fine. */
float roll = (ss->cache->initial_mouse[0] - ss->cache->mouse[0]) * ss->cache->bstrength *
0.02f;
BKE_curvemapping_initialize(brush->curve);
pose_solve_roll_chain(ik_chain, brush, roll);
}
else {
/* IK follow target mode */
/* Calculate the IK target. */
sub_v3_v3v3(initial_v, pose_initial_co, pose_origin);
normalize_v3(initial_v);
copy_v3_v3(grab_delta, ss->cache->grab_delta);
copy_v3_v3(ik_target, ss->cache->true_location);
add_v3_v3(ik_target, ss->cache->grab_delta);
add_v3_v3v3(temp, pose_initial_co, grab_delta);
sub_v3_v3v3(current_v, temp, pose_origin);
normalize_v3(current_v);
/* Solve the IK positions */
pose_solve_ik_chain(ik_chain, ik_target);
}
rotation_between_vecs_to_quat(rot_quat, initial_v, current_v);
unit_m4(transform_rot);
unit_m4(transform_trans);
quat_to_mat4(transform_rot, rot_quat);
translate_m4(transform_trans, pose_origin[0], pose_origin[1], pose_origin[2]);
invert_m4_m4(transform_trans_inv, transform_trans);
/* Flip the segment chain in all symmetry axis and calculate the transform matrices for each
* possible combination. */
/* This can be optimized by skipping the calculation of matrices where the symmetry is not
* enabled. */
for (int symm_it = 0; symm_it < PAINT_SYMM_AREAS; symm_it++) {
for (int i = 0; i < brush->pose_ik_segments; i++) {
float symm_rot[4];
float symm_orig[3];
float symm_initial_orig[3];
ePaintSymmetryAreas symm_area = symm_it;
copy_qt_qt(symm_rot, ik_chain->segments[i].rot);
copy_v3_v3(symm_orig, ik_chain->segments[i].orig);
copy_v3_v3(symm_initial_orig, ik_chain->segments[i].initial_orig);
/* Flip the origins and rotation quats of each segment. */
sculpt_flip_quat_by_symm_area(symm_rot, symm, symm_area, ss->cache->orig_grab_location);
sculpt_flip_v3_by_symm_area(symm_orig, symm, symm_area, ss->cache->orig_grab_location);
sculpt_flip_v3_by_symm_area(
symm_initial_orig, symm, symm_area, ss->cache->orig_grab_location);
/* Create the transform matrix and store it in the segment. */
unit_m4(ik_chain->segments[i].pivot_mat[symm_it]);
quat_to_mat4(ik_chain->segments[i].trans_mat[symm_it], symm_rot);
translate_m4(ik_chain->segments[i].trans_mat[symm_it],
symm_orig[0] - symm_initial_orig[0],
symm_orig[1] - symm_initial_orig[1],
symm_orig[2] - symm_initial_orig[2]);
translate_m4(
ik_chain->segments[i].pivot_mat[symm_it], symm_orig[0], symm_orig[1], symm_orig[2]);
invert_m4_m4(ik_chain->segments[i].pivot_mat_inv[symm_it],
ik_chain->segments[i].pivot_mat[symm_it]);
}
}
SculptThreadedTaskData data = {
.sd = sd,
@ -3604,11 +3795,6 @@ static void do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
.brush = brush,
.nodes = nodes,
.grab_delta = grab_delta,
.pose_origin = pose_origin,
.pose_initial_co = pose_initial_co,
.transform_rot = transform_rot,
.transform_trans = transform_trans,
.transform_trans_inv = transform_trans_inv,
};
PBVHParallelSettings settings;
@ -3629,23 +3815,24 @@ static void pose_brush_grow_factor_task_cb_ex(void *__restrict userdata,
PoseGrowFactorTLSData *gftd = tls->userdata_chunk;
SculptSession *ss = data->ob->sculpt;
const char symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const float *active_co = sculpt_active_vertex_co_get(ss);
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
SculptVertexNeighborIter ni;
float max = 0.0f;
/* Grow the factor. */
sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
{
float vmask_f = data->prev_mask[ni.index];
if (vmask_f > max) {
max = vmask_f;
}
max = MAX2(vmask_f, max);
}
sculpt_vertex_neighbors_iter_end(ni);
if (max != data->prev_mask[vd.index]) {
/* Keep the count of the vertices that where added to the factors in this grow iteration. */
if (max > data->prev_mask[vd.index]) {
data->pose_factor[vd.index] = max;
if (check_vertex_pivot_symmetry(vd.co, active_co, symm)) {
if (check_vertex_pivot_symmetry(vd.co, data->pose_initial_co, symm)) {
add_v3_v3(gftd->pos_avg, vd.co);
gftd->pos_count++;
}
@ -3665,9 +3852,16 @@ static void pose_brush_grow_factor_reduce(const void *__restrict UNUSED(userdata
join->pos_count += gftd->pos_count;
}
/* Grow the factor until its boundary is near to the offset pose origin */
static void sculpt_pose_grow_pose_factor(
Sculpt *sd, Object *ob, SculptSession *ss, float pose_origin[3], float *pose_factor)
/* Grow the factor until its boundary is near to the offset pose origin or outside the target
* distance. */
static void sculpt_pose_grow_pose_factor(Sculpt *sd,
Object *ob,
SculptSession *ss,
float pose_origin[3],
float pose_target[3],
float max_len,
float *r_pose_origin,
float *pose_factor)
{
PBVHNode **nodes;
PBVH *pbvh = ob->sculpt->pbvh;
@ -3681,6 +3875,8 @@ static void sculpt_pose_grow_pose_factor(
.totnode = totnode,
.pose_factor = pose_factor,
};
data.pose_initial_co = pose_target;
PBVHParallelSettings settings;
PoseGrowFactorTLSData gftd;
gftd.pos_count = 0;
@ -3698,19 +3894,44 @@ static void sculpt_pose_grow_pose_factor(
gftd.pos_count = 0;
memcpy(data.prev_mask, pose_factor, sculpt_vertex_count_get(ss) * sizeof(float));
BKE_pbvh_parallel_range(0, totnode, &data, pose_brush_grow_factor_task_cb_ex, &settings);
if (gftd.pos_count != 0) {
mul_v3_fl(gftd.pos_avg, 1.0f / (float)gftd.pos_count);
float len = len_v3v3(gftd.pos_avg, pose_origin);
if (len < prev_len) {
prev_len = len;
grow_next_iteration = true;
if (pose_origin) {
/* Test with pose origin. Used when growing the factors to compensate the Origin Offset. */
/* Stop when the factor's avg_pos starts moving away from the origin instead of getting
* closert to it. */
float len = len_v3v3(gftd.pos_avg, pose_origin);
if (len < prev_len) {
prev_len = len;
grow_next_iteration = true;
}
else {
grow_next_iteration = false;
memcpy(pose_factor, data.prev_mask, sculpt_vertex_count_get(ss) * sizeof(float));
}
}
else {
grow_next_iteration = false;
memcpy(pose_factor, data.prev_mask, sculpt_vertex_count_get(ss) * sizeof(float));
/* Test with length. Used to calculate the origin positions of the IK chain. */
/* Stops when the factors have grown enough to generate a new segment origin. */
float len = len_v3v3(gftd.pos_avg, pose_target);
if (len < max_len) {
prev_len = len;
grow_next_iteration = true;
}
else {
grow_next_iteration = false;
if (r_pose_origin) {
copy_v3_v3(r_pose_origin, gftd.pos_avg);
}
memcpy(pose_factor, data.prev_mask, sculpt_vertex_count_get(ss) * sizeof(float));
}
}
}
else {
if (r_pose_origin) {
copy_v3_v3(r_pose_origin, pose_target);
}
grow_next_iteration = false;
}
}
@ -3736,10 +3957,6 @@ static bool sculpt_pose_brush_is_vertex_inside_brush_radius(const float vertex[3
return false;
}
/* Calculate the pose origin and (Optionaly the pose factor) that is used when using the pose brush
*
* r_pose_origin must be a valid pointer. the r_pose_factor is optional. When set to NULL it won't
* be calculated. */
typedef struct PoseFloodFillData {
float pose_initial_co[3];
float radius;
@ -3774,6 +3991,10 @@ static bool pose_floodfill_cb(
return false;
}
/* Calculate the pose origin and (Optionaly the pose factor) that is used when using the pose brush
*
* r_pose_origin must be a valid pointer. the r_pose_factor is optional. When set to NULL it won't
* be calculated. */
void sculpt_pose_calc_pose_data(Sculpt *sd,
Object *ob,
SculptSession *ss,
@ -3812,8 +4033,11 @@ void sculpt_pose_calc_pose_data(Sculpt *sd,
madd_v3_v3fl(fdata.pose_origin, pose_d, radius * pose_offset);
copy_v3_v3(r_pose_origin, fdata.pose_origin);
/* Do the initial grow of the factors to get the first segment of the chain with Origin Offset.
*/
if (pose_offset != 0.0f && r_pose_factor) {
sculpt_pose_grow_pose_factor(sd, ob, ss, fdata.pose_origin, r_pose_factor);
sculpt_pose_grow_pose_factor(
sd, ob, ss, fdata.pose_origin, fdata.pose_origin, 0, NULL, r_pose_factor);
}
}
@ -3827,33 +4051,137 @@ static void pose_brush_init_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
SculptVertexNeighborIter ni;
float avg = 0;
int total = 0;
float avg = 0.0f;
int total = 0.0f;
sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
{
avg += ss->cache->pose_factor[ni.index];
avg += data->pose_factor[ni.index];
total++;
}
sculpt_vertex_neighbors_iter_end(ni);
if (total > 0) {
ss->cache->pose_factor[vd.index] = avg / (float)total;
data->pose_factor[vd.index] = avg / total;
}
}
BKE_pbvh_vertex_iter_end;
}
static void sculpt_pose_brush_init(
Sculpt *sd, Object *ob, SculptSession *ss, Brush *br, float initial_location[3], float radius)
void sculpt_pose_ik_chain_free(PoseIKChain *ik_chain)
{
float *pose_factor = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(float), "Pose factor");
for (int i = 0; i < ik_chain->tot_segments; i++) {
MEM_SAFE_FREE(ik_chain->segments[i].weights);
}
MEM_SAFE_FREE(ik_chain->segments);
MEM_SAFE_FREE(ik_chain);
}
sculpt_pose_calc_pose_data(
sd, ob, ss, initial_location, radius, br->pose_offset, ss->cache->pose_origin, pose_factor);
PoseIKChain *sculpt_pose_ik_chain_init(Sculpt *sd,
Object *ob,
SculptSession *ss,
Brush *br,
const float initial_location[3],
const float radius)
{
copy_v3_v3(ss->cache->pose_initial_co, initial_location);
ss->cache->pose_factor = pose_factor;
float chain_end[3];
float chain_segment_len = len_v3v3(initial_location, chain_end) / br->pose_ik_segments;
chain_segment_len = radius * (1.0f + br->pose_offset);
float next_chain_segment_target[3];
int totvert = sculpt_vertex_count_get(ss);
int nearest_vertex_index = sculpt_nearest_vertex_get(sd, ob, initial_location, FLT_MAX, true);
/* Init the buffers used to keep track of the changes in the pose factors as more segments are
* added to the IK chain. */
/* This stores the whole pose factors values as they grow through the mesh. */
float *pose_factor_grow = MEM_callocN(totvert * sizeof(float), "Pose Factor Grow");
/* This stores the previous status of the factors when growing a new iteration. */
float *pose_factor_grow_prev = MEM_callocN(totvert * sizeof(float),
"Pose Factor Grow Prev Iteration");
pose_factor_grow[nearest_vertex_index] = 1.0f;
/* Init the IK chain with empty weights. */
PoseIKChain *ik_chain = MEM_callocN(sizeof(PoseIKChain), "Pose IK Chain");
ik_chain->tot_segments = br->pose_ik_segments;
ik_chain->segments = MEM_callocN(ik_chain->tot_segments * sizeof(PoseIKChainSegment),
"Pose IK Chain Segments");
for (int i = 0; i < br->pose_ik_segments; i++) {
ik_chain->segments[i].weights = MEM_callocN(totvert * sizeof(float), "Pose IK weights");
}
/* Calculate the first segment in the chain using the brush radius and the pose origin offset. */
copy_v3_v3(next_chain_segment_target, initial_location);
sculpt_pose_calc_pose_data(sd,
ob,
ss,
next_chain_segment_target,
radius,
br->pose_offset,
ik_chain->segments[0].orig,
pose_factor_grow);
copy_v3_v3(next_chain_segment_target, ik_chain->segments[0].orig);
/* Init the weights of this segment and store the status of the pose factors to start calculating
* new segment origins. */
for (int j = 0; j < totvert; j++) {
ik_chain->segments[0].weights[j] = pose_factor_grow[j];
pose_factor_grow_prev[j] = pose_factor_grow[j];
}
/* Calculate the next segments in the chain growing the pose factors. */
for (int i = 1; i < ik_chain->tot_segments; i++) {
/* Grow the factors to get the new segment origin. */
sculpt_pose_grow_pose_factor(sd,
ob,
ss,
NULL,
next_chain_segment_target,
chain_segment_len,
ik_chain->segments[i].orig,
pose_factor_grow);
copy_v3_v3(next_chain_segment_target, ik_chain->segments[i].orig);
/* Create the weights for this segment from the difference between the previous grow factor
* iteration an the current iteration. */
for (int j = 0; j < totvert; j++) {
ik_chain->segments[i].weights[j] = pose_factor_grow[j] - pose_factor_grow_prev[j];
/* Store the current grow factor status for the next interation. */
pose_factor_grow_prev[j] = pose_factor_grow[j];
}
}
/* Init the origin/head pairs of all the segments from the calculated origins. */
float origin[3];
float head[3];
for (int i = 0; i < ik_chain->tot_segments; i++) {
if (i == 0) {
copy_v3_v3(head, initial_location);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
else {
copy_v3_v3(head, ik_chain->segments[i - 1].orig);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
copy_v3_v3(ik_chain->segments[i].orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_head, head);
ik_chain->segments[i].len = len_v3v3(head, origin);
}
MEM_freeN(pose_factor_grow);
MEM_freeN(pose_factor_grow_prev);
return ik_chain;
}
static void sculpt_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br)
{
PBVHNode **nodes;
PBVH *pbvh = ob->sculpt->pbvh;
int totnode;
@ -3867,11 +4195,18 @@ static void sculpt_pose_brush_init(
.nodes = nodes,
};
/* Smooth the pose brush factor for cleaner deformation */
for (int i = 0; i < br->pose_smooth_iterations; i++) {
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(0, totnode, &data, pose_brush_init_task_cb_ex, &settings);
/* Init the IK chain that is going to be used to deform the vertices. */
ss->cache->pose_ik_chain = sculpt_pose_ik_chain_init(
sd, ob, ss, br, ss->cache->true_location, ss->cache->radius);
/* Smooth the weights of each segment for cleaner deformation. */
for (int ik = 0; ik < ss->cache->pose_ik_chain->tot_segments; ik++) {
data.pose_factor = ss->cache->pose_ik_chain->segments[ik].weights;
for (int i = 0; i < br->pose_smooth_iterations; i++) {
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(0, totnode, &data, pose_brush_init_task_cb_ex, &settings);
}
}
MEM_SAFE_FREE(nodes);
@ -5625,24 +5960,14 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
/* Build a list of all nodes that are potentially within the brush's area of influence */
/* These brushes need to update all nodes as they are not constrained by the brush radius */
if (brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) {
/* Elastic deform needs all nodes to avoid artifacts as the effect of the brush is not
* constrained by the radius */
/* Pose needs all nodes because it applies all symmetry iterations at the same time and the IK
* chain can grow to any area of the model. */
/* This can be optimized by filtering the nodes after calculating the chain. */
if (ELEM(brush->sculpt_tool, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE)) {
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
}
else if (brush->sculpt_tool == SCULPT_TOOL_POSE) {
/* After smoothing the pose factor an arbitrary number of times, the pose factor values can
* expand to nodes that are not inside the original radius of the brush. Using a slightly
* bigger radius should prevent those artifacts. */
/* We can optimize this further by removing the nodes that have all 0 values in the pose factor
* after calculating it. */
float final_radius = ss->cache->radius * 1.5f * (1.0f + brush->pose_offset);
SculptSearchSphereData data = {
.ss = ss,
.sd = sd,
.radius_squared = final_radius * final_radius,
.original = true,
};
BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode);
}
else {
const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
ss->cache->original;
@ -5686,7 +6011,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
if (brush->sculpt_tool == SCULPT_TOOL_POSE && ss->cache->first_time &&
ss->cache->mirror_symmetry_pass == 0) {
sculpt_pose_brush_init(sd, ob, ss, brush, ss->cache->location, ss->cache->radius);
sculpt_pose_brush_init(sd, ob, ss, brush);
}
bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN;
@ -6301,8 +6626,8 @@ void sculpt_cache_free(StrokeCache *cache)
if (cache->dial) {
MEM_freeN(cache->dial);
}
if (cache->pose_factor) {
MEM_freeN(cache->pose_factor);
if (cache->pose_ik_chain) {
sculpt_pose_ik_chain_free(cache->pose_ik_chain);
}
MEM_freeN(cache);
}
@ -9969,79 +10294,6 @@ void ED_sculpt_init_transform(struct bContext *C)
sculpt_filter_cache_init(ob, sd);
}
typedef enum PaintSymmetryAreas {
AREA_SYMM_X = (1 << 0),
AREA_SYMM_Y = (1 << 1),
AREA_SYMM_Z = (1 << 2),
} PaintSymmetryAreas;
static char sculpt_get_vertex_symm_area(float co[3])
{
float vco[3];
char symm_area = 0;
copy_v3_v3(vco, co);
if (vco[0] < 0) {
symm_area |= AREA_SYMM_X;
}
if (vco[1] < 0) {
symm_area |= AREA_SYMM_Y;
}
if (vco[2] < 0) {
symm_area |= AREA_SYMM_Z;
}
return symm_area;
}
static void flip_qt(float qt[4], char symm)
{
float euler[3];
if (symm & PAINT_SYMM_X) {
quat_to_eul(euler, qt);
euler[1] = -euler[1];
euler[2] = -euler[2];
eul_to_quat(qt, euler);
}
if (symm & PAINT_SYMM_Y) {
quat_to_eul(euler, qt);
euler[0] = -euler[0];
euler[2] = -euler[2];
eul_to_quat(qt, euler);
}
if (symm & PAINT_SYMM_Z) {
quat_to_eul(euler, qt);
euler[0] = -euler[0];
euler[1] = -euler[1];
eul_to_quat(qt, euler);
}
}
static void sculpt_flip_transform_by_symm_area(
float disp[3], float rot[4], char symm, char symmarea, float pivot[3])
{
for (char i = 0; i < 3; i++) {
char symm_it = 1 << i;
if (symm & symm_it) {
if (symmarea & symm_it) {
if (disp) {
flip_v3(disp, symm_it);
}
if (rot) {
flip_qt(rot, symm_it);
}
}
if (pivot[0] < 0) {
if (disp) {
flip_v3(disp, symm_it);
}
if (rot) {
flip_qt(rot, symm_it);
}
}
}
}
}
static void sculpt_transform_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
@ -10103,7 +10355,9 @@ void ED_sculpt_update_modal_transform(struct bContext *C)
transform_mat[4][4];
copy_v3_v3(final_pivot_pos, ss->pivot_pos);
for (int i = 0; i < 8; i++) {
for (int i = 0; i < PAINT_SYMM_AREAS; i++) {
ePaintSymmetryAreas v_symm = i;
copy_v3_v3(final_pivot_pos, ss->pivot_pos);
unit_m4(pivot_mat);
@ -10114,20 +10368,20 @@ void ED_sculpt_update_modal_transform(struct bContext *C)
/* Translation matrix */
sub_v3_v3v3(d_t, ss->pivot_pos, ss->init_pivot_pos);
sculpt_flip_transform_by_symm_area(d_t, NULL, symm, (char)i, ss->init_pivot_pos);
sculpt_flip_v3_by_symm_area(d_t, symm, v_symm, ss->init_pivot_pos);
translate_m4(t_mat, d_t[0], d_t[1], d_t[2]);
/* Rotation matrix */
sub_qt_qtqt(d_r, ss->pivot_rot, ss->init_pivot_rot);
normalize_qt(d_r);
sculpt_flip_transform_by_symm_area(NULL, d_r, symm, (char)i, ss->init_pivot_pos);
sculpt_flip_quat_by_symm_area(d_r, symm, v_symm, ss->init_pivot_pos);
quat_to_mat4(r_mat, d_r);
/* Scale matrix */
size_to_mat4(s_mat, ss->pivot_scale);
/* Pivot matrix */
sculpt_flip_transform_by_symm_area(final_pivot_pos, NULL, symm, (char)i, ss->init_pivot_pos);
sculpt_flip_v3_by_symm_area(final_pivot_pos, symm, v_symm, ss->init_pivot_pos);
translate_m4(pivot_mat, final_pivot_pos[0], final_pivot_pos[1], final_pivot_pos[2]);
invert_m4_m4(pivot_imat, pivot_mat);

View File

@ -37,6 +37,7 @@ struct KeyBlock;
struct Object;
struct SculptUndoNode;
struct bContext;
struct PoseIKChainSegment;
bool sculpt_mode_poll(struct bContext *C);
bool sculpt_mode_poll_view3d(struct bContext *C);
@ -74,6 +75,15 @@ void sculpt_pose_calc_pose_data(struct Sculpt *sd,
float *r_pose_origin,
float *r_pose_factor);
struct PoseIKChain *sculpt_pose_ik_chain_init(struct Sculpt *sd,
struct Object *ob,
struct SculptSession *ss,
struct Brush *br,
const float initial_location[3],
const float radius);
void sculpt_pose_ik_chain_free(struct PoseIKChain *ik_chain);
/* Sculpt PBVH abstraction API */
const float *sculpt_vertex_co_get(struct SculptSession *ss, int index);
@ -201,10 +211,9 @@ typedef struct SculptThreadedTaskData {
float *prev_mask;
float *pose_origin;
float *pose_initial_co;
float *pose_factor;
float (*transform_rot)[4], (*transform_trans)[4], (*transform_trans_inv)[4];
float *pose_initial_co;
int pose_chain_segment;
float multiplane_scrape_angle;
float multiplane_scrape_planes[2][4];
@ -250,7 +259,7 @@ typedef struct {
struct Sculpt *sd;
struct SculptSession *ss;
float radius_squared;
float *center;
const float *center;
bool original;
bool ignore_fully_masked;
} SculptSearchSphereData;
@ -379,9 +388,7 @@ typedef struct StrokeCache {
float anchored_location[3];
/* Pose brush */
float *pose_factor;
float pose_initial_co[3];
float pose_origin[3];
struct PoseIKChain *pose_ik_chain;
float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
struct Dial *dial;

View File

@ -336,8 +336,7 @@ typedef struct Brush {
/* pose */
float pose_offset;
int pose_smooth_iterations;
char _pad2[4];
int pose_ik_segments;
/* multiplane scrape */
float multiplane_scrape_angle;

View File

@ -1927,6 +1927,16 @@ static void rna_def_brush(BlenderRNA *brna)
"Smooth iterations applied after calculating the pose factor of each vertex");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "pose_ik_segments", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, NULL, "pose_ik_segments");
RNA_def_property_range(prop, 1, 20);
RNA_def_property_ui_range(prop, 1, 20, 1, 3);
RNA_def_property_ui_text(
prop,
"Pose IK Segments",
"Number of segments of the inverse kinematics chain that will deform the mesh");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "auto_smooth_factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "autosmooth_factor");
RNA_def_property_float_default(prop, 0);