diff options
Diffstat (limited to 'source/blender/blenkernel/intern/gpencil.c')
-rw-r--r-- | source/blender/blenkernel/intern/gpencil.c | 500 |
1 files changed, 495 insertions, 5 deletions
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 058c0d10b8e..ed4c6848751 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -414,7 +414,7 @@ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[]) /* general flags */ gpd->flag |= GP_DATA_VIEWALIGN; gpd->flag |= GP_DATA_STROKE_FORCE_RECALC; - /* always enable object onion skin swith */ + /* always enable object onion skin switch */ gpd->flag |= GP_DATA_SHOW_ONIONSKINS; /* GP object specific settings */ ARRAY_SET_ITEMS(gpd->line_color, 0.6f, 0.6f, 0.6f, 0.5f); @@ -1404,6 +1404,313 @@ void BKE_gpencil_dvert_ensure(bGPDstroke *gps) /* ************************************************** */ +static void stroke_defvert_create_nr_list(MDeformVert *dv_list, + int count, + ListBase *result, + int *totweight) +{ + LinkData *ld; + MDeformVert *dv; + MDeformWeight *dw; + int i, j; + int tw = 0; + for (i = 0; i < count; i++) { + dv = &dv_list[i]; + + /* find def_nr in list, if not exist, then create one */ + for (j = 0; j < dv->totweight; j++) { + int found = 0; + dw = &dv->dw[j]; + for (ld = result->first; ld; ld = ld->next) { + if (ld->data == POINTER_FROM_INT(dw->def_nr)) { + found = 1; + break; + } + } + if (!found) { + ld = MEM_callocN(sizeof(LinkData), "def_nr_item"); + ld->data = POINTER_FROM_INT(dw->def_nr); + BLI_addtail(result, ld); + tw++; + } + } + } + + *totweight = tw; +} + +static MDeformVert *stroke_defvert_new_count(int count, int totweight, ListBase *def_nr_list) +{ + int i, j; + LinkData *ld; + MDeformVert *dst = MEM_mallocN(count * sizeof(MDeformVert), "new_deformVert"); + + dst->totweight = totweight; + + for (i = 0; i < count; i++) { + dst[i].dw = MEM_mallocN(sizeof(MDeformWeight) * totweight, "new_deformWeight"); + j = 0; + /* re-assign deform groups */ + for (ld = def_nr_list->first; ld; ld = ld->next) { + dst[i].dw[j].def_nr = POINTER_AS_INT(ld->data); + j++; + } + } + + return dst; +} + +static void stroke_interpolate_deform_weights( + bGPDstroke *gps, int index_from, int index_to, float ratio, MDeformVert *vert) +{ + const MDeformVert *vl = &gps->dvert[index_from]; + const MDeformVert *vr = &gps->dvert[index_to]; + int i; + + for (i = 0; i < vert->totweight; i++) { + float wl = defvert_find_weight(vl, vert->dw[i].def_nr); + float wr = defvert_find_weight(vr, vert->dw[i].def_nr); + vert->dw[i].weight = interpf(wr, wl, ratio); + } +} + +static int stroke_march_next_point(const bGPDstroke *gps, + const int index_next_pt, + const float *current, + const float dist, + float *result, + float *pressure, + float *strength, + float *ratio_result, + int *index_from, + int *index_to) +{ + float remaining_till_next = 0.0f; + float remaining_march = dist; + float step_start[3]; + float point[3]; + int next_point_index = index_next_pt; + bGPDspoint *pt = NULL; + + if (!(next_point_index < gps->totpoints)) { + return -1; + } + + copy_v3_v3(step_start, current); + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + + while (remaining_till_next < remaining_march) { + remaining_march -= remaining_till_next; + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + copy_v3_v3(step_start, point); + next_point_index++; + if (!(next_point_index < gps->totpoints)) { + next_point_index = gps->totpoints - 1; + break; + } + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + } + if (remaining_till_next < remaining_march) { + pt = &gps->points[next_point_index]; + copy_v3_v3(result, &pt->x); + *pressure = gps->points[next_point_index].pressure; + *strength = gps->points[next_point_index].strength; + + *index_from = next_point_index - 1; + *index_to = next_point_index; + *ratio_result = 1.0f; + + return 0; + } + else { + float ratio = remaining_march / remaining_till_next; + interp_v3_v3v3(result, step_start, point, ratio); + *pressure = interpf( + gps->points[next_point_index].pressure, gps->points[next_point_index - 1].pressure, ratio); + *strength = interpf( + gps->points[next_point_index].strength, gps->points[next_point_index - 1].strength, ratio); + + *index_from = next_point_index - 1; + *index_to = next_point_index; + *ratio_result = ratio; + + return next_point_index; + } +} + +static int stroke_march_next_point_no_interp(const bGPDstroke *gps, + const int index_next_pt, + const float *current, + const float dist, + float *result) +{ + float remaining_till_next = 0.0f; + float remaining_march = dist; + float step_start[3]; + float point[3]; + int next_point_index = index_next_pt; + bGPDspoint *pt = NULL; + + if (!(next_point_index < gps->totpoints)) { + return -1; + } + + copy_v3_v3(step_start, current); + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + + while (remaining_till_next < remaining_march) { + remaining_march -= remaining_till_next; + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + copy_v3_v3(step_start, point); + next_point_index++; + if (!(next_point_index < gps->totpoints)) { + next_point_index = gps->totpoints - 1; + break; + } + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + } + if (remaining_till_next < remaining_march) { + pt = &gps->points[next_point_index]; + copy_v3_v3(result, &pt->x); + return 0; + } + else { + float ratio = remaining_march / remaining_till_next; + interp_v3_v3v3(result, step_start, point, ratio); + return next_point_index; + } +} + +static int stroke_march_count(const bGPDstroke *gps, const float dist) +{ + int point_count = 0; + float point[3]; + int next_point_index = 1; + bGPDspoint *pt = NULL; + + pt = &gps->points[0]; + copy_v3_v3(point, &pt->x); + point_count++; + + while ((next_point_index = stroke_march_next_point_no_interp( + gps, next_point_index, point, dist, point)) > -1) { + point_count++; + if (next_point_index == 0) { + break; /* last point finished */ + } + } + return point_count; +} + +/** + * Resample a stroke + * \param gps: Stroke to sample + * \param dist: Distance of one segment + */ +bool BKE_gpencil_sample_stroke(bGPDstroke *gps, const float dist, const bool select) +{ + bGPDspoint *pt = gps->points; + bGPDspoint *pt1 = NULL; + bGPDspoint *pt2 = NULL; + int i; + LinkData *ld; + ListBase def_nr_list = {0}; + + if (gps->totpoints < 2 || dist < FLT_EPSILON) { + return false; + } + /* TODO: Implement feature point preservation. */ + int count = stroke_march_count(gps, dist); + + bGPDspoint *new_pt = MEM_callocN(sizeof(bGPDspoint) * count, "gp_stroke_points_sampled"); + MDeformVert *new_dv = NULL; + + int result_totweight; + + if (gps->dvert != NULL) { + stroke_defvert_create_nr_list(gps->dvert, count, &def_nr_list, &result_totweight); + new_dv = stroke_defvert_new_count(count, result_totweight, &def_nr_list); + } + + int next_point_index = 1; + i = 0; + float pressure, strength, ratio_result; + int index_from, index_to; + float last_coord[3]; + + /* 1st point is always at the start */ + pt1 = &gps->points[0]; + copy_v3_v3(last_coord, &pt1->x); + pt2 = &new_pt[i]; + copy_v3_v3(&pt2->x, last_coord); + new_pt[i].pressure = pt[0].pressure; + new_pt[i].strength = pt[0].strength; + if (select) { + new_pt[i].flag |= GP_SPOINT_SELECT; + } + i++; + + if (new_dv) { + stroke_interpolate_deform_weights(gps, 0, 0, 0, &new_dv[0]); + } + + /* the rest */ + while ((next_point_index = stroke_march_next_point(gps, + next_point_index, + last_coord, + dist, + last_coord, + &pressure, + &strength, + &ratio_result, + &index_from, + &index_to)) > -1) { + pt2 = &new_pt[i]; + copy_v3_v3(&pt2->x, last_coord); + new_pt[i].pressure = pressure; + new_pt[i].strength = strength; + if (select) { + new_pt[i].flag |= GP_SPOINT_SELECT; + } + + if (new_dv) { + stroke_interpolate_deform_weights(gps, index_from, index_to, ratio_result, &new_dv[i]); + } + + i++; + if (next_point_index == 0) { + break; /* last point finished */ + } + } + + gps->points = new_pt; + gps->totpoints = i; + MEM_freeN(pt); /* original */ + + if (new_dv) { + BKE_gpencil_free_stroke_weights(gps); + while ((ld = BLI_pophead(&def_nr_list))) { + MEM_freeN(ld); + } + gps->dvert = new_dv; + } + + gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->tot_triangles = 0; + + return true; +} + /** * Apply smooth to stroke point * \param gps: Stroke to smooth @@ -1632,13 +1939,13 @@ float BKE_gpencil_multiframe_falloff_calc( if (gpf->framenum < actnum) { fnum = (float)(gpf->framenum - f_init) / (actnum - f_init); fnum *= 0.5f; - value = curvemapping_evaluateF(cur_falloff, 0, fnum); + value = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum); } /* frames to the left of the active frame */ else if (gpf->framenum > actnum) { fnum = (float)(gpf->framenum - actnum) / (f_end - actnum); fnum *= 0.5f; - value = curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f); + value = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f); } else { value = 1.0f; @@ -1662,6 +1969,22 @@ void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index) } } +/* remove strokes using a material */ +bool BKE_gpencil_material_index_used(bGPdata *gpd, int index) +{ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + if (gps->mat_nr == index) { + return true; + } + } + } + } + + return false; +} + void BKE_gpencil_material_remap(struct bGPdata *gpd, const unsigned int *remap, unsigned int remap_len) @@ -1990,6 +2313,12 @@ bool BKE_gpencil_close_stroke(bGPDstroke *gps) pt2 = &gps->points[0]; float dist_close = len_v3v3(&pt1->x, &pt2->x); + /* if the distance to close is very small, don't need add points and just enable cyclic. */ + if (dist_close <= dist_avg) { + gps->flag |= GP_STROKE_CYCLIC; + return true; + } + /* Calc number of points required using the average distance. */ int tot_newpoints = MAX2(dist_close / dist_avg, 1); @@ -2006,9 +2335,11 @@ bool BKE_gpencil_close_stroke(bGPDstroke *gps) pt2 = &gps->points[0]; bGPDspoint *pt = &gps->points[old_tot]; for (int i = 1; i < tot_newpoints + 1; i++, pt++) { - float step = ((float)i / (float)tot_newpoints); + float step = (tot_newpoints > 1) ? ((float)i / (float)tot_newpoints) : 0.99f; /* Clamp last point to be near, but not on top of first point. */ - CLAMP(step, 0.0f, 0.99f); + if ((tot_newpoints > 1) && (i == tot_newpoints)) { + step *= 0.99f; + } /* Average point. */ interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step); @@ -2040,3 +2371,162 @@ bool BKE_gpencil_close_stroke(bGPDstroke *gps) return true; } +/* Dissolve points in stroke */ +void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag) +{ + bGPDspoint *pt; + MDeformVert *dvert = NULL; + int i; + + int tot = gps->totpoints; /* number of points in new buffer */ + /* first pass: count points to remove */ + /* Count how many points are selected (i.e. how many to remove) */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & tag) { + /* selected point - one of the points to remove */ + tot--; + } + } + + /* if no points are left, we simply delete the entire stroke */ + if (tot <= 0) { + /* remove the entire stroke */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + if (gps->triangles) { + MEM_freeN(gps->triangles); + } + BLI_freelinkN(&gpf->strokes, gps); + } + else { + /* just copy all points to keep into a smaller buffer */ + bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); + bGPDspoint *npt = new_points; + + MDeformVert *new_dvert = NULL; + MDeformVert *ndvert = NULL; + + if (gps->dvert != NULL) { + new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); + ndvert = new_dvert; + } + + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & tag) == 0) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + } + } + if (gps->dvert != NULL) { + dvert++; + } + } + + /* free the old buffer */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + + /* save the new buffer */ + gps->points = new_points; + gps->dvert = new_dvert; + gps->totpoints = tot; + + /* triangles cache needs to be recalculated */ + gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->tot_triangles = 0; + } +} + +/* Merge by distance ------------------------------------- */ +/* Reduce a series of points when the distance is below a threshold. + * Special case for first and last points (both are keeped) for other points, + * the merge point always is at first point. + * \param gpf: Grease Pencil frame + * \param gps: Grease Pencil stroke + * \param threshold: Distance between points + * \param use_unselected: Set to true to analyze all stroke and not only selected points + */ +void BKE_gpencil_merge_distance_stroke(bGPDframe *gpf, + bGPDstroke *gps, + const float threshold, + const bool use_unselected) +{ + bGPDspoint *pt = NULL; + bGPDspoint *pt_next = NULL; + float tagged = false; + /* Use square distance to speed up loop */ + const float th_square = threshold * threshold; + /* Need to have something to merge. */ + if (gps->totpoints < 2) { + return; + } + int i = 0; + int step = 1; + while ((i < gps->totpoints - 1) && (i + step < gps->totpoints)) { + pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_TAG) { + i++; + step = 1; + continue; + } + pt_next = &gps->points[i + step]; + /* Do not recalc tagged points. */ + if (pt_next->flag & GP_SPOINT_TAG) { + step++; + continue; + } + /* Check if contiguous points are selected. */ + if (!use_unselected) { + if (((pt->flag & GP_SPOINT_SELECT) == 0) || ((pt_next->flag & GP_SPOINT_SELECT) == 0)) { + i++; + step = 1; + continue; + } + } + float len_square = len_squared_v3v3(&pt->x, &pt_next->x); + if (len_square <= th_square) { + tagged = true; + if (i != gps->totpoints - 1) { + /* Tag second point for delete. */ + pt_next->flag |= GP_SPOINT_TAG; + } + else { + pt->flag |= GP_SPOINT_TAG; + } + /* Jump to next pair of points, keeping first point segment equals.*/ + step++; + } + else { + /* Analyze next point. */ + i++; + step = 1; + } + } + + /* Always untag extremes. */ + pt = &gps->points[0]; + pt->flag &= ~GP_SPOINT_TAG; + pt = &gps->points[gps->totpoints - 1]; + pt->flag &= ~GP_SPOINT_TAG; + + /* Dissolve tagged points */ + if (tagged) { + BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); + } +} |