Vertex Paint: projection options
authorCampbell Barton <ideasman42@gmail.com>
Mon, 2 Oct 2017 10:07:25 +0000 (21:07 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Mon, 2 Oct 2017 11:07:58 +0000 (22:07 +1100)
This makes vertex paint match image painting more closely.

- Add falloff shape option sphere/circle
  where sphere uses a 3D radius around the cursor and
  circle uses a 2D radius (projected), like previous releases.
- Add normal angle option so you can control the falloff.
- Add Cull option, to paint onto faces pointing away.

Disabling normals, culling and using circle falloff
allows you to paint through the mesh.

release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/BKE_brush.h
source/blender/blenkernel/BKE_paint.h
source/blender/blenkernel/intern/brush.c
source/blender/blenloader/intern/versioning_270.c
source/blender/editors/sculpt_paint/paint_vertex.c
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_intern.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_sculpt_paint.c

index d260bfe3460320322a1893acdc6331009c004d32..798b5e28d9c83150081f55bf87b73d231d382f09 100644 (file)
@@ -1761,9 +1761,16 @@ class VIEW3D_PT_tools_weightpaint_options(Panel, View3DPaintPanel):
         wpaint = tool_settings.weight_paint
 
         col = layout.column()
+        col.label("Falloff:")
         row = col.row()
-
-        row.prop(wpaint, "use_normal")
+        row.prop(wpaint, "falloff_shape", expand=True)
+        row = col.row()
+        row.prop(wpaint, "use_backface_culling")
+        row = col.row()
+        row.prop(wpaint, "use_normal_falloff")
+        sub = row.row()
+        sub.active = (wpaint.use_normal_falloff)
+        sub.prop(wpaint, "normal_angle", text="")
         col = layout.column()
         row = col.row()
         row.prop(wpaint, "use_spray")
@@ -1798,19 +1805,21 @@ class VIEW3D_PT_tools_vertexpaint(Panel, View3DPaintPanel):
         vpaint = toolsettings.vertex_paint
 
         col = layout.column()
+        col.label("Falloff:")
+        row = col.row()
+        row.prop(vpaint, "falloff_shape", expand=True)
         row = col.row()
-        # col.prop(vpaint, "mode", text="")
-        row.prop(vpaint, "use_normal")
+        row.prop(vpaint, "use_backface_culling")
+        row = col.row()
+        row.prop(vpaint, "use_normal_falloff")
+        sub = row.row()
+        sub.active = (vpaint.use_normal_falloff)
+        sub.prop(vpaint, "normal_angle", text="")
+
         col.prop(vpaint, "use_spray")
 
         self.unified_paint_settings(col, context)
 
-# Commented out because the Apply button isn't an operator yet, making these settings useless
-#~         col.label(text="Gamma:")
-#~         col.prop(vpaint, "gamma", text="")
-#~         col.label(text="Multiply:")
-#~         col.prop(vpaint, "mul", text="")
-
 
 class VIEW3D_PT_tools_vertexpaint_symmetry(Panel, View3DPaintPanel):
     bl_category = "Tools"
index dedb75a080a569bcdf7c515043597b2552ab9e6f..c1e107e101ac791b5305449c6499e1e0cfe2feeb 100644 (file)
@@ -70,7 +70,7 @@ void BKE_brush_randomize_texture_coords(struct UnifiedPaintSettings *ups, bool m
 /* brush curve */
 void BKE_brush_curve_preset(struct Brush *b, int preset);
 float BKE_brush_curve_strength_clamped(struct Brush *br, float p, const float len);
-float BKE_brush_curve_strength(struct Brush *br, float p, const float len);
+float BKE_brush_curve_strength(const struct Brush *br, float p, const float len);
 
 /* sampling */
 float BKE_brush_sample_tex_3D(
index fe7461c810d2208c73948bab1c69ae12d29fea89..19f332f5f54b17e7e5a286b5f032a7377ed45b93 100644 (file)
@@ -231,7 +231,7 @@ typedef struct SculptSession {
                        /* Keep track of how much each vertex has been painted (non-airbrush only). */
                        float *alpha_weight;
 
-                       /* Needed to continuously re-apply over the same weights (VP_SPRAY disabled).
+                       /* Needed to continuously re-apply over the same weights (VP_FLAG_SPRAY disabled).
                         * Lazy initialize as needed (flag is set to 1 to tag it as uninitialized). */
                        struct MDeformVert *dvert_prev;
                } wpaint;
index 03b0710c8fc1f0bb6cfdcfda337ccd25c074c62d..0300dfe5891504b3b36452f8fb7da840cfb46a38 100644 (file)
@@ -966,7 +966,7 @@ void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask)
 }
 
 /* Uses the brush curve control to find a strength value */
-float BKE_brush_curve_strength(Brush *br, float p, const float len)
+float BKE_brush_curve_strength(const Brush *br, float p, const float len)
 {
        float strength;
 
index 8459e080f99295ad56fdb12c5560b2572341f39b..b374e49fc836c1f4ebca6b3f51a87ce12cb6b940 100644 (file)
@@ -1699,6 +1699,20 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
                                }
                        }
                }
+
+               if (!DNA_struct_elem_find(fd->filesdna, "VPaint", "char", "falloff_shape")) {
+                       for (Scene *scene = main->scene.first; scene; scene = scene->id.next) {
+                               ToolSettings *ts = scene->toolsettings;
+                               for (int i = 0; i < 2; i++) {
+                                       VPaint *vp = i ? ts->vpaint : ts->wpaint;
+                                       if (vp != NULL) {
+                                               /* remove all other flags */
+                                               vp->flag &= (VP_FLAG_SPRAY | VP_FLAG_VGROUP_RESTRICT);
+                                               vp->normal_angle = 80;
+                                       }
+                               }
+                       }
+               }
        }
 }
 
index 7b534e12f31d4ebd143446a57630185e38d13859..ea85ae7079449cd2aafeeffdbe5519e847301735 100644 (file)
@@ -88,6 +88,66 @@ struct WPaintAverageAccum {
        double value;
 };
 
+struct NormalAnglePrecalc {
+       bool do_mask_normal;
+       /* what angle to mask at */
+       float angle;
+       /* cos(angle), faster to compare */
+       float angle__cos;
+       float angle_inner;
+       float angle_inner__cos;
+       /* difference between angle and angle_inner, for easy access */
+       float angle_range;
+};
+
+
+static void view_angle_limits_init(
+        struct NormalAnglePrecalc *a, float angle, bool do_mask_normal)
+{
+       a->do_mask_normal = do_mask_normal;
+       if (do_mask_normal) {
+               a->angle_inner = angle;
+               a->angle = (a->angle_inner + 90.0f) * 0.5f;
+       }
+       else {
+               a->angle_inner = a->angle = angle;
+       }
+
+       a->angle_inner *=   (float)(M_PI_2 / 90);
+       a->angle *=         (float)(M_PI_2 / 90);
+       a->angle_range = a->angle - a->angle_inner;
+
+       if (a->angle_range <= 0.0f) {
+               a->do_mask_normal = false;  /* no need to do blending */
+       }
+
+       a->angle__cos       = cosf(a->angle);
+       a->angle_inner__cos = cosf(a->angle_inner);
+}
+
+static float view_angle_limits_apply_falloff(
+        const struct NormalAnglePrecalc *a, float angle_cos, float *mask_p)
+{
+       if (angle_cos <= a->angle__cos) {
+               /* outsize the normal limit */
+               return false;
+       }
+       else if (angle_cos < a->angle_inner__cos) {
+               *mask_p *= (a->angle - acosf(angle_cos)) / a->angle_range;
+               return true;
+       }
+       else {
+               return true;
+       }
+}
+
+static bool vwpaint_use_normal(const VPaint *vp)
+{
+       return ((vp->flag & VP_FLAG_PROJECT_BACKFACE) == 0) ||
+              ((vp->flag & VP_FLAG_PROJECT_FLAT) == 0);
+}
+
+
 static void defweight_prev_restore_or_init(MDeformVert *dvert_prev, MDeformVert *dvert_curr, int index)
 {
        MDeformVert *dv_curr = &dvert_curr[index];
@@ -185,7 +245,7 @@ static VPaint *new_vpaint(int wpaint)
 {
        VPaint *vp = MEM_callocN(sizeof(VPaint), "VPaint");
 
-       vp->flag = (wpaint) ? 0 : VP_SPRAY;
+       vp->flag = (wpaint) ? 0 : VP_FLAG_SPRAY;
        vp->paint.flags |= PAINT_SHOW_BRUSH;
 
        return vp;
@@ -213,7 +273,7 @@ static uint vpaint_blend(
        uint color_blend = ED_vpaint_blend_tool(tool, color_curr, color_paint, alpha_i);
 
        /* if no spray, clip color adding with colorig & orig alpha */
-       if ((vp->flag & VP_SPRAY) == 0) {
+       if ((vp->flag & VP_FLAG_SPRAY) == 0) {
                uint color_test, a;
                char *cp, *ct, *co;
 
@@ -299,7 +359,7 @@ static float calc_vp_alpha_col_dl(
        if (strength > 0.0f) {
                float alpha = brush_alpha_pressure * strength;
 
-               if (vp->flag & VP_NORMALS) {
+               if ((vp->flag & VP_FLAG_PROJECT_FLAT) == 0) {
                        float dvec[3];
 
                        /* transpose ! */
@@ -670,7 +730,7 @@ static void do_weight_paint_vertex_single(
                index_mirr = vgroup_mirr = -1;
        }
 
-       if ((wp->flag & VP_SPRAY) == 0) {
+       if ((wp->flag & VP_FLAG_SPRAY) == 0) {
                struct MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev;
                defweight_prev_restore_or_init(dvert_prev, me->dvert, index);
                if (index_mirr != -1) {
@@ -678,7 +738,7 @@ static void do_weight_paint_vertex_single(
                }
        }
 
-       if (wp->flag & VP_ONLYVGROUP) {
+       if (wp->flag & VP_FLAG_VGROUP_RESTRICT) {
                dw = defvert_find_index(dv, wpi->active.index);
        }
        else {
@@ -692,7 +752,7 @@ static void do_weight_paint_vertex_single(
        /* get the mirror def vars */
        if (index_mirr != -1) {
                dv_mirr = &me->dvert[index_mirr];
-               if (wp->flag & VP_ONLYVGROUP) {
+               if (wp->flag & VP_FLAG_VGROUP_RESTRICT) {
                        dw_mirr = defvert_find_index(dv_mirr, vgroup_mirr);
 
                        if (dw_mirr == NULL) {
@@ -814,7 +874,7 @@ static void do_weight_paint_vertex_multi(
                }
        }
 
-       if ((wp->flag & VP_SPRAY) == 0) {
+       if ((wp->flag & VP_FLAG_SPRAY) == 0) {
                struct MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev;
                defweight_prev_restore_or_init(dvert_prev, me->dvert, index);
                if (index_mirr != -1) {
@@ -947,7 +1007,7 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob)
 
        /* Create average brush arrays */
        if (ob->mode == OB_MODE_VERTEX_PAINT) {
-               if ((ts->vpaint->flag & VP_SPRAY) == 0) {
+               if ((ts->vpaint->flag & VP_FLAG_SPRAY) == 0) {
                        if (ob->sculpt->mode.vpaint.previous_color == NULL) {
                                ob->sculpt->mode.vpaint.previous_color =
                                        MEM_callocN(me->totloop * sizeof(uint), __func__);
@@ -968,7 +1028,7 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob)
                }
        }
        else if (ob->mode == OB_MODE_WEIGHT_PAINT) {
-               if ((ts->wpaint->flag & VP_SPRAY) == 0) {
+               if ((ts->wpaint->flag & VP_FLAG_SPRAY) == 0) {
                        if (ob->sculpt->mode.wpaint.alpha_weight == NULL) {
                                ob->sculpt->mode.wpaint.alpha_weight =
                                        MEM_callocN(me->totvert * sizeof(float), __func__);
@@ -1116,6 +1176,7 @@ void PAINT_OT_weight_paint_toggle(wmOperatorType *ot)
 
 struct WPaintData {
        ViewContext vc;
+       struct NormalAnglePrecalc normal_angle_precalc;
 
        struct WeightPaintGroupData active, mirror;
 
@@ -1138,12 +1199,12 @@ struct WPaintData {
 
 /* 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])
+        bContext *C, VPaint *vp, SculptSession *ss, wmOperator *op, const float mouse[2])
 {
        StrokeCache *cache;
        Scene *scene = CTX_data_scene(C);
        UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings;
-       Brush *brush = BKE_paint_brush(&vd->paint);
+       Brush *brush = BKE_paint_brush(&vp->paint);
        ViewContext *vc = paint_stroke_view_context(op->customdata);
        Object *ob = CTX_data_active_object(C);
        float mat[3][3];
@@ -1195,12 +1256,12 @@ static void vwpaint_update_cache_invariants(
 }
 
 /* Initialize the stroke cache variants from operator properties */
-static void vwpaint_update_cache_variants(bContext *C, VPaint *vd, Object *ob, PointerRNA *ptr)
+static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr)
 {
        Scene *scene = CTX_data_scene(C);
        SculptSession *ss = ob->sculpt;
        StrokeCache *cache = ss->cache;
-       Brush *brush = BKE_paint_brush(&vd->paint);
+       Brush *brush = BKE_paint_brush(&vp->paint);
 
        /* This effects the actual brush radius, so things farther away
         * are compared with a larger radius and vise versa. */
@@ -1258,7 +1319,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo
        int defbase_tot, defbase_tot_sel;
        bool *defbase_sel;
        SculptSession *ss = ob->sculpt;
-       VPaint *vd = CTX_data_tool_settings(C)->wpaint;
+       VPaint *vp = CTX_data_tool_settings(C)->wpaint;
 
        float mat[4][4], imat[4][4];
 
@@ -1313,6 +1374,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo
        wpd = MEM_callocN(sizeof(struct WPaintData), "WPaintData");
        paint_stroke_set_mode_data(stroke, wpd);
        view3d_set_viewcontext(C, &wpd->vc);
+       view_angle_limits_init(&wpd->normal_angle_precalc, vp->normal_angle, (vp->flag & VP_FLAG_PROJECT_FLAT) == 0);
 
        wpd->active.index = vgroup_index.active;
        wpd->mirror.index = vgroup_index.mirror;
@@ -1369,7 +1431,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo
 
        /* 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);
+       vwpaint_update_cache_invariants(C, vp, ss, op, mouse);
        vertex_paint_init_session_data(ts, ob);
 
        if (ob->sculpt->mode.wpaint.dvert_prev != NULL) {
@@ -1391,7 +1453,7 @@ static float dot_vf3vs3(const float brushNormal[3], const short vertexNormal[3])
 }
 
 static void get_brush_alpha_data(
-        Scene *scene, SculptSession *ss, Brush *brush,
+        const Scene *scene, const SculptSession *ss, const Brush *brush,
         float *r_brush_size_pressure, float *r_brush_alpha_value, float *r_brush_alpha_pressure)
 {
        *r_brush_size_pressure =
@@ -1417,6 +1479,22 @@ static float wpaint_get_active_weight(const MDeformVert *dv, const WeightPaintIn
        }
 }
 
+static SculptBrushTestFn sculpt_brush_test_init_with_falloff_shape(
+        SculptSession *ss, SculptBrushTest *test, char falloff_shape)
+{
+       sculpt_brush_test_init(ss, test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn;
+       if (falloff_shape == VP_FALLOFF_SHAPE_SPHERE) {
+               sculpt_brush_test_sq_fn = sculpt_brush_test_sphere_sq;
+       }
+       else {
+               /* VP_FALLOFF_SHAPE_TUBE */
+               plane_from_point_normal_v3(test->plane, test->location, ss->cache->view_normal);
+               sculpt_brush_test_sq_fn = sculpt_brush_test_circle_sq;
+       }
+       return sculpt_brush_test_sq_fn;
+}
+
 static void do_wpaint_brush_blur_task_cb_ex(
         void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id))
 {
@@ -1425,25 +1503,26 @@ static void do_wpaint_brush_blur_task_cb_ex(
        CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh);
        const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap;
 
-       Brush *brush = data->brush;
-       StrokeCache *cache = ss->cache;
+       const Brush *brush = data->brush;
+       const 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_normal = vwpaint_use_normal(data->vp);
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
        const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_sq(&test, vd.co)) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                        /* For grid based pbvh, take the vert whose loop coopresponds to the current grid.
                         * Otherwise, take the current vert. */
                        const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
@@ -1469,16 +1548,19 @@ static void do_wpaint_brush_blur_task_cb_ex(
 
                                /* 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) {
+                                       float brush_strength = cache->bstrength;
+                                       const float angle_cos = (use_normal && vd.no) ?
+                                               dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                                       if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                            (angle_cos > 0.0f)) &&
+                                           ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                            view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                                       {
                                                const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius);
                                                float final_alpha =
                                                        brush_fade * brush_strength *
                                                        grid_alpha * brush_alpha_pressure;
 
-                                               if (data->vp->flag & VP_NORMALS) {
-                                                       final_alpha *= view_dot;
-                                               }
                                                if (brush->flag & BRUSH_ACCUMULATE) {
                                                        float mask_accum = ss->mode.wpaint.previous_accum[v_index];
                                                        final_alpha = min_ff(final_alpha + mask_accum, brush_strength);
@@ -1506,12 +1588,12 @@ static void do_wpaint_brush_smear_task_cb_ex(
        CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh);
        const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap;
 
-       Brush *brush = data->brush;
-       Scene *scene = CTX_data_scene(data->C);
-       StrokeCache *cache = ss->cache;
-       const float brush_strength = cache->bstrength;
+       const Brush *brush = data->brush;
+       const Scene *scene = CTX_data_scene(data->C);
+       const StrokeCache *cache = ss->cache;
        float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
        get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
+       const bool use_normal = vwpaint_use_normal(data->vp);
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
        const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
        float brush_dir[3];
@@ -1522,26 +1604,32 @@ static void do_wpaint_brush_smear_task_cb_ex(
        if (normalize_v3(brush_dir) != 0.0f) {
 
                SculptBrushTest test;
-               sculpt_brush_test_init(ss, &test);
+               SculptBrushTestFn sculpt_brush_test_sq_fn =
+                       sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
                /* For each vertex */
                PBVHVertexIter vd;
                BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
                {
                        /* Test to see if the vertex coordinates are within the spherical brush region. */
-                       if (sculpt_brush_test_sphere_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];
-
-                                       /* If the vertex is selected */
-                                       if (!(use_face_sel || use_vert_sel) || mv_curr->flag & SELECT) {
+                       if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+                               /* For grid based pbvh, take the vert whose loop cooresponds to the current grid.
+                                * Otherwise, take the current vert. */
+                               const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
+                               const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f;
+                               const MVert *mv_curr = &data->me->mvert[v_index];
+
+                               /* If the vertex is selected */
+                               if (!(use_face_sel || use_vert_sel) || mv_curr->flag & SELECT) {
+                                       float brush_strength = cache->bstrength;
+                                       const float angle_cos = (use_normal && vd.no) ?
+                                               dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                                       if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                            (angle_cos > 0.0f)) &&
+                                           ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                            view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                                       {
+                                               bool do_color = false;
                                                /* Minimum dot product between brush direction and current
                                                 * to neighbor direction is 0.0, meaning orthogonal. */
                                                float stroke_dot_max = 0.0f;
@@ -1578,14 +1666,10 @@ static void do_wpaint_brush_smear_task_cb_ex(
                                                }
                                                /* Apply weight to vertex */
                                                if (do_color) {
-                                                       const float brush_fade = BKE_brush_curve_strength(brush, test.dist, cache->radius);
+                                                       const float brush_fade = BKE_brush_curve_strength(brush, 0.0f, cache->radius);
                                                        float final_alpha =
                                                                brush_fade * brush_strength *
                                                                grid_alpha * brush_alpha_pressure;
-
-                                                       if (data->vp->flag & VP_NORMALS) {
-                                                               final_alpha *= view_dot;
-                                                       }
                                                        do_weight_paint_vertex(
                                                                data->vp, data->ob, data->wpi,
                                                                v_index, final_alpha, (float)weight_final);
@@ -1604,26 +1688,27 @@ static void do_wpaint_brush_draw_task_cb_ex(
        SculptThreadedTaskData *data = userdata;
        SculptSession *ss = data->ob->sculpt;
        CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh);
-       Scene *scene = CTX_data_scene(data->C);
+       const Scene *scene = CTX_data_scene(data->C);
 
-       Brush *brush = data->brush;
-       StrokeCache *cache = ss->cache;
-       const float brush_strength = cache->bstrength;
+       const Brush *brush = data->brush;
+       const StrokeCache *cache = ss->cache;
        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_normal = vwpaint_use_normal(data->vp);
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
        const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_sq(&test, vd.co)) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                        /* Note: grids are 1:1 with corners (aka loops).
                         * For multires, take the vert whose loop cooresponds to the current grid.
                         * Otherwise, take the current vert. */
@@ -1633,14 +1718,16 @@ static void do_wpaint_brush_draw_task_cb_ex(
                        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) {
+                               float brush_strength = cache->bstrength;
+                               const float angle_cos = (use_normal && vd.no) ?
+                                       dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                               if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                    (angle_cos > 0.0f)) &&
+                                   ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                    view_angle_limits_apply_falloff(&data->wpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                               {
                                        const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius);
                                        float final_alpha = brush_fade * brush_strength * grid_alpha * brush_alpha_pressure;
-
-                                       if (data->vp->flag & VP_NORMALS) {
-                                               final_alpha *= view_dot;
-                                       }
                                        if (brush->flag & BRUSH_ACCUMULATE) {
                                                float mask_accum = ss->mode.wpaint.previous_accum[v_index];
                                                final_alpha = min_ff(final_alpha + mask_accum, brush_strength);
@@ -1648,7 +1735,7 @@ static void do_wpaint_brush_draw_task_cb_ex(
                                        }
 
                                        /* Non-spray logic. */
-                                       if ((data->vp->flag & VP_SPRAY) == 0) {
+                                       if ((data->vp->flag & VP_FLAG_SPRAY) == 0) {
                                                /* Only paint if we have greater alpha. */
                                                if (ss->mode.wpaint.alpha_weight[v_index] < final_alpha) {
                                                        ss->mode.wpaint.alpha_weight[v_index] = final_alpha;
@@ -1676,6 +1763,7 @@ static void do_wpaint_brush_calc_average_weight_cb_ex(
        StrokeCache *cache = ss->cache;
        CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh);
 
+       const bool use_normal = vwpaint_use_normal(data->vp);
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
        const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
 
@@ -1684,16 +1772,18 @@ static void do_wpaint_brush_calc_average_weight_cb_ex(
        accum->value = 0.0;
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_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) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+                       const float angle_cos = (use_normal && vd.no) ?
+                               dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                       if (angle_cos > 0.0 && BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) > 0.0) {
                                const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
                                // const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f;
                                const char v_flag = data->me->mvert[v_index].flag;
@@ -1776,6 +1866,50 @@ static void wpaint_paint_leaves(
        }
 }
 
+static PBVHNode **vwpaint_pbvh_gather_generic(
+        Object *ob, VPaint *wp, Sculpt *sd, Brush *brush, int *r_totnode)
+{
+       SculptSession *ss = ob->sculpt;
+       const bool use_normal = vwpaint_use_normal(wp);
+       PBVHNode **nodes = NULL;
+
+       /* Build a list of all nodes that are potentially within the brush's area of influence */
+       if (wp->falloff_shape == VP_FALLOFF_SHAPE_SPHERE) {
+               SculptSearchSphereData data = {
+                       .ss = ss,
+                       .sd = sd,
+                       .radius_squared = ss->cache->radius_squared,
+                       .original = true,
+               };
+               BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, r_totnode);
+               if (use_normal) {
+                       sculpt_pbvh_calc_area_normal(brush, ob, nodes, *r_totnode, true, ss->cache->sculpt_normal_symm);
+               }
+               else {
+                       zero_v3(ss->cache->sculpt_normal_symm);
+               }
+       }
+       else {
+               struct DistRayAABB_Precalc dist_ray_to_aabb_precalc;
+               dist_squared_ray_to_aabb_precalc(&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal);
+               SculptSearchCircleData data = {
+                       .ss = ss,
+                       .sd = sd,
+                       .radius_squared = ss->cache->radius_squared,
+                       .original = true,
+                       .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc,
+               };
+               BKE_pbvh_search_gather(ss->pbvh, sculpt_search_circle_cb, &data, &nodes, r_totnode);
+               if (use_normal) {
+                       copy_v3_v3(ss->cache->sculpt_normal_symm, ss->cache->view_normal);
+               }
+               else {
+                       zero_v3(ss->cache->sculpt_normal_symm);
+               }
+       }
+       return nodes;
+}
+
 static void wpaint_do_paint(
         bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi,
         Mesh *me, Brush *brush, const char symm, const int axis, const int i, const float angle)
@@ -1784,19 +1918,9 @@ static void wpaint_do_paint(
        ss->cache->radial_symmetry_pass = i;
        sculpt_cache_calc_brushdata_symm(ss->cache, symm, axis, angle);
 
-       SculptSearchSphereData data;
-       PBVHNode **nodes = NULL;
        int totnode;
+       PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, wp, sd, brush, &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);
-
-       sculpt_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, ss->cache->sculpt_normal_symm);
        wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes, totnode);
 
        if (nodes)
@@ -2179,6 +2303,8 @@ typedef struct PolyFaceMap {
 
 struct VPaintData {
        ViewContext vc;
+       struct NormalAnglePrecalc normal_angle_precalc;
+
        uint paintcol;
 
        struct VertProjHandle *vp_handle;
@@ -2223,6 +2349,7 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f
        vpd = MEM_callocN(sizeof(*vpd), "VPaintData");
        paint_stroke_set_mode_data(stroke, vpd);
        view3d_set_viewcontext(C, &vpd->vc);
+       view_angle_limits_init(&vpd->normal_angle_precalc, vp->normal_angle, (vp->flag & VP_FLAG_PROJECT_FLAT) == 0);
 
        vpd->paintcol = vpaint_get_current_col(scene, vp);
 
@@ -2287,16 +2414,17 @@ static void do_vpaint_brush_calc_average_color_cb_ex(
        memset(accum->value, 0, sizeof(accum->value));
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_fast(&test, vd.co)) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                        const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
-                       if (BKE_brush_curve_strength(data->brush, test.dist, cache->radius) > 0.0) {
+                       if (BKE_brush_curve_strength(data->brush, 0.0, cache->radius) > 0.0) {
                                /* If the vertex is selected for painting. */
                                const MVert *mv = &data->me->mvert[v_index];
                                if (!use_vert_sel || mv->flag & SELECT) {
@@ -2344,25 +2472,26 @@ static void do_vpaint_brush_draw_task_cb_ex(
        CCGDerivedMesh *ccgdm = BKE_pbvh_get_ccgdm(ss->pbvh);
        const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
 
-       Brush *brush = data->brush;
-       StrokeCache *cache = ss->cache;
-       const float brush_strength = cache->bstrength;
+       const Brush *brush = data->brush;
+       const StrokeCache *cache = ss->cache;
        uint *lcol = data->lcol;
-       Scene *scene = CTX_data_scene(data->C);
+       const Scene *scene = CTX_data_scene(data->C);
        float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
        get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
+       const bool use_normal = vwpaint_use_normal(data->vp);
        const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_sq(&test, vd.co)) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                        /* Note: Grids are 1:1 with corners (aka loops).
                         * For grid based pbvh, take the vert whose loop cooresponds to the current grid.
                         * Otherwise, take the current vert. */
@@ -2374,8 +2503,14 @@ static void do_vpaint_brush_draw_task_cb_ex(
                        if (!use_vert_sel || mv->flag & SELECT) {
                                /* Calc the dot prod. between ray norm on surf and current vert
                                 * (ie splash prevention factor), and only paint front facing verts. */
-                               const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0;
-                               if (view_dot > 0.0f) {
+                               float brush_strength = cache->bstrength;
+                               const float angle_cos = (use_normal && vd.no) ?
+                                       dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                               if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                    (angle_cos > 0.0f)) &&
+                                   ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                    view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                               {
                                        const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius);
                                        uint color_final = data->vpd->paintcol;
 
@@ -2404,10 +2539,6 @@ static void do_vpaint_brush_draw_task_cb_ex(
                                                        float final_alpha =
                                                                255 * brush_fade * brush_strength *
                                                                tex_alpha * brush_alpha_pressure * grid_alpha;
-
-                                                       if (data->vp->flag & VP_NORMALS) {
-                                                               final_alpha *= view_dot;
-                                                       }
                                                        if (brush->flag & BRUSH_ACCUMULATE) {
                                                                float mask_accum = ss->mode.vpaint.previous_accum[l_index];
                                                                final_alpha = min_ff(final_alpha + mask_accum, 255.0f * brush_strength);
@@ -2436,36 +2567,43 @@ static void do_vpaint_brush_blur_task_cb_ex(
 
        Scene *scene = CTX_data_scene(data->C);
        const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
-       Brush *brush = data->brush;
-       StrokeCache *cache = ss->cache;
-       const float brush_strength = cache->bstrength;
+       const Brush *brush = data->brush;
+       const StrokeCache *cache = ss->cache;
        uint *lcol = data->lcol;
        float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
        get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
+       const bool use_normal = vwpaint_use_normal(data->vp);
        const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
 
        SculptBrushTest test;
-       sculpt_brush_test_init(ss, &test);
+       SculptBrushTestFn sculpt_brush_test_sq_fn =
+               sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
        /* For each vertex */
        PBVHVertexIter vd;
        BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
        {
                /* Test to see if the vertex coordinates are within the spherical brush region. */
-               if (sculpt_brush_test_sphere_sq(&test, vd.co)) {
+               if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                        /* For grid based pbvh, take the vert whose loop cooresponds to the current grid.
                         * Otherwise, take the current vert. */
                        const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
                        const float grid_alpha = ccgdm ? 1.0f / vd.gridsize : 1.0f;
                        const MVert *mv = &data->me->mvert[v_index];
 
-                       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);
+                       /* If the vertex is selected for painting. */
+                       if (!use_vert_sel || mv->flag & SELECT) {
+                               float brush_strength = cache->bstrength;
+                               const float angle_cos = (use_normal && vd.no) ?
+                                       dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                               if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                    (angle_cos > 0.0f)) &&
+                                   ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                    view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                               {
+                                       const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius);
 
-                               /* If the vertex is selected for painting. */
-                               if (!use_vert_sel || mv->flag & SELECT) {
                                        /* Get the average poly color */
                                        uint color_final = 0;
                                        int total_hit_loops = 0;
@@ -2512,10 +2650,6 @@ static void do_vpaint_brush_blur_task_cb_ex(
                                                                float final_alpha =
                                                                        255 * brush_fade * brush_strength *
                                                                        brush_alpha_pressure * grid_alpha;
-
-                                                               if (data->vp->flag & VP_NORMALS) {
-                                                                       final_alpha *= view_dot;
-                                                               }
                                                                /* Mix the new color with the original
                                                                 * based on the brush strength and the curve. */
                                                                lcol[l_index] = vpaint_blend(
@@ -2540,13 +2674,13 @@ static void do_vpaint_brush_smear_task_cb_ex(
 
        Scene *scene = CTX_data_scene(data->C);
        const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
-       Brush *brush = data->brush;
-       StrokeCache *cache = ss->cache;
-       const float brush_strength = cache->bstrength;
+       const Brush *brush = data->brush;
+       const StrokeCache *cache = ss->cache;
        uint *lcol = data->lcol;
        float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
        get_brush_alpha_data(scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
        float brush_dir[3];
+       const bool use_normal = vwpaint_use_normal(data->vp);
        const bool use_vert_sel = (data->me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
        const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
 
@@ -2556,14 +2690,15 @@ static void do_vpaint_brush_smear_task_cb_ex(
        if (normalize_v3(brush_dir) != 0.0f) {
 
                SculptBrushTest test;
-               sculpt_brush_test_init(ss, &test);
+               SculptBrushTestFn sculpt_brush_test_sq_fn =
+                       sculpt_brush_test_init_with_falloff_shape(ss, &test, data->vp->falloff_shape);
 
                /* For each vertex */
                PBVHVertexIter vd;
                BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
                {
                        /* Test to see if the vertex coordinates are within the spherical brush region. */
-                       if (sculpt_brush_test_sphere_sq(&test, vd.co)) {
+                       if (sculpt_brush_test_sq_fn(&test, vd.co)) {
                                /* For grid based pbvh, take the vert whose loop cooresponds to the current grid.
                                 * Otherwise, take the current vert. */
                                const int v_index = ccgdm ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i];
@@ -2574,8 +2709,14 @@ static void do_vpaint_brush_smear_task_cb_ex(
                                if (!use_vert_sel || mv_curr->flag & SELECT) {
                                        /* Calc the dot prod. between ray norm on surf and current vert
                                         * (ie splash prevention factor), and only paint front facing verts. */
-                                       const float view_dot = (vd.no) ? dot_vf3vs3(cache->sculpt_normal_symm, vd.no) : 1.0;
-                                       if (view_dot > 0.0f) {
+                                       float brush_strength = cache->bstrength;
+                                       const float angle_cos = (use_normal && vd.no) ?
+                                               dot_vf3vs3(ss->cache->sculpt_normal_symm, vd.no) : 1.0f;
+                                       if (((data->vp->flag & VP_FLAG_PROJECT_BACKFACE) ||
+                                            (angle_cos > 0.0f)) &&
+                                           ((data->vp->flag & VP_FLAG_PROJECT_FLAT) ||
+                                            view_angle_limits_apply_falloff(&data->vpd->normal_angle_precalc, angle_cos, &brush_strength)))
+                                       {
                                                const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius);
 
                                                bool do_color = false;
@@ -2636,10 +2777,6 @@ static void do_vpaint_brush_smear_task_cb_ex(
                                                                        float final_alpha =
                                                                                255 * brush_fade * brush_strength *
                                                                                brush_alpha_pressure * grid_alpha;
-
-                                                                       if (data->vp->flag & VP_NORMALS) {
-                                                                               final_alpha *= view_dot;
-                                                                       }
                                                                        /* Mix the new color with the original
                                                                         * based on the brush strength and the curve. */
                                                                        lcol[l_index] = vpaint_blend(
@@ -2721,27 +2858,18 @@ static void vpaint_paint_leaves(
 }
 
 static void vpaint_do_paint(
-        bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd,
+        bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd,
         Object *ob, Mesh *me, Brush *brush, const char symm, const int axis, const int i, const float angle)
 {
        SculptSession *ss = ob->sculpt;
        ss->cache->radial_symmetry_pass = i;
        sculpt_cache_calc_brushdata_symm(ss->cache, symm, axis, angle);
-       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);
 
-       sculpt_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, ss->cache->sculpt_normal_symm);
+       int totnode;
+       PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, vp, sd, brush, &totnode);
 
        /* Paint those leaves. */
-       vpaint_paint_leaves(C, sd, vd, vpd, ob, me, nodes, totnode);
+       vpaint_paint_leaves(C, sd, vp, vpd, ob, me, nodes, totnode);
 
        if (nodes) {
                MEM_freeN(nodes);
@@ -2749,31 +2877,31 @@ static void vpaint_do_paint(
 }
 
 static void vpaint_do_radial_symmetry(
-        bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob, Mesh *me,
+        bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob, Mesh *me,
         Brush *brush, const char symm, const int axis)
 {
-       for (int i = 1; i < 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);
+       for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) {
+               const float angle = (2.0 * M_PI) * i / vp->radial_symm[axis - 'X'];
+               vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, symm, axis, i, angle);
        }
 }
 
 /* near duplicate of: sculpt.c's, 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */
 static void vpaint_do_symmetrical_brush_actions(
-        bContext *C, Sculpt *sd, VPaint *vd, struct VPaintData *vpd, Object *ob)
+        bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob)
 {
-       Brush *brush = BKE_paint_brush(&vd->paint);
+       Brush *brush = BKE_paint_brush(&vp->paint);
        Mesh *me = ob->data;
        SculptSession *ss = ob->sculpt;
        StrokeCache *cache = ss->cache;
-       const char symm = vd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+       const char symm = vp->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');
+       vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0);
+       vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X');
+       vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y');
+       vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z');
 
        cache->symmetry = symm;
 
@@ -2785,16 +2913,16 @@ static void vpaint_do_symmetrical_brush_actions(
                        sculpt_cache_calc_brushdata_symm(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');
+                               vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0);
+                               vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X');
                        }
                        if (i & (1 << 1)) {
-                               vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Y', 0, 0);
-                               vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y');
+                               vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0);
+                               vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y');
                        }
                        if (i & (1 << 2)) {
-                               vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Z', 0, 0);
-                               vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Z');
+                               vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0);
+                               vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z');
                        }
                }
        }
index 69ae47790a0a5d486b0847e43d22acf085c6482e..1698ab9e5c7f618e9eb9464af42613bd06fbe263 100644 (file)
@@ -587,6 +587,24 @@ bool sculpt_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3
        return len_squared_v3v3(co, test->location) <= test->radius_squared;
 }
 
+bool sculpt_brush_test_circle_sq(SculptBrushTest *test, const float co[3])
+{
+       float co_proj[3];
+       closest_to_plane_normalized_v3(co_proj, test->plane, co);
+       float distsq = len_squared_v3v3(co_proj, test->location);
+
+       if (distsq <= test->radius_squared) {
+               if (sculpt_brush_test_clipping(test, co)) {
+                       return 0;
+               }
+               test->dist = distsq;
+               return 1;
+       }
+       else {
+               return 0;
+       }
+}
+
 bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], float local[4][4])
 {
        float side = M_SQRT1_2;
@@ -1216,6 +1234,24 @@ bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v)
        return len_squared_v3(t) < data->radius_squared;
 }
 
+/* 2D projection (distance to line). */
+bool sculpt_search_circle_cb(PBVHNode *node, void *data_v)
+{
+       SculptSearchCircleData *data = data_v;
+       float bb_min[3], bb_max[3];
+
+       if (data->original)
+               BKE_pbvh_node_get_original_BB(node, bb_min, bb_max);
+       else
+               BKE_pbvh_node_get_BB(node, bb_min, bb_min);
+
+       float dummy_co[3], dummy_depth;
+       const float dist_sq = dist_squared_ray_to_aabb(
+               data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth);
+
+       return dist_sq < data->radius_squared;
+}
+
 /* Handles clipping against a mirror modifier and SCULPT_LOCK axis flags */
 static void sculpt_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3])
 {
index 7d9d3ad783dcfc9f9b79a1729e2ec0211050806b..425cecb0010a6b6e01669fe8c1cc6378d02fb58c 100644 (file)
@@ -188,6 +188,8 @@ typedef struct SculptBrushTest {
        struct RegionView3D *clip_rv3d;
 } SculptBrushTest;
 
+typedef bool (*SculptBrushTestFn)(SculptBrushTest *test, const float co[3]);
+
 typedef struct {
        struct Sculpt *sd;
        struct SculptSession *ss;
@@ -195,12 +197,22 @@ typedef struct {
        bool original;
 } SculptSearchSphereData;
 
+typedef struct {
+       struct Sculpt *sd;
+       struct SculptSession *ss;
+       float radius_squared;
+       bool original;
+       struct DistRayAABB_Precalc *dist_ray_to_aabb_precalc;
+} SculptSearchCircleData;
+
 void sculpt_brush_test_init(struct SculptSession *ss, SculptBrushTest *test);
 bool sculpt_brush_test_sphere(SculptBrushTest *test, const float co[3]);
 bool sculpt_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]);
 bool sculpt_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]);
 bool sculpt_brush_test_cube(SculptBrushTest *test, const float co[3], float local[4][4]);
+bool sculpt_brush_test_circle_sq(SculptBrushTest *test, const float co[3]);
 bool sculpt_search_sphere_cb(PBVHNode *node, void *data_v);
+bool sculpt_search_circle_cb(PBVHNode *node, void *data_v);
 float tex_strength(
         struct SculptSession *ss, struct Brush *br,
         const float point[3],
index 3201b75ee1e0985a33ce50c8a523443b720aace5..e9caf7ada1c1d0d9c2d2826bf716dc5ce904968f 100644 (file)
@@ -1117,18 +1117,26 @@ typedef struct UvSculpt {
 /* Vertex Paint */
 typedef struct VPaint {
        Paint paint;
-       short flag, pad;
+       short flag;
+       char falloff_shape, normal_angle;
        int radial_symm[3]; /* For mirrored painting */
 } VPaint;
 
 /* VPaint.flag */
 enum {
-       // VP_COLINDEX  = (1 << 0),  /* only paint onto active material*/  /* deprecated since before 2.49 */
-       // VP_AREA      = (1 << 1),  /* deprecated since 2.70 */
-       VP_NORMALS      = (1 << 3),
-       VP_SPRAY        = (1 << 4),
-       // VP_MIRROR_X  = (1 << 5),  /* deprecated in 2.5x use (me->editflag & ME_EDIT_MIRROR_X) */
-       VP_ONLYVGROUP   = (1 << 7)   /* weight paint only */
+       VP_FLAG_PROJECT_BACKFACE    = (1 << 0),
+       /* TODO */
+       // VP_FLAG_PROJECT_XRAY        = (1 << 1),
+       VP_FLAG_PROJECT_FLAT        = (1 << 3),
+       VP_FLAG_SPRAY               = (1 << 4),
+       /* weight paint only */
+       VP_FLAG_VGROUP_RESTRICT     = (1 << 7)
+};
+
+/* VPaint.falloff_shape */
+enum {
+       VP_FALLOFF_SHAPE_SPHERE = 0,
+       VP_FALLOFF_SHAPE_TUBE = 1,
 };
 
 /* ------------------------------------------- */
index 09da34d59d86650117a55115910e6f618813292d..6c89767ad29f42afdfe031df4f0f78815e081893 100644 (file)
@@ -671,23 +671,44 @@ static void rna_def_vertex_paint(BlenderRNA *brna)
        RNA_def_struct_path_func(srna, "rna_VertexPaint_path");
        RNA_def_struct_ui_text(srna, "Vertex Paint", "Properties of vertex and weight paint mode");
 
-       /* vertex paint only */
-       prop = RNA_def_property(srna, "use_normal", PROP_BOOLEAN, PROP_NONE);
-       RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_NORMALS);
-       RNA_def_property_ui_text(prop, "Normals", "Apply the vertex normal before painting");
+       static EnumPropertyItem prop_falloff_items[] = {
+               {VP_FALLOFF_SHAPE_SPHERE, "SPHERE", 0, "Sphere", "Spherical falloff from the brush"},
+               {VP_FALLOFF_SHAPE_TUBE, "TUBE", 0, "Circle", "Circular falloff from the brush along the view"},
+               {0, NULL, 0, NULL, NULL}
+       };
+
+       prop = RNA_def_property(srna, "falloff_shape", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "falloff_shape");
+       RNA_def_property_enum_items(prop, prop_falloff_items);
+       RNA_def_property_ui_text(prop, "Falloff", "");
+       RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
+       prop = RNA_def_property(srna, "use_backface_culling", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", VP_FLAG_PROJECT_BACKFACE);
+       RNA_def_property_ui_text(prop, "Cull", "Ignore vertices pointing away from the view (faster)");
+       RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
+       prop = RNA_def_property(srna, "use_normal_falloff", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", VP_FLAG_PROJECT_FLAT);
+       RNA_def_property_ui_text(prop, "Normals", "Paint most on faces pointing towards the view");
        RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
        
+
        prop = RNA_def_property(srna, "use_spray", PROP_BOOLEAN, PROP_NONE);
-       RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_SPRAY);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_FLAG_SPRAY);
        RNA_def_property_ui_text(prop, "Spray", "Keep applying paint effect while holding mouse");
        RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
 
        /* weight paint only */
        prop = RNA_def_property(srna, "use_group_restrict", PROP_BOOLEAN, PROP_NONE);
-       RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_ONLYVGROUP);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", VP_FLAG_VGROUP_RESTRICT);
        RNA_def_property_ui_text(prop, "Restrict", "Restrict painting to vertices in the group");
        RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
 
+       prop = RNA_def_property(srna, "normal_angle", PROP_INT, PROP_UNSIGNED);
+       RNA_def_property_range(prop, 0, 90);
+       RNA_def_property_ui_text(prop, "Angle", "Paint most on faces pointing towards the view according to this angle");
+
        /* Mirroring */
        prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ);
        RNA_def_property_int_sdna(prop, NULL, "radial_symm");