diff options
author | Antonio Vazquez <blendergit@gmail.com> | 2020-03-09 18:27:24 +0300 |
---|---|---|
committer | Antonio Vazquez <blendergit@gmail.com> | 2020-03-09 18:27:24 +0300 |
commit | 29f3af95272590d26f610ae828b2eeee89c82a00 (patch) | |
tree | a696a58a2561c48f7ec6166e369e22081e0a64d8 /source/blender/editors/gpencil/gpencil_paint.c | |
parent | dcb93126876879d969a30a7865700abd072066f8 (diff) |
GPencil: Refactor of Draw Engine, Vertex Paint and all internal functions
This commit is a full refactor of the grease pencil modules including Draw Engine, Modifiers, VFX, depsgraph update, improvements in operators and conversion of Sculpt and Weight paint tools to real brushes.
Also, a huge code cleanup has been done at all levels.
Thanks to @fclem for his work and yo @pepeland and @mendio for the testing and help in the development.
Differential Revision: https://developer.blender.org/D6293
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_paint.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_paint.c | 1374 |
1 files changed, 482 insertions, 892 deletions
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index afc0e66a8a6..fea589746c4 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -149,6 +149,8 @@ typedef struct tGPsdata { /** current object. */ Object *ob; + /** Obeject eval. */ + Object *ob_eval; /** window where painting originated. */ wmWindow *win; /** area where painting originated. */ @@ -215,6 +217,8 @@ typedef struct tGPsdata { float imat[4][4]; float mat[4][4]; + float diff_mat[4][4]; + /** custom color - hack for enforcing a particular color for track/mask editing. */ float custom_color[4]; @@ -270,11 +274,6 @@ static void gp_update_cache(bGPdata *gpd) } } -static bool gp_stroke_added_check(tGPsdata *p) -{ - return (p->gpf && p->gpf->strokes.last && p->flags & GP_PAINTFLAG_STROKEADDED); -} - static void gp_stroke_added_enable(tGPsdata *p) { BLI_assert(p->gpf->strokes.last != NULL); @@ -353,7 +352,7 @@ static void gp_get_3d_reference(tGPsdata *p, float vec[3]) if (p->ownerPtr.type == &RNA_Object) { ob = (Object *)p->ownerPtr.data; } - ED_gp_get_drawing_reference(p->scene, ob, p->gpl, *p->align_flag, vec); + ED_gpencil_drawing_reference_get(p->scene, ob, p->gpl, *p->align_flag, vec); } /* Stroke Editing ---------------------------- */ @@ -483,49 +482,26 @@ static void gp_stroke_convertcoords(tGPsdata *p, const float mval[2], float out[ } } -/* apply jitter to stroke */ -static void gp_brush_jitter(bGPdata *gpd, - Brush *brush, - tGPspoint *pt, - const float mval[2], - const float pressure, - float r_mval[2], - RNG *rng) +/* Apply jitter to stroke point. */ +static void gp_brush_jitter(bGPdata *gpd, tGPspoint *pt, const float amplitude) { - float tmp_pressure = pressure; - if (brush->gpencil_settings->draw_jitter > 0.0f) { - float curvef = BKE_curvemapping_evaluateF(brush->gpencil_settings->curve_jitter, 0, pressure); - tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - } - /* exponential value */ - const float exfactor = (brush->gpencil_settings->draw_jitter + 2.0f) * - (brush->gpencil_settings->draw_jitter + 2.0f); - const float fac = BLI_rng_get_float(rng) * exfactor * tmp_pressure; - /* Jitter is applied perpendicular to the mouse movement vector (2D space) */ - float mvec[2], svec[2]; - /* mouse movement in ints -> floats */ + /* Jitter is applied perpendicular to the mouse movement vector (2D space). */ + float mvec[2]; + /* Mouse movement in ints -> floats. */ if (gpd->runtime.sbuffer_used > 1) { - mvec[0] = (mval[0] - (pt - 1)->x); - mvec[1] = (mval[1] - (pt - 1)->y); + tGPspoint *pt_prev = pt - 1; + sub_v2_v2v2(mvec, &pt->x, &pt_prev->x); normalize_v2(mvec); } else { mvec[0] = 0.0f; mvec[1] = 0.0f; } - /* rotate mvec by 90 degrees... */ - svec[0] = -mvec[1]; - svec[1] = mvec[0]; - /* scale the displacement by the random, and apply */ - if (BLI_rng_get_float(rng) > 0.5f) { - mul_v2_fl(svec, -fac); - } - else { - mul_v2_fl(svec, fac); - } - - r_mval[0] = mval[0] + svec[0]; - r_mval[1] = mval[1] + svec[1]; + /* Rotate mvec by 90 degrees... */ + SWAP(float, mvec[0], mvec[1]); + mvec[0] -= mvec[0]; + /* Scale by displacement amount, and apply. */ + madd_v2_v2fl(&pt->x, mvec, amplitude); } /* apply pressure change depending of the angle of the stroke to simulate a pen with shape */ @@ -581,6 +557,7 @@ static void gp_brush_angle(bGPdata *gpd, Brush *brush, tGPspoint *pt, const floa static void gp_smooth_buffer(tGPsdata *p, float inf, int idx) { bGPdata *gpd = p->gpd; + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; const short num_points = gpd->runtime.sbuffer_used; /* Do nothing if not enough points to smooth out */ @@ -628,9 +605,12 @@ static void gp_smooth_buffer(tGPsdata *p, float inf, int idx) strength += ptd->strength * average_fac; } - /* Based on influence factor, blend between original and optimal smoothed coordinate. */ - interp_v2_v2v2(c, c, sco, inf); - copy_v2_v2(&ptc->x, c); + /* Based on influence factor, blend between original and optimal smoothed coordinate but not + * for Guide mode. */ + if (!guide->use_guide) { + interp_v2_v2v2(c, c, sco, inf); + copy_v2_v2(&ptc->x, c); + } /* Interpolate pressure. */ ptc->pressure = interpf(ptc->pressure, pressure, inf); /* Interpolate strength. */ @@ -661,140 +641,59 @@ static void gp_smooth_segment(bGPdata *gpd, const float inf, int from_idx, int t tGPspoint *ptd = &points[i]; float sco[2] = {0.0f}; + float pressure = 0.0f; + float strength = 0.0f; /* Compute smoothed coordinate by taking the ones nearby */ if (pta) { madd_v2_v2fl(sco, &pta->x, average_fac); + pressure += pta->pressure * average_fac; + strength += pta->strength * average_fac; } else { madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; } if (ptb) { madd_v2_v2fl(sco, &ptb->x, average_fac); + pressure += ptb->pressure * average_fac; + strength += ptb->strength * average_fac; } else { madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; } madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; madd_v2_v2fl(sco, &ptd->x, average_fac); + pressure += ptd->pressure * average_fac; + strength += ptd->strength * average_fac; /* Based on influence factor, blend between original and optimal smoothed coordinate. */ interp_v2_v2v2(&ptc->x, &ptc->x, sco, inf); - } -} - -/* Smooth all the sections created with fake events to avoid abrupt transitions. - * - * As the fake events add points between two real events, this produces a straight line, but if - * there is 3 or more real points that used fakes, the stroke is not smooth and produces abrupt - * angles. - * This function reads these segments and finds the real points and smooth with the surrounding - * points. */ -static void gp_smooth_fake_segments(tGPsdata *p) -{ - bGPdata *gpd = p->gpd; - Brush *brush = p->brush; - if (brush->gpencil_settings->input_samples < 2) { - return; - } - - tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; - tGPspoint *pt = NULL; - /* Index where segment starts. */ - int from_idx = 0; - /* Index where segment ends. */ - int to_idx = 0; - - bool doit = false; - /* Loop all points except the extremes. */ - for (int i = 1; i < gpd->runtime.sbuffer_used - 1; i++) { - pt = &points[i]; - bool is_fake = (bool)(pt->tflag & GP_TPOINT_FAKE); - to_idx = i; - - /* Detect fake points in the stroke. */ - if ((!doit) && (is_fake)) { - from_idx = i; - doit = true; - } - /* If detect control point after fake points, select a segment with same length in both sides, - * except if it is more than stroke length. */ - if ((doit) && (!is_fake)) { - if (i + (i - from_idx) < gpd->runtime.sbuffer_used - 1) { - to_idx = i + (i - from_idx); - /* Smooth this segments (need loop to get cumulative smooth). */ - for (int r = 0; r < 5; r++) { - gp_smooth_segment(gpd, 0.1f, from_idx, to_idx); - } - } - else { - break; - } - /* Reset to new segments. */ - from_idx = i; - doit = false; - } - } -} - -/* Smooth the section added with fake events when pen moves very fast. */ -static void gp_smooth_fake_events(tGPsdata *p, int size_before, int size_after) -{ - bGPdata *gpd = p->gpd; - const short totpoints = size_after - size_before - 1; - /* Do nothing if not enough data to smooth out. */ - if (totpoints < 1) { - return; - } - - /* Back two points to get smoother effect. */ - size_before -= 2; - CLAMP_MIN(size_before, 1); - - tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; - /* Extreme points. */ - const tGPspoint *pta = &points[size_before - 1]; - const tGPspoint *ptb = &points[size_after - 1]; - tGPspoint *pt1, *pt2; - int i; - - /* Get total length of the segment to smooth. */ - float totlen = 0.0f; - for (i = size_before; i < size_after; i++) { - pt1 = &points[i - 1]; - pt2 = &points[i]; - totlen += len_v2v2(&pt1->x, &pt2->x); - } - /* Smooth interpolating the position of the points. */ - float pointlen = 0.0f; - for (i = size_before; i < size_after - 1; i++) { - pt1 = &points[i - 1]; - pt2 = &points[i]; - pointlen += len_v2v2(&pt1->x, &pt2->x); - pt2->pressure = interpf(ptb->pressure, pta->pressure, pointlen / totlen); - pt2->strength = interpf(ptb->strength, pta->strength, pointlen / totlen); + /* Interpolate pressure. */ + ptc->pressure = interpf(ptc->pressure, pressure, inf); + /* Interpolate strength. */ + ptc->strength = interpf(ptc->strength, strength, inf); } } /* add current stroke-point to buffer (returns whether point was successfully added) */ -static short gp_stroke_addpoint( - tGPsdata *p, const float mval[2], float pressure, double curtime, bool is_fake) +static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure, double curtime) { bGPdata *gpd = p->gpd; Brush *brush = p->brush; + BrushGpencilSettings *brush_settings = p->brush->gpencil_settings; tGPspoint *pt; - ToolSettings *ts = p->scene->toolsettings; Object *obact = (Object *)p->ownerPtr.data; - Depsgraph *depsgraph = p->depsgraph; RegionView3D *rv3d = p->region->regiondata; - View3D *v3d = p->sa->spacedata.first; - MaterialGPencilStyle *gp_style = p->material->gp_style; - const int def_nr = obact->actdef - 1; - const bool have_weight = (bool)BLI_findlink(&obact->defbase, def_nr); /* check painting mode */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { @@ -843,111 +742,73 @@ static short gp_stroke_addpoint( return GP_STROKEADD_INVALID; } + /* Set vertex colors for buffer. */ + ED_gpencil_sbuffer_vertex_color_set( + p->depsgraph, p->ob, p->scene->toolsettings, p->brush, p->material); + /* get pointer to destination point */ pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_used); - /* Set if point was created by fake events. */ - if (is_fake) { - pt->tflag |= GP_TPOINT_FAKE; - } - else { - pt->tflag &= ~GP_TPOINT_FAKE; - } - /* store settings */ + pt->strength = brush_settings->draw_strength; + pt->pressure = 1.0f; + pt->uv_rot = 0.0f; + copy_v2_v2(&pt->x, mval); + /* pressure */ - if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_sensitivity, 0, pressure); - pt->pressure = curvef * brush->gpencil_settings->draw_sensitivity; - } - else { - pt->pressure = 1.0f; + if (brush_settings->flag & GP_BRUSH_USE_PRESSURE) { + pt->pressure *= BKE_curvemapping_evaluateF(brush_settings->curve_sensitivity, 0, pressure); } - /* Apply jitter to position */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_jitter > 0.0f)) { - float r_mval[2]; - const float jitpress = (brush->gpencil_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) ? - pressure : - 1.0f; - gp_brush_jitter(gpd, brush, pt, mval, jitpress, r_mval, p->rng); - copy_v2_v2(&pt->x, r_mval); - } - else { - copy_v2_v2(&pt->x, mval); + /* color strength */ + if (brush_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { + pt->strength *= BKE_curvemapping_evaluateF(brush_settings->curve_strength, 0, pressure); + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); } - /* apply randomness to pressure */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_press > 0.0f)) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_sensitivity, 0, pressure); - float tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->pressure -= tmp_pressure * brush->gpencil_settings->draw_random_press * - BLI_rng_get_float(p->rng); + + if (brush_settings->flag & GP_BRUSH_GROUP_RANDOM) { + /* Apply jitter to position */ + if (brush_settings->draw_jitter > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + float jitpress = 1.0f; + if (brush_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) { + jitpress = BKE_curvemapping_evaluateF(brush_settings->curve_jitter, 0, pressure); + } + /* FIXME the +2 means minimum jitter is 4 which is a bit strange for UX. */ + const float exp_factor = brush_settings->draw_jitter + 2.0f; + const float fac = rand * square_f(exp_factor) * jitpress; + gp_brush_jitter(gpd, pt, fac); } - else { - pt->pressure += tmp_pressure * brush->gpencil_settings->draw_random_press * - BLI_rng_get_float(p->rng); + /* apply randomness to pressure */ + if (brush_settings->draw_random_press > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->pressure *= 1.0 + rand * 2.0 * brush_settings->draw_random_press; + CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); } - CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); - } - - /* apply randomness to uv texture rotation */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->uv_random > 0.0f)) { - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->uv_rot = (BLI_rng_get_float(p->rng) * M_PI * -1) * brush->gpencil_settings->uv_random; + /* apply randomness to uv texture rotation */ + if (brush_settings->uv_random > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->uv_rot += rand * M_PI * brush_settings->uv_random; + CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); } - else { - pt->uv_rot = (BLI_rng_get_float(p->rng) * M_PI) * brush->gpencil_settings->uv_random; + /* apply randomness to color strength */ + if (brush_settings->draw_random_strength) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->strength *= 1.0 + rand * brush_settings->draw_random_strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); } - CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); - } - else { - pt->uv_rot = 0.0f; } /* apply angle of stroke to brush size */ - if (brush->gpencil_settings->draw_angle_factor != 0.0f) { + if (brush_settings->draw_angle_factor != 0.0f) { gp_brush_angle(gpd, brush, pt, mval); } - /* color strength */ - if (brush->gpencil_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_strength, 0, pressure); - float tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - - pt->strength = tmp_pressure * brush->gpencil_settings->draw_strength; - } - else { - pt->strength = brush->gpencil_settings->draw_strength; - } - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - - /* apply randomness to color strength */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_strength > 0.0f)) { - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->strength -= pt->strength * brush->gpencil_settings->draw_random_strength * - BLI_rng_get_float(p->rng); - } - else { - pt->strength += pt->strength * brush->gpencil_settings->draw_random_strength * - BLI_rng_get_float(p->rng); - } - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - } - /* point time */ pt->time = (float)(curtime - p->inittime); /* point uv (only 3d view) */ if ((p->sa->spacetype == SPACE_VIEW3D) && (gpd->runtime.sbuffer_used > 0)) { - float pixsize = gp_style->texture_pixsize / 1000000.0f; tGPspoint *ptb = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; bGPDspoint spt, spt2; @@ -961,11 +822,8 @@ static short gp_stroke_addpoint( /* reproject previous */ ED_gpencil_tpoint_to_point(p->region, origin, ptb, &spt2); ED_gp_project_point_to_plane(p->scene, obact, rv3d, origin, p->lock_axis - 1, &spt2); - p->totpixlen += len_v3v3(&spt.x, &spt2.x) / pixsize; + p->totpixlen += len_v3v3(&spt.x, &spt2.x); pt->uv_fac = p->totpixlen; - if ((gp_style) && (gp_style->sima)) { - pt->uv_fac /= gp_style->sima->gen_x; - } } else { p->totpixlen = 0.0f; @@ -975,7 +833,7 @@ static short gp_stroke_addpoint( /* increment counters */ gpd->runtime.sbuffer_used++; - /* smooth while drawing previous points with a reduction factor for previous */ + /* Smooth while drawing previous points with a reduction factor for previous. */ if (brush->gpencil_settings->active_smooth > 0.0f) { for (int s = 0; s < 3; s++) { gp_smooth_buffer(p, @@ -984,100 +842,11 @@ static short gp_stroke_addpoint( } } - return GP_STROKEADD_NORMAL; - } - else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - - /* enable special flag for drawing engine */ - gpd->flag |= GP_DATA_STROKE_POLYGON; - - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); - /* get pointer to destination point */ - pt = (tGPspoint *)(gpd->runtime.sbuffer); - - /* store settings */ - copy_v2_v2(&pt->x, mval); - /* T44932 - Pressure vals are unreliable, so ignore for now */ - pt->pressure = 1.0f; - pt->strength = 1.0f; - pt->time = (float)(curtime - p->inittime); - - /* if there's stroke for this poly line session add (or replace last) point - * to stroke. This allows to draw lines more interactively (see new segment - * during mouse slide, e.g.) - */ - if (gp_stroke_added_check(p)) { - bGPDstroke *gps = p->gpf->strokes.last; - bGPDspoint *pts; - MDeformVert *dvert = NULL; - - /* First time point is adding to temporary buffer (need to allocate new point in stroke) */ - if (gpd->runtime.sbuffer_used == 0) { - gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); - if (gps->dvert != NULL) { - gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1)); - } - gps->totpoints++; - } - - pts = &gps->points[gps->totpoints - 1]; - if (gps->dvert != NULL) { - dvert = &gps->dvert[gps->totpoints - 1]; - } - /* special case for poly lines: normally, - * depth is needed only when creating new stroke from buffer, - * but poly lines are converting to stroke instantly, - * so initialize depth buffer before converting coordinates - */ - if (gpencil_project_check(p)) { - view3d_region_operator_needs_opengl(p->win, p->region); - ED_view3d_autodist_init(p->depsgraph, - p->region, - v3d, - (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0); - } - - /* convert screen-coordinates to appropriate coordinates (and store them) */ - gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL); - /* reproject to plane (only in 3d space) */ - gp_reproject_toplane(p, gps); - /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pts); - /* copy pressure and time */ - pts->pressure = pt->pressure; - pts->strength = pt->strength; - pts->time = pt->time; - pts->uv_fac = pt->uv_fac; - pts->uv_rot = pt->uv_rot; - - if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { - BKE_gpencil_dvert_ensure(gps); - MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); - if (dw) { - dw->weight = ts->vgroup_weight; - } - } - else { - if (dvert != NULL) { - dvert->totweight = 0; - dvert->dw = NULL; - } - } - - /* force fill recalc */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - /* drawing batch cache is dirty now */ - gp_update_cache(p->gpd); - } - - /* increment counters */ - if (gpd->runtime.sbuffer_used == 0) { - gpd->runtime.sbuffer_used++; - } + /* Update evaluated data. */ + ED_gpencil_sbuffer_update_eval(gpd, p->ob_eval); return GP_STROKEADD_NORMAL; } - /* return invalid state for now... */ return GP_STROKEADD_INVALID; } @@ -1143,42 +912,25 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) return; } - /* special case for poly line -- for already added stroke during session - * coordinates are getting added to stroke immediately to allow more - * interactive behavior - */ - if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - /* be sure to hide any lazy cursor */ - ED_gpencil_toggle_brush_cursor(p->C, true, NULL); - - if (gp_stroke_added_check(p)) { - return; - } - } - /* allocate memory for a new stroke */ gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); /* copy appropriate settings for stroke */ gps->totpoints = totelem; gps->thickness = brush->size; - gps->gradient_f = brush->gpencil_settings->gradient_f; - copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s); + gps->fill_opacity_fac = 1.0f; + gps->hardeness = brush->gpencil_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush->gpencil_settings->aspect_ratio); gps->flag = gpd->runtime.sbuffer_sflag; gps->inittime = p->inittime; - - /* enable recalculation flag by default (only used if hq fill) */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->uv_scale = 1.0f; /* allocate enough memory for a continuous array for storage points */ const int subdivide = brush->gpencil_settings->draw_subdivide; gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + gps->dvert = NULL; - /* initialize triangle memory to dummy data */ - gps->triangles = NULL; - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; /* drawing batch cache is dirty now */ gp_update_cache(p->gpd); /* set pointer to first non-initialized point */ @@ -1187,6 +939,9 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) dvert = gps->dvert + (gps->totpoints - totelem); } + /* Apply the vertex color to fill. */ + ED_gpencil_fill_vertex_color_set(ts, brush, gps); + /* copy points from the buffer to the stroke */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { /* straight lines only -> only endpoints */ @@ -1201,6 +956,9 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->strength = ptc->strength; CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); + pt++; if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { @@ -1231,6 +989,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->strength = ptc->strength; CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { BKE_gpencil_dvert_ensure(gps); @@ -1252,7 +1012,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt = gps->points; for (i = 0; i < gps->totpoints; i++, pt++) { /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); + gp_apply_parent_point(depsgraph, obact, gpl, pt); } /* if camera view, reproject flat to view to avoid perspective effect */ @@ -1260,40 +1020,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); } } - else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - /* first point */ - ptc = gpd->runtime.sbuffer; - - /* convert screen-coordinates to appropriate coordinates (and store them) */ - gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); - /* reproject to plane (only in 3d space) */ - gp_reproject_toplane(p, gps); - /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); - /* if camera view, reproject flat to view to avoid perspective effect */ - if (is_camera) { - ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); - } - /* copy pressure and time */ - pt->pressure = ptc->pressure; - pt->strength = ptc->strength; - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - pt->time = ptc->time; - - if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { - BKE_gpencil_dvert_ensure(gps); - MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); - if (dw) { - dw->weight = ts->vgroup_weight; - } - } - else { - if (dvert != NULL) { - dvert->totweight = 0; - dvert->dw = NULL; - } - } - } else { float *depth_arr = NULL; @@ -1370,9 +1096,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) } } - /* Smooth any point created with fake events when the mouse/pen move very fast. */ - gp_smooth_fake_segments(p); - pt = gps->points; dvert = gps->dvert; @@ -1389,6 +1112,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->time = ptc->time; pt->uv_fac = ptc->uv_fac; pt->uv_rot = ptc->uv_rot; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); if (dvert != NULL) { dvert->totweight = 0; @@ -1401,11 +1126,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (subdivide > 0)) { gp_subdivide_stroke(gps, subdivide); } - /* apply randomness to stroke */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_sub > 0.0f)) { - gp_randomize_stroke(gps, brush, p->rng); - } /* Smooth stroke after subdiv - only if there's something to do for each iteration, * the factor is reduced to get a better smoothing @@ -1415,8 +1135,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) float reduce = 0.0f; for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) { for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_smooth_stroke(gps, i, brush->gpencil_settings->draw_smoothfac - reduce); - BKE_gpencil_smooth_stroke_strength(gps, i, brush->gpencil_settings->draw_smoothfac); + BKE_gpencil_stroke_smooth(gps, i, brush->gpencil_settings->draw_smoothfac - reduce); + BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac); } reduce += 0.25f; /* reduce the factor */ } @@ -1425,23 +1145,13 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* Simplify adaptive */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (brush->gpencil_settings->simplify_f > 0.0f)) { - BKE_gpencil_simplify_stroke(gps, brush->gpencil_settings->simplify_f); - } - - /* smooth thickness */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && - (brush->gpencil_settings->thick_smoothfac > 0.0f)) { - for (int r = 0; r < brush->gpencil_settings->thick_smoothlvl * 2; r++) { - for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_smooth_stroke_thickness(gps, i, brush->gpencil_settings->thick_smoothfac); - } - } + BKE_gpencil_stroke_simplify_adaptive(gps, brush->gpencil_settings->simplify_f); } /* reproject to plane (only in 3d space) */ gp_reproject_toplane(p, gps); /* change position relative to parent object */ - gp_apply_parent(depsgraph, obact, gpd, gpl, gps); + gp_apply_parent(depsgraph, obact, gpl, gps); /* if camera view, reproject flat to view to avoid perspective effect */ if (is_camera) { ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); @@ -1463,15 +1173,11 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) } } - /* calculate UVs along the stroke */ - ED_gpencil_calc_stroke_uv(obact, gps); - /* add stroke to frame, usually on tail of the listbase, but if on back is enabled the stroke * is added on listbase head because the drawing order is inverse and the head stroke is the * first to draw. This is very useful for artist when drawing the background. */ - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode != GP_PAINTMODE_DRAW_POLY)) { + if (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) { BLI_addhead(&p->gpf->strokes, gps); } else { @@ -1492,9 +1198,12 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* post process stroke */ if ((p->brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) { - BKE_gpencil_trim_stroke(gps); + BKE_gpencil_stroke_trim(gps); } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + gp_stroke_added_enable(p); } @@ -1540,7 +1249,7 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, float diff_mat[4][4]; /* calculate difference matrix if parent object */ - ED_gpencil_parent_location(p->depsgraph, obact, p->gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(p->depsgraph, obact, gpl, diff_mat); if (ED_view3d_autodist_simple(p->region, mval_i, mval_3d, 0, NULL)) { const float depth_mval = view3d_point_depth(rv3d, mval_3d); @@ -1642,9 +1351,7 @@ static void gp_stroke_soft_refine(bGPDstroke *gps) } /* eraser tool - evaluation per stroke */ -/* TODO: this could really do with some optimization (KD-Tree/BVH?) */ static void gp_stroke_eraser_dostroke(tGPsdata *p, - bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const float mval[2], @@ -1652,21 +1359,15 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, const int radius, const rcti *rect) { - Depsgraph *depsgraph = p->depsgraph; - Object *obact = (Object *)p->ownerPtr.data; Brush *eraser = p->eraser; bGPDspoint *pt0, *pt1, *pt2; int pc0[2] = {0}; int pc1[2] = {0}; int pc2[2] = {0}; int i; - float diff_mat[4][4]; int mval_i[2]; round_v2i_v2fl(mval_i, mval); - /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, p->gpd, gpl, diff_mat); - if (gps->totpoints == 0) { /* just free stroke */ gp_free_stroke(p->gpd, gpf, gps); @@ -1675,7 +1376,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* only process if it hasn't been masked out... */ if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) { bGPDspoint pt_temp; - gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_parent_space(gps->points, p->diff_mat, &pt_temp); gp_point_to_xy(&p->gsc, gps, &pt_temp, &pc1[0], &pc1[1]); /* do boundbox check first */ if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) { @@ -1699,7 +1400,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* get points to work with */ pt1 = gps->points + i; bGPDspoint npt; - gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_parent_space(pt1, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); /* do boundbox check first */ @@ -1751,7 +1452,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, bGPDspoint npt; if (pt0) { - gp_point_to_parent_space(pt0, diff_mat, &npt); + gp_point_to_parent_space(pt0, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc0[0], &pc0[1]); } else { @@ -1759,10 +1460,10 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, copy_v2_v2_int(pc0, pc1); } - gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_parent_space(pt1, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); - gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_parent_space(pt2, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); /* Check that point segment of the boundbox of the eraser stroke */ @@ -1862,8 +1563,6 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* erase strokes which fall under the eraser strokes */ static void gp_stroke_doeraser(tGPsdata *p) { - bGPDlayer *gpl; - bGPDstroke *gps, *gpn; rcti rect; Brush *brush = p->brush; Brush *eraser = p->eraser; @@ -1903,29 +1602,36 @@ static void gp_stroke_doeraser(tGPsdata *p) * only a subset of layers, it is harder to perform the same erase operation * on multiple layers... */ - for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) { bGPDframe *gpf = gpl->actframe; /* only affect layer if it's editable (and visible) */ - if (gpencil_layer_is_editable(gpl) == false) { + if (BKE_gpencil_layer_is_editable(gpl) == false) { continue; } else if (gpf == NULL) { continue; } + /* calculate difference matrix */ + BKE_gpencil_parent_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat); /* loop over strokes, checking segments for intersections */ - for (gps = gpf->strokes.first; gps; gps = gpn) { - gpn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* check if the color is editable */ if (ED_gpencil_stroke_color_use(p->ob, gpl, gps) == false) { continue; } + + /* Check if the stroke collide with mouse. */ + if (!ED_gpencil_stroke_check_collision(&p->gsc, gps, p->mval, calc_radius, p->diff_mat)) { + continue; + } + /* Not all strokes in the datablock may be valid in the current editor/context * (e.g. 2D space strokes in the 3D view, if the same datablock is shared) */ if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) { - gp_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, p->mvalo, calc_radius, &rect); + gp_stroke_eraser_dostroke(p, gpf, gps, p->mval, p->mvalo, calc_radius, &rect); } } } @@ -1964,7 +1670,7 @@ static Brush *gp_get_default_eraser(Main *bmain, ToolSettings *ts) { Brush *brush_dft = NULL; Paint *paint = &ts->gp_paint->paint; - Brush *brush_old = paint->brush; + Brush *brush_prev = paint->brush; for (Brush *brush = bmain->brushes.first; brush; brush = brush->id.next) { if (brush->gpencil_settings == NULL) { continue; @@ -1987,15 +1693,15 @@ static Brush *gp_get_default_eraser(Main *bmain, ToolSettings *ts) } /* create a new soft eraser brush */ else { - brush_dft = BKE_brush_add_gpencil(bmain, ts, "Soft Eraser"); + brush_dft = BKE_brush_add_gpencil(bmain, ts, "Soft Eraser", OB_MODE_PAINT_GPENCIL); brush_dft->size = 30.0f; - brush_dft->gpencil_settings->flag |= (GP_BRUSH_ENABLE_CURSOR | GP_BRUSH_DEFAULT_ERASER); + brush_dft->gpencil_settings->flag |= GP_BRUSH_DEFAULT_ERASER; brush_dft->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_SOFT; brush_dft->gpencil_tool = GPAINT_TOOL_ERASE; brush_dft->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; /* reset current brush */ - BKE_paint_brush_set(paint, brush_old); + BKE_paint_brush_set(paint, brush_prev); return brush_dft; } @@ -2032,7 +1738,7 @@ static void gp_init_drawing_brush(bContext *C, tGPsdata *p) /* if not exist, create a new one */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { /* create new brushes */ - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); changed = true; } /* be sure curves are initializated */ @@ -2054,10 +1760,7 @@ static void gp_init_drawing_brush(bContext *C, tGPsdata *p) /* use radius of eraser */ p->radius = (short)p->eraser->size; - /* GPXX: Need this update to synchronize brush with draw manager. - * Maybe this update can be removed when the new tool system - * will be in place, but while, we need this to keep drawing working. - */ + /* Need this update to synchronize brush with draw manager. */ if (changed) { DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } @@ -2069,33 +1772,11 @@ static void gp_init_colors(tGPsdata *p) bGPdata *gpd = p->gpd; Brush *brush = p->brush; - MaterialGPencilStyle *gp_style = NULL; - /* use brush material */ p->material = BKE_gpencil_object_material_ensure_from_active_input_brush(p->bmain, p->ob, brush); - /* assign color information to temp tGPsdata */ - gp_style = p->material->gp_style; - if (gp_style) { - - /* set colors */ - if (gp_style->flag & GP_STYLE_STROKE_SHOW) { - copy_v4_v4(gpd->runtime.scolor, gp_style->stroke_rgba); - } - else { - /* if no stroke, use fill */ - copy_v4_v4(gpd->runtime.scolor, gp_style->fill_rgba); - } - copy_v4_v4(gpd->runtime.sfill, gp_style->fill_rgba); - /* add some alpha to make easy the filling without hide strokes */ - if (gpd->runtime.sfill[3] > 0.8f) { - gpd->runtime.sfill[3] = 0.8f; - } - - gpd->runtime.mode = (short)gp_style->mode; - gpd->runtime.bstroke_style = gp_style->stroke_style; - gpd->runtime.bfill_style = gp_style->fill_style; - } + gpd->runtime.matid = BKE_object_material_slot_find_index(p->ob, p->material); + gpd->runtime.sbuffer_brush = brush; } /* (re)init new painting data */ @@ -2131,9 +1812,6 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) switch (curarea->spacetype) { /* supported views first */ case SPACE_VIEW3D: { - /* View3D *v3d = curarea->spacedata.first; */ - /* RegionView3D *rv3d = region->regiondata; */ - /* set current area * - must verify that region data is 3D-view (and not something else) */ @@ -2162,10 +1840,11 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) local_view_bits = v3d->local_view_uuid; } /* create new default object */ - obact = ED_gpencil_add_object(C, p->scene, cur, local_view_bits); + obact = ED_gpencil_add_object(C, cur, local_view_bits); } /* assign object after all checks to be sure we have one active */ p->ob = obact; + p->ob_eval = (Object *)DEG_get_evaluated_object(p->depsgraph, p->ob); break; } @@ -2204,21 +1883,14 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) gp_init_drawing_brush(C, p); /* setup active color */ - if (curarea->spacetype == SPACE_VIEW3D) { - /* region where paint was originated */ - p->gpd->runtime.ar = CTX_wm_region(C); - - /* NOTE: This is only done for 3D view, as Materials aren't used for - * annotations in 2D editors - */ - int totcol = p->ob->totcol; + /* region where paint was originated */ + p->gpd->runtime.ar = CTX_wm_region(C); + int totcol = p->ob->totcol; + gp_init_colors(p); - gp_init_colors(p); - - /* check whether the material was newly added */ - if (totcol != p->ob->totcol) { - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, NULL); - } + /* check whether the material was newly added */ + if (totcol != p->ob->totcol) { + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, NULL); } /* lock axis (in some modes, disable) */ @@ -2274,7 +1946,6 @@ static void gp_session_cleanup(tGPsdata *p) /* free stroke buffer */ if (gpd->runtime.sbuffer) { - /* printf("\t\tGP - free sbuffer\n"); */ MEM_SAFE_FREE(gpd->runtime.sbuffer); gpd->runtime.sbuffer = NULL; } @@ -2300,12 +1971,13 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps { Scene *scene = p->scene; ToolSettings *ts = scene->toolsettings; + bool changed = false; /* get active layer (or add a new one if non-existent) */ - p->gpl = BKE_gpencil_layer_getactive(p->gpd); + p->gpl = BKE_gpencil_layer_active_get(p->gpd); if (p->gpl == NULL) { p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true); - + changed = true; if (p->custom_color[3]) { copy_v3_v3(p->gpl->color, p->custom_color); } @@ -2318,46 +1990,28 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps return; } - /* get active frame (add a new one if not matching frame) */ + /* Eraser mode: If no active strokes, just return. */ if (paintmode == GP_PAINTMODE_ERASER) { - /* Eraser mode: - * 1) Add new frames to all frames that we might touch, - * 2) Ensure that p->gpf refers to the frame used for the active layer - * (to avoid problems with other tools which expect it to exist) - */ bool has_layer_to_erase = false; - for (bGPDlayer *gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) { /* Skip if layer not editable */ - if (gpencil_layer_is_editable(gpl) == false) { + if (BKE_gpencil_layer_is_editable(gpl) == false) { continue; } - /* Add a new frame if needed (and based off the active frame, - * as we need some existing strokes to erase) - * - * Note: We don't add a new frame if there's nothing there now, so - * -> If there are no frames at all, don't add one - * -> If there are no strokes in that frame, don't add a new empty frame - */ if (gpl->actframe && gpl->actframe->strokes.first) { - gpl->actframe = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY); has_layer_to_erase = true; + break; } - - /* XXX: we omit GP_FRAME_PAINT here for now, - * as it is only really useful for doing - * paintbuffer drawing - */ } - /* Ensure this gets set... */ - p->gpf = p->gpl->actframe; - if (has_layer_to_erase == false) { p->status = GP_STATUS_ERROR; return; } + /* Ensure this gets set... */ + p->gpf = p->gpl->actframe; } else { /* Drawing Modes - Add a new frame if needed on the active layer */ @@ -2370,7 +2024,7 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps add_frame_mode = GP_GETFRAME_ADD_NEW; } - p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); + p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode); if (p->gpf == NULL) { p->status = GP_STATUS_ERROR; @@ -2397,8 +2051,6 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps /* set special fill stroke mode */ if (p->disable_fill == true) { p->gpd->runtime.sbuffer_sflag |= GP_STROKE_NOFILL; - /* replace stroke color with fill color */ - copy_v4_v4(p->gpd->runtime.scolor, p->gpd->runtime.sfill); } /* set 'initial run' flag, which is only used to denote when a new stroke is starting */ @@ -2443,6 +2095,14 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps } } } + if (!changed) { + /* Copy the brush to avoid a full tag (very slow). */ + bGPdata *gpd_eval = (bGPdata *)p->ob_eval->data; + gpd_eval->runtime.sbuffer_brush = p->gpd->runtime.sbuffer_brush; + } + else { + gp_update_cache(p->gpd); + } } /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ @@ -2487,7 +2147,6 @@ static void gp_paint_cleanup(tGPsdata *p) p->gpf->flag &= ~GP_FRAME_PAINT; } } - /* ------------------------------- */ /* Helper callback for drawing the cursor itself */ @@ -2591,10 +2250,7 @@ static void gpencil_draw_exit(bContext *C, wmOperator *op) else { /* drawing batch cache is dirty now */ bGPdata *gpd = CTX_data_gpencil_data(C); - if (gpd) { - gpd->flag &= ~GP_DATA_STROKE_POLYGON; - gp_update_cache(gpd); - } + gp_update_cache(gpd); } /* clear undo stack */ @@ -2665,23 +2321,6 @@ static int gpencil_draw_init(bContext *C, wmOperator *op, const wmEvent *event) /* ------------------------------- */ -/* ensure that the correct cursor icon is set */ -static void gpencil_draw_cursor_set(tGPsdata *p) -{ - UNUSED_VARS(p); - return; - /* Disable while we get a better cursor handling for direct input devices (Cintiq/Ipad)*/ -#if 0 - Brush *brush = p->brush; - if ((p->paintmode == GP_PAINTMODE_ERASER) || (brush->gpencil_tool == GPAINT_TOOL_ERASE)) { - WM_cursor_modal_set(p->win, WM_CURSOR_CROSS); /* XXX need a better cursor */ - } - else { - WM_cursor_modal_set(p->win, WM_CURSOR_NONE); - } -#endif -} - /* update UI indicators of status, including cursor and header prints */ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) { @@ -2724,13 +2363,6 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) } break; } - case GP_PAINTMODE_DRAW_POLY: { - ED_workspace_status_text( - C, - TIP_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " - "Release Shift/ESC/Enter to end (or click outside this area)")); - break; - } default: /* unhandled future cases */ { ED_workspace_status_text( @@ -2753,88 +2385,6 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) /* ------------------------------- */ -/* create a new stroke point at the point indicated by the painting context */ -static void gpencil_draw_apply( - bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph, bool is_fake) -{ - bGPdata *gpd = p->gpd; - tGPspoint *pt = NULL; - - /* handle drawing/erasing -> test for erasing first */ - if (p->paintmode == GP_PAINTMODE_ERASER) { - /* do 'live' erasing now */ - gp_stroke_doeraser(p); - - /* store used values */ - copy_v2_v2(p->mvalo, p->mval); - p->opressure = p->pressure; - } - /* Only add current point to buffer if mouse moved - * (even though we got an event, it might be just noise). */ - else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) { - - /* if lazy mouse, interpolate the last and current mouse positions */ - if (GPENCIL_LAZY_MODE(p->brush, p->shift)) { - float now_mouse[2]; - float last_mouse[2]; - copy_v2_v2(now_mouse, p->mval); - copy_v2_v2(last_mouse, p->mvalo); - interp_v2_v2v2(now_mouse, now_mouse, last_mouse, p->brush->smooth_stroke_factor); - copy_v2_v2(p->mval, now_mouse); - } - - /* try to add point */ - short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake); - - /* handle errors while adding point */ - if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { - /* finish off old stroke */ - gp_paint_strokeend(p); - /* And start a new one!!! Else, projection errors! */ - gp_paint_initstroke(p, p->paintmode, depsgraph); - - /* start a new stroke, starting from previous point */ - /* XXX Must manually reset inittime... */ - /* XXX We only need to reuse previous point if overflow! */ - if (ok == GP_STROKEADD_OVERFLOW) { - p->inittime = p->ocurtime; - gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime, is_fake); - } - else { - p->inittime = p->curtime; - } - gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake); - } - else if (ok == GP_STROKEADD_INVALID) { - /* the painting operation cannot continue... */ - BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke"); - p->status = GP_STATUS_ERROR; - - if (G.debug & G_DEBUG) { - printf("Error: Grease-Pencil Paint - Add Point Invalid\n"); - } - return; - } - - /* store used values */ - copy_v2_v2(p->mvalo, p->mval); - p->opressure = p->pressure; - p->ocurtime = p->curtime; - - pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; - if (p->paintmode != GP_PAINTMODE_ERASER) { - ED_gpencil_toggle_brush_cursor(C, true, &pt->x); - } - } - else if ((p->brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) && - (gpd->runtime.sbuffer_used > 0)) { - pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; - if (p->paintmode != GP_PAINTMODE_ERASER) { - ED_gpencil_toggle_brush_cursor(C, true, &pt->x); - } - } -} - /* Helper to rotate point around origin */ static void gp_rotate_v2_v2v2fl(float v[2], const float p[2], @@ -2956,66 +2506,151 @@ static void gpencil_speed_guide_init(tGPsdata *p, GP_Sculpt_Guide *guide) } /* apply speed guide */ -static void gpencil_speed_guide(tGPsdata *p, GP_Sculpt_Guide *guide) +static void gpencil_snap_to_guide(const tGPsdata *p, const GP_Sculpt_Guide *guide, float point[2]) { switch (guide->type) { default: case GP_GUIDE_CIRCULAR: { - dist_ensure_v2_v2fl(p->mval, p->guide.origin, p->guide.origin_distance); + dist_ensure_v2_v2fl(point, p->guide.origin, p->guide.origin_distance); break; } case GP_GUIDE_RADIAL: { if (guide->use_snapping && (guide->angle_snap > 0.0f)) { - closest_to_line_v2(p->mval, p->mval, p->guide.rot_point, p->guide.origin); + closest_to_line_v2(point, point, p->guide.rot_point, p->guide.origin); } else { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.origin); + closest_to_line_v2(point, point, p->mvali, p->guide.origin); } break; } case GP_GUIDE_PARALLEL: { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (guide->use_snapping && (guide->spacing > 0.0f)) { - gp_snap_to_rotated_grid_fl(p->mval, p->guide.origin, p->guide.spacing, guide->angle); + gp_snap_to_rotated_grid_fl(point, p->guide.origin, p->guide.spacing, guide->angle); } break; } case GP_GUIDE_ISO: { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (guide->use_snapping && (guide->spacing > 0.0f)) { - gp_snap_to_rotated_grid_fl(p->mval, p->guide.origin, p->guide.spacing, p->guide.rot_angle); + gp_snap_to_rotated_grid_fl(point, p->guide.origin, p->guide.spacing, p->guide.rot_angle); } break; } case GP_GUIDE_GRID: { if (guide->use_snapping && (guide->spacing > 0.0f)) { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (p->straight == STROKE_HORIZONTAL) { - p->mval[1] = gp_snap_to_grid_fl(p->mval[1], p->guide.origin[1], p->guide.spacing); + point[1] = gp_snap_to_grid_fl(point[1], p->guide.origin[1], p->guide.spacing); } else { - p->mval[0] = gp_snap_to_grid_fl(p->mval[0], p->guide.origin[0], p->guide.spacing); + point[0] = gp_snap_to_grid_fl(point[0], p->guide.origin[0], p->guide.spacing); } } else if (p->straight == STROKE_HORIZONTAL) { - p->mval[1] = p->mvali[1]; /* replace y */ + point[1] = p->mvali[1]; /* replace y */ } else { - p->mval[0] = p->mvali[0]; /* replace x */ + point[0] = p->mvali[0]; /* replace x */ } break; } } } +/* create a new stroke point at the point indicated by the painting context */ +static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph) +{ + bGPdata *gpd = p->gpd; + tGPspoint *pt = NULL; + + /* handle drawing/erasing -> test for erasing first */ + if (p->paintmode == GP_PAINTMODE_ERASER) { + /* do 'live' erasing now */ + gp_stroke_doeraser(p); + + /* store used values */ + copy_v2_v2(p->mvalo, p->mval); + p->opressure = p->pressure; + } + /* Only add current point to buffer if mouse moved + * (even though we got an event, it might be just noise). */ + else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) { + + /* if lazy mouse, interpolate the last and current mouse positions */ + if (GPENCIL_LAZY_MODE(p->brush, p->shift)) { + float now_mouse[2]; + float last_mouse[2]; + copy_v2_v2(now_mouse, p->mval); + copy_v2_v2(last_mouse, p->mvalo); + interp_v2_v2v2(now_mouse, now_mouse, last_mouse, p->brush->smooth_stroke_factor); + copy_v2_v2(p->mval, now_mouse); + + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); + if (is_speed_guide) { + gpencil_snap_to_guide(p, guide, p->mval); + } + } + + /* try to add point */ + short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime); + + /* handle errors while adding point */ + if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { + /* finish off old stroke */ + gp_paint_strokeend(p); + /* And start a new one!!! Else, projection errors! */ + gp_paint_initstroke(p, p->paintmode, depsgraph); + + /* start a new stroke, starting from previous point */ + /* XXX Must manually reset inittime... */ + /* XXX We only need to reuse previous point if overflow! */ + if (ok == GP_STROKEADD_OVERFLOW) { + p->inittime = p->ocurtime; + gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime); + } + else { + p->inittime = p->curtime; + } + gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime); + } + else if (ok == GP_STROKEADD_INVALID) { + /* the painting operation cannot continue... */ + BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke"); + p->status = GP_STATUS_ERROR; + + if (G.debug & G_DEBUG) { + printf("Error: Grease-Pencil Paint - Add Point Invalid\n"); + } + return; + } + + /* store used values */ + copy_v2_v2(p->mvalo, p->mval); + p->opressure = p->pressure; + p->ocurtime = p->curtime; + + pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; + if (p->paintmode != GP_PAINTMODE_ERASER) { + ED_gpencil_toggle_brush_cursor(C, true, &pt->x); + } + } + else if ((p->brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) && + (gpd->runtime.sbuffer_used > 0)) { + pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; + if (p->paintmode != GP_PAINTMODE_ERASER) { + ED_gpencil_toggle_brush_cursor(C, true, &pt->x); + } + } +} + /* handle draw event */ static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent *event, - Depsgraph *depsgraph, - float x, - float y, - const bool is_fake) + Depsgraph *depsgraph) { tGPsdata *p = op->customdata; GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; @@ -3025,13 +2660,12 @@ static void gpencil_draw_apply_event(bContext *C, (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); /* convert from window-space to area-space mouse coordinates - * add any x,y override position for fake events + * add any x,y override position */ - p->mval[0] = (float)event->mval[0] - x; - p->mval[1] = (float)event->mval[1] - y; + copy_v2fl_v2i(p->mval, event->mval); p->shift = event->shift; - /* verify direction for straight lines */ + /* verify direction for straight lines and guides */ if ((is_speed_guide) || ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false))) { if (p->straight == 0) { @@ -3099,12 +2733,12 @@ static void gpencil_draw_apply_event(bContext *C, p->flags &= ~GP_PAINTFLAG_FIRSTRUN; /* set values */ - copy_v2_v2(p->mvalo, p->mval); p->opressure = p->pressure; p->inittime = p->ocurtime = p->curtime; p->straight = 0; /* save initial mouse */ + copy_v2_v2(p->mvalo, p->mval); copy_v2_v2(p->mvali, p->mval); if (is_speed_guide && !ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP) && @@ -3120,7 +2754,7 @@ static void gpencil_draw_apply_event(bContext *C, } /* wait for vector then add initial point */ - if (is_speed_guide && p->flags & GP_PAINTFLAG_REQ_VECTOR) { + if (is_speed_guide && (p->flags & GP_PAINTFLAG_REQ_VECTOR)) { if (p->straight == 0) { return; } @@ -3154,23 +2788,13 @@ static void gpencil_draw_apply_event(bContext *C, p->mvali, (p->straight == STROKE_VERTICAL) ? M_PI_2 : 0.0f); } - - /* create fake events */ - float tmp[2]; - copy_v2_v2(tmp, p->mval); - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false); - if (len_v2v2(p->mval, p->mvalo)) { - sub_v2_v2v2(pt, p->mval, p->mvalo); - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false); - } - copy_v2_v2(p->mval, tmp); } /* check if stroke is straight or guided */ if ((p->paintmode != GP_PAINTMODE_ERASER) && ((p->straight) || (is_speed_guide))) { /* guided stroke */ if (is_speed_guide) { - gpencil_speed_guide(p, guide); + gpencil_snap_to_guide(p, guide, p->mval); } else if (p->straight == STROKE_HORIZONTAL) { p->mval[1] = p->mvali[1]; /* replace y */ @@ -3192,7 +2816,7 @@ static void gpencil_draw_apply_event(bContext *C, RNA_float_set(&itemptr, "time", p->curtime - p->inittime); /* apply the current latest drawing point */ - gpencil_draw_apply(C, op, p, depsgraph, is_fake); + gpencil_draw_apply(C, op, p, depsgraph); /* force refresh */ /* just active area for now, since doing whole screen is too slow */ @@ -3207,28 +2831,21 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) tGPsdata *p = NULL; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - /* printf("GPencil - Starting Re-Drawing\n"); */ - /* try to initialize context data needed while drawing */ if (!gpencil_draw_init(C, op, NULL)) { MEM_SAFE_FREE(op->customdata); - /* printf("\tGP - no valid data\n"); */ return OPERATOR_CANCELLED; } else { p = op->customdata; } - /* printf("\tGP - Start redrawing stroke\n"); */ - /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement), * setting the relevant values in context at each step, then applying */ RNA_BEGIN (op->ptr, itemptr, "stroke") { float mousef[2]; - /* printf("\t\tGP - stroke elem\n"); */ - /* get relevant data for this point from stroke */ RNA_float_get_array(&itemptr, "mouse", mousef); p->mval[0] = mousef[0]; @@ -3258,12 +2875,10 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) } /* apply this data as necessary now (as per usual) */ - gpencil_draw_apply(C, op, p, depsgraph, false); + gpencil_draw_apply(C, op, p, depsgraph); } RNA_END; - /* printf("\tGP - done\n"); */ - /* cleanup */ gpencil_draw_exit(C, op); @@ -3400,9 +3015,9 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event else { /* don't erase empty frames */ bool has_layer_to_erase = false; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* Skip if layer not editable */ - if (gpencil_layer_is_editable(gpl)) { + if (BKE_gpencil_layer_is_editable(gpl)) { if (gpl->actframe && gpl->actframe->strokes.first) { has_layer_to_erase = true; break; @@ -3430,35 +3045,25 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event } /* TODO: set any additional settings that we can take from the events? - * TODO? if tablet is erasing, force eraser to be on? */ - - /* if eraser is on, draw radial aid */ + * if eraser is on, draw radial aid */ if (p->paintmode == GP_PAINTMODE_ERASER) { gpencil_draw_toggle_eraser_cursor(C, p, true); } else { ED_gpencil_toggle_brush_cursor(C, true, NULL); } - /* set cursor - * NOTE: This may change later (i.e. intentionally via brush toggle, - * or unintentionally if the user scrolls outside the area)... - */ - gpencil_draw_cursor_set(p); /* only start drawing immediately if we're allowed to do so... */ if (RNA_boolean_get(op->ptr, "wait_for_input") == false) { /* hotkey invoked - start drawing */ - /* printf("\tGP - set first spot\n"); */ p->status = GP_STATUS_PAINTING; /* handle the initial drawing - i.e. for just doing a simple dot */ - gpencil_draw_apply_event( - C, op, event, CTX_data_ensure_evaluated_depsgraph(C), 0.0f, 0.0f, false); + gpencil_draw_apply_event(C, op, event, CTX_data_ensure_evaluated_depsgraph(C)); op->flag |= OP_IS_MODAL_CURSOR_REGION; } else { /* toolbar invoked - don't start drawing yet... */ - /* printf("\tGP - hotkey invoked... waiting for click-drag\n"); */ op->flag |= OP_IS_MODAL_CURSOR_REGION; } @@ -3510,12 +3115,7 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) p->status = GP_STATUS_ERROR; } - /* printf("\t\tGP - start stroke\n"); */ - /* we may need to set up paint env again if we're resuming */ - /* XXX: watch it with the paintmode! in future, - * it'd be nice to allow changing paint-mode when in sketching-sessions */ - if (gp_session_initdata(C, op, p)) { gp_paint_initstroke(p, p->paintmode, CTX_data_depsgraph_pointer(C)); } @@ -3528,134 +3128,222 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) return op->customdata; } -static void gpencil_stroke_end(wmOperator *op) +/* Add arc points between two mouse events using the previous segment to determine the vertice of + * the arc. + * /+ CTL + * / | + * / | + * PtA +...|...+ PtB + * / + * / + * + PtA - 1 + * / + * CTL is the vertice of the triangle created between PtA and PtB */ +static void gpencil_add_arc_points(tGPsdata *p, float mval[2], int segments) { - tGPsdata *p = op->customdata; + bGPdata *gpd = p->gpd; + if (gpd->runtime.sbuffer_used < 3) { + return; + } + + int idx_prev = gpd->runtime.sbuffer_used; + + /* Add space for new arc points. */ + gpd->runtime.sbuffer_used += segments - 1; + + /* Check if still room in buffer or add more. */ + gpd->runtime.sbuffer = ED_gpencil_sbuffer_ensure( + gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); + + tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; + tGPspoint *pt = NULL; + tGPspoint *pt_before = &points[idx_prev - 1]; /* current - 2 */ + tGPspoint *pt_prev = &points[idx_prev - 2]; /* previous */ + + /* Create two vectors, previous and half way of the actual to get the vertex of the triangle + * for arc curve. + */ + float v_prev[2], v_cur[2], v_half[2]; + sub_v2_v2v2(v_cur, mval, &pt_prev->x); + + sub_v2_v2v2(v_prev, &pt_prev->x, &pt_before->x); + interp_v2_v2v2(v_half, &pt_prev->x, mval, 0.5f); + sub_v2_v2(v_half, &pt_prev->x); + + /* If angle is too sharp undo all changes and return. */ + const float min_angle = DEG2RADF(120.0f); + float angle = angle_v2v2(v_prev, v_half); + if (angle < min_angle) { + gpd->runtime.sbuffer_used -= segments - 1; + return; + } + + /* Project the half vector to the previous vector and calculate the mid projected point. */ + float dot = dot_v2v2(v_prev, v_half); + float l = len_squared_v2(v_prev); + if (l > 0.0f) { + mul_v2_fl(v_prev, dot / l); + } - gp_paint_cleanup(p); + /* Calc the position of the control point. */ + float ctl[2]; + add_v2_v2v2(ctl, &pt_prev->x, v_prev); - gpencil_undo_push(p->gpd); + float step = M_PI_2 / (float)(segments + 1); + float a = step; - gp_session_cleanup(p); + float midpoint[2], start[2], end[2], cp1[2], corner[2]; + mid_v2_v2v2(midpoint, &pt_prev->x, mval); + copy_v2_v2(start, &pt_prev->x); + copy_v2_v2(end, mval); + copy_v2_v2(cp1, ctl); - p->status = GP_STATUS_IDLING; - op->flag |= OP_IS_MODAL_CURSOR_REGION; + corner[0] = midpoint[0] - (cp1[0] - midpoint[0]); + corner[1] = midpoint[1] - (cp1[1] - midpoint[1]); - p->gpd = NULL; - p->gpl = NULL; - p->gpf = NULL; + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + pt->x = corner[0] + (end[0] - corner[0]) * sinf(a) + (start[0] - corner[0]) * cosf(a); + pt->y = corner[1] + (end[1] - corner[1]) * sinf(a) + (start[1] - corner[1]) * cosf(a); + + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_prev->pressure; + pt->strength = pt_prev->strength; + + a += step; + } } -/* Move last stroke in the listbase to the head - * to be drawn below all previous strokes in the layer. */ -static void gpencil_move_last_stroke_to_back(bContext *C) +static void gpencil_add_guide_points(const tGPsdata *p, + const GP_Sculpt_Guide *guide, + const float start[2], + const float end[2], + int segments) { - /* Move last stroke (the polygon) to head of the listbase stroke - * to draw on back of all previous strokes. */ - bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); - - /* sanity checks */ - if (ELEM(NULL, gpd, gpl, gpl->actframe)) { + bGPdata *gpd = p->gpd; + if ((gpd->runtime.sbuffer_used == 0)) { return; } - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps = gpf->strokes.last; - if (ELEM(NULL, gps)) { - return; + int idx_prev = gpd->runtime.sbuffer_used; + + /* Add space for new points. */ + gpd->runtime.sbuffer_used += segments - 1; + + /* Check if still room in buffer or add more. */ + gpd->runtime.sbuffer = ED_gpencil_sbuffer_ensure( + gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); + + tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; + tGPspoint *pt = NULL; + tGPspoint *pt_before = &points[idx_prev - 1]; + + /* Use arc sampling for circular guide */ + if (guide->type == GP_GUIDE_CIRCULAR) { + float cw = cross_tri_v2(start, p->guide.origin, end); + float angle = angle_v2v2v2(start, p->guide.origin, end); + + float step = angle / (float)(segments + 1); + if (cw < 0.0f) { + step = -step; + } + + float a = step; + + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + + gp_rotate_v2_v2v2fl(&pt->x, start, p->guide.origin, -a); + gpencil_snap_to_guide(p, guide, &pt->x); + a += step; + + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_before->pressure; + pt->strength = pt_before->strength; + } } + else { + float step = 1.0f / (float)(segments + 1); + float a = step; + + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + + interp_v2_v2v2(&pt->x, start, end, a); + gpencil_snap_to_guide(p, guide, &pt->x); + a += step; - BLI_remlink(&gpf->strokes, gps); - BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps); + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_before->pressure; + pt->strength = pt_before->strength; + } + } } -/* Add fake events for missing mouse movements when the artist draw very fast */ -static bool gpencil_add_fake_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p) +/* Add fake points for missing mouse movements when the artist draw very fast creating an arc + * with the vertice in the midle of the segment and using the angle of the previous segment. */ +static void gpencil_add_fake_points(const wmEvent *event, tGPsdata *p) { Brush *brush = p->brush; + /* Lazy mode do not use fake events. */ + if (GPENCIL_LAZY_MODE(brush, p->shift)) { + return; + } + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); int input_samples = brush->gpencil_settings->input_samples; - bool added_events = false; - /* ensure sampling when using circular guide */ - if (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR)) { + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); + + /* TODO: ensure sampling enough points when using circular guide, + but the arc must be around the center. (see if above to check other guides only) + */ + if (is_speed_guide && (guide->type == GP_GUIDE_CIRCULAR)) { input_samples = GP_MAX_INPUT_SAMPLES; } if (input_samples == 0) { - return added_events; + return; } - RegionView3D *rv3d = p->region->regiondata; - float defaultpixsize = rv3d->pixsize * 1000.0f; - int samples = (GP_MAX_INPUT_SAMPLES - input_samples + 1); - float thickness = (float)brush->size; + int samples = GP_MAX_INPUT_SAMPLES - input_samples + 1; - float pt[2], a[2], b[2]; - float vec[3]; - float scale = 1.0f; + float mouse_prv[2], mouse_cur[2]; + float min_dist = 4.0f * samples; - /* get pixel scale */ - gp_get_3d_reference(p, vec); - mul_m4_v3(rv3d->persmat, vec); - if (rv3d->is_persp) { - scale = vec[2] * defaultpixsize; - } - else { - scale = defaultpixsize; - } + copy_v2_v2(mouse_prv, p->mvalo); + copy_v2fl_v2i(mouse_cur, event->mval); - /* The thickness of the brush is reduced of thickness to get overlap dots */ - float dot_factor = 0.50f; - if (samples < 2) { - dot_factor = 0.05f; - } - else if (samples < 4) { - dot_factor = 0.10f; - } - else if (samples < 7) { - dot_factor = 0.3f; - } - else if (samples < 10) { - dot_factor = 0.4f; + /* get distance in pixels */ + float dist = len_v2v2(mouse_prv, mouse_cur); + + /* get distance for circular guide */ + if (is_speed_guide && (guide->type == GP_GUIDE_CIRCULAR)) { + float middle[2]; + gpencil_snap_to_guide(p, guide, mouse_prv); + gpencil_snap_to_guide(p, guide, mouse_cur); + mid_v2_v2v2(middle, mouse_cur, mouse_prv); + gpencil_snap_to_guide(p, guide, middle); + dist = len_v2v2(mouse_prv, middle) + len_v2v2(middle, mouse_cur); } - float factor = ((thickness * dot_factor) / scale) * samples; - copy_v2_v2(a, p->mvalo); - b[0] = (float)event->mval[0] + 1.0f; - b[1] = (float)event->mval[1] + 1.0f; + if ((dist > 3.0f) && (dist > min_dist)) { + int slices = (dist / min_dist) + 1; - /* get distance in pixels */ - float dist = len_v2v2(a, b); - - /* for very small distances, add a half way point */ - if (dist <= 2.0f) { - interp_v2_v2v2(pt, a, b, 0.5f); - sub_v2_v2v2(pt, b, pt); - /* create fake event */ - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true); - added_events = true; - } - else if (dist >= factor) { - int slices = 2 + (int)((dist - 1.0) / factor); - float n = 1.0f / slices; - for (int i = 1; i < slices; i++) { - interp_v2_v2v2(pt, a, b, n * i); - sub_v2_v2v2(pt, b, pt); - /* create fake event */ - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true); - added_events = true; - } - } - return added_events; + if (is_speed_guide) { + gpencil_add_guide_points(p, guide, mouse_prv, mouse_cur, slices); + } + else { + gpencil_add_arc_points(p, mouse_cur, slices); + } + } } /* events handling during interactive drawing part of operator */ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) { tGPsdata *p = op->customdata; - ToolSettings *ts = CTX_data_tool_settings(C); + // ToolSettings *ts = CTX_data_tool_settings(C); GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; /* default exit state - pass through to support MMB view nav, etc. */ @@ -3754,98 +3442,26 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - // printf("\tGP - handle modal event...\n"); - /* Exit painting mode (and/or end current stroke). * - * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) - * as that would break polyline T32647. */ - /* if polyline and release shift must cancel */ - if ((ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) || - ((p->paintmode == GP_PAINTMODE_DRAW_POLY) && (event->shift == 0))) { - /* exit() ends the current stroke before cleaning up */ - /* printf("\t\tGP - end of paint op + end of stroke\n"); */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } + if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) { p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } /* toggle painting mode upon mouse-button movement - * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox - * only) - * - RIGHTMOUSE = polyline (hotkey) / eraser (all) + * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) + * - RIGHTMOUSE = eraser (all) * (Disabling RIGHTMOUSE case here results in bugs like [#32647]) * also making sure we have a valid event value, to not exit too early */ if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (ELEM(event->val, KM_PRESS, KM_RELEASE))) { /* if painting, end stroke */ if (p->status == GP_STATUS_PAINTING) { - int sketch = 0; - - /* basically, this should be mouse-button up = end stroke - * BUT, polyline drawing is an exception -- all knots should be added during one session - */ - sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY); - - if (sketch) { - /* end stroke only, and then wait to resume painting soon */ - /* printf("\t\tGP - end stroke only\n"); */ - gpencil_stroke_end(op); - - /* If eraser mode is on, turn it off after the stroke finishes - * NOTE: This just makes it nicer to work with drawing sessions - */ - if (p->paintmode == GP_PAINTMODE_ERASER) { - p->paintmode = RNA_enum_get(op->ptr, "mode"); - - /* if the original mode was *still* eraser, - * we'll let it say for now, since this gives - * users an opportunity to have visual feedback - * when adjusting eraser size - */ - if (p->paintmode != GP_PAINTMODE_ERASER) { - /* turn off cursor... - * NOTE: this should be enough for now - * Just hiding this makes it seem like - * you can paint again... - */ - gpencil_draw_toggle_eraser_cursor(C, p, false); - } - } - - /* we've just entered idling state, so this event was processed (but no others yet) */ - estate = OPERATOR_RUNNING_MODAL; - - /* stroke could be smoothed, send notifier to refresh screen */ - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); - } - else { - /* printf("\t\tGP - end of stroke + op\n"); */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } - /* drawing batch cache is dirty now */ - gp_update_cache(p->gpd); - - p->status = GP_STATUS_DONE; - estate = OPERATOR_FINISHED; - } + p->status = GP_STATUS_DONE; + estate = OPERATOR_FINISHED; } else if (event->val == KM_PRESS) { bool in_bounds = false; @@ -3929,15 +3545,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * NOTE: Don't enter this case if an error occurred while finding the * region (as above) */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } @@ -3954,25 +3561,24 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* handle painting mouse-movements? */ if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ - /* printf("\t\tGP - add point\n"); */ + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); int size_before = p->gpd->runtime.sbuffer_used; - bool added_events = false; - if (((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) && (p->paintmode != GP_PAINTMODE_ERASER)) { - added_events = gpencil_add_fake_events(C, op, event, p); + if (((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) && (p->paintmode != GP_PAINTMODE_ERASER) && + !(is_speed_guide && (p->flags & GP_PAINTFLAG_REQ_VECTOR))) { + gpencil_add_fake_points(event, p); } - gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C), 0.0f, 0.0f, false); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C)); int size_after = p->gpd->runtime.sbuffer_used; - /* Last point of the event is always real (not fake). */ - tGPspoint *points = (tGPspoint *)p->gpd->runtime.sbuffer; - tGPspoint *pt = &points[size_after - 1]; - pt->tflag &= ~GP_TPOINT_FAKE; - - /* Smooth the fake events to get smoother strokes, specially at ends. */ - if (added_events) { - gp_smooth_fake_events(p, size_before, size_after); + /* Smooth segments if some fake points were added (need loop to get cumulative smooth). + * the 0.15 value gets a good result in Windows and Linux. */ + if (!is_speed_guide && (size_after - size_before > 1)) { + for (int r = 0; r < 5; r++) { + gp_smooth_segment(p->gpd, 0.15f, size_before - 1, size_after - 1); + } } /* finish painting operation if anything went wrong just now */ @@ -3982,17 +3588,13 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } else { /* event handled, so just tag as running modal */ - /* printf("\t\t\t\tGP - add point handled!\n"); */ estate = OPERATOR_RUNNING_MODAL; } } /* eraser size */ else if ((p->paintmode == GP_PAINTMODE_ERASER) && ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE, PADPLUSKEY, PADMINUS)) { - /* just resize the brush (local version) - * TODO: fix the hardcoded size jumps (set to make a visible difference) and hardcoded keys - */ - /* printf("\t\tGP - resize eraser\n"); */ + /* Just resize the brush (local version). */ switch (event->type) { case WHEELDOWNMOUSE: /* larger */ case PADPLUSKEY: @@ -4032,7 +3634,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) else { /* update status indicators - cursor, header, etc. */ gpencil_draw_status_indicators(C, p); - gpencil_draw_cursor_set(p); /* cursor may have changed outside our control - T44084 */ } /* process last operations before exiting */ @@ -4056,12 +3657,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) case OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH: /* event doesn't need to be handled */ -#if 0 - printf("unhandled event -> %d (mmb? = %d | mmv? = %d)\n", - event->type, - event->type == MIDDLEMOUSE, - event->type == MOUSEMOVE); -#endif break; } @@ -4078,11 +3673,6 @@ static const EnumPropertyItem prop_gpencil_drawmodes[] = { 0, "Draw Straight Lines", "Draw straight line segment(s)"}, - {GP_PAINTMODE_DRAW_POLY, - "DRAW_POLY", - 0, - "Draw Poly Line", - "Click to place endpoints of straight line segments (connected)"}, {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Grease Pencil strokes"}, {0, NULL, 0, NULL, NULL}, }; @@ -4094,7 +3684,7 @@ void GPENCIL_OT_draw(wmOperatorType *ot) /* identifiers */ ot->name = "Grease Pencil Draw"; ot->idname = "GPENCIL_OT_draw"; - ot->description = "Draw a new stroke in the active Grease Pencil Object"; + ot->description = "Draw mouse_prv new stroke in the active Grease Pencil Object"; /* api callbacks */ ot->exec = gpencil_draw_exec; |