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>2020-03-06 18:00:33 +0300
committerPablo Dobarro <pablodp606@gmail.com>2020-03-06 18:00:33 +0300
commitf2f8c5b2bd984f1034f4ba9999bdc5d3fe72d45a (patch)
tree6d2e19a727e917eb4ebe6734f210316ef6c8a472 /source/blender
parent40ac8250b246743f73010152671b9e60c83c8883 (diff)
Cleanup: Move Multiplane Scrape brush to its own file
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c66
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c383
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h18
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c464
5 files changed, 504 insertions, 428 deletions
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index 1f568250f3f..f497b80e0f4 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -59,6 +59,7 @@ set(SRC
paint_vertex_weight_ops.c
paint_vertex_weight_utils.c
sculpt.c
+ sculpt_multiplane_scrape.c
sculpt_cloth.c
sculpt_pose.c
sculpt_undo.c
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index 963fc556827..07dfa970404 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1223,70 +1223,6 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession
}
}
-static void sculpt_multiplane_scrape_preview_draw(const uint gpuattr,
- SculptSession *ss,
- const float outline_col[3],
- const 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_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) {
@@ -1629,7 +1565,7 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
NULL);
GPU_matrix_push();
GPU_matrix_mul(vc.obact->obmat);
- sculpt_multiplane_scrape_preview_draw(pos, ss, outline_col, outline_alpha);
+ SCULPT_multiplane_scrape_preview_draw(pos, ss, outline_col, outline_alpha);
GPU_matrix_pop();
GPU_matrix_pop_projection();
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index dcbcdc98ddf..3f60d96b7ce 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1897,7 +1897,7 @@ static void calc_area_center(
}
}
-static void calc_area_normal(
+void SCULPT_calc_area_normal(
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3])
{
const Brush *brush = BKE_paint_brush(&sd->paint);
@@ -2413,7 +2413,7 @@ static void calc_sculpt_normal(
break;
case SCULPT_DISP_DIR_AREA:
- calc_area_normal(sd, ob, nodes, totnode, r_area_no);
+ SCULPT_calc_area_normal(sd, ob, nodes, totnode, r_area_no);
break;
default:
@@ -4690,7 +4690,7 @@ static void do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totno
BKE_pbvh_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings);
}
-static int plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3])
+int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3])
{
return (!(brush->flag & BRUSH_PLANE_TRIM) ||
((dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared)));
@@ -4705,13 +4705,13 @@ static bool plane_point_side_flip(const float co[3], const float plane[4], const
return d <= 0.0f;
}
-static int plane_point_side(const float co[3], const float plane[4])
+int SCULPT_plane_point_side(const float co[3], const float plane[4])
{
float d = plane_point_side_v3(plane, co);
return d <= 0.0f;
}
-static float get_offset(Sculpt *sd, SculptSession *ss)
+float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss)
{
Brush *brush = BKE_paint_brush(&sd->paint);
@@ -4756,7 +4756,7 @@ static void do_flatten_brush_task_cb_ex(void *__restrict userdata,
sub_v3_v3v3(val, intr, vd.co);
- if (plane_trim(ss->cache, brush, val)) {
+ if (SCULPT_plane_trim(ss->cache, brush, val)) {
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
@@ -4788,7 +4788,7 @@ static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totno
float area_no[3];
float area_co[3];
- float offset = get_offset(sd, ss);
+ float offset = SCULPT_brush_plane_offset_get(sd, ss);
float displace;
float temp[3];
@@ -4944,7 +4944,7 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
const float initial_radius = fabsf(ss->cache->initial_radius);
bool flip = ss->cache->bstrength < 0.0f;
- float offset = get_offset(sd, ss);
+ float offset = SCULPT_brush_plane_offset_get(sd, ss);
float displace;
float area_no[3];
@@ -5001,349 +5001,6 @@ 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 = SCULPT_brush_strength_factor(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 * SCULPT_brush_strength_factor(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;
-
- /* The sculpt-plane normal (whatever its set to) */
- float area_no_sp[3];
-
- /* Geometry normal. */
- float area_no[3];
- float area_co[3];
-
- float temp[3];
- float mat[4][4];
-
- SCULPT_calc_brush_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) {
- ss->cache->multiplane_scrape_angle = 0.0f;
- 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.0f;
- cross_v3_v3v3(mat[1], area_no, mat[0]);
- mat[1][3] = 0.0f;
- copy_v3_v3(mat[2], area_no);
- mat[2][3] = 0.0f;
- copy_v3_v3(mat[3], ss->cache->location);
- mat[3][3] = 1.0f;
- normalize_m4(mat);
- invert_m4(mat);
-
- /* Update matrix for the cursor preview. */
- if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_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);
- }
-
- /* Interpolate between the previous and new sampled angles to avoid artifacts when if angle
- * difference between two samples is too big. */
- ss->cache->multiplane_scrape_angle = interpf(
- RAD2DEGF(sampled_angle), ss->cache->multiplane_scrape_angle, 0.2f);
- }
- else {
-
- /* Standard mode: Scrape with the brush property fixed angle. */
- copy_v3_v3(area_co, ss->cache->location);
- ss->cache->multiplane_scrape_angle = brush->multiplane_scrape_angle;
- if (flip) {
- ss->cache->multiplane_scrape_angle *= -1.0f;
- }
- }
-
- SculptThreadedTaskData data = {
- .sd = sd,
- .ob = ob,
- .brush = brush,
- .nodes = nodes,
- .mat = mat,
- .multiplane_scrape_angle = ss->cache->multiplane_scrape_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(-ss->cache->multiplane_scrape_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(ss->cache->multiplane_scrape_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,
const int n,
const TaskParallelTLS *__restrict tls)
@@ -5377,7 +5034,7 @@ static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
sub_v3_v3v3(val, intr, vd.co);
- if (plane_trim(ss->cache, brush, val)) {
+ if (SCULPT_plane_trim(ss->cache, brush, val)) {
/* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */
const float fade = bstrength *
SCULPT_brush_strength_factor(ss,
@@ -5409,7 +5066,7 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
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 offset = SCULPT_brush_plane_offset_get(sd, ss);
const float displace = radius * (0.25f + offset);
/* The sculpt-plane normal (whatever its set to). */
@@ -5427,7 +5084,7 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
SCULPT_calc_brush_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);
+ SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
}
else {
copy_v3_v3(area_no, area_no_sp);
@@ -5502,7 +5159,7 @@ static void do_fill_brush_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
- if (plane_point_side(vd.co, test.plane_tool)) {
+ if (SCULPT_plane_point_side(vd.co, test.plane_tool)) {
float intr[3];
float val[3];
@@ -5510,7 +5167,7 @@ static void do_fill_brush_task_cb_ex(void *__restrict userdata,
sub_v3_v3v3(val, intr, vd.co);
- if (plane_trim(ss->cache, brush, val)) {
+ if (SCULPT_plane_trim(ss->cache, brush, val)) {
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
@@ -5542,7 +5199,7 @@ static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
float area_no[3];
float area_co[3];
- float offset = get_offset(sd, ss);
+ float offset = SCULPT_brush_plane_offset_get(sd, ss);
float displace;
@@ -5594,7 +5251,7 @@ static void do_scrape_brush_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
- if (!plane_point_side(vd.co, test.plane_tool)) {
+ if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) {
float intr[3];
float val[3];
@@ -5602,7 +5259,7 @@ static void do_scrape_brush_task_cb_ex(void *__restrict userdata,
sub_v3_v3v3(val, intr, vd.co);
- if (plane_trim(ss->cache, brush, val)) {
+ if (SCULPT_plane_trim(ss->cache, brush, val)) {
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
@@ -5634,7 +5291,7 @@ static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
float area_no[3];
float area_co[3];
- float offset = get_offset(sd, ss);
+ float offset = SCULPT_brush_plane_offset_get(sd, ss);
float displace;
@@ -5754,7 +5411,7 @@ static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
Brush *brush = BKE_paint_brush(&sd->paint);
const float radius = ss->cache->radius;
- const float offset = get_offset(sd, ss);
+ const float offset = SCULPT_brush_plane_offset_get(sd, ss);
const float displace = radius * (0.25f + offset);
/* Sampled geometry normal and area center. */
@@ -5770,7 +5427,7 @@ static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
SCULPT_calc_brush_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);
+ SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no);
}
else {
copy_v3_v3(area_no, area_no_sp);
@@ -6161,7 +5818,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
do_clay_strips_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_MULTIPLANE_SCRAPE:
- do_multiplane_scrape_brush(sd, ob, nodes, totnode);
+ SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_CLAY_THUMB:
do_clay_thumb_brush(sd, ob, nodes, totnode);
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index e65b7404558..612908edfa1 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -155,12 +155,23 @@ void SCULPT_calc_brush_plane(struct Sculpt *sd,
int totnode,
float r_area_no[3],
float r_area_co[3]);
+
+void SCULPT_calc_area_normal(
+ Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]);
+
int SCULPT_nearest_vertex_get(struct Sculpt *sd,
struct Object *ob,
const float co[3],
float max_distance,
bool use_original);
+int SCULPT_plane_point_side(const float co[3], const float plane[4]);
+int SCULPT_plane_trim(const struct StrokeCache *cache,
+ const struct Brush *brush,
+ const float val[3]);
+
+float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss);
+
ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]);
bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm);
bool SCULPT_is_symmetry_iteration_valid(char i, char symm);
@@ -239,6 +250,13 @@ struct SculptPoseIKChain *SCULPT_pose_ik_chain_init(struct Sculpt *sd,
const float radius);
void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
+/* Multiplane Scrape Brush. */
+void SCULPT_do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+void SCULPT_multiplane_scrape_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ const float outline_col[3],
+ const float outline_alpha);
+
/* Sculpt Visibility API */
void SCULPT_visibility_sync_all_face_sets_to_vertices(struct SculptSession *ss);
void SCULPT_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss);
diff --git a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c
new file mode 100644
index 00000000000..038d0e5a08c
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.c
@@ -0,0 +1,464 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+#include "BLI_task.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_brush_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_ccg.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_mesh.h"
+#include "BKE_multires.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BKE_scene.h"
+
+#include "paint_intern.h"
+#include "sculpt_intern.h"
+
+#include "GPU_draw.h"
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
+
+#include "bmesh.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+
+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 = SCULPT_brush_strength_factor(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 = !SCULPT_plane_point_side(vd.co, scrape_planes[0]);
+ }
+ else {
+ deform = !SCULPT_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 (SCULPT_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 * SCULPT_brush_strength_factor(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;
+}
+
+
+/* Public functions. */
+
+/* Main Brush Function. */
+void SCULPT_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 = SCULPT_brush_plane_offset_get(sd, ss);
+ const float displace = -radius * offset;
+
+ /* The sculpt-plane normal (whatever its set to) */
+ float area_no_sp[3];
+
+ /* Geometry normal. */
+ float area_no[3];
+ float area_co[3];
+
+ float temp[3];
+ float mat[4][4];
+
+ SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+
+ if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
+ SCULPT_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) {
+ ss->cache->multiplane_scrape_angle = 0.0f;
+ 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.0f;
+ cross_v3_v3v3(mat[1], area_no, mat[0]);
+ mat[1][3] = 0.0f;
+ copy_v3_v3(mat[2], area_no);
+ mat[2][3] = 0.0f;
+ copy_v3_v3(mat[3], ss->cache->location);
+ mat[3][3] = 1.0f;
+ normalize_m4(mat);
+ invert_m4(mat);
+
+ /* Update matrix for the cursor preview. */
+ if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_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);
+ }
+
+ /* Interpolate between the previous and new sampled angles to avoid artifacts when if angle
+ * difference between two samples is too big. */
+ ss->cache->multiplane_scrape_angle = interpf(
+ RAD2DEGF(sampled_angle), ss->cache->multiplane_scrape_angle, 0.2f);
+ }
+ else {
+
+ /* Standard mode: Scrape with the brush property fixed angle. */
+ copy_v3_v3(area_co, ss->cache->location);
+ ss->cache->multiplane_scrape_angle = brush->multiplane_scrape_angle;
+ if (flip) {
+ ss->cache->multiplane_scrape_angle *= -1.0f;
+ }
+ }
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .mat = mat,
+ .multiplane_scrape_angle = ss->cache->multiplane_scrape_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(-ss->cache->multiplane_scrape_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(ss->cache->multiplane_scrape_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);
+}
+
+void SCULPT_multiplane_scrape_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ const float outline_col[3],
+ const 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_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();
+}