Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarshan Kadu <darsh7807@gmail.com>2017-06-01 11:34:06 +0300
committerDarshan Kadu <darsh7807@gmail.com>2017-06-01 11:34:06 +0300
commit29645a58384273512560c6b27eb1c979ea514682 (patch)
treeddd68e1f45e4266e1f2b442fe37e340a2e0ac12a
parent24a0b332e2a72ec164b1398a4a15be7ec0c5f807 (diff)
applied patch D2150.id8618.diff and fixed versioning_270.c
-rw-r--r--p1.orig0
-rw-r--r--p1.rej3708
m---------release/datafiles/locale0
m---------release/scripts/addons0
m---------release/scripts/addons_contrib0
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py48
-rw-r--r--source/blender/blenkernel/BKE_paint.h20
-rw-r--r--source/blender/blenkernel/BKE_pbvh.h6
-rw-r--r--source/blender/blenkernel/intern/CCGSubSurf.c1
-rw-r--r--source/blender/blenkernel/intern/DerivedMesh.c2
-rw-r--r--source/blender/blenkernel/intern/cdderivedmesh.c5
-rw-r--r--source/blender/blenkernel/intern/object.c5
-rw-r--r--source/blender/blenkernel/intern/paint.c29
-rw-r--r--source/blender/blenkernel/intern/pbvh.c19
-rw-r--r--source/blender/blenkernel/intern/pbvh_intern.h2
-rw-r--r--source/blender/blenkernel/intern/subsurf_ccg.c19
-rw-r--r--source/blender/blenloader/intern/versioning_270.c18
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c10
-rw-r--r--source/blender/editors/sculpt_paint/paint_image.c22
-rw-r--r--source/blender/editors/sculpt_paint/paint_intern.h1
-rw-r--r--source/blender/editors/sculpt_paint/paint_ops.c1
-rw-r--r--source/blender/editors/sculpt_paint/paint_vertex.c1888
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c259
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h231
-rw-r--r--source/blender/makesdna/DNA_brush_types.h4
-rw-r--r--source/blender/makesdna/DNA_object_types.h3
-rw-r--r--source/blender/makesdna/DNA_scene_types.h3
-rw-r--r--source/blender/makesrna/intern/rna_brush.c2
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c9
m---------source/tools0
30 files changed, 5587 insertions, 728 deletions
diff --git a/p1.orig b/p1.orig
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/p1.orig
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
-Subproject c93ed11a47b3016cf59711ec16de2e2e94c30e9
+Subproject 19a637ce9f38112146daca394af4a7db1bae668
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 371960484a38fc64e0a2635170a41a0d8ab2f6b
+Subproject 0926c1e7dcbe566d3a92116c6e8f91ba440f378
diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib
-Subproject a8515cfdfe9a98127b592f36fcbe51b7e23b969
+Subproject 706fce2d1d195096d081b92ba47b43a38dc120f
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
-Subproject b11375e89061303401376f7aeae42ac2fd64692
+Subproject 4ace84b09b04f62192c0cdf4f3b87a68f45aabe