From 29645a58384273512560c6b27eb1c979ea514682 Mon Sep 17 00:00:00 2001 From: Darshan Kadu Date: Thu, 1 Jun 2017 14:04:06 +0530 Subject: applied patch D2150.id8618.diff and fixed versioning_270.c --- p1.orig | 0 p1.rej | 3708 ++++++++++++++++++++ release/datafiles/locale | 2 +- release/scripts/addons | 2 +- release/scripts/addons_contrib | 2 +- .../scripts/startup/bl_ui/space_view3d_toolbar.py | 48 +- source/blender/blenkernel/BKE_paint.h | 20 + source/blender/blenkernel/BKE_pbvh.h | 6 +- source/blender/blenkernel/intern/CCGSubSurf.c | 1 + source/blender/blenkernel/intern/DerivedMesh.c | 2 +- source/blender/blenkernel/intern/cdderivedmesh.c | 5 + source/blender/blenkernel/intern/object.c | 5 +- source/blender/blenkernel/intern/paint.c | 29 +- source/blender/blenkernel/intern/pbvh.c | 19 +- source/blender/blenkernel/intern/pbvh_intern.h | 2 + source/blender/blenkernel/intern/subsurf_ccg.c | 19 +- source/blender/blenloader/intern/versioning_270.c | 18 + .../blenloader/intern/versioning_defaults.c | 10 + source/blender/editors/sculpt_paint/paint_image.c | 22 +- source/blender/editors/sculpt_paint/paint_intern.h | 1 + source/blender/editors/sculpt_paint/paint_ops.c | 1 + source/blender/editors/sculpt_paint/paint_vertex.c | 1888 +++++++--- source/blender/editors/sculpt_paint/sculpt.c | 259 +- .../blender/editors/sculpt_paint/sculpt_intern.h | 231 ++ source/blender/makesdna/DNA_brush_types.h | 4 +- source/blender/makesdna/DNA_object_types.h | 3 + source/blender/makesdna/DNA_scene_types.h | 3 + source/blender/makesrna/intern/rna_brush.c | 2 + source/blender/makesrna/intern/rna_sculpt_paint.c | 9 + source/tools | 2 +- 30 files changed, 5591 insertions(+), 732 deletions(-) create mode 100644 p1.orig create mode 100644 p1.rej diff --git a/p1.orig b/p1.orig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/p1.rej b/p1.rej new file mode 100644 index 00000000000..7461baad21d --- /dev/null +++ b/p1.rej @@ -0,0 +1,3708 @@ +--- space_view3d_toolbar.py ++++ space_view3d_toolbar.py +@@ -51,6 +51,19 @@ def draw_keyframing_tools(context, layout): + row.operator("anim.keyframe_delete_v3d", text="Remove") + + ++# Used by vertex & weight paint ++def draw_vpaint_symmetry(layout, vpaint): ++ col = layout.column(align=True) ++ col.label(text="Mirror:") ++ row = col.row(align=True) ++ ++ row.prop(vpaint, "use_symmetry_x", text="X", toggle=True) ++ row.prop(vpaint, "use_symmetry_y", text="Y", toggle=True) ++ row.prop(vpaint, "use_symmetry_z", text="Z", toggle=True) ++ ++ col = layout.column() ++ col.prop(vpaint, "radial_symmetry", text="Radial") ++ + # ********** default tools for object-mode **************** + + +@@ -1132,7 +1145,11 @@ class VIEW3D_PT_tools_brush(Panel, View3DPaintPanel): + self.prop_unified_color_picker(col, context, brush, "color", value_slider=True) + if settings.palette: + col.template_palette(settings, "palette", color=True) +- self.prop_unified_color(col, context, brush, "color", text="") ++ row = col.row(align=True) ++ self.prop_unified_color(row, context, brush, "color", text="") ++ self.prop_unified_color(row, context, brush, "secondary_color", text="") ++ row.separator() ++ row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="") + + col.separator() + row = col.row(align=True) +@@ -1713,6 +1730,19 @@ class VIEW3D_PT_tools_weightpaint(View3DPanel, Panel): + props.data_type = 'VGROUP_WEIGHTS' + + ++class VIEW3D_PT_tools_weightpaint_symmetry(Panel, View3DPaintPanel): ++ bl_category = "Tools" ++ bl_context = "weightpaint" ++ bl_options = {'DEFAULT_CLOSED'} ++ bl_label = "Symmetry" ++ ++ def draw(self, context): ++ layout = self.layout ++ toolsettings = context.tool_settings ++ wpaint = toolsettings.weight_paint ++ draw_vpaint_symmetry(layout, wpaint) ++ ++ + class VIEW3D_PT_tools_weightpaint_options(Panel, View3DPaintPanel): + bl_category = "Options" + bl_context = "weightpaint" +@@ -1775,6 +1805,20 @@ class VIEW3D_PT_tools_vertexpaint(Panel, View3DPaintPanel): + #~ col.label(text="Multiply:") + #~ col.prop(vpaint, "mul", text="") + ++ ++class VIEW3D_PT_tools_vertexpaint_symmetry(Panel, View3DPaintPanel): ++ bl_category = "Tools" ++ bl_context = "vertexpaint" ++ bl_options = {'DEFAULT_CLOSED'} ++ bl_label = "Symmetry" ++ ++ def draw(self, context): ++ layout = self.layout ++ toolsettings = context.tool_settings ++ vpaint = toolsettings.vertex_paint ++ draw_vpaint_symmetry(layout, vpaint) ++ ++ + # ********** default tools for texture-paint **************** + + +@@ -2054,8 +2098,10 @@ classes = ( + VIEW3D_PT_sculpt_symmetry, + VIEW3D_PT_tools_brush_appearance, + VIEW3D_PT_tools_weightpaint, ++ VIEW3D_PT_tools_weightpaint_symmetry, + VIEW3D_PT_tools_weightpaint_options, + VIEW3D_PT_tools_vertexpaint, ++ VIEW3D_PT_tools_vertexpaint_symmetry, + VIEW3D_PT_tools_imagepaint_external, + VIEW3D_PT_tools_imagepaint_symmetry, + VIEW3D_PT_tools_projectpaint, +--- BKE_paint.h ++++ BKE_paint.h +@@ -201,10 +201,30 @@ typedef struct SculptSession { + + struct SculptStroke *stroke; + struct StrokeCache *cache; ++ ++ union { ++ struct { ++ int *vert_map_mem; ++ struct MeshElemMap *vert_to_loop; ++ int *poly_map_mem; ++ struct MeshElemMap *vert_to_poly; ++ ++ unsigned int (*total_color)[3]; ++ double *total_weight; ++ unsigned int *tot_loops_hit; ++ float *max_weight; ++ unsigned int *previous_color; ++ bool building_vp_handle; ++ } vwpaint; ++ //struct { ++ //ToDo: identify sculpt-only fields ++ //} sculpt; ++ } modes; + } SculptSession; + + void BKE_sculptsession_free(struct Object *ob); + void BKE_sculptsession_free_deformMats(struct SculptSession *ss); ++void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss); + void BKE_sculptsession_bm_to_me(struct Object *ob, bool reorder); + void BKE_sculptsession_bm_to_me_for_render(struct Object *object); + void BKE_sculpt_update_mesh_elements(struct Scene *scene, struct Sculpt *sd, struct Object *ob, +--- BKE_pbvh.h ++++ BKE_pbvh.h +@@ -32,6 +32,7 @@ + + struct CCGElem; + struct CCGKey; ++struct CCGDerivedMesh; + struct CustomData; + struct DMFlagMat; + struct MPoly; +@@ -71,7 +72,7 @@ void BKE_pbvh_build_grids(PBVH *bvh, struct CCGElem **grid_elems, + struct CCGKey *key, void **gridfaces, struct DMFlagMat *flagmats, + unsigned int **grid_hidden); + void BKE_pbvh_build_bmesh(PBVH *bvh, struct BMesh *bm, bool smooth_shading, struct BMLog *log, const int cd_vert_node_offset, const int cd_face_node_offset); +- ++void BKE_pbvh_add_ccgdm(PBVH *bvh, struct CCGDerivedMesh *ccgdm); + void BKE_pbvh_free(PBVH *bvh); + void BKE_pbvh_free_layer_disp(PBVH *bvh); + +@@ -118,6 +119,7 @@ void BKE_pbvh_raycast_project_ray_root( + void BKE_pbvh_node_draw(PBVHNode *node, void *data); + void BKE_pbvh_draw(PBVH *bvh, float (*planes)[4], float (*face_nors)[3], + int (*setMaterial)(int matnr, void *attribs), bool wireframe, bool fast); ++void BKE_pbvh_draw_BB(PBVH *bvh); + + /* PBVH Access */ + typedef enum { +@@ -141,6 +143,7 @@ int BKE_pbvh_count_grid_quads(BLI_bitmap **grid_hidden, + + /* multires level, only valid for type == PBVH_GRIDS */ + void BKE_pbvh_get_grid_key(const PBVH *pbvh, struct CCGKey *key); ++struct CCGDerivedMesh *BKE_pbvh_get_ccgdm(const PBVH *bvh); + + /* Only valid for type == PBVH_BMESH */ + struct BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh); +@@ -189,6 +192,7 @@ void BKE_pbvh_node_num_verts( + void BKE_pbvh_node_get_verts( + PBVH *bvh, PBVHNode *node, + const int **r_vert_indices, struct MVert **r_verts); ++void BKE_pbvh_get_num_nodes(const PBVH *bvh, int *r_totnode); + + void BKE_pbvh_node_get_BB(PBVHNode *node, float bb_min[3], float bb_max[3]); + void BKE_pbvh_node_get_original_BB(PBVHNode *node, float bb_min[3], float bb_max[3]); +--- CCGSubSurf.c ++++ CCGSubSurf.c +@@ -1196,6 +1196,7 @@ int ccgSubSurf_getNumEdges(const CCGSubSurf *ss) + } + int ccgSubSurf_getNumFaces(const CCGSubSurf *ss) + { ++ + return ss->fMap->numEntries; + } + +--- DerivedMesh.c ++++ DerivedMesh.c +@@ -2641,7 +2641,7 @@ static void mesh_build_data( + ob->lastDataMask = dataMask; + ob->lastNeedMapping = need_mapping; + +- if ((ob->mode & OB_MODE_SCULPT) && ob->sculpt) { ++ if ((ob->mode & OB_MODE_ALL_SCULPT) && ob->sculpt) { + /* create PBVH immediately (would be created on the fly too, + * but this avoids waiting on first stroke) */ + +--- cdderivedmesh.c ++++ cdderivedmesh.c +@@ -660,6 +660,11 @@ static void cdDM_drawMappedFaces( + + const int *index_mp_to_orig = dm->getPolyDataArray(dm, CD_ORIGINDEX); + ++ if (cddm->pbvh) { ++ if (G.debug_value == 14) ++ BKE_pbvh_draw_BB(cddm->pbvh); ++ } ++ + /* fist, setup common buffers */ + GPU_vertex_setup(dm); + GPU_triangle_setup(dm); +--- object.c ++++ object.c +@@ -2679,7 +2679,7 @@ void BKE_object_sculpt_modifiers_changed(Object *ob) + { + SculptSession *ss = ob->sculpt; + +- if (ss) { ++ if (ss && ss->modes.vwpaint.building_vp_handle == false) { + if (!ss->cache) { + /* we free pbvh on changes, except during sculpt since it can't deal with + * changing PVBH node organization, we hope topology does not change in +@@ -2690,6 +2690,9 @@ void BKE_object_sculpt_modifiers_changed(Object *ob) + } + + BKE_sculptsession_free_deformMats(ob->sculpt); ++ ++ /* In vertex/weight paint, force maps to be rebuilt. */ ++ BKE_sculptsession_free_vwpaint_data(ob->sculpt); + } + else { + PBVHNode **nodes; +--- paint.c ++++ paint.c +@@ -656,6 +656,22 @@ void BKE_sculptsession_free_deformMats(SculptSession *ss) + MEM_SAFE_FREE(ss->deform_imats); + } + ++void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss) ++{ ++ /* Free maps */ ++ MEM_SAFE_FREE(ss->modes.vwpaint.vert_to_loop); ++ MEM_SAFE_FREE(ss->modes.vwpaint.vert_map_mem); ++ MEM_SAFE_FREE(ss->modes.vwpaint.vert_to_poly); ++ MEM_SAFE_FREE(ss->modes.vwpaint.poly_map_mem); ++ ++ /* Free average, blur, and spray brush arrays */ ++ MEM_SAFE_FREE(ss->modes.vwpaint.tot_loops_hit); ++ MEM_SAFE_FREE(ss->modes.vwpaint.total_color); ++ MEM_SAFE_FREE(ss->modes.vwpaint.total_weight); ++ MEM_SAFE_FREE(ss->modes.vwpaint.max_weight); ++ MEM_SAFE_FREE(ss->modes.vwpaint.previous_color); ++} ++ + /* Write out the sculpt dynamic-topology BMesh to the Mesh */ + static void sculptsession_bm_to_me_update_data_only(Object *ob, bool reorder) + { +@@ -697,10 +713,7 @@ void BKE_sculptsession_bm_to_me_for_render(Object *object) + */ + BKE_object_free_derived_caches(object); + +- if (object->sculpt->pbvh) { +- BKE_pbvh_free(object->sculpt->pbvh); +- object->sculpt->pbvh = NULL; +- } ++ MEM_SAFE_FREE(object->sculpt->pbvh); + + sculptsession_bm_to_me_update_data_only(object, false); + +@@ -747,6 +760,8 @@ void BKE_sculptsession_free(Object *ob) + if (ss->deform_imats) + MEM_freeN(ss->deform_imats); + ++ BKE_sculptsession_free_vwpaint_data(ob->sculpt); ++ + MEM_freeN(ss); + + ob->sculpt = NULL; +@@ -831,6 +846,9 @@ void BKE_sculpt_update_mesh_elements(Scene *scene, Sculpt *sd, Object *ob, + ss->modifiers_active = sculpt_modifiers_active(scene, sd, ob); + ss->show_diffuse_color = (sd->flags & SCULPT_SHOW_DIFFUSE) != 0; + ++ /* This flag prevents PBVH from being freed when creating the vp_handle for texture paint */ ++ ss->modes.vwpaint.building_vp_handle = false; ++ + if (need_mask) { + if (mmd == NULL) { + if (!CustomData_has_layer(&me->vdata, CD_PAINT_MASK)) { +@@ -859,7 +877,8 @@ void BKE_sculpt_update_mesh_elements(Scene *scene, Sculpt *sd, Object *ob, + + dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); + +- if (mmd) { ++ /* VWPaint require mesh info for loop lookup, so require sculpt mode here */ ++ if (mmd && ob->mode & OB_MODE_SCULPT) { + ss->multires = mmd; + ss->totvert = dm->getNumVerts(dm); + ss->totpoly = dm->getNumPolys(dm); +--- pbvh.c ++++ pbvh.c +@@ -34,6 +34,7 @@ + + #include "BKE_pbvh.h" + #include "BKE_ccg.h" ++#include "BKE_subsurf.h" + #include "BKE_DerivedMesh.h" + #include "BKE_global.h" + #include "BKE_mesh.h" /* for BKE_mesh_calc_normals */ +@@ -606,6 +607,10 @@ void BKE_pbvh_build_grids(PBVH *bvh, CCGElem **grids, + MEM_freeN(prim_bbc); + } + ++void BKE_pbvh_add_ccgdm(PBVH *bvh, CCGDerivedMesh *ccgdm) { ++ bvh->ccgdm = ccgdm; ++} ++ + PBVH *BKE_pbvh_new(void) + { + PBVH *bvh = MEM_callocN(sizeof(PBVH), "pbvh"); +@@ -1156,7 +1161,7 @@ static void pbvh_update_draw_buffers(PBVH *bvh, PBVHNode **nodes, int totnode) + } + } + +-static void pbvh_draw_BB(PBVH *bvh) ++void BKE_pbvh_draw_BB(PBVH *bvh) + { + GPU_pbvh_BB_draw_init(); + +@@ -1329,6 +1334,11 @@ void BKE_pbvh_get_grid_key(const PBVH *bvh, CCGKey *key) + *key = bvh->gridkey; + } + ++CCGDerivedMesh *BKE_pbvh_get_ccgdm(const PBVH *bvh) { ++ return bvh->ccgdm; ++} ++ ++ + BMesh *BKE_pbvh_get_bmesh(PBVH *bvh) + { + BLI_assert(bvh->type == PBVH_BMESH); +@@ -1405,6 +1415,11 @@ void BKE_pbvh_node_num_verts( + } + } + ++void BKE_pbvh_get_num_nodes(const PBVH *bvh, int *r_totnode) ++{ ++ *r_totnode = bvh->totnode; ++} ++ + void BKE_pbvh_node_get_grids( + PBVH *bvh, PBVHNode *node, + int **r_grid_indices, int *r_totgrid, int *r_maxgrid, int *r_gridsize, CCGElem ***r_griddata) +@@ -1860,7 +1875,7 @@ void BKE_pbvh_draw(PBVH *bvh, float (*planes)[4], float (*fnors)[3], + } + + if (G.debug_value == 14) +- pbvh_draw_BB(bvh); ++ BKE_pbvh_draw_BB(bvh); + } + + void BKE_pbvh_grids_update(PBVH *bvh, CCGElem **grids, void **gridfaces, +--- pbvh_intern.h ++++ pbvh_intern.h +@@ -149,6 +149,8 @@ struct PBVH { + * objects in sculpt mode with different sizes at the same time, so now storing that common gpu buffer + * in an opaque pointer per pbvh. See T47637. */ + struct GridCommonGPUBuffer *grid_common_gpu_buffer; ++ /* The ccgdm is required for CD_ORIGINDEX lookup in vertex paint + multires */ ++ struct CCGDerivedMesh *ccgdm; + + /* Only used during BVH build and update, + * don't need to remain valid after */ +--- subsurf_ccg.c ++++ subsurf_ccg.c +@@ -3681,6 +3681,11 @@ static void ccgDM_drawMappedFaces(DerivedMesh *dm, + int gridFaces = gridSize - 1, totface; + int prev_mat_nr = -1; + ++ if (ccgdm->pbvh) { ++ if (G.debug_value == 14) ++ BKE_pbvh_draw_BB(ccgdm->pbvh); ++ } ++ + #ifdef WITH_OPENSUBDIV + if (ccgdm->useGpuBackend) { + int new_matnr; +@@ -4414,7 +4419,8 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) + if (!ob->sculpt) + return NULL; + +- grid_pbvh = ccgDM_use_grid_pbvh(ccgdm); ++ /* In vwpaint, we always use a grid_pbvh for multires/subsurf */ ++ grid_pbvh = (!(ob->mode & OB_MODE_SCULPT) || ccgDM_use_grid_pbvh(ccgdm)); + + if (ob->sculpt->pbvh) { + if (grid_pbvh) { +@@ -4430,12 +4436,17 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) + ccgdm->pbvh = ob->sculpt->pbvh; + } + +- if (ccgdm->pbvh) ++ if (ccgdm->pbvh) { ++ /* For vertex paint, keep track of ccgdm */ ++ if (!(ob->mode & OB_MODE_SCULPT)) ++ BKE_pbvh_add_ccgdm(ccgdm->pbvh, ccgdm); + return ccgdm->pbvh; ++ } + + /* no pbvh exists yet, we need to create one. only in case of multires + * we build a pbvh over the modified mesh, in other cases the base mesh + * is being sculpted, so we build a pbvh from that. */ ++ /* Note: vwpaint always builds a pbvh over the modified mesh. */ + if (grid_pbvh) { + ccgdm_create_grids(dm); + +@@ -4466,6 +4477,10 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) + if (ccgdm->pbvh) + pbvh_show_diffuse_color_set(ccgdm->pbvh, ob->sculpt->show_diffuse_color); + ++ /* For vertex paint, keep track of ccgdm */ ++ if (!(ob->mode & OB_MODE_SCULPT) && ccgdm->pbvh) ++ BKE_pbvh_add_ccgdm(ccgdm->pbvh, ccgdm); ++ + return ccgdm->pbvh; + } + +--- versioning_270.c ++++ versioning_270.c +@@ -60,6 +60,7 @@ + #include "DNA_genfile.h" + + #include "BKE_animsys.h" ++#include "BKE_brush.h" + #include "BKE_colortools.h" + #include "BKE_library.h" + #include "BKE_main.h" +@@ -1645,6 +1646,23 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) + } + } + ++ { ++ Brush *br; ++ br = (Brush *)BKE_libblock_find_name_ex(main, ID_BR, "Average"); ++ if (!br) { ++ br = BKE_brush_add(main, "Average", OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT); ++ br->vertexpaint_tool = PAINT_BLEND_AVERAGE; ++ br->ob_mode = OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT; ++ } ++ ++ br = (Brush *)BKE_libblock_find_name_ex(main, ID_BR, "Smear"); ++ if (!br) { ++ br = BKE_brush_add(main, "Smear", OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT); ++ br->vertexpaint_tool = PAINT_BLEND_SMEAR; ++ br->ob_mode = OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT; ++ } ++ } ++ + FOREACH_NODETREE(main, ntree, id) { + if (ntree->type == NTREE_COMPOSIT) { + do_versions_compositor_render_passes(ntree); +--- versioning_defaults.c ++++ versioning_defaults.c +@@ -106,6 +106,16 @@ void BLO_update_defaults_startup_blend(Main *bmain) + sculpt->detail_size = 12; + } + ++ if (ts->vpaint) { ++ VPaint *vp = ts->vpaint; ++ vp->radial_symm[0] = vp->radial_symm[1] = vp->radial_symm[2] = 1; ++ } ++ ++ if (ts->wpaint) { ++ VPaint *wp = ts->wpaint; ++ wp->radial_symm[0] = wp->radial_symm[1] = wp->radial_symm[2] = 1; ++ } ++ + if (ts->gp_sculpt.brush[0].size == 0) { + GP_BrushEdit_Settings *gset = &ts->gp_sculpt; + GP_EditBrush_Data *brush; +--- paint_image.c ++++ paint_image.c +@@ -1448,7 +1448,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); + } +@@ -1467,7 +1480,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; + } + +--- paint_intern.h ++++ paint_intern.h +@@ -54,6 +54,7 @@ struct wmOperator; + struct wmOperatorType; + struct wmWindowManager; + struct DMCoNo; ++struct MeshElemMap; + enum PaintMode; + + /* paint_stroke.c */ +--- paint_ops.c ++++ paint_ops.c +@@ -1615,6 +1615,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, +--- paint_vertex.c ++++ paint_vertex.c +@@ -35,7 +35,7 @@ + #include "BLI_math.h" + #include "BLI_array_utils.h" + #include "BLI_bitmap.h" +-#include "BLI_stack.h" ++#include "BLI_task.h" + #include "BLI_string_utils.h" + + #include "IMB_imbuf.h" +@@ -66,6 +66,7 @@ + #include "BKE_paint.h" + #include "BKE_report.h" + #include "BKE_colortools.h" ++#include "BKE_subsurf.h" + + #include "WM_api.h" + #include "WM_types.h" +@@ -76,13 +77,11 @@ + #include "ED_screen.h" + #include "ED_view3d.h" + +-#include "paint_intern.h" /* own include */ ++#include "bmesh.h" ++#include "BKE_ccg.h" + +-/* small structure to defer applying weight-paint results */ +-struct WPaintDefer { +- int index; +- float alpha, weight; +-}; ++#include "sculpt_intern.h" ++#include "paint_intern.h" /* own include */ + + /* check if we can do partial updates and have them draw realtime + * (without rebuilding the 'derivedFinal') */ +@@ -174,11 +173,6 @@ static VPaint *new_vpaint(int wpaint) + 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) + { + Brush *brush = BKE_paint_brush(&vp->paint); +@@ -191,7 +185,7 @@ unsigned int vpaint_get_current_col(Scene *scene, VPaint *vp) + static void do_shared_vertexcol(Mesh *me, bool *mlooptag) + { + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; +- MPoly *mp; ++ const MPoly *mp; + int (*scol)[4]; + int i, j; + bool has_shared = false; +@@ -205,7 +199,7 @@ static void do_shared_vertexcol(Mesh *me, bool *mlooptag) + + 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; ++ 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; +@@ -228,7 +222,7 @@ static void do_shared_vertexcol(Mesh *me, bool *mlooptag) + + 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; ++ 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]) { +@@ -292,15 +286,6 @@ static int wpaint_mirror_vgroup_ensure(Object *ob, const int vgroup_active) + 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) { +@@ -310,19 +295,6 @@ static void free_wpaint_prev(VPaint *vp) + } + } + +-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); +@@ -338,9 +310,8 @@ static void copy_wpaint_prev(VPaint *wp, MDeformVert *dverts, int dcount) + bool ED_vpaint_fill(Object *ob, unsigned int paintcol) + { + Mesh *me; +- MPoly *mp; ++ const MPoly *mp; + int i, j; +- bool selected; + + if (((me = BKE_mesh_from_object(ob)) == NULL) || + (me->mloopcol == NULL && (make_vertexcol(ob) == false))) +@@ -348,13 +319,13 @@ bool ED_vpaint_fill(Object *ob, unsigned int paintcol) + return false; + } + +- selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ const bool use_face_sel = (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)) ++ if (use_face_sel && !(mp->flag & ME_FACE_SEL)) + continue; + + for (j = 0; j < mp->totloop; j++, lcol++) { +@@ -375,7 +346,7 @@ bool ED_vpaint_fill(Object *ob, unsigned int paintcol) + bool ED_wpaint_fill(VPaint *wp, Object *ob, float paintweight) + { + Mesh *me = ob->data; +- MPoly *mp; ++ const MPoly *mp; + MDeformWeight *dw, *dw_prev; + int vgroup_active, vgroup_mirror = -1; + unsigned int index; +@@ -458,12 +429,11 @@ bool ED_wpaint_fill(VPaint *wp, Object *ob, float paintweight) + bool ED_vpaint_smooth(Object *ob) + { + Mesh *me; +- MPoly *mp; ++ const MPoly *mp; + + int i, j; + + bool *mlooptag; +- bool selected; + + if (((me = BKE_mesh_from_object(ob)) == NULL) || + (me->mloopcol == NULL && (make_vertexcol(ob) == false))) +@@ -471,17 +441,17 @@ bool ED_vpaint_smooth(Object *ob) + return false; + } + +- selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ 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++) { +- MLoop *ml = me->mloop + mp->loopstart; ++ const MLoop *ml = me->mloop + mp->loopstart; + int ml_index = mp->loopstart; + +- if (selected && !(mp->flag & ME_FACE_SEL)) ++ if (use_face_sel && !(mp->flag & ME_FACE_SEL)) + continue; + + for (j = 0; j < mp->totloop; j++, ml_index++, ml++) { +@@ -518,13 +488,13 @@ bool ED_vpaint_color_transform( + return false; + } + +- const bool do_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ 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 (do_face_sel && !(mp->flag & ME_FACE_SEL)) { ++ if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { + continue; + } + +@@ -609,9 +579,18 @@ BLI_INLINE unsigned int mcol_blend(unsigned int col1, unsigned int col2, int fac + 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); ++ /* 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 r2 = cp2[0] * cp2[0]; ++ int g2 = cp2[1] * cp2[1]; ++ int b2 = cp2[2] * cp2[2]; ++ ++ cp[0] = (unsigned char)round(sqrt(divide_round_i((mfac * r1 + fac * r2), 255))); ++ cp[1] = (unsigned char)round(sqrt(divide_round_i((mfac * g1 + fac * g2), 255))); ++ cp[2] = (unsigned char)round(sqrt(divide_round_i((mfac * b1 + fac * b2), 255))); + cp[3] = 255; + + return col; +@@ -764,6 +743,8 @@ static unsigned int vpaint_blend_tool(const int tool, const unsigned int col, + 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); +@@ -776,10 +757,11 @@ static unsigned int vpaint_blend_tool(const int tool, const unsigned int 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 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) + { + Brush *brush = BKE_paint_brush(&vp->paint); + const int tool = brush->vertexpaint_tool; +@@ -813,49 +795,10 @@ static unsigned int vpaint_blend(VPaint *vp, unsigned int col, unsigned int colo + } + + +-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); +- } +- +- return tot; +-} +- + /* 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 */ + +@@ -891,10 +834,11 @@ 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); + +@@ -960,6 +904,8 @@ static float wpaint_blend_tool(const int tool, + { + 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); +@@ -973,9 +919,9 @@ static float wpaint_blend_tool(const int tool, + } + + /* vpaint has 'vpaint_blend' */ +-static float wpaint_blend(VPaint *wp, float weight, float weight_prev, ++static float wpaint_blend(VPaint *wp, float weight, + const float alpha, float paintval, +- const float brush_alpha_value, ++ const float UNUSED(brush_alpha_value), + const short do_flip) + { + Brush *brush = BKE_paint_brush(&wp->paint); +@@ -1000,21 +946,6 @@ static float wpaint_blend(VPaint *wp, float weight, float weight_prev, + + 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); +- +- 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; +- } +- } +- + return weight; + } + +@@ -1164,7 +1095,7 @@ static EnumPropertyItem *weight_paint_sample_enum_itemf( + } + else { + if (ED_mesh_pick_face(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { +- MPoly *mp = &me->mpoly[index]; ++ const MPoly *mp = &me->mpoly[index]; + unsigned int fidx = mp->totloop - 1; + + do { +@@ -1476,7 +1407,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, +@@ -1530,8 +1461,8 @@ static void do_weight_paint_vertex_single( + Mesh *me = ob->data; + MDeformVert *dv = &me->dvert[index]; + bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; +- +- MDeformWeight *dw, *dw_prev; ++ ++ MDeformWeight *dw; + + /* mirror vars */ + int index_mirr; +@@ -1542,14 +1473,12 @@ static void do_weight_paint_vertex_single( + + 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) { ++ if (dw == NULL) { + return; + } + +@@ -1607,7 +1536,7 @@ static void do_weight_paint_vertex_single( + * then there is no need to run the more complicated checks */ + + { +- dw->weight = wpaint_blend(wp, dw->weight, dw_prev->weight, alpha, paintweight, ++ dw->weight = wpaint_blend(wp, dw->weight, alpha, paintweight, + wpi->brush_alpha_value, wpi->do_flip); + + /* WATCH IT: take care of the ordering of applying mirror -> normalize, +@@ -1673,7 +1602,6 @@ static void do_weight_paint_vertex_multi( + { + 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 */ +@@ -1681,7 +1609,7 @@ static void do_weight_paint_vertex_multi( + MDeformVert *dv_mirr = NULL; + + /* weights */ +- float oldw, curw, neww, change, curw_mirr, change_mirr; ++ float curw, 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) { +@@ -1693,8 +1621,6 @@ static void do_weight_paint_vertex_multi( + } + + /* 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); + +@@ -1703,7 +1629,7 @@ static void do_weight_paint_vertex_multi( + return; + } + +- neww = wpaint_blend(wp, curw, oldw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); ++ neww = wpaint_blend(wp, curw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); + + change = neww / curw; + +@@ -1769,6 +1695,59 @@ 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(Scene *scene, Object *ob) ++{ ++ if (ob->sculpt == NULL) { ++ ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); ++ BKE_sculpt_update_mesh_elements(scene, scene->toolsettings->sculpt, ob, 0, false); ++ } ++} ++ ++static void vertex_paint_init_session_maps(Object *ob) ++{ ++ /* Create maps */ ++ if (ob->sculpt->modes.vwpaint.vert_to_loop == NULL) { ++ Mesh *me = ob->data; ++ ob->sculpt->modes.vwpaint.vert_map_mem = NULL; ++ ob->sculpt->modes.vwpaint.vert_to_loop = NULL; ++ ob->sculpt->modes.vwpaint.poly_map_mem = NULL; ++ ob->sculpt->modes.vwpaint.vert_to_poly = NULL; ++ BKE_mesh_vert_loop_map_create( ++ &ob->sculpt->modes.vwpaint.vert_to_loop, ++ &ob->sculpt->modes.vwpaint.vert_map_mem, ++ me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); ++ BKE_mesh_vert_poly_map_create( ++ &ob->sculpt->modes.vwpaint.vert_to_poly, ++ &ob->sculpt->modes.vwpaint.poly_map_mem, ++ me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); ++ } ++} ++ ++static void vertex_paint_init_session_average_arrays(Object *ob) ++{ ++ /* Create average brush arrays */ ++ if (!ob->sculpt->modes.vwpaint.tot_loops_hit) { ++ int totNode = 0; ++ /* I think the totNodes might include internal nodes, and we really only need the tot leaves. */ ++ BKE_pbvh_get_num_nodes(ob->sculpt->pbvh, &totNode); ++ Mesh *me = BKE_mesh_from_object(ob); ++ ++ ob->sculpt->modes.vwpaint.total_color = ++ MEM_callocN(totNode * 3 * sizeof(unsigned int), "total_color"); ++ ob->sculpt->modes.vwpaint.total_weight = ++ MEM_callocN(totNode * sizeof(double), "total_weight"); ++ ob->sculpt->modes.vwpaint.tot_loops_hit = ++ MEM_callocN(totNode * sizeof(unsigned int), "tot_loops_hit"); ++ ob->sculpt->modes.vwpaint.max_weight = ++ MEM_callocN(me->totvert * sizeof(float), "max_weight"); ++ ob->sculpt->modes.vwpaint.previous_color = ++ MEM_callocN(me->totloop * sizeof(unsigned int), "previous_color"); ++ } ++} ++ + /* *************** set wpaint operator ****************** */ + + /** +@@ -1805,6 +1784,14 @@ 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 { +@@ -1820,6 +1807,12 @@ 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(scene, ob); + } + + /* Weightpaint works by overriding colors in mesh, +@@ -1877,7 +1870,6 @@ struct WPaintVGroupIndex { + + struct WPaintData { + ViewContext vc; +- int *indexar; + + struct WeightPaintGroupData active, mirror; + +@@ -1901,8 +1893,6 @@ struct WPaintData { + int *vmap_mem; + } blur_data; + +- BLI_Stack *accumulate_stack; /* for reuse (WPaintDefer) */ +- + int defbase_tot; + }; + +@@ -1982,19 +1972,129 @@ static bool wpaint_ensure_data( + return true; + } + +-static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UNUSED(mouse[2])) ++/* Initialize the stroke cache invariants from operator properties */ ++static void vwpaint_update_cache_invariants( ++ bContext *C, VPaint *vd, 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; ++ Brush *brush = BKE_paint_brush(&vd->paint); ++ ViewContext *vc = paint_stroke_view_context(op->customdata); ++ Object *ob = CTX_data_active_object(C); ++ float mat[3][3]; ++ float view_dir[3] = {0.0f, 0.0f, 1.0f}; ++ int mode; ++ ++ /* VW paint needs to allocate stroke cache before update is called. */ ++ if (!ss->cache) { ++ cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); ++ ss->cache = cache; ++ } ++ else { ++ cache = ss->cache; ++ } ++ ++ /* Initial mouse location */ ++ if (mouse) ++ copy_v2_v2(cache->initial_mouse, mouse); ++ else ++ zero_v2(cache->initial_mouse); ++ ++ 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; ++ ++ 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; ++ ++ /* cache projection matrix */ ++ ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); ++ ++ 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 *vd, Object *ob, PointerRNA *ptr) ++{ ++ Scene *scene = CTX_data_scene(C); ++ SculptSession *ss = ob->sculpt; ++ StrokeCache *cache = ss->cache; ++ Brush *brush = BKE_paint_brush(&vd->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); ++ } ++ ++ 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"); ++ } ++ ++ /* 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); ++ } ++ } ++ ++ 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 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 *vd = CTX_data_tool_settings(C)->wpaint; + + float mat[4][4], imat[4][4]; + +@@ -2094,62 +2194,596 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UN + } + + /* painting on subsurfs should give correct points too, this returns me->totvert amount */ ++ ob->sculpt->modes.vwpaint.building_vp_handle = true; + 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__); +- } ++ ob->sculpt->modes.vwpaint.building_vp_handle = false; + + /* imat for normals */ + mul_m4_m4m4(mat, wpd->vc.rv3d->viewmat, ob->obmat); + 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(scene, ob); ++ vwpaint_update_cache_invariants(C, vd, ss, op, mouse); ++ vertex_paint_init_session_maps(ob); ++ vertex_paint_init_session_average_arrays(ob); ++ ++ for (int i = 0; i < me->totvert; i++) ++ ss->modes.vwpaint.max_weight[i] = -1.0; ++ + return true; + } + +-static float wpaint_blur_weight_single(const MDeformVert *dv, const WeightPaintInfo *wpi) ++static void calc_area_normal_and_center_task_cb(void *userdata, const int n) + { +- return defvert_find_weight(dv, wpi->active.index); ++ SculptThreadedTaskData *data = userdata; ++ SculptSession *ss = data->ob->sculpt; ++ float(*area_nos)[3] = data->area_nos; ++ float(*area_cos)[3] = data->area_cos; ++ ++ float private_co[2][3] = {{0.0f}}; ++ float private_no[2][3] = {{0.0f}}; ++ int private_count[2] = {0}; ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ PBVHVertexIter vd; ++ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) ++ { ++ const float *co; ++ ++ co = vd.co; ++ ++ if (sculpt_brush_test_fast(&test, co)) { ++ float no_buf[3]; ++ const float *no; ++ int flip_index; ++ ++ if (vd.no) { ++ normal_short_to_float_v3(no_buf, vd.no); ++ no = no_buf; ++ } ++ else { ++ no = vd.fno; ++ } ++ ++ flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); ++ if (area_cos) ++ add_v3_v3(private_co[flip_index], co); ++ if (area_nos) ++ add_v3_v3(private_no[flip_index], no); ++ private_count[flip_index] += 1; ++ } ++ } ++ BKE_pbvh_vertex_iter_end; ++ ++ ++ BLI_mutex_lock(&data->mutex); ++ ++ /* for flatten center */ ++ if (area_cos) { ++ add_v3_v3(area_cos[0], private_co[0]); ++ add_v3_v3(area_cos[1], private_co[1]); ++ } ++ ++ /* for area normal */ ++ if (area_nos) { ++ add_v3_v3(area_nos[0], private_no[0]); ++ add_v3_v3(area_nos[1], private_no[1]); ++ } ++ ++ /* weights */ ++ data->count[0] += private_count[0]; ++ data->count[1] += private_count[1]; ++ ++ BLI_mutex_unlock(&data->mutex); + } + +-static float wpaint_blur_weight_multi(const MDeformVert *dv, const WeightPaintInfo *wpi) ++static void calc_area_normal( ++ VPaint *vp, Object *ob, ++ PBVHNode **nodes, int totnode, ++ float r_area_no[3]) + { +- 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; ++ /* 0=towards view, 1=flipped */ ++ float area_nos[2][3] = {{0.0f}}; ++ ++ int count[2] = {0}; ++ ++ SculptThreadedTaskData data = { ++ .vp = vp, .ob = ob, .nodes = nodes, .totnode = totnode, ++ .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, true); ++ ++ BLI_mutex_end(&data.mutex); ++ ++ /* for area normal */ ++ for (int i = 0; i < ARRAY_SIZE(area_nos); i++) { ++ if (normalize_v3_v3(r_area_no, area_nos[i]) != 0.0f) { ++ break; ++ } ++ } ++} ++ ++static float dot_vf3vs3(const float brushNormal[3], const short vertexNormal[3]) ++{ ++ float normal[3]; ++ normal_short_to_float_v3(normal, vertexNormal); ++ return dot_v3v3(brushNormal, normal); ++} ++ ++/* 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( ++ VPaint *vd, StrokeCache *cache, const char symm, ++ const char axis, const float angle) ++{ ++ (void)vd; /* 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); ++ ++ unit_m4(cache->symm_rot_mat); ++ unit_m4(cache->symm_rot_mat_inv); ++ zero_v3(cache->plane_offset); ++ ++ if (axis) { /* expects XYZ */ ++ rotate_m4(cache->symm_rot_mat, axis, angle); ++ rotate_m4(cache->symm_rot_mat_inv, axis, -angle); ++ } ++ ++ mul_m4_v3(cache->symm_rot_mat, cache->location); ++ mul_m4_v3(cache->symm_rot_mat, cache->last_location); ++ mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); ++ ++ if (cache->supports_gravity) { ++ flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); ++ mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); ++ } ++ ++ if (cache->is_rake_rotation_valid) { ++ flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); ++ } ++} ++ ++static void get_brush_alpha_data( ++ Scene *scene, SculptSession *ss, Brush *brush, ++ float *r_brush_size_pressure, float *r_brush_alpha_value, float *r_brush_alpha_pressure) ++{ ++ *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 = ++ *r_brush_alpha_value * ++ (BKE_brush_use_alpha_pressure(scene, brush) ? ss->cache->pressure : 1.0f); + } + +-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 void do_wpaint_brush_blur_task_cb_ex( ++ void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) + { +- 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); ++ SculptThreadedTaskData *data = userdata; ++ SculptSession *ss = data->ob->sculpt; ++ CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); ++ ++ Brush *brush = data->brush; ++ StrokeCache *cache = ss->cache; ++ Scene *scene = CTX_data_scene(data->C); ++ const float brush_strength = cache->bstrength; ++ 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_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; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.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 += defvert_find_weight(dv, data->wpi->active.index); ++ } ++ } ++ ++ /* Apply the weight to the vertex. */ ++ if (total_hit_loops != 0) { ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); ++ const float final_alpha = ++ view_dot * brush_fade * brush_strength * ++ grid_alpha * brush_alpha_pressure; ++ 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); ++ } ++ } ++ } + } +- paintweight /= map->count; + } +- else { +- paintweight = blur_weight_func(&dvert[vidx], wpi); ++ 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); ++ ++ Brush *brush = data->brush; ++ Scene *scene = CTX_data_scene(data->C); ++ StrokeCache *cache = ss->cache; ++ const float brush_strength = cache->bstrength; ++ 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_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); ++ if (normalize_v3(brush_dir) != 0.0f) { ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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_fast(&test, vd.co)) { ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ bool do_color = false; ++ ++ /* 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]; ++ 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) { ++ /* 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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; ++ const MPoly *mp = &data->me->mpoly[p_index]; ++ for (int k = 0; k < mp->totloop; k++) { ++ const unsigned int l_index = mp->loopstart + k; ++ const MLoop *ml = &data->me->mloop[l_index]; ++ const unsigned int v_other_index = ml->v; ++ 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); ++ 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; ++ MDeformVert *dv = &data->me->dvert[v_other_index]; ++ weight_final = defvert_find_weight(dv, data->wpi->active.index); ++ do_color = true; ++ } ++ } ++ } ++ /* Apply weight to vertex */ ++ if (do_color) { ++ const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); ++ const float final_alpha = ++ view_dot * 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); ++ } ++ } ++ } ++ } ++ } ++ 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); ++ Scene *scene = CTX_data_scene(data->C); ++ ++ Brush *brush = data->brush; ++ StrokeCache *cache = ss->cache; ++ const float brush_strength = cache->bstrength; ++ const float paintweight = BKE_brush_weight_get(scene, brush); ++ 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_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; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&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) { ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); ++ float final_alpha = view_dot * brush_fade * brush_strength * grid_alpha * brush_alpha_pressure; ++ ++ /* Spray logic */ ++ if ((data->vp->flag & VP_SPRAY) == 0) { ++ MDeformVert *dv = &data->me->dvert[v_index]; ++ const MDeformWeight *dw; ++ dw = (data->vp->flag & VP_ONLYVGROUP) ? ++ defvert_find_index(dv, data->wpi->active.index) : ++ defvert_verify_index(dv, data->wpi->active.index); ++ const float weight_curr = dw->weight; ++ if (ss->modes.vwpaint.max_weight[v_index] < 0) { ++ ss->modes.vwpaint.max_weight[v_index] = min_ff(brush_strength + weight_curr, 1.0f); ++ } ++ CLAMP(final_alpha, 0.0, ss->modes.vwpaint.max_weight[v_index] - weight_curr); + +- return paintweight; ++ if (weight_curr >= ss->modes.vwpaint.max_weight[v_index]) { ++ 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_ave_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); ++ ++ double weight = 0.0; ++ ++ data->ob->sculpt->modes.vwpaint.tot_loops_hit[n] = 0.0; ++ data->ob->sculpt->modes.vwpaint.total_weight[n] = 0.0; ++ 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; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&test, vd.co)) { ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 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) { ++ ss->modes.vwpaint.tot_loops_hit[n] += ss->modes.vwpaint.vert_to_loop[v_index].count; ++ /* if a vertex is within the brush region, then add it's weight to the total weight. */ ++ for (int j = 0; j < ss->modes.vwpaint.vert_to_loop[v_index].count; j++) { ++ const int l_index = ss->modes.vwpaint.vert_to_loop[v_index].indices[j]; ++ ++ const MLoop *ml = &data->me->mloop[l_index]; ++ const MDeformVert *dv = &data->me->dvert[ml->v]; ++ weight += defvert_find_weight(dv, data->wpi->active.index); ++ } ++ } ++ } ++ } ++ } ++ BKE_pbvh_vertex_iter_end; ++ data->ob->sculpt->modes.vwpaint.total_weight[n] = weight; ++} ++ ++static void calculate_average_weight(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) ++{ ++ Scene *scene = CTX_data_scene(data->C); ++ UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; ++ BLI_task_parallel_range_ex( ++ 0, totnode, data, NULL, 0, do_wpaint_brush_calc_ave_weight_cb_ex, ++ ((data->sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT), false); ++ ++ unsigned int total_hit_loops = 0; ++ double total_weight = 0.0; ++ for (int i = 0; i < totnode; i++) { ++ total_hit_loops += data->ob->sculpt->modes.vwpaint.tot_loops_hit[i]; ++ total_weight += data->ob->sculpt->modes.vwpaint.total_weight[i]; ++ } ++ if (total_hit_loops != 0) { ++ total_weight /= total_hit_loops; ++ if (ups->flag & UNIFIED_PAINT_WEIGHT) ++ ups->weight = (float)total_weight; ++ else ++ data->brush->weight = (float)total_weight; ++ } ++} ++ ++ ++static void wpaint_paint_leaves( ++ bContext *C, Object *ob, Sculpt *sd, VPaint *vp, struct WPaintData *wpd, WeightPaintInfo *wpi, ++ Mesh *me, PBVHNode **nodes, int totnode) ++{ ++ 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, ++ }; ++ ++ 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, true, false); ++ break; ++ case PAINT_BLEND_SMEAR: ++ BLI_task_parallel_range_ex( ++ 0, totnode, &data, NULL, 0, ++ do_wpaint_brush_smear_task_cb_ex, true, false); ++ break; ++ case PAINT_BLEND_BLUR: ++ BLI_task_parallel_range_ex( ++ 0, totnode, &data, NULL, 0, ++ do_wpaint_brush_blur_task_cb_ex, true, false); ++ break; ++ default: ++ BLI_task_parallel_range_ex( ++ 0, totnode, &data, NULL, 0, ++ do_wpaint_brush_draw_task_cb_ex, true, false); ++ break; ++ } ++} ++ ++static void wpaint_do_paint( ++ bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi, ++ Mesh *me, Brush *UNUSED(brush), const char symm, const int axis, const int i, const float angle) ++{ ++ SculptSession *ss = ob->sculpt; ++ ss->cache->radial_symmetry_pass = i; ++ calc_brushdata_symm(wp, ss->cache, symm, axis, angle); ++ ++ 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 = true; ++ BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); ++ ++ calc_area_normal(wp, ob, nodes, totnode, ss->cache->sculpt_normal_symm); ++ 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); ++ } ++} ++ ++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; ++ calc_brushdata_symm(wp, 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) +@@ -2160,24 +2794,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; +@@ -2190,13 +2817,8 @@ 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); +@@ -2204,7 +2826,6 @@ static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P + /* 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; +@@ -2222,180 +2843,49 @@ 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; +- } +- +- /* 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); +- +- 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; ++ /* 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_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; ++ DAG_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); +- +- DAG_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) +@@ -2407,23 +2897,9 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) + 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); +- } +- + MEM_freeN(wpd); + } + +- /* frees prev buffer */ +- copy_wpaint_prev(ts->wpaint, NULL, 0); +- + /* and particles too */ + if (ob->particlesystem.first) { + ParticleSystem *psys; +@@ -2442,6 +2918,9 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) + DAG_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; + } + + +@@ -2449,9 +2928,10 @@ 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); +@@ -2468,9 +2948,10 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) + + 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); +@@ -2480,6 +2961,12 @@ 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); + } + +@@ -2570,6 +3057,14 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) + BKE_mesh_flush_select_from_polys(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 { +@@ -2585,6 +3080,16 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) + 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(scene, ob); + } + + /* update modifier stack for mapping requirements */ +@@ -2638,13 +3143,12 @@ typedef struct PolyFaceMap { + int facenr; + } PolyFaceMap; + +-typedef struct VPaintData { ++struct VPaintData { + ViewContext vc; + unsigned int paintcol; +- int *indexar; + + struct VertProjHandle *vp_handle; +- DMCoNo *vertexcosnos; ++ struct DMCoNo *vertexcosnos; + + float vpimat[3][3]; + +@@ -2657,9 +3161,9 @@ 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])) ++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; +@@ -2670,6 +3174,7 @@ 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; + + /* context checks could be a poll() */ + me = BKE_mesh_from_object(ob); +@@ -2682,13 +3187,10 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f + 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); +- +- vpd->indexar = get_indexarray(me); + vpd->paintcol = vpaint_get_current_col(scene, vp); + + vpd->is_texbrush = !(brush->vertexpaint_tool == PAINT_BLEND_BLUR) && +@@ -2710,84 +3212,517 @@ 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); +- ++ /* Create projection handle */ ++ if (vpd->is_texbrush) { ++ ob->sculpt->modes.vwpaint.building_vp_handle = true; ++ vpd->vp_handle = ED_vpaint_proj_handle_create(scene, ob, &vpd->vertexcosnos); ++ ob->sculpt->modes.vwpaint.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(scene, ob); ++ vwpaint_update_cache_invariants(C, vp, ss, op, mouse); ++ vertex_paint_init_session_maps(ob); ++ vertex_paint_init_session_average_arrays(ob); ++ ++ for (int i = 0; i < me->totloop; i++) { ++ ob->sculpt->modes.vwpaint.previous_color[i] = 0; ++ } ++ + 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_ave_color_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); ++ ++ StrokeCache *cache = ss->cache; ++ unsigned int *lcol = data->lcol; ++ unsigned int blend[3] = {0}; ++ char *col; ++ data->ob->sculpt->modes.vwpaint.tot_loops_hit[n] = 0; ++ const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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_fast(&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, test.dist, cache->radius) > 0.0) { ++ /* If the vertex is selected for painting. */ ++ const MVert *mv = &data->me->mvert[v_index]; ++ if (!use_face_sel || mv->flag & SELECT) { ++ ss->modes.vwpaint.tot_loops_hit[n] += ss->modes.vwpaint.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 < ss->modes.vwpaint.vert_to_loop[v_index].count; j++) { ++ const int l_index = ss->modes.vwpaint.vert_to_loop[v_index].indices[j]; ++ col = (char *)(&lcol[l_index]); ++ /* Color is squared to compensate the sqrt color encoding. */ ++ blend[0] += col[0] * col[0]; ++ blend[1] += col[1] * col[1]; ++ blend[2] += col[2] * col[2]; ++ } ++ } ++ } ++ } ++ } ++ BKE_pbvh_vertex_iter_end; ++ ++ data->ob->sculpt->modes.vwpaint.total_color[n][0] = blend[0]; ++ data->ob->sculpt->modes.vwpaint.total_color[n][1] = blend[1]; ++ data->ob->sculpt->modes.vwpaint.total_color[n][2] = blend[2]; ++} ++ ++static void handle_texture_brush( ++ SculptThreadedTaskData *data, PBVHVertexIter vd, float size_pressure, float alpha_pressure, ++ float *r_alpha, unsigned int *r_color) + { +- 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; ++ 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 unsigned char *)&data->vpd->paintcol); ++ mul_v3_v3(rgba_br, rgba); ++ rgb_float_to_uchar((unsigned char *)r_color, rgba_br); ++} + +- int brush_alpha_pressure_i = (int)(brush_alpha_pressure * 255.0f); ++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); ++ ++ Brush *brush = data->brush; ++ StrokeCache *cache = ss->cache; ++ const float brush_strength = cache->bstrength; ++ unsigned int *lcol = data->lcol; ++ 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_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&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_face_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. */ ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); ++ unsigned int 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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; ++ const int l_index = ss->modes.vwpaint.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 */ ++ if (ss->modes.vwpaint.previous_color[l_index] == 0) { ++ ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; ++ } ++ const float final_alpha = ++ 255 * brush_fade * brush_strength * view_dot * ++ 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], ++ ss->modes.vwpaint.previous_color[l_index], color_final, ++ final_alpha, 255 * brush_strength); ++ } ++ } ++ } ++ } ++ } ++ } ++ BKE_pbvh_vertex_iter_end; ++} + +- if (brush->vertexpaint_tool == PAINT_BLEND_BLUR) { +- unsigned int blend[4] = {0}; +- unsigned int tcol; +- char *col; +- +- 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]; +- } +- +- 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]; +- +- vpd->paintcol = *((unsigned int *)col); +- } +- +- 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); +- +- 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); ++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); ++ ++ Brush *brush = data->brush; ++ StrokeCache *cache = ss->cache; ++ const float brush_strength = cache->bstrength; ++ unsigned int *lcol = data->lcol; ++ 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_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&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]; ++ ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); ++ ++ /* If the vertex is selected for painting. */ ++ if (!use_face_sel || mv->flag & SELECT) { ++ /* Get the average poly color */ ++ unsigned int color_final = 0; ++ int total_hit_loops = 0; ++ unsigned int blend[4] = {0}; ++ for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ int p_index = ss->modes.vwpaint.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 unsigned int l_index = mp->loopstart + k; ++ const char *col = (const char *)(&lcol[l_index]); ++ /* Color is squared to compensate the sqrt color encoding. */ ++ blend[0] += (unsigned int)col[0] * (unsigned int)col[0]; ++ blend[1] += (unsigned int)col[1] * (unsigned int)col[1]; ++ blend[2] += (unsigned int)col[2] * (unsigned int)col[2]; ++ blend[3] += (unsigned int)col[3] * (unsigned int)col[3]; ++ } ++ } ++ } ++ if (total_hit_loops != 0) { ++ /* Use rgb^2 color averaging. */ ++ char *col = (char *)(&color_final); ++ col[0] = (unsigned char)round(sqrtl(divide_round_i(blend[0], total_hit_loops))); ++ col[1] = (unsigned char)round(sqrtl(divide_round_i(blend[1], total_hit_loops))); ++ col[2] = (unsigned char)round(sqrtl(divide_round_i(blend[2], total_hit_loops))); ++ col[3] = (unsigned char)round(sqrtl(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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; ++ const int l_index = ss->modes.vwpaint.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 */ ++ if (ss->modes.vwpaint.previous_color[l_index] == 0) { ++ ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; ++ } ++ const float final_alpha = ++ 255 * brush_fade * brush_strength * view_dot * ++ 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], ++ ss->modes.vwpaint.previous_color[l_index], ++ *((unsigned int *)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); ++ ++ Brush *brush = data->brush; ++ StrokeCache *cache = ss->cache; ++ const float brush_strength = cache->bstrength; ++ unsigned int *lcol = data->lcol; ++ 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); ++ float brush_dir[3]; ++ const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; ++ ++ sub_v3_v3v3(brush_dir, cache->location, cache->last_location); ++ if (normalize_v3(brush_dir) != 0.0f) { ++ ++ SculptBrushTest test; ++ sculpt_brush_test_init(ss, &test); ++ ++ /* 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(&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_face_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. */ ++ const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; ++ if (view_dot > 0.0f) { ++ const float brush_fade = BKE_brush_curve_strength(brush, 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 */ ++ unsigned int color_final = 0; ++ for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; ++ const int l_index = ss->modes.vwpaint.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) { ++ for (int k = 0; k < mp->totloop; k++) { ++ const MLoop *ml = &data->me->mloop[l_index]; ++ const unsigned int v_other_index = ml->v; ++ 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); ++ 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 = lcol[l_index]; ++ do_color = true; ++ } ++ } ++ } ++ } ++ ++ if (do_color) { ++ /* For each poly owning this vert, paint each loop belonging to this vert. */ ++ for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { ++ const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; ++ const int l_index = ss->modes.vwpaint.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 */ ++ if (ss->modes.vwpaint.previous_color[l_index] == 0) { ++ ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; ++ } ++ const float final_alpha = ++ 255 * brush_fade * brush_strength * ++ view_dot * 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], ++ ss->modes.vwpaint.previous_color[l_index], color_final, ++ final_alpha, 255 * brush_strength); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ BKE_pbvh_vertex_iter_end; ++ } ++} ++ ++static void calculate_average_color(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) ++{ ++ BLI_task_parallel_range_ex( ++ 0, totnode, data, NULL, 0, do_vpaint_brush_calc_ave_color_cb_ex, ++ true, false); ++ ++ unsigned int total_hit_loops = 0; ++ unsigned int total_color[3] = {0}; ++ unsigned char blend[4] = {0}; ++ for (int i = 0; i < totnode; i++) { ++ total_hit_loops += data->ob->sculpt->modes.vwpaint.tot_loops_hit[i]; ++ total_color[0] += data->ob->sculpt->modes.vwpaint.total_color[i][0]; ++ total_color[1] += data->ob->sculpt->modes.vwpaint.total_color[i][1]; ++ total_color[2] += data->ob->sculpt->modes.vwpaint.total_color[i][2]; ++ } ++ if (total_hit_loops != 0) { ++ blend[0] = (unsigned char)round(sqrtl(divide_round_i(total_color[0], total_hit_loops))); ++ blend[1] = (unsigned char)round(sqrtl(divide_round_i(total_color[1], total_hit_loops))); ++ blend[2] = (unsigned char)round(sqrtl(divide_round_i(total_color[2], total_hit_loops))); ++ blend[3] = 255; ++ data->vpd->paintcol = *((unsigned int *)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); ++static void vpaint_paint_leaves( ++ bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, ++ Object *ob, Mesh *me, PBVHNode **nodes, int totnode) ++{ ++ Brush *brush = ob->sculpt->cache->brush; + +- if (mlooptag) mlooptag[i] = 1; ++ SculptThreadedTaskData data = { ++ .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .vp = vp, .vpd = vpd, ++ .lcol = (unsigned int *)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 *vd, struct VPaintData *vpd, ++ Object *ob, Mesh *me, Brush *UNUSED(brush), const char symm, const int axis, const int i, const float angle) ++{ ++ SculptSession *ss = ob->sculpt; ++ ss->cache->radial_symmetry_pass = i; ++ calc_brushdata_symm(vd, ss->cache, symm, axis, angle); ++ 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 = true; ++ BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); ++ ++ calc_area_normal(vd, ob, nodes, totnode, ss->cache->sculpt_normal_symm); ++ ++ /* Paint those leaves. */ ++ vpaint_paint_leaves(C, sd, vd, vpd, ob, me, nodes, totnode); ++ ++ if (nodes) { ++ MEM_freeN(nodes); ++ } ++} ++ ++static void vpaint_do_radial_symmetry( ++ bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob, Mesh *me, ++ Brush *brush, const char symm, const int axis) ++{ ++ for (int i = 1; i < vd->radial_symm[axis - 'X']; i++) { ++ const float angle = (2.0 * M_PI) * i / vd->radial_symm[axis - 'X']; ++ vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, symm, axis, i, angle); ++ } ++} ++ ++static void vpaint_do_symmetrical_brush_actions( ++ bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob) ++{ ++ Brush *brush = BKE_paint_brush(&vd->paint); ++ Mesh *me = ob->data; ++ SculptSession *ss = ob->sculpt; ++ StrokeCache *cache = ss->cache; ++ const char symm = vd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; ++ int i = 0; ++ ++ /* initial stroke */ ++ vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0); ++ vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X'); ++ vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y'); ++ vpaint_do_radial_symmetry(C, sd, vd, 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; ++ calc_brushdata_symm(vd, cache, i, 0, 0); ++ ++ if (i & (1 << 0)) { ++ vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0); ++ vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X'); ++ } ++ if (i & (1 << 1)) { ++ vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Y', 0, 0); ++ vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y'); ++ } ++ if (i & (1 << 2)) { ++ vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Z', 0, 0); ++ vpaint_do_radial_symmetry(C, sd, vd, 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) +@@ -2796,65 +3731,26 @@ 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); ++ vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob); + +- /* clear modified tag for blur tool */ +- if (vpd->mlooptag) +- memset(vpd->mlooptag, 0, sizeof(bool) * me->totloop); +- +- 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); +- } +- + /* calculate pivot for rotation around seletion if needed */ + /* also needed for "View Selected" on last stroke */ + paint_last_stroke_update(scene, vc->ar, mval); +@@ -2874,32 +3770,26 @@ 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); + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); +- DAG_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, ++ 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); + +@@ -2919,7 +3809,7 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) + + static int vpaint_exec(bContext *C, wmOperator *op) + { +- op->customdata = paint_stroke_new(C, op, NULL, vpaint_stroke_test_start, ++ op->customdata = paint_stroke_new(C, op, sculpt_stroke_get_location, vpaint_stroke_test_start, + vpaint_stroke_update_step, NULL, + vpaint_stroke_done, 0); + +@@ -2931,6 +3821,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); + } + +@@ -3115,7 +4011,7 @@ static void gradientVertInit__mapFunc( + { + /* ok */ + MDeformVert *dv = &me->dvert[index]; +- MDeformWeight *dw; ++ const MDeformWeight *dw; + dw = defvert_find_index(dv, grad_data->def_nr); + if (dw) { + vs->weight_orig = dw->weight; +--- sculpt.c ++++ 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" + +@@ -165,131 +164,17 @@ 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 { +- BMLog *bm_log; +- +- SculptUndoNode *unode; +- float (*coords)[3]; +- short (*normals)[3]; +- const float *vmasks; +- +- /* Original coordinate, normal, and mask */ +- const float *co; +- const short *no; +- float mask; +-} SculptOrigVertData; +- +- + /* Initialize a SculptOrigVertData for accessing original vertex data; + * handles BMesh, mesh, and multires */ +-static void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, ++void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, + Object *ob, + SculptUndoNode *unode) + { +@@ -311,7 +196,7 @@ static void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, + + /* Initialize a SculptOrigVertData for accessing original vertex data; + * handles BMesh, mesh, and multires */ +-static void sculpt_orig_vert_data_init(SculptOrigVertData *data, ++void sculpt_orig_vert_data_init(SculptOrigVertData *data, + Object *ob, + PBVHNode *node) + { +@@ -322,7 +207,7 @@ static void sculpt_orig_vert_data_init(SculptOrigVertData *data, + + /* Update a SculptOrigVertData for a particular vertex from the PBVH + * iterator */ +-static void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, ++void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, + PBVHVertexIter *iter) + { + if (orig_data->unode->type == SCULPT_UNDO_COORDS) { +@@ -406,21 +291,6 @@ static void sculpt_project_v3_normal_align(SculptSession *ss, const float normal + madd_v3_v3fl(grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); + } + +- +-/** \name SculptProjectVector +- * +- * Fast-path for #project_plane_v3_v3v3 +- * +- * \{ */ +- +-typedef struct SculptProjectVector { +- float plane[3]; +- float len_sq; +- float len_sq_inv_neg; +- bool is_valid; +- +-} SculptProjectVector; +- + /** + * \param plane Direction, can be any length. + */ +@@ -476,41 +346,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; +@@ -600,7 +435,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 +485,7 @@ void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *ar, + + /************************ 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; + +@@ -689,7 +514,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(SculptBrushTest *test, const float co[3]) + { + float distsq = len_squared_v3v3(co, test->location); + +@@ -705,7 +530,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_sq(SculptBrushTest *test, const float co[3]) + { + float distsq = len_squared_v3v3(co, test->location); + +@@ -721,7 +546,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_fast(const SculptBrushTest *test, const float co[3]) + { + if (sculpt_brush_test_clipping(test, co)) { + return 0; +@@ -729,7 +554,7 @@ 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_cube(SculptBrushTest *test, const float co[3], float local[4][4]) + { + float side = M_SQRT1_2; + float local_co[3]; +@@ -1237,13 +1062,13 @@ 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, 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; +@@ -1316,15 +1141,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]; +@@ -1632,6 +1450,22 @@ typedef struct SculptDoBrushSmoothGridDataChunk { + size_t tmpgrid_size; + } SculptDoBrushSmoothGridDataChunk; + ++typedef struct { ++ SculptSession *ss; ++ const float *ray_start, *ray_normal; ++ bool hit; ++ float dist; ++ bool original; ++ PBVHNode* node; ++} SculptRaycastData; ++ ++typedef struct { ++ const float *ray_start, *ray_normal; ++ bool hit; ++ float dist; ++ float detail; ++} SculptDetailRaycastData; ++ + static void do_smooth_brush_mesh_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int thread_id) + { +@@ -3948,7 +3782,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); +@@ -4398,21 +4232,6 @@ static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob) + } + } + +-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,6 +4256,9 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) + { + srd->hit = 1; + *tmin = srd->dist; ++ ++ //for vwpaint testing ++ srd->node = node; + } + } + } +@@ -4521,12 +4343,17 @@ bool sculpt_stroke_get_location(bContext *C, float out[3], const float mouse[2]) + srd.dist = dist; + + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, +- ray_start, ray_normal, srd.original); ++ ray_start, ray_normal, srd.original); + + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.dist); + add_v3_v3(out, ray_start); + ++ //used in vwpaint ++ if (cache && srd.hit){ ++ copy_v3_v3(cache->true_location, out); ++ } ++ + return srd.hit; + } + +--- sculpt_intern.h ++++ sculpt_intern.h +@@ -38,6 +38,8 @@ + #include "DNA_key_types.h" + + #include "BLI_bitmap.h" ++#include "BLI_threads.h" ++ + #include "BKE_pbvh.h" + + struct bContext; +@@ -115,6 +117,233 @@ typedef struct SculptUndoNode { + char shapeName[sizeof(((KeyBlock *)0))->name]; + } SculptUndoNode; + ++/************** Access to original unmodified vertex data *************/ ++ ++typedef struct SculptOrigVertData { ++ struct BMLog *bm_log; ++ ++ SculptUndoNode *unode; ++ float(*coords)[3]; ++ short(*normals)[3]; ++ const float *vmasks; ++ ++ /* Original coordinate, normal, and mask */ ++ const float *co; ++ const short *no; ++ float mask; ++} SculptOrigVertData; ++ ++ ++void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, ++ Object *ob, ++ SculptUndoNode *unode); ++void sculpt_orig_vert_data_init(SculptOrigVertData *data, ++ Object *ob, ++ PBVHNode *node); ++void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, ++ PBVHVertexIter *iter); ++ ++/* 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]; ++}; ++ ++/** \name SculptProjectVector ++* ++* Fast-path for #project_plane_v3_v3v3 ++* ++* \{ */ ++ ++typedef struct SculptProjectVector { ++ float plane[3]; ++ float len_sq; ++ float len_sq_inv_neg; ++ bool is_valid; ++ ++} SculptProjectVector; ++ ++/* Single struct used by all BLI_task threaded callbacks, let's avoid adding 10's of those... */ ++typedef struct SculptThreadedTaskData { ++ bContext *C; ++ struct Sculpt *sd; ++ struct Object *ob; ++ struct Brush *brush; ++ struct PBVHNode **nodes; ++ int totnode; ++ ++ struct VPaint *vp; ++ struct VPaintData *vpd; ++ struct WPaintData *wpd; ++ struct WeightPaintInfo *wpi; ++ unsigned int *lcol; ++ struct MeshElemMap **vertToLoopMaps; ++ struct Mesh *me; ++ ++ ++ /* 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; ++ ++/*************** Brush testing declarations ****************/ ++typedef struct SculptBrushTest { ++ float radius_squared; ++ float location[3]; ++ float dist; ++ int mirror_symmetry_pass; ++ ++ /* View3d clipping - only set rv3d for clipping */ ++ struct RegionView3D *clip_rv3d; ++} SculptBrushTest; ++ ++typedef struct { ++ struct Sculpt *sd; ++ struct SculptSession *ss; ++ float radius_squared; ++ bool original; ++} SculptSearchSphereData; ++ ++void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test); ++bool sculpt_brush_test(SculptBrushTest *test, const float co[3]); ++bool sculpt_brush_test_sq(SculptBrushTest *test, const float co[3]); ++bool sculpt_brush_test_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_search_sphere_cb(PBVHNode *node, void *data_v); ++float tex_strength( ++ SculptSession *ss, 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); ++ ++ ++/* 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; ++ 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_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 +353,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 +--- DNA_brush_types.h ++++ DNA_brush_types.h +@@ -315,7 +315,9 @@ enum { + PAINT_BLEND_MUL = 3, + PAINT_BLEND_BLUR = 4, + PAINT_BLEND_LIGHTEN = 5, +- PAINT_BLEND_DARKEN = 6 ++ PAINT_BLEND_DARKEN = 6, ++ PAINT_BLEND_AVERAGE = 7, ++ PAINT_BLEND_SMEAR = 8, + }; + + typedef enum { +--- DNA_object_types.h ++++ DNA_object_types.h +@@ -683,6 +683,9 @@ typedef enum ObjectMode { + /* any mode where the brush system is used */ + #define OB_MODE_ALL_PAINT (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT) + ++/* any mode that uses ob->sculpt */ ++#define OB_MODE_ALL_SCULPT (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT) ++ + #define MAX_DUPLI_RECUR 8 + + #ifdef __cplusplus +--- DNA_scene_types.h ++++ DNA_scene_types.h +@@ -1163,6 +1163,9 @@ typedef struct VPaint { + struct MDeformVert *wpaint_prev; /* previous vertex weights */ + + void *paintcursor; /* wm handle */ ++ ++ int radial_symm[3]; /* For mirrored painting */ ++ int pad2; + } VPaint; + + /* VPaint.flag */ +--- rna_brush.c ++++ rna_brush.c +@@ -94,6 +94,8 @@ EnumPropertyItem rna_enum_brush_vertex_tool_items[] = { + {PAINT_BLEND_BLUR, "BLUR", ICON_BRUSH_BLUR, "Blur", "Blur the color with surrounding values"}, + {PAINT_BLEND_LIGHTEN, "LIGHTEN", ICON_BRUSH_LIGHTEN, "Lighten", "Use lighten blending mode while painting"}, + {PAINT_BLEND_DARKEN, "DARKEN", ICON_BRUSH_DARKEN, "Darken", "Use darken blending mode while painting"}, ++ {PAINT_BLEND_AVERAGE, "AVERAGE", ICON_BRUSH_BLUR, "Average", "Use average blending mode while painting" }, ++ {PAINT_BLEND_SMEAR, "SMEAR", ICON_BRUSH_BLUR, "Smear", "Use smear blending mode while painting" }, + {0, NULL, 0, NULL, NULL} + }; + +--- rna_sculpt_paint.c ++++ rna_sculpt_paint.c +@@ -684,6 +684,15 @@ static void rna_def_vertex_paint(BlenderRNA *brna) + RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_ONLYVGROUP); + RNA_def_property_ui_text(prop, "Restrict", "Restrict painting to vertices in the group"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); ++ ++ /* Mirroring */ ++ prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ); ++ RNA_def_property_int_sdna(prop, NULL, "radial_symm"); ++ RNA_def_property_int_default(prop, 1); ++ RNA_def_property_range(prop, 1, 64); ++ RNA_def_property_ui_range(prop, 1, 32, 1, 1); ++ RNA_def_property_ui_text(prop, "Radial Symmetry Count X Axis", ++ "Number of times to copy strokes across the surface"); + } + + static void rna_def_image_paint(BlenderRNA *brna) diff --git a/release/datafiles/locale b/release/datafiles/locale index c93ed11a47b..19a637ce9f3 160000 --- a/release/datafiles/locale +++ b/release/datafiles/locale @@ -1 +1 @@ -Subproject commit c93ed11a47b3016cf59711ec16de2e2e94c30e99 +Subproject commit 19a637ce9f38112146daca394af4a7db1bae6687 diff --git a/release/scripts/addons b/release/scripts/addons index 371960484a3..0926c1e7dcb 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 371960484a38fc64e0a2635170a41a0d8ab2f6bd +Subproject commit 0926c1e7dcbe566d3a92116c6e8f91ba440f3789 diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib index a8515cfdfe9..706fce2d1d1 160000 --- a/release/scripts/addons_contrib +++ b/release/scripts/addons_contrib @@ -1 +1 @@ -Subproject commit a8515cfdfe9a98127b592f36fcbe51b7e23b969a +Subproject commit 706fce2d1d195096d081b92ba47b43a38dc120f7 diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index d58453deaef..06249c807b7 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -51,6 +51,19 @@ def draw_keyframing_tools(context, layout): row.operator("anim.keyframe_delete_v3d", text="Remove") +# Used by vertex & weight paint +def draw_vpaint_symmetry(layout, vpaint): + col = layout.column(align=True) + col.label(text="Mirror:") + row = col.row(align=True) + + row.prop(vpaint, "use_symmetry_x", text="X", toggle=True) + row.prop(vpaint, "use_symmetry_y", text="Y", toggle=True) + row.prop(vpaint, "use_symmetry_z", text="Z", toggle=True) + + col = layout.column() + col.prop(vpaint, "radial_symmetry", text="Radial") + # ********** default tools for object-mode **************** @@ -1132,7 +1145,11 @@ class VIEW3D_PT_tools_brush(Panel, View3DPaintPanel): self.prop_unified_color_picker(col, context, brush, "color", value_slider=True) if settings.palette: col.template_palette(settings, "palette", color=True) - self.prop_unified_color(col, context, brush, "color", text="") + row = col.row(align=True) + self.prop_unified_color(row, context, brush, "color", text="") + self.prop_unified_color(row, context, brush, "secondary_color", text="") + row.separator() + row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="") col.separator() row = col.row(align=True) @@ -1713,6 +1730,19 @@ class VIEW3D_PT_tools_weightpaint(View3DPanel, Panel): props.data_type = 'VGROUP_WEIGHTS' +class VIEW3D_PT_tools_weightpaint_symmetry(Panel, View3DPaintPanel): + bl_category = "Tools" + bl_context = "weightpaint" + bl_options = {'DEFAULT_CLOSED'} + bl_label = "Symmetry" + + def draw(self, context): + layout = self.layout + toolsettings = context.tool_settings + wpaint = toolsettings.weight_paint + draw_vpaint_symmetry(layout, wpaint) + + class VIEW3D_PT_tools_weightpaint_options(Panel, View3DPaintPanel): bl_category = "Options" bl_context = "weightpaint" @@ -1775,6 +1805,20 @@ class VIEW3D_PT_tools_vertexpaint(Panel, View3DPaintPanel): #~ col.label(text="Multiply:") #~ col.prop(vpaint, "mul", text="") + +class VIEW3D_PT_tools_vertexpaint_symmetry(Panel, View3DPaintPanel): + bl_category = "Tools" + bl_context = "vertexpaint" + bl_options = {'DEFAULT_CLOSED'} + bl_label = "Symmetry" + + def draw(self, context): + layout = self.layout + toolsettings = context.tool_settings + vpaint = toolsettings.vertex_paint + draw_vpaint_symmetry(layout, vpaint) + + # ********** default tools for texture-paint **************** @@ -2054,8 +2098,10 @@ classes = ( VIEW3D_PT_sculpt_symmetry, VIEW3D_PT_tools_brush_appearance, VIEW3D_PT_tools_weightpaint, + VIEW3D_PT_tools_weightpaint_symmetry, VIEW3D_PT_tools_weightpaint_options, VIEW3D_PT_tools_vertexpaint, + VIEW3D_PT_tools_vertexpaint_symmetry, VIEW3D_PT_tools_imagepaint_external, VIEW3D_PT_tools_imagepaint_symmetry, VIEW3D_PT_tools_projectpaint, diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 0a3cc950f32..9f472d3b9d1 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -201,10 +201,30 @@ typedef struct SculptSession { struct SculptStroke *stroke; struct StrokeCache *cache; + + union { + struct { + int *vert_map_mem; + struct MeshElemMap *vert_to_loop; + int *poly_map_mem; + struct MeshElemMap *vert_to_poly; + + unsigned int (*total_color)[3]; + double *total_weight; + unsigned int *tot_loops_hit; + float *max_weight; + unsigned int *previous_color; + bool building_vp_handle; + } vwpaint; + //struct { + //ToDo: identify sculpt-only fields + //} sculpt; + } modes; } SculptSession; void BKE_sculptsession_free(struct Object *ob); void BKE_sculptsession_free_deformMats(struct SculptSession *ss); +void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss); void BKE_sculptsession_bm_to_me(struct Object *ob, bool reorder); void BKE_sculptsession_bm_to_me_for_render(struct Object *object); void BKE_sculpt_update_mesh_elements(struct Scene *scene, struct Sculpt *sd, struct Object *ob, diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index 927303f8b3c..997609d9a7d 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -32,6 +32,7 @@ struct CCGElem; struct CCGKey; +struct CCGDerivedMesh; struct CustomData; struct DMFlagMat; struct MPoly; @@ -71,7 +72,7 @@ void BKE_pbvh_build_grids(PBVH *bvh, struct CCGElem **grid_elems, struct CCGKey *key, void **gridfaces, struct DMFlagMat *flagmats, unsigned int **grid_hidden); void BKE_pbvh_build_bmesh(PBVH *bvh, struct BMesh *bm, bool smooth_shading, struct BMLog *log, const int cd_vert_node_offset, const int cd_face_node_offset); - +void BKE_pbvh_add_ccgdm(PBVH *bvh, struct CCGDerivedMesh *ccgdm); void BKE_pbvh_free(PBVH *bvh); void BKE_pbvh_free_layer_disp(PBVH *bvh); @@ -118,6 +119,7 @@ void BKE_pbvh_raycast_project_ray_root( void BKE_pbvh_node_draw(PBVHNode *node, void *data); void BKE_pbvh_draw(PBVH *bvh, float (*planes)[4], float (*face_nors)[3], int (*setMaterial)(int matnr, void *attribs), bool wireframe, bool fast); +void BKE_pbvh_draw_BB(PBVH *bvh); /* PBVH Access */ typedef enum { @@ -141,6 +143,7 @@ int BKE_pbvh_count_grid_quads(BLI_bitmap **grid_hidden, /* multires level, only valid for type == PBVH_GRIDS */ void BKE_pbvh_get_grid_key(const PBVH *pbvh, struct CCGKey *key); +struct CCGDerivedMesh *BKE_pbvh_get_ccgdm(const PBVH *bvh); /* Only valid for type == PBVH_BMESH */ struct BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh); @@ -189,6 +192,7 @@ void BKE_pbvh_node_num_verts( void BKE_pbvh_node_get_verts( PBVH *bvh, PBVHNode *node, const int **r_vert_indices, struct MVert **r_verts); +void BKE_pbvh_get_num_nodes(const PBVH *bvh, int *r_totnode); void BKE_pbvh_node_get_BB(PBVHNode *node, float bb_min[3], float bb_max[3]); void BKE_pbvh_node_get_original_BB(PBVHNode *node, float bb_min[3], float bb_max[3]); diff --git a/source/blender/blenkernel/intern/CCGSubSurf.c b/source/blender/blenkernel/intern/CCGSubSurf.c index 792e9195f12..7b74bbcba04 100644 --- a/source/blender/blenkernel/intern/CCGSubSurf.c +++ b/source/blender/blenkernel/intern/CCGSubSurf.c @@ -1196,6 +1196,7 @@ int ccgSubSurf_getNumEdges(const CCGSubSurf *ss) } int ccgSubSurf_getNumFaces(const CCGSubSurf *ss) { + return ss->fMap->numEntries; } diff --git a/source/blender/blenkernel/intern/DerivedMesh.c b/source/blender/blenkernel/intern/DerivedMesh.c index 18e9bdf6cd1..22d2eff6672 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.c +++ b/source/blender/blenkernel/intern/DerivedMesh.c @@ -2641,7 +2641,7 @@ static void mesh_build_data( ob->lastDataMask = dataMask; ob->lastNeedMapping = need_mapping; - if ((ob->mode & OB_MODE_SCULPT) && ob->sculpt) { + if ((ob->mode & OB_MODE_ALL_SCULPT) && ob->sculpt) { /* create PBVH immediately (would be created on the fly too, * but this avoids waiting on first stroke) */ diff --git a/source/blender/blenkernel/intern/cdderivedmesh.c b/source/blender/blenkernel/intern/cdderivedmesh.c index 46a067ea0bc..8b769f760f6 100644 --- a/source/blender/blenkernel/intern/cdderivedmesh.c +++ b/source/blender/blenkernel/intern/cdderivedmesh.c @@ -660,6 +660,11 @@ static void cdDM_drawMappedFaces( const int *index_mp_to_orig = dm->getPolyDataArray(dm, CD_ORIGINDEX); + if (cddm->pbvh) { + if (G.debug_value == 14) + BKE_pbvh_draw_BB(cddm->pbvh); + } + /* fist, setup common buffers */ GPU_vertex_setup(dm); GPU_triangle_setup(dm); diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index b65cc408ae5..536170bf108 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -2679,7 +2679,7 @@ void BKE_object_sculpt_modifiers_changed(Object *ob) { SculptSession *ss = ob->sculpt; - if (ss) { + if (ss && ss->modes.vwpaint.building_vp_handle == false) { if (!ss->cache) { /* we free pbvh on changes, except during sculpt since it can't deal with * changing PVBH node organization, we hope topology does not change in @@ -2690,6 +2690,9 @@ void BKE_object_sculpt_modifiers_changed(Object *ob) } BKE_sculptsession_free_deformMats(ob->sculpt); + + /* In vertex/weight paint, force maps to be rebuilt. */ + BKE_sculptsession_free_vwpaint_data(ob->sculpt); } else { PBVHNode **nodes; diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 6b954f060d3..b8dbfc6f71b 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -656,6 +656,22 @@ void BKE_sculptsession_free_deformMats(SculptSession *ss) MEM_SAFE_FREE(ss->deform_imats); } +void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss) +{ + /* Free maps */ + MEM_SAFE_FREE(ss->modes.vwpaint.vert_to_loop); + MEM_SAFE_FREE(ss->modes.vwpaint.vert_map_mem); + MEM_SAFE_FREE(ss->modes.vwpaint.vert_to_poly); + MEM_SAFE_FREE(ss->modes.vwpaint.poly_map_mem); + + /* Free average, blur, and spray brush arrays */ + MEM_SAFE_FREE(ss->modes.vwpaint.tot_loops_hit); + MEM_SAFE_FREE(ss->modes.vwpaint.total_color); + MEM_SAFE_FREE(ss->modes.vwpaint.total_weight); + MEM_SAFE_FREE(ss->modes.vwpaint.max_weight); + MEM_SAFE_FREE(ss->modes.vwpaint.previous_color); +} + /* Write out the sculpt dynamic-topology BMesh to the Mesh */ static void sculptsession_bm_to_me_update_data_only(Object *ob, bool reorder) { @@ -697,10 +713,7 @@ void BKE_sculptsession_bm_to_me_for_render(Object *object) */ BKE_object_free_derived_caches(object); - if (object->sculpt->pbvh) { - BKE_pbvh_free(object->sculpt->pbvh); - object->sculpt->pbvh = NULL; - } + MEM_SAFE_FREE(object->sculpt->pbvh); sculptsession_bm_to_me_update_data_only(object, false); @@ -747,6 +760,8 @@ void BKE_sculptsession_free(Object *ob) if (ss->deform_imats) MEM_freeN(ss->deform_imats); + BKE_sculptsession_free_vwpaint_data(ob->sculpt); + MEM_freeN(ss); ob->sculpt = NULL; @@ -831,6 +846,9 @@ void BKE_sculpt_update_mesh_elements(Scene *scene, Sculpt *sd, Object *ob, ss->modifiers_active = sculpt_modifiers_active(scene, sd, ob); ss->show_diffuse_color = (sd->flags & SCULPT_SHOW_DIFFUSE) != 0; + /* This flag prevents PBVH from being freed when creating the vp_handle for texture paint */ + ss->modes.vwpaint.building_vp_handle = false; + if (need_mask) { if (mmd == NULL) { if (!CustomData_has_layer(&me->vdata, CD_PAINT_MASK)) { @@ -859,7 +877,8 @@ void BKE_sculpt_update_mesh_elements(Scene *scene, Sculpt *sd, Object *ob, dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); - if (mmd) { + /* VWPaint require mesh info for loop lookup, so require sculpt mode here */ + if (mmd && ob->mode & OB_MODE_SCULPT) { ss->multires = mmd; ss->totvert = dm->getNumVerts(dm); ss->totpoly = dm->getNumPolys(dm); diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index dacaad8d703..549b0293bd1 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -34,6 +34,7 @@ #include "BKE_pbvh.h" #include "BKE_ccg.h" +#include "BKE_subsurf.h" #include "BKE_DerivedMesh.h" #include "BKE_global.h" #include "BKE_mesh.h" /* for BKE_mesh_calc_normals */ @@ -606,6 +607,10 @@ void BKE_pbvh_build_grids(PBVH *bvh, CCGElem **grids, MEM_freeN(prim_bbc); } +void BKE_pbvh_add_ccgdm(PBVH *bvh, CCGDerivedMesh *ccgdm) { + bvh->ccgdm = ccgdm; +} + PBVH *BKE_pbvh_new(void) { PBVH *bvh = MEM_callocN(sizeof(PBVH), "pbvh"); @@ -1156,7 +1161,7 @@ static void pbvh_update_draw_buffers(PBVH *bvh, PBVHNode **nodes, int totnode) } } -static void pbvh_draw_BB(PBVH *bvh) +void BKE_pbvh_draw_BB(PBVH *bvh) { GPU_pbvh_BB_draw_init(); @@ -1329,6 +1334,11 @@ void BKE_pbvh_get_grid_key(const PBVH *bvh, CCGKey *key) *key = bvh->gridkey; } +CCGDerivedMesh *BKE_pbvh_get_ccgdm(const PBVH *bvh) { + return bvh->ccgdm; +} + + BMesh *BKE_pbvh_get_bmesh(PBVH *bvh) { BLI_assert(bvh->type == PBVH_BMESH); @@ -1405,6 +1415,11 @@ void BKE_pbvh_node_num_verts( } } +void BKE_pbvh_get_num_nodes(const PBVH *bvh, int *r_totnode) +{ + *r_totnode = bvh->totnode; +} + void BKE_pbvh_node_get_grids( PBVH *bvh, PBVHNode *node, int **r_grid_indices, int *r_totgrid, int *r_maxgrid, int *r_gridsize, CCGElem ***r_griddata) @@ -1860,7 +1875,7 @@ void BKE_pbvh_draw(PBVH *bvh, float (*planes)[4], float (*fnors)[3], } if (G.debug_value == 14) - pbvh_draw_BB(bvh); + BKE_pbvh_draw_BB(bvh); } void BKE_pbvh_grids_update(PBVH *bvh, CCGElem **grids, void **gridfaces, diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index 19d3b31bd31..01057318568 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.h +++ b/source/blender/blenkernel/intern/pbvh_intern.h @@ -149,6 +149,8 @@ struct PBVH { * objects in sculpt mode with different sizes at the same time, so now storing that common gpu buffer * in an opaque pointer per pbvh. See T47637. */ struct GridCommonGPUBuffer *grid_common_gpu_buffer; + /* The ccgdm is required for CD_ORIGINDEX lookup in vertex paint + multires */ + struct CCGDerivedMesh *ccgdm; /* Only used during BVH build and update, * don't need to remain valid after */ diff --git a/source/blender/blenkernel/intern/subsurf_ccg.c b/source/blender/blenkernel/intern/subsurf_ccg.c index c4665c40ec4..e6943f4ba2b 100644 --- a/source/blender/blenkernel/intern/subsurf_ccg.c +++ b/source/blender/blenkernel/intern/subsurf_ccg.c @@ -3681,6 +3681,11 @@ static void ccgDM_drawMappedFaces(DerivedMesh *dm, int gridFaces = gridSize - 1, totface; int prev_mat_nr = -1; + if (ccgdm->pbvh) { + if (G.debug_value == 14) + BKE_pbvh_draw_BB(ccgdm->pbvh); + } + #ifdef WITH_OPENSUBDIV if (ccgdm->useGpuBackend) { int new_matnr; @@ -4414,7 +4419,8 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) if (!ob->sculpt) return NULL; - grid_pbvh = ccgDM_use_grid_pbvh(ccgdm); + /* In vwpaint, we always use a grid_pbvh for multires/subsurf */ + grid_pbvh = (!(ob->mode & OB_MODE_SCULPT) || ccgDM_use_grid_pbvh(ccgdm)); if (ob->sculpt->pbvh) { if (grid_pbvh) { @@ -4430,12 +4436,17 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) ccgdm->pbvh = ob->sculpt->pbvh; } - if (ccgdm->pbvh) + if (ccgdm->pbvh) { + /* For vertex paint, keep track of ccgdm */ + if (!(ob->mode & OB_MODE_SCULPT)) + BKE_pbvh_add_ccgdm(ccgdm->pbvh, ccgdm); return ccgdm->pbvh; + } /* no pbvh exists yet, we need to create one. only in case of multires * we build a pbvh over the modified mesh, in other cases the base mesh * is being sculpted, so we build a pbvh from that. */ + /* Note: vwpaint always builds a pbvh over the modified mesh. */ if (grid_pbvh) { ccgdm_create_grids(dm); @@ -4466,6 +4477,10 @@ static struct PBVH *ccgDM_getPBVH(Object *ob, DerivedMesh *dm) if (ccgdm->pbvh) pbvh_show_diffuse_color_set(ccgdm->pbvh, ob->sculpt->show_diffuse_color); + /* For vertex paint, keep track of ccgdm */ + if (!(ob->mode & OB_MODE_SCULPT) && ccgdm->pbvh) + BKE_pbvh_add_ccgdm(ccgdm->pbvh, ccgdm); + return ccgdm->pbvh; } diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index d2f43a2d79e..1d5bd7d33d1 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -60,6 +60,7 @@ #include "DNA_genfile.h" #include "BKE_animsys.h" +#include "BKE_brush.h" #include "BKE_colortools.h" #include "BKE_library.h" #include "BKE_main.h" @@ -1645,6 +1646,23 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) } } + { + Brush *br; + br = (Brush *)BKE_libblock_find_name_ex(main, ID_BR, "Average"); + if (!br) { + br = BKE_brush_add(main, "Average", OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT); + br->vertexpaint_tool = PAINT_BLEND_AVERAGE; + br->ob_mode = OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT; + } + + br = (Brush *)BKE_libblock_find_name_ex(main, ID_BR, "Smear"); + if (!br) { + br = BKE_brush_add(main, "Smear", OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT); + br->vertexpaint_tool = PAINT_BLEND_SMEAR; + br->ob_mode = OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT; + } + } + FOREACH_NODETREE(main, ntree, id) { if (ntree->type == NTREE_COMPOSIT) { do_versions_compositor_render_passes(ntree); diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index e34f12b1cf9..845f8ff0523 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -106,6 +106,16 @@ void BLO_update_defaults_startup_blend(Main *bmain) sculpt->detail_size = 12; } + if (ts->vpaint) { + VPaint *vp = ts->vpaint; + vp->radial_symm[0] = vp->radial_symm[1] = vp->radial_symm[2] = 1; + } + + if (ts->wpaint) { + VPaint *wp = ts->wpaint; + wp->radial_symm[0] = wp->radial_symm[1] = wp->radial_symm[2] = 1; + } + if (ts->gp_sculpt.brush[0].size == 0) { GP_BrushEdit_Settings *gset = &ts->gp_sculpt; GP_EditBrush_Data *brush; diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index bf344e1f721..fc6b0122ed2 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -1448,7 +1448,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); } @@ -1467,7 +1480,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_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 7e05ab929ae..f91a6baf6a9 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -54,6 +54,7 @@ struct wmOperator; struct wmOperatorType; struct wmWindowManager; struct DMCoNo; +struct MeshElemMap; enum PaintMode; /* paint_stroke.c */ diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index f88b64129e7..ace18044bda 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -1615,6 +1615,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_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 729dd9dc57b..f647015e41e 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -35,7 +35,7 @@ #include "BLI_math.h" #include "BLI_array_utils.h" #include "BLI_bitmap.h" -#include "BLI_stack.h" +#include "BLI_task.h" #include "BLI_string_utils.h" #include "IMB_imbuf.h" @@ -66,6 +66,7 @@ #include "BKE_paint.h" #include "BKE_report.h" #include "BKE_colortools.h" +#include "BKE_subsurf.h" #include "WM_api.h" #include "WM_types.h" @@ -76,13 +77,11 @@ #include "ED_screen.h" #include "ED_view3d.h" -#include "paint_intern.h" /* own include */ +#include "bmesh.h" +#include "BKE_ccg.h" -/* small structure to defer applying weight-paint results */ -struct WPaintDefer { - int index; - float alpha, weight; -}; +#include "sculpt_intern.h" +#include "paint_intern.h" /* own include */ /* check if we can do partial updates and have them draw realtime * (without rebuilding the 'derivedFinal') */ @@ -174,11 +173,6 @@ static VPaint *new_vpaint(int wpaint) 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) { Brush *brush = BKE_paint_brush(&vp->paint); @@ -191,7 +185,7 @@ unsigned int vpaint_get_current_col(Scene *scene, VPaint *vp) static void do_shared_vertexcol(Mesh *me, bool *mlooptag) { const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - MPoly *mp; + const MPoly *mp; int (*scol)[4]; int i, j; bool has_shared = false; @@ -205,7 +199,7 @@ static void do_shared_vertexcol(Mesh *me, bool *mlooptag) 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; + 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; @@ -228,7 +222,7 @@ static void do_shared_vertexcol(Mesh *me, bool *mlooptag) 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; + 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]) { @@ -292,15 +286,6 @@ static int wpaint_mirror_vgroup_ensure(Object *ob, const int vgroup_active) 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) { @@ -310,19 +295,6 @@ static void free_wpaint_prev(VPaint *vp) } } -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); @@ -338,9 +310,8 @@ static void copy_wpaint_prev(VPaint *wp, MDeformVert *dverts, int dcount) bool ED_vpaint_fill(Object *ob, unsigned int paintcol) { Mesh *me; - MPoly *mp; + const MPoly *mp; int i, j; - bool selected; if (((me = BKE_mesh_from_object(ob)) == NULL) || (me->mloopcol == NULL && (make_vertexcol(ob) == false))) @@ -348,13 +319,13 @@ bool ED_vpaint_fill(Object *ob, unsigned int paintcol) return false; } - selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_face_sel = (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)) + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) continue; for (j = 0; j < mp->totloop; j++, lcol++) { @@ -375,7 +346,7 @@ bool ED_vpaint_fill(Object *ob, unsigned int paintcol) bool ED_wpaint_fill(VPaint *wp, Object *ob, float paintweight) { Mesh *me = ob->data; - MPoly *mp; + const MPoly *mp; MDeformWeight *dw, *dw_prev; int vgroup_active, vgroup_mirror = -1; unsigned int index; @@ -458,12 +429,11 @@ bool ED_wpaint_fill(VPaint *wp, Object *ob, float paintweight) bool ED_vpaint_smooth(Object *ob) { Mesh *me; - MPoly *mp; + const MPoly *mp; int i, j; bool *mlooptag; - bool selected; if (((me = BKE_mesh_from_object(ob)) == NULL) || (me->mloopcol == NULL && (make_vertexcol(ob) == false))) @@ -471,17 +441,17 @@ bool ED_vpaint_smooth(Object *ob) return false; } - selected = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + 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++) { - MLoop *ml = me->mloop + mp->loopstart; + const MLoop *ml = me->mloop + mp->loopstart; int ml_index = mp->loopstart; - if (selected && !(mp->flag & ME_FACE_SEL)) + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) continue; for (j = 0; j < mp->totloop; j++, ml_index++, ml++) { @@ -518,13 +488,13 @@ bool ED_vpaint_color_transform( return false; } - const bool do_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + 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 (do_face_sel && !(mp->flag & ME_FACE_SEL)) { + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { continue; } @@ -609,9 +579,18 @@ BLI_INLINE unsigned int mcol_blend(unsigned int col1, unsigned int col2, int fac 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); + /* 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 r2 = cp2[0] * cp2[0]; + int g2 = cp2[1] * cp2[1]; + int b2 = cp2[2] * cp2[2]; + + cp[0] = (unsigned char)round(sqrt(divide_round_i((mfac * r1 + fac * r2), 255))); + cp[1] = (unsigned char)round(sqrt(divide_round_i((mfac * g1 + fac * g2), 255))); + cp[2] = (unsigned char)round(sqrt(divide_round_i((mfac * b1 + fac * b2), 255))); cp[3] = 255; return col; @@ -764,6 +743,8 @@ static unsigned int vpaint_blend_tool(const int tool, const unsigned int col, 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); @@ -776,10 +757,11 @@ static unsigned int vpaint_blend_tool(const int tool, const unsigned int 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 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) { Brush *brush = BKE_paint_brush(&vp->paint); const int tool = brush->vertexpaint_tool; @@ -813,49 +795,10 @@ static unsigned int vpaint_blend(VPaint *vp, unsigned int col, unsigned int colo } -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); - } - - return tot; -} - /* 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 */ @@ -891,10 +834,11 @@ 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); @@ -960,6 +904,8 @@ static float wpaint_blend_tool(const int tool, { 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); @@ -973,9 +919,9 @@ static float wpaint_blend_tool(const int tool, } /* vpaint has 'vpaint_blend' */ -static float wpaint_blend(VPaint *wp, float weight, float weight_prev, +static float wpaint_blend(VPaint *wp, float weight, const float alpha, float paintval, - const float brush_alpha_value, + const float UNUSED(brush_alpha_value), const short do_flip) { Brush *brush = BKE_paint_brush(&wp->paint); @@ -1000,21 +946,6 @@ static float wpaint_blend(VPaint *wp, float weight, float weight_prev, 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); - - 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; - } - } - return weight; } @@ -1164,7 +1095,7 @@ static EnumPropertyItem *weight_paint_sample_enum_itemf( } else { if (ED_mesh_pick_face(C, vc.obact, mval, &index, ED_MESH_PICK_DEFAULT_FACE_SIZE)) { - MPoly *mp = &me->mpoly[index]; + const MPoly *mp = &me->mpoly[index]; unsigned int fidx = mp->totloop - 1; do { @@ -1476,7 +1407,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, @@ -1530,8 +1461,8 @@ static void do_weight_paint_vertex_single( Mesh *me = ob->data; MDeformVert *dv = &me->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - MDeformWeight *dw, *dw_prev; + + MDeformWeight *dw; /* mirror vars */ int index_mirr; @@ -1542,14 +1473,12 @@ static void do_weight_paint_vertex_single( 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) { + if (dw == NULL) { return; } @@ -1607,7 +1536,7 @@ static void do_weight_paint_vertex_single( * then there is no need to run the more complicated checks */ { - dw->weight = wpaint_blend(wp, dw->weight, dw_prev->weight, alpha, paintweight, + dw->weight = wpaint_blend(wp, dw->weight, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); /* WATCH IT: take care of the ordering of applying mirror -> normalize, @@ -1673,7 +1602,6 @@ static void do_weight_paint_vertex_multi( { 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 */ @@ -1681,7 +1609,7 @@ static void do_weight_paint_vertex_multi( MDeformVert *dv_mirr = NULL; /* weights */ - float oldw, curw, neww, change, curw_mirr, change_mirr; + float curw, 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) { @@ -1693,8 +1621,6 @@ static void do_weight_paint_vertex_multi( } /* 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); @@ -1703,7 +1629,7 @@ static void do_weight_paint_vertex_multi( return; } - neww = wpaint_blend(wp, curw, oldw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); + neww = wpaint_blend(wp, curw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip); change = neww / curw; @@ -1769,6 +1695,59 @@ 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(Scene *scene, Object *ob) +{ + if (ob->sculpt == NULL) { + ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); + BKE_sculpt_update_mesh_elements(scene, scene->toolsettings->sculpt, ob, 0, false); + } +} + +static void vertex_paint_init_session_maps(Object *ob) +{ + /* Create maps */ + if (ob->sculpt->modes.vwpaint.vert_to_loop == NULL) { + Mesh *me = ob->data; + ob->sculpt->modes.vwpaint.vert_map_mem = NULL; + ob->sculpt->modes.vwpaint.vert_to_loop = NULL; + ob->sculpt->modes.vwpaint.poly_map_mem = NULL; + ob->sculpt->modes.vwpaint.vert_to_poly = NULL; + BKE_mesh_vert_loop_map_create( + &ob->sculpt->modes.vwpaint.vert_to_loop, + &ob->sculpt->modes.vwpaint.vert_map_mem, + me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); + BKE_mesh_vert_poly_map_create( + &ob->sculpt->modes.vwpaint.vert_to_poly, + &ob->sculpt->modes.vwpaint.poly_map_mem, + me->mpoly, me->mloop, me->totvert, me->totpoly, me->totloop); + } +} + +static void vertex_paint_init_session_average_arrays(Object *ob) +{ + /* Create average brush arrays */ + if (!ob->sculpt->modes.vwpaint.tot_loops_hit) { + int totNode = 0; + /* I think the totNodes might include internal nodes, and we really only need the tot leaves. */ + BKE_pbvh_get_num_nodes(ob->sculpt->pbvh, &totNode); + Mesh *me = BKE_mesh_from_object(ob); + + ob->sculpt->modes.vwpaint.total_color = + MEM_callocN(totNode * 3 * sizeof(unsigned int), "total_color"); + ob->sculpt->modes.vwpaint.total_weight = + MEM_callocN(totNode * sizeof(double), "total_weight"); + ob->sculpt->modes.vwpaint.tot_loops_hit = + MEM_callocN(totNode * sizeof(unsigned int), "tot_loops_hit"); + ob->sculpt->modes.vwpaint.max_weight = + MEM_callocN(me->totvert * sizeof(float), "max_weight"); + ob->sculpt->modes.vwpaint.previous_color = + MEM_callocN(me->totloop * sizeof(unsigned int), "previous_color"); + } +} + /* *************** set wpaint operator ****************** */ /** @@ -1805,6 +1784,14 @@ 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 { @@ -1820,6 +1807,12 @@ 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(scene, ob); } /* Weightpaint works by overriding colors in mesh, @@ -1877,7 +1870,6 @@ struct WPaintVGroupIndex { struct WPaintData { ViewContext vc; - int *indexar; struct WeightPaintGroupData active, mirror; @@ -1901,8 +1893,6 @@ struct WPaintData { int *vmap_mem; } blur_data; - BLI_Stack *accumulate_stack; /* for reuse (WPaintDefer) */ - int defbase_tot; }; @@ -1982,19 +1972,129 @@ static bool wpaint_ensure_data( return true; } -static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UNUSED(mouse[2])) +/* Initialize the stroke cache invariants from operator properties */ +static void vwpaint_update_cache_invariants( + bContext *C, VPaint *vd, 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; + Brush *brush = BKE_paint_brush(&vd->paint); + ViewContext *vc = paint_stroke_view_context(op->customdata); + Object *ob = CTX_data_active_object(C); + float mat[3][3]; + float view_dir[3] = {0.0f, 0.0f, 1.0f}; + int mode; + + /* VW paint needs to allocate stroke cache before update is called. */ + if (!ss->cache) { + cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); + ss->cache = cache; + } + else { + cache = ss->cache; + } + + /* Initial mouse location */ + if (mouse) + copy_v2_v2(cache->initial_mouse, mouse); + else + zero_v2(cache->initial_mouse); + + 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; + + 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; + + /* cache projection matrix */ + ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); + + 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 *vd, Object *ob, PointerRNA *ptr) +{ + Scene *scene = CTX_data_scene(C); + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + Brush *brush = BKE_paint_brush(&vd->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); + } + + 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"); + } + + /* 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); + } + } + + 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 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 *vd = CTX_data_tool_settings(C)->wpaint; float mat[4][4], imat[4][4]; @@ -2094,62 +2194,596 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float UN } /* painting on subsurfs should give correct points too, this returns me->totvert amount */ + ob->sculpt->modes.vwpaint.building_vp_handle = true; 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__); - } + ob->sculpt->modes.vwpaint.building_vp_handle = false; /* imat for normals */ mul_m4_m4m4(mat, wpd->vc.rv3d->viewmat, ob->obmat); 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(scene, ob); + vwpaint_update_cache_invariants(C, vd, ss, op, mouse); + vertex_paint_init_session_maps(ob); + vertex_paint_init_session_average_arrays(ob); + + for (int i = 0; i < me->totvert; i++) + ss->modes.vwpaint.max_weight[i] = -1.0; + return true; } -static float wpaint_blur_weight_single(const MDeformVert *dv, const WeightPaintInfo *wpi) +static void calc_area_normal_and_center_task_cb(void *userdata, const int n) { - return defvert_find_weight(dv, wpi->active.index); + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + float(*area_nos)[3] = data->area_nos; + float(*area_cos)[3] = data->area_cos; + + float private_co[2][3] = {{0.0f}}; + float private_no[2][3] = {{0.0f}}; + int private_count[2] = {0}; + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + const float *co; + + co = vd.co; + + if (sculpt_brush_test_fast(&test, co)) { + float no_buf[3]; + const float *no; + int flip_index; + + if (vd.no) { + normal_short_to_float_v3(no_buf, vd.no); + no = no_buf; + } + else { + no = vd.fno; + } + + flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); + if (area_cos) + add_v3_v3(private_co[flip_index], co); + if (area_nos) + add_v3_v3(private_no[flip_index], no); + private_count[flip_index] += 1; + } + } + BKE_pbvh_vertex_iter_end; + + + BLI_mutex_lock(&data->mutex); + + /* for flatten center */ + if (area_cos) { + add_v3_v3(area_cos[0], private_co[0]); + add_v3_v3(area_cos[1], private_co[1]); + } + + /* for area normal */ + if (area_nos) { + add_v3_v3(area_nos[0], private_no[0]); + add_v3_v3(area_nos[1], private_no[1]); + } + + /* weights */ + data->count[0] += private_count[0]; + data->count[1] += private_count[1]; + + BLI_mutex_unlock(&data->mutex); } -static float wpaint_blur_weight_multi(const MDeformVert *dv, const WeightPaintInfo *wpi) +static void calc_area_normal( + VPaint *vp, Object *ob, + PBVHNode **nodes, int totnode, + float r_area_no[3]) { - 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; + /* 0=towards view, 1=flipped */ + float area_nos[2][3] = {{0.0f}}; + + int count[2] = {0}; + + SculptThreadedTaskData data = { + .vp = vp, .ob = ob, .nodes = nodes, .totnode = totnode, + .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, true); + + BLI_mutex_end(&data.mutex); + + /* for area normal */ + for (int i = 0; i < ARRAY_SIZE(area_nos); i++) { + if (normalize_v3_v3(r_area_no, area_nos[i]) != 0.0f) { + break; + } + } +} + +static float dot_vf3vs3(const float brushNormal[3], const short vertexNormal[3]) +{ + float normal[3]; + normal_short_to_float_v3(normal, vertexNormal); + return dot_v3v3(brushNormal, normal); +} + +/* 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( + VPaint *vd, StrokeCache *cache, const char symm, + const char axis, const float angle) +{ + (void)vd; /* 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); + + unit_m4(cache->symm_rot_mat); + unit_m4(cache->symm_rot_mat_inv); + zero_v3(cache->plane_offset); + + if (axis) { /* expects XYZ */ + rotate_m4(cache->symm_rot_mat, axis, angle); + rotate_m4(cache->symm_rot_mat_inv, axis, -angle); + } + + mul_m4_v3(cache->symm_rot_mat, cache->location); + mul_m4_v3(cache->symm_rot_mat, cache->last_location); + mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); + + if (cache->supports_gravity) { + flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); + mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); + } + + if (cache->is_rake_rotation_valid) { + flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); + } +} + +static void get_brush_alpha_data( + Scene *scene, SculptSession *ss, Brush *brush, + float *r_brush_size_pressure, float *r_brush_alpha_value, float *r_brush_alpha_pressure) +{ + *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 = + *r_brush_alpha_value * + (BKE_brush_use_alpha_pressure(scene, brush) ? ss->cache->pressure : 1.0f); } -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 void do_wpaint_brush_blur_task_cb_ex( + void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id)) { - 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); + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh); + + Brush *brush = data->brush; + StrokeCache *cache = ss->cache; + Scene *scene = CTX_data_scene(data->C); + const float brush_strength = cache->bstrength; + 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_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; + sculpt_brush_test_init(ss, &test); + + /* 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(&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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.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 += defvert_find_weight(dv, data->wpi->active.index); + } + } + + /* Apply the weight to the vertex. */ + if (total_hit_loops != 0) { + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + const float final_alpha = + view_dot * brush_fade * brush_strength * + grid_alpha * brush_alpha_pressure; + 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); + } + } + } } - paintweight /= map->count; } - else { - paintweight = blur_weight_func(&dvert[vidx], wpi); + 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); + + Brush *brush = data->brush; + Scene *scene = CTX_data_scene(data->C); + StrokeCache *cache = ss->cache; + const float brush_strength = cache->bstrength; + 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_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); + if (normalize_v3(brush_dir) != 0.0f) { + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + /* 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_fast(&test, vd.co)) { + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + bool do_color = false; + + /* 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]; + 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) { + /* 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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; + const MPoly *mp = &data->me->mpoly[p_index]; + for (int k = 0; k < mp->totloop; k++) { + const unsigned int l_index = mp->loopstart + k; + const MLoop *ml = &data->me->mloop[l_index]; + const unsigned int v_other_index = ml->v; + 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); + 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; + MDeformVert *dv = &data->me->dvert[v_other_index]; + weight_final = defvert_find_weight(dv, data->wpi->active.index); + do_color = true; + } + } + } + /* Apply weight to vertex */ + if (do_color) { + const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); + const float final_alpha = + view_dot * 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); + } + } + } + } + } + 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); + Scene *scene = CTX_data_scene(data->C); + + Brush *brush = data->brush; + StrokeCache *cache = ss->cache; + const float brush_strength = cache->bstrength; + const float paintweight = BKE_brush_weight_get(scene, brush); + 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_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; + sculpt_brush_test_init(ss, &test); + + /* 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(&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) { + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); + float final_alpha = view_dot * brush_fade * brush_strength * grid_alpha * brush_alpha_pressure; + + /* Spray logic */ + if ((data->vp->flag & VP_SPRAY) == 0) { + MDeformVert *dv = &data->me->dvert[v_index]; + const MDeformWeight *dw; + dw = (data->vp->flag & VP_ONLYVGROUP) ? + defvert_find_index(dv, data->wpi->active.index) : + defvert_verify_index(dv, data->wpi->active.index); + const float weight_curr = dw->weight; + if (ss->modes.vwpaint.max_weight[v_index] < 0) { + ss->modes.vwpaint.max_weight[v_index] = min_ff(brush_strength + weight_curr, 1.0f); + } + CLAMP(final_alpha, 0.0, ss->modes.vwpaint.max_weight[v_index] - weight_curr); - return paintweight; + if (weight_curr >= ss->modes.vwpaint.max_weight[v_index]) { + 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_ave_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); + + double weight = 0.0; + + data->ob->sculpt->modes.vwpaint.tot_loops_hit[n] = 0.0; + data->ob->sculpt->modes.vwpaint.total_weight[n] = 0.0; + 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; + sculpt_brush_test_init(ss, &test); + + /* 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(&test, vd.co)) { + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 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) { + ss->modes.vwpaint.tot_loops_hit[n] += ss->modes.vwpaint.vert_to_loop[v_index].count; + /* if a vertex is within the brush region, then add it's weight to the total weight. */ + for (int j = 0; j < ss->modes.vwpaint.vert_to_loop[v_index].count; j++) { + const int l_index = ss->modes.vwpaint.vert_to_loop[v_index].indices[j]; + + const MLoop *ml = &data->me->mloop[l_index]; + const MDeformVert *dv = &data->me->dvert[ml->v]; + weight += defvert_find_weight(dv, data->wpi->active.index); + } + } + } + } + } + BKE_pbvh_vertex_iter_end; + data->ob->sculpt->modes.vwpaint.total_weight[n] = weight; +} + +static void calculate_average_weight(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) +{ + Scene *scene = CTX_data_scene(data->C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + BLI_task_parallel_range_ex( + 0, totnode, data, NULL, 0, do_wpaint_brush_calc_ave_weight_cb_ex, + ((data->sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT), false); + + unsigned int total_hit_loops = 0; + double total_weight = 0.0; + for (int i = 0; i < totnode; i++) { + total_hit_loops += data->ob->sculpt->modes.vwpaint.tot_loops_hit[i]; + total_weight += data->ob->sculpt->modes.vwpaint.total_weight[i]; + } + if (total_hit_loops != 0) { + total_weight /= total_hit_loops; + if (ups->flag & UNIFIED_PAINT_WEIGHT) + ups->weight = (float)total_weight; + else + data->brush->weight = (float)total_weight; + } +} + + +static void wpaint_paint_leaves( + bContext *C, Object *ob, Sculpt *sd, VPaint *vp, struct WPaintData *wpd, WeightPaintInfo *wpi, + Mesh *me, PBVHNode **nodes, int totnode) +{ + 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, + }; + + 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, true, false); + break; + case PAINT_BLEND_SMEAR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_smear_task_cb_ex, true, false); + break; + case PAINT_BLEND_BLUR: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_blur_task_cb_ex, true, false); + break; + default: + BLI_task_parallel_range_ex( + 0, totnode, &data, NULL, 0, + do_wpaint_brush_draw_task_cb_ex, true, false); + break; + } +} + +static void wpaint_do_paint( + bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi, + Mesh *me, Brush *UNUSED(brush), const char symm, const int axis, const int i, const float angle) +{ + SculptSession *ss = ob->sculpt; + ss->cache->radial_symmetry_pass = i; + calc_brushdata_symm(wp, ss->cache, symm, axis, angle); + + 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 = true; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); + + calc_area_normal(wp, ob, nodes, totnode, ss->cache->sculpt_normal_symm); + 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); + } +} + +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; + calc_brushdata_symm(wp, 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) @@ -2160,24 +2794,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; @@ -2190,13 +2817,8 @@ 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); @@ -2204,7 +2826,6 @@ static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, P /* 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; @@ -2222,180 +2843,49 @@ 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; - } - - /* 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); - - 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; + /* 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_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; + DAG_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); - - DAG_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) @@ -2407,23 +2897,9 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) 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); - } - MEM_freeN(wpd); } - /* frees prev buffer */ - copy_wpaint_prev(ts->wpaint, NULL, 0); - /* and particles too */ if (ob->particlesystem.first) { ParticleSystem *psys; @@ -2442,6 +2918,9 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) DAG_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; } @@ -2449,9 +2928,10 @@ 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); @@ -2468,9 +2948,10 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) 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); @@ -2480,6 +2961,12 @@ 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); } @@ -2570,6 +3057,14 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) BKE_mesh_flush_select_from_polys(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 { @@ -2585,6 +3080,16 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) 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(scene, ob); } /* update modifier stack for mapping requirements */ @@ -2638,13 +3143,12 @@ typedef struct PolyFaceMap { int facenr; } PolyFaceMap; -typedef struct VPaintData { +struct VPaintData { ViewContext vc; unsigned int paintcol; - int *indexar; struct VertProjHandle *vp_handle; - DMCoNo *vertexcosnos; + struct DMCoNo *vertexcosnos; float vpimat[3][3]; @@ -2657,9 +3161,9 @@ 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])) +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; @@ -2670,6 +3174,7 @@ 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; /* context checks could be a poll() */ me = BKE_mesh_from_object(ob); @@ -2682,13 +3187,10 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f 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); - - vpd->indexar = get_indexarray(me); vpd->paintcol = vpaint_get_current_col(scene, vp); vpd->is_texbrush = !(brush->vertexpaint_tool == PAINT_BLEND_BLUR) && @@ -2710,84 +3212,517 @@ 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); - + /* Create projection handle */ + if (vpd->is_texbrush) { + ob->sculpt->modes.vwpaint.building_vp_handle = true; + vpd->vp_handle = ED_vpaint_proj_handle_create(scene, ob, &vpd->vertexcosnos); + ob->sculpt->modes.vwpaint.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(scene, ob); + vwpaint_update_cache_invariants(C, vp, ss, op, mouse); + vertex_paint_init_session_maps(ob); + vertex_paint_init_session_average_arrays(ob); + + for (int i = 0; i < me->totloop; i++) { + ob->sculpt->modes.vwpaint.previous_color[i] = 0; + } + 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_ave_color_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); + + StrokeCache *cache = ss->cache; + unsigned int *lcol = data->lcol; + unsigned int blend[3] = {0}; + char *col; + data->ob->sculpt->modes.vwpaint.tot_loops_hit[n] = 0; + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + /* 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_fast(&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, test.dist, cache->radius) > 0.0) { + /* If the vertex is selected for painting. */ + const MVert *mv = &data->me->mvert[v_index]; + if (!use_face_sel || mv->flag & SELECT) { + ss->modes.vwpaint.tot_loops_hit[n] += ss->modes.vwpaint.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 < ss->modes.vwpaint.vert_to_loop[v_index].count; j++) { + const int l_index = ss->modes.vwpaint.vert_to_loop[v_index].indices[j]; + col = (char *)(&lcol[l_index]); + /* Color is squared to compensate the sqrt color encoding. */ + blend[0] += col[0] * col[0]; + blend[1] += col[1] * col[1]; + blend[2] += col[2] * col[2]; + } + } + } + } + } + BKE_pbvh_vertex_iter_end; + + data->ob->sculpt->modes.vwpaint.total_color[n][0] = blend[0]; + data->ob->sculpt->modes.vwpaint.total_color[n][1] = blend[1]; + data->ob->sculpt->modes.vwpaint.total_color[n][2] = blend[2]; +} + +static void handle_texture_brush( + SculptThreadedTaskData *data, PBVHVertexIter vd, float size_pressure, float alpha_pressure, + float *r_alpha, unsigned int *r_color) { - 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; + 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 unsigned char *)&data->vpd->paintcol); + mul_v3_v3(rgba_br, rgba); + rgb_float_to_uchar((unsigned char *)r_color, rgba_br); +} - int brush_alpha_pressure_i = (int)(brush_alpha_pressure * 255.0f); +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); + + Brush *brush = data->brush; + StrokeCache *cache = ss->cache; + const float brush_strength = cache->bstrength; + unsigned int *lcol = data->lcol; + 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_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + /* 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(&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_face_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. */ + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); + unsigned int 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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; + const int l_index = ss->modes.vwpaint.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 */ + if (ss->modes.vwpaint.previous_color[l_index] == 0) { + ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; + } + const float final_alpha = + 255 * brush_fade * brush_strength * view_dot * + 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], + ss->modes.vwpaint.previous_color[l_index], color_final, + final_alpha, 255 * brush_strength); + } + } + } + } + } + } + BKE_pbvh_vertex_iter_end; +} - if (brush->vertexpaint_tool == PAINT_BLEND_BLUR) { - unsigned int blend[4] = {0}; - unsigned int tcol; - char *col; - - 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]; - } - - 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]; - - vpd->paintcol = *((unsigned int *)col); - } - - 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); - - 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); +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); + + Brush *brush = data->brush; + StrokeCache *cache = ss->cache; + const float brush_strength = cache->bstrength; + unsigned int *lcol = data->lcol; + 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_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + /* 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(&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]; + + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius); + + /* If the vertex is selected for painting. */ + if (!use_face_sel || mv->flag & SELECT) { + /* Get the average poly color */ + unsigned int color_final = 0; + int total_hit_loops = 0; + unsigned int blend[4] = {0}; + for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + int p_index = ss->modes.vwpaint.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 unsigned int l_index = mp->loopstart + k; + const char *col = (const char *)(&lcol[l_index]); + /* Color is squared to compensate the sqrt color encoding. */ + blend[0] += (unsigned int)col[0] * (unsigned int)col[0]; + blend[1] += (unsigned int)col[1] * (unsigned int)col[1]; + blend[2] += (unsigned int)col[2] * (unsigned int)col[2]; + blend[3] += (unsigned int)col[3] * (unsigned int)col[3]; + } + } + } + if (total_hit_loops != 0) { + /* Use rgb^2 color averaging. */ + char *col = (char *)(&color_final); + col[0] = (unsigned char)round(sqrtl(divide_round_i(blend[0], total_hit_loops))); + col[1] = (unsigned char)round(sqrtl(divide_round_i(blend[1], total_hit_loops))); + col[2] = (unsigned char)round(sqrtl(divide_round_i(blend[2], total_hit_loops))); + col[3] = (unsigned char)round(sqrtl(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 < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; + const int l_index = ss->modes.vwpaint.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 */ + if (ss->modes.vwpaint.previous_color[l_index] == 0) { + ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; + } + const float final_alpha = + 255 * brush_fade * brush_strength * view_dot * + 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], + ss->modes.vwpaint.previous_color[l_index], + *((unsigned int *)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); + + Brush *brush = data->brush; + StrokeCache *cache = ss->cache; + const float brush_strength = cache->bstrength; + unsigned int *lcol = data->lcol; + 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); + float brush_dir[3]; + const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + sub_v3_v3v3(brush_dir, cache->location, cache->last_location); + if (normalize_v3(brush_dir) != 0.0f) { + + SculptBrushTest test; + sculpt_brush_test_init(ss, &test); + + /* 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(&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_face_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. */ + const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0; + if (view_dot > 0.0f) { + const float brush_fade = BKE_brush_curve_strength(brush, 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 */ + unsigned int color_final = 0; + for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; + const int l_index = ss->modes.vwpaint.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) { + for (int k = 0; k < mp->totloop; k++) { + const MLoop *ml = &data->me->mloop[l_index]; + const unsigned int v_other_index = ml->v; + 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); + 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 = lcol[l_index]; + do_color = true; + } + } + } + } + + if (do_color) { + /* For each poly owning this vert, paint each loop belonging to this vert. */ + for (int j = 0; j < ss->modes.vwpaint.vert_to_poly[v_index].count; j++) { + const int p_index = ss->modes.vwpaint.vert_to_poly[v_index].indices[j]; + const int l_index = ss->modes.vwpaint.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 */ + if (ss->modes.vwpaint.previous_color[l_index] == 0) { + ss->modes.vwpaint.previous_color[l_index] = lcol[l_index]; + } + const float final_alpha = + 255 * brush_fade * brush_strength * + view_dot * 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], + ss->modes.vwpaint.previous_color[l_index], color_final, + final_alpha, 255 * brush_strength); + } + } + } + } + } + } + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calculate_average_color(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) +{ + BLI_task_parallel_range_ex( + 0, totnode, data, NULL, 0, do_vpaint_brush_calc_ave_color_cb_ex, + true, false); + + unsigned int total_hit_loops = 0; + unsigned int total_color[3] = {0}; + unsigned char blend[4] = {0}; + for (int i = 0; i < totnode; i++) { + total_hit_loops += data->ob->sculpt->modes.vwpaint.tot_loops_hit[i]; + total_color[0] += data->ob->sculpt->modes.vwpaint.total_color[i][0]; + total_color[1] += data->ob->sculpt->modes.vwpaint.total_color[i][1]; + total_color[2] += data->ob->sculpt->modes.vwpaint.total_color[i][2]; + } + if (total_hit_loops != 0) { + blend[0] = (unsigned char)round(sqrtl(divide_round_i(total_color[0], total_hit_loops))); + blend[1] = (unsigned char)round(sqrtl(divide_round_i(total_color[1], total_hit_loops))); + blend[2] = (unsigned char)round(sqrtl(divide_round_i(total_color[2], total_hit_loops))); + blend[3] = 255; + data->vpd->paintcol = *((unsigned int *)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); +static void vpaint_paint_leaves( + bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, + Object *ob, Mesh *me, PBVHNode **nodes, int totnode) +{ + Brush *brush = ob->sculpt->cache->brush; - if (mlooptag) mlooptag[i] = 1; + SculptThreadedTaskData data = { + .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .vp = vp, .vpd = vpd, + .lcol = (unsigned int *)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 *vd, struct VPaintData *vpd, + Object *ob, Mesh *me, Brush *UNUSED(brush), const char symm, const int axis, const int i, const float angle) +{ + SculptSession *ss = ob->sculpt; + ss->cache->radial_symmetry_pass = i; + calc_brushdata_symm(vd, ss->cache, symm, axis, angle); + 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 = true; + BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode); + + calc_area_normal(vd, ob, nodes, totnode, ss->cache->sculpt_normal_symm); + + /* Paint those leaves. */ + vpaint_paint_leaves(C, sd, vd, vpd, ob, me, nodes, totnode); + + if (nodes) { + MEM_freeN(nodes); + } +} + +static void vpaint_do_radial_symmetry( + bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob, Mesh *me, + Brush *brush, const char symm, const int axis) +{ + for (int i = 1; i < vd->radial_symm[axis - 'X']; i++) { + const float angle = (2.0 * M_PI) * i / vd->radial_symm[axis - 'X']; + vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, symm, axis, i, angle); + } +} + +static void vpaint_do_symmetrical_brush_actions( + bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob) +{ + Brush *brush = BKE_paint_brush(&vd->paint); + Mesh *me = ob->data; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const char symm = vd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + int i = 0; + + /* initial stroke */ + vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X'); + vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y'); + vpaint_do_radial_symmetry(C, sd, vd, 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; + calc_brushdata_symm(vd, cache, i, 0, 0); + + if (i & (1 << 0)) { + vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X'); + } + if (i & (1 << 1)) { + vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Y', 0, 0); + vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y'); + } + if (i & (1 << 2)) { + vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Z', 0, 0); + vpaint_do_radial_symmetry(C, sd, vd, 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) @@ -2796,65 +3731,26 @@ 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); + vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob); - /* clear modified tag for blur tool */ - if (vpd->mlooptag) - memset(vpd->mlooptag, 0, sizeof(bool) * me->totloop); - - 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); - } - /* calculate pivot for rotation around seletion if needed */ /* also needed for "View Selected" on last stroke */ paint_last_stroke_update(scene, vc->ar, mval); @@ -2874,32 +3770,26 @@ 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); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - DAG_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, + 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); @@ -2919,7 +3809,7 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) static int vpaint_exec(bContext *C, wmOperator *op) { - op->customdata = paint_stroke_new(C, op, NULL, vpaint_stroke_test_start, + op->customdata = paint_stroke_new(C, op, sculpt_stroke_get_location, vpaint_stroke_test_start, vpaint_stroke_update_step, NULL, vpaint_stroke_done, 0); @@ -2931,6 +3821,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); } @@ -3115,7 +4011,7 @@ static void gradientVertInit__mapFunc( { /* ok */ MDeformVert *dv = &me->dvert[index]; - MDeformWeight *dw; + const MDeformWeight *dw; dw = defvert_find_index(dv, grad_data->def_nr); if (dw) { vs->weight_orig = dw->weight; diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 44cc2720a32..746c560090d 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" @@ -165,131 +164,17 @@ 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 { - BMLog *bm_log; - - SculptUndoNode *unode; - float (*coords)[3]; - short (*normals)[3]; - const float *vmasks; - - /* Original coordinate, normal, and mask */ - const float *co; - const short *no; - float mask; -} SculptOrigVertData; - - /* Initialize a SculptOrigVertData for accessing original vertex data; * handles BMesh, mesh, and multires */ -static void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, +void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, SculptUndoNode *unode) { @@ -311,7 +196,7 @@ static void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, /* Initialize a SculptOrigVertData for accessing original vertex data; * handles BMesh, mesh, and multires */ -static void sculpt_orig_vert_data_init(SculptOrigVertData *data, +void sculpt_orig_vert_data_init(SculptOrigVertData *data, Object *ob, PBVHNode *node) { @@ -322,7 +207,7 @@ static void sculpt_orig_vert_data_init(SculptOrigVertData *data, /* Update a SculptOrigVertData for a particular vertex from the PBVH * iterator */ -static void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, +void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) { if (orig_data->unode->type == SCULPT_UNDO_COORDS) { @@ -406,21 +291,6 @@ static void sculpt_project_v3_normal_align(SculptSession *ss, const float normal madd_v3_v3fl(grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); } - -/** \name SculptProjectVector - * - * Fast-path for #project_plane_v3_v3v3 - * - * \{ */ - -typedef struct SculptProjectVector { - float plane[3]; - float len_sq; - float len_sq_inv_neg; - bool is_valid; - -} SculptProjectVector; - /** * \param plane Direction, can be any length. */ @@ -476,41 +346,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; @@ -600,7 +435,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 +485,7 @@ void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *ar, /************************ 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; @@ -689,7 +514,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(SculptBrushTest *test, const float co[3]) { float distsq = len_squared_v3v3(co, test->location); @@ -705,7 +530,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_sq(SculptBrushTest *test, const float co[3]) { float distsq = len_squared_v3v3(co, test->location); @@ -721,7 +546,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_fast(const SculptBrushTest *test, const float co[3]) { if (sculpt_brush_test_clipping(test, co)) { return 0; @@ -729,7 +554,7 @@ 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_cube(SculptBrushTest *test, const float co[3], float local[4][4]) { float side = M_SQRT1_2; float local_co[3]; @@ -1237,13 +1062,13 @@ 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, 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; @@ -1316,15 +1141,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]; @@ -1632,6 +1450,22 @@ typedef struct SculptDoBrushSmoothGridDataChunk { size_t tmpgrid_size; } SculptDoBrushSmoothGridDataChunk; +typedef struct { + SculptSession *ss; + const float *ray_start, *ray_normal; + bool hit; + float dist; + bool original; + PBVHNode* node; +} SculptRaycastData; + +typedef struct { + const float *ray_start, *ray_normal; + bool hit; + float dist; + float detail; +} SculptDetailRaycastData; + static void do_smooth_brush_mesh_task_cb_ex( void *userdata, void *UNUSED(userdata_chunk), const int n, const int thread_id) { @@ -3948,7 +3782,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); @@ -4398,21 +4232,6 @@ static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob) } } -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,6 +4256,9 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) { srd->hit = 1; *tmin = srd->dist; + + //for vwpaint testing + srd->node = node; } } } @@ -4521,12 +4343,17 @@ bool sculpt_stroke_get_location(bContext *C, float out[3], const float mouse[2]) srd.dist = dist; BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, - ray_start, ray_normal, srd.original); + ray_start, ray_normal, srd.original); copy_v3_v3(out, ray_normal); mul_v3_fl(out, srd.dist); add_v3_v3(out, ray_start); + //used in vwpaint + if (cache && srd.hit){ + copy_v3_v3(cache->true_location, out); + } + return srd.hit; } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 108fe3532e3..b72404f974f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -38,6 +38,8 @@ #include "DNA_key_types.h" #include "BLI_bitmap.h" +#include "BLI_threads.h" + #include "BKE_pbvh.h" struct bContext; @@ -115,6 +117,233 @@ typedef struct SculptUndoNode { char shapeName[sizeof(((KeyBlock *)0))->name]; } SculptUndoNode; +/************** Access to original unmodified vertex data *************/ + +typedef struct SculptOrigVertData { + struct BMLog *bm_log; + + SculptUndoNode *unode; + float(*coords)[3]; + short(*normals)[3]; + const float *vmasks; + + /* Original coordinate, normal, and mask */ + const float *co; + const short *no; + float mask; +} SculptOrigVertData; + + +void sculpt_orig_vert_data_unode_init(SculptOrigVertData *data, + Object *ob, + SculptUndoNode *unode); +void sculpt_orig_vert_data_init(SculptOrigVertData *data, + Object *ob, + PBVHNode *node); +void sculpt_orig_vert_data_update(SculptOrigVertData *orig_data, + PBVHVertexIter *iter); + +/* 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]; +}; + +/** \name SculptProjectVector +* +* Fast-path for #project_plane_v3_v3v3 +* +* \{ */ + +typedef struct SculptProjectVector { + float plane[3]; + float len_sq; + float len_sq_inv_neg; + bool is_valid; + +} SculptProjectVector; + +/* Single struct used by all BLI_task threaded callbacks, let's avoid adding 10's of those... */ +typedef struct SculptThreadedTaskData { + bContext *C; + struct Sculpt *sd; + struct Object *ob; + struct Brush *brush; + struct PBVHNode **nodes; + int totnode; + + struct VPaint *vp; + struct VPaintData *vpd; + struct WPaintData *wpd; + struct WeightPaintInfo *wpi; + unsigned int *lcol; + struct MeshElemMap **vertToLoopMaps; + struct Mesh *me; + + + /* 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; + +/*************** Brush testing declarations ****************/ +typedef struct SculptBrushTest { + float radius_squared; + float location[3]; + float dist; + int mirror_symmetry_pass; + + /* View3d clipping - only set rv3d for clipping */ + struct RegionView3D *clip_rv3d; +} SculptBrushTest; + +typedef struct { + struct Sculpt *sd; + struct SculptSession *ss; + float radius_squared; + bool original; +} SculptSearchSphereData; + +void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test); +bool sculpt_brush_test(SculptBrushTest *test, const float co[3]); +bool sculpt_brush_test_sq(SculptBrushTest *test, const float co[3]); +bool sculpt_brush_test_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_search_sphere_cb(PBVHNode *node, void *data_v); +float tex_strength( + SculptSession *ss, 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); + + +/* 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; + 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_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 +353,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/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index f4a1677efc4..de85ca13f35 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -315,7 +315,9 @@ enum { PAINT_BLEND_MUL = 3, PAINT_BLEND_BLUR = 4, PAINT_BLEND_LIGHTEN = 5, - PAINT_BLEND_DARKEN = 6 + PAINT_BLEND_DARKEN = 6, + PAINT_BLEND_AVERAGE = 7, + PAINT_BLEND_SMEAR = 8, }; typedef enum { diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 6d79e6d49f8..0f341aa4001 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -683,6 +683,9 @@ typedef enum ObjectMode { /* any mode where the brush system is used */ #define OB_MODE_ALL_PAINT (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT) +/* any mode that uses ob->sculpt */ +#define OB_MODE_ALL_SCULPT (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT) + #define MAX_DUPLI_RECUR 8 #ifdef __cplusplus diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c2711c465e1..3503abb606e 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1163,6 +1163,9 @@ typedef struct VPaint { struct MDeformVert *wpaint_prev; /* previous vertex weights */ void *paintcursor; /* wm handle */ + + int radial_symm[3]; /* For mirrored painting */ + int pad2; } VPaint; /* VPaint.flag */ diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index ac348c1750c..7b3636f1615 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -94,6 +94,8 @@ EnumPropertyItem rna_enum_brush_vertex_tool_items[] = { {PAINT_BLEND_BLUR, "BLUR", ICON_BRUSH_BLUR, "Blur", "Blur the color with surrounding values"}, {PAINT_BLEND_LIGHTEN, "LIGHTEN", ICON_BRUSH_LIGHTEN, "Lighten", "Use lighten blending mode while painting"}, {PAINT_BLEND_DARKEN, "DARKEN", ICON_BRUSH_DARKEN, "Darken", "Use darken blending mode while painting"}, + {PAINT_BLEND_AVERAGE, "AVERAGE", ICON_BRUSH_BLUR, "Average", "Use average blending mode while painting" }, + {PAINT_BLEND_SMEAR, "SMEAR", ICON_BRUSH_BLUR, "Smear", "Use smear blending mode while painting" }, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 40aea37d9d2..98dbfa3ae5f 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -684,6 +684,15 @@ static void rna_def_vertex_paint(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_ONLYVGROUP); RNA_def_property_ui_text(prop, "Restrict", "Restrict painting to vertices in the group"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + /* Mirroring */ + prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ); + RNA_def_property_int_sdna(prop, NULL, "radial_symm"); + RNA_def_property_int_default(prop, 1); + RNA_def_property_range(prop, 1, 64); + RNA_def_property_ui_range(prop, 1, 32, 1, 1); + RNA_def_property_ui_text(prop, "Radial Symmetry Count X Axis", + "Number of times to copy strokes across the surface"); } static void rna_def_image_paint(BlenderRNA *brna) diff --git a/source/tools b/source/tools index b11375e8906..4ace84b09b0 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit b11375e89061303401376f7aeae42ac2fd64692a +Subproject commit 4ace84b09b04f62192c0cdf4f3b87a68f45aabe9 -- cgit v1.2.3