/* * ***** BEGIN GPL LICENSE BLOCK ***** * * 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) 2012 Blender Foundation. * All rights reserved. * * * Contributor(s): Blender Foundation, * Sergey Sharybin * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/mask/mask_add.c * \ingroup edmask */ #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_mask.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_mask_types.h" #include "WM_api.h" #include "WM_types.h" #include "ED_mask.h" /* own include */ #include "ED_screen.h" #include "RNA_access.h" #include "RNA_define.h" #include "mask_intern.h" /* own include */ bool ED_mask_find_nearest_diff_point(const bContext *C, struct Mask *mask, const float normal_co[2], int threshold, bool feather, float tangent[2], const bool use_deform, const bool use_project, MaskLayer **masklay_r, MaskSpline **spline_r, MaskSplinePoint **point_r, float *u_r, float *score_r) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); MaskLayer *masklay, *point_masklay; MaskSpline *point_spline; MaskSplinePoint *point = NULL; float dist_best_sq = FLT_MAX, co[2]; int width, height; float u = 0.0f; float scalex, scaley; ED_mask_get_size(sa, &width, &height); ED_mask_pixelspace_factor(sa, ar, &scalex, &scaley); co[0] = normal_co[0] * scalex; co[1] = normal_co[1] * scaley; for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) { MaskSpline *spline; if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) { continue; } for (spline = masklay->splines.first; spline; spline = spline->next) { int i; MaskSplinePoint *cur_point; for (i = 0, cur_point = use_deform ? spline->points_deform : spline->points; i < spline->tot_point; i++, cur_point++) { float *diff_points; unsigned int tot_diff_point; diff_points = BKE_mask_point_segment_diff(spline, cur_point, width, height, &tot_diff_point); if (diff_points) { int j, tot_point; unsigned int tot_feather_point; float *feather_points = NULL, *points; if (feather) { feather_points = BKE_mask_point_segment_feather_diff(spline, cur_point, width, height, &tot_feather_point); points = feather_points; tot_point = tot_feather_point; } else { points = diff_points; tot_point = tot_diff_point; } for (j = 0; j < tot_point - 1; j++) { float dist_sq, a[2], b[2]; a[0] = points[2 * j] * scalex; a[1] = points[2 * j + 1] * scaley; b[0] = points[2 * j + 2] * scalex; b[1] = points[2 * j + 3] * scaley; dist_sq = dist_squared_to_line_segment_v2(co, a, b); if (dist_sq < dist_best_sq) { if (tangent) sub_v2_v2v2(tangent, &diff_points[2 * j + 2], &diff_points[2 * j]); point_masklay = masklay; point_spline = spline; point = use_deform ? &spline->points[(cur_point - spline->points_deform)] : cur_point; dist_best_sq = dist_sq; u = (float)j / tot_point; } } if (feather_points) MEM_freeN(feather_points); MEM_freeN(diff_points); } } } } if (point && dist_best_sq < threshold) { if (masklay_r) *masklay_r = point_masklay; if (spline_r) *spline_r = point_spline; if (point_r) *point_r = point; if (u_r) { /* TODO(sergey): Projection fails in some weirdo cases.. */ if (use_project) { u = BKE_mask_spline_project_co(point_spline, point, u, normal_co, MASK_PROJ_ANY); } *u_r = u; } if (score_r) { *score_r = dist_best_sq; } return true; } if (masklay_r) *masklay_r = NULL; if (spline_r) *spline_r = NULL; if (point_r) *point_r = NULL; return false; } /******************** add vertex *********************/ static void setup_vertex_point( Mask *mask, MaskSpline *spline, MaskSplinePoint *new_point, const float point_co[2], const float u, const float ctime, const MaskSplinePoint *reference_point, const bool reference_adjacent) { const MaskSplinePoint *reference_parent_point = NULL; BezTriple *bezt; float co[3]; copy_v2_v2(co, point_co); co[2] = 0.0f; /* point coordinate */ bezt = &new_point->bezt; bezt->h1 = bezt->h2 = HD_ALIGN; if (reference_point) { if (reference_point->bezt.h1 == HD_VECT && reference_point->bezt.h2 == HD_VECT) { /* If the reference point is sharp try using some smooth point as reference * for handles. */ int point_index = reference_point - spline->points; int delta = new_point == spline->points ? 1 : -1; int i = 0; for (i = 0; i < spline->tot_point - 1; ++i) { MaskSplinePoint *current_point; point_index += delta; if (point_index == -1 || point_index >= spline->tot_point) { if (spline->flag & MASK_SPLINE_CYCLIC) { if (point_index == -1) { point_index = spline->tot_point - 1; } else if (point_index >= spline->tot_point) { point_index = 0; } } else { break; } } current_point = &spline->points[point_index]; if (current_point->bezt.h1 != HD_VECT || current_point->bezt.h2 != HD_VECT) { bezt->h1 = bezt->h2 = MAX2(current_point->bezt.h2, current_point->bezt.h1); break; } } } else { bezt->h1 = bezt->h2 = MAX2(reference_point->bezt.h2, reference_point->bezt.h1); } reference_parent_point = reference_point; } else if (reference_adjacent) { if (spline->tot_point != 1) { MaskSplinePoint *prev_point, *next_point, *close_point; const int index = (int)(new_point - spline->points); if (spline->flag & MASK_SPLINE_CYCLIC) { prev_point = &spline->points[mod_i(index - 1, spline->tot_point)]; next_point = &spline->points[mod_i(index + 1, spline->tot_point)]; } else { prev_point = (index != 0) ? &spline->points[index - 1] : NULL; next_point = (index != spline->tot_point - 1) ? &spline->points[index + 1] : NULL; } if (prev_point && next_point) { close_point = (len_squared_v2v2(new_point->bezt.vec[1], prev_point->bezt.vec[1]) < len_squared_v2v2(new_point->bezt.vec[1], next_point->bezt.vec[1])) ? prev_point : next_point; } else { close_point = prev_point ? prev_point : next_point; } /* handle type */ char handle_type = 0; if (prev_point) { handle_type = prev_point->bezt.h2; } if (next_point) { handle_type = MAX2(next_point->bezt.h2, handle_type); } bezt->h1 = bezt->h2 = handle_type; /* parent */ reference_parent_point = close_point; /* note, we may want to copy other attributes later, radius? pressure? color? */ } } copy_v3_v3(bezt->vec[0], co); copy_v3_v3(bezt->vec[1], co); copy_v3_v3(bezt->vec[2], co); if (reference_parent_point) { new_point->parent = reference_parent_point->parent; if (new_point->parent.id) { float parent_matrix[3][3]; BKE_mask_point_parent_matrix_get(new_point, ctime, parent_matrix); invert_m3(parent_matrix); mul_m3_v2(parent_matrix, new_point->bezt.vec[1]); } } else { BKE_mask_parent_init(&new_point->parent); } if (spline->tot_point != 1) { BKE_mask_calc_handle_adjacent_interp(spline, new_point, u); } /* select new point */ MASKPOINT_SEL_ALL(new_point); ED_mask_select_flush_all(mask); } /* **** add extrude vertex **** */ static void finSelectedSplinePoint(MaskLayer *masklay, MaskSpline **spline, MaskSplinePoint **point, bool check_active) { MaskSpline *cur_spline = masklay->splines.first; *spline = NULL; *point = NULL; if (check_active) { /* TODO, having an active point but no active spline is possible, why? */ if (masklay->act_spline && masklay->act_point && MASKPOINT_ISSEL_ANY(masklay->act_point)) { *spline = masklay->act_spline; *point = masklay->act_point; return; } } while (cur_spline) { int i; for (i = 0; i < cur_spline->tot_point; i++) { MaskSplinePoint *cur_point = &cur_spline->points[i]; if (MASKPOINT_ISSEL_ANY(cur_point)) { if (*spline != NULL && *spline != cur_spline) { *spline = NULL; *point = NULL; return; } else if (*point) { *point = NULL; } else { *spline = cur_spline; *point = cur_point; } } } cur_spline = cur_spline->next; } } /* **** add subdivide vertex **** */ static void mask_spline_add_point_at_index(MaskSpline *spline, int point_index) { MaskSplinePoint *new_point_array; new_point_array = MEM_callocN(sizeof(MaskSplinePoint) * (spline->tot_point + 1), "add mask vert points"); memcpy(new_point_array, spline->points, sizeof(MaskSplinePoint) * (point_index + 1)); memcpy(new_point_array + point_index + 2, spline->points + point_index + 1, sizeof(MaskSplinePoint) * (spline->tot_point - point_index - 1)); MEM_freeN(spline->points); spline->points = new_point_array; spline->tot_point++; } static bool add_vertex_subdivide(const bContext *C, Mask *mask, const float co[2]) { MaskLayer *masklay; MaskSpline *spline; MaskSplinePoint *point = NULL; const float threshold = 9; float tangent[2]; float u; if (ED_mask_find_nearest_diff_point(C, mask, co, threshold, false, tangent, true, true, &masklay, &spline, &point, &u, NULL)) { Scene *scene = CTX_data_scene(C); const float ctime = CFRA; MaskSplinePoint *new_point; int point_index = point - spline->points; ED_mask_select_toggle_all(mask, SEL_DESELECT); mask_spline_add_point_at_index(spline, point_index); new_point = &spline->points[point_index + 1]; setup_vertex_point(mask, spline, new_point, co, u, ctime, NULL, true); /* TODO - we could pass the spline! */ BKE_mask_layer_shape_changed_add(masklay, BKE_mask_layer_shape_spline_to_index(masklay, spline) + point_index + 1, true, true); masklay->act_spline = spline; masklay->act_point = new_point; WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); return true; } return false; } static bool add_vertex_extrude(const bContext *C, Mask *mask, MaskLayer *masklay, const float co[2]) { Scene *scene = CTX_data_scene(C); const float ctime = CFRA; MaskSpline *spline; MaskSplinePoint *point; MaskSplinePoint *new_point = NULL, *ref_point = NULL; /* check on which side we want to add the point */ int point_index; float tangent_point[2]; float tangent_co[2]; bool do_cyclic_correct = false; bool do_prev; /* use prev point rather then next?? */ if (!masklay) { return false; } else { finSelectedSplinePoint(masklay, &spline, &point, true); } ED_mask_select_toggle_all(mask, SEL_DESELECT); point_index = (point - spline->points); MASKPOINT_DESEL_ALL(point); if ((spline->flag & MASK_SPLINE_CYCLIC) || (point_index > 0 && point_index != spline->tot_point - 1)) { BKE_mask_calc_tangent_polyline(spline, point, tangent_point); sub_v2_v2v2(tangent_co, co, point->bezt.vec[1]); if (dot_v2v2(tangent_point, tangent_co) < 0.0f) { do_prev = true; } else { do_prev = false; } } else if (((spline->flag & MASK_SPLINE_CYCLIC) == 0) && (point_index == 0)) { do_prev = true; } else if (((spline->flag & MASK_SPLINE_CYCLIC) == 0) && (point_index == spline->tot_point - 1)) { do_prev = false; } else { do_prev = false; /* quiet warning */ /* should never get here */ BLI_assert(0); } /* use the point before the active one */ if (do_prev) { point_index--; if (point_index < 0) { point_index += spline->tot_point; /* wrap index */ if ((spline->flag & MASK_SPLINE_CYCLIC) == 0) { do_cyclic_correct = true; point_index = 0; } } } // print_v2("", tangent_point); // printf("%d\n", point_index); mask_spline_add_point_at_index(spline, point_index); if (do_cyclic_correct) { ref_point = &spline->points[point_index + 1]; new_point = &spline->points[point_index]; *ref_point = *new_point; memset(new_point, 0, sizeof(*new_point)); } else { ref_point = &spline->points[point_index]; new_point = &spline->points[point_index + 1]; } masklay->act_point = new_point; setup_vertex_point(mask, spline, new_point, co, 0.5f, ctime, ref_point, false); if (masklay->splines_shapes.first) { point_index = (((int)(new_point - spline->points) + 0) % spline->tot_point); BKE_mask_layer_shape_changed_add(masklay, BKE_mask_layer_shape_spline_to_index(masklay, spline) + point_index, true, true); } WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); return true; } static bool add_vertex_new(const bContext *C, Mask *mask, MaskLayer *masklay, const float co[2]) { Scene *scene = CTX_data_scene(C); const float ctime = CFRA; MaskSpline *spline; MaskSplinePoint *new_point = NULL, *ref_point = NULL; if (!masklay) { /* if there's no masklay currently operationg on, create new one */ masklay = BKE_mask_layer_new(mask, ""); mask->masklay_act = mask->masklay_tot - 1; } ED_mask_select_toggle_all(mask, SEL_DESELECT); spline = BKE_mask_spline_add(masklay); masklay->act_spline = spline; new_point = spline->points; masklay->act_point = new_point; setup_vertex_point(mask, spline, new_point, co, 0.5f, ctime, ref_point, false); { int point_index = (((int)(new_point - spline->points) + 0) % spline->tot_point); BKE_mask_layer_shape_changed_add(masklay, BKE_mask_layer_shape_spline_to_index(masklay, spline) + point_index, true, true); } WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); return true; } static int add_vertex_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Mask *mask = CTX_data_edit_mask(C); MaskLayer *masklay; float co[2]; if (mask == NULL) { /* if there's no active mask, create one */ mask = ED_mask_new(C, NULL); } masklay = BKE_mask_layer_active(mask); if (masklay && masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) { masklay = NULL; } RNA_float_get_array(op->ptr, "location", co); /* TODO, having an active point but no active spline is possible, why? */ if (masklay && masklay->act_spline && masklay->act_point && MASKPOINT_ISSEL_ANY(masklay->act_point)) { /* cheap trick - double click for cyclic */ MaskSpline *spline = masklay->act_spline; MaskSplinePoint *point = masklay->act_point; const bool is_sta = (point == spline->points); const bool is_end = (point == &spline->points[spline->tot_point - 1]); /* then check are we overlapping the mouse */ if ((is_sta || is_end) && equals_v2v2(co, point->bezt.vec[1])) { if (spline->flag & MASK_SPLINE_CYCLIC) { /* nothing to do */ return OPERATOR_CANCELLED; } else { /* recalc the connecting point as well to make a nice even curve */ MaskSplinePoint *point_other = is_end ? spline->points : &spline->points[spline->tot_point - 1]; spline->flag |= MASK_SPLINE_CYCLIC; /* TODO, update keyframes in time */ BKE_mask_calc_handle_point_auto(spline, point, false); BKE_mask_calc_handle_point_auto(spline, point_other, false); /* TODO: only update this spline */ BKE_mask_update_display(mask, CFRA); WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); return OPERATOR_FINISHED; } } if (!add_vertex_subdivide(C, mask, co)) { if (!add_vertex_extrude(C, mask, masklay, co)) { return OPERATOR_CANCELLED; } } } else { if (!add_vertex_subdivide(C, mask, co)) { if (!add_vertex_new(C, mask, masklay, co)) { return OPERATOR_CANCELLED; } } } /* TODO: only update this spline */ BKE_mask_update_display(mask, CFRA); return OPERATOR_FINISHED; } static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); float co[2]; ED_mask_mouse_pos(sa, ar, event->mval, co); RNA_float_set_array(op->ptr, "location", co); return add_vertex_exec(C, op); } void MASK_OT_add_vertex(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Vertex"; ot->description = "Add vertex to active spline"; ot->idname = "MASK_OT_add_vertex"; /* api callbacks */ ot->exec = add_vertex_exec; ot->invoke = add_vertex_invoke; ot->poll = ED_operator_mask; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Location of vertex in normalized space", -1.0f, 1.0f); } /******************** add feather vertex *********************/ static int add_feather_vertex_exec(bContext *C, wmOperator *op) { Mask *mask = CTX_data_edit_mask(C); MaskLayer *masklay; MaskSpline *spline; MaskSplinePoint *point = NULL; const float threshold = 9; float co[2], u; RNA_float_get_array(op->ptr, "location", co); point = ED_mask_point_find_nearest(C, mask, co, threshold, NULL, NULL, NULL, NULL); if (point) return OPERATOR_FINISHED; if (ED_mask_find_nearest_diff_point(C, mask, co, threshold, true, NULL, true, true, &masklay, &spline, &point, &u, NULL)) { Scene *scene = CTX_data_scene(C); float w = BKE_mask_point_weight(spline, point, u); float weight_scalar = BKE_mask_point_weight_scalar(spline, point, u); if (weight_scalar != 0.0f) { w = w / weight_scalar; } BKE_mask_point_add_uw(point, u, w); BKE_mask_update_display(mask, scene->r.cfra); WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); DAG_id_tag_update(&mask->id, 0); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } static int add_feather_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); float co[2]; ED_mask_mouse_pos(sa, ar, event->mval, co); RNA_float_set_array(op->ptr, "location", co); return add_feather_vertex_exec(C, op); } void MASK_OT_add_feather_vertex(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Feather Vertex"; ot->description = "Add vertex to feather"; ot->idname = "MASK_OT_add_feather_vertex"; /* api callbacks */ ot->exec = add_feather_vertex_exec; ot->invoke = add_feather_vertex_invoke; ot->poll = ED_maskedit_mask_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Location of vertex in normalized space", -1.0f, 1.0f); } /******************** common primitive functions *********************/ static int create_primitive_from_points(bContext *C, wmOperator *op, const float (*points)[2], int num_points, char handle_type) { ScrArea *sa = CTX_wm_area(C); Scene *scene = CTX_data_scene(C); Mask *mask; MaskLayer *mask_layer; MaskSpline *new_spline; float scale, location[2], frame_size[2]; int i, width, height; int size = RNA_float_get(op->ptr, "size"); ED_mask_get_size(sa, &width, &height); scale = (float)size / max_ii(width, height); /* Get location in mask space. */ frame_size[0] = width; frame_size[1] = height; RNA_float_get_array(op->ptr, "location", location); location[0] /= width; location[1] /= height; BKE_mask_coord_from_frame(location, location, frame_size); /* Make it so new primitive is centered to mouse location. */ location[0] -= 0.5f * scale; location[1] -= 0.5f * scale; mask_layer = ED_mask_layer_ensure(C); mask = CTX_data_edit_mask(C); ED_mask_select_toggle_all(mask, SEL_DESELECT); new_spline = BKE_mask_spline_add(mask_layer); new_spline->flag = MASK_SPLINE_CYCLIC | SELECT; new_spline->tot_point = num_points; new_spline->points = MEM_recallocN(new_spline->points, sizeof(MaskSplinePoint) * new_spline->tot_point); mask_layer->act_spline = new_spline; mask_layer->act_point = NULL; for (i = 0; i < num_points; i++) { MaskSplinePoint *new_point = &new_spline->points[i]; BKE_mask_parent_init(&new_point->parent); copy_v2_v2(new_point->bezt.vec[1], points[i]); mul_v2_fl(new_point->bezt.vec[1], scale); add_v2_v2(new_point->bezt.vec[1], location); new_point->bezt.h1 = handle_type; new_point->bezt.h2 = handle_type; BKE_mask_point_select_set(new_point, true); } WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask); /* TODO: only update this spline */ BKE_mask_update_display(mask, CFRA); return OPERATOR_FINISHED; } static int primitive_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { ScrArea *sa = CTX_wm_area(C); float cursor[2]; int width, height; ED_mask_get_size(sa, &width, &height); ED_mask_cursor_location_get(sa, cursor); cursor[0] *= width; cursor[1] *= height; RNA_float_set_array(op->ptr, "location", cursor); return op->type->exec(C, op); } static void define_prinitive_add_properties(wmOperatorType *ot) { RNA_def_float(ot->srna, "size", 100, -FLT_MAX, FLT_MAX, "Size", "Size of new circle", -FLT_MAX, FLT_MAX); RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Location of new circle", -FLT_MAX, FLT_MAX); } /******************** primitive add circle *********************/ static int primitive_circle_add_exec(bContext *C, wmOperator *op) { const float points[4][2] = {{0.0f, 0.5f}, {0.5f, 1.0f}, {1.0f, 0.5f}, {0.5f, 0.0f}}; int num_points = sizeof(points) / (2 * sizeof(float)); create_primitive_from_points(C, op, points, num_points, HD_AUTO); return OPERATOR_FINISHED; } void MASK_OT_primitive_circle_add(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Circle"; ot->description = "Add new circle-shaped spline"; ot->idname = "MASK_OT_primitive_circle_add"; /* api callbacks */ ot->exec = primitive_circle_add_exec; ot->invoke = primitive_add_invoke; ot->poll = ED_operator_mask; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ define_prinitive_add_properties(ot); } /******************** primitive add suqare *********************/ static int primitive_square_add_exec(bContext *C, wmOperator *op) { const float points[4][2] = {{0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}}; int num_points = sizeof(points) / (2 * sizeof(float)); create_primitive_from_points(C, op, points, num_points, HD_VECT); return OPERATOR_FINISHED; } void MASK_OT_primitive_square_add(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Square"; ot->description = "Add new square-shaped spline"; ot->idname = "MASK_OT_primitive_square_add"; /* api callbacks */ ot->exec = primitive_square_add_exec; ot->invoke = primitive_add_invoke; ot->poll = ED_operator_mask; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ define_prinitive_add_properties(ot); }