Sculpt: Fix #104631: Tip Roundness on Paint brush causes jitering

I cleaned up the cube brush tip code quite a bit; more remains
to be done.  There is a new function to initialize cube
tip matrices, SCULPT_cube_tip_init.  It's currently only
used by the paint brush, I'll need to do a bit of testing
before using it for clay strips and multiplane scrape.

Note: SCULPT_cube_tip_init uses the brush local matrix code
to avoid code duplication (and to take advantage of the debouncing
that is done there).
This commit is contained in:
Joseph Eagar 2023-04-12 18:33:53 -07:00
parent d48939f103
commit 35071af465
10 changed files with 74 additions and 47 deletions

View File

@ -11,6 +11,8 @@
#include "DNA_color_types.h"
#include "DNA_object_enums.h"
#include "BKE_paint.h" /* for ePaintMode */
#ifdef __cplusplus
extern "C" {
#endif
@ -185,6 +187,11 @@ void BKE_brush_scale_size(int *r_brush_size,
float new_unprojected_radius,
float old_unprojected_radius);
/* Returns true if a brush requires a cube
* (often presented to the user as a square) tip inside a specific paint mode.
*/
bool BKE_brush_has_cube_tip(const struct Brush *brush, ePaintMode paint_mode);
/* Accessors */
#define BKE_brush_tool_get(brush, p) \
(CHECK_TYPE_ANY(brush, struct Brush *, const struct Brush *), \

View File

@ -238,7 +238,8 @@ void BKE_paint_face_set_overlay_color_get(int face_set, int seed, uchar r_color[
bool paint_calculate_rake_rotation(struct UnifiedPaintSettings *ups,
struct Brush *brush,
const float mouse_pos[2]);
const float mouse_pos[2],
ePaintMode paint_mode);
void paint_update_brush_rake_rotation(struct UnifiedPaintSettings *ups,
struct Brush *brush,
float rotation);

View File

@ -2576,3 +2576,22 @@ struct ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool
return im;
}
bool BKE_brush_has_cube_tip(const Brush *brush, ePaintMode paint_mode)
{
switch (paint_mode) {
case PAINT_MODE_SCULPT:
if (brush->sculpt_tool == SCULPT_TOOL_MULTIPLANE_SCRAPE) {
return true;
}
if (ELEM(brush->sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT) &&
brush->tip_roundness < 1.0f) {
return true;
}
break;
}
return false;
}

View File

@ -1302,12 +1302,7 @@ float paint_grid_paint_mask(const GridPaintMask *gpm, uint level, uint x, uint y
void paint_update_brush_rake_rotation(UnifiedPaintSettings *ups, Brush *brush, float rotation)
{
if (brush->mtex.brush_angle_mode & MTEX_ANGLE_RAKE) {
ups->brush_rotation = rotation;
}
else {
ups->brush_rotation = 0.0f;
}
ups->brush_rotation = rotation;
if (brush->mask_mtex.brush_angle_mode & MTEX_ANGLE_RAKE) {
ups->brush_rotation_sec = rotation;
@ -1322,17 +1317,19 @@ static bool paint_rake_rotation_active(const MTex &mtex)
return mtex.tex && mtex.brush_angle_mode & MTEX_ANGLE_RAKE;
}
static bool paint_rake_rotation_active(const Brush &brush)
const bool paint_rake_rotation_active(const Brush &brush, ePaintMode paint_mode)
{
return paint_rake_rotation_active(brush.mtex) || paint_rake_rotation_active(brush.mask_mtex);
return paint_rake_rotation_active(brush.mtex) || paint_rake_rotation_active(brush.mask_mtex) ||
BKE_brush_has_cube_tip(&brush, paint_mode);
}
bool paint_calculate_rake_rotation(UnifiedPaintSettings *ups,
Brush *brush,
const float mouse_pos[2])
const float mouse_pos[2],
ePaintMode paint_mode)
{
bool ok = false;
if (paint_rake_rotation_active(*brush)) {
if (paint_rake_rotation_active(*brush, paint_mode)) {
const float r = RAKE_THRESHHOLD;
float rotation;

View File

@ -1860,7 +1860,8 @@ static void paint_cursor_update_rake_rotation(PaintCursorContext *pcontext)
* and we may get interference with the stroke itself.
* For line strokes, such interference is visible. */
if (!pcontext->ups->stroke_active) {
paint_calculate_rake_rotation(pcontext->ups, pcontext->brush, pcontext->translation);
paint_calculate_rake_rotation(
pcontext->ups, pcontext->brush, pcontext->translation, pcontext->mode);
}
}

View File

@ -440,7 +440,7 @@ static bool paint_brush_update(bContext *C,
}
/* curve strokes do their own rake calculation */
else if (!(brush->flag & BRUSH_CURVE)) {
if (!paint_calculate_rake_rotation(ups, brush, mouse_init)) {
if (!paint_calculate_rake_rotation(ups, brush, mouse_init, mode)) {
/* Not enough motion to define an angle. */
if (!stroke->rake_started) {
is_dry_run = true;
@ -1562,7 +1562,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS
(br->mask_mtex.brush_angle_mode & MTEX_ANGLE_RAKE)) {
copy_v2_v2(stroke->ups->last_rake, stroke->last_mouse_position);
}
paint_calculate_rake_rotation(stroke->ups, br, mouse);
paint_calculate_rake_rotation(stroke->ups, br, mouse, mode);
}
}
else if (first_modal ||

View File

@ -1307,12 +1307,6 @@ void SCULPT_floodfill_free(SculptFloodFill *flood)
/** \} */
static bool sculpt_tool_has_cube_tip(const char sculpt_tool)
{
return ELEM(
sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT, SCULPT_TOOL_MULTIPLANE_SCRAPE);
}
/* -------------------------------------------------------------------- */
/** \name Tool Capabilities
*
@ -1376,7 +1370,8 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, Sculpt *sd, const
SCULPT_TOOL_THUMB) ||
(mask_tex->brush_map_mode == MTEX_MAP_MODE_AREA)) ||
sculpt_brush_use_topology_rake(ss, brush);
sculpt_brush_use_topology_rake(ss, brush) ||
BKE_brush_has_cube_tip(brush, PAINT_MODE_SCULPT);
}
static bool sculpt_brush_needs_rake_rotation(const Brush *brush)
@ -3018,7 +3013,7 @@ static void calc_local_y(ViewContext *vc, const float center[3], float y[3])
mul_m4_v3(ob->world_to_object, y);
}
static void calc_brush_local_mat(const MTex *mtex,
static void calc_brush_local_mat(const float rotation,
Object *ob,
float local_mat[4][4],
float local_mat_inv[4][4])
@ -3045,7 +3040,7 @@ static void calc_brush_local_mat(const MTex *mtex,
/* Calculate the X axis of the local matrix. */
cross_v3_v3v3(v, up, cache->sculpt_normal);
/* Apply rotation (user angle, rake, etc.) to X axis. */
angle = mtex->rot - cache->special_rotation;
angle = rotation - cache->special_rotation;
rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle);
/* Get other axes. */
@ -3059,7 +3054,7 @@ static void calc_brush_local_mat(const MTex *mtex,
float radius = cache->radius;
/* Square tips should scale by square root of 2. */
if (sculpt_tool_has_cube_tip(cache->brush->sculpt_tool)) {
if (BKE_brush_has_cube_tip(cache->brush, PAINT_MODE_SCULPT)) {
radius += (radius * M_SQRT2 - radius) * (1.0f - cache->brush->tip_roundness);
}
@ -3103,7 +3098,7 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob)
if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) {
const Brush *brush = BKE_paint_brush(&sd->paint);
const MTex *mask_tex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT);
calc_brush_local_mat(mask_tex, ob, cache->brush_local_mat, cache->brush_local_mat_inv);
calc_brush_local_mat(mask_tex->rot, ob, cache->brush_local_mat, cache->brush_local_mat_inv);
}
}
@ -3614,7 +3609,7 @@ static void do_brush_action(Sculpt *sd,
float radius_scale = 1.0f;
/* Corners of square brushes can go outside the brush radius. */
if (sculpt_tool_has_cube_tip(brush->sculpt_tool)) {
if (BKE_brush_has_cube_tip(brush, PAINT_MODE_SCULPT)) {
radius_scale = M_SQRT2;
}
@ -3686,10 +3681,7 @@ static void do_brush_action(Sculpt *sd,
update_sculpt_normal(sd, ob, nodes, totnode);
}
const MTex *mask_tex = BKE_brush_mask_texture_get(brush, static_cast<eObjectMode>(ob->mode));
if (mask_tex->brush_map_mode == MTEX_MAP_MODE_AREA) {
update_brush_local_mat(sd, ob);
}
update_brush_local_mat(sd, ob);
if (brush->sculpt_tool == SCULPT_TOOL_POSE && SCULPT_stroke_is_first_brush_step(ss->cache)) {
SCULPT_pose_brush_init(sd, ob, ss, brush);
@ -6456,4 +6448,27 @@ void SCULPT_topology_islands_ensure(Object *ob)
ss->islands_valid = true;
}
void SCULPT_cube_tip_init(Sculpt * /*sd*/, Object *ob, Brush *brush, float mat[4][4])
{
SculptSession *ss = ob->sculpt;
float scale[4][4];
float tmat[4][4];
float unused[4][4];
zero_m4(mat);
calc_brush_local_mat(0.0, ob, unused, mat);
/* Note: we ignore the radius scaling done inside of calc_brush_local_mat to
* duplicate prior behavior.
*
* TODO: try disabling this and check that all edge cases work properly.
*/
normalize_m4(mat);
scale_m4_fl(scale, ss->cache->radius);
mul_m4_m4m4(tmat, mat, scale);
mul_v3_fl(tmat[1], brush->tip_scale_x);
invert_m4_m4(mat, tmat);
}
/** \} */

View File

@ -227,6 +227,7 @@ struct SculptUndoNode {
struct SculptRakeData {
float follow_dist;
float follow_co[3];
float angle;
};
/**
@ -1247,6 +1248,7 @@ SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss,
char falloff_shape);
const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss,
char falloff_shape);
void SCULPT_cube_tip_init(Sculpt *sd, Object *ob, Brush *brush, float mat[4][4]);
/**
* Return a multiplier for brush strength on a particular vertex.

View File

@ -280,28 +280,12 @@ void SCULPT_do_paint_brush(PaintModeSettings *paint_mode_settings,
float area_no[3];
float mat[4][4];
float scale[4][4];
float tmat[4][4];
/* If the brush is round the tip does not need to be aligned to the surface, so this saves a
* whole iteration over the affected nodes. */
if (brush->tip_roundness < 1.0f) {
SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
SCULPT_cube_tip_init(sd, ob, brush, mat);
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
mat[0][3] = 0;
cross_v3_v3v3(mat[1], area_no, mat[0]);
mat[1][3] = 0;
copy_v3_v3(mat[2], area_no);
mat[2][3] = 0;
copy_v3_v3(mat[3], ss->cache->location);
mat[3][3] = 1;
normalize_m4(mat);
scale_m4_fl(scale, ss->cache->radius);
mul_m4_m4m4(tmat, mat, scale);
mul_v3_fl(tmat[1], brush->tip_scale_x);
invert_m4_m4(mat, tmat);
if (is_zero_m4(mat)) {
return;
}

View File

@ -15,6 +15,7 @@ set(INC
../blender/makesrna
../blender/render
../blender/windowmanager
../blender/bmesh
)
set(LIB