diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint')
21 files changed, 5031 insertions, 2690 deletions
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index ead420b1faa..b3e125baae3 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -53,6 +53,10 @@ set(SRC paint_undo.c paint_utils.c paint_vertex.c + paint_vertex_color_ops.c + paint_vertex_color_utils.c + paint_vertex_weight_ops.c + paint_vertex_weight_utils.c paint_vertex_proj.c sculpt.c sculpt_undo.c diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index a9b4df81277..befb635cfc4 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -42,6 +42,7 @@ #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_userdef_types.h" +#include "DNA_view3d_types.h" #include "BKE_brush.h" #include "BKE_context.h" @@ -849,12 +850,12 @@ BLI_INLINE void draw_rect_point( float maxx = co[0] + w; float maxy = co[1] + w; - imm_draw_line_box(pos, minx, miny, maxx, maxy); + imm_draw_box_wire_2d(pos, minx, miny, maxx, maxy); immUniformColor4f(1.0f, 1.0f, 1.0f, 0.5f); glLineWidth(1.0f); - imm_draw_line_box(pos, minx, miny, maxx, maxy); + imm_draw_box_wire_2d(pos, minx, miny, maxx, maxy); } @@ -1024,9 +1025,9 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) ViewContext vc; view3d_set_viewcontext(C, &vc); - float zoomx, zoomy; - get_imapaint_zoom(C, &zoomx, &zoomy); - zoomx = max_ff(zoomx, zoomy); + if (vc.rv3d->rflag & RV3D_NAVIGATING) { + return; + } /* skip everything and draw brush here */ if (brush->flag & BRUSH_CURVE) { @@ -1034,6 +1035,10 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) return; } + float zoomx, zoomy; + get_imapaint_zoom(C, &zoomx, &zoomy); + zoomx = max_ff(zoomx, zoomy); + /* set various defaults */ const float *outline_col = brush->add_col; const float outline_alpha = 0.5f; @@ -1064,11 +1069,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) /* check if brush is subtracting, use different color then */ /* TODO: no way currently to know state of pen flip or * invert key modifier without starting a stroke */ - if (((ups->draw_inverted == 0) ^ - ((brush->flag & BRUSH_DIR_IN) == 0)) && - ELEM(brush->sculpt_tool, SCULPT_TOOL_DRAW, - SCULPT_TOOL_INFLATE, SCULPT_TOOL_CLAY, - SCULPT_TOOL_PINCH, SCULPT_TOOL_CREASE)) + if (((ups->draw_inverted == 0) ^ ((brush->flag & BRUSH_DIR_IN) == 0)) && + BKE_brush_sculpt_has_secondary_color(brush)) { outline_col = brush->sub_col; } @@ -1098,11 +1100,11 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) /* draw brush outline */ if (ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) { /* inner at full alpha */ - imm_draw_circle_wire(pos, translation[0], translation[1], final_radius * ups->size_pressure_value, 40); + imm_draw_circle_wire_2d(pos, translation[0], translation[1], final_radius * ups->size_pressure_value, 40); /* outer at half alpha */ immUniformColor3fvAlpha(outline_col, outline_alpha * 0.5f); } - imm_draw_circle_wire(pos, translation[0], translation[1], final_radius, 40); + imm_draw_circle_wire_2d(pos, translation[0], translation[1], final_radius, 40); immUnbindProgram(); diff --git a/source/blender/editors/sculpt_paint/paint_hide.c b/source/blender/editors/sculpt_paint/paint_hide.c index d7c3b7377da..d2e2df01238 100644 --- a/source/blender/editors/sculpt_paint/paint_hide.c +++ b/source/blender/editors/sculpt_paint/paint_hide.c @@ -52,6 +52,8 @@ #include "BKE_paint.h" #include "BKE_subsurf.h" +#include "DEG_depsgraph.h" + #include "WM_api.h" #include "WM_types.h" @@ -360,6 +362,7 @@ static int hide_show_exec(bContext *C, wmOperator *op) { ARegion *ar = CTX_wm_region(C); Object *ob = CTX_data_active_object(C); + EvaluationContext eval_ctx; Mesh *me = ob->data; PartialVisAction action; PartialVisArea area; @@ -371,6 +374,8 @@ static int hide_show_exec(bContext *C, wmOperator *op) rcti rect; int totnode, i; + CTX_data_eval_ctx(C, &eval_ctx); + /* read operator properties */ action = RNA_enum_get(op->ptr, "action"); area = RNA_enum_get(op->ptr, "area"); @@ -378,7 +383,7 @@ static int hide_show_exec(bContext *C, wmOperator *op) clip_planes_from_rect(C, clip_planes, &rect); - dm = mesh_get_derived_final(CTX_data_scene(C), ob, CD_MASK_BAREMESH); + dm = mesh_get_derived_final(&eval_ctx, CTX_data_scene(C), ob, CD_MASK_BAREMESH); pbvh = dm->getPBVH(ob, dm); ob->sculpt->pbvh = pbvh; diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index 8c6b3b0106c..e324f2b89f0 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -766,7 +766,7 @@ static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const flo /* initialize from context */ if (CTX_wm_region_view3d(C)) { SceneLayer *sl = CTX_data_scene_layer(C); - Object *ob = OBACT_NEW; + Object *ob = OBACT_NEW(sl); bool uvs, mat, tex, stencil; if (!BKE_paint_proj_mesh_data_check(scene, ob, &uvs, &mat, &tex, &stencil)) { BKE_paint_data_warning(op->reports, uvs, mat, tex, stencil); @@ -1470,7 +1470,20 @@ void PAINT_OT_texture_paint_toggle(wmOperatorType *ot) static int brush_colors_flip_exec(bContext *C, wmOperator *UNUSED(op)) { UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Brush *br = image_paint_brush(C); + + Brush *br; + Object *ob = CTX_data_active_object(C); + if (!(ob && (ob->mode & OB_MODE_VERTEX_PAINT))) { + br = image_paint_brush(C); + } + else { + /* At the moment, wpaint does not support the color flipper. + * So for now we're only handling vpaint */ + ToolSettings *ts = CTX_data_tool_settings(C); + VPaint *vp = ts->vpaint; + br = BKE_paint_brush(&vp->paint); + } + if (ups->flag & UNIFIED_PAINT_COLOR) { swap_v3_v3(ups->rgb, ups->secondary_rgb); } @@ -1489,7 +1502,12 @@ static int brush_colors_flip_poll(bContext *C) if (br->imagepaint_tool == PAINT_TOOL_DRAW) return 1; } - + else { + Object *ob = CTX_data_active_object(C); + if (ob && (ob->mode & OB_MODE_VERTEX_PAINT)) { + return 1; + } + } return 0; } diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index eb6eebf3c54..30830e4e7bc 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -798,6 +798,7 @@ static void paint_2d_ibuf_rgb_set(ImBuf *ibuf, int x, int y, const bool is_torus float map_alpha = (rgb[3] == 0.0f) ? rrgbf[3] : rrgbf[3] / rgb[3]; mul_v3_v3fl(rrgbf, rgb, map_alpha); + rrgbf[3] = rgb[3]; } else { unsigned char straight[4]; @@ -807,6 +808,7 @@ static void paint_2d_ibuf_rgb_set(ImBuf *ibuf, int x, int y, const bool is_torus rrgb[0] = straight[0]; rrgb[1] = straight[1]; rrgb[2] = straight[2]; + rrgb[3] = straight[3]; } } @@ -996,7 +998,7 @@ static void paint_2d_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos, short tile) IMB_rectblend(ibufb, ibufb, ibuf, NULL, NULL, NULL, 0, region[a].destx, region[a].desty, region[a].destx, region[a].desty, region[a].srcx, region[a].srcy, - region[a].width, region[a].height, IMB_BLEND_COPY_RGB, false); + region[a].width, region[a].height, IMB_BLEND_COPY, false); } static ImBuf *paint_2d_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos) @@ -1097,6 +1099,7 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *curveb, unsign /* lift from canvas */ if (s->tool == PAINT_TOOL_SOFTEN) { paint_2d_lift_soften(s, s->canvas, ibufb, bpos, tile); + blend = IMB_BLEND_INTERPOLATE; } else if (s->tool == PAINT_TOOL_SMEAR) { if (lastpos[0] == pos[0] && lastpos[1] == pos[1]) @@ -1104,6 +1107,7 @@ static int paint_2d_op(void *state, ImBuf *ibufb, unsigned short *curveb, unsign paint_2d_convert_brushco(ibufb, lastpos, blastpos); paint_2d_lift_smear(s->canvas, ibufb, blastpos, tile); + blend = IMB_BLEND_INTERPOLATE; } else if (s->tool == PAINT_TOOL_CLONE && s->clonecanvas) { liftpos[0] = pos[0] - offset[0] * s->canvas->x; diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index d47c96bf22c..2ffd9757f92 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -3421,12 +3421,15 @@ static void project_paint_bleed_add_face_user( #endif /* Return true if DM can be painted on, false otherwise */ -static bool proj_paint_state_dm_init(ProjPaintState *ps) +static bool proj_paint_state_dm_init(const bContext *C, ProjPaintState *ps) { + EvaluationContext eval_ctx; + CTX_data_eval_ctx(C, &eval_ctx); + /* Workaround for subsurf selection, try the display mesh first */ if (ps->source == PROJ_SRC_IMAGE_CAM) { /* using render mesh, assume only camera was rendered from */ - ps->dm = mesh_create_derived_render(ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE); + ps->dm = mesh_create_derived_render(&eval_ctx, ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE); ps->dm_release = true; } else if (ps->ob->derivedFinal && @@ -3438,7 +3441,7 @@ static bool proj_paint_state_dm_init(ProjPaintState *ps) } else { ps->dm = mesh_get_derived_final( - ps->scene, ps->ob, + &eval_ctx, ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE | (ps->do_face_sel ? CD_ORIGINDEX : 0)); ps->dm_release = true; } @@ -3713,8 +3716,12 @@ static void project_paint_prepare_all_faces( } /* don't allow using the same inage for painting and stencilling */ - if (slot->ima == ps->stencil_ima) + if (slot->ima == ps->stencil_ima) { + /* While this shouldn't be used, face-winding reads all polys. + * It's less trouble to set all faces to valid UV's, avoiding NULL checks all over. */ + ps->dm_mloopuv[lt->poly] = mloopuv_base; continue; + } tpage = slot->ima; } @@ -3814,7 +3821,7 @@ static void project_paint_prepare_all_faces( /* run once per stroke before projection painting */ static void project_paint_begin( - ProjPaintState *ps, + const bContext *C, ProjPaintState *ps, const bool is_multi_view, const char symmetry_flag) { ProjPaintLayerClone layer_clone; @@ -3837,7 +3844,7 @@ static void project_paint_begin( /* paint onto the derived mesh */ if (ps->is_shared_user == false) { - if (!proj_paint_state_dm_init(ps)) { + if (!proj_paint_state_dm_init(C, ps)) { return; } } @@ -4292,7 +4299,7 @@ static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, f return; } else { - blend_color_interpolate_float(rgba, rgba, projPixel->pixel.f_pt, mask); + blend_color_interpolate_float(rgba, projPixel->pixel.f_pt, rgba, mask); } BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena); @@ -4751,6 +4758,9 @@ static void *do_projectpaint_thread(void *ph_v) copy_v3_v3(texrgb, texrgba); mask *= texrgba[3]; } + else { + zero_v3(texrgb); + } /* extra mask for normal, layer stencil, .. */ mask *= ((float)projPixel->mask) * (1.0f / 65535.0f); @@ -5017,6 +5027,7 @@ void paint_proj_stroke( /* clone gets special treatment here to avoid going through image initialization */ if (ps_handle->is_clone_cursor_pick) { + EvaluationContext eval_ctx; Scene *scene = ps_handle->scene; struct Depsgraph *graph = CTX_data_depsgraph(C); View3D *v3d = CTX_wm_view3d(C); @@ -5026,7 +5037,9 @@ void paint_proj_stroke( view3d_operator_needs_opengl(C); - if (!ED_view3d_autodist(graph, ar, v3d, mval_i, cursor, false, NULL)) { + CTX_data_eval_ctx(C, &eval_ctx); + + if (!ED_view3d_autodist(&eval_ctx, graph, ar, v3d, mval_i, cursor, false, NULL)) { return; } @@ -5230,7 +5243,7 @@ void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int m PROJ_PAINT_STATE_SHARED_MEMCPY(ps, ps_handle->ps_views[0]); } - project_paint_begin(ps, is_multi_view, symmetry_flag_views[i]); + project_paint_begin(C, ps, is_multi_view, symmetry_flag_views[i]); paint_proj_begin_clone(ps, mouse); @@ -5313,7 +5326,7 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) int orig_brush_size; IDProperty *idgroup; IDProperty *view_data = NULL; - Object *ob = OBACT_NEW; + Object *ob = OBACT_NEW(sl); bool uvs, mat, tex; if (ob == NULL || ob->type != OB_MESH) { @@ -5384,7 +5397,7 @@ static int texture_paint_camera_project_exec(bContext *C, wmOperator *op) ED_image_undo_restore, ED_image_undo_free, NULL); /* allocate and initialize spatial data structures */ - project_paint_begin(&ps, false, 0); + project_paint_begin(C, &ps, false, 0); if (ps.dm == NULL) { BKE_brush_size_set(scene, ps.brush, orig_brush_size); @@ -5443,12 +5456,15 @@ static int texture_paint_image_from_view_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); SceneLayer *sl = CTX_data_scene_layer(C); + EvaluationContext eval_ctx; ToolSettings *settings = scene->toolsettings; int w = settings->imapaint.screen_grab_size[0]; int h = settings->imapaint.screen_grab_size[1]; int maxsize; char err_out[256] = "unknown"; + CTX_data_eval_ctx(C, &eval_ctx); + RNA_string_get(op->ptr, "filepath", filename); maxsize = GPU_max_texture_size(); @@ -5457,7 +5473,7 @@ static int texture_paint_image_from_view_exec(bContext *C, wmOperator *op) if (h > maxsize) h = maxsize; ibuf = ED_view3d_draw_offscreen_imbuf( - scene, sl, CTX_wm_view3d(C), CTX_wm_region(C), + &eval_ctx, scene, sl, CTX_wm_view3d(C), CTX_wm_region(C), w, h, IB_rect, false, R_ALPHAPREMUL, 0, false, NULL, NULL, NULL, err_out); if (!ibuf) { diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 69c4621945b..b3b7d40391a 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -96,21 +96,11 @@ int weight_paint_mode_poll(struct bContext *C); int vertex_paint_poll(struct bContext *C); int vertex_paint_mode_poll(struct bContext *C); -bool ED_vpaint_fill(struct Object *ob, unsigned int paintcol); -bool ED_wpaint_fill(struct VPaint *wp, struct Object *ob, float paintweight); - -bool ED_vpaint_smooth(struct Object *ob); - typedef void (*VPaintTransform_Callback)(const float col[3], const void *user_data, float r_col[3]); -bool ED_vpaint_color_transform(struct Object *ob, VPaintTransform_Callback vpaint_tx_fn, const void *user_data); - void PAINT_OT_weight_paint_toggle(struct wmOperatorType *ot); void PAINT_OT_weight_paint(struct wmOperatorType *ot); void PAINT_OT_weight_set(struct wmOperatorType *ot); -void PAINT_OT_weight_from_bones(struct wmOperatorType *ot); -void PAINT_OT_weight_sample(struct wmOperatorType *ot); -void PAINT_OT_weight_sample_group(struct wmOperatorType *ot); enum { WPAINT_GRADIENT_TYPE_LINEAR, @@ -123,14 +113,52 @@ void PAINT_OT_vertex_paint(struct wmOperatorType *ot); unsigned int vpaint_get_current_col(struct Scene *scene, struct VPaint *vp); +/* paint_vertex_color_utils.c */ +unsigned int ED_vpaint_blend_tool( + const int tool, const uint col, + const uint paintcol, const int alpha_i); +bool ED_vpaint_color_transform( + struct Object *ob, VPaintTransform_Callback vpaint_tx_fn, const void *user_data); + +/* paint_vertex_weight_utils.c */ +float ED_wpaint_blend_tool( + const int tool, + const float weight, + const float paintval, const float alpha); +/* Utility for tools to ensure vertex groups exist before they begin. */ +enum eWPaintFlag { + WPAINT_ENSURE_MIRROR = (1 << 0), +}; +struct WPaintVGroupIndex { + int active; + int mirror; +}; +bool ED_wpaint_ensure_data( + struct bContext *C, struct ReportList *reports, + enum eWPaintFlag flag, struct WPaintVGroupIndex *vgroup_index); +int ED_wpaint_mirror_vgroup_ensure(struct Object *ob, const int vgroup_active); + +/* paint_vertex_color_ops.c */ +void PAINT_OT_vertex_color_set(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_from_weight(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_smooth(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_brightness_contrast(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_hsv(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_invert(struct wmOperatorType *ot); +void PAINT_OT_vertex_color_levels(struct wmOperatorType *ot); + +/* paint_vertex_weight_ops.c */ +void PAINT_OT_weight_from_bones(struct wmOperatorType *ot); +void PAINT_OT_weight_sample(struct wmOperatorType *ot); +void PAINT_OT_weight_sample_group(struct wmOperatorType *ot); /* paint_vertex_proj.c */ struct VertProjHandle; struct VertProjHandle *ED_vpaint_proj_handle_create( - struct Scene *scene, struct Object *ob, + const struct EvaluationContext *eval_ctx, struct Scene *scene, struct Object *ob, struct DMCoNo **r_vcosnos); void ED_vpaint_proj_handle_update( - struct VertProjHandle *vp_handle, + const struct EvaluationContext *eval_ctx, struct VertProjHandle *vp_handle, /* runtime vars */ struct ARegion *ar, const float mval_fl[2]); void ED_vpaint_proj_handle_free( @@ -160,7 +188,7 @@ void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr); void imapaint_region_tiles(struct ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th); int get_imapaint_zoom(struct bContext *C, float *zoomx, float *zoomy); void *paint_2d_new_stroke(struct bContext *, struct wmOperator *, int mode); -void paint_2d_redraw(const bContext *C, void *ps, bool final); +void paint_2d_redraw(const struct bContext *C, void *ps, bool final); void paint_2d_stroke_done(void *ps); void paint_2d_stroke(void *ps, const float prev_mval[2], const float mval[2], const bool eraser, float pressure, float distance, float size); void paint_2d_bucket_fill(const struct bContext *C, const float color[3], struct Brush *br, const float mouse_init[2], void *ps); @@ -213,10 +241,10 @@ void paint_calc_redraw_planes(float planes[4][4], const struct rcti *screen_rect); float paint_calc_object_space_radius(struct ViewContext *vc, const float center[3], float pixel_radius); -float paint_get_tex_pixel(struct MTex *mtex, float u, float v, struct ImagePool *pool, int thread); -void paint_get_tex_pixel_col(struct MTex *mtex, float u, float v, float rgba[4], struct ImagePool *pool, int thread, bool convert, struct ColorSpace *colorspace); +float paint_get_tex_pixel(const struct MTex *mtex, float u, float v, struct ImagePool *pool, int thread); +void paint_get_tex_pixel_col(const struct MTex *mtex, float u, float v, float rgba[4], struct ImagePool *pool, int thread, bool convert, struct ColorSpace *colorspace); -void paint_sample_color(bContext *C, struct ARegion *ar, int x, int y, bool texpaint_proj, bool palette); +void paint_sample_color(struct bContext *C, struct ARegion *ar, int x, int y, bool texpaint_proj, bool palette); void paint_stroke_operator_properties(struct wmOperatorType *ot); diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index 1ac40b0354e..b7159c375cd 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -50,6 +50,8 @@ #include "BKE_paint.h" #include "BKE_subsurf.h" +#include "DEG_depsgraph.h" + #include "RNA_access.h" #include "RNA_define.h" @@ -129,6 +131,7 @@ static int mask_flood_fill_exec(bContext *C, wmOperator *op) ARegion *ar = CTX_wm_region(C); struct Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); + EvaluationContext eval_ctx; PaintMaskFloodMode mode; float value; PBVH *pbvh; @@ -137,10 +140,12 @@ static int mask_flood_fill_exec(bContext *C, wmOperator *op) bool multires; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + CTX_data_eval_ctx(C, &eval_ctx); + mode = RNA_enum_get(op->ptr, "mode"); value = RNA_float_get(op->ptr, "value"); - BKE_sculpt_update_mesh_elements(scene, sd, ob, false, true); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, false, true); pbvh = ob->sculpt->pbvh; multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS); @@ -247,6 +252,7 @@ static void mask_box_select_task_cb(void *userdata, const int i) int ED_sculpt_mask_box_select(struct bContext *C, ViewContext *vc, const rcti *rect, bool select, bool UNUSED(extend)) { + EvaluationContext eval_ctx; Sculpt *sd = vc->scene->toolsettings->sculpt; BoundBox bb; float clip_planes[4][4]; @@ -262,6 +268,8 @@ int ED_sculpt_mask_box_select(struct bContext *C, ViewContext *vc, const rcti *r int totnode, symmpass; int symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + CTX_data_eval_ctx(C, &eval_ctx); + mode = PAINT_MASK_FLOOD_VALUE; value = select ? 1.0 : 0.0; @@ -269,7 +277,7 @@ int ED_sculpt_mask_box_select(struct bContext *C, ViewContext *vc, const rcti *r ED_view3d_clipping_calc(&bb, clip_planes, vc->ar, vc->obact, rect); negate_m4(clip_planes); - BKE_sculpt_update_mesh_elements(scene, sd, ob, false, true); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, false, true); pbvh = ob->sculpt->pbvh; multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS); @@ -405,6 +413,7 @@ static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op) const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot); if (mcords) { + EvaluationContext eval_ctx; float clip_planes[4][4], clip_planes_final[4][4]; BoundBox bb; Object *ob; @@ -420,6 +429,8 @@ static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op) PaintMaskFloodMode mode = RNA_enum_get(op->ptr, "mode"); float value = RNA_float_get(op->ptr, "value"); + CTX_data_eval_ctx(C, &eval_ctx); + /* Calculations of individual vertices are done in 2D screen space to diminish the amount of * calculations done. Bounding box PBVH collision is not computed against enclosing rectangle * of lasso */ @@ -442,7 +453,7 @@ static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op) ED_view3d_clipping_calc(&bb, clip_planes, vc.ar, vc.obact, &data.rect); negate_m4(clip_planes); - BKE_sculpt_update_mesh_elements(scene, sd, ob, false, true); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, false, true); pbvh = ob->sculpt->pbvh; multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS); diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index f88b64129e7..2899cfeedcf 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -256,300 +256,6 @@ static void PALETTE_OT_color_delete(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } - - -static int vertex_color_set_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Scene *scene = CTX_data_scene(C); - Object *obact = CTX_data_active_object(C); - unsigned int paintcol = vpaint_get_current_col(scene, scene->toolsettings->vpaint); - - if (ED_vpaint_fill(obact, paintcol)) { - ED_region_tag_redraw(CTX_wm_region(C)); // XXX - should redraw all 3D views - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_set(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Set Vertex Colors"; - ot->idname = "PAINT_OT_vertex_color_set"; - ot->description = "Fill the active vertex color layer with the current paint color"; - - /* api callbacks */ - ot->exec = vertex_color_set_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int vertex_color_smooth_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *obact = CTX_data_active_object(C); - if (ED_vpaint_smooth(obact)) { - ED_region_tag_redraw(CTX_wm_region(C)); // XXX - should redraw all 3D views - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_smooth(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Smooth Vertex Colors"; - ot->idname = "PAINT_OT_vertex_color_smooth"; - ot->description = "Smooth colors across vertices"; - - /* api callbacks */ - ot->exec = vertex_color_smooth_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - - -/** \name Vertex Color Transformations - * \{ */ - -struct VPaintTx_BrightContrastData { - /* pre-calculated */ - float gain; - float offset; -}; - -static void vpaint_tx_brightness_contrast(const float col[3], const void *user_data, float r_col[3]) -{ - const struct VPaintTx_BrightContrastData *data = user_data; - - for (int i = 0; i < 3; i++) { - r_col[i] = data->gain * col[i] + data->offset; - } -} - -static int vertex_color_brightness_contrast_exec(bContext *C, wmOperator *op) -{ - Object *obact = CTX_data_active_object(C); - - float gain, offset; - { - float brightness = RNA_float_get(op->ptr, "brightness"); - float contrast = RNA_float_get(op->ptr, "contrast"); - brightness /= 100.0f; - float delta = contrast / 200.0f; - gain = 1.0f - delta * 2.0f; - /* - * The algorithm is by Werner D. Streidt - * (http://visca.com/ffactory/archives/5-99/msg00021.html) - * Extracted of OpenCV demhist.c - */ - if (contrast > 0) { - gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON); - offset = gain * (brightness - delta); - } - else { - delta *= -1; - offset = gain * (brightness + delta); - } - } - - const struct VPaintTx_BrightContrastData user_data = { - .gain = gain, - .offset = offset, - }; - - if (ED_vpaint_color_transform(obact, vpaint_tx_brightness_contrast, &user_data)) { - ED_region_tag_redraw(CTX_wm_region(C)); - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_brightness_contrast(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Vertex Paint Bright/Contrast"; - ot->idname = "PAINT_OT_vertex_color_brightness_contrast"; - ot->description = "Adjust vertex color brightness/contrast"; - - /* api callbacks */ - ot->exec = vertex_color_brightness_contrast_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* params */ - const float min = -100, max = +100; - prop = RNA_def_float(ot->srna, "brightness", 0.0f, min, max, "Brightness", "", min, max); - prop = RNA_def_float(ot->srna, "contrast", 0.0f, min, max, "Contrast", "", min, max); - RNA_def_property_ui_range(prop, min, max, 1, 1); -} - -struct VPaintTx_HueSatData { - float hue; - float sat; - float val; -}; - -static void vpaint_tx_hsv(const float col[3], const void *user_data, float r_col[3]) -{ - const struct VPaintTx_HueSatData *data = user_data; - float hsv[3]; - rgb_to_hsv_v(col, hsv); - - hsv[0] += (data->hue - 0.5f); - if (hsv[0] > 1.0f) { - hsv[0] -= 1.0f; - } - else if (hsv[0] < 0.0f) { - hsv[0] += 1.0f; - } - hsv[1] *= data->sat; - hsv[2] *= data->val; - - hsv_to_rgb_v(hsv, r_col); -} - -static int vertex_color_hsv_exec(bContext *C, wmOperator *op) -{ - Object *obact = CTX_data_active_object(C); - - const struct VPaintTx_HueSatData user_data = { - .hue = RNA_float_get(op->ptr, "h"), - .sat = RNA_float_get(op->ptr, "s"), - .val = RNA_float_get(op->ptr, "v"), - }; - - if (ED_vpaint_color_transform(obact, vpaint_tx_hsv, &user_data)) { - ED_region_tag_redraw(CTX_wm_region(C)); - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_hsv(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Paint Hue Saturation Value"; - ot->idname = "PAINT_OT_vertex_color_hsv"; - ot->description = "Adjust vertex color HSV values"; - - /* api callbacks */ - ot->exec = vertex_color_hsv_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* params */ - RNA_def_float(ot->srna, "h", 0.5f, 0.0f, 1.0f, "Hue", "", 0.0f, 1.0f); - RNA_def_float(ot->srna, "s", 1.0f, 0.0f, 2.0f, "Saturation", "", 0.0f, 2.0f); - RNA_def_float(ot->srna, "v", 1.0f, 0.0f, 2.0f, "Value", "", 0.0f, 2.0f); -} - -static void vpaint_tx_invert(const float col[3], const void *UNUSED(user_data), float r_col[3]) -{ - for (int i = 0; i < 3; i++) { - r_col[i] = 1.0f - col[i]; - } -} - -static int vertex_color_invert_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *obact = CTX_data_active_object(C); - - if (ED_vpaint_color_transform(obact, vpaint_tx_invert, NULL)) { - ED_region_tag_redraw(CTX_wm_region(C)); - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_invert(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Paint Invert"; - ot->idname = "PAINT_OT_vertex_color_invert"; - ot->description = "Invert RGB values"; - - /* api callbacks */ - ot->exec = vertex_color_invert_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - - -struct VPaintTx_LevelsData { - float gain; - float offset; -}; - -static void vpaint_tx_levels(const float col[3], const void *user_data, float r_col[3]) -{ - const struct VPaintTx_LevelsData *data = user_data; - for (int i = 0; i < 3; i++) { - r_col[i] = data->gain * (col[i] + data->offset); - } -} - -static int vertex_color_levels_exec(bContext *C, wmOperator *op) -{ - Object *obact = CTX_data_active_object(C); - - const struct VPaintTx_LevelsData user_data = { - .gain = RNA_float_get(op->ptr, "gain"), - .offset = RNA_float_get(op->ptr, "offset"), - }; - - if (ED_vpaint_color_transform(obact, vpaint_tx_levels, &user_data)) { - ED_region_tag_redraw(CTX_wm_region(C)); - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -static void PAINT_OT_vertex_color_levels(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Paint Levels"; - ot->idname = "PAINT_OT_vertex_color_levels"; - ot->description = "Adjust levels of vertex colors"; - - /* api callbacks */ - ot->exec = vertex_color_levels_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* params */ - RNA_def_float(ot->srna, "offset", 0.0f, -1.0f, 1.0f, "Offset", "Value to add to colors", -1.0f, 1.0f); - RNA_def_float(ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply colors by", 0.0f, 10.0f); -} - -/** \} */ - - static int brush_reset_exec(bContext *C, wmOperator *UNUSED(op)) { Paint *paint = BKE_paint_get_active_from_context(C); @@ -558,9 +264,14 @@ static int brush_reset_exec(bContext *C, wmOperator *UNUSED(op)) if (!ob || !brush) return OPERATOR_CANCELLED; - if (ob->mode & OB_MODE_SCULPT) - BKE_brush_sculpt_reset(brush); /* TODO: other modes */ + if (ob->mode & OB_MODE_SCULPT) { + BKE_brush_sculpt_reset(brush); + } + else { + return OPERATOR_CANCELLED; + } + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); return OPERATOR_FINISHED; } @@ -821,8 +532,6 @@ static int brush_uv_sculpt_tool_set_exec(bContext *C, wmOperator *op) static void BRUSH_OT_uv_sculpt_tool_set(wmOperatorType *ot) { - /* from rna_scene.c */ - extern EnumPropertyItem uv_sculpt_tool_items[]; /* identifiers */ ot->name = "UV Sculpt Tool Set"; ot->description = "Set the UV sculpt tool"; @@ -836,7 +545,7 @@ static void BRUSH_OT_uv_sculpt_tool_set(wmOperatorType *ot) ot->flag = 0; /* props */ - ot->prop = RNA_def_enum(ot->srna, "tool", uv_sculpt_tool_items, 0, "Tool", ""); + ot->prop = RNA_def_enum(ot->srna, "tool", rna_enum_uv_sculpt_tool_items, 0, "Tool", ""); } /***** Stencil Control *****/ @@ -1351,6 +1060,7 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PAINT_OT_vertex_color_hsv); WM_operatortype_append(PAINT_OT_vertex_color_invert); WM_operatortype_append(PAINT_OT_vertex_color_levels); + WM_operatortype_append(PAINT_OT_vertex_color_from_weight); /* face-select */ WM_operatortype_append(PAINT_OT_face_select_linked); @@ -1615,6 +1325,7 @@ void ED_keymap_paint(wmKeyConfig *keyconf) keymap->poll = vertex_paint_mode_poll; WM_keymap_verify_item(keymap, "PAINT_OT_vertex_paint", LEFTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_brush_colors_flip", XKEY, KM_PRESS, 0, 0); WM_keymap_add_item(keymap, "PAINT_OT_sample_color", SKEY, KM_PRESS, 0, 0); WM_keymap_add_item(keymap, diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 86008ec0308..d92afd414f8 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -172,7 +172,7 @@ static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata) uint shdr_pos = GWN_vertformat_attr_add(immVertexFormat(), "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_COLOR); + immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; glGetFloatv(GL_VIEWPORT, viewport_size); @@ -589,7 +589,10 @@ static float paint_stroke_integrate_overlap(Brush *br, float factor) max = overlap; } - return 1.0f / max; + if (max == 0.0f) + return 1.0f; + else + return 1.0f / max; } static float paint_space_stroke_spacing_variable(const Scene *scene, PaintStroke *stroke, float pressure, float dpressure, float length) diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index e36cc275ba9..bb69a5d3db9 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -56,9 +56,12 @@ #include "BKE_paint.h" #include "BKE_report.h" +#include "DEG_depsgraph.h" + #include "RNA_access.h" #include "RNA_define.h" +#include "GPU_glew.h" #include "GPU_matrix.h" #include "IMB_colormanagement.h" @@ -165,7 +168,7 @@ float paint_calc_object_space_radius(ViewContext *vc, const float center[3], return len_v3(delta) / scale; } -float paint_get_tex_pixel(MTex *mtex, float u, float v, struct ImagePool *pool, int thread) +float paint_get_tex_pixel(const MTex *mtex, float u, float v, struct ImagePool *pool, int thread) { float intensity, rgba[4]; float co[3] = {u, v, 0.0f}; @@ -176,7 +179,7 @@ float paint_get_tex_pixel(MTex *mtex, float u, float v, struct ImagePool *pool, return intensity; } -void paint_get_tex_pixel_col(MTex *mtex, float u, float v, float rgba[4], struct ImagePool *pool, int thread, bool convert_to_linear, struct ColorSpace *colorspace) +void paint_get_tex_pixel_col(const MTex *mtex, float u, float v, float rgba[4], struct ImagePool *pool, int thread, bool convert_to_linear, struct ColorSpace *colorspace) { float co[3] = {u, v, 0.0f}; int hasrgb; @@ -268,9 +271,9 @@ static void imapaint_tri_weights(float matrix[4][4], GLint view[4], } /* compute uv coordinates of mouse in face */ -static void imapaint_pick_uv(Scene *scene, Object *ob, unsigned int faceindex, const int xy[2], float uv[2]) +static void imapaint_pick_uv(EvaluationContext *eval_ctx, Scene *scene, Object *ob, unsigned int faceindex, const int xy[2], float uv[2]) { - DerivedMesh *dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); + DerivedMesh *dm = mesh_get_derived_final(eval_ctx, scene, ob, CD_MASK_BAREMESH); const int tottri = dm->getNumLoopTri(dm); int i, findex; float p[2], w[3], absw, minabsw; @@ -346,13 +349,15 @@ static void imapaint_pick_uv(Scene *scene, Object *ob, unsigned int faceindex, c } /* returns 0 if not found, otherwise 1 */ -static int imapaint_pick_face(ViewContext *vc, const int mval[2], unsigned int *r_index, unsigned int totpoly) +static int imapaint_pick_face( + const struct EvaluationContext *eval_ctx, ViewContext *vc, const int mval[2], + unsigned int *r_index, unsigned int totpoly) { if (totpoly == 0) return 0; /* sample only on the exact position */ - *r_index = ED_view3d_backbuf_sample(vc, mval[0], mval[1]); + *r_index = ED_view3d_backbuf_sample(eval_ctx, vc, mval[0], mval[1]); if ((*r_index) == 0 || (*r_index) > (unsigned int)totpoly) { return 0; @@ -418,13 +423,16 @@ void flip_qt_qt(float out[4], const float in[4], const char symm) void paint_sample_color(bContext *C, ARegion *ar, int x, int y, bool texpaint_proj, bool use_palette) { Scene *scene = CTX_data_scene(C); + EvaluationContext eval_ctx; Paint *paint = BKE_paint_get_active_from_context(C); Palette *palette = BKE_paint_palette(paint); - PaletteColor *color; + PaletteColor *color = NULL; Brush *br = BKE_paint_brush(BKE_paint_get_active_from_context(C)); unsigned int col; const unsigned char *cp; + CTX_data_eval_ctx(C, &eval_ctx); + CLAMP(x, 0, ar->winx); CLAMP(y, 0, ar->winy); @@ -442,14 +450,14 @@ void paint_sample_color(bContext *C, ARegion *ar, int x, int y, bool texpaint_pr if (CTX_wm_view3d(C) && texpaint_proj) { /* first try getting a colour directly from the mesh faces if possible */ SceneLayer *sl = CTX_data_scene_layer(C); - Object *ob = OBACT_NEW; + Object *ob = OBACT_NEW(sl); bool sample_success = false; ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; bool use_material = (imapaint->mode == IMAGEPAINT_MODE_MATERIAL); if (ob) { Mesh *me = (Mesh *)ob->data; - DerivedMesh *dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); + DerivedMesh *dm = mesh_get_derived_final(&eval_ctx, scene, ob, CD_MASK_BAREMESH); ViewContext vc; const int mval[2] = {x, y}; @@ -461,7 +469,7 @@ void paint_sample_color(bContext *C, ARegion *ar, int x, int y, bool texpaint_pr view3d_operator_needs_opengl(C); - if (imapaint_pick_face(&vc, mval, &faceindex, totpoly)) { + if (imapaint_pick_face(&eval_ctx, &vc, mval, &faceindex, totpoly)) { Image *image; if (use_material) @@ -474,7 +482,7 @@ void paint_sample_color(bContext *C, ARegion *ar, int x, int y, bool texpaint_pr if (ibuf && ibuf->rect) { float uv[2]; float u, v; - imapaint_pick_uv(scene, ob, faceindex, mval, uv); + imapaint_pick_uv(&eval_ctx, scene, ob, faceindex, mval, uv); sample_success = true; u = fmodf(uv[0], 1.0f); diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 98bf0aa0a66..fd88ea2d15f 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -27,20 +27,20 @@ /** \file blender/editors/sculpt_paint/paint_vertex.c * \ingroup edsculpt + * + * Used for vertex color & weight paint and mode switching. + * + * \note This file is already big, + * use `paint_vertex_color_ops.c` & `paint_vertex_weight_ops.c` for general purpose operators. */ #include "MEM_guardedalloc.h" -#include "BLI_blenlib.h" +#include "BLI_listbase.h" +#include "BLI_rect.h" #include "BLI_math.h" #include "BLI_array_utils.h" -#include "BLI_bitmap.h" -#include "BLI_stack.h" -#include "BLI_string_utils.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" -#include "IMB_colormanagement.h" +#include "BLI_task.h" #include "DNA_armature_types.h" #include "DNA_mesh_types.h" @@ -51,40 +51,116 @@ #include "RNA_access.h" #include "RNA_define.h" -#include "RNA_enum_types.h" -#include "BKE_DerivedMesh.h" -#include "BKE_action.h" #include "BKE_brush.h" #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" -#include "BKE_modifier.h" #include "BKE_object_deform.h" #include "BKE_paint.h" #include "BKE_report.h" -#include "BKE_colortools.h" +#include "BKE_subsurf.h" #include "DEG_depsgraph.h" #include "WM_api.h" #include "WM_types.h" -#include "ED_armature.h" #include "ED_object.h" #include "ED_mesh.h" #include "ED_screen.h" #include "ED_view3d.h" +#include "bmesh.h" +#include "BKE_ccg.h" + +#include "sculpt_intern.h" #include "paint_intern.h" /* own include */ -/* small structure to defer applying weight-paint results */ -struct WPaintDefer { - int index; - float alpha, weight; +/* Use for 'blur' brush, align with PBVH nodes, created and freed on each update. */ +struct VPaintAverageAccum { + uint len; + uint value[3]; +}; + +struct WPaintAverageAccum { + uint len; + double value; +}; + +struct NormalAnglePrecalc { + bool do_mask_normal; + /* what angle to mask at */ + float angle; + /* cos(angle), faster to compare */ + float angle__cos; + float angle_inner; + float angle_inner__cos; + /* difference between angle and angle_inner, for easy access */ + float angle_range; }; + +static void view_angle_limits_init( + struct NormalAnglePrecalc *a, float angle, bool do_mask_normal) +{ + angle = RAD2DEGF(angle); + a->do_mask_normal = do_mask_normal; + if (do_mask_normal) { + a->angle_inner = angle; + a->angle = (a->angle_inner + 90.0f) * 0.5f; + } + else { + a->angle_inner = a->angle = angle; + } + + a->angle_inner *= (float)(M_PI_2 / 90); + a->angle *= (float)(M_PI_2 / 90); + a->angle_range = a->angle - a->angle_inner; + + if (a->angle_range <= 0.0f) { + a->do_mask_normal = false; /* no need to do blending */ + } + + a->angle__cos = cosf(a->angle); + a->angle_inner__cos = cosf(a->angle_inner); +} + +static float view_angle_limits_apply_falloff( + const struct NormalAnglePrecalc *a, float angle_cos, float *mask_p) +{ + if (angle_cos <= a->angle__cos) { + /* outsize the normal limit */ + return false; + } + else if (angle_cos < a->angle_inner__cos) { + *mask_p *= (a->angle - acosf(angle_cos)) / a->angle_range; + return true; + } + else { + return true; + } +} + +static bool vwpaint_use_normal(const VPaint *vp) +{ + return ((vp->paint.brush->flag & BRUSH_FRONTFACE) != 0) || + ((vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0); +} + + +static MDeformVert *defweight_prev_init(MDeformVert *dvert_prev, MDeformVert *dvert_curr, int index) +{ + MDeformVert *dv_curr = &dvert_curr[index]; + MDeformVert *dv_prev = &dvert_prev[index]; + if (dv_prev->flag == 1) { + dv_prev->flag = 0; + defvert_copy(dv_prev, dv_curr); + } + return dv_prev; +} + /* check if we can do partial updates and have them draw realtime * (without rebuilding the 'derivedFinal') */ static bool vertex_paint_use_fast_update_check(Object *ob) @@ -126,7 +202,7 @@ int vertex_paint_mode_poll(bContext *C) int vertex_paint_poll(bContext *C) { - if (vertex_paint_mode_poll(C) && + if (vertex_paint_mode_poll(C) && BKE_paint_brush(&CTX_data_tool_settings(C)->vpaint->paint)) { ScrArea *sa = CTX_wm_area(C); @@ -165,639 +241,47 @@ int weight_paint_poll(bContext *C) return 0; } -static VPaint *new_vpaint(int wpaint) +static VPaint *new_vpaint(void) { VPaint *vp = MEM_callocN(sizeof(VPaint), "VPaint"); - - vp->flag = (wpaint) ? 0 : VP_SPRAY; + vp->paint.flags |= PAINT_SHOW_BRUSH; return vp; } -static int *get_indexarray(Mesh *me) -{ - return MEM_mallocN(sizeof(int) * (me->totpoly + 1), "vertexpaint"); -} - -unsigned int vpaint_get_current_col(Scene *scene, VPaint *vp) +uint vpaint_get_current_col(Scene *scene, VPaint *vp) { Brush *brush = BKE_paint_brush(&vp->paint); - unsigned char col[4]; + uchar col[4]; rgb_float_to_uchar(col, BKE_brush_color_get(scene, brush)); col[3] = 255; /* alpha isn't used, could even be removed to speedup paint a little */ - return *(unsigned int *)col; -} - -static void do_shared_vertexcol(Mesh *me, bool *mlooptag) -{ - const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - MPoly *mp; - int (*scol)[4]; - int i, j; - bool has_shared = false; - - /* if no mloopcol: do not do */ - /* if mtexpoly: only the involved faces, otherwise all */ - - if (me->mloopcol == NULL || me->totvert == 0 || me->totpoly == 0) return; - - scol = MEM_callocN(sizeof(int) * me->totvert * 5, "scol"); - - for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) { - if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) { - MLoop *ml = me->mloop + mp->loopstart; - MLoopCol *lcol = me->mloopcol + mp->loopstart; - for (j = 0; j < mp->totloop; j++, ml++, lcol++) { - scol[ml->v][0] += lcol->r; - scol[ml->v][1] += lcol->g; - scol[ml->v][2] += lcol->b; - scol[ml->v][3] += 1; - has_shared = 1; - } - } - } - - if (has_shared) { - for (i = 0; i < me->totvert; i++) { - if (scol[i][3] != 0) { - scol[i][0] = divide_round_i(scol[i][0], scol[i][3]); - scol[i][1] = divide_round_i(scol[i][1], scol[i][3]); - scol[i][2] = divide_round_i(scol[i][2], scol[i][3]); - } - } - - for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) { - if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) { - MLoop *ml = me->mloop + mp->loopstart; - MLoopCol *lcol = me->mloopcol + mp->loopstart; - for (j = 0; j < mp->totloop; j++, ml++, lcol++) { - if (mlooptag[mp->loopstart + j]) { - lcol->r = scol[ml->v][0]; - lcol->g = scol[ml->v][1]; - lcol->b = scol[ml->v][2]; - } - } - } - } - } - - MEM_freeN(scol); -} - -static bool make_vertexcol(Object *ob) /* single ob */ -{ - Mesh *me; - - if (ID_IS_LINKED_DATABLOCK(ob) || - ((me = BKE_mesh_from_object(ob)) == NULL) || - (me->totpoly == 0) || - (me->edit_btmesh)) - { - return false; - } - - /* copies from shadedisplist to mcol */ - if (!me->mloopcol && me->totloop) { - CustomData_add_layer(&me->ldata, CD_MLOOPCOL, CD_DEFAULT, NULL, me->totloop); - BKE_mesh_update_customdata_pointers(me, true); - } - - DEG_id_tag_update(&me->id, 0); - - return (me->mloopcol != NULL); -} - -/* mirror_vgroup is set to -1 when invalid */ -static int wpaint_mirror_vgroup_ensure(Object *ob, const int vgroup_active) -{ - bDeformGroup *defgroup = BLI_findlink(&ob->defbase, vgroup_active); - - if (defgroup) { - int mirrdef; - char name_flip[MAXBONENAME]; - - BLI_string_flip_side_name(name_flip, defgroup->name, false, sizeof(name_flip)); - mirrdef = defgroup_name_index(ob, name_flip); - if (mirrdef == -1) { - if (BKE_defgroup_new(ob, name_flip)) { - mirrdef = BLI_listbase_count(&ob->defbase) - 1; - } - } - - /* curdef should never be NULL unless this is - * a lamp and BKE_object_defgroup_add_name fails */ - return mirrdef; - } - - return -1; -} - -static void free_vpaint_prev(VPaint *vp) -{ - if (vp->vpaint_prev) { - MEM_freeN(vp->vpaint_prev); - vp->vpaint_prev = NULL; - vp->tot = 0; - } -} - -static void free_wpaint_prev(VPaint *vp) -{ - if (vp->wpaint_prev) { - BKE_defvert_array_free(vp->wpaint_prev, vp->tot); - vp->wpaint_prev = NULL; - vp->tot = 0; - } -} - -static void copy_vpaint_prev(VPaint *vp, unsigned int *lcol, int tot) -{ - free_vpaint_prev(vp); - - vp->tot = tot; - - if (lcol == NULL || tot == 0) return; - - vp->vpaint_prev = MEM_mallocN(sizeof(int) * tot, "vpaint_prev"); - memcpy(vp->vpaint_prev, lcol, sizeof(int) * tot); - -} - -static void copy_wpaint_prev(VPaint *wp, MDeformVert *dverts, int dcount) -{ - free_wpaint_prev(wp); - - if (dverts && dcount) { - - wp->wpaint_prev = MEM_mallocN(sizeof(MDeformVert) * dcount, "wpaint prev"); - wp->tot = dcount; - BKE_defvert_array_copy(wp->wpaint_prev, dverts, dcount); - } -} - -bool ED_vpaint_fill(Object *ob, unsigned int paintcol) -{ - Mesh *me; - MPoly *mp; - int i, j; - bool selected; - - if (((me = BKE_mesh_from_object(ob)) == NULL) || - (me->mloopcol == NULL && (make_vertexcol(ob) == false))) - { - return false; - } - - selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - - mp = me->mpoly; - for (i = 0; i < me->totpoly; i++, mp++) { - MLoopCol *lcol = me->mloopcol + mp->loopstart; - - if (selected && !(mp->flag & ME_FACE_SEL)) - continue; - - for (j = 0; j < mp->totloop; j++, lcol++) { - *(int *)lcol = paintcol; - } - } - - /* remove stale me->mcol, will be added later */ - BKE_mesh_tessface_clear(me); - - DEG_id_tag_update(&me->id, 0); - - return true; -} - - -/* fills in the selected faces with the current weight and vertex group */ -bool ED_wpaint_fill(VPaint *wp, Object *ob, float paintweight) -{ - Mesh *me = ob->data; - MPoly *mp; - MDeformWeight *dw, *dw_prev; - int vgroup_active, vgroup_mirror = -1; - unsigned int index; - const bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - /* mutually exclusive, could be made into a */ - const short paint_selmode = ME_EDIT_PAINT_SEL_MODE(me); - - if (me->totpoly == 0 || me->dvert == NULL || !me->mpoly) { - return false; - } - - vgroup_active = ob->actdef - 1; - - /* if mirror painting, find the other group */ - if (me->editflag & ME_EDIT_MIRROR_X) { - vgroup_mirror = wpaint_mirror_vgroup_ensure(ob, vgroup_active); - } - - copy_wpaint_prev(wp, me->dvert, me->totvert); - - for (index = 0, mp = me->mpoly; index < me->totpoly; index++, mp++) { - unsigned int fidx = mp->totloop - 1; - - if ((paint_selmode == SCE_SELECT_FACE) && !(mp->flag & ME_FACE_SEL)) { - continue; - } - - do { - unsigned int vidx = me->mloop[mp->loopstart + fidx].v; - - if (!me->dvert[vidx].flag) { - if ((paint_selmode == SCE_SELECT_VERTEX) && !(me->mvert[vidx].flag & SELECT)) { - continue; - } - - dw = defvert_verify_index(&me->dvert[vidx], vgroup_active); - if (dw) { - dw_prev = defvert_verify_index(wp->wpaint_prev + vidx, vgroup_active); - dw_prev->weight = dw->weight; /* set the undo weight */ - dw->weight = paintweight; - - if (me->editflag & ME_EDIT_MIRROR_X) { /* x mirror painting */ - int j = mesh_get_x_mirror_vert(ob, NULL, vidx, topology); - if (j >= 0) { - /* copy, not paint again */ - if (vgroup_mirror != -1) { - dw = defvert_verify_index(me->dvert + j, vgroup_mirror); - dw_prev = defvert_verify_index(wp->wpaint_prev + j, vgroup_mirror); - } - else { - dw = defvert_verify_index(me->dvert + j, vgroup_active); - dw_prev = defvert_verify_index(wp->wpaint_prev + j, vgroup_active); - } - dw_prev->weight = dw->weight; /* set the undo weight */ - dw->weight = paintweight; - } - } - } - me->dvert[vidx].flag = 1; - } - - } while (fidx--); - } - - { - MDeformVert *dv = me->dvert; - for (index = me->totvert; index != 0; index--, dv++) { - dv->flag = 0; - } - } - - copy_wpaint_prev(wp, NULL, 0); - - DEG_id_tag_update(&me->id, 0); - - return true; -} - -bool ED_vpaint_smooth(Object *ob) -{ - Mesh *me; - MPoly *mp; - - int i, j; - - bool *mlooptag; - bool selected; - - if (((me = BKE_mesh_from_object(ob)) == NULL) || - (me->mloopcol == NULL && (make_vertexcol(ob) == false))) - { - return false; - } - - selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - - mlooptag = MEM_callocN(sizeof(bool) * me->totloop, "VPaintData mlooptag"); - - /* simply tag loops of selected faces */ - mp = me->mpoly; - for (i = 0; i < me->totpoly; i++, mp++) { - MLoop *ml = me->mloop + mp->loopstart; - int ml_index = mp->loopstart; - - if (selected && !(mp->flag & ME_FACE_SEL)) - continue; - - for (j = 0; j < mp->totloop; j++, ml_index++, ml++) { - mlooptag[ml_index] = true; - } - } - - /* remove stale me->mcol, will be added later */ - BKE_mesh_tessface_clear(me); - - do_shared_vertexcol(me, mlooptag); - - MEM_freeN(mlooptag); - - DEG_id_tag_update(&me->id, 0); - - return true; -} - -/** - * Apply callback to each vertex of the active vertex color layer. - */ -bool ED_vpaint_color_transform( - struct Object *ob, - VPaintTransform_Callback vpaint_tx_fn, - const void *user_data) -{ - Mesh *me; - const MPoly *mp; - - if (((me = BKE_mesh_from_object(ob)) == NULL) || - (me->mloopcol == NULL && (make_vertexcol(ob) == false))) - { - return false; - } - - const bool do_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - mp = me->mpoly; - - for (int i = 0; i < me->totpoly; i++, mp++) { - MLoopCol *lcol = &me->mloopcol[mp->loopstart]; - - if (do_face_sel && !(mp->flag & ME_FACE_SEL)) { - continue; - } - - for (int j = 0; j < mp->totloop; j++, lcol++) { - float col[3]; - rgb_uchar_to_float(col, &lcol->r); - - vpaint_tx_fn(col, user_data, col); - - rgb_float_to_uchar(&lcol->r, col); - } - } - - /* remove stale me->mcol, will be added later */ - BKE_mesh_tessface_clear(me); - - DEG_id_tag_update(&me->id, 0); - - return true; -} - -/* XXX: should be re-implemented as a vertex/weight paint 'color correct' operator */ -#if 0 -void vpaint_dogamma(Scene *scene) -{ - VPaint *vp = scene->toolsettings->vpaint; - Mesh *me; - Object *ob; - float igam, fac; - int a, temp; - unsigned char *cp, gamtab[256]; - - ob = OBACT; - me = BKE_mesh_from_object(ob); - - if (!(ob->mode & OB_MODE_VERTEX_PAINT)) return; - if (me == 0 || me->mcol == 0 || me->totface == 0) return; - - igam = 1.0 / vp->gamma; - for (a = 0; a < 256; a++) { - - fac = ((float)a) / 255.0; - fac = vp->mul * pow(fac, igam); - - temp = 255.9 * fac; - - if (temp <= 0) gamtab[a] = 0; - else if (temp >= 255) gamtab[a] = 255; - else gamtab[a] = temp; - } - - a = 4 * me->totface; - cp = (unsigned char *)me->mcol; - while (a--) { - - cp[1] = gamtab[cp[1]]; - cp[2] = gamtab[cp[2]]; - cp[3] = gamtab[cp[3]]; - - cp += 4; - } -} -#endif - -BLI_INLINE unsigned int mcol_blend(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int mfac; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - - if (fac >= 255) { - return col2; - } - - mfac = 255 - fac; - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - cp[0] = divide_round_i((mfac * cp1[0] + fac * cp2[0]), 255); - cp[1] = divide_round_i((mfac * cp1[1] + fac * cp2[1]), 255); - cp[2] = divide_round_i((mfac * cp1[2] + fac * cp2[2]), 255); - cp[3] = 255; - - return col; -} - -BLI_INLINE unsigned int mcol_add(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int temp; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - temp = cp1[0] + divide_round_i((fac * cp2[0]), 255); - cp[0] = (temp > 254) ? 255 : temp; - temp = cp1[1] + divide_round_i((fac * cp2[1]), 255); - cp[1] = (temp > 254) ? 255 : temp; - temp = cp1[2] + divide_round_i((fac * cp2[2]), 255); - cp[2] = (temp > 254) ? 255 : temp; - cp[3] = 255; - - return col; -} - -BLI_INLINE unsigned int mcol_sub(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int temp; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - temp = cp1[0] - divide_round_i((fac * cp2[0]), 255); - cp[0] = (temp < 0) ? 0 : temp; - temp = cp1[1] - divide_round_i((fac * cp2[1]), 255); - cp[1] = (temp < 0) ? 0 : temp; - temp = cp1[2] - divide_round_i((fac * cp2[2]), 255); - cp[2] = (temp < 0) ? 0 : temp; - cp[3] = 255; - - return col; -} - -BLI_INLINE unsigned int mcol_mul(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int mfac; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - - mfac = 255 - fac; - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - /* first mul, then blend the fac */ - cp[0] = divide_round_i(mfac * cp1[0] * 255 + fac * cp2[0] * cp1[0], 255 * 255); - cp[1] = divide_round_i(mfac * cp1[1] * 255 + fac * cp2[1] * cp1[1], 255 * 255); - cp[2] = divide_round_i(mfac * cp1[2] * 255 + fac * cp2[2] * cp1[2], 255 * 255); - cp[3] = 255; - - return col; -} - -BLI_INLINE unsigned int mcol_lighten(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int mfac; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - else if (fac >= 255) { - return col2; - } - - mfac = 255 - fac; - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - /* See if are lighter, if so mix, else don't do anything. - * if the paint col is darker then the original, then ignore */ - if (IMB_colormanagement_get_luminance_byte(cp1) > IMB_colormanagement_get_luminance_byte(cp2)) { - return col1; - } - - cp[0] = divide_round_i(mfac * cp1[0] + fac * cp2[0], 255); - cp[1] = divide_round_i(mfac * cp1[1] + fac * cp2[1], 255); - cp[2] = divide_round_i(mfac * cp1[2] + fac * cp2[2], 255); - cp[3] = 255; - - return col; -} - -BLI_INLINE unsigned int mcol_darken(unsigned int col1, unsigned int col2, int fac) -{ - unsigned char *cp1, *cp2, *cp; - int mfac; - unsigned int col = 0; - - if (fac == 0) { - return col1; - } - else if (fac >= 255) { - return col2; - } - - mfac = 255 - fac; - - cp1 = (unsigned char *)&col1; - cp2 = (unsigned char *)&col2; - cp = (unsigned char *)&col; - - /* See if were darker, if so mix, else don't do anything. - * if the paint col is brighter then the original, then ignore */ - if (IMB_colormanagement_get_luminance_byte(cp1) < IMB_colormanagement_get_luminance_byte(cp2)) { - return col1; - } - - cp[0] = divide_round_i((mfac * cp1[0] + fac * cp2[0]), 255); - cp[1] = divide_round_i((mfac * cp1[1] + fac * cp2[1]), 255); - cp[2] = divide_round_i((mfac * cp1[2] + fac * cp2[2]), 255); - cp[3] = 255; - return col; -} - -/* wpaint has 'wpaint_blend_tool' */ -static unsigned int vpaint_blend_tool(const int tool, const unsigned int col, - const unsigned int paintcol, const int alpha_i) -{ - switch (tool) { - case PAINT_BLEND_MIX: - case PAINT_BLEND_BLUR: return mcol_blend(col, paintcol, alpha_i); - case PAINT_BLEND_ADD: return mcol_add(col, paintcol, alpha_i); - case PAINT_BLEND_SUB: return mcol_sub(col, paintcol, alpha_i); - case PAINT_BLEND_MUL: return mcol_mul(col, paintcol, alpha_i); - case PAINT_BLEND_LIGHTEN: return mcol_lighten(col, paintcol, alpha_i); - case PAINT_BLEND_DARKEN: return mcol_darken(col, paintcol, alpha_i); - default: - BLI_assert(0); - return 0; - } + return *(uint *)col; } /* wpaint has 'wpaint_blend' */ -static unsigned int vpaint_blend(VPaint *vp, unsigned int col, unsigned int colorig, const - unsigned int paintcol, const int alpha_i, - /* pre scaled from [0-1] --> [0-255] */ - const int brush_alpha_value_i) +static uint vpaint_blend( + const VPaint *vp, uint color_curr, uint color_orig, + uint color_paint, const int alpha_i, + /* pre scaled from [0-1] --> [0-255] */ + const int brush_alpha_value_i) { - Brush *brush = BKE_paint_brush(&vp->paint); + const Brush *brush = vp->paint.brush; const int tool = brush->vertexpaint_tool; - col = vpaint_blend_tool(tool, col, paintcol, alpha_i); + uint color_blend = ED_vpaint_blend_tool(tool, color_curr, color_paint, alpha_i); - /* if no spray, clip color adding with colorig & orig alpha */ - if ((vp->flag & VP_SPRAY) == 0) { - unsigned int testcol, a; + /* if no accumulate, clip color adding with colorig & orig alpha */ + if ((brush->flag & BRUSH_ACCUMULATE) == 0) { + uint color_test, a; char *cp, *ct, *co; - - testcol = vpaint_blend_tool(tool, colorig, paintcol, brush_alpha_value_i); - - cp = (char *)&col; - ct = (char *)&testcol; - co = (char *)&colorig; - + + color_test = ED_vpaint_blend_tool(tool, color_orig, color_paint, brush_alpha_value_i); + + cp = (char *)&color_blend; + ct = (char *)&color_test; + co = (char *)&color_orig; + for (a = 0; a < 4; a++) { if (ct[a] < co[a]) { if (cp[a] < ct[a]) cp[a] = ct[a]; @@ -810,59 +294,30 @@ static unsigned int vpaint_blend(VPaint *vp, unsigned int col, unsigned int colo } } - return col; -} - - -static int sample_backbuf_area(ViewContext *vc, int *indexar, int totpoly, int x, int y, float size) -{ - struct ImBuf *ibuf; - int a, tot = 0, index; - - /* brecht: disabled this because it obviously fails for - * brushes with size > 64, why is this here? */ - /*if (size > 64.0) size = 64.0;*/ - - ibuf = ED_view3d_backbuf_read(vc, x - size, y - size, x + size, y + size); - if (ibuf) { - unsigned int *rt = ibuf->rect; - - memset(indexar, 0, sizeof(int) * (totpoly + 1)); - - size = ibuf->x * ibuf->y; - while (size--) { - - if (*rt) { - index = *rt; - if (index > 0 && index <= totpoly) { - indexar[index] = 1; - } - } - - rt++; - } - - for (a = 1; a <= totpoly; a++) { - if (indexar[a]) { - indexar[tot++] = a; - } - } - - IMB_freeImBuf(ibuf); + if ((brush->flag & BRUSH_LOCK_ALPHA) && + !ELEM(tool, PAINT_BLEND_ALPHA_SUB, PAINT_BLEND_ALPHA_ADD)) + { + char *cp, *cc; + cp = (char *)&color_blend; + cc = (char *)&color_curr; + cp[3] = cc[3]; } - - return tot; + + return color_blend; } + /* whats _dl mean? */ -static float calc_vp_strength_col_dl(VPaint *vp, ViewContext *vc, const float co[3], - const float mval[2], const float brush_size_pressure, float rgba[4]) +static float calc_vp_strength_col_dl( + VPaint *vp, const ViewContext *vc, const float co[3], + const float mval[2], const float brush_size_pressure, float rgba[4]) { float co_ss[2]; /* screenspace */ - if (ED_view3d_project_float_object(vc->ar, - co, co_ss, - V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) == V3D_PROJ_RET_OK) + if (ED_view3d_project_float_object( + vc->ar, + co, co_ss, + V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) == V3D_PROJ_RET_OK) { const float dist_sq = len_squared_v2v2(mval, co_ss); @@ -892,17 +347,18 @@ static float calc_vp_strength_col_dl(VPaint *vp, ViewContext *vc, const float co return 0.0f; } -static float calc_vp_alpha_col_dl(VPaint *vp, ViewContext *vc, - float vpimat[3][3], const DMCoNo *v_co_no, - const float mval[2], - const float brush_size_pressure, const float brush_alpha_pressure, float rgba[4]) +static float calc_vp_alpha_col_dl( + VPaint *vp, const ViewContext *vc, + float vpimat[3][3], const DMCoNo *v_co_no, + const float mval[2], + const float brush_size_pressure, const float brush_alpha_pressure, float rgba[4]) { float strength = calc_vp_strength_col_dl(vp, vc, v_co_no->co, mval, brush_size_pressure, rgba); if (strength > 0.0f) { float alpha = brush_alpha_pressure * strength; - if (vp->flag & VP_NORMALS) { + if ((vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0) { float dvec[3]; /* transpose ! */ @@ -924,62 +380,14 @@ static float calc_vp_alpha_col_dl(VPaint *vp, ViewContext *vc, return 0.0f; } - -BLI_INLINE float wval_blend(const float weight, const float paintval, const float alpha) -{ - const float talpha = min_ff(alpha, 1.0f); /* blending with values over 1 doesn't make sense */ - return (paintval * talpha) + (weight * (1.0f - talpha)); -} -BLI_INLINE float wval_add(const float weight, const float paintval, const float alpha) -{ - return weight + (paintval * alpha); -} -BLI_INLINE float wval_sub(const float weight, const float paintval, const float alpha) -{ - return weight - (paintval * alpha); -} -BLI_INLINE float wval_mul(const float weight, const float paintval, const float alpha) -{ /* first mul, then blend the fac */ - return ((1.0f - alpha) + (alpha * paintval)) * weight; -} -BLI_INLINE float wval_lighten(const float weight, const float paintval, const float alpha) -{ - return (weight < paintval) ? wval_blend(weight, paintval, alpha) : weight; -} -BLI_INLINE float wval_darken(const float weight, const float paintval, const float alpha) -{ - return (weight > paintval) ? wval_blend(weight, paintval, alpha) : weight; -} - - -/* vpaint has 'vpaint_blend_tool' */ -/* result is not clamped from [0-1] */ -static float wpaint_blend_tool(const int tool, - /* dw->weight */ - const float weight, - const float paintval, const float alpha) -{ - switch (tool) { - case PAINT_BLEND_MIX: - case PAINT_BLEND_BLUR: return wval_blend(weight, paintval, alpha); - case PAINT_BLEND_ADD: return wval_add(weight, paintval, alpha); - case PAINT_BLEND_SUB: return wval_sub(weight, paintval, alpha); - case PAINT_BLEND_MUL: return wval_mul(weight, paintval, alpha); - case PAINT_BLEND_LIGHTEN: return wval_lighten(weight, paintval, alpha); - case PAINT_BLEND_DARKEN: return wval_darken(weight, paintval, alpha); - default: - BLI_assert(0); - return 0.0f; - } -} - /* vpaint has 'vpaint_blend' */ -static float wpaint_blend(VPaint *wp, float weight, float weight_prev, - const float alpha, float paintval, - const float brush_alpha_value, - const short do_flip) +static float wpaint_blend( + const VPaint *wp, float weight, + const float alpha, float paintval, + const float UNUSED(brush_alpha_value), + const short do_flip) { - Brush *brush = BKE_paint_brush(&wp->paint); + const Brush *brush = wp->paint.brush; int tool = brush->vertexpaint_tool; if (do_flip) { @@ -996,257 +404,30 @@ static float wpaint_blend(VPaint *wp, float weight, float weight_prev, tool = PAINT_BLEND_LIGHTEN; break; } } - - weight = wpaint_blend_tool(tool, weight, paintval, alpha); - CLAMP(weight, 0.0f, 1.0f); - - /* if no spray, clip result with orig weight & orig alpha */ - if ((wp->flag & VP_SPRAY) == 0) { - float testw = wpaint_blend_tool(tool, weight_prev, paintval, brush_alpha_value); + weight = ED_wpaint_blend_tool(tool, weight, paintval, alpha); - CLAMP(testw, 0.0f, 1.0f); - if (testw < weight_prev) { - if (weight < testw) weight = testw; - else if (weight > weight_prev) weight = weight_prev; - } - else { - if (weight > testw) weight = testw; - else if (weight < weight_prev) weight = weight_prev; - } - } + CLAMP(weight, 0.0f, 1.0f); return weight; } -/* ----------------------------------------------------- */ - - -/* sets wp->weight to the closest weight value to vertex */ -/* note: we cant sample frontbuf, weight colors are interpolated too unpredictable */ -static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - ViewContext vc; - Mesh *me; - bool changed = false; - - view3d_set_viewcontext(C, &vc); - me = BKE_mesh_from_object(vc.obact); - - if (me && me->dvert && vc.v3d && vc.rv3d && (vc.obact->actdef != 0)) { - const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; - int v_idx_best = -1; - unsigned int index; - - view3d_operator_needs_opengl(C); - ED_view3d_init_mats_rv3d(vc.obact, vc.rv3d); - - if (use_vert_sel) { - if (ED_mesh_pick_vert(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_VERT_SIZE, true)) { - v_idx_best = index; - } - } - else { - if (ED_mesh_pick_face_vert(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { - v_idx_best = index; - } - else if (ED_mesh_pick_face(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { - /* this relies on knowning the internal worksings of ED_mesh_pick_face_vert() */ - BKE_report(op->reports, RPT_WARNING, "The modifier used does not support deformed locations"); - } - } - - if (v_idx_best != -1) { /* should always be valid */ - ToolSettings *ts = vc.scene->toolsettings; - Brush *brush = BKE_paint_brush(&ts->wpaint->paint); - const int vgroup_active = vc.obact->actdef - 1; - float vgroup_weight = defvert_find_weight(&me->dvert[v_idx_best], vgroup_active); - - /* use combined weight in multipaint mode, since that's what is displayed to the user in the colors */ - if (ts->multipaint) { - int defbase_tot_sel; - const int defbase_tot = BLI_listbase_count(&vc.obact->defbase); - bool *defbase_sel = BKE_object_defgroup_selected_get(vc.obact, defbase_tot, &defbase_tot_sel); - - if (defbase_tot_sel > 1) { - if (me->editflag & ME_EDIT_MIRROR_X) { - BKE_object_defgroup_mirror_selection( - vc.obact, defbase_tot, defbase_sel, defbase_sel, &defbase_tot_sel); - } - - vgroup_weight = BKE_defvert_multipaint_collective_weight( - &me->dvert[v_idx_best], defbase_tot, defbase_sel, defbase_tot_sel, ts->auto_normalize); - - /* if autonormalize is enabled, but weights are not normalized, the value can exceed 1 */ - CLAMP(vgroup_weight, 0.0f, 1.0f); - } - - MEM_freeN(defbase_sel); - } - - BKE_brush_weight_set(vc.scene, brush, vgroup_weight); - changed = true; - } - } - - if (changed) { - /* not really correct since the brush didnt change, but redraws the toolbar */ - WM_main_add_notifier(NC_BRUSH | NA_EDITED, NULL); /* ts->wpaint->paint.brush */ - - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} - -void PAINT_OT_weight_sample(wmOperatorType *ot) +static float wpaint_clamp_monotonic(float oldval, float curval, float newval) { - /* identifiers */ - ot->name = "Weight Paint Sample Weight"; - ot->idname = "PAINT_OT_weight_sample"; - ot->description = "Use the mouse to sample a weight in the 3D view"; - - /* api callbacks */ - ot->invoke = weight_sample_invoke; - ot->poll = weight_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO; + if (newval < oldval) + return MIN2(newval, curval); + else if (newval > oldval) + return MAX2(newval, curval); + else + return newval; } -/* samples cursor location, and gives menu with vertex groups to activate */ -static bool weight_paint_sample_enum_itemf__helper(const MDeformVert *dvert, const int defbase_tot, int *groups) -{ - /* this func fills in used vgroup's */ - bool found = false; - int i = dvert->totweight; - MDeformWeight *dw; - for (dw = dvert->dw; i > 0; dw++, i--) { - if (dw->def_nr < defbase_tot) { - groups[dw->def_nr] = true; - found = true; - } - } - return found; -} -static EnumPropertyItem *weight_paint_sample_enum_itemf( - bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) -{ - if (C) { - wmWindow *win = CTX_wm_window(C); - if (win && win->eventstate) { - ViewContext vc; - Mesh *me; - - view3d_set_viewcontext(C, &vc); - me = BKE_mesh_from_object(vc.obact); - - if (me && me->dvert && vc.v3d && vc.rv3d && vc.obact->defbase.first) { - const int defbase_tot = BLI_listbase_count(&vc.obact->defbase); - const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; - int *groups = MEM_callocN(defbase_tot * sizeof(int), "groups"); - bool found = false; - unsigned int index; - - const int mval[2] = { - win->eventstate->x - vc.ar->winrct.xmin, - win->eventstate->y - vc.ar->winrct.ymin, - }; - - view3d_operator_needs_opengl(C); - ED_view3d_init_mats_rv3d(vc.obact, vc.rv3d); - - if (use_vert_sel) { - if (ED_mesh_pick_vert(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_VERT_SIZE, true)) { - MDeformVert *dvert = &me->dvert[index]; - found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); - } - } - else { - if (ED_mesh_pick_face(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { - MPoly *mp = &me->mpoly[index]; - unsigned int fidx = mp->totloop - 1; - - do { - MDeformVert *dvert = &me->dvert[me->mloop[mp->loopstart + fidx].v]; - found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); - } while (fidx--); - } - } - - if (found == false) { - MEM_freeN(groups); - } - else { - EnumPropertyItem *item = NULL, item_tmp = {0}; - int totitem = 0; - int i = 0; - bDeformGroup *dg; - for (dg = vc.obact->defbase.first; dg && i < defbase_tot; i++, dg = dg->next) { - if (groups[i]) { - item_tmp.identifier = item_tmp.name = dg->name; - item_tmp.value = i; - RNA_enum_item_add(&item, &totitem, &item_tmp); - } - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - MEM_freeN(groups); - return item; - } - } - } - } - - return DummyRNA_NULL_items; -} - -static int weight_sample_group_exec(bContext *C, wmOperator *op) -{ - int type = RNA_enum_get(op->ptr, "group"); - ViewContext vc; - view3d_set_viewcontext(C, &vc); - - BLI_assert(type + 1 >= 0); - vc.obact->actdef = type + 1; - - DEG_id_tag_update(&vc.obact->id, OB_RECALC_DATA); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, vc.obact); - return OPERATOR_FINISHED; -} - -/* TODO, we could make this a menu into OBJECT_OT_vertex_group_set_active rather than its own operator */ -void PAINT_OT_weight_sample_group(wmOperatorType *ot) -{ - PropertyRNA *prop = NULL; - - /* identifiers */ - ot->name = "Weight Paint Sample Group"; - ot->idname = "PAINT_OT_weight_sample_group"; - ot->description = "Select one of the vertex groups available under current mouse position"; - - /* api callbacks */ - ot->exec = weight_sample_group_exec; - ot->invoke = WM_menu_invoke; - ot->poll = weight_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* keyingset to use (dynamic enum) */ - prop = RNA_def_enum(ot->srna, "group", DummyRNA_DEFAULT_items, 0, "Keying Set", "The Keying Set to use"); - RNA_def_enum_funcs(prop, weight_paint_sample_enum_itemf); - RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; -} +/* ----------------------------------------------------- */ static void do_weight_paint_normalize_all(MDeformVert *dvert, const int defbase_tot, const bool *vgroup_validmap) { float sum = 0.0f, fac; - unsigned int i, tot = 0; + uint i, tot = 0; MDeformWeight *dw; for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) { @@ -1292,7 +473,7 @@ static bool do_weight_paint_normalize_all_locked( float sum = 0.0f, fac; float sum_unlock = 0.0f; float lock_weight = 0.0f; - unsigned int i, tot = 0; + uint i, tot = 0; MDeformWeight *dw; if (lock_flags == NULL) { @@ -1392,8 +573,9 @@ static void do_weight_paint_normalize_all_locked_try_active( } #if 0 /* UNUSED */ -static bool has_unselected_unlocked_bone_group(int defbase_tot, bool *defbase_sel, int selected, - const bool *lock_flags, const bool *vgroup_validmap) +static bool has_unselected_unlocked_bone_group( + int defbase_tot, bool *defbase_sel, int selected, + const bool *lock_flags, const bool *vgroup_validmap) { int i; if (defbase_tot == selected) { @@ -1477,7 +659,7 @@ static void multipaint_apply_change(MDeformVert *dvert, const int defbase_tot, f * Variables stored both for 'active' and 'mirror' sides. */ struct WeightPaintGroupData { - /** index of active group or its mirror + /** index of active group or its mirror * * - 'active' is always `ob->actdef`. * - 'mirror' is -1 when 'ME_EDIT_MIRROR_X' flag id disabled, @@ -1506,13 +688,15 @@ typedef struct WeightPaintInfo { struct WeightPaintGroupData active, mirror; - const bool *lock_flags; /* boolean array for locked bones, - * length of defbase_tot */ - const bool *defbase_sel; /* boolean array for selected bones, - * length of defbase_tot, cant be const because of how its passed */ - - const bool *vgroup_validmap; /* same as WeightPaintData.vgroup_validmap, - * only added here for convenience */ + /* boolean array for locked bones, + * length of defbase_tot */ + const bool *lock_flags; + /* boolean array for selected bones, + * length of defbase_tot, cant be const because of how its passed */ + const bool *defbase_sel; + /* same as WeightPaintData.vgroup_validmap, + * only added here for convenience */ + const bool *vgroup_validmap; bool do_flip; bool do_multipaint; @@ -1523,16 +707,16 @@ typedef struct WeightPaintInfo { static void do_weight_paint_vertex_single( /* vars which remain the same for every vert */ - VPaint *wp, Object *ob, const WeightPaintInfo *wpi, + const VPaint *wp, Object *ob, const WeightPaintInfo *wpi, /* vars which change on each stroke */ - const unsigned int index, float alpha, float paintweight - ) + const uint index, float alpha, float paintweight) { Mesh *me = ob->data; MDeformVert *dv = &me->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - MDeformWeight *dw, *dw_prev; + + MDeformWeight *dw; + float weight_prev; /* mirror vars */ int index_mirr; @@ -1541,20 +725,6 @@ static void do_weight_paint_vertex_single( MDeformVert *dv_mirr; MDeformWeight *dw_mirr; - if (wp->flag & VP_ONLYVGROUP) { - dw = defvert_find_index(dv, wpi->active.index); - dw_prev = defvert_find_index(wp->wpaint_prev + index, wpi->active.index); - } - else { - dw = defvert_verify_index(dv, wpi->active.index); - dw_prev = defvert_verify_index(wp->wpaint_prev + index, wpi->active.index); - } - - if (dw == NULL || dw_prev == NULL) { - return; - } - - /* from now on we can check if mirrors enabled if this var is -1 and not bother with the flag */ if (me->editflag & ME_EDIT_MIRROR_X) { index_mirr = mesh_get_x_mirror_vert(ob, NULL, index, topology); @@ -1570,11 +740,21 @@ static void do_weight_paint_vertex_single( index_mirr = vgroup_mirr = -1; } + if (wp->flag & VP_FLAG_VGROUP_RESTRICT) { + dw = defvert_find_index(dv, wpi->active.index); + } + else { + dw = defvert_verify_index(dv, wpi->active.index); + } + + if (dw == NULL) { + return; + } /* get the mirror def vars */ if (index_mirr != -1) { dv_mirr = &me->dvert[index_mirr]; - if (wp->flag & VP_ONLYVGROUP) { + if (wp->flag & VP_FLAG_VGROUP_RESTRICT) { dw_mirr = defvert_find_index(dv_mirr, vgroup_mirr); if (dw_mirr == NULL) { @@ -1604,12 +784,28 @@ static void do_weight_paint_vertex_single( dw_mirr = NULL; } + if ((wp->paint.brush->flag & BRUSH_ACCUMULATE) == 0) { + MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index); + if (index_mirr != -1) { + defweight_prev_init(dvert_prev, me->dvert, index_mirr); + } + + weight_prev = defvert_find_weight(dv_prev, wpi->active.index); + } + else { + weight_prev = dw->weight; + } + /* If there are no normalize-locks or multipaint, * then there is no need to run the more complicated checks */ { - dw->weight = wpaint_blend(wp, dw->weight, dw_prev->weight, alpha, paintweight, - wpi->brush_alpha_value, wpi->do_flip); + float new_weight = wpaint_blend( + wp, weight_prev, alpha, paintweight, + wpi->brush_alpha_value, wpi->do_flip); + + dw->weight = wpaint_clamp_monotonic(weight_prev, dw->weight, new_weight); /* WATCH IT: take care of the ordering of applying mirror -> normalize, * can give wrong results [#26193], least confusing if normalize is done last */ @@ -1668,13 +864,12 @@ static void do_weight_paint_vertex_single( static void do_weight_paint_vertex_multi( /* vars which remain the same for every vert */ - VPaint *wp, Object *ob, const WeightPaintInfo *wpi, + const VPaint *wp, Object *ob, const WeightPaintInfo *wpi, /* vars which change on each stroke */ - const unsigned int index, float alpha, float paintweight) + const uint index, float alpha, float paintweight) { Mesh *me = ob->data; MDeformVert *dv = &me->dvert[index]; - MDeformVert *dv_prev = &wp->wpaint_prev[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; /* mirror vars */ @@ -1682,7 +877,7 @@ static void do_weight_paint_vertex_multi( MDeformVert *dv_mirr = NULL; /* weights */ - float oldw, curw, neww, change, curw_mirr, change_mirr; + float curw, oldw, neww, change, curw_mirr, change_mirr; /* from now on we can check if mirrors enabled if this var is -1 and not bother with the flag */ if (me->editflag & ME_EDIT_MIRROR_X) { @@ -1691,11 +886,12 @@ static void do_weight_paint_vertex_multi( if (index_mirr != -1 && index_mirr != index) { dv_mirr = &me->dvert[index_mirr]; } + else { + index_mirr = -1; + } } /* compute weight change by applying the brush to average or sum of group weights */ - oldw = BKE_defvert_multipaint_collective_weight( - dv_prev, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->do_auto_normalize); curw = BKE_defvert_multipaint_collective_weight( dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->do_auto_normalize); @@ -1704,7 +900,22 @@ static void do_weight_paint_vertex_multi( return; } - neww = wpaint_blend(wp, curw, oldw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); + if ((wp->paint.brush->flag & BRUSH_ACCUMULATE) == 0) { + MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index); + if (index_mirr != -1) { + defweight_prev_init(dvert_prev, me->dvert, index_mirr); + } + + oldw = BKE_defvert_multipaint_collective_weight( + dv_prev, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->do_auto_normalize); + } + else { + oldw = curw; + } + + neww = wpaint_blend(wp, oldw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); + neww = wpaint_clamp_monotonic(oldw, curw, neww); change = neww / curw; @@ -1758,9 +969,9 @@ static void do_weight_paint_vertex_multi( static void do_weight_paint_vertex( /* vars which remain the same for every vert */ - VPaint *wp, Object *ob, const WeightPaintInfo *wpi, + const VPaint *wp, Object *ob, const WeightPaintInfo *wpi, /* vars which change on each stroke */ - const unsigned int index, float alpha, float paintweight) + const uint index, float alpha, float paintweight) { if (wpi->do_multipaint) { do_weight_paint_vertex_multi(wp, ob, wpi, index, alpha, paintweight); @@ -1770,13 +981,101 @@ static void do_weight_paint_vertex( } } + +/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.c) */ +static void vertex_paint_init_session(const EvaluationContext *eval_ctx, Scene *scene, Object *ob) +{ + if (ob->sculpt == NULL) { + ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); + BKE_sculpt_update_mesh_elements(eval_ctx, scene, scene->toolsettings->sculpt, ob, 0, false); + } +} + +static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) +{ + /* Create maps */ + struct SculptVertexPaintGeomMap *gmap = NULL; + const Brush *brush = NULL; + if (ob->mode == OB_MODE_VERTEX_PAINT) { + gmap = &ob->sculpt->mode.vpaint.gmap; + brush = BKE_paint_brush(&ts->vpaint->paint); + ob->sculpt->mode_type = OB_MODE_VERTEX_PAINT; + } + else if (ob->mode == OB_MODE_WEIGHT_PAINT) { + gmap = &ob->sculpt->mode.wpaint.gmap; + brush = BKE_paint_brush(&ts->wpaint->paint); + ob->sculpt->mode_type = OB_MODE_WEIGHT_PAINT; + } + else { + ob->sculpt->mode_type = 0; + BLI_assert(0); + return; + } + + Mesh *me = ob->data; + + if (gmap->vert_to_loop == NULL) { + gmap->vert_map_mem = NULL; + gmap->vert_to_loop = NULL; + gmap->poly_map_mem = NULL; + gmap->vert_to_poly = NULL; + BKE_mesh_vert_loop_map_create( + &gmap->vert_to_loop, + &gmap->vert_map_mem, + me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); + BKE_mesh_vert_poly_map_create( + &gmap->vert_to_poly, + &gmap->poly_map_mem, + me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); + } + + /* Create average brush arrays */ + if (ob->mode == OB_MODE_VERTEX_PAINT) { + if ((brush->flag & BRUSH_ACCUMULATE) == 0) { + if (ob->sculpt->mode.vpaint.previous_color == NULL) { + ob->sculpt->mode.vpaint.previous_color = + MEM_callocN(me->totloop * sizeof(uint), __func__); + } + } + else { + MEM_SAFE_FREE(ob->sculpt->mode.vpaint.previous_color); + } + } + else if (ob->mode == OB_MODE_WEIGHT_PAINT) { + if ((brush->flag & BRUSH_ACCUMULATE) == 0) { + if (ob->sculpt->mode.wpaint.alpha_weight == NULL) { + ob->sculpt->mode.wpaint.alpha_weight = + MEM_callocN(me->totvert * sizeof(float), __func__); + } + if (ob->sculpt->mode.wpaint.dvert_prev == NULL) { + ob->sculpt->mode.wpaint.dvert_prev = + MEM_callocN(me->totvert * sizeof(MDeformVert), __func__); + MDeformVert *dv = ob->sculpt->mode.wpaint.dvert_prev; + for (int i = 0; i < me->totvert; i++, dv++) { + /* Use to show this isn't initialized, never apply to the mesh data. */ + dv->flag = 1; + } + } + } + else { + MEM_SAFE_FREE(ob->sculpt->mode.wpaint.alpha_weight); + if (ob->sculpt->mode.wpaint.dvert_prev != NULL) { + BKE_defvert_array_free_elems(ob->sculpt->mode.wpaint.dvert_prev, me->totvert); + MEM_freeN(ob->sculpt->mode.wpaint.dvert_prev); + ob->sculpt->mode.wpaint.dvert_prev = NULL; + } + } + } + +} + /* *************** set wpaint operator ****************** */ /** * \note Keep in sync with #vpaint_mode_toggle_exec */ static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op) -{ +{ Object *ob = CTX_data_active_object(C); const int mode_flag = OB_MODE_WEIGHT_PAINT; const bool is_mode_set = (ob->mode & mode_flag) != 0; @@ -1806,13 +1105,25 @@ static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op) ED_mesh_mirror_spatial_table(NULL, NULL, NULL, NULL, 'e'); ED_mesh_mirror_topo_table(NULL, NULL, 'e'); + /* If the cache is not released by a cancel or a done, free it now. */ + if (ob->sculpt->cache) { + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; + } + + BKE_sculptsession_free(ob); + paint_cursor_delete_textures(); } else { + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); + ob->mode |= mode_flag; if (wp == NULL) - wp = scene->toolsettings->wpaint = new_vpaint(1); + wp = scene->toolsettings->wpaint = new_vpaint(); paint_cursor_start(C, weight_paint_poll); @@ -1821,9 +1132,15 @@ static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op) /* weight paint specific */ ED_mesh_mirror_spatial_table(ob, NULL, NULL, NULL, 's'); ED_vgroup_sync_from_pose(ob); + + /* Create vertex/weight paint mode session data */ + if (ob->sculpt) { + BKE_sculptsession_free(ob); + } + vertex_paint_init_session(&eval_ctx, scene, ob); } - - BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_PAINT); + + BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_ALL); /* Weightpaint works by overriding colors in mesh, * so need to make sure we recalc on enter and @@ -1852,41 +1169,28 @@ static int paint_poll_test(bContext *C) void PAINT_OT_weight_paint_toggle(wmOperatorType *ot) { - + /* identifiers */ ot->name = "Weight Paint Mode"; ot->idname = "PAINT_OT_weight_paint_toggle"; ot->description = "Toggle weight paint mode in 3D view"; - + /* api callbacks */ ot->exec = wpaint_mode_toggle_exec; ot->poll = paint_poll_test; - + /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - } /* ************ weight paint operator ********** */ -enum eWPaintFlag { - WPAINT_ENSURE_MIRROR = (1 << 0), -}; - -struct WPaintVGroupIndex { - int active; - int mirror; -}; - struct WPaintData { ViewContext vc; - int *indexar; + struct NormalAnglePrecalc normal_angle_precalc; struct WeightPaintGroupData active, mirror; - void *vp_handle; - DMCoNo *vertexcosnos; - float wpimat[3][3]; /* variables for auto normalize */ @@ -1898,113 +1202,148 @@ struct WPaintData { int defbase_tot_sel; /* number of selected groups */ bool do_multipaint; /* true if multipaint enabled and multiple groups selected */ - /* variables for blur */ - struct { - MeshElemMap *vmap; - int *vmap_mem; - } blur_data; - - BLI_Stack *accumulate_stack; /* for reuse (WPaintDefer) */ - int defbase_tot; + + /* Special storage for smear brush, avoid feedback loop - update each step and swap. */ + struct { + float *weight_prev; + float *weight_curr; + } smear; }; -/* ensure we have data on wpaint start, add if needed */ -static bool wpaint_ensure_data( - bContext *C, wmOperator *op, - enum eWPaintFlag flag, struct WPaintVGroupIndex *vgroup_index) +/* Initialize the stroke cache invariants from operator properties */ +static void vwpaint_update_cache_invariants( + bContext *C, const VPaint *vp, SculptSession *ss, wmOperator *op, const float mouse[2]) { + StrokeCache *cache; Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + const Brush *brush = vp->paint.brush; + ViewContext *vc = paint_stroke_view_context(op->customdata); Object *ob = CTX_data_active_object(C); - Mesh *me = BKE_mesh_from_object(ob); + float mat[3][3]; + float view_dir[3] = {0.0f, 0.0f, 1.0f}; + int mode; - if (vgroup_index) { - vgroup_index->active = -1; - vgroup_index->mirror = -1; + /* VW paint needs to allocate stroke cache before update is called. */ + if (!ss->cache) { + cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); + ss->cache = cache; } - - if (scene->obedit) { - return false; + else { + cache = ss->cache; } - if (me == NULL || me->totpoly == 0) { - return false; - } + /* Initial mouse location */ + if (mouse) + copy_v2_v2(cache->initial_mouse, mouse); + else + zero_v2(cache->initial_mouse); - /* if nothing was added yet, we make dverts and a vertex deform group */ - if (!me->dvert) { - BKE_object_defgroup_data_create(&me->id); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); - } + mode = RNA_enum_get(op->ptr, "mode"); + cache->invert = mode == BRUSH_STROKE_INVERT; + cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH; + /* not very nice, but with current events system implementation + * we can't handle brush appearance inversion hotkey separately (sergey) */ + if (cache->invert) ups->draw_inverted = true; + else ups->draw_inverted = false; - /* this happens on a Bone select, when no vgroup existed yet */ - if (ob->actdef <= 0) { - Object *modob; - if ((modob = modifiers_isDeformedByArmature(ob))) { - Bone *actbone = ((bArmature *)modob->data)->act_bone; - if (actbone) { - bPoseChannel *pchan = BKE_pose_channel_find_name(modob->pose, actbone->name); + copy_v2_v2(cache->mouse, cache->initial_mouse); + /* Truly temporary data that isn't stored in properties */ + cache->vc = vc; + cache->brush = brush; + cache->first_time = 1; - if (pchan) { - bDeformGroup *dg = defgroup_find_name(ob, pchan->name); - if (dg == NULL) { - dg = BKE_object_defgroup_add_name(ob, pchan->name); /* sets actdef */ - } - else { - int actdef = 1 + BLI_findindex(&ob->defbase, dg); - BLI_assert(actdef >= 0); - ob->actdef = actdef; - } - } - } - } - } - if (BLI_listbase_is_empty(&ob->defbase)) { - BKE_object_defgroup_add(ob); - } + /* cache projection matrix */ + ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); - /* ensure we don't try paint onto an invalid group */ - if (ob->actdef <= 0) { - BKE_report(op->reports, RPT_WARNING, "No active vertex group for painting, aborting"); - return false; + invert_m4_m4(ob->imat, ob->obmat); + copy_m3_m4(mat, cache->vc->rv3d->viewinv); + mul_m3_v3(mat, view_dir); + copy_m3_m4(mat, ob->imat); + mul_m3_v3(mat, view_dir); + normalize_v3_v3(cache->true_view_normal, view_dir); + + copy_v3_v3(cache->view_normal, cache->true_view_normal); + cache->bstrength = BKE_brush_alpha_get(scene, brush); + cache->is_last_valid = false; +} + +/* Initialize the stroke cache variants from operator properties */ +static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr) +{ + Scene *scene = CTX_data_scene(C); + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + Brush *brush = BKE_paint_brush(&vp->paint); + + /* This effects the actual brush radius, so things farther away + * are compared with a larger radius and vise versa. */ + if (cache->first_time) { + RNA_float_get_array(ptr, "location", cache->true_location); } - if (vgroup_index) { - vgroup_index->active = ob->actdef - 1; + RNA_float_get_array(ptr, "mouse", cache->mouse); + + /* XXX: Use pressure value from first brush step for brushes which don't + * support strokes (grab, thumb). They depends on initial state and + * brush coord/pressure/etc. + * It's more an events design issue, which doesn't split coordinate/pressure/angle + * changing events. We should avoid this after events system re-design */ + if (paint_supports_dynamic_size(brush, ePaintSculpt) || cache->first_time) { + cache->pressure = RNA_float_get(ptr, "pressure"); } - if (flag & WPAINT_ENSURE_MIRROR) { - if (me->editflag & ME_EDIT_MIRROR_X) { - int mirror = wpaint_mirror_vgroup_ensure(ob, ob->actdef - 1); - if (vgroup_index) { - vgroup_index->mirror = mirror; - } + /* Truly temporary data that isn't stored in properties */ + if (cache->first_time) { + if (!BKE_brush_use_locked_size(scene, brush)) { + cache->initial_radius = paint_calc_object_space_radius( + cache->vc, cache->true_location, BKE_brush_size_get(scene, brush)); + BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius); + } + else { + cache->initial_radius = BKE_brush_unprojected_radius_get(scene, brush); } } - return true; + if (BKE_brush_use_size_pressure(scene, brush) && paint_supports_dynamic_size(brush, ePaintSculpt)) { + cache->radius = cache->initial_radius * cache->pressure; + } + else { + cache->radius = cache->initial_radius; + } + + cache->radius_squared = cache->radius * cache->radius; + + if (ss->pbvh) { + BKE_pbvh_update(ss->pbvh, PBVH_UpdateRedraw, NULL); + BKE_pbvh_update(ss->pbvh, PBVH_UpdateBB, NULL); + } } -static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UNUSED(mouse[2])) +static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) { Scene *scene = CTX_data_scene(C); struct PaintStroke *stroke = op->customdata; ToolSettings *ts = scene->toolsettings; - VPaint *wp = ts->wpaint; Object *ob = CTX_data_active_object(C); Mesh *me = BKE_mesh_from_object(ob); struct WPaintData *wpd; struct WPaintVGroupIndex vgroup_index; int defbase_tot, defbase_tot_sel; bool *defbase_sel; - const Brush *brush = BKE_paint_brush(&wp->paint); + SculptSession *ss = ob->sculpt; + VPaint *vp = CTX_data_tool_settings(C)->wpaint; + EvaluationContext eval_ctx; float mat[4][4], imat[4][4]; - if (wpaint_ensure_data(C, op, WPAINT_ENSURE_MIRROR, &vgroup_index) == false) { + if (ED_wpaint_ensure_data(C, op->reports, WPAINT_ENSURE_MIRROR, &vgroup_index) == false) { return false; } + CTX_data_eval_ctx(C, &eval_ctx); + { /* check if we are attempting to paint onto a locked vertex group, * and other options disallow it from doing anything useful */ @@ -2052,6 +1391,8 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UN wpd = MEM_callocN(sizeof(struct WPaintData), "WPaintData"); paint_stroke_set_mode_data(stroke, wpd); view3d_set_viewcontext(C, &wpd->vc); + view_angle_limits_init(&wpd->normal_angle_precalc, vp->paint.brush->falloff_angle, + (vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0); wpd->active.index = vgroup_index.active; wpd->mirror.index = vgroup_index.mirror; @@ -2096,22 +1437,24 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UN wpd->mirror.lock = tmpflags; } - /* painting on subsurfs should give correct points too, this returns me->totvert amount */ - wpd->vp_handle = ED_vpaint_proj_handle_create(scene, ob, &wpd->vertexcosnos); - - wpd->indexar = get_indexarray(me); - copy_wpaint_prev(wp, me->dvert, me->totvert); - - if (brush->vertexpaint_tool == PAINT_BLEND_BLUR) { - BKE_mesh_vert_edge_vert_map_create( - &wpd->blur_data.vmap, &wpd->blur_data.vmap_mem, - me->medge, me->totvert, me->totedge); - } - - if ((brush->vertexpaint_tool == PAINT_BLEND_BLUR) && - (brush->flag & BRUSH_ACCUMULATE)) - { - wpd->accumulate_stack = BLI_stack_new(sizeof(struct WPaintDefer), __func__); + if (vp->paint.brush->vertexpaint_tool == PAINT_BLEND_SMEAR) { + wpd->smear.weight_prev = MEM_mallocN(sizeof(float) * me->totvert, __func__); + const MDeformVert *dv = me->dvert; + if (wpd->do_multipaint) { + const bool do_auto_normalize = ((ts->auto_normalize != 0) && (wpd->vgroup_validmap != NULL)); + for (int i = 0; i < me->totvert; i++, dv++) { + float weight = BKE_defvert_multipaint_collective_weight( + dv, wpd->defbase_tot, wpd->defbase_sel, wpd->defbase_tot_sel, do_auto_normalize); + CLAMP(weight, 0.0f, 1.0f); + wpd->smear.weight_prev[i] = weight; + } + } + else { + for (int i = 0; i < me->totvert; i++, dv++) { + wpd->smear.weight_prev[i] = defvert_find_weight(dv, wpd->active.index); + } + } + wpd->smear.weight_curr = MEM_dupallocN(wpd->smear.weight_prev); } /* imat for normals */ @@ -2119,40 +1462,549 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UN invert_m4_m4(imat, mat); copy_m3_m4(wpd->wpimat, imat); + /* If not previously created, create vertex/weight paint mode session data */ + vertex_paint_init_session(&eval_ctx, scene, ob); + vwpaint_update_cache_invariants(C, vp, ss, op, mouse); + vertex_paint_init_session_data(ts, ob); + + if (ob->sculpt->mode.wpaint.dvert_prev != NULL) { + MDeformVert *dv = ob->sculpt->mode.wpaint.dvert_prev; + for (int i = 0; i < me->totvert; i++, dv++) { + /* Use to show this isn't initialized, never apply to the mesh data. */ + dv->flag = 1; + } + } + return true; } -static float wpaint_blur_weight_single(const MDeformVert *dv, const WeightPaintInfo *wpi) +static float dot_vf3vs3(const float brushNormal[3], const short vertexNormal[3]) { - return defvert_find_weight(dv, wpi->active.index); + float normal[3]; + normal_short_to_float_v3(normal, vertexNormal); + return dot_v3v3(brushNormal, normal); } -static float wpaint_blur_weight_multi(const MDeformVert *dv, const WeightPaintInfo *wpi) +static void get_brush_alpha_data( + const Scene *scene, const SculptSession *ss, const Brush *brush, + float *r_brush_size_pressure, float *r_brush_alpha_value, float *r_brush_alpha_pressure) { - float weight = BKE_defvert_multipaint_collective_weight( - dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->do_auto_normalize); - CLAMP(weight, 0.0f, 1.0f); - return weight; + *r_brush_size_pressure = + BKE_brush_size_get(scene, brush) * + (BKE_brush_use_size_pressure(scene, brush) ? ss->cache->pressure : 1.0f); + *r_brush_alpha_value = + BKE_brush_alpha_get(scene, brush); + *r_brush_alpha_pressure = + (BKE_brush_use_alpha_pressure(scene, brush) ? ss->cache->pressure : 1.0f); +} + +static float wpaint_get_active_weight(const MDeformVert *dv, const WeightPaintInfo *wpi) +{ + if (wpi->do_multipaint) { + float weight = BKE_defvert_multipaint_collective_weight( + dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->do_auto_normalize); + + CLAMP(weight, 0.0f, 1.0f); + return weight; + } + else { + return defvert_find_weight(dv, wpi->active.index); + } +} + +static void do_wpaint_brush_blur_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap; + + const Brush *brush = data->brush; + const StrokeCache *cache = ss->cache; + Scene *scene = CTX_data_scene(data->C); + + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop coopresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const char v_flag = data->me->mvert[v_index].flag; + /* If the vertex is selected */ + if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { + /* Get the average poly weight */ + int total_hit_loops = 0; + float weight_final = 0.0f; + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const MPoly *mp = &data->me->mpoly[p_index]; + + total_hit_loops += mp->totloop; + for (int k = 0; k < mp->totloop; k++) { + const int l_index = mp->loopstart + k; + const MLoop *ml = &data->me->mloop[l_index]; + const MDeformVert *dv = &data->me->dvert[ml->v]; + weight_final += wpaint_get_active_weight(dv, data->wpi); + } + } + + /* Apply the weight to the vertex. */ + if (total_hit_loops != 0) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + const float final_alpha = + brush_fade * brush_strength * + grid_alpha * brush_alpha_pressure; + + if ((brush->flag & BRUSH_ACCUMULATE) == 0) { + if (ss->mode.wpaint.alpha_weight[v_index] < final_alpha) { + ss->mode.wpaint.alpha_weight[v_index] = final_alpha; + } + else { + continue; + } + } + + weight_final /= total_hit_loops; + /* Only paint visable verts */ + do_weight_paint_vertex( + data->vp, data->ob, data->wpi, + v_index, final_alpha, weight_final); + } + } + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_wpaint_brush_smear_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap; + + const Brush *brush = data->brush; + const Scene *scene = CTX_data_scene(data->C); + const StrokeCache *cache = ss->cache; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + float brush_dir[3]; + + sub_v3_v3v3(brush_dir, cache->location, cache->last_location); + project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal); + + if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) { + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop cooresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv_curr = &data->me->mvert[v_index]; + + /* If the vertex is selected */ + if (!(use_face_sel || use_vert_sel) || mv_curr->flag & SELECT) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + bool do_color = false; + /* Minimum dot product between brush direction and current + * to neighbor direction is 0.0, meaning orthogonal. */ + float stroke_dot_max = 0.0f; + + /* Get the color of the loop in the opposite direction of the brush movement + * (this callback is specifically for smear.) */ + float weight_final = 0.0; + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const MPoly *mp = &data->me->mpoly[p_index]; + const MLoop *ml_other = &data->me->mloop[mp->loopstart]; + for (int k = 0; k < mp->totloop; k++, ml_other++) { + const uint v_other_index = ml_other->v; + if (v_other_index != v_index) { + const MVert *mv_other = &data->me->mvert[v_other_index]; + + /* Get the direction from the selected vert to the neighbor. */ + float other_dir[3]; + sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co); + project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal); + + normalize_v3(other_dir); + + const float stroke_dot = dot_v3v3(other_dir, brush_dir); + + if (stroke_dot > stroke_dot_max) { + stroke_dot_max = stroke_dot; + weight_final = data->wpd->smear.weight_prev[v_other_index]; + do_color = true; + } + } + } + } + /* Apply weight to vertex */ + if (do_color) { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + const float final_alpha = + brush_fade * brush_strength * + grid_alpha * brush_alpha_pressure; + do_weight_paint_vertex( + data->vp, data->ob, data->wpi, + v_index, final_alpha, (float)weight_final); + /* Access the weight again because it might not have been applied completely. */ + data->wpd->smear.weight_curr[v_index] = + wpaint_get_active_weight(&data->me->dvert[v_index], data->wpi); + } + } + } + } + } + BKE_pbvh_vertex_iter_end; + } +} + +static void do_wpaint_brush_draw_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const Scene *scene = CTX_data_scene(data->C); + + const Brush *brush = data->brush; + const StrokeCache *cache = ss->cache; + /* note: normally `BKE_brush_weight_get(scene, brush)` is used, + * however in this case we calculate a new weight each time. */ + const float paintweight = data->strength; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* Note: grids are 1:1 with corners (aka loops). + * For multires, take the vert whose loop cooresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + + const char v_flag = data->me->mvert[v_index].flag; + /* If the vertex is selected */ + if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + const float final_alpha = brush_fade * brush_strength * grid_alpha * brush_alpha_pressure; + + if ((brush->flag & BRUSH_ACCUMULATE) == 0) { + if (ss->mode.wpaint.alpha_weight[v_index] < final_alpha) { + ss->mode.wpaint.alpha_weight[v_index] = final_alpha; + } + else { + continue; + } + } + + do_weight_paint_vertex( + data->vp, data->ob, data->wpi, + v_index, final_alpha, paintweight); + } + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_wpaint_brush_calc_average_weight_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + StrokeCache *cache = ss->cache; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + struct WPaintAverageAccum *accum = (struct WPaintAverageAccum *)data->custom_data + n; + accum->len = 0; + accum->value = 0.0; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (angle_cos > 0.0 && BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) > 0.0) { + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + // const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const char v_flag = data->me->mvert[v_index].flag; + + /* If the vertex is selected. */ + if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) { + const MDeformVert *dv = &data->me->dvert[v_index]; + accum->len += 1; + accum->value += wpaint_get_active_weight(dv, data->wpi); + } + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void calculate_average_weight(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) +{ + struct WPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__); + data->custom_data = accum; + + BLI_task_parallel_range_ex( + 0, totnode, data, NULL, 0, do_wpaint_brush_calc_average_weight_cb_ex, + ((data->sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT), false); + + uint accum_len = 0; + double accum_weight = 0.0; + for (int i = 0; i < totnode; i++) { + accum_len += accum[i].len; + accum_weight += accum[i].value; + } + if (accum_len != 0) { + accum_weight /= accum_len; + data->strength = (float)accum_weight; + } + + MEM_SAFE_FREE(data->custom_data); /* 'accum' */ +} + + +static void wpaint_paint_leaves( + bContext *C, Object *ob, Sculpt *sd, VPaint *vp, struct WPaintData *wpd, WeightPaintInfo *wpi, + Mesh *me, PBVHNode **nodes, int totnode) +{ + Scene *scene = CTX_data_scene(C); + const Brush *brush = ob->sculpt->cache->brush; + + /* threaded loop over nodes */ + SculptThreadedTaskData data = { + .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .vp = vp, .wpd = wpd, .wpi = wpi, .me = me, .C = C, + }; + + /* Use this so average can modify its weight without touching the brush. */ + data.strength = BKE_brush_weight_get(scene, brush); + + /* current mirroring code cannot be run in parallel */ + bool use_threading = !(me->editflag & ME_EDIT_MIRROR_X); + + switch (brush->vertexpaint_tool) { + case PAINT_BLEND_AVERAGE: + calculate_average_weight(&data, nodes, totnode); + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_draw_task_cb_ex, use_threading, false); + break; + case PAINT_BLEND_SMEAR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_smear_task_cb_ex, use_threading, false); + break; + case PAINT_BLEND_BLUR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_blur_task_cb_ex, use_threading, false); + break; + default: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_draw_task_cb_ex, use_threading, false); + break; + } } -static float wpaint_blur_weight_calc_from_connected( - const MDeformVert *dvert, WeightPaintInfo *wpi, struct WPaintData *wpd, const unsigned int vidx, - float (*blur_weight_func)(const MDeformVert *, const WeightPaintInfo *)) +static PBVHNode **vwpaint_pbvh_gather_generic( + Object *ob, VPaint *wp, Sculpt *sd, Brush *brush, int *r_totnode) { - const MeshElemMap *map = &wpd->blur_data.vmap[vidx]; - float paintweight; - if (map->count != 0) { - paintweight = 0.0f; - for (int j = 0; j < map->count; j++) { - paintweight += blur_weight_func(&dvert[map->indices[j]], wpi); + SculptSession *ss = ob->sculpt; + const bool use_normal = vwpaint_use_normal(wp); + PBVHNode **nodes = NULL; + + /* Build a list of all nodes that are potentially within the brush's area of influence */ + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + SculptSearchSphereData data = { + .ss = ss, + .sd = sd, + .radius_squared = ss->cache->radius_squared, + .original = true, + }; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, r_totnode); + if (use_normal) { + sculpt_pbvh_calc_area_normal(brush, ob, nodes, *r_totnode, true, ss->cache->sculpt_normal_symm); + } + else { + zero_v3(ss->cache->sculpt_normal_symm); } - paintweight /= map->count; } else { - paintweight = blur_weight_func(&dvert[vidx], wpi); + struct DistRayAABB_Precalc dist_ray_to_aabb_precalc; + dist_squared_ray_to_aabb_v3_precalc(&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); + SculptSearchCircleData data = { + .ss = ss, + .sd = sd, + .radius_squared = ss->cache->radius_squared, + .original = true, + .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc, + }; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_circle_cb, &data, &nodes, r_totnode); + if (use_normal) { + copy_v3_v3(ss->cache->sculpt_normal_symm, ss->cache->view_normal); + } + else { + zero_v3(ss->cache->sculpt_normal_symm); + } + } + return nodes; +} + +static void wpaint_do_paint( + bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi, + Mesh *me, Brush *brush, const char symm, const int axis, const int i, const float angle) +{ + SculptSession *ss = ob->sculpt; + ss->cache->radial_symmetry_pass = i; + sculpt_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); + + int totnode; + PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, wp, sd, brush, &totnode); + + wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes, totnode); + + if (nodes) + MEM_freeN(nodes); +} + +static void wpaint_do_radial_symmetry( + bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi, + Mesh *me, Brush *brush, const char symm, const int axis) +{ + for (int i = 1; i < wp->radial_symm[axis - 'X']; i++) { + const float angle = (2.0 * M_PI) * i / wp->radial_symm[axis - 'X']; + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, axis, i, angle); } +} - return paintweight; +/* near duplicate of: sculpt.c's, 'do_symmetrical_brush_actions' and 'vpaint_do_symmetrical_brush_actions'. */ +static void wpaint_do_symmetrical_brush_actions( + bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi) +{ + Brush *brush = BKE_paint_brush(&wp->paint); + Mesh *me = ob->data; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const char symm = wp->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + int i = 0; + + /* initial stroke */ + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Y'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Z'); + + cache->symmetry = symm; + + /* symm is a bit combination of XYZ - 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ + for (i = 1; i <= symm; i++) { + if ((symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5)))) { + cache->mirror_symmetry_pass = i; + cache->radial_symmetry_pass = 0; + sculpt_cache_calc_brushdata_symm(cache, i, 0, 0); + + if (i & (1 << 0)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X'); + } + if (i & (1 << 1)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y'); + } + if (i & (1 << 2)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z'); + } + } + } + copy_v3_v3(cache->true_last_location, cache->true_location); + cache->is_last_valid = true; } static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) @@ -2163,24 +2015,17 @@ static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P Brush *brush = BKE_paint_brush(&wp->paint); struct WPaintData *wpd = paint_stroke_mode_data(stroke); ViewContext *vc; - Object *ob; - Mesh *me; + Object *ob = CTX_data_active_object(C); + + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + vwpaint_update_cache_variants(C, wp, ob, itemptr); + float mat[4][4]; - float paintweight; - int *indexar; - unsigned int index, totindex; float mval[2]; - const bool use_blur = (brush->vertexpaint_tool == PAINT_BLEND_BLUR); - bool use_vert_sel; - bool use_face_sel; - bool use_depth; - - const float pressure = RNA_float_get(itemptr, "pressure"); - const float brush_size_pressure = - BKE_brush_size_get(scene, brush) * (BKE_brush_use_size_pressure(scene, brush) ? pressure : 1.0f); + const float brush_alpha_value = BKE_brush_alpha_get(scene, brush); - const float brush_alpha_pressure = - brush_alpha_value * (BKE_brush_use_alpha_pressure(scene, brush) ? pressure : 1.0f); /* intentionally don't initialize as NULL, make sure we initialize all members below */ WeightPaintInfo wpi; @@ -2193,21 +2038,15 @@ static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P return; } - float (*blur_weight_func)(const MDeformVert *, const WeightPaintInfo *) = - wpd->do_multipaint ? wpaint_blur_weight_multi : wpaint_blur_weight_single; - vc = &wpd->vc; ob = vc->obact; - me = ob->data; - indexar = wpd->indexar; - + view3d_operator_needs_opengl(C); ED_view3d_init_mats_rv3d(ob, vc->rv3d); /* load projection matrix */ mul_m4_m4m4(mat, vc->rv3d->persmat, ob->obmat); - RNA_float_get_array(itemptr, "mouse", mval); /* *** setup WeightPaintInfo - pass onto do_weight_paint_vertex *** */ wpi.defbase_tot = wpd->defbase_tot; @@ -2225,182 +2064,53 @@ static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P wpi.brush_alpha_value = brush_alpha_value; /* *** done setting up WeightPaintInfo *** */ + wpaint_do_symmetrical_brush_actions(C, ob, wp, sd, wpd, &wpi); + swap_m4m4(vc->rv3d->persmat, mat); - swap_m4m4(wpd->vc.rv3d->persmat, mat); - - use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; - use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - use_depth = (vc->v3d->flag & V3D_ZBUF_SELECT) != 0; - - /* which faces are involved */ - if (use_depth) { - char editflag_prev = me->editflag; - - /* Ugly hack, to avoid drawing vertex index when getting the face index buffer - campbell */ - me->editflag &= ~ME_EDIT_PAINT_VERT_SEL; - if (use_vert_sel) { - /* Ugly x2, we need this so hidden faces don't draw */ - me->editflag |= ME_EDIT_PAINT_FACE_SEL; - } - totindex = sample_backbuf_area(vc, indexar, me->totpoly, mval[0], mval[1], brush_size_pressure); - me->editflag = editflag_prev; - - if (use_face_sel && me->totpoly) { - MPoly *mpoly = me->mpoly; - for (index = 0; index < totindex; index++) { - if (indexar[index] && indexar[index] <= me->totpoly) { - MPoly *mp = &mpoly[indexar[index] - 1]; - - if ((mp->flag & ME_FACE_SEL) == 0) { - indexar[index] = 0; - } - } - } - } - } - else { - indexar = NULL; + if (wp->paint.brush->vertexpaint_tool == PAINT_BLEND_SMEAR) { + SWAP(float *, wpd->smear.weight_curr, wpd->smear.weight_prev); } - /* incase we have modifiers */ - ED_vpaint_proj_handle_update(wpd->vp_handle, vc->ar, mval); - - /* make sure each vertex gets treated only once */ - /* and calculate filter weight */ - paintweight = BKE_brush_weight_get(scene, brush); + /* calculate pivot for rotation around seletion if needed */ + /* also needed for "View Selected" on last stroke */ + paint_last_stroke_update(scene, vc->ar, mval); - if (use_depth) { - for (index = 0; index < totindex; index++) { - if (indexar[index] && indexar[index] <= me->totpoly) { - MPoly *mpoly = me->mpoly + (indexar[index] - 1); - MLoop *ml = me->mloop + mpoly->loopstart; - int i; + BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_ALL); - if (use_vert_sel) { - for (i = 0; i < mpoly->totloop; i++, ml++) { - me->dvert[ml->v].flag = (me->mvert[ml->v].flag & SELECT); - } - } - else { - for (i = 0; i < mpoly->totloop; i++, ml++) { - me->dvert[ml->v].flag = 1; - } - } - } - } - } - else { - const unsigned int totvert = me->totvert; - unsigned int i; + DEG_id_tag_update(ob->data, 0); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + swap_m4m4(wpd->vc.rv3d->persmat, mat); - /* in the case of face selection we need to flush */ - if (use_vert_sel || use_face_sel) { - for (i = 0; i < totvert; i++) { - me->dvert[i].flag = me->mvert[i].flag & SELECT; - } + rcti r; + if (sculpt_get_redraw_rect(vc->ar, CTX_wm_region_view3d(C), ob, &r)) { + if (ss->cache) { + ss->cache->current_r = r; } - else { - for (i = 0; i < totvert; i++) { - me->dvert[i].flag = SELECT; - } - } - } - /* accumulate means we refer to the previous, - * which is either the last update, or when we started painting */ - BLI_Stack *accumulate_stack = wpd->accumulate_stack; - const bool use_accumulate = (accumulate_stack != NULL); - BLI_assert(accumulate_stack == NULL || BLI_stack_is_empty(accumulate_stack)); - - const MDeformVert *dvert_prev = use_accumulate ? me->dvert : wp->wpaint_prev; - -#define WP_PAINT(v_idx_var) \ - { \ - unsigned int vidx = v_idx_var; \ - if (me->dvert[vidx].flag) { \ - const float alpha = calc_vp_alpha_col_dl( \ - wp, vc, wpd->wpimat, &wpd->vertexcosnos[vidx], \ - mval, brush_size_pressure, brush_alpha_pressure, NULL); \ - if (alpha) { \ - if (use_blur) { \ - paintweight = wpaint_blur_weight_calc_from_connected( \ - dvert_prev, &wpi, wpd, vidx, blur_weight_func); \ - } \ - if (use_accumulate) { \ - struct WPaintDefer *dweight = BLI_stack_push_r(accumulate_stack); \ - dweight->index = vidx; \ - dweight->alpha = alpha; \ - dweight->weight = paintweight; \ - } \ - else { \ - do_weight_paint_vertex(wp, ob, &wpi, vidx, alpha, paintweight); \ - } \ - } \ - me->dvert[vidx].flag = 0; \ - } \ - } (void)0 - - if (use_depth) { - for (index = 0; index < totindex; index++) { - - if (indexar[index] && indexar[index] <= me->totpoly) { - MPoly *mpoly = me->mpoly + (indexar[index] - 1); - MLoop *ml = me->mloop + mpoly->loopstart; - int i; - - for (i = 0; i < mpoly->totloop; i++, ml++) { - WP_PAINT(ml->v); - } - } + /* previous is not set in the current cache else + * the partial rect will always grow */ + if (ss->cache) { + if (!BLI_rcti_is_empty(&ss->cache->previous_r)) + BLI_rcti_union(&r, &ss->cache->previous_r); } - } - else { - const unsigned int totvert = me->totvert; - unsigned int i; - for (i = 0; i < totvert; i++) { - WP_PAINT(i); - } - } -#undef WP_PAINT + r.xmin += vc->ar->winrct.xmin - 2; + r.xmax += vc->ar->winrct.xmin + 2; + r.ymin += vc->ar->winrct.ymin - 2; + r.ymax += vc->ar->winrct.ymin + 2; - if (use_accumulate) { - unsigned int defer_count = BLI_stack_count(accumulate_stack); - while (defer_count--) { - struct WPaintDefer *dweight = BLI_stack_peek(accumulate_stack); - do_weight_paint_vertex(wp, ob, &wpi, dweight->index, dweight->alpha, dweight->weight); - BLI_stack_discard(accumulate_stack); - } + ss->partial_redraw = 1; } - - - /* *** free wpi members */ - /* *** done freeing wpi members */ - - - swap_m4m4(vc->rv3d->persmat, mat); - - /* calculate pivot for rotation around seletion if needed */ - /* also needed for "View Selected" on last stroke */ - paint_last_stroke_update(scene, vc->ar, mval); - - BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_PAINT); - - DEG_id_tag_update(ob->data, 0); - ED_region_tag_redraw(vc->ar); + ED_region_tag_redraw_partial(vc->ar, &r); } static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) { - ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); struct WPaintData *wpd = paint_stroke_mode_data(stroke); - + if (wpd) { - ED_vpaint_proj_handle_free(wpd->vp_handle); - MEM_freeN(wpd->indexar); - if (wpd->defbase_sel) MEM_freeN((void *)wpd->defbase_sel); if (wpd->vgroup_validmap) @@ -2411,29 +2121,19 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) MEM_freeN((void *)wpd->active.lock); if (wpd->mirror.lock) MEM_freeN((void *)wpd->mirror.lock); - - if (wpd->blur_data.vmap) { - MEM_freeN(wpd->blur_data.vmap); - } - if (wpd->blur_data.vmap_mem) { - MEM_freeN(wpd->blur_data.vmap_mem); - } - - if (wpd->accumulate_stack) { - BLI_stack_free(wpd->accumulate_stack); - } + if (wpd->smear.weight_prev) + MEM_freeN(wpd->smear.weight_prev); + if (wpd->smear.weight_curr) + MEM_freeN(wpd->smear.weight_curr); MEM_freeN(wpd); } - - /* frees prev buffer */ - copy_wpaint_prev(ts->wpaint, NULL, 0); - + /* and particles too */ if (ob->particlesystem.first) { ParticleSystem *psys; int i; - + for (psys = ob->particlesystem.first; psys; psys = psys->next) { for (i = 0; i < PSYS_TOT_VG; i++) { if (psys->vgroup[i] == ob->actdef) { @@ -2447,6 +2147,9 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) DEG_id_tag_update(ob->data, 0); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; } @@ -2454,10 +2157,11 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) { int retval; - op->customdata = paint_stroke_new(C, op, NULL, wpaint_stroke_test_start, - wpaint_stroke_update_step, NULL, - wpaint_stroke_done, event->type); - + op->customdata = paint_stroke_new( + C, op, sculpt_stroke_get_location, wpaint_stroke_test_start, + wpaint_stroke_update_step, NULL, + wpaint_stroke_done, event->type); + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { paint_stroke_data_free(op); return OPERATOR_FINISHED; @@ -2467,15 +2171,16 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) OPERATOR_RETVAL_CHECK(retval); BLI_assert(retval == OPERATOR_RUNNING_MODAL); - + return OPERATOR_RUNNING_MODAL; } static int wpaint_exec(bContext *C, wmOperator *op) { - op->customdata = paint_stroke_new(C, op, NULL, wpaint_stroke_test_start, - wpaint_stroke_update_step, NULL, - wpaint_stroke_done, 0); + op->customdata = paint_stroke_new( + C, op, sculpt_stroke_get_location, wpaint_stroke_test_start, + wpaint_stroke_update_step, NULL, + wpaint_stroke_done, 0); /* frees op->customdata */ paint_stroke_exec(C, op); @@ -2485,64 +2190,33 @@ static int wpaint_exec(bContext *C, wmOperator *op) static void wpaint_cancel(bContext *C, wmOperator *op) { + Object *ob = CTX_data_active_object(C); + if (ob->sculpt->cache) { + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; + } + paint_stroke_cancel(C, op); } void PAINT_OT_weight_paint(wmOperatorType *ot) { - /* identifiers */ ot->name = "Weight Paint"; ot->idname = "PAINT_OT_weight_paint"; ot->description = "Paint a stroke in the current vertex group's weights"; - + /* api callbacks */ ot->invoke = wpaint_invoke; ot->modal = paint_stroke_modal; ot->exec = wpaint_exec; ot->poll = weight_paint_poll; ot->cancel = wpaint_cancel; - + /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; - - paint_stroke_operator_properties(ot); -} - -static int weight_paint_set_exec(bContext *C, wmOperator *op) -{ - struct Scene *scene = CTX_data_scene(C); - Object *obact = CTX_data_active_object(C); - ToolSettings *ts = CTX_data_tool_settings(C); - Brush *brush = BKE_paint_brush(&ts->wpaint->paint); - float vgroup_weight = BKE_brush_weight_get(scene, brush); - - if (wpaint_ensure_data(C, op, WPAINT_ENSURE_MIRROR, NULL) == false) { - return OPERATOR_CANCELLED; - } - - if (ED_wpaint_fill(scene->toolsettings->wpaint, obact, vgroup_weight)) { - ED_region_tag_redraw(CTX_wm_region(C)); /* XXX - should redraw all 3D views */ - return OPERATOR_FINISHED; - } - else { - return OPERATOR_CANCELLED; - } -} -void PAINT_OT_weight_set(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Set Weight"; - ot->idname = "PAINT_OT_weight_set"; - ot->description = "Fill the active vertex group with the current paint weight"; - - /* api callbacks */ - ot->exec = weight_paint_set_exec; - ot->poll = mask_paint_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + paint_stroke_operator_properties(ot); } /* ************ set / clear vertex paint mode ********** */ @@ -2551,7 +2225,7 @@ void PAINT_OT_weight_set(wmOperatorType *ot) * \note Keep in sync with #wpaint_mode_toggle_exec */ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) -{ +{ Object *ob = CTX_data_active_object(C); const int mode_flag = OB_MODE_VERTEX_PAINT; const bool is_mode_set = (ob->mode & mode_flag) != 0; @@ -2566,7 +2240,7 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) } me = BKE_mesh_from_object(ob); - + /* toggle: end vpaint */ if (is_mode_set) { ob->mode &= ~mode_flag; @@ -2574,46 +2248,68 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) if (me->editflag & ME_EDIT_PAINT_FACE_SEL) { BKE_mesh_flush_select_from_polys(me); } + else if (me->editflag & ME_EDIT_PAINT_VERT_SEL) { + BKE_mesh_flush_select_from_verts(me); + } + + /* If the cache is not released by a cancel or a done, free it now. */ + if (ob->sculpt->cache) { + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; + } + + BKE_sculptsession_free(ob); paint_cursor_delete_textures(); } else { + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); + ob->mode |= mode_flag; - if (me->mloopcol == NULL) { - make_vertexcol(ob); - } + ED_mesh_color_ensure(me, NULL); if (vp == NULL) - vp = scene->toolsettings->vpaint = new_vpaint(0); - + vp = scene->toolsettings->vpaint = new_vpaint(); + paint_cursor_start(C, vertex_paint_poll); BKE_paint_init(scene, ePaintVertex, PAINT_CURSOR_VERTEX_PAINT); + + /* Create vertex/weight paint mode session data */ + if (ob->sculpt) { + if (ob->sculpt->cache) { + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; + } + BKE_sculptsession_free(ob); + } + vertex_paint_init_session(&eval_ctx, scene, ob); } - - BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_PAINT); + + BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_ALL); /* update modifier stack for mapping requirements */ DEG_id_tag_update(&me->id, 0); - + WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); - + return OPERATOR_FINISHED; } void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot) { - /* identifiers */ ot->name = "Vertex Paint Mode"; ot->idname = "PAINT_OT_vertex_paint_toggle"; ot->description = "Toggle the vertex paint mode in 3D view"; - + /* api callbacks */ ot->exec = vpaint_mode_toggle_exec; ot->poll = paint_poll_test; - + /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } @@ -2628,7 +2324,7 @@ void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot) * - validate context (add mcol) * - create customdata storage * - call paint once (mouse click) - * - add modal handler + * - add modal handler * * Operator->modal() * - for every mousemove, apply vertex paint @@ -2645,13 +2341,14 @@ typedef struct PolyFaceMap { int facenr; } PolyFaceMap; -typedef struct VPaintData { +struct VPaintData { ViewContext vc; - unsigned int paintcol; - int *indexar; + struct NormalAnglePrecalc normal_angle_precalc; + + uint paintcol; struct VertProjHandle *vp_handle; - DMCoNo *vertexcosnos; + struct DMCoNo *vertexcosnos; float vpimat[3][3]; @@ -2664,9 +2361,15 @@ typedef struct VPaintData { bool *mlooptag; bool is_texbrush; -} VPaintData; -static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const float UNUSED(mouse[2])) + /* Special storage for smear brush, avoid feedback loop - update each step and swap. */ + struct { + uint *color_prev; + uint *color_curr; + } smear; +}; + +static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) { Scene *scene = CTX_data_scene(C); ToolSettings *ts = scene->toolsettings; @@ -2677,25 +2380,27 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f Object *ob = CTX_data_active_object(C); Mesh *me; float mat[4][4], imat[4][4]; + SculptSession *ss = ob->sculpt; + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); /* context checks could be a poll() */ me = BKE_mesh_from_object(ob); if (me == NULL || me->totpoly == 0) return false; - - if (me->mloopcol == NULL) - make_vertexcol(ob); + + ED_mesh_color_ensure(me, NULL); if (me->mloopcol == NULL) return false; /* make mode data storage */ - vpd = MEM_callocN(sizeof(struct VPaintData), "VPaintData"); + vpd = MEM_callocN(sizeof(*vpd), "VPaintData"); paint_stroke_set_mode_data(stroke, vpd); view3d_set_viewcontext(C, &vpd->vc); - - vpd->vp_handle = ED_vpaint_proj_handle_create(vpd->vc.scene, ob, &vpd->vertexcosnos); + view_angle_limits_init(&vpd->normal_angle_precalc, vp->paint.brush->falloff_angle, + (vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0); - vpd->indexar = get_indexarray(me); vpd->paintcol = vpaint_get_current_col(scene, vp); vpd->is_texbrush = !(brush->vertexpaint_tool == PAINT_BLEND_BLUR) && @@ -2717,84 +2422,574 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f vpd->mlooptag = MEM_mallocN(sizeof(bool) * me->totloop, "VPaintData mlooptag"); } - /* for filtering */ - copy_vpaint_prev(vp, (unsigned int *)me->mloopcol, me->totloop); - + if (brush->vertexpaint_tool == PAINT_BLEND_SMEAR) { + vpd->smear.color_prev = MEM_mallocN(sizeof(uint) * me->totloop, __func__); + memcpy(vpd->smear.color_prev, me->mloopcol, sizeof(uint) * me->totloop); + vpd->smear.color_curr = MEM_dupallocN(vpd->smear.color_prev); + } + + /* Create projection handle */ + if (vpd->is_texbrush) { + ob->sculpt->building_vp_handle = true; + vpd->vp_handle = ED_vpaint_proj_handle_create(&eval_ctx, scene, ob, &vpd->vertexcosnos); + ob->sculpt->building_vp_handle = false; + } + /* some old cruft to sort out later */ mul_m4_m4m4(mat, vpd->vc.rv3d->viewmat, ob->obmat); invert_m4_m4(imat, mat); copy_m3_m4(vpd->vpimat, imat); + /* If not previously created, create vertex/weight paint mode session data */ + vertex_paint_init_session(&eval_ctx, scene, ob); + vwpaint_update_cache_invariants(C, vp, ss, op, mouse); + vertex_paint_init_session_data(ts, ob); + + if (ob->sculpt->mode.vpaint.previous_color != NULL) { + memset(ob->sculpt->mode.vpaint.previous_color, 0, sizeof(uint) * me->totloop); + } + return 1; } -static void vpaint_paint_poly(VPaint *vp, VPaintData *vpd, Mesh *me, - const unsigned int index, const float mval[2], - const float brush_size_pressure, const float brush_alpha_pressure) +static void do_vpaint_brush_calc_average_color_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) { - ViewContext *vc = &vpd->vc; - Brush *brush = BKE_paint_brush(&vp->paint); - MPoly *mpoly = &me->mpoly[index]; - MLoop *ml; - unsigned int *lcol = ((unsigned int *)me->mloopcol) + mpoly->loopstart; - unsigned int *lcolorig = ((unsigned int *)vp->vpaint_prev) + mpoly->loopstart; - bool *mlooptag = (vpd->mlooptag) ? vpd->mlooptag + mpoly->loopstart : NULL; - float alpha; - int i, j; - int totloop = mpoly->totloop; + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; - int brush_alpha_pressure_i = (int)(brush_alpha_pressure * 255.0f); + StrokeCache *cache = ss->cache; + uint *lcol = data->lcol; + char *col; + const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; - if (brush->vertexpaint_tool == PAINT_BLEND_BLUR) { - unsigned int blend[4] = {0}; - unsigned int tcol; - char *col; + struct VPaintAverageAccum *accum = (struct VPaintAverageAccum *)data->custom_data + n; + accum->len = 0; + memset(accum->value, 0, sizeof(accum->value)); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); - for (j = 0; j < totloop; j++) { - col = (char *)(lcol + j); - blend[0] += col[0]; - blend[1] += col[1]; - blend[2] += col[2]; - blend[3] += col[3]; + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + if (BKE_brush_curve_strength(data->brush, 0.0, cache->radius) > 0.0) { + /* If the vertex is selected for painting. */ + const MVert *mv = &data->me->mvert[v_index]; + if (!use_vert_sel || mv->flag & SELECT) { + accum->len += gmap->vert_to_loop[v_index].count; + /* if a vertex is within the brush region, then add it's color to the blend. */ + for (int j = 0; j < gmap->vert_to_loop[v_index].count; j++) { + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + col = (char *)(&lcol[l_index]); + /* Color is squared to compensate the sqrt color encoding. */ + accum->value[0] += col[0] * col[0]; + accum->value[1] += col[1] * col[1]; + accum->value[2] += col[2] * col[2]; + } + } + } } + } + BKE_pbvh_vertex_iter_end; +} + +static void handle_texture_brush( + SculptThreadedTaskData *data, PBVHVertexIter vd, float size_pressure, float alpha_pressure, + float *r_alpha, uint *r_color) +{ + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + + float rgba[4]; + float rgba_br[3]; + + *r_alpha = calc_vp_alpha_col_dl( + data->vp, &data->vpd->vc, data->vpd->vpimat, + &data->vpd->vertexcosnos[v_index], ss->cache->mouse, size_pressure, alpha_pressure, rgba); + rgb_uchar_to_float(rgba_br, (const uchar *)&data->vpd->paintcol); + mul_v3_v3(rgba_br, rgba); + rgb_float_to_uchar((uchar *)r_color, rgba_br); +} + +static void do_vpaint_brush_draw_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + + const Brush *brush = data->brush; + const StrokeCache *cache = ss->cache; + uint *lcol = data->lcol; + const Scene *scene = CTX_data_scene(data->C); + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - blend[0] = divide_round_i(blend[0], totloop); - blend[1] = divide_round_i(blend[1], totloop); - blend[2] = divide_round_i(blend[2], totloop); - blend[3] = divide_round_i(blend[3], totloop); - col = (char *)&tcol; - col[0] = blend[0]; - col[1] = blend[1]; - col[2] = blend[2]; - col[3] = blend[3]; + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); - vpd->paintcol = *((unsigned int *)col); + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* Note: Grids are 1:1 with corners (aka loops). + * For grid based pbvh, take the vert whose loop cooresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv = &data->me->mvert[v_index]; + + /* If the vertex is selected for painting. */ + if (!use_vert_sel || mv->flag & SELECT) { + /* Calc the dot prod. between ray norm on surf and current vert + * (ie splash prevention factor), and only paint front facing verts. */ + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + uint color_final = data->vpd->paintcol; + + /* If we're painting with a texture, sample the texture color and alpha. */ + float tex_alpha = 1.0; + if (data->vpd->is_texbrush) { + handle_texture_brush( + data, vd, brush_size_pressure, brush_alpha_pressure, + &tex_alpha, &color_final); + } + /* For each poly owning this vert, paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(data->me->mloop[l_index].v == v_index); + const MPoly *mp = &data->me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + uint color_orig = 0; /* unused when array is NULL */ + if (ss->mode.vpaint.previous_color != NULL) { + /* Get the previous loop color */ + if (ss->mode.vpaint.previous_color[l_index] == 0) { + ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; + } + color_orig = ss->mode.vpaint.previous_color[l_index]; + } + const float final_alpha = + 255 * brush_fade * brush_strength * + tex_alpha * brush_alpha_pressure * grid_alpha; + + /* Mix the new color with the original based on final_alpha. */ + lcol[l_index] = vpaint_blend( + data->vp, lcol[l_index], color_orig, color_final, + final_alpha, 255 * brush_strength); + } + } + } + } + } } + BKE_pbvh_vertex_iter_end; +} - ml = me->mloop + mpoly->loopstart; - for (i = 0; i < totloop; i++, ml++) { - float rgba[4]; - unsigned int paintcol; - alpha = calc_vp_alpha_col_dl(vp, vc, vpd->vpimat, - &vpd->vertexcosnos[ml->v], mval, - brush_size_pressure, brush_alpha_pressure, rgba); +static void do_vpaint_brush_blur_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + + Scene *scene = CTX_data_scene(data->C); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + const Brush *brush = data->brush; + const StrokeCache *cache = ss->cache; + uint *lcol = data->lcol; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - if (vpd->is_texbrush) { - float rgba_br[3]; - rgb_uchar_to_float(rgba_br, (const unsigned char *)&vpd->paintcol); - mul_v3_v3(rgba_br, rgba); - rgb_float_to_uchar((unsigned char *)&paintcol, rgba_br); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop cooresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv = &data->me->mvert[v_index]; + + /* If the vertex is selected for painting. */ + if (!use_vert_sel || mv->flag & SELECT) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + + /* Get the average poly color */ + uint color_final = 0; + int total_hit_loops = 0; + uint blend[4] = {0}; + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + int p_index = gmap->vert_to_poly[v_index].indices[j]; + const MPoly *mp = &data->me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + total_hit_loops += mp->totloop; + for (int k = 0; k < mp->totloop; k++) { + const uint l_index = mp->loopstart + k; + const char *col = (const char *)(&lcol[l_index]); + /* Color is squared to compensate the sqrt color encoding. */ + blend[0] += (uint)col[0] * (uint)col[0]; + blend[1] += (uint)col[1] * (uint)col[1]; + blend[2] += (uint)col[2] * (uint)col[2]; + blend[3] += (uint)col[3] * (uint)col[3]; + } + } + } + if (total_hit_loops != 0) { + /* Use rgb^2 color averaging. */ + char *col = (char *)(&color_final); + col[0] = round_fl_to_uchar(sqrtf(divide_round_i(blend[0], total_hit_loops))); + col[1] = round_fl_to_uchar(sqrtf(divide_round_i(blend[1], total_hit_loops))); + col[2] = round_fl_to_uchar(sqrtf(divide_round_i(blend[2], total_hit_loops))); + col[3] = round_fl_to_uchar(sqrtf(divide_round_i(blend[3], total_hit_loops))); + + /* For each poly owning this vert, paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(data->me->mloop[l_index].v == v_index); + const MPoly *mp = &data->me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + uint color_orig = 0; /* unused when array is NULL */ + if (ss->mode.vpaint.previous_color != NULL) { + /* Get the previous loop color */ + if (ss->mode.vpaint.previous_color[l_index] == 0) { + ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; + } + color_orig = ss->mode.vpaint.previous_color[l_index]; + } + const float final_alpha = + 255 * brush_fade * brush_strength * + brush_alpha_pressure * grid_alpha; + /* Mix the new color with the original + * based on the brush strength and the curve. */ + lcol[l_index] = vpaint_blend( + data->vp, lcol[l_index], color_orig, *((uint *)col), + final_alpha, 255 * brush_strength); + } + } + } + } + } } - else - paintcol = vpd->paintcol; + } + BKE_pbvh_vertex_iter_end; +} + +static void do_vpaint_brush_smear_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + + Scene *scene = CTX_data_scene(data->C); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + const Brush *brush = data->brush; + const StrokeCache *cache = ss->cache; + uint *lcol = data->lcol; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + float brush_dir[3]; + const bool use_normal = vwpaint_use_normal(data->vp); + const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + sub_v3_v3v3(brush_dir, cache->location, cache->last_location); + project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal); + + if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) { + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + const float *sculpt_normal_frontface = + sculpt_brush_frontface_normal_from_falloff_shape(ss, data->brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop cooresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; + const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv_curr = &data->me->mvert[v_index]; + + /* if the vertex is selected for painting. */ + if (!use_vert_sel || mv_curr->flag & SELECT) { + /* Calc the dot prod. between ray norm on surf and current vert + * (ie splash prevention factor), and only paint front facing verts. */ + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_vf3vs3(sculpt_normal_frontface, vd.no) : 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || + (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) + { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + + bool do_color = false; + /* Minimum dot product between brush direction and current + * to neighbor direction is 0.0, meaning orthogonal. */ + float stroke_dot_max = 0.0f; + + /* Get the color of the loop in the opposite direction of the brush movement */ + uint color_final = 0; + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(data->me->mloop[l_index].v == v_index); + UNUSED_VARS_NDEBUG(l_index); + const MPoly *mp = &data->me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + const MLoop *ml_other = &data->me->mloop[mp->loopstart]; + for (int k = 0; k < mp->totloop; k++, ml_other++) { + const uint v_other_index = ml_other->v; + if (v_other_index != v_index) { + const MVert *mv_other = &data->me->mvert[v_other_index]; + + /* Get the direction from the selected vert to the neighbor. */ + float other_dir[3]; + sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co); + project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal); + + normalize_v3(other_dir); + + const float stroke_dot = dot_v3v3(other_dir, brush_dir); + + if (stroke_dot > stroke_dot_max) { + stroke_dot_max = stroke_dot; + color_final = data->vpd->smear.color_prev[mp->loopstart + k]; + do_color = true; + } + } + } + } + } + + if (do_color) { + const float final_alpha = + 255 * brush_fade * brush_strength * + brush_alpha_pressure * grid_alpha; + + /* For each poly owning this vert, paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(data->me->mloop[l_index].v == v_index); + const MPoly *mp = &data->me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + /* Get the previous loop color */ + uint color_orig = 0; /* unused when array is NULL */ + if (ss->mode.vpaint.previous_color != NULL) { + /* Get the previous loop color */ + if (ss->mode.vpaint.previous_color[l_index] == 0) { + ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; + } + color_orig = ss->mode.vpaint.previous_color[l_index]; + } + /* Mix the new color with the original + * based on the brush strength and the curve. */ + lcol[l_index] = vpaint_blend( + data->vp, lcol[l_index], color_orig, color_final, + final_alpha, 255 * brush_strength); + + data->vpd->smear.color_curr[l_index] = lcol[l_index]; + } + } + } + } + } + } + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calculate_average_color(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) +{ + struct VPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__); + data->custom_data = accum; + + BLI_task_parallel_range_ex( + 0, totnode, data, NULL, 0, do_vpaint_brush_calc_average_color_cb_ex, + true, false); + + uint accum_len = 0; + uint accum_value[3] = {0}; + uchar blend[4] = {0}; + for (int i = 0; i < totnode; i++) { + accum_len += accum[i].len; + accum_value[0] += accum[i].value[0]; + accum_value[1] += accum[i].value[1]; + accum_value[2] += accum[i].value[2]; + } + if (accum_len != 0) { + blend[0] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[0], accum_len))); + blend[1] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[1], accum_len))); + blend[2] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[2], accum_len))); + blend[3] = 255; + data->vpd->paintcol = *((uint *)blend); + } - if (alpha > 0.0f) { - const int alpha_i = (int)(alpha * 255.0f); - lcol[i] = vpaint_blend(vp, lcol[i], lcolorig[i], paintcol, alpha_i, brush_alpha_pressure_i); + MEM_SAFE_FREE(data->custom_data); /* 'accum' */ +} + +static void vpaint_paint_leaves( + bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, + Object *ob, Mesh *me, PBVHNode **nodes, int totnode) +{ + const Brush *brush = ob->sculpt->cache->brush; + + SculptThreadedTaskData data = { + .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .vp = vp, .vpd = vpd, + .lcol = (uint *)me->mloopcol, .me = me, .C = C, + }; + switch (brush->vertexpaint_tool) { + case PAINT_BLEND_AVERAGE: + calculate_average_color(&data, nodes, totnode); + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_vpaint_brush_draw_task_cb_ex, true, false); + break; + case PAINT_BLEND_BLUR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_vpaint_brush_blur_task_cb_ex, true, false); + break; + case PAINT_BLEND_SMEAR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_vpaint_brush_smear_task_cb_ex, true, false); + break; + default: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_vpaint_brush_draw_task_cb_ex, true, false); + break; + } +} + +static void vpaint_do_paint( + bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, + Object *ob, Mesh *me, Brush *brush, const char symm, const int axis, const int i, const float angle) +{ + SculptSession *ss = ob->sculpt; + ss->cache->radial_symmetry_pass = i; + sculpt_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); + + int totnode; + PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, vp, sd, brush, &totnode); + + /* Paint those leaves. */ + vpaint_paint_leaves(C, sd, vp, vpd, ob, me, nodes, totnode); + + if (nodes) { + MEM_freeN(nodes); + } +} - if (mlooptag) mlooptag[i] = 1; +static void vpaint_do_radial_symmetry( + bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob, Mesh *me, + Brush *brush, const char symm, const int axis) +{ + for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) { + const float angle = (2.0 * M_PI) * i / vp->radial_symm[axis - 'X']; + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, symm, axis, i, angle); + } +} + +/* near duplicate of: sculpt.c's, 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */ +static void vpaint_do_symmetrical_brush_actions( + bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob) +{ + Brush *brush = BKE_paint_brush(&vp->paint); + Mesh *me = ob->data; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const char symm = vp->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + int i = 0; + + /* initial stroke */ + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + + cache->symmetry = symm; + + /* symm is a bit combination of XYZ - 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ + for (i = 1; i <= symm; i++) { + if (symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))) { + cache->mirror_symmetry_pass = i; + cache->radial_symmetry_pass = 0; + sculpt_cache_calc_brushdata_symm(cache, i, 0, 0); + + if (i & (1 << 0)) { + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); + } + if (i & (1 << 1)) { + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + } + if (i & (1 << 2)) { + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0); + vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + } } } + + copy_v3_v3(cache->true_last_location, cache->true_location); + cache->is_last_valid = true; } static void vpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) @@ -2803,71 +2998,36 @@ static void vpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P ToolSettings *ts = CTX_data_tool_settings(C); struct VPaintData *vpd = paint_stroke_mode_data(stroke); VPaint *vp = ts->vpaint; - Brush *brush = BKE_paint_brush(&vp->paint); ViewContext *vc = &vpd->vc; Object *ob = vc->obact; - Mesh *me = ob->data; - float mat[4][4]; - int *indexar = vpd->indexar; - int totindex, index; - float mval[2]; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - const float pressure = RNA_float_get(itemptr, "pressure"); - const float brush_size_pressure = - BKE_brush_size_get(scene, brush) * (BKE_brush_use_size_pressure(scene, brush) ? pressure : 1.0f); - const float brush_alpha_pressure = - BKE_brush_alpha_get(scene, brush) * (BKE_brush_use_alpha_pressure(scene, brush) ? pressure : 1.0f); + vwpaint_update_cache_variants(C, vp, ob, itemptr); - RNA_float_get_array(itemptr, "mouse", mval); + float mat[4][4]; + float mval[2]; - view3d_operator_needs_opengl(C); ED_view3d_init_mats_rv3d(ob, vc->rv3d); /* load projection matrix */ mul_m4_m4m4(mat, vc->rv3d->persmat, ob->obmat); - /* which faces are involved */ - totindex = sample_backbuf_area(vc, indexar, me->totpoly, mval[0], mval[1], brush_size_pressure); - - if ((me->editflag & ME_EDIT_PAINT_FACE_SEL) && me->mpoly) { - for (index = 0; index < totindex; index++) { - if (indexar[index] && indexar[index] <= me->totpoly) { - const MPoly *mpoly = &me->mpoly[indexar[index] - 1]; - - if ((mpoly->flag & ME_FACE_SEL) == 0) - indexar[index] = 0; - } - } - } - swap_m4m4(vc->rv3d->persmat, mat); - /* incase we have modifiers */ - ED_vpaint_proj_handle_update(vpd->vp_handle, vc->ar, mval); - - /* clear modified tag for blur tool */ - if (vpd->mlooptag) - memset(vpd->mlooptag, 0, sizeof(bool) * me->totloop); + vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob); - for (index = 0; index < totindex; index++) { - if (indexar[index] && indexar[index] <= me->totpoly) { - vpaint_paint_poly(vp, vpd, me, indexar[index] - 1, mval, brush_size_pressure, brush_alpha_pressure); - } - } - swap_m4m4(vc->rv3d->persmat, mat); - /* was disabled because it is slow, but necessary for blur */ - if (brush->vertexpaint_tool == PAINT_BLEND_BLUR) { - do_shared_vertexcol(me, vpd->mlooptag); + BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + + if (vp->paint.brush->vertexpaint_tool == PAINT_BLEND_SMEAR) { + SWAP(uint *, vpd->smear.color_curr, vpd->smear.color_prev); } /* calculate pivot for rotation around seletion if needed */ /* also needed for "View Selected" on last stroke */ paint_last_stroke_update(scene, vc->ar, mval); - BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_PAINT); - ED_region_tag_redraw(vc->ar); if (vpd->use_fast_update == false) { @@ -2883,35 +3043,34 @@ static void vpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) { - ToolSettings *ts = CTX_data_tool_settings(C); struct VPaintData *vpd = paint_stroke_mode_data(stroke); ViewContext *vc = &vpd->vc; Object *ob = vc->obact; - Mesh *me = ob->data; - - ED_vpaint_proj_handle_free(vpd->vp_handle); - MEM_freeN(vpd->indexar); - - /* frees prev buffer */ - copy_vpaint_prev(ts->vpaint, NULL, 0); if (vpd->mlooptag) MEM_freeN(vpd->mlooptag); + if (vpd->smear.color_prev) + MEM_freeN(vpd->smear.color_prev); + if (vpd->smear.color_curr) + MEM_freeN(vpd->smear.color_curr); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - DEG_id_tag_update(&me->id, 0); MEM_freeN(vpd); + + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; } static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) { int retval; - op->customdata = paint_stroke_new(C, op, NULL, vpaint_stroke_test_start, - vpaint_stroke_update_step, NULL, - vpaint_stroke_done, event->type); - + op->customdata = paint_stroke_new( + C, op, sculpt_stroke_get_location, vpaint_stroke_test_start, + vpaint_stroke_update_step, NULL, + vpaint_stroke_done, event->type); + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { paint_stroke_data_free(op); return OPERATOR_FINISHED; @@ -2922,15 +3081,16 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) OPERATOR_RETVAL_CHECK(retval); BLI_assert(retval == OPERATOR_RUNNING_MODAL); - + return OPERATOR_RUNNING_MODAL; } static int vpaint_exec(bContext *C, wmOperator *op) { - op->customdata = paint_stroke_new(C, op, NULL, vpaint_stroke_test_start, - vpaint_stroke_update_step, NULL, - vpaint_stroke_done, 0); + op->customdata = paint_stroke_new( + C, op, sculpt_stroke_get_location, vpaint_stroke_test_start, + vpaint_stroke_update_step, NULL, + vpaint_stroke_done, 0); /* frees op->customdata */ paint_stroke_exec(C, op); @@ -2940,6 +3100,12 @@ static int vpaint_exec(bContext *C, wmOperator *op) static void vpaint_cancel(bContext *C, wmOperator *op) { + Object *ob = CTX_data_active_object(C); + if (ob->sculpt->cache) { + sculpt_cache_free(ob->sculpt->cache); + ob->sculpt->cache = NULL; + } + paint_stroke_cancel(C, op); } @@ -2949,382 +3115,17 @@ void PAINT_OT_vertex_paint(wmOperatorType *ot) ot->name = "Vertex Paint"; ot->idname = "PAINT_OT_vertex_paint"; ot->description = "Paint a stroke in the active vertex color layer"; - + /* api callbacks */ ot->invoke = vpaint_invoke; ot->modal = paint_stroke_modal; ot->exec = vpaint_exec; ot->poll = vertex_paint_poll; ot->cancel = vpaint_cancel; - + /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; paint_stroke_operator_properties(ot); } -/* ********************** weight from bones operator ******************* */ - -static int weight_from_bones_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - - return (ob && (ob->mode & OB_MODE_WEIGHT_PAINT) && modifiers_isDeformedByArmature(ob)); -} - -static int weight_from_bones_exec(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - Object *armob = modifiers_isDeformedByArmature(ob); - Mesh *me = ob->data; - int type = RNA_enum_get(op->ptr, "type"); - - create_vgroups_from_armature(op->reports, scene, ob, armob, type, (me->editflag & ME_EDIT_MIRROR_X)); - - DEG_id_tag_update(&me->id, 0); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); - - return OPERATOR_FINISHED; -} - -void PAINT_OT_weight_from_bones(wmOperatorType *ot) -{ - static EnumPropertyItem type_items[] = { - {ARM_GROUPS_AUTO, "AUTOMATIC", 0, "Automatic", "Automatic weights from bones"}, - {ARM_GROUPS_ENVELOPE, "ENVELOPES", 0, "From Envelopes", "Weights from envelopes with user defined radius"}, - {0, NULL, 0, NULL, NULL}}; - - /* identifiers */ - ot->name = "Weight from Bones"; - ot->idname = "PAINT_OT_weight_from_bones"; - ot->description = "Set the weights of the groups matching the attached armature's selected bones, " - "using the distance between the vertices and the bones"; - - /* api callbacks */ - ot->exec = weight_from_bones_exec; - ot->invoke = WM_menu_invoke; - ot->poll = weight_from_bones_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - ot->prop = RNA_def_enum(ot->srna, "type", type_items, 0, "Type", "Method to use for assigning weights"); -} - -/* *** VGroups Gradient *** */ -typedef struct DMGradient_vertStore { - float sco[2]; - float weight_orig; - enum { - VGRAD_STORE_NOP = 0, - VGRAD_STORE_DW_EXIST = (1 << 0) - } flag; -} DMGradient_vertStore; - -typedef struct DMGradient_userData { - struct ARegion *ar; - Scene *scene; - Mesh *me; - Brush *brush; - const float *sco_start; /* [2] */ - const float *sco_end; /* [2] */ - float sco_line_div; /* store (1.0f / len_v2v2(sco_start, sco_end)) */ - int def_nr; - bool is_init; - DMGradient_vertStore *vert_cache; - /* only for init */ - BLI_bitmap *vert_visit; - - /* options */ - short use_select; - short type; - float weightpaint; -} DMGradient_userData; - -static void gradientVert_update(DMGradient_userData *grad_data, int index) -{ - Mesh *me = grad_data->me; - DMGradient_vertStore *vs = &grad_data->vert_cache[index]; - float alpha; - - if (grad_data->type == WPAINT_GRADIENT_TYPE_LINEAR) { - alpha = line_point_factor_v2(vs->sco, grad_data->sco_start, grad_data->sco_end); - } - else { - BLI_assert(grad_data->type == WPAINT_GRADIENT_TYPE_RADIAL); - alpha = len_v2v2(grad_data->sco_start, vs->sco) * grad_data->sco_line_div; - } - /* no need to clamp 'alpha' yet */ - - /* adjust weight */ - alpha = BKE_brush_curve_strength_clamped(grad_data->brush, alpha, 1.0f); - - if (alpha != 0.0f) { - MDeformVert *dv = &me->dvert[index]; - MDeformWeight *dw = defvert_verify_index(dv, grad_data->def_nr); - // dw->weight = alpha; // testing - int tool = grad_data->brush->vertexpaint_tool; - float testw; - - /* init if we just added */ - testw = wpaint_blend_tool(tool, vs->weight_orig, grad_data->weightpaint, alpha * grad_data->brush->alpha); - CLAMP(testw, 0.0f, 1.0f); - dw->weight = testw; - } - else { - MDeformVert *dv = &me->dvert[index]; - if (vs->flag & VGRAD_STORE_DW_EXIST) { - /* normally we NULL check, but in this case we know it exists */ - MDeformWeight *dw = defvert_find_index(dv, grad_data->def_nr); - dw->weight = vs->weight_orig; - } - else { - /* wasn't originally existing, remove */ - MDeformWeight *dw = defvert_find_index(dv, grad_data->def_nr); - if (dw) { - defvert_remove_group(dv, dw); - } - } - } -} - -static void gradientVertUpdate__mapFunc( - void *userData, int index, const float UNUSED(co[3]), - const float UNUSED(no_f[3]), const short UNUSED(no_s[3])) -{ - DMGradient_userData *grad_data = userData; - Mesh *me = grad_data->me; - if ((grad_data->use_select == false) || (me->mvert[index].flag & SELECT)) { - DMGradient_vertStore *vs = &grad_data->vert_cache[index]; - if (vs->sco[0] != FLT_MAX) { - gradientVert_update(grad_data, index); - } - } -} - -static void gradientVertInit__mapFunc( - void *userData, int index, const float co[3], - const float UNUSED(no_f[3]), const short UNUSED(no_s[3])) -{ - DMGradient_userData *grad_data = userData; - Mesh *me = grad_data->me; - - if ((grad_data->use_select == false) || (me->mvert[index].flag & SELECT)) { - /* run first pass only, - * the screen coords of the verts need to be cached because - * updating the mesh may move them about (entering feedback loop) */ - - if (BLI_BITMAP_TEST(grad_data->vert_visit, index) == 0) { - DMGradient_vertStore *vs = &grad_data->vert_cache[index]; - if (ED_view3d_project_float_object(grad_data->ar, - co, vs->sco, - V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) == V3D_PROJ_RET_OK) - { - /* ok */ - MDeformVert *dv = &me->dvert[index]; - MDeformWeight *dw; - dw = defvert_find_index(dv, grad_data->def_nr); - if (dw) { - vs->weight_orig = dw->weight; - vs->flag = VGRAD_STORE_DW_EXIST; - } - else { - vs->weight_orig = 0.0f; - vs->flag = VGRAD_STORE_NOP; - } - - BLI_BITMAP_ENABLE(grad_data->vert_visit, index); - - gradientVert_update(grad_data, index); - } - else { - /* no go */ - copy_v2_fl(vs->sco, FLT_MAX); - } - } - } -} - -static int paint_weight_gradient_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - int ret = WM_gesture_straightline_modal(C, op, event); - - if (ret & OPERATOR_RUNNING_MODAL) { - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { /* XXX, hardcoded */ - /* generally crap! redo! */ - WM_gesture_straightline_cancel(C, op); - ret &= ~OPERATOR_RUNNING_MODAL; - ret |= OPERATOR_FINISHED; - } - } - - if (ret & OPERATOR_CANCELLED) { - ToolSettings *ts = CTX_data_tool_settings(C); - VPaint *wp = ts->wpaint; - Object *ob = CTX_data_active_object(C); - Mesh *me = ob->data; - if (wp->wpaint_prev) { - BKE_defvert_array_free_elems(me->dvert, me->totvert); - BKE_defvert_array_copy(me->dvert, wp->wpaint_prev, me->totvert); - free_wpaint_prev(wp); - } - - DEG_id_tag_update(&ob->id, OB_RECALC_DATA); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - } - else if (ret & OPERATOR_FINISHED) { - ToolSettings *ts = CTX_data_tool_settings(C); - VPaint *wp = ts->wpaint; - free_wpaint_prev(wp); - } - - return ret; -} - -static int paint_weight_gradient_exec(bContext *C, wmOperator *op) -{ - wmGesture *gesture = op->customdata; - DMGradient_vertStore *vert_cache; - struct ARegion *ar = CTX_wm_region(C); - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - Mesh *me = ob->data; - int x_start = RNA_int_get(op->ptr, "xstart"); - int y_start = RNA_int_get(op->ptr, "ystart"); - int x_end = RNA_int_get(op->ptr, "xend"); - int y_end = RNA_int_get(op->ptr, "yend"); - float sco_start[2] = {x_start, y_start}; - float sco_end[2] = {x_end, y_end}; - const bool is_interactive = (gesture != NULL); - DerivedMesh *dm = mesh_get_derived_final(scene, ob, scene->customdata_mask); - - DMGradient_userData data = {NULL}; - - if (is_interactive) { - if (gesture->userdata == NULL) { - VPaint *wp = scene->toolsettings->wpaint; - - gesture->userdata = MEM_mallocN(sizeof(DMGradient_vertStore) * me->totvert, __func__); - data.is_init = true; - - copy_wpaint_prev(wp, me->dvert, me->totvert); - - /* on init only, convert face -> vert sel */ - if (me->editflag & ME_EDIT_PAINT_FACE_SEL) { - BKE_mesh_flush_select_from_polys(me); - } - } - - vert_cache = gesture->userdata; - } - else { - if (wpaint_ensure_data(C, op, 0, NULL) == false) { - return OPERATOR_CANCELLED; - } - - data.is_init = true; - vert_cache = MEM_mallocN(sizeof(DMGradient_vertStore) * me->totvert, __func__); - } - - data.ar = ar; - data.scene = scene; - data.me = ob->data; - data.sco_start = sco_start; - data.sco_end = sco_end; - data.sco_line_div = 1.0f / len_v2v2(sco_start, sco_end); - data.def_nr = ob->actdef - 1; - data.use_select = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)); - data.vert_cache = vert_cache; - data.vert_visit = NULL; - data.type = RNA_enum_get(op->ptr, "type"); - - { - ToolSettings *ts = CTX_data_tool_settings(C); - VPaint *wp = ts->wpaint; - struct Brush *brush = BKE_paint_brush(&wp->paint); - - curvemapping_initialize(brush->curve); - - data.brush = brush; - data.weightpaint = BKE_brush_weight_get(scene, brush); - } - - ED_view3d_init_mats_rv3d(ob, ar->regiondata); - - if (data.is_init) { - data.vert_visit = BLI_BITMAP_NEW(me->totvert, __func__); - - dm->foreachMappedVert(dm, gradientVertInit__mapFunc, &data, DM_FOREACH_NOP); - - MEM_freeN(data.vert_visit); - data.vert_visit = NULL; - } - else { - dm->foreachMappedVert(dm, gradientVertUpdate__mapFunc, &data, DM_FOREACH_NOP); - } - - DEG_id_tag_update(&ob->id, OB_RECALC_DATA); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - if (is_interactive == false) { - MEM_freeN(vert_cache); - } - - return OPERATOR_FINISHED; -} - -static int paint_weight_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - int ret; - - if (wpaint_ensure_data(C, op, 0, NULL) == false) { - return OPERATOR_CANCELLED; - } - - ret = WM_gesture_straightline_invoke(C, op, event); - if (ret & OPERATOR_RUNNING_MODAL) { - struct ARegion *ar = CTX_wm_region(C); - if (ar->regiontype == RGN_TYPE_WINDOW) { - /* TODO, hardcoded, extend WM_gesture_straightline_ */ - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - wmGesture *gesture = op->customdata; - gesture->mode = 1; - } - } - } - return ret; -} - -void PAINT_OT_weight_gradient(wmOperatorType *ot) -{ - /* defined in DNA_space_types.h */ - static EnumPropertyItem gradient_types[] = { - {WPAINT_GRADIENT_TYPE_LINEAR, "LINEAR", 0, "Linear", ""}, - {WPAINT_GRADIENT_TYPE_RADIAL, "RADIAL", 0, "Radial", ""}, - {0, NULL, 0, NULL, NULL} - }; - - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Weight Gradient"; - ot->idname = "PAINT_OT_weight_gradient"; - ot->description = "Draw a line to apply a weight gradient to selected vertices"; - - /* api callbacks */ - ot->invoke = paint_weight_gradient_invoke; - ot->modal = paint_weight_gradient_modal; - ot->exec = paint_weight_gradient_exec; - ot->poll = weight_paint_poll; - ot->cancel = WM_gesture_straightline_cancel; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - prop = RNA_def_enum(ot->srna, "type", gradient_types, 0, "Type", ""); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - - WM_operator_properties_gesture_straightline(ot, CURSOR_EDIT); -} diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c new file mode 100644 index 00000000000..d7668a48139 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c @@ -0,0 +1,575 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/sculpt_paint/paint_vertex_color_ops.c + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_math_base.h" +#include "BLI_math_color.h" + +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_deform.h" + +#include "DEG_depsgraph.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_mesh.h" + +#include "paint_intern.h" /* own include */ + + +static int vertex_weight_paint_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + Mesh *me = BKE_mesh_from_object(ob); + return (ob && (ob->mode == OB_MODE_VERTEX_PAINT || ob->mode == OB_MODE_WEIGHT_PAINT)) && + (me && me->totpoly && me->dvert); +} + +/* -------------------------------------------------------------------- */ +/** \name Set Vertex Colors Operator + * \{ */ + +static bool vertex_color_set(Object *ob, uint paintcol) +{ + Mesh *me; + const MPoly *mp; + int i, j; + + if (((me = BKE_mesh_from_object(ob)) == NULL) || + (ED_mesh_color_ensure(me, NULL) == false)) + { + return false; + } + + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + mp = me->mpoly; + for (i = 0; i < me->totpoly; i++, mp++) { + MLoopCol *lcol = me->mloopcol + mp->loopstart; + + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) + continue; + + j = 0; + do { + uint vidx = me->mloop[mp->loopstart + j].v; + if (!(use_vert_sel && !(me->mvert[vidx].flag & SELECT))) { + *(int *)lcol = paintcol; + } + lcol++; + j++; + } while (j < mp->totloop); + + } + + /* remove stale me->mcol, will be added later */ + BKE_mesh_tessface_clear(me); + + DEG_id_tag_update(&me->id, 0); + + return true; +} + +static int vertex_color_set_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Scene *scene = CTX_data_scene(C); + Object *obact = CTX_data_active_object(C); + unsigned int paintcol = vpaint_get_current_col(scene, scene->toolsettings->vpaint); + + if (vertex_color_set(obact, paintcol)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Vertex Colors"; + ot->idname = "PAINT_OT_vertex_color_set"; + ot->description = "Fill the active vertex color layer with the current paint color"; + + /* api callbacks */ + ot->exec = vertex_color_set_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Color from Weight Operator + * \{ */ + +static bool vertex_paint_from_weight(Object *ob) +{ + Mesh *me; + const MPoly *mp; + int vgroup_active; + + if (((me = BKE_mesh_from_object(ob)) == NULL || + (ED_mesh_color_ensure(me, NULL)) == false)) + { + return false; + } + + /* TODO: respect selection. */ + mp = me->mpoly; + vgroup_active = ob->actdef - 1; + for (int i = 0; i < me->totpoly; i++, mp++) { + MLoopCol *lcol = &me->mloopcol[mp->loopstart]; + uint j = 0; + do { + uint vidx = me->mloop[mp->loopstart + j].v; + const float weight = defvert_find_weight(&me->dvert[vidx], vgroup_active); + const uchar grayscale = weight * 255; + lcol->r = grayscale; + lcol->b = grayscale; + lcol->g = grayscale; + lcol++; + j++; + } while (j < mp->totloop); + } + + DEG_id_tag_update(&ob->id, OB_RECALC_DATA); + + return true; +} + +static int vertex_paint_from_weight_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *obact = CTX_data_active_object(C); + if (vertex_paint_from_weight(obact)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_from_weight(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Color from Weight"; + ot->idname = "PAINT_OT_vertex_color_from_weight"; + ot->description = "Convert active weight into gray scale vertex colors"; + + /* api callback */ + ot->exec = vertex_paint_from_weight_exec; + ot->poll = vertex_weight_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* TODO: invert, alpha */ +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Smooth Vertex Colors Operator + * \{ */ + +static void vertex_color_smooth_looptag(Mesh *me, bool *mlooptag) +{ + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const MPoly *mp; + int (*scol)[4]; + int i, j; + bool has_shared = false; + + /* if no mloopcol: do not do */ + /* if mtexpoly: only the involved faces, otherwise all */ + + if (me->mloopcol == NULL || me->totvert == 0 || me->totpoly == 0) return; + + scol = MEM_callocN(sizeof(int) * me->totvert * 5, "scol"); + + for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) { + if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) { + const MLoop *ml = me->mloop + mp->loopstart; + MLoopCol *lcol = me->mloopcol + mp->loopstart; + for (j = 0; j < mp->totloop; j++, ml++, lcol++) { + scol[ml->v][0] += lcol->r; + scol[ml->v][1] += lcol->g; + scol[ml->v][2] += lcol->b; + scol[ml->v][3] += 1; + has_shared = 1; + } + } + } + + if (has_shared) { + for (i = 0; i < me->totvert; i++) { + if (scol[i][3] != 0) { + scol[i][0] = divide_round_i(scol[i][0], scol[i][3]); + scol[i][1] = divide_round_i(scol[i][1], scol[i][3]); + scol[i][2] = divide_round_i(scol[i][2], scol[i][3]); + } + } + + for (i = 0, mp = me->mpoly; i < me->totpoly; i++, mp++) { + if ((use_face_sel == false) || (mp->flag & ME_FACE_SEL)) { + const MLoop *ml = me->mloop + mp->loopstart; + MLoopCol *lcol = me->mloopcol + mp->loopstart; + for (j = 0; j < mp->totloop; j++, ml++, lcol++) { + if (mlooptag[mp->loopstart + j]) { + lcol->r = scol[ml->v][0]; + lcol->g = scol[ml->v][1]; + lcol->b = scol[ml->v][2]; + } + } + } + } + } + + MEM_freeN(scol); +} + +static bool vertex_color_smooth(Object *ob) +{ + Mesh *me; + const MPoly *mp; + + int i, j; + + bool *mlooptag; + + if (((me = BKE_mesh_from_object(ob)) == NULL) || + (ED_mesh_color_ensure(me, NULL) == false)) + { + return false; + } + + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + mlooptag = MEM_callocN(sizeof(bool) * me->totloop, "VPaintData mlooptag"); + + /* simply tag loops of selected faces */ + mp = me->mpoly; + for (i = 0; i < me->totpoly; i++, mp++) { + const MLoop *ml = me->mloop + mp->loopstart; + int ml_index = mp->loopstart; + + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) + continue; + + for (j = 0; j < mp->totloop; j++, ml_index++, ml++) { + mlooptag[ml_index] = true; + } + } + + /* remove stale me->mcol, will be added later */ + BKE_mesh_tessface_clear(me); + + vertex_color_smooth_looptag(me, mlooptag); + + MEM_freeN(mlooptag); + + DEG_id_tag_update(&me->id, 0); + + return true; +} + + +static int vertex_color_smooth_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *obact = CTX_data_active_object(C); + if (vertex_color_smooth(obact)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_smooth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Smooth Vertex Colors"; + ot->idname = "PAINT_OT_vertex_color_smooth"; + ot->description = "Smooth colors across vertices"; + + /* api callbacks */ + ot->exec = vertex_color_smooth_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Color Transformation Operators + * \{ */ + +struct VPaintTx_BrightContrastData { + /* pre-calculated */ + float gain; + float offset; +}; + +static void vpaint_tx_brightness_contrast(const float col[3], const void *user_data, float r_col[3]) +{ + const struct VPaintTx_BrightContrastData *data = user_data; + + for (int i = 0; i < 3; i++) { + r_col[i] = data->gain * col[i] + data->offset; + } +} + +static int vertex_color_brightness_contrast_exec(bContext *C, wmOperator *op) +{ + Object *obact = CTX_data_active_object(C); + + float gain, offset; + { + float brightness = RNA_float_get(op->ptr, "brightness"); + float contrast = RNA_float_get(op->ptr, "contrast"); + brightness /= 100.0f; + float delta = contrast / 200.0f; + gain = 1.0f - delta * 2.0f; + /* + * The algorithm is by Werner D. Streidt + * (http://visca.com/ffactory/archives/5-99/msg00021.html) + * Extracted of OpenCV demhist.c + */ + if (contrast > 0) { + gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON); + offset = gain * (brightness - delta); + } + else { + delta *= -1; + offset = gain * (brightness + delta); + } + } + + const struct VPaintTx_BrightContrastData user_data = { + .gain = gain, + .offset = offset, + }; + + if (ED_vpaint_color_transform(obact, vpaint_tx_brightness_contrast, &user_data)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_brightness_contrast(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Vertex Paint Bright/Contrast"; + ot->idname = "PAINT_OT_vertex_color_brightness_contrast"; + ot->description = "Adjust vertex color brightness/contrast"; + + /* api callbacks */ + ot->exec = vertex_color_brightness_contrast_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + const float min = -100, max = +100; + prop = RNA_def_float(ot->srna, "brightness", 0.0f, min, max, "Brightness", "", min, max); + prop = RNA_def_float(ot->srna, "contrast", 0.0f, min, max, "Contrast", "", min, max); + RNA_def_property_ui_range(prop, min, max, 1, 1); +} + +struct VPaintTx_HueSatData { + float hue; + float sat; + float val; +}; + +static void vpaint_tx_hsv(const float col[3], const void *user_data, float r_col[3]) +{ + const struct VPaintTx_HueSatData *data = user_data; + float hsv[3]; + rgb_to_hsv_v(col, hsv); + + hsv[0] += (data->hue - 0.5f); + if (hsv[0] > 1.0f) { + hsv[0] -= 1.0f; + } + else if (hsv[0] < 0.0f) { + hsv[0] += 1.0f; + } + hsv[1] *= data->sat; + hsv[2] *= data->val; + + hsv_to_rgb_v(hsv, r_col); +} + +static int vertex_color_hsv_exec(bContext *C, wmOperator *op) +{ + Object *obact = CTX_data_active_object(C); + + const struct VPaintTx_HueSatData user_data = { + .hue = RNA_float_get(op->ptr, "h"), + .sat = RNA_float_get(op->ptr, "s"), + .val = RNA_float_get(op->ptr, "v"), + }; + + if (ED_vpaint_color_transform(obact, vpaint_tx_hsv, &user_data)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_hsv(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Paint Hue Saturation Value"; + ot->idname = "PAINT_OT_vertex_color_hsv"; + ot->description = "Adjust vertex color HSV values"; + + /* api callbacks */ + ot->exec = vertex_color_hsv_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + RNA_def_float(ot->srna, "h", 0.5f, 0.0f, 1.0f, "Hue", "", 0.0f, 1.0f); + RNA_def_float(ot->srna, "s", 1.0f, 0.0f, 2.0f, "Saturation", "", 0.0f, 2.0f); + RNA_def_float(ot->srna, "v", 1.0f, 0.0f, 2.0f, "Value", "", 0.0f, 2.0f); +} + +static void vpaint_tx_invert(const float col[3], const void *UNUSED(user_data), float r_col[3]) +{ + for (int i = 0; i < 3; i++) { + r_col[i] = 1.0f - col[i]; + } +} + +static int vertex_color_invert_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *obact = CTX_data_active_object(C); + + if (ED_vpaint_color_transform(obact, vpaint_tx_invert, NULL)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_invert(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Paint Invert"; + ot->idname = "PAINT_OT_vertex_color_invert"; + ot->description = "Invert RGB values"; + + /* api callbacks */ + ot->exec = vertex_color_invert_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + + +struct VPaintTx_LevelsData { + float gain; + float offset; +}; + +static void vpaint_tx_levels(const float col[3], const void *user_data, float r_col[3]) +{ + const struct VPaintTx_LevelsData *data = user_data; + for (int i = 0; i < 3; i++) { + r_col[i] = data->gain * (col[i] + data->offset); + } +} + +static int vertex_color_levels_exec(bContext *C, wmOperator *op) +{ + Object *obact = CTX_data_active_object(C); + + const struct VPaintTx_LevelsData user_data = { + .gain = RNA_float_get(op->ptr, "gain"), + .offset = RNA_float_get(op->ptr, "offset"), + }; + + if (ED_vpaint_color_transform(obact, vpaint_tx_levels, &user_data)) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_vertex_color_levels(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Paint Levels"; + ot->idname = "PAINT_OT_vertex_color_levels"; + ot->description = "Adjust levels of vertex colors"; + + /* api callbacks */ + ot->exec = vertex_color_levels_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + RNA_def_float(ot->srna, "offset", 0.0f, -1.0f, 1.0f, "Offset", "Value to add to colors", -1.0f, 1.0f); + RNA_def_float(ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply colors by", 0.0f, 10.0f); +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c b/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c new file mode 100644 index 00000000000..a8045232e05 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c @@ -0,0 +1,649 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/sculpt_paint/paint_vertex_color_utils.c + * \ingroup edsculpt + * + * Intended for use by `paint_vertex.c` & `paint_vertex_color_ops.c`. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_math_base.h" +#include "BLI_math_color.h" + +#include "IMB_colormanagement.h" + +#include "BKE_context.h" +#include "BKE_mesh.h" + +#include "DEG_depsgraph.h" + +#include "ED_mesh.h" + +#include "paint_intern.h" /* own include */ + +#define EPS_SATURATION 0.0005f + +/** + * Apply callback to each vertex of the active vertex color layer. + */ +bool ED_vpaint_color_transform( + struct Object *ob, + VPaintTransform_Callback vpaint_tx_fn, + const void *user_data) +{ + Mesh *me; + const MPoly *mp; + + if (((me = BKE_mesh_from_object(ob)) == NULL) || + (ED_mesh_color_ensure(me, NULL) == false)) + { + return false; + } + + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + mp = me->mpoly; + + for (int i = 0; i < me->totpoly; i++, mp++) { + MLoopCol *lcol = &me->mloopcol[mp->loopstart]; + + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { + continue; + } + + for (int j = 0; j < mp->totloop; j++, lcol++) { + float col[3]; + rgb_uchar_to_float(col, &lcol->r); + + vpaint_tx_fn(col, user_data, col); + + rgb_float_to_uchar(&lcol->r, col); + } + } + + /* remove stale me->mcol, will be added later */ + BKE_mesh_tessface_clear(me); + + DEG_id_tag_update(&me->id, 0); + + return true; +} + +/* -------------------------------------------------------------------- */ +/** \name Color Blending Modes + * \{ */ + +BLI_INLINE uint mcol_blend(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + + if (fac >= 255) { + return col2; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + /* Updated to use the rgb squared color model which blends nicer. */ + int r1 = cp1[0] * cp1[0]; + int g1 = cp1[1] * cp1[1]; + int b1 = cp1[2] * cp1[2]; + int a1 = cp1[3] * cp1[3]; + + int r2 = cp2[0] * cp2[0]; + int g2 = cp2[1] * cp2[1]; + int b2 = cp2[2] * cp2[2]; + int a2 = cp2[3] * cp2[3]; + + cp[0] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * r1 + fac * r2), 255))); + cp[1] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * g1 + fac * g2), 255))); + cp[2] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * b1 + fac * b2), 255))); + cp[3] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * a1 + fac * a2), 255))); + + return col; +} + +BLI_INLINE uint mcol_add(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + temp = cp1[0] + divide_round_i((fac * cp2[0]), 255); + cp[0] = (temp > 254) ? 255 : temp; + temp = cp1[1] + divide_round_i((fac * cp2[1]), 255); + cp[1] = (temp > 254) ? 255 : temp; + temp = cp1[2] + divide_round_i((fac * cp2[2]), 255); + cp[2] = (temp > 254) ? 255 : temp; + temp = cp1[3] + divide_round_i((fac * cp2[3]), 255); + cp[3] = (temp > 254) ? 255 : temp; + + return col; +} + +BLI_INLINE uint mcol_sub(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + temp = cp1[0] - divide_round_i((fac * cp2[0]), 255); + cp[0] = (temp < 0) ? 0 : temp; + temp = cp1[1] - divide_round_i((fac * cp2[1]), 255); + cp[1] = (temp < 0) ? 0 : temp; + temp = cp1[2] - divide_round_i((fac * cp2[2]), 255); + cp[2] = (temp < 0) ? 0 : temp; + temp = cp1[3] - divide_round_i((fac * cp2[3]), 255); + cp[3] = (temp < 0) ? 0 : temp; + + return col; +} + +BLI_INLINE uint mcol_mul(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + /* first mul, then blend the fac */ + cp[0] = divide_round_i(mfac * cp1[0] * 255 + fac * cp2[0] * cp1[0], 255 * 255); + cp[1] = divide_round_i(mfac * cp1[1] * 255 + fac * cp2[1] * cp1[1], 255 * 255); + cp[2] = divide_round_i(mfac * cp1[2] * 255 + fac * cp2[2] * cp1[2], 255 * 255); + cp[3] = divide_round_i(mfac * cp1[3] * 255 + fac * cp2[3] * cp1[3], 255 * 255); + + return col; +} + +BLI_INLINE uint mcol_lighten(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + else if (fac >= 255) { + return col2; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + /* See if are lighter, if so mix, else don't do anything. + * if the paint col is darker then the original, then ignore */ + if (IMB_colormanagement_get_luminance_byte(cp1) > IMB_colormanagement_get_luminance_byte(cp2)) { + return col1; + } + + cp[0] = divide_round_i(mfac * cp1[0] + fac * cp2[0], 255); + cp[1] = divide_round_i(mfac * cp1[1] + fac * cp2[1], 255); + cp[2] = divide_round_i(mfac * cp1[2] + fac * cp2[2], 255); + cp[3] = divide_round_i(mfac * cp1[3] + fac * cp2[3], 255); + + return col; +} + +BLI_INLINE uint mcol_darken(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + else if (fac >= 255) { + return col2; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + /* See if were darker, if so mix, else don't do anything. + * if the paint col is brighter then the original, then ignore */ + if (IMB_colormanagement_get_luminance_byte(cp1) < IMB_colormanagement_get_luminance_byte(cp2)) { + return col1; + } + + cp[0] = divide_round_i((mfac * cp1[0] + fac * cp2[0]), 255); + cp[1] = divide_round_i((mfac * cp1[1] + fac * cp2[1]), 255); + cp[2] = divide_round_i((mfac * cp1[2] + fac * cp2[2]), 255); + cp[3] = divide_round_i((mfac * cp1[3] + fac * cp2[3]), 255); + return col; +} + +BLI_INLINE uint mcol_colordodge(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + temp = (cp2[0] == 255) ? 255 : min_ii((cp1[0] * 225) / (255 - cp2[0]), 255); + cp[0] = (mfac * cp1[0] + temp * fac) / 255; + temp = (cp2[1] == 255) ? 255 : min_ii((cp1[1] * 225) / (255 - cp2[1]), 255); + cp[1] = (mfac * cp1[1] + temp * fac) / 255; + temp = (cp2[2] == 255) ? 255 : min_ii((cp1[2] * 225) / (255 - cp2[2]), 255); + cp[2] = (mfac * cp1[2] + temp * fac) / 255; + temp = (cp2[3] == 255) ? 255 : min_ii((cp1[3] * 225) / (255 - cp2[3]), 255); + cp[3] = (mfac * cp1[3] + temp * fac) / 255; + return col; +} + +BLI_INLINE uint mcol_difference(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + temp = abs(cp1[0] - cp2[0]); + cp[0] = (mfac * cp1[0] + temp * fac) / 255; + temp = abs(cp1[1] - cp2[1]); + cp[1] = (mfac * cp1[1] + temp * fac) / 255; + temp = abs(cp1[2] - cp2[2]); + cp[2] = (mfac * cp1[2] + temp * fac) / 255; + temp = abs(cp1[3] - cp2[3]); + cp[3] = (mfac * cp1[3] + temp * fac) / 255; + return col; +} + +BLI_INLINE uint mcol_screen(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + temp = max_ii(255 - (((255 - cp1[0]) * (255 - cp2[0])) / 255), 0); + cp[0] = (mfac * cp1[0] + temp * fac) / 255; + temp = max_ii(255 - (((255 - cp1[1]) * (255 - cp2[1])) / 255), 0); + cp[1] = (mfac * cp1[1] + temp * fac) / 255; + temp = max_ii(255 - (((255 - cp1[2]) * (255 - cp2[2])) / 255), 0); + cp[2] = (mfac * cp1[2] + temp * fac) / 255; + temp = max_ii(255 - (((255 - cp1[3]) * (255 - cp2[3])) / 255), 0); + cp[3] = (mfac * cp1[3] + temp * fac) / 255; + return col; +} + +BLI_INLINE uint mcol_hardlight(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + int i = 0; + + for (i = 0; i < 4; i++) { + if (cp2[i] > 127) { + temp = 255 - ((255 - 2 * (cp2[i] - 127)) * (255 - cp1[i]) / 255); + } + else { + temp = (2 * cp2[i] * cp1[i]) >> 8; + } + cp[i] = min_ii((mfac * cp1[i] + temp * fac) / 255, 255); + } + return col; +} + +BLI_INLINE uint mcol_overlay(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + int i = 0; + + for (i = 0; i < 4; i++) { + if (cp1[i] > 127) { + temp = 255 - ((255 - 2 * (cp1[i] - 127)) * (255 - cp2[i]) / 255); + } + else { + temp = (2 * cp2[i] * cp1[i]) >> 8; + } + cp[i] = min_ii((mfac * cp1[i] + temp * fac) / 255, 255); + } + return col; +} + +BLI_INLINE uint mcol_softlight(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + int i = 0; + + for (i = 0; i < 4; i++) { + if (cp1[i] < 127) { + temp = ((2 * ((cp2[i] / 2) + 64)) * cp1[i]) / 255; + } + else { + temp = 255 - (2 * (255 - ((cp2[i] / 2) + 64)) * (255 - cp1[i]) / 255); + } + cp[i] = (temp * fac + cp1[i] * mfac) / 255; + } + return col; +} + +BLI_INLINE uint mcol_exclusion(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac, temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + int i = 0; + + for (i = 0; i < 4; i++) { + temp = 127 - ((2 * (cp1[i] - 127) * (cp2[i] - 127)) / 255); + cp[i] = (temp * fac + cp1[i] * mfac) / 255; + } + return col; +} + +BLI_INLINE uint mcol_luminosity(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp1[0] / 255.0f, cp1[1] / 255.0f, cp1[2] / 255.0f, &h1, &s1, &v1); + rgb_to_hsv(cp2[0] / 255.0f, cp2[1] / 255.0f, cp2[2] / 255.0f, &h2, &s2, &v2); + + v1 = v2; + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp[0] = ((int)(r * 255.0f) * fac + mfac * cp1[0]) / 255; + cp[1] = ((int)(g * 255.0f) * fac + mfac * cp1[1]) / 255; + cp[2] = ((int)(b * 255.0f) * fac + mfac * cp1[2]) / 255; + cp[3] = ((int)(cp2[3]) * fac + mfac * cp1[3]) / 255; + return col; +} + +BLI_INLINE uint mcol_saturation(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp1[0] / 255.0f, cp1[1] / 255.0f, cp1[2] / 255.0f, &h1, &s1, &v1); + rgb_to_hsv(cp2[0] / 255.0f, cp2[1] / 255.0f, cp2[2] / 255.0f, &h2, &s2, &v2); + + if (s1 > EPS_SATURATION) { + s1 = s2; + } + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp[0] = ((int)(r * 255.0f) * fac + mfac * cp1[0]) / 255; + cp[1] = ((int)(g * 255.0f) * fac + mfac * cp1[1]) / 255; + cp[2] = ((int)(b * 255.0f) * fac + mfac * cp1[2]) / 255; + return col; +} + +BLI_INLINE uint mcol_hue(uint col1, uint col2, int fac) +{ + uchar *cp1, *cp2, *cp; + int mfac; + uint col = 0; + + if (fac == 0) { + return col1; + } + + mfac = 255 - fac; + + cp1 = (uchar *)&col1; + cp2 = (uchar *)&col2; + cp = (uchar *)&col; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp1[0] / 255.0f, cp1[1] / 255.0f, cp1[2] / 255.0f, &h1, &s1, &v1); + rgb_to_hsv(cp2[0] / 255.0f, cp2[1] / 255.0f, cp2[2] / 255.0f, &h2, &s2, &v2); + + h1 = h2; + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp[0] = ((int)(r * 255.0f) * fac + mfac * cp1[0]) / 255; + cp[1] = ((int)(g * 255.0f) * fac + mfac * cp1[1]) / 255; + cp[2] = ((int)(b * 255.0f) * fac + mfac * cp1[2]) / 255; + cp[3] = ((int)(cp2[3]) * fac + mfac * cp1[3]) / 255; + return col; +} + +BLI_INLINE uint mcol_alpha_add(uint col1, int fac) +{ + uchar *cp1, *cp; + int temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + cp1 = (uchar *)&col1; + cp = (uchar *)&col; + + temp = cp1[3] + fac; + cp[3] = (temp > 254) ? 255 : temp; + + return col; +} + +BLI_INLINE uint mcol_alpha_sub(uint col1, int fac) +{ + uchar *cp1, *cp; + int temp; + uint col = 0; + + if (fac == 0) { + return col1; + } + + cp1 = (uchar *)&col1; + cp = (uchar *)&col; + + temp = cp1[3] - fac; + cp[3] = temp < 0 ? 0 : temp; + + return col; +} + +/* wpaint has 'ED_wpaint_blend_tool' */ +uint ED_vpaint_blend_tool( + const int tool, const uint col, + const uint paintcol, const int alpha_i) +{ + switch (tool) { + case PAINT_BLEND_MIX: + case PAINT_BLEND_BLUR: return mcol_blend(col, paintcol, alpha_i); + case PAINT_BLEND_AVERAGE: return mcol_blend(col, paintcol, alpha_i); + case PAINT_BLEND_SMEAR: return mcol_blend(col, paintcol, alpha_i); + case PAINT_BLEND_ADD: return mcol_add(col, paintcol, alpha_i); + case PAINT_BLEND_SUB: return mcol_sub(col, paintcol, alpha_i); + case PAINT_BLEND_MUL: return mcol_mul(col, paintcol, alpha_i); + case PAINT_BLEND_LIGHTEN: return mcol_lighten(col, paintcol, alpha_i); + case PAINT_BLEND_DARKEN: return mcol_darken(col, paintcol, alpha_i); + case PAINT_BLEND_COLORDODGE: return mcol_colordodge(col, paintcol, alpha_i); + case PAINT_BLEND_DIFFERENCE: return mcol_difference(col, paintcol, alpha_i); + case PAINT_BLEND_SCREEN: return mcol_screen(col, paintcol, alpha_i); + case PAINT_BLEND_HARDLIGHT: return mcol_hardlight(col, paintcol, alpha_i); + case PAINT_BLEND_OVERLAY: return mcol_overlay(col, paintcol, alpha_i); + case PAINT_BLEND_SOFTLIGHT: return mcol_softlight(col, paintcol, alpha_i); + case PAINT_BLEND_EXCLUSION: return mcol_exclusion(col, paintcol, alpha_i); + case PAINT_BLEND_LUMINOCITY: return mcol_luminosity(col, paintcol, alpha_i); + case PAINT_BLEND_SATURATION: return mcol_saturation(col, paintcol, alpha_i); + case PAINT_BLEND_HUE: return mcol_hue(col, paintcol, alpha_i); + /* non-color */ + case PAINT_BLEND_ALPHA_SUB: return mcol_alpha_sub(col, alpha_i); + case PAINT_BLEND_ALPHA_ADD: return mcol_alpha_add(col, alpha_i); + default: + BLI_assert(0); + return 0; + } +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex_proj.c b/source/blender/editors/sculpt_paint/paint_vertex_proj.c index c939eb6df35..999c9dc7880 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_proj.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_proj.c @@ -42,6 +42,8 @@ #include "BKE_DerivedMesh.h" #include "BKE_context.h" +#include "DEG_depsgraph.h" + #include "ED_screen.h" #include "ED_view3d.h" @@ -98,13 +100,14 @@ static void vpaint_proj_dm_map_cosnos_init__map_cb(void *userData, int index, co } } -static void vpaint_proj_dm_map_cosnos_init(Scene *scene, Object *ob, - struct VertProjHandle *vp_handle) +static void vpaint_proj_dm_map_cosnos_init( + const struct EvaluationContext *eval_ctx, Scene *scene, Object *ob, + struct VertProjHandle *vp_handle) { Mesh *me = ob->data; DerivedMesh *dm; - dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH | CD_MASK_ORIGINDEX); + dm = mesh_get_derived_final(eval_ctx, scene, ob, CD_MASK_BAREMESH | CD_MASK_ORIGINDEX); if (dm->foreachMappedVert) { memset(vp_handle->vcosnos, 0, sizeof(DMCoNo) * me->totvert); @@ -169,8 +172,9 @@ static void vpaint_proj_dm_map_cosnos_update__map_cb(void *userData, int index, } } -static void vpaint_proj_dm_map_cosnos_update(struct VertProjHandle *vp_handle, - ARegion *ar, const float mval_fl[2]) +static void vpaint_proj_dm_map_cosnos_update( + const struct EvaluationContext *eval_ctx, struct VertProjHandle *vp_handle, + ARegion *ar, const float mval_fl[2]) { struct VertProjUpdate vp_update = {vp_handle, ar, mval_fl}; @@ -182,7 +186,7 @@ static void vpaint_proj_dm_map_cosnos_update(struct VertProjHandle *vp_handle, /* quick sanity check - we shouldn't have to run this if there are no modifiers */ BLI_assert(BLI_listbase_is_empty(&ob->modifiers) == false); - dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH | CD_MASK_ORIGINDEX); + dm = mesh_get_derived_final(eval_ctx, scene, ob, CD_MASK_BAREMESH | CD_MASK_ORIGINDEX); /* highly unlikely this will become unavailable once painting starts (perhaps with animated modifiers) */ if (LIKELY(dm->foreachMappedVert)) { @@ -198,8 +202,9 @@ static void vpaint_proj_dm_map_cosnos_update(struct VertProjHandle *vp_handle, /* -------------------------------------------------------------------- */ /* Public Functions */ -struct VertProjHandle *ED_vpaint_proj_handle_create(Scene *scene, Object *ob, - DMCoNo **r_vcosnos) +struct VertProjHandle *ED_vpaint_proj_handle_create( + const struct EvaluationContext *eval_ctx, Scene *scene, Object *ob, + DMCoNo **r_vcosnos) { struct VertProjHandle *vp_handle = MEM_mallocN(sizeof(struct VertProjHandle), __func__); Mesh *me = ob->data; @@ -209,7 +214,7 @@ struct VertProjHandle *ED_vpaint_proj_handle_create(Scene *scene, Object *ob, vp_handle->use_update = false; /* sets 'use_update' if needed */ - vpaint_proj_dm_map_cosnos_init(scene, ob, vp_handle); + vpaint_proj_dm_map_cosnos_init(eval_ctx, scene, ob, vp_handle); if (vp_handle->use_update) { vp_handle->dists_sq = MEM_mallocN(sizeof(float) * me->totvert, __func__); @@ -228,11 +233,12 @@ struct VertProjHandle *ED_vpaint_proj_handle_create(Scene *scene, Object *ob, return vp_handle; } -void ED_vpaint_proj_handle_update(struct VertProjHandle *vp_handle, - ARegion *ar, const float mval_fl[2]) +void ED_vpaint_proj_handle_update( + const struct EvaluationContext *eval_ctx, struct VertProjHandle *vp_handle, + ARegion *ar, const float mval_fl[2]) { if (vp_handle->use_update) { - vpaint_proj_dm_map_cosnos_update(vp_handle, ar, mval_fl); + vpaint_proj_dm_map_cosnos_update(eval_ctx, vp_handle, ar, mval_fl); } } diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c new file mode 100644 index 00000000000..ef86f3c773d --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c @@ -0,0 +1,869 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/sculpt_paint/paint_vertex_weight_ops.c + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_array_utils.h" +#include "BLI_bitmap.h" +#include "BLI_task.h" +#include "BLI_string_utils.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_colormanagement.h" + +//#include "DNA_armature_types.h" +#include "DNA_mesh_types.h" +#include "DNA_particle_types.h" +#include "DNA_brush_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "BKE_DerivedMesh.h" +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_modifier.h" +#include "BKE_object_deform.h" +#include "BKE_paint.h" +#include "BKE_report.h" +#include "BKE_colortools.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_armature.h" +#include "ED_mesh.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "paint_intern.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Store Previous Weights + * + * Use to avoid feedback loop w/ mirrored edits. + * \{ */ + +struct WPaintPrev { + /* previous vertex weights */ + struct MDeformVert *wpaint_prev; + /* allocation size of prev buffers */ + int tot; +}; + + +static void wpaint_prev_init(struct WPaintPrev *wpp) +{ + wpp->wpaint_prev = NULL; + wpp->tot = 0; +} + +static void wpaint_prev_create(struct WPaintPrev *wpp, MDeformVert *dverts, int dcount) +{ + wpaint_prev_init(wpp); + + if (dverts && dcount) { + wpp->wpaint_prev = MEM_mallocN(sizeof(MDeformVert) * dcount, "wpaint prev"); + wpp->tot = dcount; + BKE_defvert_array_copy(wpp->wpaint_prev, dverts, dcount); + } +} + +static void wpaint_prev_destroy(struct WPaintPrev *wpp) +{ + if (wpp->wpaint_prev) { + BKE_defvert_array_free(wpp->wpaint_prev, wpp->tot); + } + wpp->wpaint_prev = NULL; + wpp->tot = 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Weight from Bones Operator + * \{ */ + +static int weight_from_bones_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + + return (ob && (ob->mode & OB_MODE_WEIGHT_PAINT) && modifiers_isDeformedByArmature(ob)); +} + +static int weight_from_bones_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + Object *armob = modifiers_isDeformedByArmature(ob); + Mesh *me = ob->data; + int type = RNA_enum_get(op->ptr, "type"); + + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); + + create_vgroups_from_armature(op->reports, &eval_ctx, scene, ob, armob, type, (me->editflag & ME_EDIT_MIRROR_X)); + + DEG_id_tag_update(&me->id, 0); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); + + return OPERATOR_FINISHED; +} + +void PAINT_OT_weight_from_bones(wmOperatorType *ot) +{ + static EnumPropertyItem type_items[] = { + {ARM_GROUPS_AUTO, "AUTOMATIC", 0, "Automatic", "Automatic weights from bones"}, + {ARM_GROUPS_ENVELOPE, "ENVELOPES", 0, "From Envelopes", "Weights from envelopes with user defined radius"}, + {0, NULL, 0, NULL, NULL}}; + + /* identifiers */ + ot->name = "Weight from Bones"; + ot->idname = "PAINT_OT_weight_from_bones"; + ot->description = "Set the weights of the groups matching the attached armature's selected bones, " + "using the distance between the vertices and the bones"; + + /* api callbacks */ + ot->exec = weight_from_bones_exec; + ot->invoke = WM_menu_invoke; + ot->poll = weight_from_bones_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", type_items, 0, "Type", "Method to use for assigning weights"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sample Weight Operator + * \{ */ + +/* sets wp->weight to the closest weight value to vertex */ +/* note: we cant sample frontbuf, weight colors are interpolated too unpredictable */ +static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ViewContext vc; + Mesh *me; + bool changed = false; + + view3d_set_viewcontext(C, &vc); + me = BKE_mesh_from_object(vc.obact); + + if (me && me->dvert && vc.v3d && vc.rv3d && (vc.obact->actdef != 0)) { + const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + int v_idx_best = -1; + uint index; + + view3d_operator_needs_opengl(C); + ED_view3d_init_mats_rv3d(vc.obact, vc.rv3d); + + if (use_vert_sel) { + if (ED_mesh_pick_vert(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_VERT_SIZE, true)) { + v_idx_best = index; + } + } + else { + if (ED_mesh_pick_face_vert(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { + v_idx_best = index; + } + else if (ED_mesh_pick_face(C, vc.obact, event->mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { + /* this relies on knowning the internal worksings of ED_mesh_pick_face_vert() */ + BKE_report(op->reports, RPT_WARNING, "The modifier used does not support deformed locations"); + } + } + + if (v_idx_best != -1) { /* should always be valid */ + ToolSettings *ts = vc.scene->toolsettings; + Brush *brush = BKE_paint_brush(&ts->wpaint->paint); + const int vgroup_active = vc.obact->actdef - 1; + float vgroup_weight = defvert_find_weight(&me->dvert[v_idx_best], vgroup_active); + + /* use combined weight in multipaint mode, since that's what is displayed to the user in the colors */ + if (ts->multipaint) { + int defbase_tot_sel; + const int defbase_tot = BLI_listbase_count(&vc.obact->defbase); + bool *defbase_sel = BKE_object_defgroup_selected_get(vc.obact, defbase_tot, &defbase_tot_sel); + + if (defbase_tot_sel > 1) { + if (me->editflag & ME_EDIT_MIRROR_X) { + BKE_object_defgroup_mirror_selection( + vc.obact, defbase_tot, defbase_sel, defbase_sel, &defbase_tot_sel); + } + + vgroup_weight = BKE_defvert_multipaint_collective_weight( + &me->dvert[v_idx_best], defbase_tot, defbase_sel, defbase_tot_sel, ts->auto_normalize); + + /* if autonormalize is enabled, but weights are not normalized, the value can exceed 1 */ + CLAMP(vgroup_weight, 0.0f, 1.0f); + } + + MEM_freeN(defbase_sel); + } + + BKE_brush_weight_set(vc.scene, brush, vgroup_weight); + changed = true; + } + } + + if (changed) { + /* not really correct since the brush didnt change, but redraws the toolbar */ + WM_main_add_notifier(NC_BRUSH | NA_EDITED, NULL); /* ts->wpaint->paint.brush */ + + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_weight_sample(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Weight Paint Sample Weight"; + ot->idname = "PAINT_OT_weight_sample"; + ot->description = "Use the mouse to sample a weight in the 3D view"; + + /* api callbacks */ + ot->invoke = weight_sample_invoke; + ot->poll = weight_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Weight Paint Sample Group Operator + * \{ */ + +/* samples cursor location, and gives menu with vertex groups to activate */ +static bool weight_paint_sample_enum_itemf__helper(const MDeformVert *dvert, const int defbase_tot, int *groups) +{ + /* this func fills in used vgroup's */ + bool found = false; + int i = dvert->totweight; + MDeformWeight *dw; + for (dw = dvert->dw; i > 0; dw++, i--) { + if (dw->def_nr < defbase_tot) { + groups[dw->def_nr] = true; + found = true; + } + } + return found; +} +static EnumPropertyItem *weight_paint_sample_enum_itemf( + bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + if (C) { + wmWindow *win = CTX_wm_window(C); + if (win && win->eventstate) { + ViewContext vc; + Mesh *me; + + view3d_set_viewcontext(C, &vc); + me = BKE_mesh_from_object(vc.obact); + + if (me && me->dvert && vc.v3d && vc.rv3d && vc.obact->defbase.first) { + const int defbase_tot = BLI_listbase_count(&vc.obact->defbase); + const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + int *groups = MEM_callocN(defbase_tot * sizeof(int), "groups"); + bool found = false; + uint index; + + const int mval[2] = { + win->eventstate->x - vc.ar->winrct.xmin, + win->eventstate->y - vc.ar->winrct.ymin, + }; + + view3d_operator_needs_opengl(C); + ED_view3d_init_mats_rv3d(vc.obact, vc.rv3d); + + if (use_vert_sel) { + if (ED_mesh_pick_vert(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_VERT_SIZE, true)) { + MDeformVert *dvert = &me->dvert[index]; + found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); + } + } + else { + if (ED_mesh_pick_face(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { + const MPoly *mp = &me->mpoly[index]; + uint fidx = mp->totloop - 1; + + do { + MDeformVert *dvert = &me->dvert[me->mloop[mp->loopstart + fidx].v]; + found |= weight_paint_sample_enum_itemf__helper(dvert, defbase_tot, groups); + } while (fidx--); + } + } + + if (found == false) { + MEM_freeN(groups); + } + else { + EnumPropertyItem *item = NULL, item_tmp = {0}; + int totitem = 0; + int i = 0; + bDeformGroup *dg; + for (dg = vc.obact->defbase.first; dg && i < defbase_tot; i++, dg = dg->next) { + if (groups[i]) { + item_tmp.identifier = item_tmp.name = dg->name; + item_tmp.value = i; + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + MEM_freeN(groups); + return item; + } + } + } + } + + return DummyRNA_NULL_items; +} + +static int weight_sample_group_exec(bContext *C, wmOperator *op) +{ + int type = RNA_enum_get(op->ptr, "group"); + ViewContext vc; + view3d_set_viewcontext(C, &vc); + + BLI_assert(type + 1 >= 0); + vc.obact->actdef = type + 1; + + DEG_id_tag_update(&vc.obact->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, vc.obact); + return OPERATOR_FINISHED; +} + +/* TODO, we could make this a menu into OBJECT_OT_vertex_group_set_active rather than its own operator */ +void PAINT_OT_weight_sample_group(wmOperatorType *ot) +{ + PropertyRNA *prop = NULL; + + /* identifiers */ + ot->name = "Weight Paint Sample Group"; + ot->idname = "PAINT_OT_weight_sample_group"; + ot->description = "Select one of the vertex groups available under current mouse position"; + + /* api callbacks */ + ot->exec = weight_sample_group_exec; + ot->invoke = WM_menu_invoke; + ot->poll = weight_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* keyingset to use (dynamic enum) */ + prop = RNA_def_enum(ot->srna, "group", DummyRNA_DEFAULT_items, 0, "Keying Set", "The Keying Set to use"); + RNA_def_enum_funcs(prop, weight_paint_sample_enum_itemf); + RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); + ot->prop = prop; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Weight Set Operator + * \{ */ + +/* fills in the selected faces with the current weight and vertex group */ +static bool weight_paint_set(Object *ob, float paintweight) +{ + Mesh *me = ob->data; + const MPoly *mp; + MDeformWeight *dw, *dw_prev; + int vgroup_active, vgroup_mirror = -1; + uint index; + const bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + + /* mutually exclusive, could be made into a */ + const short paint_selmode = ME_EDIT_PAINT_SEL_MODE(me); + + if (me->totpoly == 0 || me->dvert == NULL || !me->mpoly) { + return false; + } + + vgroup_active = ob->actdef - 1; + + /* if mirror painting, find the other group */ + if (me->editflag & ME_EDIT_MIRROR_X) { + vgroup_mirror = ED_wpaint_mirror_vgroup_ensure(ob, vgroup_active); + } + + struct WPaintPrev wpp; + wpaint_prev_create(&wpp, me->dvert, me->totvert); + + for (index = 0, mp = me->mpoly; index < me->totpoly; index++, mp++) { + uint fidx = mp->totloop - 1; + + if ((paint_selmode == SCE_SELECT_FACE) && !(mp->flag & ME_FACE_SEL)) { + continue; + } + + do { + uint vidx = me->mloop[mp->loopstart + fidx].v; + + if (!me->dvert[vidx].flag) { + if ((paint_selmode == SCE_SELECT_VERTEX) && !(me->mvert[vidx].flag & SELECT)) { + continue; + } + + dw = defvert_verify_index(&me->dvert[vidx], vgroup_active); + if (dw) { + dw_prev = defvert_verify_index(wpp.wpaint_prev + vidx, vgroup_active); + dw_prev->weight = dw->weight; /* set the undo weight */ + dw->weight = paintweight; + + if (me->editflag & ME_EDIT_MIRROR_X) { /* x mirror painting */ + int j = mesh_get_x_mirror_vert(ob, NULL, vidx, topology); + if (j >= 0) { + /* copy, not paint again */ + if (vgroup_mirror != -1) { + dw = defvert_verify_index(me->dvert + j, vgroup_mirror); + dw_prev = defvert_verify_index(wpp.wpaint_prev + j, vgroup_mirror); + } + else { + dw = defvert_verify_index(me->dvert + j, vgroup_active); + dw_prev = defvert_verify_index(wpp.wpaint_prev + j, vgroup_active); + } + dw_prev->weight = dw->weight; /* set the undo weight */ + dw->weight = paintweight; + } + } + } + me->dvert[vidx].flag = 1; + } + + } while (fidx--); + } + + { + MDeformVert *dv = me->dvert; + for (index = me->totvert; index != 0; index--, dv++) { + dv->flag = 0; + } + } + + wpaint_prev_destroy(&wpp); + + DEG_id_tag_update(&me->id, 0); + + return true; +} + + +static int weight_paint_set_exec(bContext *C, wmOperator *op) +{ + struct Scene *scene = CTX_data_scene(C); + Object *obact = CTX_data_active_object(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Brush *brush = BKE_paint_brush(&ts->wpaint->paint); + float vgroup_weight = BKE_brush_weight_get(scene, brush); + + if (ED_wpaint_ensure_data(C, op->reports, WPAINT_ENSURE_MIRROR, NULL) == false) { + return OPERATOR_CANCELLED; + } + + if (weight_paint_set(obact, vgroup_weight)) { + ED_region_tag_redraw(CTX_wm_region(C)); /* XXX - should redraw all 3D views */ + return OPERATOR_FINISHED; + } + else { + return OPERATOR_CANCELLED; + } +} + +void PAINT_OT_weight_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Weight"; + ot->idname = "PAINT_OT_weight_set"; + ot->description = "Fill the active vertex group with the current paint weight"; + + /* api callbacks */ + ot->exec = weight_paint_set_exec; + ot->poll = mask_paint_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Interactive Weight Gradient Operator + * \{ */ + +/* *** VGroups Gradient *** */ +typedef struct DMGradient_vertStore { + float sco[2]; + float weight_orig; + enum { + VGRAD_STORE_NOP = 0, + VGRAD_STORE_DW_EXIST = (1 << 0) + } flag; +} DMGradient_vertStore; + +typedef struct DMGradient_vertStoreBase { + struct WPaintPrev wpp; + DMGradient_vertStore elem[0]; +} DMGradient_vertStoreBase; + +typedef struct DMGradient_userData { + struct ARegion *ar; + Scene *scene; + Mesh *me; + Brush *brush; + const float *sco_start; /* [2] */ + const float *sco_end; /* [2] */ + float sco_line_div; /* store (1.0f / len_v2v2(sco_start, sco_end)) */ + int def_nr; + bool is_init; + DMGradient_vertStoreBase *vert_cache; + /* only for init */ + BLI_bitmap *vert_visit; + + /* options */ + short use_select; + short type; + float weightpaint; +} DMGradient_userData; + +static void gradientVert_update(DMGradient_userData *grad_data, int index) +{ + Mesh *me = grad_data->me; + DMGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; + float alpha; + + if (grad_data->type == WPAINT_GRADIENT_TYPE_LINEAR) { + alpha = line_point_factor_v2(vs->sco, grad_data->sco_start, grad_data->sco_end); + } + else { + BLI_assert(grad_data->type == WPAINT_GRADIENT_TYPE_RADIAL); + alpha = len_v2v2(grad_data->sco_start, vs->sco) * grad_data->sco_line_div; + } + /* no need to clamp 'alpha' yet */ + + /* adjust weight */ + alpha = BKE_brush_curve_strength_clamped(grad_data->brush, alpha, 1.0f); + + if (alpha != 0.0f) { + MDeformVert *dv = &me->dvert[index]; + MDeformWeight *dw = defvert_verify_index(dv, grad_data->def_nr); + // dw->weight = alpha; // testing + int tool = grad_data->brush->vertexpaint_tool; + float testw; + + /* init if we just added */ + testw = ED_wpaint_blend_tool(tool, vs->weight_orig, grad_data->weightpaint, alpha * grad_data->brush->alpha); + CLAMP(testw, 0.0f, 1.0f); + dw->weight = testw; + } + else { + MDeformVert *dv = &me->dvert[index]; + if (vs->flag & VGRAD_STORE_DW_EXIST) { + /* normally we NULL check, but in this case we know it exists */ + MDeformWeight *dw = defvert_find_index(dv, grad_data->def_nr); + dw->weight = vs->weight_orig; + } + else { + /* wasn't originally existing, remove */ + MDeformWeight *dw = defvert_find_index(dv, grad_data->def_nr); + if (dw) { + defvert_remove_group(dv, dw); + } + } + } +} + +static void gradientVertUpdate__mapFunc( + void *userData, int index, const float UNUSED(co[3]), + const float UNUSED(no_f[3]), const short UNUSED(no_s[3])) +{ + DMGradient_userData *grad_data = userData; + Mesh *me = grad_data->me; + if ((grad_data->use_select == false) || (me->mvert[index].flag & SELECT)) { + DMGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; + if (vs->sco[0] != FLT_MAX) { + gradientVert_update(grad_data, index); + } + } +} + +static void gradientVertInit__mapFunc( + void *userData, int index, const float co[3], + const float UNUSED(no_f[3]), const short UNUSED(no_s[3])) +{ + DMGradient_userData *grad_data = userData; + Mesh *me = grad_data->me; + + if ((grad_data->use_select == false) || (me->mvert[index].flag & SELECT)) { + /* run first pass only, + * the screen coords of the verts need to be cached because + * updating the mesh may move them about (entering feedback loop) */ + + if (BLI_BITMAP_TEST(grad_data->vert_visit, index) == 0) { + DMGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; + if (ED_view3d_project_float_object(grad_data->ar, + co, vs->sco, + V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) == V3D_PROJ_RET_OK) + { + /* ok */ + MDeformVert *dv = &me->dvert[index]; + const MDeformWeight *dw; + dw = defvert_find_index(dv, grad_data->def_nr); + if (dw) { + vs->weight_orig = dw->weight; + vs->flag = VGRAD_STORE_DW_EXIST; + } + else { + vs->weight_orig = 0.0f; + vs->flag = VGRAD_STORE_NOP; + } + + BLI_BITMAP_ENABLE(grad_data->vert_visit, index); + + gradientVert_update(grad_data, index); + } + else { + /* no go */ + copy_v2_fl(vs->sco, FLT_MAX); + } + } + } +} + +static int paint_weight_gradient_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmGesture *gesture = op->customdata; + DMGradient_vertStoreBase *vert_cache = gesture->userdata; + int ret = WM_gesture_straightline_modal(C, op, event); + + if (ret & OPERATOR_RUNNING_MODAL) { + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { /* XXX, hardcoded */ + /* generally crap! redo! */ + WM_gesture_straightline_cancel(C, op); + ret &= ~OPERATOR_RUNNING_MODAL; + ret |= OPERATOR_FINISHED; + } + } + + if (ret & OPERATOR_CANCELLED) { + Object *ob = CTX_data_active_object(C); + Mesh *me = ob->data; + if (vert_cache->wpp.wpaint_prev) { + BKE_defvert_array_free_elems(me->dvert, me->totvert); + BKE_defvert_array_copy(me->dvert, vert_cache->wpp.wpaint_prev, me->totvert); + wpaint_prev_destroy(&vert_cache->wpp); + } + MEM_freeN(vert_cache); + + DEG_id_tag_update(&ob->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + } + else if (ret & OPERATOR_FINISHED) { + wpaint_prev_destroy(&vert_cache->wpp); + MEM_freeN(vert_cache); + } + + return ret; +} + +static int paint_weight_gradient_exec(bContext *C, wmOperator *op) +{ + wmGesture *gesture = op->customdata; + DMGradient_vertStoreBase *vert_cache; + struct ARegion *ar = CTX_wm_region(C); + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + Mesh *me = ob->data; + int x_start = RNA_int_get(op->ptr, "xstart"); + int y_start = RNA_int_get(op->ptr, "ystart"); + int x_end = RNA_int_get(op->ptr, "xend"); + int y_end = RNA_int_get(op->ptr, "yend"); + float sco_start[2] = {x_start, y_start}; + float sco_end[2] = {x_end, y_end}; + const bool is_interactive = (gesture != NULL); + + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); + + DerivedMesh *dm = mesh_get_derived_final(&eval_ctx, scene, ob, scene->customdata_mask); + + DMGradient_userData data = {NULL}; + + if (is_interactive) { + if (gesture->userdata == NULL) { + gesture->userdata = MEM_mallocN( + sizeof(DMGradient_vertStoreBase) + + (sizeof(DMGradient_vertStore) * me->totvert), + __func__); + gesture->userdata_free = false; + data.is_init = true; + + wpaint_prev_create(&((DMGradient_vertStoreBase *)gesture->userdata)->wpp, me->dvert, me->totvert); + + /* on init only, convert face -> vert sel */ + if (me->editflag & ME_EDIT_PAINT_FACE_SEL) { + BKE_mesh_flush_select_from_polys(me); + } + } + + vert_cache = gesture->userdata; + } + else { + if (ED_wpaint_ensure_data(C, op->reports, 0, NULL) == false) { + return OPERATOR_CANCELLED; + } + + data.is_init = true; + vert_cache = MEM_mallocN( + sizeof(DMGradient_vertStoreBase) + + (sizeof(DMGradient_vertStore) * me->totvert), + __func__); + } + + data.ar = ar; + data.scene = scene; + data.me = ob->data; + data.sco_start = sco_start; + data.sco_end = sco_end; + data.sco_line_div = 1.0f / len_v2v2(sco_start, sco_end); + data.def_nr = ob->actdef - 1; + data.use_select = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)); + data.vert_cache = vert_cache; + data.vert_visit = NULL; + data.type = RNA_enum_get(op->ptr, "type"); + + { + ToolSettings *ts = CTX_data_tool_settings(C); + VPaint *wp = ts->wpaint; + struct Brush *brush = BKE_paint_brush(&wp->paint); + + curvemapping_initialize(brush->curve); + + data.brush = brush; + data.weightpaint = BKE_brush_weight_get(scene, brush); + } + + ED_view3d_init_mats_rv3d(ob, ar->regiondata); + + if (data.is_init) { + data.vert_visit = BLI_BITMAP_NEW(me->totvert, __func__); + + dm->foreachMappedVert(dm, gradientVertInit__mapFunc, &data, DM_FOREACH_NOP); + + MEM_freeN(data.vert_visit); + data.vert_visit = NULL; + } + else { + dm->foreachMappedVert(dm, gradientVertUpdate__mapFunc, &data, DM_FOREACH_NOP); + } + + DEG_id_tag_update(&ob->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + if (is_interactive == false) { + MEM_freeN(vert_cache); + } + + return OPERATOR_FINISHED; +} + +static int paint_weight_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + int ret; + + if (ED_wpaint_ensure_data(C, op->reports, 0, NULL) == false) { + return OPERATOR_CANCELLED; + } + + ret = WM_gesture_straightline_invoke(C, op, event); + if (ret & OPERATOR_RUNNING_MODAL) { + struct ARegion *ar = CTX_wm_region(C); + if (ar->regiontype == RGN_TYPE_WINDOW) { + /* TODO, hardcoded, extend WM_gesture_straightline_ */ + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + wmGesture *gesture = op->customdata; + gesture->mode = 1; + } + } + } + return ret; +} + +void PAINT_OT_weight_gradient(wmOperatorType *ot) +{ + /* defined in DNA_space_types.h */ + static EnumPropertyItem gradient_types[] = { + {WPAINT_GRADIENT_TYPE_LINEAR, "LINEAR", 0, "Linear", ""}, + {WPAINT_GRADIENT_TYPE_RADIAL, "RADIAL", 0, "Radial", ""}, + {0, NULL, 0, NULL, NULL} + }; + + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Weight Gradient"; + ot->idname = "PAINT_OT_weight_gradient"; + ot->description = "Draw a line to apply a weight gradient to selected vertices"; + + /* api callbacks */ + ot->invoke = paint_weight_gradient_invoke; + ot->modal = paint_weight_gradient_modal; + ot->exec = paint_weight_gradient_exec; + ot->poll = weight_paint_poll; + ot->cancel = WM_gesture_straightline_cancel; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_enum(ot->srna, "type", gradient_types, 0, "Type", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + WM_operator_properties_gesture_straightline(ot, CURSOR_EDIT); +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c new file mode 100644 index 00000000000..4d70d82d5c6 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.c @@ -0,0 +1,311 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/sculpt_paint/paint_vertex_weight_utils.c + * \ingroup edsculpt + * + * Intended for use by `paint_vertex.c` & `paint_vertex_weight_ops.c`. + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string_utils.h" + +#include "DNA_armature_types.h" +#include "DNA_mesh_types.h" +#include "DNA_scene_types.h" +#include "DNA_brush_types.h" +#include "DNA_object_types.h" + +#include "BKE_action.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_mesh.h" +#include "BKE_modifier.h" +#include "BKE_object_deform.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "paint_intern.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Weight Paint Sanity Checks + * \{ */ + +/* ensure we have data on wpaint start, add if needed */ +bool ED_wpaint_ensure_data( + bContext *C, struct ReportList *reports, + enum eWPaintFlag flag, struct WPaintVGroupIndex *vgroup_index) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + Mesh *me = BKE_mesh_from_object(ob); + + if (vgroup_index) { + vgroup_index->active = -1; + vgroup_index->mirror = -1; + } + + if (scene->obedit) { + return false; + } + + if (me == NULL || me->totpoly == 0) { + return false; + } + + /* if nothing was added yet, we make dverts and a vertex deform group */ + if (!me->dvert) { + BKE_object_defgroup_data_create(&me->id); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); + } + + /* this happens on a Bone select, when no vgroup existed yet */ + if (ob->actdef <= 0) { + Object *modob; + if ((modob = modifiers_isDeformedByArmature(ob))) { + Bone *actbone = ((bArmature *)modob->data)->act_bone; + if (actbone) { + bPoseChannel *pchan = BKE_pose_channel_find_name(modob->pose, actbone->name); + + if (pchan) { + bDeformGroup *dg = defgroup_find_name(ob, pchan->name); + if (dg == NULL) { + dg = BKE_object_defgroup_add_name(ob, pchan->name); /* sets actdef */ + } + else { + int actdef = 1 + BLI_findindex(&ob->defbase, dg); + BLI_assert(actdef >= 0); + ob->actdef = actdef; + } + } + } + } + } + if (BLI_listbase_is_empty(&ob->defbase)) { + BKE_object_defgroup_add(ob); + } + + /* ensure we don't try paint onto an invalid group */ + if (ob->actdef <= 0) { + BKE_report(reports, RPT_WARNING, "No active vertex group for painting, aborting"); + return false; + } + + if (vgroup_index) { + vgroup_index->active = ob->actdef - 1; + } + + if (flag & WPAINT_ENSURE_MIRROR) { + if (me->editflag & ME_EDIT_MIRROR_X) { + int mirror = ED_wpaint_mirror_vgroup_ensure(ob, ob->actdef - 1); + if (vgroup_index) { + vgroup_index->mirror = mirror; + } + } + } + + return true; +} +/** \} */ + +/* mirror_vgroup is set to -1 when invalid */ +int ED_wpaint_mirror_vgroup_ensure(Object *ob, const int vgroup_active) +{ + bDeformGroup *defgroup = BLI_findlink(&ob->defbase, vgroup_active); + + if (defgroup) { + int mirrdef; + char name_flip[MAXBONENAME]; + + BLI_string_flip_side_name(name_flip, defgroup->name, false, sizeof(name_flip)); + mirrdef = defgroup_name_index(ob, name_flip); + if (mirrdef == -1) { + if (BKE_defgroup_new(ob, name_flip)) { + mirrdef = BLI_listbase_count(&ob->defbase) - 1; + } + } + + /* curdef should never be NULL unless this is + * a lamp and BKE_object_defgroup_add_name fails */ + return mirrdef; + } + + return -1; +} + +/* -------------------------------------------------------------------- */ +/** \name Weight Blending Modes + * \{ */ + +BLI_INLINE float wval_blend(const float weight, const float paintval, const float alpha) +{ + const float talpha = min_ff(alpha, 1.0f); /* blending with values over 1 doesn't make sense */ + return (paintval * talpha) + (weight * (1.0f - talpha)); +} +BLI_INLINE float wval_add(const float weight, const float paintval, const float alpha) +{ + return weight + (paintval * alpha); +} +BLI_INLINE float wval_sub(const float weight, const float paintval, const float alpha) +{ + return weight - (paintval * alpha); +} +BLI_INLINE float wval_mul(const float weight, const float paintval, const float alpha) +{ /* first mul, then blend the fac */ + return ((1.0f - alpha) + (alpha * paintval)) * weight; +} +BLI_INLINE float wval_lighten(const float weight, const float paintval, const float alpha) +{ + return (weight < paintval) ? wval_blend(weight, paintval, alpha) : weight; +} +BLI_INLINE float wval_darken(const float weight, const float paintval, const float alpha) +{ + return (weight > paintval) ? wval_blend(weight, paintval, alpha) : weight; +} + +/* mainly for color */ +BLI_INLINE float wval_colordodge(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + temp = (paintval == 1.0f) ? 1.0f : min_ff((weight * (225.0f / 255.0f)) / (1.0f - paintval), 1.0f); + return mfac * weight + temp * fac; +} +BLI_INLINE float wval_difference(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + temp = fabsf(weight - paintval); + return mfac * weight + temp * fac; +} +BLI_INLINE float wval_screen(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + temp = max_ff(1.0f - (((1.0f - weight) * (1.0f - paintval))), 0); + return mfac * weight + temp * fac; +} +BLI_INLINE float wval_hardlight(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + if (paintval > 0.5f) { + temp = 1.0f - ((1.0f - 2.0f * (paintval - 0.5f)) * (1.0f - weight)); + } + else { + temp = (2.0f * paintval * weight); + } + return mfac * weight + temp * fac; +} +BLI_INLINE float wval_overlay(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + if (weight > 0.5f) { + temp = 1.0f - ((1.0f - 2.0f * (weight - 0.5f)) * (1.0f - paintval)); + } + else { + temp = (2.0f * paintval * weight); + } + return mfac * weight + temp * fac; +} +BLI_INLINE float wval_softlight(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + if (weight < 0.5f) { + temp = ((2.0f * ((paintval / 2.0f) + 0.25f)) * weight); + } + else { + temp = 1.0f - (2.0f * (1.0f - ((paintval / 2.0f) + 0.25f)) * (1.0f - weight)); + } + return temp * fac + weight * mfac; +} +BLI_INLINE float wval_exclusion(float weight, float paintval, float fac) +{ + float mfac, temp; + if (fac == 0.0f) { + return weight; + } + mfac = 1.0f - fac; + temp = 0.5f - ((2.0f * (weight - 0.5f) * (paintval - 0.5f))); + return temp * fac + weight * mfac; +} + +/* vpaint has 'vpaint_blend_tool' */ +/* result is not clamped from [0-1] */ +float ED_wpaint_blend_tool( + const int tool, + /* dw->weight */ + const float weight, + const float paintval, const float alpha) +{ + switch (tool) { + case PAINT_BLEND_MIX: + case PAINT_BLEND_AVERAGE: + case PAINT_BLEND_SMEAR: + case PAINT_BLEND_BLUR: return wval_blend(weight, paintval, alpha); + case PAINT_BLEND_ADD: return wval_add(weight, paintval, alpha); + case PAINT_BLEND_SUB: return wval_sub(weight, paintval, alpha); + case PAINT_BLEND_MUL: return wval_mul(weight, paintval, alpha); + case PAINT_BLEND_LIGHTEN: return wval_lighten(weight, paintval, alpha); + case PAINT_BLEND_DARKEN: return wval_darken(weight, paintval, alpha); + /* Mostly make sense for color: support anyway. */ + case PAINT_BLEND_COLORDODGE: return wval_colordodge(weight, paintval, alpha); + case PAINT_BLEND_DIFFERENCE: return wval_difference(weight, paintval, alpha); + case PAINT_BLEND_SCREEN: return wval_screen(weight, paintval, alpha); + case PAINT_BLEND_HARDLIGHT: return wval_hardlight(weight, paintval, alpha); + case PAINT_BLEND_OVERLAY: return wval_overlay(weight, paintval, alpha); + case PAINT_BLEND_SOFTLIGHT: return wval_softlight(weight, paintval, alpha); + case PAINT_BLEND_EXCLUSION: return wval_exclusion(weight, paintval, alpha); + /* Only for color: just use blend. */ + case PAINT_BLEND_LUMINOCITY: + case PAINT_BLEND_SATURATION: + case PAINT_BLEND_HUE: + case PAINT_BLEND_ALPHA_SUB: + case PAINT_BLEND_ALPHA_ADD: + default: return wval_blend(weight, paintval, alpha); + } +} + +/** \} */
\ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index b8e2724f786..fb5192b8072 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -39,7 +39,6 @@ #include "BLI_blenlib.h" #include "BLI_dial.h" #include "BLI_task.h" -#include "BLI_threads.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -166,111 +165,12 @@ static bool sculpt_brush_needs_rake_rotation(const Brush *brush) return SCULPT_TOOL_HAS_RAKE(brush->sculpt_tool) && (brush->rake_factor != 0.0f); } -/* Factor of brush to have rake point following behind - * (could be configurable but this is reasonable default). */ -#define SCULPT_RAKE_BRUSH_FACTOR 0.25f - -struct SculptRakeData { - float follow_dist; - float follow_co[3]; -}; - typedef enum StrokeFlags { CLIP_X = 1, CLIP_Y = 2, CLIP_Z = 4 } StrokeFlags; -/* Cache stroke properties. Used because - * RNA property lookup isn't particularly fast. - * - * For descriptions of these settings, check the operator properties. - */ -typedef struct StrokeCache { - /* Invariants */ - float initial_radius; - float scale[3]; - int flag; - float clip_tolerance[3]; - float initial_mouse[2]; - - /* Variants */ - float radius; - float radius_squared; - float true_location[3]; - float location[3]; - - bool pen_flip; - bool invert; - float pressure; - float mouse[2]; - float bstrength; - float normal_weight; /* from brush (with optional override) */ - - /* The rest is temporary storage that isn't saved as a property */ - - bool first_time; /* Beginning of stroke may do some things special */ - - /* from ED_view3d_ob_project_mat_get() */ - float projection_mat[4][4]; - - /* Clean this up! */ - ViewContext *vc; - Brush *brush; - - float special_rotation; - float grab_delta[3], grab_delta_symmetry[3]; - float old_grab_location[3], orig_grab_location[3]; - - /* screen-space rotation defined by mouse motion */ - float rake_rotation[4], rake_rotation_symmetry[4]; - bool is_rake_rotation_valid; - struct SculptRakeData rake_data; - - int symmetry; /* Symmetry index between 0 and 7 bit combo 0 is Brush only; - * 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ - int mirror_symmetry_pass; /* the symmetry pass we are currently on between 0 and 7*/ - float true_view_normal[3]; - float view_normal[3]; - - /* sculpt_normal gets calculated by calc_sculpt_normal(), then the - * sculpt_normal_symm gets updated quickly with the usual symmetry - * transforms */ - float sculpt_normal[3]; - float sculpt_normal_symm[3]; - - /* Used for area texture mode, local_mat gets calculated by - * calc_brush_local_mat() and used in tex_strength(). */ - float brush_local_mat[4][4]; - - float plane_offset[3]; /* used to shift the plane around when doing tiled strokes */ - int tile_pass; - - float last_center[3]; - int radial_symmetry_pass; - float symm_rot_mat[4][4]; - float symm_rot_mat_inv[4][4]; - bool original; - float anchored_location[3]; - - float vertex_rotation; /* amount to rotate the vertices when using rotate brush */ - Dial *dial; - - char saved_active_brush_name[MAX_ID_NAME]; - char saved_mask_brush_tool; - int saved_smooth_size; /* smooth tool copies the size of the current tool */ - bool alt_smooth; - - float plane_trim_squared; - - bool supports_gravity; - float true_gravity_direction[3]; - float gravity_direction[3]; - - rcti previous_r; /* previous redraw rectangle */ - rcti current_r; /* current redraw rectangle */ -} StrokeCache; - /************** Access to original unmodified vertex data *************/ typedef struct { @@ -477,41 +377,6 @@ static bool sculpt_stroke_is_dynamic_topology( /*** paint mesh ***/ -/* Single struct used by all BLI_task threaded callbacks, let's avoid adding 10's of those... */ -typedef struct SculptThreadedTaskData { - Sculpt *sd; - Object *ob; - Brush *brush; - PBVHNode **nodes; - int totnode; - - /* Data specific to some callbacks. */ - /* Note: even if only one or two of those are used at a time, keeping them separated, names help figuring out - * what it is, and memory overhead is ridiculous anyway... */ - float flippedbstrength; - float angle; - float strength; - bool smooth_mask; - bool has_bm_orco; - - SculptProjectVector *spvc; - float *offset; - float *grab_delta; - float *cono; - float *area_no; - float *area_no_sp; - float *area_co; - float (*mat)[4]; - float (*vertCos)[3]; - - /* 0=towards view, 1=flipped */ - float (*area_cos)[3]; - float (*area_nos)[3]; - int *count; - - ThreadMutex mutex; -} SculptThreadedTaskData; - static void paint_mesh_restore_co_task_cb(void *userdata, const int n) { SculptThreadedTaskData *data = userdata; @@ -601,7 +466,7 @@ static void sculpt_extend_redraw_rect_previous(Object *ob, rcti *rect) } /* Get a screen-space rectangle of the modified area */ -static bool sculpt_get_redraw_rect(ARegion *ar, RegionView3D *rv3d, +bool sculpt_get_redraw_rect(ARegion *ar, RegionView3D *rv3d, Object *ob, rcti *rect) { PBVH *pbvh = ob->sculpt->pbvh; @@ -650,17 +515,7 @@ void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *ar, Object *ob) /************************ Brush Testing *******************/ -typedef struct SculptBrushTest { - float radius_squared; - float location[3]; - float dist; - int mirror_symmetry_pass; - - /* View3d clipping - only set rv3d for clipping */ - RegionView3D *clip_rv3d; -} SculptBrushTest; - -static void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test) +void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test) { RegionView3D *rv3d = ss->cache->vc->rv3d; @@ -668,6 +523,10 @@ static void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test) copy_v3_v3(test->location, ss->cache->location); test->dist = 0.0f; /* just for initialize */ + /* Only for 2D projection. */ + zero_v4(test->plane_view); + zero_v4(test->plane_tool); + test->mirror_symmetry_pass = ss->cache->mirror_symmetry_pass; if (rv3d->rflag & RV3D_CLIPPING) { @@ -689,7 +548,7 @@ BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const fl return ED_view3d_clipping_test(rv3d, symm_co, true); } -static bool sculpt_brush_test(SculptBrushTest *test, const float co[3]) +bool sculpt_brush_test_sphere(SculptBrushTest *test, const float co[3]) { float distsq = len_squared_v3v3(co, test->location); @@ -705,7 +564,7 @@ static bool sculpt_brush_test(SculptBrushTest *test, const float co[3]) } } -static bool sculpt_brush_test_sq(SculptBrushTest *test, const float co[3]) +bool sculpt_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]) { float distsq = len_squared_v3v3(co, test->location); @@ -721,7 +580,7 @@ static bool sculpt_brush_test_sq(SculptBrushTest *test, const float co[3]) } } -static bool sculpt_brush_test_fast(const SculptBrushTest *test, const float co[3]) +bool sculpt_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]) { if (sculpt_brush_test_clipping(test, co)) { return 0; @@ -729,7 +588,25 @@ static bool sculpt_brush_test_fast(const SculptBrushTest *test, const float co[3 return len_squared_v3v3(co, test->location) <= test->radius_squared; } -static bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], float local[4][4]) +bool sculpt_brush_test_circle_sq(SculptBrushTest *test, const float co[3]) +{ + float co_proj[3]; + closest_to_plane_normalized_v3(co_proj, test->plane_view, co); + float distsq = len_squared_v3v3(co_proj, test->location); + + if (distsq <= test->radius_squared) { + if (sculpt_brush_test_clipping(test, co)) { + return 0; + } + test->dist = distsq; + return 1; + } + else { + return 0; + } +} + +bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], float local[4][4]) { float side = M_SQRT1_2; float local_co[3]; @@ -758,7 +635,36 @@ static bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], flo } } -static float frontface(Brush *br, const float sculpt_normal[3], +SculptBrushTestFn sculpt_brush_test_init_with_falloff_shape( + SculptSession *ss, SculptBrushTest *test, char falloff_shape) +{ + sculpt_brush_test_init(ss, test); + SculptBrushTestFn sculpt_brush_test_sq_fn; + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + sculpt_brush_test_sq_fn = sculpt_brush_test_sphere_sq; + } + else { + /* PAINT_FALLOFF_SHAPE_TUBE */ + plane_from_point_normal_v3(test->plane_view, test->location, ss->cache->view_normal); + sculpt_brush_test_sq_fn = sculpt_brush_test_circle_sq; + } + return sculpt_brush_test_sq_fn; +} + +const float *sculpt_brush_frontface_normal_from_falloff_shape( + SculptSession *ss, char falloff_shape) +{ + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + return ss->cache->sculpt_normal_symm; + } + else { + /* PAINT_FALLOFF_SHAPE_TUBE */ + return ss->cache->view_normal; + } +} + + +static float frontface(const Brush *br, const float sculpt_normal[3], const short no[3], const float fno[3]) { if (br->flag & BRUSH_FRONTFACE) { @@ -784,7 +690,7 @@ static float frontface(Brush *br, const float sculpt_normal[3], static bool sculpt_brush_test_cyl(SculptBrushTest *test, float co[3], float location[3], const float area_no[3]) { - if (sculpt_brush_test_fast(test, co)) { + if (sculpt_brush_test_sphere_fast(test, co)) { float t1[3], t2[3], t3[3], dist; sub_v3_v3v3(t1, location, co); @@ -896,18 +802,22 @@ static void calc_area_normal_and_center_task_cb(void *userdata, const int n) float (*area_cos)[3] = data->area_cos; PBVHVertexIter vd; - SculptBrushTest test; - SculptUndoNode *unode; + SculptUndoNode *unode = NULL; float private_co[2][3] = {{0.0f}}; float private_no[2][3] = {{0.0f}}; int private_count[2] = {0}; - bool use_original; + bool use_original = false; - unode = sculpt_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - sculpt_brush_test_init(ss, &test); + if (ss->cache->original) { + unode = sculpt_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + use_original = (unode->co || unode->bm_entry); + } + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); - use_original = (ss->cache->original && (unode->co || unode->bm_entry)); /* when the mesh is edited we can't rely on original coords * (original mesh may not even have verts in brush radius) */ @@ -929,7 +839,7 @@ static void calc_area_normal_and_center_task_cb(void *userdata, const int n) closest_on_tri_to_point_v3(co, test.location, UNPACK3(co_tri)); - if (sculpt_brush_test_fast(&test, co)) { + if (sculpt_brush_test_sq_fn(&test, co)) { float no[3]; int flip_index; @@ -963,7 +873,7 @@ static void calc_area_normal_and_center_task_cb(void *userdata, const int n) co = vd.co; } - if (sculpt_brush_test_fast(&test, co)) { + if (sculpt_brush_test_sq_fn(&test, co)) { float no_buf[3]; const float *no; int flip_index; @@ -1029,8 +939,9 @@ static void calc_area_center( int count[2] = {0}; + /* Intentionally set 'sd' to NULL since we share logic with vertex paint. */ SculptThreadedTaskData data = { - .sd = sd, .ob = ob, .nodes = nodes, .totnode = totnode, + .sd = NULL, .ob = ob, .nodes = nodes, .totnode = totnode, .has_bm_orco = has_bm_orco, .area_cos = area_cos, .area_nos = NULL, .count = count, }; BLI_mutex_init(&data.mutex); @@ -1053,42 +964,53 @@ static void calc_area_center( } } - static void calc_area_normal( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) { const Brush *brush = BKE_paint_brush(&sd->paint); + bool use_threading = (sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT; + sculpt_pbvh_calc_area_normal(brush, ob, nodes, totnode, use_threading, r_area_no); +} + +/* expose 'calc_area_normal' externally. */ +void sculpt_pbvh_calc_area_normal( + const Brush *brush, Object *ob, + PBVHNode **nodes, int totnode, + bool use_threading, + float r_area_no[3]) +{ SculptSession *ss = ob->sculpt; const bool has_bm_orco = ss->bm && sculpt_stroke_is_dynamic_topology(ss, brush); - int n; /* 0=towards view, 1=flipped */ float area_nos[2][3] = {{0.0f}}; int count[2] = {0}; + /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ SculptThreadedTaskData data = { - .sd = sd, .ob = ob, .nodes = nodes, .totnode = totnode, + .sd = NULL, .ob = ob, .brush = brush, .nodes = nodes, .totnode = totnode, .has_bm_orco = has_bm_orco, .area_cos = NULL, .area_nos = area_nos, .count = count, }; BLI_mutex_init(&data.mutex); BLI_task_parallel_range( 0, totnode, &data, calc_area_normal_and_center_task_cb, - ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT)); + use_threading); BLI_mutex_end(&data.mutex); /* for area normal */ - for (n = 0; n < ARRAY_SIZE(area_nos); n++) { - if (normalize_v3_v3(r_area_no, area_nos[n]) != 0.0f) { + for (int i = 0; i < ARRAY_SIZE(area_nos); i++) { + if (normalize_v3_v3(r_area_no, area_nos[i]) != 0.0f) { break; } } } + /* 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( @@ -1107,8 +1029,9 @@ static void calc_area_normal_and_center( int count[2] = {0}; + /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ SculptThreadedTaskData data = { - .sd = sd, .ob = ob, .nodes = nodes, .totnode = totnode, + .sd = NULL, .ob = ob, .brush = brush, .nodes = nodes, .totnode = totnode, .has_bm_orco = has_bm_orco, .area_cos = area_cos, .area_nos = area_nos, .count = count, }; BLI_mutex_init(&data.mutex); @@ -1237,17 +1160,17 @@ static float brush_strength( } /* Return a multiplier for brush strength on a particular vertex. */ -static float tex_strength(SculptSession *ss, Brush *br, - const float brush_point[3], - const float len, - const short vno[3], - const float fno[3], - const float mask, - const int thread_id) +float tex_strength(SculptSession *ss, const Brush *br, + const float brush_point[3], + const float len, + const short vno[3], + const float fno[3], + const float mask, + const int thread_id) { StrokeCache *cache = ss->cache; const Scene *scene = cache->vc->scene; - MTex *mtex = &br->mtex; + const MTex *mtex = &br->mtex; float avg = 1; float rgba[4]; float point[3]; @@ -1316,15 +1239,8 @@ static float tex_strength(SculptSession *ss, Brush *br, return avg; } -typedef struct { - Sculpt *sd; - SculptSession *ss; - float radius_squared; - bool original; -} SculptSearchSphereData; - /* Test AABB against sphere */ -static bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v) +bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v) { SculptSearchSphereData *data = data_v; float *center = data->ss->cache->location, nearest[3]; @@ -1350,6 +1266,24 @@ static bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v) return len_squared_v3(t) < data->radius_squared; } +/* 2D projection (distance to line). */ +bool sculpt_search_circle_cb(PBVHNode *node, void *data_v) +{ + SculptSearchCircleData *data = data_v; + float bb_min[3], bb_max[3]; + + if (data->original) + BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); + else + BKE_pbvh_node_get_BB(node, bb_min, bb_min); + + float dummy_co[3], dummy_depth; + const float dist_sq = dist_squared_ray_to_aabb_v3( + data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth); + + return dist_sq < data->radius_squared || 1; +} + /* Handles clipping against a mirror modifier and SCULPT_LOCK axis flags */ static void sculpt_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3]) { @@ -1366,6 +1300,37 @@ static void sculpt_clip(Sculpt *sd, SculptSession *ss, float co[3], const float } } +static PBVHNode **sculpt_pbvh_gather_generic( + Object *ob, Sculpt *sd, const Brush *brush, bool use_original, float radius_scale, int *r_totnode) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = NULL; + + /* Build a list of all nodes that are potentially within the brush's area of influence */ + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + SculptSearchSphereData data = { + .ss = ss, + .sd = sd, + .radius_squared = SQUARE(ss->cache->radius * radius_scale), + .original = use_original, + }; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, r_totnode); + } + else { + struct DistRayAABB_Precalc dist_ray_to_aabb_precalc; + dist_squared_ray_to_aabb_v3_precalc(&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); + SculptSearchCircleData data = { + .ss = ss, + .sd = sd, + .radius_squared = SQUARE(ss->cache->radius * radius_scale), + .original = use_original, + .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc, + }; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_circle_cb, &data, &nodes, r_totnode); + } + return nodes; +} + /* Calculate primary direction of movement for many brushes */ static void calc_sculpt_normal( Sculpt *sd, Object *ob, @@ -1412,6 +1377,10 @@ static void update_sculpt_normal(Sculpt *sd, Object *ob, (cache->first_time || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) { calc_sculpt_normal(sd, ob, nodes, totnode, cache->sculpt_normal); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->sculpt_normal, cache->sculpt_normal, cache->view_normal); + normalize_v3(cache->sculpt_normal); + } copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); } else { @@ -1632,30 +1601,54 @@ typedef struct SculptDoBrushSmoothGridDataChunk { size_t tmpgrid_size; } SculptDoBrushSmoothGridDataChunk; +typedef struct { + SculptSession *ss; + const float *ray_start, *ray_normal; + bool hit; + float depth; + bool original; +} SculptRaycastData; + +typedef struct { + const float *ray_start, *ray_normal; + bool hit; + float depth; + float detail; +} SculptDetailRaycastData; + +typedef struct { + SculptSession *ss; + const float *ray_start, *ray_normal; + bool hit; + float depth; + float dist_sq_to_ray; + bool original; +} SculptFindNearestToRayData; + static void do_smooth_brush_mesh_task_cb_ex( void *userdata, void *UNUSED(userdata_chunk), const int n, const int thread_id) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; - Brush *brush = data->brush; + const Brush *brush = data->brush; const bool smooth_mask = data->smooth_mask; float bstrength = data->strength; PBVHVertexIter vd; - SculptBrushTest test; CLAMP(bstrength, 0.0f, 1.0f); - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, - smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f), - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f), thread_id); if (smooth_mask) { float val = neighbor_average_mask(ss, vd.vert_indices[vd.i]) - *vd.mask; val *= fade * bstrength; @@ -1686,23 +1679,24 @@ static void do_smooth_brush_bmesh_task_cb_ex( SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; - Brush *brush = data->brush; + const Brush *brush = data->brush; const bool smooth_mask = data->smooth_mask; float bstrength = data->strength; PBVHVertexIter vd; - SculptBrushTest test; CLAMP(bstrength, 0.0f, 1.0f); - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, smooth_mask ? 0.0f : *vd.mask, - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, smooth_mask ? 0.0f : *vd.mask, thread_id); if (smooth_mask) { float val = bmesh_neighbor_average_mask(vd.bm_vert, vd.cd_vert_mask_offset) - *vd.mask; val *= fade * bstrength; @@ -1734,11 +1728,10 @@ static void do_smooth_brush_multires_task_cb_ex( SculptDoBrushSmoothGridDataChunk *data_chunk = userdata_chunk; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; - Brush *brush = data->brush; + const Brush *brush = data->brush; const bool smooth_mask = data->smooth_mask; float bstrength = data->strength; - SculptBrushTest test; CCGElem **griddata, *gddata; CCGKey key; @@ -1751,7 +1744,9 @@ static void do_smooth_brush_multires_task_cb_ex( int *grid_indices, totgrid, gridsize; int i, x, y; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); CLAMP(bstrength, 0.0f, 1.0f); @@ -1838,10 +1833,11 @@ static void do_smooth_brush_multires_task_cb_ex( fno = CCG_elem_offset_no(&key, gddata, index); mask = CCG_elem_offset_mask(&key, gddata, index); - if (sculpt_brush_test(&test, co)) { + if (sculpt_brush_test_sq_fn(&test, co)) { const float strength_mask = (smooth_mask ? 0.0f : *mask); const float fade = bstrength * tex_strength( - ss, brush, co, test.dist, NULL, fno, strength_mask, thread_id); + ss, brush, co, sqrtf(test.dist), + NULL, fno, strength_mask, thread_id); float f = 1.0f / 16.0f; if (x == 0 || x == gridsize - 1) @@ -1948,18 +1944,21 @@ static void do_mask_brush_draw_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float bstrength = ss->cache->bstrength; PBVHVertexIter vd; - SculptBrushTest test; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { - const float fade = tex_strength(ss, brush, vd.co, test.dist, vd.no, vd.fno, 0.0f, thread_id); + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + const float fade = tex_strength( + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, 0.0f, thread_id); (*vd.mask) += fade * bstrength; CLAMP(*vd.mask, 0, 1); @@ -2005,23 +2004,25 @@ static void do_draw_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *offset = data->offset; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* offset vertex */ const float fade = tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); @@ -2059,35 +2060,44 @@ static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT), false); } +/** + * Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB' + */ static void do_crease_brush_task_cb_ex( void *userdata, void *UNUSED(userdata_chunk), const int n, const int thread_id) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; SculptProjectVector *spvc = data->spvc; const float flippedbstrength = data->flippedbstrength; const float *offset = data->offset; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* offset vertex */ const float fade = tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, 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); @@ -2152,25 +2162,30 @@ static void do_pinch_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); float val[3]; sub_v3_v3v3(val, test.location, vd.co); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(val, val, ss->cache->view_normal); + } mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) @@ -2198,11 +2213,10 @@ static void do_grab_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *grab_delta = data->grab_delta; PBVHVertexIter vd; - SculptBrushTest test; SculptOrigVertData orig_data; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; @@ -2211,16 +2225,18 @@ static void do_grab_brush_task_cb_ex( proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); 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(&test, orig_data.co)) { + if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = bstrength * tex_strength( - ss, brush, orig_data.co, test.dist, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, orig_data.co, sqrtf(test.dist), + orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], grab_delta, fade); @@ -2258,23 +2274,25 @@ static void do_nudge_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *cono = data->cono; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); @@ -2312,12 +2330,11 @@ static void do_snake_hook_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; SculptProjectVector *spvc = data->spvc; const float *grab_delta = data->grab_delta; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; const bool do_rake_rotation = ss->cache->is_rake_rotation_valid; @@ -2327,13 +2344,16 @@ static void do_snake_hook_brush_task_cb_ex( proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], grab_delta, fade); @@ -2342,6 +2362,9 @@ static void do_snake_hook_brush_task_cb_ex( 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); @@ -2413,11 +2436,10 @@ static void do_thumb_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *cono = data->cono; PBVHVertexIter vd; - SculptBrushTest test; SculptOrigVertData orig_data; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; @@ -2426,16 +2448,18 @@ static void do_thumb_brush_task_cb_ex( proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); 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(&test, orig_data.co)) { + if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = bstrength * tex_strength( - ss, brush, orig_data.co, test.dist, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, orig_data.co, sqrtf(test.dist), + orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); @@ -2473,11 +2497,10 @@ static void do_rotate_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float angle = data->angle; PBVHVertexIter vd; - SculptBrushTest test; SculptOrigVertData orig_data; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; @@ -2486,17 +2509,19 @@ static void do_rotate_brush_task_cb_ex( proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); 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(&test, orig_data.co)) { + if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { float vec[3], rot[3][3]; const float fade = bstrength * tex_strength( - ss, brush, orig_data.co, test.dist, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, orig_data.co, sqrtf(test.dist), + orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, 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); @@ -2535,11 +2560,10 @@ static void do_layer_brush_task_cb_ex( SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *offset = data->offset; PBVHVertexIter vd; - SculptBrushTest test; SculptOrigVertData orig_data; float *layer_disp; const float bstrength = ss->cache->bstrength; @@ -2554,15 +2578,18 @@ static void do_layer_brush_task_cb_ex( layer_disp = BKE_pbvh_node_layer_disp_get(ss->pbvh, data->nodes[n]); BLI_mutex_unlock(&data->mutex); - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); 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(&test, orig_data.co)) { + if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); float *disp = &layer_disp[vd.i]; float val[3]; @@ -2619,22 +2646,24 @@ static void do_inflate_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); float val[3]; if (vd.fno) @@ -2697,6 +2726,10 @@ static void calc_sculpt_plane( 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: @@ -2738,16 +2771,6 @@ static void calc_sculpt_plane( } } -/* Projects a point onto a plane along the plane's normal */ -static void point_plane_project( - float intr[3], - const float co[3], const float plane_normal[3], const float plane_center[3]) -{ - sub_v3_v3v3(intr, co, plane_center); - mul_v3_v3fl(intr, plane_normal, dot_v3v3(plane_normal, intr)); - sub_v3_v3v3(intr, co, intr); -} - static int plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3]) { return (!(brush->flag & BRUSH_PLANE_TRIM) || @@ -2755,26 +2778,18 @@ static int plane_trim(const StrokeCache *cache, const Brush *brush, const float } static bool plane_point_side_flip( - const float co[3], const float plane_normal[3], const float plane_center[3], + const float co[3], const float plane[4], const bool flip) { - float delta[3]; - float d; - - sub_v3_v3v3(delta, co, plane_center); - d = dot_v3v3(plane_normal, delta); - + float d = plane_point_side_v3(plane, co); if (flip) d = -d; - return d <= 0.0f; } -static int plane_point_side(const float co[3], const float plane_normal[3], const float plane_center[3]) +static int plane_point_side(const float co[3], const float plane[4]) { - float delta[3]; - - sub_v3_v3v3(delta, co, plane_center); - return dot_v3v3(plane_normal, delta) <= 0.0f; + float d = plane_point_side_v3(plane, co); + return d <= 0.0f; } static float get_offset(Sculpt *sd, SculptSession *ss) @@ -2795,33 +2810,36 @@ static void do_flatten_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + + 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(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { float intr[3]; float val[3]; - point_plane_project(intr, vd.co, area_no, area_co); + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (plane_trim(ss->cache, brush, val)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); @@ -2870,28 +2888,31 @@ static void do_clay_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const bool flip = (ss->cache->bstrength < 0); 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); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + + 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(&test, vd.co)) { - if (plane_point_side_flip(vd.co, area_no, area_co, flip)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + if (plane_point_side_flip(vd.co, test.plane_tool, flip)) { float intr[3]; float val[3]; - point_plane_project(intr, vd.co, area_no, area_co); + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); @@ -2899,8 +2920,8 @@ static void do_clay_brush_task_cb_ex( /* note, the normal from the vertices is ignored, * causes glitch with planes, see: T44390 */ const float fade = bstrength * tex_strength( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); @@ -2953,7 +2974,7 @@ static void do_clay_strips_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + 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; @@ -2967,15 +2988,16 @@ static void do_clay_strips_brush_task_cb_ex( 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); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_cube(&test, vd.co, mat)) { - if (plane_point_side_flip(vd.co, area_no_sp, area_co, flip)) { + if (plane_point_side_flip(vd.co, test.plane_tool, flip)) { float intr[3]; float val[3]; - point_plane_project(intr, vd.co, area_no_sp, area_co); + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); @@ -2983,8 +3005,8 @@ static void do_clay_strips_brush_task_cb_ex( /* note, the normal from the vertices is ignored, * causes glitch with planes, see: T44390 */ const float fade = bstrength * tex_strength( - ss, brush, vd.co, ss->cache->radius * test.dist, - vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, ss->cache->radius * test.dist, + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); @@ -3062,34 +3084,37 @@ static void do_fill_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + + 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(&test, vd.co)) { - if (plane_point_side(vd.co, area_no, area_co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + if (plane_point_side(vd.co, test.plane_tool)) { float intr[3]; float val[3]; - point_plane_project(intr, vd.co, area_no, area_co); + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (plane_trim(ss->cache, brush, val)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, sqrtf(test.dist), - vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); @@ -3140,34 +3165,36 @@ static void do_scrape_brush_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + 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(&test, vd.co)) { - if (!plane_point_side(vd.co, area_no, area_co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + if (!plane_point_side(vd.co, test.plane_tool)) { float intr[3]; float val[3]; - point_plane_project(intr, vd.co, area_no, area_co); + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (plane_trim(ss->cache, brush, val)) { const float fade = bstrength * tex_strength( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); @@ -3218,22 +3245,23 @@ static void do_gravity_task_cb_ex( { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - Brush *brush = data->brush; + const Brush *brush = data->brush; float *offset = data->offset; PBVHVertexIter vd; - SculptBrushTest test; float (*proxy)[3]; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - sculpt_brush_test_init(ss, &test); + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (sculpt_brush_test_sq(&test, vd.co)) { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = tex_strength( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, - thread_id); + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); @@ -3317,22 +3345,12 @@ void sculpt_vertcos_to_key(Object *ob, KeyBlock *kb, float (*vertCos)[3]) static void sculpt_topology_update(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *UNUSED(ups)) { SculptSession *ss = ob->sculpt; - SculptSearchSphereData data; - PBVHNode **nodes = NULL; - float radius; - int n, totnode; - - /* Build a list of all nodes that are potentially within the - * brush's area of influence */ - data.ss = ss; - data.sd = sd; - - radius = ss->cache->radius * 1.25f; - data.radius_squared = radius * radius; - data.original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : ss->cache->original; - - BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); + int n, totnode; + /* Build a list of all nodes that are potentially within the brush's area of influence */ + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : ss->cache->original; + const float radius_scale = 1.25f; + PBVHNode **nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); /* Only act if some verts are inside the brush area */ if (totnode) { @@ -3364,8 +3382,10 @@ static void sculpt_topology_update(Sculpt *sd, Object *ob, Brush *brush, Unified BKE_pbvh_bmesh_update_topology( ss->pbvh, mode, ss->cache->location, - (brush->flag & BRUSH_FRONTFACE) ? ss->cache->view_normal : NULL, - ss->cache->radius); + ss->cache->view_normal, + ss->cache->radius, + (brush->flag & BRUSH_FRONTFACE) != 0, + (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); } MEM_freeN(nodes); @@ -3388,16 +3408,12 @@ static void do_brush_action_task_cb(void *userdata, const int n) static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups) { SculptSession *ss = ob->sculpt; - SculptSearchSphereData data; - PBVHNode **nodes = NULL; int totnode; /* Build a list of all nodes that are potentially within the brush's area of influence */ - data.ss = ss; - data.sd = sd; - data.radius_squared = ss->cache->radius_squared; - data.original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : ss->cache->original; - BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : ss->cache->original; + const float radius_scale = 1.0f; + PBVHNode **nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); /* Only act if some verts are inside the brush area */ if (totnode) { @@ -3692,13 +3708,12 @@ static void sculpt_flush_stroke_deform(Sculpt *sd, Object *ob) /* Flip all the editdata across the axis/axes specified by symm. Used to * calculate multiple modifications to the mesh when symmetry is enabled. */ -static void calc_brushdata_symm(Sculpt *sd, StrokeCache *cache, const char symm, - const char axis, const float angle, - const float UNUSED(feather)) +void sculpt_cache_calc_brushdata_symm( + StrokeCache *cache, const char symm, + const char axis, const float angle) { - (void)sd; /* unused */ - flip_v3_v3(cache->location, cache->true_location, symm); + flip_v3_v3(cache->last_location, cache->true_last_location, symm); flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm); flip_v3_v3(cache->view_normal, cache->true_view_normal, symm); @@ -3795,7 +3810,7 @@ static void do_tiled(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings static void do_radial_symmetry(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action, const char symm, const int axis, - const float feather) + const float UNUSED(feather)) { SculptSession *ss = ob->sculpt; int i; @@ -3803,7 +3818,7 @@ static void do_radial_symmetry(Sculpt *sd, Object *ob, Brush *brush, UnifiedPain for (i = 1; i < sd->radial_symm[axis - 'X']; ++i) { const float angle = 2 * M_PI * i / sd->radial_symm[axis - 'X']; ss->cache->radial_symmetry_pass = i; - calc_brushdata_symm(sd, ss->cache, symm, axis, angle, feather); + sculpt_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); do_tiled(sd, ob, brush, ups, action); } } @@ -3821,8 +3836,9 @@ static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) multires_stitch_grids(ob); } -static void do_symmetrical_brush_actions(Sculpt *sd, Object *ob, - BrushActionFunc action, UnifiedPaintSettings *ups) +static void do_symmetrical_brush_actions( + Sculpt *sd, Object *ob, + BrushActionFunc action, UnifiedPaintSettings *ups) { Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; @@ -3841,7 +3857,7 @@ static void do_symmetrical_brush_actions(Sculpt *sd, Object *ob, cache->mirror_symmetry_pass = i; cache->radial_symmetry_pass = 0; - calc_brushdata_symm(sd, cache, i, 0, 0, feather); + sculpt_cache_calc_brushdata_symm(cache, i, 0, 0); do_tiled(sd, ob, brush, ups, action); do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); @@ -3952,7 +3968,7 @@ static const char *sculpt_tool_name(Sculpt *sd) * Operator for applying a stroke (various attributes including mouse path) * using the current brush. */ -static void sculpt_cache_free(StrokeCache *cache) +void sculpt_cache_free(StrokeCache *cache) { if (cache->dial) MEM_freeN(cache->dial); @@ -4215,7 +4231,6 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); } - invert_m4_m4(imat, ob->obmat); mul_mat3_m4_v3(imat, cache->grab_delta); break; @@ -4225,6 +4240,10 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru zero_v3(cache->grab_delta); } + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss->cache->true_view_normal); + } + copy_v3_v3(cache->old_grab_location, grab_location); if (tool == SCULPT_TOOL_GRAB) @@ -4394,29 +4413,17 @@ static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob) SculptSession *ss = ob->sculpt; if (ss->kb || ss->modifiers_active) { + EvaluationContext eval_ctx; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - BKE_sculpt_update_mesh_elements(CTX_data_scene(C), sd, ob, + CTX_data_eval_ctx(C, &eval_ctx); + + BKE_sculpt_update_mesh_elements(&eval_ctx, CTX_data_scene(C), sd, ob, sculpt_any_smooth_mode(brush, ss->cache, 0), false); } } -typedef struct { - SculptSession *ss; - const float *ray_start, *ray_normal; - bool hit; - float dist; - bool original; -} SculptRaycastData; - -typedef struct { - const float *ray_start, *ray_normal; - bool hit; - float dist; - float detail; -} SculptDetailRaycastData; - static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) { if (BKE_pbvh_node_get_tmin(node) < *tmin) { @@ -4437,10 +4444,40 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) } if (BKE_pbvh_node_raycast(srd->ss->pbvh, node, origco, use_origco, - srd->ray_start, srd->ray_normal, &srd->dist)) + srd->ray_start, srd->ray_normal, &srd->depth)) + { + srd->hit = 1; + *tmin = srd->depth; + } + } +} + +static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) +{ + if (BKE_pbvh_node_get_tmin(node) < *tmin) { + SculptFindNearestToRayData *srd = data_v; + float (*origco)[3] = NULL; + bool use_origco = false; + + if (srd->original && srd->ss->cache) { + if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { + use_origco = true; + } + else { + /* intersect with coordinates from before we started stroke */ + SculptUndoNode *unode = sculpt_undo_get_node(node); + origco = (unode) ? unode->co : NULL; + use_origco = origco ? true : false; + } + } + + if (BKE_pbvh_node_find_nearest_to_ray( + srd->ss->pbvh, node, origco, use_origco, + srd->ray_start, srd->ray_normal, + &srd->depth, &srd->dist_sq_to_ray)) { srd->hit = 1; - *tmin = srd->dist; + *tmin = srd->dist_sq_to_ray; } } } @@ -4450,10 +4487,10 @@ static void sculpt_raycast_detail_cb(PBVHNode *node, void *data_v, float *tmin) if (BKE_pbvh_node_get_tmin(node) < *tmin) { SculptDetailRaycastData *srd = data_v; if (BKE_pbvh_bmesh_node_raycast_detail(node, srd->ray_start, srd->ray_normal, - &srd->dist, &srd->detail)) + &srd->depth, &srd->detail)) { srd->hit = 1; - *tmin = srd->dist; + *tmin = srd->depth; } } } @@ -4500,8 +4537,7 @@ bool sculpt_stroke_get_location(bContext *C, float out[3], const float mouse[2]) Object *ob; SculptSession *ss; StrokeCache *cache; - float ray_start[3], ray_end[3], ray_normal[3], dist; - SculptRaycastData srd; + float ray_start[3], ray_end[3], ray_normal[3], depth; bool original; ViewContext vc; @@ -4515,23 +4551,58 @@ bool sculpt_stroke_get_location(bContext *C, float out[3], const float mouse[2]) sculpt_stroke_modifiers_check(C, ob); - dist = sculpt_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); + depth = sculpt_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); - srd.original = original; - srd.ss = ob->sculpt; - srd.hit = 0; - srd.ray_start = ray_start; - srd.ray_normal = ray_normal; - srd.dist = dist; + bool hit = false; + { + SculptRaycastData srd = { + .original = original, + .ss = ob->sculpt, + .hit = 0, + .ray_start = ray_start, + .ray_normal = ray_normal, + .depth = depth, + }; + BKE_pbvh_raycast( + ss->pbvh, sculpt_raycast_cb, &srd, + ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); + } + } - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, - ray_start, ray_normal, srd.original); + if (hit == false) { + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + if (ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { + SculptFindNearestToRayData srd = { + .original = original, + .ss = ob->sculpt, + .hit = 0, + .ray_start = ray_start, + .ray_normal = ray_normal, + .depth = FLT_MAX, + .dist_sq_to_ray = FLT_MAX, + }; + BKE_pbvh_find_nearest_to_ray( + ss->pbvh, sculpt_find_nearest_to_ray_cb, &srd, + ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); + } + } + } - copy_v3_v3(out, ray_normal); - mul_v3_fl(out, srd.dist); - add_v3_v3(out, ray_start); + if (cache && hit) { + copy_v3_v3(cache->true_location, out); + } - return srd.hit; + return hit; } static void sculpt_brush_init_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) @@ -4554,12 +4625,15 @@ static bool sculpt_brush_stroke_init(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + EvaluationContext eval_ctx; SculptSession *ss = CTX_data_active_object(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); int mode = RNA_enum_get(op->ptr, "mode"); bool is_smooth; bool need_mask = false; + CTX_data_eval_ctx(C, &eval_ctx); + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { need_mask = true; } @@ -4568,7 +4642,7 @@ static bool sculpt_brush_stroke_init(bContext *C, wmOperator *op) sculpt_brush_init_tex(scene, sd, ss); is_smooth = sculpt_any_smooth_mode(brush, NULL, mode); - BKE_sculpt_update_mesh_elements(scene, sd, ob, is_smooth, need_mask); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, is_smooth, need_mask); return 1; } @@ -4642,6 +4716,9 @@ static void sculpt_flush_update(bContext *C) ED_region_tag_redraw_partial(ar, &r); } } + + /* 2.8x - avoid full mesh update! */ + BKE_mesh_batch_cache_dirty(ob->data, BKE_MESH_BATCH_DIRTY_SCULPT_COORDS); } /* Returns whether the mouse/stylus is over the mesh (1) @@ -4660,8 +4737,11 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) { /* Don't start the stroke until mouse goes over the mesh. - * note: mouse will only be null when re-executing the saved stroke. */ - if (!mouse || over_mesh(C, op, mouse[0], mouse[1])) { + * note: mouse will only be null when re-executing the saved stroke. + * We have exception for 'exec' strokes since they may not set 'mouse', only 'location', see: T52195. */ + if (((op->flag & OP_IS_INVOKE) == 0) || + (mouse == NULL) || over_mesh(C, op, mouse[0], mouse[1])) + { Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; @@ -4722,7 +4802,7 @@ static void sculpt_stroke_update_step(bContext *C, struct PaintStroke *UNUSED(st * Could be optimized later, but currently don't think it's so * much common scenario. * - * Same applies to the DAG_id_tag_update() invoked from + * Same applies to the DEG_id_tag_update() invoked from * sculpt_flush_update(). */ if (ss->modifiers_active) { @@ -4758,6 +4838,7 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str if (ss->cache) { UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; Brush *brush = BKE_paint_brush(&sd->paint); + BLI_assert(brush == ss->cache->brush); /* const, so we shouldn't change. */ ups->draw_inverted = false; sculpt_stroke_modifiers_check(C, ob); @@ -4768,11 +4849,10 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str brush->mask_tool = ss->cache->saved_mask_brush_tool; } else { - Paint *p = &sd->paint; - BKE_brush_size_set(scene, ss->cache->brush, ss->cache->saved_smooth_size); + BKE_brush_size_set(scene, brush, ss->cache->saved_smooth_size); brush = (Brush *)BKE_libblock_find_name(ID_BR, ss->cache->saved_active_brush_name); if (brush) { - BKE_paint_brush_set(p, brush); + BKE_paint_brush_set(&sd->paint, brush); } } } @@ -4992,10 +5072,13 @@ void sculpt_update_after_dynamic_topology_toggle(bContext *C) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); + EvaluationContext eval_ctx; Sculpt *sd = scene->toolsettings->sculpt; + CTX_data_eval_ctx(C, &eval_ctx); + /* Create the PBVH */ - BKE_sculpt_update_mesh_elements(scene, sd, ob, false, false); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, false, false); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); } @@ -5329,11 +5412,15 @@ static void SCULPT_OT_symmetrize(wmOperatorType *ot) /**** Toggle operator for turning sculpt mode on or off ****/ -static void sculpt_init_session(Scene *scene, Object *ob) +static void sculpt_init_session(const bContext *C, Scene *scene, Object *ob) { + EvaluationContext eval_ctx; + + CTX_data_eval_ctx(C, &eval_ctx); + ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); - BKE_sculpt_update_mesh_elements(scene, scene->toolsettings->sculpt, ob, 0, false); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, scene->toolsettings->sculpt, ob, 0, false); } @@ -5425,7 +5512,7 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) if (ob->sculpt) BKE_sculptsession_free(ob); - sculpt_init_session(scene, ob); + sculpt_init_session(C, scene, ob); /* Mask layer is required */ if (mmd) { @@ -5531,7 +5618,7 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; float size; - float bb_min[3], bb_max[3]; + float bb_min[3], bb_max[3], center[3], dim[3]; int i, totnodes; PBVHNode **nodes; @@ -5543,11 +5630,12 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) for (i = 0; i < totnodes; i++) { BKE_pbvh_node_mark_topology_update(nodes[i]); } - /* get the bounding box, store the size to bb_max and center (zero) to bb_min */ + /* get the bounding box, it's center and size */ BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); - sub_v3_v3(bb_max, bb_min); - zero_v3(bb_min); - size = max_fff(bb_max[0], bb_max[1], bb_max[2]); + add_v3_v3v3(center, bb_min, bb_max); + mul_v3_fl(center, 0.5f); + sub_v3_v3v3(dim, bb_max, bb_min); + size = max_fff(dim[0], dim[1], dim[2]); /* update topology size */ BKE_pbvh_bmesh_detail_size_set(ss->pbvh, 1.0f / sd->constant_detail); @@ -5557,7 +5645,7 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op)) while (BKE_pbvh_bmesh_update_topology( ss->pbvh, PBVH_Collapse | PBVH_Subdivide, - bb_min, NULL, size)) + center, NULL, size, false, false)) { for (i = 0; i < totnodes; i++) BKE_pbvh_node_mark_topology_update(nodes[i]); @@ -5593,7 +5681,7 @@ static void sample_detail(bContext *C, int ss_co[2]) ViewContext vc; Object *ob; Sculpt *sd; - float ray_start[3], ray_end[3], ray_normal[3], dist; + float ray_start[3], ray_end[3], ray_normal[3], depth; SculptDetailRaycastData srd; float mouse[2] = {ss_co[0], ss_co[1]}; view3d_set_viewcontext(C, &vc); @@ -5603,12 +5691,12 @@ static void sample_detail(bContext *C, int ss_co[2]) sculpt_stroke_modifiers_check(C, ob); - dist = sculpt_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, false); + depth = sculpt_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, false); srd.hit = 0; srd.ray_start = ray_start; srd.ray_normal = ray_normal; - srd.dist = dist; + srd.depth = depth; srd.detail = sd->constant_detail; BKE_pbvh_raycast(ob->sculpt->pbvh, sculpt_raycast_detail_cb, &srd, diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 108fe3532e3..aaea13ce5d0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -38,12 +38,15 @@ #include "DNA_key_types.h" #include "BLI_bitmap.h" +#include "BLI_threads.h" + #include "BKE_pbvh.h" struct bContext; struct KeyBlock; struct Object; struct SculptUndoNode; +struct SculptOrigVertData; int sculpt_mode_poll(struct bContext *C); int sculpt_mode_poll_view3d(struct bContext *C); @@ -52,12 +55,12 @@ int sculpt_poll(struct bContext *C); int sculpt_poll_view3d(struct bContext *C); /* Stroke */ -bool sculpt_stroke_get_location(bContext *C, float out[3], const float mouse[2]); +bool sculpt_stroke_get_location(struct bContext *C, float out[3], const float mouse[2]); /* Dynamic topology */ void sculpt_pbvh_clear(Object *ob); void sculpt_dyntopo_node_layers_add(struct SculptSession *ss); -void sculpt_update_after_dynamic_topology_toggle(bContext *C); +void sculpt_update_after_dynamic_topology_toggle(struct bContext *C); void sculpt_dynamic_topology_enable(struct bContext *C); void sculpt_dynamic_topology_disable(struct bContext *C, struct SculptUndoNode *unode); @@ -115,6 +118,227 @@ typedef struct SculptUndoNode { char shapeName[sizeof(((KeyBlock *)0))->name]; } SculptUndoNode; +/* Factor of brush to have rake point following behind +* (could be configurable but this is reasonable default). */ +#define SCULPT_RAKE_BRUSH_FACTOR 0.25f + +struct SculptRakeData { + float follow_dist; + float follow_co[3]; +}; + +/* Single struct used by all BLI_task threaded callbacks, let's avoid adding 10's of those... */ +typedef struct SculptThreadedTaskData { + struct bContext *C; + struct Sculpt *sd; + struct Object *ob; + const struct Brush *brush; + struct PBVHNode **nodes; + int totnode; + + struct VPaint *vp; + struct VPaintData *vpd; + struct WPaintData *wpd; + struct WeightPaintInfo *wpi; + unsigned int *lcol; + struct Mesh *me; + /* For passing generic params. */ + void *custom_data; + + + /* Data specific to some callbacks. */ + /* Note: even if only one or two of those are used at a time, keeping them separated, names help figuring out + * what it is, and memory overhead is ridiculous anyway... */ + float flippedbstrength; + float angle; + float strength; + bool smooth_mask; + bool has_bm_orco; + + struct SculptProjectVector *spvc; + float *offset; + float *grab_delta; + float *cono; + float *area_no; + float *area_no_sp; + float *area_co; + float(*mat)[4]; + float(*vertCos)[3]; + + /* 0=towards view, 1=flipped */ + float(*area_cos)[3]; + float(*area_nos)[3]; + int *count; + + ThreadMutex mutex; + +} SculptThreadedTaskData; + +/*************** Brush testing declarations ****************/ +typedef struct SculptBrushTest { + float radius_squared; + float location[3]; + float dist; + int mirror_symmetry_pass; + + /* For circle (not sphere) projection. */ + float plane_view[4]; + + /* Some tool code uses a plane for it's calculateions. */ + float plane_tool[4]; + + /* View3d clipping - only set rv3d for clipping */ + struct RegionView3D *clip_rv3d; +} SculptBrushTest; + +typedef bool (*SculptBrushTestFn)(SculptBrushTest *test, const float co[3]); + +typedef struct { + struct Sculpt *sd; + struct SculptSession *ss; + float radius_squared; + bool original; +} SculptSearchSphereData; + +typedef struct { + struct Sculpt *sd; + struct SculptSession *ss; + float radius_squared; + bool original; + struct DistRayAABB_Precalc *dist_ray_to_aabb_precalc; +} SculptSearchCircleData; + +void sculpt_brush_test_init(struct SculptSession *ss, SculptBrushTest *test); +bool sculpt_brush_test_sphere(SculptBrushTest *test, const float co[3]); +bool sculpt_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]); +bool sculpt_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]); +bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], float local[4][4]); +bool sculpt_brush_test_circle_sq(SculptBrushTest *test, const float co[3]); +bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v); +bool sculpt_search_circle_cb(PBVHNode *node, void *data_v); + +SculptBrushTestFn sculpt_brush_test_init_with_falloff_shape( + SculptSession *ss, SculptBrushTest *test, char falloff_shape); +const float *sculpt_brush_frontface_normal_from_falloff_shape( + SculptSession *ss, char falloff_shape); + +float tex_strength( + struct SculptSession *ss, const struct Brush *br, + const float point[3], + const float len, + const short vno[3], + const float fno[3], + const float mask, + const int thread_id); + +/* just for vertex paint. */ +void sculpt_pbvh_calc_area_normal( + const struct Brush *brush, Object *ob, + PBVHNode **nodes, int totnode, + bool use_threading, + float r_area_no[3]); + +/* Cache stroke properties. Used because +* RNA property lookup isn't particularly fast. +* +* For descriptions of these settings, check the operator properties. +*/ + +typedef struct StrokeCache { + /* Invariants */ + float initial_radius; + float scale[3]; + int flag; + float clip_tolerance[3]; + float initial_mouse[2]; + + /* Variants */ + float radius; + float radius_squared; + float true_location[3]; + float true_last_location[3]; + float location[3]; + float last_location[3]; + bool is_last_valid; + + bool pen_flip; + bool invert; + float pressure; + float mouse[2]; + float bstrength; + float normal_weight; /* from brush (with optional override) */ + + /* The rest is temporary storage that isn't saved as a property */ + + bool first_time; /* Beginning of stroke may do some things special */ + + /* from ED_view3d_ob_project_mat_get() */ + float projection_mat[4][4]; + + /* Clean this up! */ + struct ViewContext *vc; + const struct Brush *brush; + + float special_rotation; + float grab_delta[3], grab_delta_symmetry[3]; + float old_grab_location[3], orig_grab_location[3]; + + /* screen-space rotation defined by mouse motion */ + float rake_rotation[4], rake_rotation_symmetry[4]; + bool is_rake_rotation_valid; + struct SculptRakeData rake_data; + + /* Symmetry index between 0 and 7 bit combo 0 is Brush only; + * 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ + int symmetry; + int mirror_symmetry_pass; /* the symmetry pass we are currently on between 0 and 7*/ + float true_view_normal[3]; + float view_normal[3]; + + /* sculpt_normal gets calculated by calc_sculpt_normal(), then the + * sculpt_normal_symm gets updated quickly with the usual symmetry + * transforms */ + float sculpt_normal[3]; + float sculpt_normal_symm[3]; + + /* Used for area texture mode, local_mat gets calculated by + * calc_brush_local_mat() and used in tex_strength(). */ + float brush_local_mat[4][4]; + + float plane_offset[3]; /* used to shift the plane around when doing tiled strokes */ + int tile_pass; + + float last_center[3]; + int radial_symmetry_pass; + float symm_rot_mat[4][4]; + float symm_rot_mat_inv[4][4]; + bool original; + float anchored_location[3]; + + float vertex_rotation; /* amount to rotate the vertices when using rotate brush */ + struct Dial *dial; + + char saved_active_brush_name[MAX_ID_NAME]; + char saved_mask_brush_tool; + int saved_smooth_size; /* smooth tool copies the size of the current tool */ + bool alt_smooth; + + float plane_trim_squared; + + bool supports_gravity; + float true_gravity_direction[3]; + float gravity_direction[3]; + + rcti previous_r; /* previous redraw rectangle */ + rcti current_r; /* current redraw rectangle */ + +} StrokeCache; + +void sculpt_cache_calc_brushdata_symm( + StrokeCache *cache, const char symm, + const char axis, const float angle); +void sculpt_cache_free(StrokeCache *cache); + SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType type); SculptUndoNode *sculpt_undo_get_node(PBVHNode *node); void sculpt_undo_push_begin(const char *name); @@ -124,6 +348,8 @@ void sculpt_vertcos_to_key(Object *ob, KeyBlock *kb, float (*vertCos)[3]); void sculpt_update_object_bounding_box(struct Object *ob); +bool sculpt_get_redraw_rect(struct ARegion *ar, struct RegionView3D *rv3d, Object *ob, rcti *rect); + #define SCULPT_THREADED_LIMIT 4 #endif diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c index 9d55cc1369e..a10c7477dc6 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.c +++ b/source/blender/editors/sculpt_paint/sculpt_undo.c @@ -127,9 +127,12 @@ static bool sculpt_undo_restore_coords(bContext *C, DerivedMesh *dm, SculptUndoN Scene *scene = CTX_data_scene(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Object *ob = CTX_data_active_object(C); + EvaluationContext eval_ctx; SculptSession *ss = ob->sculpt; MVert *mvert; int *index; + + CTX_data_eval_ctx(C, &eval_ctx); if (unode->maxvert) { /* regular mesh restore */ @@ -143,7 +146,7 @@ static bool sculpt_undo_restore_coords(bContext *C, DerivedMesh *dm, SculptUndoN if (kb) { ob->shapenr = BLI_findindex(&key->block, kb) + 1; - BKE_sculpt_update_mesh_elements(scene, sd, ob, 0, false); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, 0, false); WM_event_add_notifier(C, NC_OBJECT | ND_DATA, ob); } else { @@ -451,6 +454,7 @@ static void sculpt_undo_restore(bContext *C, ListBase *lb) Scene *scene = CTX_data_scene(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Object *ob = CTX_data_active_object(C); + EvaluationContext eval_ctx; DerivedMesh *dm; SculptSession *ss = ob->sculpt; SculptUndoNode *unode; @@ -458,6 +462,8 @@ static void sculpt_undo_restore(bContext *C, ListBase *lb) bool need_mask = false; bool partial_update = true; + CTX_data_eval_ctx(C, &eval_ctx); + for (unode = lb->first; unode; unode = unode->next) { if (STREQ(unode->idname, ob->id.name)) { if (unode->type == SCULPT_UNDO_MASK) { @@ -469,10 +475,10 @@ static void sculpt_undo_restore(bContext *C, ListBase *lb) } } - BKE_sculpt_update_mesh_elements(scene, sd, ob, 0, need_mask); + BKE_sculpt_update_mesh_elements(&eval_ctx, scene, sd, ob, 0, need_mask); /* call _after_ sculpt_update_mesh_elements() which may update 'ob->derivedFinal' */ - dm = mesh_get_derived_final(scene, ob, 0); + dm = mesh_get_derived_final(&eval_ctx, scene, ob, 0); if (lb->first && sculpt_undo_bmesh_restore(C, lb->first, ob, ss)) return; diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.c b/source/blender/editors/sculpt_paint/sculpt_uv.c index ee6f25ee44e..14e82e94d30 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.c +++ b/source/blender/editors/sculpt_paint/sculpt_uv.c @@ -219,7 +219,7 @@ static void brush_drawcursor_uvsculpt(bContext *C, int x, int y, void *UNUSED(cu glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); - imm_draw_circle_wire(pos, (float)x, (float)y, size, 40); + imm_draw_circle_wire_2d(pos, (float)x, (float)y, size, 40); glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); |