diff options
Diffstat (limited to 'source/blender/editors')
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_intern.h | 16 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_stroke.cc (renamed from source/blender/editors/sculpt_paint/paint_stroke.c) | 399 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 31 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 2 |
5 files changed, 432 insertions, 18 deletions
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 2709ac3fd91..b43713462b0 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -53,7 +53,7 @@ set(SRC paint_image_proj.c paint_mask.c paint_ops.c - paint_stroke.c + paint_stroke.cc paint_utils.c paint_vertex.cc paint_vertex_color_ops.cc diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index c6fe7ed3072..caa6a456467 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -16,7 +16,16 @@ #include "DNA_scene_types.h" #ifdef __cplusplus +namespace blender { +template<typename Float, int axes> class BezierSpline; +} + +using BezierSpline2f = blender::BezierSpline<float, 2>; +using BezierSpline3f = blender::BezierSpline<float, 3>; extern "C" { +#else +typedef struct BezierSpline2f BezierSpline2f; +typedef struct BezierSpline3f BezierSpline3f; #endif struct ARegion; @@ -511,6 +520,13 @@ void paint_delete_blur_kernel(BlurKernel *); /* paint curve defines */ #define PAINT_CURVE_NUM_SEGMENTS 40 +void paint_stroke_spline_uv(struct PaintStroke *stroke, + struct StrokeCache *cache, + const float co[3], + float r_out[3], + float r_tan[3]); +float paint_stroke_spline_length(struct PaintStroke *stroke); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.cc index 97f5bd77d47..99fbc92d7e1 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.cc @@ -7,8 +7,10 @@ #include "MEM_guardedalloc.h" +#include "BLI_even_spline.hh" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_rand.h" #include "BLI_utildefines.h" @@ -35,6 +37,7 @@ #include "GPU_state.h" #include "ED_screen.h" +#include "ED_space_api.h" #include "ED_view3d.h" #include "IMB_imbuf_types.h" @@ -46,6 +49,10 @@ #include <math.h> //#define DEBUG_TIME +#define DRAW_DEBUG_VIS + +using blender::float2; +using blender::float3; #ifdef DEBUG_TIME # include "PIL_time_utildefines.h" @@ -56,6 +63,14 @@ typedef struct PaintSample { float pressure; } PaintSample; +typedef struct PaintStrokePoint { + float mouse_in[2], mouse_out[2]; + float location[3]; + float pressure, x_tilt, y_tilt; + bool pen_flip; + float size; +} PaintStrokePoint; + typedef struct PaintStroke { void *mode_data; void *stroke_cursor; @@ -70,6 +85,9 @@ typedef struct PaintStroke { /* used for lines and curves */ ListBase line; + bool need_roll_mapping; + int stroke_sample_index; + /* Paint stroke can use up to PAINT_MAX_INPUT_SAMPLES prior inputs * to smooth the stroke */ PaintSample samples[PAINT_MAX_INPUT_SAMPLES]; @@ -77,6 +95,14 @@ typedef struct PaintStroke { int cur_sample; int tot_samples; + PaintStrokePoint points[PAINT_MAX_INPUT_SAMPLES]; + int num_points; + int cur_point; + int tot_points; + + BezierSpline2f *spline; + BezierSpline3f *world_spline; + float last_mouse_position[2]; float last_world_space_position[3]; float last_scene_spacing_delta[3]; @@ -84,6 +110,7 @@ typedef struct PaintStroke { bool stroke_over_mesh; /* space distance covered so far */ float stroke_distance; + float stroke_distance_world; /* Set whether any stroke step has yet occurred * e.g. in sculpt mode, stroke doesn't start until cursor @@ -104,6 +131,8 @@ typedef struct PaintStroke { float last_pressure; int stroke_mode; + float spacing_raw; + float last_tablet_event_pressure; float zoom_2d; @@ -124,14 +153,233 @@ typedef struct PaintStroke { StrokeDone done; bool original; /* Ray-cast original mesh at start of stroke. */ + void *debug_draw_handle; } PaintStroke; +static int paint_stroke_max_points(const Paint *paint, PaintStroke *stroke) +{ + if (!stroke->need_roll_mapping) { + return 1; + } + + float s = max_ff(stroke->spacing_raw, 0.05); + + int tot = (int)ceilf(1.0f / s) + 2; + tot = max_ii(tot, 5); + + return tot; +} + +static void paint_stroke_add_point(const Paint *paint, + PaintStroke *stroke, + const float mouse_in[2], + const float mouse_out[2], + const float loc[3], + float size, + float pressure, + bool pen_flip, + float x_tilt, + float y_tilt) +{ + PaintStrokePoint *point = &stroke->points[stroke->cur_point]; + int max_points = paint_stroke_max_points(paint, stroke); + + point->size = size; + copy_v2_v2(point->mouse_in, mouse_in); + copy_v2_v2(point->mouse_out, mouse_out); + point->x_tilt = x_tilt; + point->y_tilt = y_tilt; + point->pen_flip = pen_flip; + copy_v3_v3(point->location, loc); + point->pressure = pressure; + + stroke->cur_point++; + if (stroke->cur_point >= max_points) { + stroke->cur_point = 0; + } + if (stroke->num_points < max_points) { + stroke->num_points++; + } +} + +static void paint_project_cubic(bContext *C, + PaintStroke *stroke, + blender::CubicBezier<float, 2> &bezier2d, + blender::CubicBezier<float, 3> &bezier3d) +{ + float2 mvals[4]; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 2; j++) { + mvals[i][j] = bezier2d.ps[i][j]; + } + } + + for (int i = 0; i < 4; i++) { + if (!SCULPT_stroke_get_location(C, bezier3d.ps[i], mvals[i], true)) { + ED_view3d_win_to_3d(CTX_wm_view3d(C), + CTX_wm_region(C), + stroke->last_world_space_position, + mvals[i], + bezier3d.ps[i]); + } + } + + bezier3d.update(); +} + +static void paint_brush_make_spline(bContext *C, PaintStroke *stroke) +{ + float a[2], b[2], c[2], d[2]; + + if (stroke->num_points < 4) { + return; + } + + int cur = (stroke->cur_point - 1 + stroke->num_points) % stroke->num_points; + + int ia = (cur - 2 + stroke->num_points) % stroke->num_points; + int id = (cur - 1 + stroke->num_points) % stroke->num_points; + + int ib = (cur - 3 + stroke->num_points) % stroke->num_points; + int ic = (cur - 0 + stroke->num_points) % stroke->num_points; + + copy_v2_v2(a, stroke->points[ia].mouse_out); + copy_v2_v2(b, stroke->points[ib].mouse_out); + copy_v2_v2(c, stroke->points[ic].mouse_out); + copy_v2_v2(d, stroke->points[id].mouse_out); + + float scale = 1.0; + scale /= 3.0f; + + float tmp1[2]; + float tmp2[2]; +#if 1 + sub_v2_v2v2(tmp1, d, a); + sub_v2_v2v2(tmp2, a, b); + interp_v2_v2v2(b, tmp1, tmp2, 0.5f); + mul_v2_fl(b, scale); +#else + zero_v2(b); +#endif + + add_v2_v2(b, a); + +#if 1 + sub_v2_v2v2(tmp1, a, d); + sub_v2_v2v2(tmp2, d, c); + interp_v2_v2v2(c, tmp1, tmp2, 0.5f); + mul_v2_fl(c, scale); +#else + zero_v2(c); +#endif + + add_v2_v2(c, d); +#if 0 + printf("\n"); + printf("a: %.2f: %.2f\n", a[0], a[1]); + printf("b: %.2f: %.2f\n", b[0], b[1]); + printf("c: %.2f: %.2f\n", c[0], c[1]); + printf("d: %.2f: %.2f\n", d[0], d[1]); +#endif + + blender::CubicBezier<float, 2> bez(a, b, c, d); + bez.update(); + stroke->spline->add(bez); + + blender::CubicBezier<float, 3> bez3d; + paint_project_cubic(C, stroke, bez, bez3d); + bez3d.update(); + + stroke->world_spline->add(bez3d); + + while (stroke->spline->segments.size() > paint_stroke_max_points(nullptr, stroke) + 1) { + stroke->spline->pop_front(); + } + + while (stroke->world_spline->segments.size() > paint_stroke_max_points(nullptr, stroke) + 1) { + stroke->stroke_distance_world += stroke->world_spline->segments[0].bezier.length; + stroke->world_spline->pop_front(); + } +} + +#ifdef DRAW_DEBUG_VIS +static void paint_brush_cubic_vis(const bContext *C, ARegion *region, void *userdata) +{ + PaintStroke *stroke = (PaintStroke *)userdata; + Object *ob = CTX_data_active_object(C); + + if (!ob || !ob->sculpt || !ob->sculpt->cache || !stroke->world_spline) { + return; + } + + GPU_line_smooth(false); + GPU_depth_test(GPU_DEPTH_NONE); + GPU_depth_mask(false); + GPU_blend(GPU_BLEND_ALPHA); + + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); // GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); + immUniformColor4ub(255, 150, 0, 185); + + int steps = 256; + + immUniformColor4ub(45, 75, 255, 255); + + immBegin(GPU_PRIM_LINE_STRIP, steps); + + float s = 0.0f; + float ds = stroke->world_spline->length / (steps - 1); + + for (int i = 0; i < steps; i++, s += ds) { + float3 co = stroke->world_spline->evaluate(s); + + mul_v3_m4v3(co, ob->object_to_world, co); + immVertex3fv(pos, co); + } + immEnd(); + + s = 0.0f; + ds = 0.1f; + steps = (int)floorf(stroke->world_spline->length / ds + 0.5f); + + immUniformColor4ub(255, 0, 0, 170); + immBegin(GPU_PRIM_POINTS, steps); + for (int i = 0; i < steps; i++, s += ds) { + float3 co = stroke->world_spline->evaluate(s); + mul_v3_m4v3(co, ob->object_to_world, co); + + immVertex3fv(pos, co); + } + + immEnd(); + + immUniformColor4ub(0, 255, 25, 55); + for (int is_points = 0; is_points < 2; is_points++) { + immBegin(is_points ? GPU_PRIM_POINTS : GPU_PRIM_LINE_STRIP, stroke->num_points); + for (int i = 0; i < stroke->num_points; i++) { + int idx = (i + stroke->cur_point) % stroke->num_points; + float3 co = stroke->points[idx].location; + mul_v3_m4v3(co, ob->object_to_world, co); + + immVertex3fv(pos, co); + } + immEnd(); + } + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); +} +#endif + /*** Cursors ***/ static void paint_draw_smooth_cursor(bContext *C, int x, int y, void *customdata) { Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); - PaintStroke *stroke = customdata; + PaintStroke *stroke = (PaintStroke *)customdata; if (stroke && brush) { GPU_line_smooth(true); @@ -161,7 +409,7 @@ static void paint_draw_smooth_cursor(bContext *C, int x, int y, void *customdata static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata) { Paint *paint = BKE_paint_get_active_from_context(C); - PaintStroke *stroke = customdata; + PaintStroke *stroke = (PaintStroke *)customdata; GPU_line_smooth(true); @@ -616,17 +864,65 @@ static void paint_brush_stroke_add_step( /* Add to stroke */ if (add_step) { + PaintStrokePoint *point; + PaintStrokePoint temp; + + int n = 1; + int max_points = paint_stroke_max_points(paint, stroke); + + if (stroke->num_points < max_points) { + // n = max_points - stroke->num_points; + } + for (int i = 0; i < n; i++) { + paint_stroke_add_point(paint, + stroke, + mval, + mouse_out, + location, + ups->pixel_radius, + pressure, + stroke->pen_flip, + stroke->x_tilt, + stroke->y_tilt); + if (stroke->need_roll_mapping) { + paint_brush_make_spline(C, stroke); + } + } + + if (stroke->need_roll_mapping) { + if (stroke->spline->segments.size() < paint_stroke_max_points(paint, stroke)) { + return; + } + + int cur = stroke->cur_point - (paint_stroke_max_points(paint, stroke) >> 1) - 2; + cur = (cur + stroke->num_points) % stroke->num_points; + + PaintStrokePoint *p1 = stroke->points + ((cur + stroke->num_points) % stroke->num_points); + PaintStrokePoint *p2 = stroke->points + + ((cur - 1 + stroke->num_points) % stroke->num_points); + + point = &temp; + temp = *p1; + + interp_v3_v3v3(temp.location, p1->location, p2->location, 0.5f); + interp_v2_v2v2(temp.mouse_in, p1->mouse_in, p2->mouse_in, 0.5f); + interp_v2_v2v2(temp.mouse_out, p1->mouse_out, p2->mouse_out, 0.5f); + } + else { + point = stroke->points + ((stroke->cur_point - 1 + stroke->num_points) % stroke->num_points); + } + RNA_collection_add(op->ptr, "stroke", &itemptr); - RNA_float_set(&itemptr, "size", ups->pixel_radius); - RNA_float_set_array(&itemptr, "location", location); + RNA_float_set(&itemptr, "size", point->size); + RNA_float_set_array(&itemptr, "location", point->location); /* Mouse coordinates modified by the stroke type options. */ - RNA_float_set_array(&itemptr, "mouse", mouse_out); + RNA_float_set_array(&itemptr, "mouse", point->mouse_out); /* Original mouse coordinates. */ - RNA_float_set_array(&itemptr, "mouse_event", mval); - RNA_boolean_set(&itemptr, "pen_flip", stroke->pen_flip); - RNA_float_set(&itemptr, "pressure", pressure); - RNA_float_set(&itemptr, "x_tilt", stroke->x_tilt); - RNA_float_set(&itemptr, "y_tilt", stroke->y_tilt); + RNA_float_set_array(&itemptr, "mouse_event", point->mouse_in); + RNA_boolean_set(&itemptr, "pen_flip", point->pen_flip); + RNA_float_set(&itemptr, "pressure", point->pressure); + RNA_float_set(&itemptr, "x_tilt", point->x_tilt); + RNA_float_set(&itemptr, "y_tilt", point->y_tilt); stroke->update_step(C, op, stroke, &itemptr); @@ -703,6 +999,8 @@ static float paint_space_stroke_spacing(bContext *C, spacing = spacing * (1.5f - spacing_pressure); } + stroke->spacing_raw = spacing * 0.01; + if (SCULPT_is_cloth_deform_brush(brush)) { /* The spacing in tools that use the cloth solver should not be affected by the brush radius to * avoid affecting the simulation update rate when changing the radius of the brush. @@ -897,7 +1195,7 @@ PaintStroke *paint_stroke_new(bContext *C, int event_type) { struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - PaintStroke *stroke = MEM_callocN(sizeof(PaintStroke), "PaintStroke"); + PaintStroke *stroke = (PaintStroke *)MEM_callocN(sizeof(PaintStroke), "PaintStroke"); ToolSettings *toolsettings = CTX_data_tool_settings(C); UnifiedPaintSettings *ups = &toolsettings->unified_paint_settings; Paint *p = BKE_paint_get_active_from_context(C); @@ -907,6 +1205,13 @@ PaintStroke *paint_stroke_new(bContext *C, ED_view3d_viewcontext_init(C, &stroke->vc, depsgraph); +#ifdef DRAW_DEBUG_VIS + ARegion *region = CTX_wm_region(C); + + stroke->debug_draw_handle = ED_region_draw_cb_activate( + region->type, paint_brush_cubic_vis, stroke, REGION_DRAW_POST_VIEW); +#endif + stroke->get_location = get_location; stroke->test_start = test_start; stroke->update_step = update_step; @@ -921,6 +1226,18 @@ PaintStroke *paint_stroke_new(bContext *C, get_imapaint_zoom(C, &zoomx, &zoomy); stroke->zoom_2d = max_ff(zoomx, zoomy); + if (br->mtex.tex && br->mtex.brush_map_mode == MTEX_MAP_MODE_ROLL) { + stroke->need_roll_mapping = true; + } + if (br->mask_mtex.tex && br->mask_mtex.brush_map_mode == MTEX_MAP_MODE_ROLL) { + stroke->need_roll_mapping = true; + } + + if (stroke->need_roll_mapping) { + stroke->spline = MEM_new<blender::BezierSpline2f>("BezierSpline2f"); + stroke->world_spline = MEM_new<blender::BezierSpline3f>("BezierSpline3f"); + } + if (stroke->stroke_mode == BRUSH_STROKE_INVERT) { if (br->flag & BRUSH_CURVE) { RNA_enum_set(op->ptr, "mode", BRUSH_STROKE_NORMAL); @@ -948,7 +1265,7 @@ PaintStroke *paint_stroke_new(bContext *C, BKE_curvemapping_init(p->cavity_curve); } - BKE_paint_set_overlay_override(br->overlay_flags); + BKE_paint_set_overlay_override((eOverlayFlags)br->overlay_flags); ups->start_pixel_radius = BKE_brush_size_get(CTX_data_scene(C), br); @@ -962,7 +1279,7 @@ void paint_stroke_free(bContext *C, wmOperator *UNUSED(op), PaintStroke *stroke) rv3d->rflag &= ~RV3D_PAINTING; } - BKE_paint_set_overlay_override(0); + BKE_paint_set_overlay_override((eOverlayFlags)0); if (stroke == NULL) { return; @@ -981,11 +1298,18 @@ void paint_stroke_free(bContext *C, wmOperator *UNUSED(op), PaintStroke *stroke) } if (stroke->stroke_cursor) { - WM_paint_cursor_end(stroke->stroke_cursor); + WM_paint_cursor_end((wmPaintCursor *)stroke->stroke_cursor); } BLI_freelistN(&stroke->line); +#ifdef DRAW_DEBUG_VIS + ARegion *region = CTX_wm_region(C); + + ED_region_draw_cb_exit(region->type, stroke->debug_draw_handle); + ED_region_tag_redraw(region); +#endif + MEM_SAFE_FREE(stroke); } @@ -1035,7 +1359,7 @@ bool paint_space_stroke_enabled(Brush *br, ePaintMode mode) } if (mode == PAINT_MODE_SCULPT_CURVES && - !curves_sculpt_brush_uses_spacing(br->curves_sculpt_tool)) { + !curves_sculpt_brush_uses_spacing((eBrushCurvesSculptTool)br->curves_sculpt_tool)) { return false; } @@ -1451,8 +1775,22 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS stroke->last_tablet_event_pressure = pressure; } - paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure); - paint_stroke_sample_average(stroke, &sample_average); + if (!stroke->need_roll_mapping) { + paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure); + paint_stroke_sample_average(stroke, &sample_average); + } + else { + sample_average.mouse[0] = (float)event->mval[0]; + sample_average.mouse[1] = (float)event->mval[1]; + sample_average.pressure = pressure; + } + + if (stroke->stroke_sample_index == 0) { + stroke->last_mouse_position[0] = event->mval[0]; + stroke->last_mouse_position[1] = event->mval[1]; + } + + stroke->stroke_sample_index++; /* Tilt. */ if (WM_event_is_tablet(event)) { @@ -1698,3 +2036,30 @@ bool PAINT_brush_tool_poll(bContext *C) } return false; } + +void paint_stroke_spline_uv( + PaintStroke *stroke, StrokeCache *cache, const float co[3], float r_out[3], float r_tan[3]) +{ + float3 tan; + float3 p = stroke->world_spline->closest_point(co, r_out[1], tan, r_out[0]); + + r_tan = tan; + r_out[0] = len_v3v3(p, co); + r_out[2] = 0.0f; + + float3 vec = p - float3(co); + float3 vec2; + + cross_v3_v3v3(vec2, vec, tan); + + if (dot_v3v3(vec2, cache->view_normal) < 0.0f) { + r_out[0] = -r_out[0]; + } + + r_out[1] += stroke->stroke_distance_world; +} + +float paint_stroke_spline_length(PaintStroke *stroke) +{ + return stroke->world_spline->length; +} diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 3477285814e..7c43a04549f 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -2509,6 +2509,35 @@ float SCULPT_brush_strength_factor(SculptSession *ss, avg += br->texture_sample_bias; } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_ROLL) { + float point_3d[3]; + point_3d[2] = 0.0f; + + float tan[3], curv[3]; + + paint_stroke_spline_uv(ss->cache->stroke, ss->cache, brush_point, point_3d, tan); + +#if 0 + if (SCULPT_has_colors(ss)) { + float color[4] = {point_3d[0], point_3d[1], 0.0f, 1.0f}; + mul_v3_fl(color, 0.25f / ss->cache->initial_radius); + color[0] -= floorf(color[0]); + color[1] -= floorf(color[1]); + color[2] -= floorf(color[2]); + + SCULPT_vertex_color_set(ss, vertex, color); + } + +// avg = 0.0f; +#endif + + float pixel_radius = br->size; + mul_v3_fl(point_3d, 1.0f / ss->cache->initial_radius); + + //avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, thread_id, ss->tex_pool); + avg = paint_get_tex_pixel(mtex, point_3d[0], point_3d[1], ss->tex_pool, thread_id); + avg += br->texture_sample_bias; + } else { const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); @@ -5505,7 +5534,9 @@ static void sculpt_stroke_update_step(bContext *C, const Brush *brush = BKE_paint_brush(&sd->paint); ToolSettings *tool_settings = CTX_data_tool_settings(C); StrokeCache *cache = ss->cache; + cache->stroke_distance = paint_stroke_distance_get(stroke); + cache->stroke = stroke; SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index bf47b64d176..4a2088386ba 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -498,6 +498,8 @@ typedef struct StrokeCache { float true_last_location[3]; float location[3]; float last_location[3]; + + struct PaintStroke *stroke; float stroke_distance; /* Used for alternating between deformation in brushes that need to apply different ones to |