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:
authorPablo Dobarro <pablodp606@gmail.com>2019-11-06 21:39:34 +0300
committerPablo Dobarro <pablodp606@gmail.com>2019-11-21 20:16:42 +0300
commitc3279be222bdca1981a8c1a8582b19a7e693009b (patch)
tree6a828ca6e1f83f2e72940d1549b83642fe1decad /source/blender/editors/sculpt_paint
parentc23dbcf37d9ffed4d443e0f9ce36f14854a52009 (diff)
Sculpt: Multiplane Scrape Brush
The Multiplane Scrape brush creates sharp edges with a given fixed angle by trimming the mesh with two planes in local space at the same time. When working with stylized or hard surface models, this brush produces way better results and is more predictable than any other crease/flatten brush based on curves and alphas. It is also the first brush we have than can produce hard surface concave creases. The Multiplane Scrape Brush also has a dynamic mode where it samples the surface to fit the angle and scrape planes during a stroke. With this mode enabled you can sculpt multiple times over the same edge without creating artifacts. It can also create creases that change between concave and convex during the same stroke. The behavior of this brush will improve after merging patches like D5993 and its behavior in concave creases can still be improved, so I will keep tweaking its parameters and default values once we have all brush properties available. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D6174
Diffstat (limited to 'source/blender/editors/sculpt_paint')
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c82
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c348
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h6
3 files changed, 435 insertions, 1 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index b9af33e7ad6..ac738f326a3 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1214,6 +1214,70 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession
}
}
+static void sculpt_multiplane_scrape_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ float *outline_col,
+ float outline_alpha)
+{
+ float local_mat_inv[4][4];
+ invert_m4_m4(local_mat_inv, ss->cache->stroke_local_mat);
+ GPU_matrix_mul(local_mat_inv);
+ float angle = ss->cache->multiplane_scrape_sampled_angle;
+ if (ss->cache->pen_flip || ss->cache->invert) {
+ angle = -angle;
+ }
+
+ float offset = ss->cache->radius * 0.25f;
+
+ float p[3] = {0.0f, 0.0f, ss->cache->radius};
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float p_l[3];
+ float p_r[3];
+ float area_center[3] = {0.0f, 0.0f, 0.0f};
+ rotate_v3_v3v3fl(p_r, p, y_axis, DEG2RADF((angle + 180) * 0.5f));
+ rotate_v3_v3v3fl(p_l, p, y_axis, DEG2RADF(-(angle + 180) * 0.5f));
+
+ immBegin(GPU_PRIM_LINES, 14);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immEnd();
+
+ immUniformColor3fvAlpha(outline_col, outline_alpha * 0.1f);
+ immBegin(GPU_PRIM_TRIS, 12);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immEnd();
+}
+
static bool paint_use_2d_cursor(ePaintMode mode)
{
if (mode >= PAINT_MODE_TEXTURE_3D) {
@@ -1516,6 +1580,24 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
}
}
+ if (brush->sculpt_tool == SCULPT_TOOL_MULTIPLANE_SCRAPE &&
+ brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW && !ss->cache->first_time) {
+ GPU_matrix_push_projection();
+ ED_view3d_draw_setup_view(CTX_wm_window(C),
+ CTX_data_depsgraph_pointer(C),
+ CTX_data_scene(C),
+ ar,
+ CTX_wm_view3d(C),
+ NULL,
+ NULL,
+ NULL);
+ GPU_matrix_push();
+ GPU_matrix_mul(vc.obact->obmat);
+ sculpt_multiplane_scrape_preview_draw(pos, ss, outline_col, outline_alpha);
+ GPU_matrix_pop();
+ GPU_matrix_pop_projection();
+ }
+
wmWindowViewport(win);
}
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index fcc2a7a08b3..6cda6ccac06 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1771,6 +1771,10 @@ static float brush_strength(const Sculpt *sd,
return 0.125f * alpha * flip * pressure * overlap * feather;
}
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ overlap = (1.0f + overlap) / 2.0f;
+ return alpha * flip * pressure * overlap * feather;
+
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
case SCULPT_TOOL_FLATTEN:
@@ -4843,6 +4847,341 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
BKE_pbvh_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings);
}
+/* -------------------------------------------------------------------- */
+
+/** \name Sculpt Multiplane Scrape Brush
+ * \{ */
+
+typedef struct MultiplaneScrapeSampleData {
+ float area_cos[2][3];
+ float area_nos[2][3];
+ int area_count[2];
+} MultiplaneScrapeSampleData;
+
+static void calc_multiplane_scrape_surface_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+ MultiplaneScrapeSampleData *mssd = tls->userdata_chunk;
+ float(*mat)[4] = data->mat;
+
+ PBVHVertexIter vd;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+ ss, &test, brush->falloff_shape);
+
+ /* Apply the brush normal radius to the test before sampling */
+ float test_radius = sqrtf(test.radius_squared);
+ test_radius *= brush->normal_radius_factor;
+ test.radius_squared = test_radius * test_radius;
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ float local_co[3];
+ float normal[3];
+ if (vd.no) {
+ normal_short_to_float_v3(normal, vd.no);
+ }
+ else {
+ copy_v3_v3(normal, vd.fno);
+ }
+ mul_v3_m4v3(local_co, mat, vd.co);
+ /* Use the brush falloff to weight the sampled normals */
+ const float fade = tex_strength(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ /* Sample the normal and area of the +X and -X axis individually */
+ if (local_co[0] > 0.0f) {
+ madd_v3_v3fl(mssd->area_nos[0], normal, fade);
+ add_v3_v3(mssd->area_cos[0], vd.co);
+ mssd->area_count[0]++;
+ }
+ else {
+ madd_v3_v3fl(mssd->area_nos[1], normal, fade);
+ add_v3_v3(mssd->area_cos[1], vd.co);
+ mssd->area_count[1]++;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+static void calc_multiplane_scrape_surface_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ MultiplaneScrapeSampleData *join = chunk_join;
+ MultiplaneScrapeSampleData *mssd = chunk;
+
+ add_v3_v3(join->area_cos[0], mssd->area_cos[0]);
+ add_v3_v3(join->area_cos[1], mssd->area_cos[1]);
+
+ add_v3_v3(join->area_nos[0], mssd->area_nos[0]);
+ add_v3_v3(join->area_nos[1], mssd->area_nos[1]);
+
+ join->area_count[0] += mssd->area_count[0];
+ join->area_count[1] += mssd->area_count[1];
+}
+
+static void do_multiplane_scrape_brush_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+ float(*mat)[4] = data->mat;
+ float(*scrape_planes)[4] = data->multiplane_scrape_planes;
+
+ float angle = data->multiplane_scrape_angle;
+
+ PBVHVertexIter vd;
+ float(*proxy)[3];
+ const float bstrength = fabsf(ss->cache->bstrength);
+
+ proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ float local_co[3];
+ bool deform = false;
+
+ mul_v3_m4v3(local_co, mat, vd.co);
+
+ if (local_co[0] > 0.0f) {
+ deform = !plane_point_side(vd.co, scrape_planes[0]);
+ }
+ else {
+ deform = !plane_point_side(vd.co, scrape_planes[1]);
+ }
+
+ if (angle < 0.0f) {
+ deform = true;
+ }
+
+ if (deform) {
+ float intr[3];
+ float val[3];
+
+ if (local_co[0] > 0.0f) {
+ closest_to_plane_normalized_v3(intr, scrape_planes[0], vd.co);
+ }
+ else {
+ closest_to_plane_normalized_v3(intr, scrape_planes[1], vd.co);
+ }
+
+ sub_v3_v3v3(val, intr, vd.co);
+ if (plane_trim(ss->cache, brush, val)) {
+ /* Deform the local space along the Y axis to avoid artifacts on curved strokes */
+ /* This produces a not round brush tip */
+ local_co[1] *= 2.0f;
+ const float fade = bstrength * tex_strength(ss,
+ brush,
+ vd.co,
+ len_v3(local_co),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ mul_v3_v3fl(proxy[vd.i], val, fade);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ }
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float radius = flip ? -ss->cache->radius : ss->cache->radius;
+ const float offset = get_offset(sd, ss);
+ const float displace = -radius * offset;
+
+ float area_no_sp[3]; /* the sculpt-plane normal (whatever its set to) */
+ float area_no[3]; /* geometry normal */
+ float area_co[3];
+
+ float temp[3];
+ float mat[4][4];
+
+ calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+
+ if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
+ calc_area_normal(sd, ob, nodes, totnode, area_no);
+ }
+ else {
+ copy_v3_v3(area_no, area_no_sp);
+ }
+
+ /* delay the first daub because grab delta is not setup */
+ if (ss->cache->first_time) {
+ return;
+ }
+
+ if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
+ return;
+ }
+
+ mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
+ mul_v3_fl(temp, displace);
+ add_v3_v3(area_co, temp);
+
+ /* Init brush local space matrix */
+ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
+ mat[0][3] = 0;
+ cross_v3_v3v3(mat[1], area_no, mat[0]);
+ mat[1][3] = 0;
+ copy_v3_v3(mat[2], area_no);
+ mat[2][3] = 0;
+ copy_v3_v3(mat[3], ss->cache->location);
+ mat[3][3] = 1;
+ normalize_m4(mat);
+ invert_m4(mat);
+
+ float angle = brush->multiplane_scrape_angle;
+
+ /* Update matrix for the cursor preview */
+ if (ss->cache->mirror_symmetry_pass == 0) {
+ copy_m4_m4(ss->cache->stroke_local_mat, mat);
+ }
+
+ /* Dynamic mode */
+
+ if (brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_DYNAMIC) {
+ /* Sample the individual normal and area center of the two areas at both sides of the cursor */
+ SculptThreadedTaskData sample_data = {
+ .sd = NULL,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .totnode = totnode,
+ .mat = mat,
+ };
+
+ MultiplaneScrapeSampleData mssd = {{{0}}};
+
+ PBVHParallelSettings sample_settings;
+ BKE_pbvh_parallel_range_settings(&sample_settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ sample_settings.func_reduce = calc_multiplane_scrape_surface_reduce;
+ sample_settings.userdata_chunk = &mssd;
+ sample_settings.userdata_chunk_size = sizeof(MultiplaneScrapeSampleData);
+
+ BKE_pbvh_parallel_range(
+ 0, totnode, &sample_data, calc_multiplane_scrape_surface_task_cb, &sample_settings);
+
+ float sampled_plane_normals[2][3];
+ float sampled_plane_co[2][3];
+ float sampled_cv[2][3];
+ float mid_co[3];
+
+ /* Use the area center of both planes to detect if we are sculpting along a concave or convex
+ * edge */
+ mul_v3_v3fl(sampled_plane_co[0], mssd.area_cos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_co[1], mssd.area_cos[1], 1.0f / (float)mssd.area_count[1]);
+ mid_v3_v3v3(mid_co, sampled_plane_co[0], sampled_plane_co[1]);
+
+ /* Calculate the scrape planes angle based on the sampled normals */
+ mul_v3_v3fl(sampled_plane_normals[0], mssd.area_nos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_normals[1], mssd.area_nos[1], 1.0f / (float)mssd.area_count[1]);
+ normalize_v3(sampled_plane_normals[0]);
+ normalize_v3(sampled_plane_normals[1]);
+
+ float sampled_angle = angle_v3v3(sampled_plane_normals[0], sampled_plane_normals[1]);
+ copy_v3_v3(sampled_cv[0], area_no);
+ sub_v3_v3v3(sampled_cv[1], ss->cache->location, mid_co);
+
+ sampled_angle += DEG2RADF(brush->multiplane_scrape_angle) * ss->cache->pressure;
+
+ /* Invert the angle if we are sculpting along a concave edge */
+ if (dot_v3v3(sampled_cv[0], sampled_cv[1]) < 0.0f) {
+ sampled_angle = -sampled_angle;
+ }
+
+ /* In dynamic mode, set the angle to 0 when inverting the brush, so you can trim plane surfaces
+ * without changing the brush */
+ if (flip) {
+ sampled_angle = 0.0f;
+ }
+ else {
+ copy_v3_v3(area_co, ss->cache->location);
+ }
+
+ angle = RAD2DEGF(sampled_angle);
+ }
+ else {
+
+ /* Standard mode: Scrape with the brush property fixed angle */
+ copy_v3_v3(area_co, ss->cache->location);
+ if (flip) {
+ angle = -angle;
+ }
+ }
+
+ /* Set the angle for the cursor preview */
+ ss->cache->multiplane_scrape_sampled_angle = angle;
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .mat = mat,
+ .multiplane_scrape_angle = angle,
+ };
+
+ /* Calculate the final left and right scrape planes */
+ float plane_no[3];
+ float plane_no_rot[3];
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float mat_inv[4][4];
+ invert_m4_m4(mat_inv, mat);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(-angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[1], area_co, plane_no);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[0], area_co, plane_no);
+
+ PBVHParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ BKE_pbvh_parallel_range(0, totnode, &data, do_multiplane_scrape_brush_task_cb_ex, &settings);
+}
+
/** \} */
static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
@@ -5468,6 +5807,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_CLAY_STRIPS:
do_clay_strips_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ do_multiplane_scrape_brush(sd, ob, nodes, totnode);
+ break;
case SCULPT_TOOL_FILL:
do_fill_brush(sd, ob, nodes, totnode);
break;
@@ -5997,6 +6339,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Elastic Deform Brush";
case SCULPT_TOOL_POSE:
return "Pose Brush";
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ return "Multiplane Scrape Brush";
}
return "Sculpting";
@@ -6260,6 +6604,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_CLAY_STRIPS,
+ SCULPT_TOOL_MULTIPLANE_SCRAPE,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_POSE,
SCULPT_TOOL_THUMB) ||
@@ -6295,6 +6640,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
add_v3_v3(cache->grab_delta, delta);
break;
case SCULPT_TOOL_CLAY_STRIPS:
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
case SCULPT_TOOL_NUDGE:
case SCULPT_TOOL_SNAKE_HOOK:
if (brush->flag & BRUSH_ANCHORED) {
@@ -7759,7 +8105,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *UNUSED(op))
break;
case PBVH_FACES:
/* Mesh Symmetrize */
- ED_sculpt_undo_geometry_begin(ob);
+ ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize");
Mesh *mesh = ob->data;
Mesh *mesh_mirror;
MirrorModifierData mmd = {0};
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 93e4a777569..0b25ab31ce0 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -206,6 +206,9 @@ typedef struct SculptThreadedTaskData {
float *pose_factor;
float (*transform_rot)[4], (*transform_trans)[4], (*transform_trans_inv)[4];
+ float multiplane_scrape_angle;
+ float multiplane_scrape_planes[2][4];
+
float max_distance_squared;
float nearest_vertex_search_co[3];
@@ -392,6 +395,9 @@ typedef struct StrokeCache {
float *automask;
+ float stroke_local_mat[4][4];
+ float multiplane_scrape_sampled_angle;
+
rcti previous_r; /* previous redraw rectangle */
rcti current_r; /* current redraw rectangle */