/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup edcurve */ #include "DNA_curve_types.h" #include "DNA_windowmanager_types.h" #include "MEM_guardedalloc.h" #include "BLI_listbase.h" #include "BLI_math.h" #include "BKE_context.h" #include "BKE_curve.h" #include "DEG_depsgraph.h" #include "WM_api.h" #include "ED_curve.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_view3d.h" #include "BKE_object.h" #include "curve_intern.h" #include "RNA_access.h" #include "RNA_define.h" #include "float.h" #define FOREACH_SELECTED_BEZT_BEGIN(bezt, nurbs) \ LISTBASE_FOREACH (Nurb *, nu, nurbs) { \ if (nu->type == CU_BEZIER) { \ for (int i = 0; i < nu->pntsu; i++) { \ BezTriple *bezt = nu->bezt + i; \ if (BEZT_ISSEL_ANY(bezt) && !bezt->hide) { #define FOREACH_SELECTED_BEZT_END \ } \ } \ } \ BKE_nurb_handles_calc(nu); \ } \ ((void)0) /* Used to scale the default select distance. */ #define SEL_DIST_FACTOR 0.2f /** * Data structure to keep track of details about the cut location */ typedef struct CutData { /* Index of the last #BezTriple or BPoint before the cut. */ int bezt_index, bp_index; /* Nurb to which the cut belongs to. */ Nurb *nurb; /* Minimum distance to curve from mouse location. */ float min_dist; /* Fraction of segments after which the new point divides the curve segment. */ float parameter; /* Whether the currently identified closest point has any vertices before/after it. */ bool has_prev, has_next; /* Locations of adjacent vertices and cut location. */ float prev_loc[3], cut_loc[3], next_loc[3]; /* Mouse location in floats. */ float mval[2]; } CutData; /** * Data required for segment altering functionality. */ typedef struct MoveSegmentData { /* Nurb being altered. */ Nurb *nu; /* Index of the #BezTriple before the segment. */ int bezt_index; /* Fraction along the segment at which mouse was pressed. */ float t; } MoveSegmentData; typedef struct CurvePenData { MoveSegmentData *msd; /* Whether the mouse is clicking and dragging. */ bool dragging; /* Whether a new point was added at the beginning of tool execution. */ bool new_point; /* Whether a segment is being altered by click and drag. */ bool spline_nearby; /* Whether some action was done. Used for select. */ bool acted; /* Whether a point was found underneath the mouse. */ bool found_point; /* Whether multiple selected points should be moved. */ bool multi_point; /* Whether a point has already been selected. */ bool selection_made; /* Whether a shift-click occurred. */ bool select_multi; /* Whether the current handle type of the moved handle is free. */ bool free_toggle; /* Whether the shortcut for moving the adjacent handle is pressed. */ bool move_adjacent; /* Whether the current state of the moved handle is linked. */ bool link_handles; /* Whether the current state of the handle angle is locked. */ bool lock_angle; /* Whether the shortcut for moving the entire point is pressed. */ bool move_entire; /* Data about found point. Used for closing splines. */ Nurb *nu; BezTriple *bezt; BPoint *bp; } CurvePenData; static const EnumPropertyItem prop_handle_types[] = { {HD_AUTO, "AUTO", 0, "Auto", ""}, {HD_VECT, "VECTOR", 0, "Vector", ""}, {0, NULL, 0, NULL, NULL}, }; typedef enum eClose_opt { OFF = 0, ON_PRESS = 1, ON_CLICK = 2, } eClose_opt; static const EnumPropertyItem prop_close_spline_method[] = { {OFF, "OFF", 0, "None", ""}, {ON_PRESS, "ON_PRESS", 0, "On Press", "Move handles after closing the spline"}, {ON_CLICK, "ON_CLICK", 0, "On Click", "Spline closes on release if not dragged"}, {0, NULL, 0, NULL, NULL}, }; static void update_location_for_2d_curve(const ViewContext *vc, float location[3]) { Curve *cu = vc->obedit->data; if (CU_IS_2D(cu)) { const float eps = 1e-6f; /* Get the view vector to `location`. */ float view_dir[3]; ED_view3d_global_to_vector(vc->rv3d, location, view_dir); /* Get the plane. */ float plane[4]; /* Only normalize to avoid precision errors. */ normalize_v3_v3(plane, vc->obedit->obmat[2]); plane[3] = -dot_v3v3(plane, vc->obedit->obmat[3]); if (fabsf(dot_v3v3(view_dir, plane)) < eps) { /* Can't project on an aligned plane. */ } else { float lambda; if (isect_ray_plane_v3(location, view_dir, plane, &lambda, false)) { /* Check if we're behind the viewport */ float location_test[3]; madd_v3_v3v3fl(location_test, location, view_dir, lambda); if ((vc->rv3d->is_persp == false) || (mul_project_m4_v3_zfac(vc->rv3d->persmat, location_test) > 0.0f)) { copy_v3_v3(location, location_test); } } } } float imat[4][4]; invert_m4_m4(imat, vc->obedit->obmat); mul_m4_v3(imat, location); if (CU_IS_2D(cu)) { location[2] = 0.0f; } } static void screenspace_to_worldspace(const ViewContext *vc, const float pos_2d[2], const float depth[3], float r_pos_3d[3]) { mul_v3_m4v3(r_pos_3d, vc->obedit->obmat, depth); ED_view3d_win_to_3d(vc->v3d, vc->region, r_pos_3d, pos_2d, r_pos_3d); update_location_for_2d_curve(vc, r_pos_3d); } static void screenspace_to_worldspace_int(const ViewContext *vc, const int pos_2d[2], const float depth[3], float r_pos_3d[3]) { const float pos_2d_fl[2] = {UNPACK2(pos_2d)}; screenspace_to_worldspace(vc, pos_2d_fl, depth, r_pos_3d); } static bool worldspace_to_screenspace(const ViewContext *vc, const float pos_3d[3], float r_pos_2d[2]) { return ED_view3d_project_float_object( vc->region, pos_3d, r_pos_2d, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == V3D_PROJ_RET_OK; } static void move_bezt_by_displacement(BezTriple *bezt, const float disp_3d[3]) { add_v3_v3(bezt->vec[0], disp_3d); add_v3_v3(bezt->vec[1], disp_3d); add_v3_v3(bezt->vec[2], disp_3d); } /** * Move entire control point to given worldspace location. */ static void move_bezt_to_location(BezTriple *bezt, const float location[3]) { float disp_3d[3]; sub_v3_v3v3(disp_3d, location, bezt->vec[1]); move_bezt_by_displacement(bezt, disp_3d); } /** * Alter handle types to allow free movement (Set handles to #FREE or #ALIGN). */ static void remove_handle_movement_constraints(BezTriple *bezt, const bool f1, const bool f3) { if (f1) { if (bezt->h1 == HD_VECT) { bezt->h1 = HD_FREE; } if (bezt->h1 == HD_AUTO) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; } } if (f3) { if (bezt->h2 == HD_VECT) { bezt->h2 = HD_FREE; } if (bezt->h2 == HD_AUTO) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; } } } static void move_bezt_handle_or_vertex_by_displacement(const ViewContext *vc, BezTriple *bezt, const int bezt_idx, const float disp_2d[2], const float distance, const bool link_handles, const bool lock_angle) { if (lock_angle) { float disp_3d[3]; sub_v3_v3v3(disp_3d, bezt->vec[bezt_idx], bezt->vec[1]); normalize_v3_length(disp_3d, distance); add_v3_v3v3(bezt->vec[bezt_idx], bezt->vec[1], disp_3d); } else { float pos[2], dst[2]; worldspace_to_screenspace(vc, bezt->vec[bezt_idx], pos); add_v2_v2v2(dst, pos, disp_2d); float location[3]; screenspace_to_worldspace(vc, dst, bezt->vec[bezt_idx], location); if (bezt_idx == 1) { move_bezt_to_location(bezt, location); } else { copy_v3_v3(bezt->vec[bezt_idx], location); if (bezt->h1 == HD_ALIGN && bezt->h2 == HD_ALIGN) { /* Move the handle on the opposite side. */ float handle_vec[3]; sub_v3_v3v3(handle_vec, bezt->vec[1], location); const int other_handle = bezt_idx == 2 ? 0 : 2; normalize_v3_length(handle_vec, len_v3v3(bezt->vec[1], bezt->vec[other_handle])); add_v3_v3v3(bezt->vec[other_handle], bezt->vec[1], handle_vec); } } if (link_handles) { float handle[3]; sub_v3_v3v3(handle, bezt->vec[1], bezt->vec[bezt_idx]); add_v3_v3v3(bezt->vec[(bezt_idx + 2) % 4], bezt->vec[1], handle); } } } static void move_bp_to_location(const ViewContext *vc, BPoint *bp, const float mval[2]) { float location[3]; screenspace_to_worldspace(vc, mval, bp->vec, location); copy_v3_v3(bp->vec, location); } /** * Get the average position of selected points. * \param mid_only: Use only the middle point of the three points on a #BezTriple. * \param bezt_only: Use only points of Bezier splines. */ static bool get_selected_center(const ListBase *nurbs, const bool mid_only, const bool bezt_only, float r_center[3]) { int end_count = 0; zero_v3(r_center); LISTBASE_FOREACH (Nurb *, nu, nurbs) { if (nu->type == CU_BEZIER) { for (int i = 0; i < nu->pntsu; i++) { BezTriple *bezt = nu->bezt + i; if (bezt->hide) { continue; } if (mid_only) { if (BEZT_ISSEL_ANY(bezt)) { add_v3_v3(r_center, bezt->vec[1]); end_count++; } } else { if (BEZT_ISSEL_IDX(bezt, 1)) { add_v3_v3(r_center, bezt->vec[1]); end_count++; } else if (BEZT_ISSEL_IDX(bezt, 0)) { add_v3_v3(r_center, bezt->vec[0]); end_count++; } else if (BEZT_ISSEL_IDX(bezt, 2)) { add_v3_v3(r_center, bezt->vec[2]); end_count++; } } } } else if (!bezt_only) { for (int i = 0; i < nu->pntsu; i++) { if (!nu->bp->hide && (nu->bp + i)->f1 & SELECT) { add_v3_v3(r_center, (nu->bp + i)->vec); end_count++; } } } } if (end_count) { mul_v3_fl(r_center, 1.0f / end_count); return true; } return false; } /** * Move all selected points by an amount equivalent to the distance moved by mouse. */ static void move_all_selected_points(const ViewContext *vc, const wmEvent *event, CurvePenData *cpd, ListBase *nurbs, const bool bezt_only) { const float mval[2] = {UNPACK2(event->xy)}; const float prev_mval[2] = {UNPACK2(event->prev_xy)}; float disp_2d[2]; sub_v2_v2v2(disp_2d, mval, prev_mval); const bool link_handles = cpd->link_handles && !cpd->free_toggle; const bool lock_angle = cpd->lock_angle; const bool move_entire = cpd->move_entire; float distance = 0.0f; if (lock_angle) { float mval_3d[3], center_mid[3]; get_selected_center(nurbs, true, true, center_mid); screenspace_to_worldspace_int(vc, event->mval, center_mid, mval_3d); distance = len_v3v3(center_mid, mval_3d); } LISTBASE_FOREACH (Nurb *, nu, nurbs) { if (nu->type == CU_BEZIER) { for (int i = 0; i < nu->pntsu; i++) { BezTriple *bezt = nu->bezt + i; if (bezt->hide) { continue; } if (BEZT_ISSEL_IDX(bezt, 1) || (move_entire && BEZT_ISSEL_ANY(bezt))) { move_bezt_handle_or_vertex_by_displacement(vc, bezt, 1, disp_2d, 0.0f, false, false); } else { remove_handle_movement_constraints( bezt, BEZT_ISSEL_IDX(bezt, 0), BEZT_ISSEL_IDX(bezt, 2)); if (BEZT_ISSEL_IDX(bezt, 0)) { move_bezt_handle_or_vertex_by_displacement( vc, bezt, 0, disp_2d, distance, link_handles, lock_angle); } else if (BEZT_ISSEL_IDX(bezt, 2)) { move_bezt_handle_or_vertex_by_displacement( vc, bezt, 2, disp_2d, distance, link_handles, lock_angle); } } } BKE_nurb_handles_calc(nu); } else if (!bezt_only) { for (int i = 0; i < nu->pntsu; i++) { BPoint *bp = nu->bp + i; if (!bp->hide && (bp->f1 & SELECT)) { float pos[2], dst[2]; worldspace_to_screenspace(vc, bp->vec, pos); add_v2_v2v2(dst, pos, disp_2d); move_bp_to_location(vc, bp, dst); } } } } } static int get_nurb_index(const ListBase *nurbs, const Nurb *nurb) { return BLI_findindex(nurbs, nurb); } static void delete_nurb(Curve *cu, Nurb *nu) { EditNurb *editnurb = cu->editnurb; ListBase *nurbs = &editnurb->nurbs; const int nu_index = get_nurb_index(nurbs, nu); if (cu->actnu == nu_index) { BKE_curve_nurb_vert_active_set(cu, NULL, NULL); } BLI_remlink(nurbs, nu); BKE_nurb_free(nu); } static void delete_bezt_from_nurb(const BezTriple *bezt, Nurb *nu, EditNurb *editnurb) { BLI_assert(nu->type == CU_BEZIER); const int index = BKE_curve_nurb_vert_index_get(nu, bezt); nu->pntsu -= 1; memmove(nu->bezt + index, nu->bezt + index + 1, (nu->pntsu - index) * sizeof(BezTriple)); BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, nu->bezt + index); } static void delete_bp_from_nurb(const BPoint *bp, Nurb *nu, EditNurb *editnurb) { BLI_assert(nu->type == CU_NURBS || nu->type == CU_POLY); const int index = BKE_curve_nurb_vert_index_get(nu, bp); nu->pntsu -= 1; memmove(nu->bp + index, nu->bp + index + 1, (nu->pntsu - index) * sizeof(BPoint)); BKE_curve_editNurb_keyIndex_delCV(editnurb->keyindex, nu->bp + index); } /** * Get closest vertex in all nurbs in given #ListBase to a given point. * Returns true if point is found. */ static bool get_closest_vertex_to_point_in_nurbs(const ViewContext *vc, const ListBase *nurbs, const float point[2], Nurb **r_nu, BezTriple **r_bezt, BPoint **r_bp, int *r_bezt_idx) { *r_nu = NULL; *r_bezt = NULL; *r_bp = NULL; float min_dist_bezt = FLT_MAX; int closest_handle = 0; BezTriple *closest_bezt = NULL; Nurb *closest_bezt_nu = NULL; float min_dist_bp = FLT_MAX; BPoint *closest_bp = NULL; Nurb *closest_bp_nu = NULL; LISTBASE_FOREACH (Nurb *, nu, nurbs) { if (nu->type == CU_BEZIER) { for (int i = 0; i < nu->pntsu; i++) { BezTriple *bezt = &nu->bezt[i]; float bezt_vec[2]; int start = 0, end = 3; /* Consider handles only if visible. Else only consider the middle point of the triple. */ int handle_display = vc->v3d->overlay.handle_display; if (handle_display == CURVE_HANDLE_NONE || (handle_display == CURVE_HANDLE_SELECTED && !BEZT_ISSEL_ANY(bezt))) { start = 1, end = 2; } /* Loop over each of the 3 points of the #BezTriple and update data of closest bezt. */ for (int j = start; j < end; j++) { if (worldspace_to_screenspace(vc, bezt->vec[j], bezt_vec)) { const float dist = len_manhattan_v2v2(bezt_vec, point); if (dist < min_dist_bezt) { min_dist_bezt = dist; closest_bezt = bezt; closest_bezt_nu = nu; closest_handle = j; } } } } } else { for (int i = 0; i < nu->pntsu; i++) { BPoint *bp = &nu->bp[i]; float bp_vec[2]; /* Update data of closest #BPoint. */ if (worldspace_to_screenspace(vc, bp->vec, bp_vec)) { const float dist = len_manhattan_v2v2(bp_vec, point); if (dist < min_dist_bp) { min_dist_bp = dist; closest_bp = bp; closest_bp_nu = nu; } } } } } /* Assign closest data to the returned variables. */ const float threshold_dist = ED_view3d_select_dist_px() * SEL_DIST_FACTOR; if (min_dist_bezt < threshold_dist || min_dist_bp < threshold_dist) { if (min_dist_bp < min_dist_bezt) { *r_bp = closest_bp; *r_nu = closest_bp_nu; } else { *r_bezt = closest_bezt; *r_bezt_idx = closest_handle; *r_nu = closest_bezt_nu; } return true; } return false; } /** * Interpolate along the Bezier segment by a parameter (between 0 and 1) and get its location. */ static void get_bezier_interpolated_point(const BezTriple *bezt1, const BezTriple *bezt2, const float parameter, float r_point[3]) { float tmp1[3], tmp2[3], tmp3[3]; interp_v3_v3v3(tmp1, bezt1->vec[1], bezt1->vec[2], parameter); interp_v3_v3v3(tmp2, bezt1->vec[2], bezt2->vec[0], parameter); interp_v3_v3v3(tmp3, bezt2->vec[0], bezt2->vec[1], parameter); interp_v3_v3v3(tmp1, tmp1, tmp2, parameter); interp_v3_v3v3(tmp2, tmp2, tmp3, parameter); interp_v3_v3v3(r_point, tmp1, tmp2, parameter); } /** * Calculate handle positions of added and adjacent control points such that shape is preserved. */ static void calculate_new_bezier_point(const float point_prev[3], float handle_prev[3], float new_left_handle[3], float new_right_handle[3], float handle_next[3], const float point_next[3], const float parameter) { float center_point[3]; interp_v3_v3v3(center_point, handle_prev, handle_next, parameter); interp_v3_v3v3(handle_prev, point_prev, handle_prev, parameter); interp_v3_v3v3(handle_next, handle_next, point_next, parameter); interp_v3_v3v3(new_left_handle, handle_prev, center_point, parameter); interp_v3_v3v3(new_right_handle, center_point, handle_next, parameter); } static bool is_cyclic(const Nurb *nu) { return nu->flagu & CU_NURB_CYCLIC; } /** * Insert a #BezTriple to a nurb at the location specified by `data`. */ static void insert_bezt_to_nurb(Nurb *nu, const CutData *data, Curve *cu) { EditNurb *editnurb = cu->editnurb; BezTriple *new_bezt_array = (BezTriple *)MEM_mallocN((nu->pntsu + 1) * sizeof(BezTriple), __func__); const int index = data->bezt_index + 1; /* Copy all control points before the cut to the new memory. */ ED_curve_beztcpy(editnurb, new_bezt_array, nu->bezt, index); BezTriple *new_bezt = new_bezt_array + index; /* Duplicate control point after the cut. */ ED_curve_beztcpy(editnurb, new_bezt, new_bezt - 1, 1); copy_v3_v3(new_bezt->vec[1], data->cut_loc); if (index < nu->pntsu) { /* Copy all control points after the cut to the new memory. */ ED_curve_beztcpy(editnurb, new_bezt_array + index + 1, nu->bezt + index, nu->pntsu - index); } nu->pntsu += 1; BKE_curve_nurb_vert_active_set(cu, nu, nu->bezt + index); BezTriple *next_bezt; if (is_cyclic(nu) && (index == nu->pntsu - 1)) { next_bezt = new_bezt_array; } else { next_bezt = new_bezt + 1; } /* Interpolate radius, tilt, weight */ new_bezt->tilt = interpf(next_bezt->tilt, (new_bezt - 1)->tilt, data->parameter); new_bezt->radius = interpf(next_bezt->radius, (new_bezt - 1)->radius, data->parameter); new_bezt->weight = interpf(next_bezt->weight, (new_bezt - 1)->weight, data->parameter); new_bezt->h1 = new_bezt->h2 = HD_ALIGN; calculate_new_bezier_point((new_bezt - 1)->vec[1], (new_bezt - 1)->vec[2], new_bezt->vec[0], new_bezt->vec[2], next_bezt->vec[0], next_bezt->vec[1], data->parameter); MEM_freeN(nu->bezt); nu->bezt = new_bezt_array; ED_curve_deselect_all(editnurb); BKE_nurb_handles_calc(nu); BEZT_SEL_ALL(new_bezt); } /** * Insert a #BPoint to a nurb at the location specified by `op_data`. */ static void insert_bp_to_nurb(Nurb *nu, const CutData *data, Curve *cu) { EditNurb *editnurb = cu->editnurb; BPoint *new_bp_array = (BPoint *)MEM_mallocN((nu->pntsu + 1) * sizeof(BPoint), __func__); const int index = data->bp_index + 1; /* Copy all control points before the cut to the new memory. */ ED_curve_bpcpy(editnurb, new_bp_array, nu->bp, index); BPoint *new_bp = new_bp_array + index; /* Duplicate control point after the cut. */ ED_curve_bpcpy(editnurb, new_bp, new_bp - 1, 1); copy_v3_v3(new_bp->vec, data->cut_loc); if (index < nu->pntsu) { /* Copy all control points after the cut to the new memory. */ ED_curve_bpcpy(editnurb, new_bp_array + index + 1, nu->bp + index, (nu->pntsu - index)); } nu->pntsu += 1; BKE_curve_nurb_vert_active_set(cu, nu, nu->bp + index); BPoint *next_bp; if (is_cyclic(nu) && (index == nu->pntsu - 1)) { next_bp = new_bp_array; } else { next_bp = new_bp + 1; } /* Interpolate radius, tilt, weight */ new_bp->tilt = interpf(next_bp->tilt, (new_bp - 1)->tilt, data->parameter); new_bp->radius = interpf(next_bp->radius, (new_bp - 1)->radius, data->parameter); new_bp->weight = interpf(next_bp->weight, (new_bp - 1)->weight, data->parameter); MEM_freeN(nu->bp); nu->bp = new_bp_array; ED_curve_deselect_all(editnurb); BKE_nurb_knot_calc_u(nu); new_bp->f1 |= SELECT; } /** * Update r_min_dist, r_min_i, and r_param based on the edge and the external point. * \param point: External point * \param point1: One end of the edge * \param point2: The other end of the edge * \param point_idx: Index of the control point out of the points on the Nurb * \param resolu_idx: Index of the edge on a Bezier segment (zero for non-Bezier edges) * \param r_min_dist: minimum distance from point to edge * \param r_min_i: index of closest point on Nurb * \param r_param: the fraction along the edge at which the closest point lies */ static void get_updated_data_for_edge(const float point[2], const float point1[2], const float point2[2], const int point_idx, const int resolu_idx, float *r_min_dist, int *r_min_i, float *r_param) { float edge[2], vec1[2], vec2[2]; sub_v2_v2v2(edge, point1, point2); sub_v2_v2v2(vec1, point1, point); sub_v2_v2v2(vec2, point, point2); const float len_vec1 = len_v2(vec1); const float len_vec2 = len_v2(vec2); const float dot1 = dot_v2v2(edge, vec1); const float dot2 = dot_v2v2(edge, vec2); /* Signs of dot products being equal implies that the angles formed with the external point are * either both acute or both obtuse, meaning the external point is closer to a point on the edge * rather than an endpoint. */ if ((dot1 > 0) == (dot2 > 0)) { const float perp_dist = len_vec1 * sinf(angle_v2v2(vec1, edge)); if (*r_min_dist > perp_dist) { *r_min_dist = perp_dist; *r_min_i = point_idx; *r_param = resolu_idx + len_vec1 * cos_v2v2v2(point, point1, point2) / len_v2(edge); } } else { if (*r_min_dist > len_vec2) { *r_min_dist = len_vec2; *r_min_i = point_idx; *r_param = resolu_idx; } } } /** * Update #CutData for a single #Nurb. */ static void update_cut_data_for_nurb( const ViewContext *vc, CutData *cd, Nurb *nu, const int resolu, const float point[2]) { float min_dist = cd->min_dist, param = 0.0f; int min_i = 0; const int end = is_cyclic(nu) ? nu->pntsu : nu->pntsu - 1; if (nu->type == CU_BEZIER) { for (int i = 0; i < end; i++) { float *points = MEM_mallocN(sizeof(float[3]) * (resolu + 1), __func__); const BezTriple *bezt1 = nu->bezt + i; const BezTriple *bezt2 = nu->bezt + (i + 1) % nu->pntsu; /* Calculate all points on curve. */ for (int j = 0; j < 3; j++) { BKE_curve_forward_diff_bezier(bezt1->vec[1][j], bezt1->vec[2][j], bezt2->vec[0][j], bezt2->vec[1][j], points + j, resolu, sizeof(float[3])); } float point1[2], point2[2]; worldspace_to_screenspace(vc, points, point1); const float len_vec1 = len_v2v2(point, point1); if (min_dist > len_vec1) { min_dist = len_vec1; min_i = i; param = 0; } for (int j = 0; j < resolu; j++) { worldspace_to_screenspace(vc, points + 3 * (j + 1), point2); get_updated_data_for_edge(point, point1, point2, i, j, &min_dist, &min_i, ¶m); copy_v2_v2(point1, point2); } MEM_freeN(points); } if (cd->min_dist > min_dist) { cd->min_dist = min_dist; cd->nurb = nu; cd->bezt_index = min_i; cd->parameter = param / resolu; } } else { float point1[2], point2[2]; worldspace_to_screenspace(vc, nu->bp->vec, point1); for (int i = 0; i < end; i++) { worldspace_to_screenspace(vc, (nu->bp + (i + 1) % nu->pntsu)->vec, point2); get_updated_data_for_edge(point, point1, point2, i, 0, &min_dist, &min_i, ¶m); copy_v2_v2(point1, point2); } if (cd->min_dist > min_dist) { cd->min_dist = min_dist; cd->nurb = nu; cd->bp_index = min_i; cd->parameter = param; } } } /* Update #CutData for all the Nurbs in the curve. */ static bool update_cut_data_for_all_nurbs(const ViewContext *vc, const ListBase *nurbs, const float point[2], const float sel_dist, CutData *cd) { cd->min_dist = FLT_MAX; LISTBASE_FOREACH (Nurb *, nu, nurbs) { update_cut_data_for_nurb(vc, cd, nu, nu->resolu, point); } return cd->min_dist < sel_dist; } static CutData init_cut_data(const wmEvent *event) { CutData cd = {.bezt_index = 0, .bp_index = 0, .min_dist = FLT_MAX, .parameter = 0.5f, .has_prev = false, .has_next = false, .mval[0] = event->mval[0], .mval[1] = event->mval[1]}; return cd; } static bool insert_point_to_segment(const ViewContext *vc, const wmEvent *event) { Curve *cu = vc->obedit->data; CutData cd = init_cut_data(event); const float mval[2] = {UNPACK2(event->mval)}; const float threshold_dist_px = ED_view3d_select_dist_px() * SEL_DIST_FACTOR; const bool near_spline = update_cut_data_for_all_nurbs( vc, BKE_curve_editNurbs_get(cu), mval, threshold_dist_px, &cd); if (near_spline && !cd.nurb->hide) { Nurb *nu = cd.nurb; if (nu->type == CU_BEZIER) { cd.min_dist = FLT_MAX; /* Update cut data at a higher resolution for better accuracy. */ update_cut_data_for_nurb(vc, &cd, cd.nurb, 25, mval); get_bezier_interpolated_point(&nu->bezt[cd.bezt_index], &nu->bezt[(cd.bezt_index + 1) % (nu->pntsu)], cd.parameter, cd.cut_loc); insert_bezt_to_nurb(nu, &cd, cu); } else { interp_v2_v2v2(cd.cut_loc, (nu->bp + cd.bp_index)->vec, (nu->bp + (cd.bp_index + 1) % nu->pntsu)->vec, cd.parameter); insert_bp_to_nurb(nu, &cd, cu); } return true; } return false; } /** * Get the first selected point from the curve. If more than one selected point is found, * define and return only the first detected nu. */ static void get_first_selected_point( Curve *cu, View3D *v3d, Nurb **r_nu, BezTriple **r_bezt, BPoint **r_bp) { ListBase *nurbs = &cu->editnurb->nurbs; BezTriple *bezt; BPoint *bp; int a; *r_nu = NULL; *r_bezt = NULL; *r_bp = NULL; LISTBASE_FOREACH (Nurb *, nu, nurbs) { if (nu->type == CU_BEZIER) { bezt = nu->bezt; a = nu->pntsu; while (a--) { if (BEZT_ISSEL_ANY_HIDDENHANDLES(v3d, bezt)) { if (*r_bezt || *r_bp) { *r_bp = NULL; *r_bezt = NULL; return; } *r_bezt = bezt; *r_nu = nu; } bezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { if (bp->f1 & SELECT) { if (*r_bezt || *r_bp) { *r_bp = NULL; *r_bezt = NULL; return; } *r_bp = bp; *r_nu = nu; } bp++; } } } } static void extrude_vertices_from_selected_endpoints(EditNurb *editnurb, ListBase *nurbs, Curve *cu, const float disp_3d[3]) { int nu_index = 0; LISTBASE_FOREACH (Nurb *, nu1, nurbs) { if (nu1->type == CU_BEZIER) { BezTriple *last_bezt = nu1->bezt + nu1->pntsu - 1; const bool first_sel = BEZT_ISSEL_ANY(nu1->bezt); const bool last_sel = BEZT_ISSEL_ANY(last_bezt) && nu1->pntsu > 1; if (first_sel) { if (last_sel) { BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 2) * sizeof(BezTriple), __func__); ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, 1); ED_curve_beztcpy(editnurb, new_bezt + nu1->pntsu + 1, last_bezt, 1); BEZT_DESEL_ALL(nu1->bezt); BEZT_DESEL_ALL(last_bezt); ED_curve_beztcpy(editnurb, new_bezt + 1, nu1->bezt, nu1->pntsu); move_bezt_by_displacement(new_bezt, disp_3d); move_bezt_by_displacement(new_bezt + nu1->pntsu + 1, disp_3d); MEM_freeN(nu1->bezt); nu1->bezt = new_bezt; nu1->pntsu += 2; } else { BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BezTriple), __func__); ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, 1); BEZT_DESEL_ALL(nu1->bezt); ED_curve_beztcpy(editnurb, new_bezt + 1, nu1->bezt, nu1->pntsu); move_bezt_by_displacement(new_bezt, disp_3d); MEM_freeN(nu1->bezt); nu1->bezt = new_bezt; nu1->pntsu++; } cu->actnu = nu_index; cu->actvert = 0; } else if (last_sel) { BezTriple *new_bezt = (BezTriple *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BezTriple), __func__); ED_curve_beztcpy(editnurb, new_bezt + nu1->pntsu, last_bezt, 1); BEZT_DESEL_ALL(last_bezt); ED_curve_beztcpy(editnurb, new_bezt, nu1->bezt, nu1->pntsu); move_bezt_by_displacement(new_bezt + nu1->pntsu, disp_3d); MEM_freeN(nu1->bezt); nu1->bezt = new_bezt; nu1->pntsu++; cu->actnu = nu_index; cu->actvert = nu1->pntsu - 1; } } else { BPoint *last_bp = nu1->bp + nu1->pntsu - 1; const bool first_sel = nu1->bp->f1 & SELECT; const bool last_sel = last_bp->f1 & SELECT && nu1->pntsu > 1; if (first_sel) { if (last_sel) { BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 2) * sizeof(BPoint), __func__); ED_curve_bpcpy(editnurb, new_bp, nu1->bp, 1); ED_curve_bpcpy(editnurb, new_bp + nu1->pntsu + 1, last_bp, 1); nu1->bp->f1 &= ~SELECT; last_bp->f1 &= ~SELECT; ED_curve_bpcpy(editnurb, new_bp + 1, nu1->bp, nu1->pntsu); add_v3_v3(new_bp->vec, disp_3d); add_v3_v3((new_bp + nu1->pntsu + 1)->vec, disp_3d); MEM_freeN(nu1->bp); nu1->bp = new_bp; nu1->pntsu += 2; } else { BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BPoint), __func__); ED_curve_bpcpy(editnurb, new_bp, nu1->bp, 1); nu1->bp->f1 &= ~SELECT; ED_curve_bpcpy(editnurb, new_bp + 1, nu1->bp, nu1->pntsu); add_v3_v3(new_bp->vec, disp_3d); MEM_freeN(nu1->bp); nu1->bp = new_bp; nu1->pntsu++; } BKE_nurb_knot_calc_u(nu1); cu->actnu = nu_index; cu->actvert = 0; } else if (last_sel) { BPoint *new_bp = (BPoint *)MEM_mallocN((nu1->pntsu + 1) * sizeof(BPoint), __func__); ED_curve_bpcpy(editnurb, new_bp, nu1->bp, nu1->pntsu); ED_curve_bpcpy(editnurb, new_bp + nu1->pntsu, last_bp, 1); last_bp->f1 &= ~SELECT; ED_curve_bpcpy(editnurb, new_bp, nu1->bp, nu1->pntsu); add_v3_v3((new_bp + nu1->pntsu)->vec, disp_3d); MEM_freeN(nu1->bp); nu1->bp = new_bp; nu1->pntsu++; BKE_nurb_knot_calc_u(nu1); cu->actnu = nu_index; cu->actvert = nu1->pntsu - 1; } BKE_curve_nurb_vert_active_validate(cu); } nu_index++; } } /** * Deselect all vertices that are not endpoints. */ static void deselect_all_center_vertices(ListBase *nurbs) { LISTBASE_FOREACH (Nurb *, nu1, nurbs) { if (nu1->pntsu > 1) { int start, end; if (is_cyclic(nu1)) { start = 0; end = nu1->pntsu; } else { start = 1; end = nu1->pntsu - 1; } for (int i = start; i < end; i++) { if (nu1->type == CU_BEZIER) { BEZT_DESEL_ALL(nu1->bezt + i); } else { (nu1->bp + i)->f1 &= ~SELECT; } } } } } static bool is_last_bezt(const Nurb *nu, const BezTriple *bezt) { return nu->pntsu > 1 && nu->bezt + nu->pntsu - 1 == bezt && !is_cyclic(nu); } /** * Add new vertices connected to the selected vertices. */ static void extrude_points_from_selected_vertices(const ViewContext *vc, const wmEvent *event, const int extrude_handle) { Curve *cu = vc->obedit->data; ListBase *nurbs = BKE_curve_editNurbs_get(cu); float center[3] = {0.0f, 0.0f, 0.0f}; deselect_all_center_vertices(nurbs); bool sel_exists = get_selected_center(nurbs, true, false, center); float location[3]; if (sel_exists) { mul_v3_m4v3(location, vc->obedit->obmat, center); } else { copy_v3_v3(location, vc->scene->cursor.location); } ED_view3d_win_to_3d_int(vc->v3d, vc->region, location, event->mval, location); update_location_for_2d_curve(vc, location); EditNurb *editnurb = cu->editnurb; if (sel_exists) { float disp_3d[3]; sub_v3_v3v3(disp_3d, location, center); /* Reimplemenented due to unexpected behavior for extrusion of 2-point spline. */ extrude_vertices_from_selected_endpoints(editnurb, nurbs, cu, disp_3d); } else { Nurb *old_last_nu = editnurb->nurbs.last; ed_editcurve_addvert(cu, editnurb, vc->v3d, location); Nurb *new_last_nu = editnurb->nurbs.last; if (old_last_nu != new_last_nu) { BKE_curve_nurb_vert_active_set(cu, new_last_nu, new_last_nu->bezt ? (const void *)new_last_nu->bezt : (const void *)new_last_nu->bp); new_last_nu->flagu = ~CU_NURB_CYCLIC; } } FOREACH_SELECTED_BEZT_BEGIN (bezt, &cu->editnurb->nurbs) { if (bezt) { bezt->h1 = extrude_handle; bezt->h2 = extrude_handle; } } FOREACH_SELECTED_BEZT_END; } /** * Check if a spline segment is nearby. */ static bool is_spline_nearby(ViewContext *vc, struct wmOperator *op, const wmEvent *event, const float sel_dist) { Curve *cu = vc->obedit->data; ListBase *nurbs = BKE_curve_editNurbs_get(cu); CutData cd = init_cut_data(event); const float mval[2] = {UNPACK2(event->mval)}; const bool nearby = update_cut_data_for_all_nurbs(vc, nurbs, mval, sel_dist, &cd); if (nearby) { if (cd.nurb && (cd.nurb->type == CU_BEZIER) && RNA_boolean_get(op->ptr, "move_segment")) { MoveSegmentData *seg_data; CurvePenData *cpd = (CurvePenData *)(op->customdata); cpd->msd = seg_data = MEM_callocN(sizeof(MoveSegmentData), __func__); seg_data->bezt_index = cd.bezt_index; seg_data->nu = cd.nurb; seg_data->t = cd.parameter; } return true; } return false; } static void move_segment(ViewContext *vc, MoveSegmentData *seg_data, const wmEvent *event) { Nurb *nu = seg_data->nu; BezTriple *bezt1 = nu->bezt + seg_data->bezt_index; BezTriple *bezt2 = BKE_nurb_bezt_get_next(nu, bezt1); int h1 = 2, h2 = 0; if (bezt1->hide) { if (bezt2->hide) { return; } /* * Swap bezt1 and bezt2 in all calculations if only bezt2 is visible. * (The first point needs to be visible for the calculations of the second point to be valid) */ BezTriple *temp_bezt = bezt2; bezt2 = bezt1; bezt1 = temp_bezt; h1 = 0, h2 = 2; } const float t = max_ff(min_ff(seg_data->t, 0.9f), 0.1f); const float t_sq = t * t; const float t_cu = t_sq * t; const float one_minus_t = 1 - t; const float one_minus_t_sq = one_minus_t * one_minus_t; const float one_minus_t_cu = one_minus_t_sq * one_minus_t; float mouse_3d[3]; float depth[3]; /* Use the center of the spline segment as depth. */ get_bezier_interpolated_point(bezt1, bezt2, t, depth); screenspace_to_worldspace_int(vc, event->mval, depth, mouse_3d); /* * Equation of Bezier Curve * => B(t) = (1-t)^3 * P0 + 3(1-t)^2 * t * P1 + 3(1-t) * t^2 * P2 + t^3 * P3 * * Mouse location (Say Pm) should satisfy this equation. * Therefore => (1/t - 1) * P1 + P2 = (Pm - (1 - t)^3 * P0 - t^3 * P3) / [3 * (1 - t) * t^2] = k1 * (in code) * * Another constraint is required to identify P1 and P2. * The constraint used is that the vector between P1 and P2 doesn't change. * Therefore => P1 - P2 = k2 * * From the two equations => P1 = t(k1 + k2) and P2 = P1 - K2 */ float k1[3]; const float denom = 3.0f * one_minus_t * t_sq; k1[0] = (mouse_3d[0] - one_minus_t_cu * bezt1->vec[1][0] - t_cu * bezt2->vec[1][0]) / denom; k1[1] = (mouse_3d[1] - one_minus_t_cu * bezt1->vec[1][1] - t_cu * bezt2->vec[1][1]) / denom; k1[2] = (mouse_3d[2] - one_minus_t_cu * bezt1->vec[1][2] - t_cu * bezt2->vec[1][2]) / denom; float k2[3]; sub_v3_v3v3(k2, bezt1->vec[h1], bezt2->vec[h2]); if (!bezt1->hide) { /* P1 = t(k1 + k2) */ add_v3_v3v3(bezt1->vec[h1], k1, k2); mul_v3_fl(bezt1->vec[h1], t); remove_handle_movement_constraints(bezt1, true, true); /* Move opposite handle as well if type is align. */ if (bezt1->h1 == HD_ALIGN) { float handle_vec[3]; sub_v3_v3v3(handle_vec, bezt1->vec[1], bezt1->vec[h1]); normalize_v3_length(handle_vec, len_v3v3(bezt1->vec[1], bezt1->vec[h2])); add_v3_v3v3(bezt1->vec[h2], bezt1->vec[1], handle_vec); } } if (!bezt2->hide) { /* P2 = P1 - K2 */ sub_v3_v3v3(bezt2->vec[h2], bezt1->vec[h1], k2); remove_handle_movement_constraints(bezt2, true, true); /* Move opposite handle as well if type is align. */ if (bezt2->h2 == HD_ALIGN) { float handle_vec[3]; sub_v3_v3v3(handle_vec, bezt2->vec[1], bezt2->vec[h2]); normalize_v3_length(handle_vec, len_v3v3(bezt2->vec[1], bezt2->vec[h1])); add_v3_v3v3(bezt2->vec[h1], bezt2->vec[1], handle_vec); } } } /** * Toggle between #HD_FREE and #HD_ALIGN handles of the given #BezTriple */ static void toggle_bezt_free_align_handles(BezTriple *bezt) { if (bezt->h1 != HD_FREE || bezt->h2 != HD_FREE) { bezt->h1 = bezt->h2 = HD_FREE; } else { bezt->h1 = bezt->h2 = HD_ALIGN; } } /** * Toggle between #HD_FREE and #HD_ALIGN handles of the all selected #BezTriple */ static void toggle_sel_bezt_free_align_handles(ListBase *nurbs) { FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) { toggle_bezt_free_align_handles(bezt); } FOREACH_SELECTED_BEZT_END; } /** * If a point is found under mouse, delete point and return true. Else return false. */ static bool delete_point_under_mouse(ViewContext *vc, const wmEvent *event) { BezTriple *bezt = NULL; BPoint *bp = NULL; Nurb *nu = NULL; int temp = 0; Curve *cu = vc->obedit->data; EditNurb *editnurb = cu->editnurb; ListBase *nurbs = BKE_curve_editNurbs_get(cu); const float mouse_point[2] = {UNPACK2(event->mval)}; get_closest_vertex_to_point_in_nurbs(vc, nurbs, mouse_point, &nu, &bezt, &bp, &temp); const bool found_point = nu != NULL; bool deleted = false; if (found_point) { ED_curve_deselect_all(cu->editnurb); if (nu) { if (nu->type == CU_BEZIER) { BezTriple *next_bezt = BKE_nurb_bezt_get_next(nu, bezt); BezTriple *prev_bezt = BKE_nurb_bezt_get_prev(nu, bezt); if (next_bezt && prev_bezt) { const int bez_index = BKE_curve_nurb_vert_index_get(nu, bezt); const uint span_step[2] = {bez_index, bez_index}; ed_dissolve_bez_segment(prev_bezt, next_bezt, nu, cu, 1, span_step); } delete_bezt_from_nurb(bezt, nu, editnurb); } else { delete_bp_from_nurb(bp, nu, editnurb); } if (nu->pntsu == 0) { delete_nurb(cu, nu); nu = NULL; } deleted = true; cu->actvert = CU_ACT_NONE; } } if (nu && nu->type == CU_BEZIER) { BKE_nurb_handles_calc(nu); } return deleted; } static void move_adjacent_handle(ViewContext *vc, const wmEvent *event, ListBase *nurbs) { FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) { BezTriple *adj_bezt; int bezt_idx; if (nu->pntsu == 1) { continue; } if (nu->bezt == bezt) { adj_bezt = BKE_nurb_bezt_get_next(nu, bezt); bezt_idx = 0; } else if (nu->bezt + nu->pntsu - 1 == bezt) { adj_bezt = BKE_nurb_bezt_get_prev(nu, bezt); bezt_idx = 2; } else { if (BEZT_ISSEL_IDX(bezt, 0)) { adj_bezt = BKE_nurb_bezt_get_prev(nu, bezt); bezt_idx = 2; } else if (BEZT_ISSEL_IDX(bezt, 2)) { adj_bezt = BKE_nurb_bezt_get_next(nu, bezt); bezt_idx = 0; } else { continue; } } adj_bezt->h1 = adj_bezt->h2 = HD_FREE; int displacement[2]; sub_v2_v2v2_int(displacement, event->xy, event->prev_xy); const float disp_fl[2] = {UNPACK2(displacement)}; move_bezt_handle_or_vertex_by_displacement( vc, adj_bezt, bezt_idx, disp_fl, 0.0f, false, false); BKE_nurb_handles_calc(nu); } FOREACH_SELECTED_BEZT_END; } /** * Close the spline if endpoints are selected consecutively. Return true if cycle was created. */ static bool make_cyclic_if_endpoints(ViewContext *vc, Nurb *sel_nu, BezTriple *sel_bezt, BPoint *sel_bp) { if (sel_bezt || (sel_bp && sel_nu->pntsu > 2)) { const bool is_bezt_endpoint = ((sel_nu->type == CU_BEZIER) && ELEM(sel_bezt, sel_nu->bezt, sel_nu->bezt + sel_nu->pntsu - 1)); const bool is_bp_endpoint = ((sel_nu->type != CU_BEZIER) && ELEM(sel_bp, sel_nu->bp, sel_nu->bp + sel_nu->pntsu - 1)); if (!(is_bezt_endpoint || is_bp_endpoint)) { return false; } Nurb *nu = NULL; BezTriple *bezt = NULL; BPoint *bp = NULL; Curve *cu = vc->obedit->data; int bezt_idx; const float mval_fl[2] = {UNPACK2(vc->mval)}; get_closest_vertex_to_point_in_nurbs( vc, &(cu->editnurb->nurbs), mval_fl, &nu, &bezt, &bp, &bezt_idx); if (nu == sel_nu && ((nu->type == CU_BEZIER && bezt != sel_bezt && ELEM(bezt, nu->bezt, nu->bezt + nu->pntsu - 1) && bezt_idx == 1) || (nu->type != CU_BEZIER && bp != sel_bp && ELEM(bp, nu->bp, nu->bp + nu->pntsu - 1)))) { View3D *v3d = vc->v3d; ListBase *nurbs = object_editcurve_get(vc->obedit); curve_toggle_cyclic(v3d, nurbs, 0); return true; } } return false; } static void init_selected_bezt_handles(ListBase *nurbs) { FOREACH_SELECTED_BEZT_BEGIN (bezt, nurbs) { bezt->h1 = bezt->h2 = HD_ALIGN; copy_v3_v3(bezt->vec[0], bezt->vec[1]); copy_v3_v3(bezt->vec[2], bezt->vec[1]); BEZT_DESEL_ALL(bezt); BEZT_SEL_IDX(bezt, is_last_bezt(nu, bezt) ? 2 : 0); } FOREACH_SELECTED_BEZT_END; } static void toggle_select_bezt(BezTriple *bezt, const int bezt_idx, Curve *cu, Nurb *nu) { if (bezt_idx == 1) { if (BEZT_ISSEL_IDX(bezt, 1)) { BEZT_DESEL_ALL(bezt); } else { BEZT_SEL_ALL(bezt); } } else { if (BEZT_ISSEL_IDX(bezt, bezt_idx)) { BEZT_DESEL_IDX(bezt, bezt_idx); } else { BEZT_SEL_IDX(bezt, bezt_idx); } } if (BEZT_ISSEL_ANY(bezt)) { BKE_curve_nurb_vert_active_set(cu, nu, bezt); } } static void toggle_select_bp(BPoint *bp, Curve *cu, Nurb *nu) { if (bp->f1 & SELECT) { bp->f1 &= ~SELECT; } else { bp->f1 |= SELECT; BKE_curve_nurb_vert_active_set(cu, nu, bp); } } static void toggle_handle_types(BezTriple *bezt, int bezt_idx, CurvePenData *cpd) { if (bezt_idx == 0) { if (bezt->h1 == HD_VECT) { bezt->h1 = bezt->h2 = HD_AUTO; } else { bezt->h1 = HD_VECT; if (bezt->h2 != HD_VECT) { bezt->h2 = HD_FREE; } } cpd->acted = true; } else if (bezt_idx == 2) { if (bezt->h2 == HD_VECT) { bezt->h1 = bezt->h2 = HD_AUTO; } else { bezt->h2 = HD_VECT; if (bezt->h1 != HD_VECT) { bezt->h1 = HD_FREE; } } cpd->acted = true; } } static void cycle_handles(BezTriple *bezt) { if (bezt->h1 == HD_AUTO) { bezt->h1 = bezt->h2 = HD_VECT; } else if (bezt->h1 == HD_VECT) { bezt->h1 = bezt->h2 = HD_ALIGN; } else if (bezt->h1 == HD_ALIGN) { bezt->h1 = bezt->h2 = HD_FREE; } else { bezt->h1 = bezt->h2 = HD_AUTO; } } enum { PEN_MODAL_FREE_ALIGN_TOGGLE = 1, PEN_MODAL_MOVE_ADJACENT, PEN_MODAL_MOVE_ENTIRE, PEN_MODAL_LINK_HANDLES, PEN_MODAL_LOCK_ANGLE }; wmKeyMap *curve_pen_modal_keymap(wmKeyConfig *keyconf) { static const EnumPropertyItem modal_items[] = { {PEN_MODAL_FREE_ALIGN_TOGGLE, "FREE_ALIGN_TOGGLE", 0, "Free-Align Toggle", "Move handle of newly added point freely"}, {PEN_MODAL_MOVE_ADJACENT, "MOVE_ADJACENT", 0, "Move Adjacent Handle", "Move the closer handle of the adjacent vertex"}, {PEN_MODAL_MOVE_ENTIRE, "MOVE_ENTIRE", 0, "Move Entire Point", "Move the entire point using its handles"}, {PEN_MODAL_LINK_HANDLES, "LINK_HANDLES", 0, "Link Handles", "Mirror the movement of one handle onto the other"}, {PEN_MODAL_LOCK_ANGLE, "LOCK_ANGLE", 0, "Lock Angle", "Move the handle along its current angle"}, {0, NULL, 0, NULL, NULL}, }; wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Curve Pen Modal Map"); /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return NULL; } keymap = WM_modalkeymap_ensure(keyconf, "Curve Pen Modal Map", modal_items); WM_modalkeymap_assign(keymap, "CURVE_OT_pen"); return keymap; } static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewContext vc; Object *obedit = CTX_data_edit_object(C); ED_view3d_viewcontext_init(C, &vc, depsgraph); Curve *cu = vc.obedit->data; ListBase *nurbs = &cu->editnurb->nurbs; const float threshold_dist_px = ED_view3d_select_dist_px() * SEL_DIST_FACTOR; BezTriple *bezt = NULL; BPoint *bp = NULL; Nurb *nu = NULL; const struct SelectPick_Params params = { .sel_op = SEL_OP_SET, .deselect_all = false, }; int ret = OPERATOR_RUNNING_MODAL; /* Distance threshold for mouse clicks to affect the spline or its points */ const float mval_fl[2] = {UNPACK2(event->mval)}; const bool extrude_point = RNA_boolean_get(op->ptr, "extrude_point"); const bool delete_point = RNA_boolean_get(op->ptr, "delete_point"); const bool insert_point = RNA_boolean_get(op->ptr, "insert_point"); const bool move_seg = RNA_boolean_get(op->ptr, "move_segment"); const bool select_point = RNA_boolean_get(op->ptr, "select_point"); const bool move_point = RNA_boolean_get(op->ptr, "move_point"); const bool close_spline = RNA_boolean_get(op->ptr, "close_spline"); const bool toggle_vector = RNA_boolean_get(op->ptr, "toggle_vector"); const bool cycle_handle_type = RNA_boolean_get(op->ptr, "cycle_handle_type"); const int close_spline_method = RNA_enum_get(op->ptr, "close_spline_method"); const int extrude_handle = RNA_enum_get(op->ptr, "extrude_handle"); CurvePenData *cpd; if (op->customdata == NULL) { op->customdata = cpd = MEM_callocN(sizeof(CurvePenData), __func__); } else { cpd = (CurvePenData *)(op->customdata); cpd->select_multi = event->modifier == KM_SHIFT; } if (event->type == EVT_MODAL_MAP) { if (cpd->msd == NULL) { if (event->val == PEN_MODAL_FREE_ALIGN_TOGGLE) { toggle_sel_bezt_free_align_handles(nurbs); cpd->link_handles = false; } else if (event->val == PEN_MODAL_LINK_HANDLES) { cpd->link_handles = !cpd->link_handles; if (cpd->link_handles) { move_all_selected_points(&vc, event, cpd, nurbs, false); } } else if (event->val == PEN_MODAL_MOVE_ENTIRE) { cpd->move_entire = !cpd->move_entire; } else if (event->val == PEN_MODAL_MOVE_ADJACENT) { cpd->move_adjacent = !cpd->move_adjacent; } else if (event->val == PEN_MODAL_LOCK_ANGLE) { cpd->lock_angle = !cpd->lock_angle; } } else { if (event->val == PEN_MODAL_FREE_ALIGN_TOGGLE) { BezTriple *bezt1 = cpd->msd->nu->bezt + cpd->msd->bezt_index; BezTriple *bezt2 = BKE_nurb_bezt_get_next(cpd->msd->nu, bezt1); toggle_bezt_free_align_handles(bezt1); toggle_bezt_free_align_handles(bezt2); } } } if (ISMOUSE_MOTION(event->type)) { /* Check if dragging */ if (!cpd->dragging && WM_event_drag_test(event, event->prev_press_xy)) { cpd->dragging = true; if (cpd->new_point) { init_selected_bezt_handles(nurbs); } } if (cpd->dragging) { if (cpd->spline_nearby && move_seg && cpd->msd != NULL) { MoveSegmentData *seg_data = cpd->msd; move_segment(&vc, seg_data, event); cpd->acted = true; if (seg_data->nu && seg_data->nu->type == CU_BEZIER) { BKE_nurb_handles_calc(seg_data->nu); } } else if (cpd->move_adjacent) { move_adjacent_handle(&vc, event, nurbs); cpd->acted = true; } else if (cpd->new_point || (move_point && !cpd->spline_nearby && cpd->found_point)) { /* Move only the bezt handles if it's a new point. */ move_all_selected_points(&vc, event, cpd, nurbs, cpd->new_point); cpd->acted = true; } } } else if (ELEM(event->type, LEFTMOUSE)) { if (ELEM(event->val, KM_RELEASE, KM_DBL_CLICK)) { if (delete_point && !cpd->new_point && !cpd->dragging) { if (ED_curve_editnurb_select_pick(C, event->mval, threshold_dist_px, false, ¶ms)) { cpd->acted = delete_point_under_mouse(&vc, event); } } /* Close spline on Click, if enabled. */ if (!cpd->acted && close_spline && close_spline_method == ON_CLICK && cpd->found_point && !cpd->dragging) { if (cpd->nu && !is_cyclic(cpd->nu)) { copy_v2_v2_int(vc.mval, event->mval); cpd->acted = make_cyclic_if_endpoints(&vc, cpd->nu, cpd->bezt, cpd->bp); } } if (!cpd->acted && (insert_point || extrude_point) && cpd->spline_nearby && !cpd->dragging) { if (insert_point) { insert_point_to_segment(&vc, event); cpd->new_point = true; cpd->acted = true; } else if (extrude_point) { extrude_points_from_selected_vertices(&vc, event, extrude_handle); cpd->acted = true; } } if (!cpd->acted && toggle_vector) { int bezt_idx; get_closest_vertex_to_point_in_nurbs(&vc, nurbs, mval_fl, &nu, &bezt, &bp, &bezt_idx); if (bezt) { if (bezt_idx == 1 && cycle_handle_type) { cycle_handles(bezt); cpd->acted = true; } else { toggle_handle_types(bezt, bezt_idx, cpd); } if (nu && nu->type == CU_BEZIER) { BKE_nurb_handles_calc(nu); } } } if (!cpd->selection_made && !cpd->acted) { if (cpd->select_multi) { int bezt_idx; get_closest_vertex_to_point_in_nurbs(&vc, nurbs, mval_fl, &nu, &bezt, &bp, &bezt_idx); if (bezt) { toggle_select_bezt(bezt, bezt_idx, cu, nu); } else if (bp) { toggle_select_bp(bp, cu, nu); } else { ED_curve_deselect_all(cu->editnurb); } } else if (select_point) { ED_curve_editnurb_select_pick(C, event->mval, threshold_dist_px, false, ¶ms); } } if (cpd->msd != NULL) { MEM_freeN(cpd->msd); } MEM_freeN(cpd); ret = OPERATOR_FINISHED; } } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); DEG_id_tag_update(obedit->data, 0); return ret; } static int curve_pen_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ViewContext vc; ED_view3d_viewcontext_init(C, &vc, CTX_data_ensure_evaluated_depsgraph(C)); Curve *cu = vc.obedit->data; ListBase *nurbs = &cu->editnurb->nurbs; BezTriple *bezt = NULL; BPoint *bp = NULL; Nurb *nu = NULL; CurvePenData *cpd; op->customdata = cpd = MEM_callocN(sizeof(CurvePenData), __func__); /* Distance threshold for mouse clicks to affect the spline or its points */ const float mval_fl[2] = {UNPACK2(event->mval)}; const float threshold_dist_px = ED_view3d_select_dist_px() * SEL_DIST_FACTOR; const bool extrude_point = RNA_boolean_get(op->ptr, "extrude_point"); const bool insert_point = RNA_boolean_get(op->ptr, "insert_point"); const bool move_seg = RNA_boolean_get(op->ptr, "move_segment"); const bool move_point = RNA_boolean_get(op->ptr, "move_point"); const bool close_spline = RNA_boolean_get(op->ptr, "close_spline"); const int close_spline_method = RNA_enum_get(op->ptr, "close_spline_method"); const int extrude_handle = RNA_enum_get(op->ptr, "extrude_handle"); if (ELEM(event->type, LEFTMOUSE) && ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { /* Get the details of points selected at the start of the operation. * Used for closing the spline when endpoints are clicked consecutively and for selecting a * single point. */ get_first_selected_point(cu, vc.v3d, &nu, &bezt, &bp); cpd->nu = nu; cpd->bezt = bezt; cpd->bp = bp; /* Get the details of the vertex closest to the mouse at the start of the operation. */ Nurb *nu1; BezTriple *bezt1; BPoint *bp1; int bezt_idx = 0; cpd->found_point = get_closest_vertex_to_point_in_nurbs( &vc, nurbs, mval_fl, &nu1, &bezt1, &bp1, &bezt_idx); if (move_point && nu1 && !nu1->hide && (bezt || (bezt1 && !BEZT_ISSEL_IDX(bezt1, bezt_idx)) || (bp1 && !(bp1->f1 & SELECT)))) { /* Select the closest bezt or bp. */ ED_curve_deselect_all(cu->editnurb); if (bezt1) { if (bezt_idx == 1) { BEZT_SEL_ALL(bezt1); } else { BEZT_SEL_IDX(bezt1, bezt_idx); } BKE_curve_nurb_vert_active_set(cu, nu1, bezt1); } else if (bp1) { bp1->f1 |= SELECT; BKE_curve_nurb_vert_active_set(cu, nu1, bp1); } cpd->selection_made = true; } if (cpd->found_point) { /* Close the spline on press. */ if (close_spline && close_spline_method == ON_PRESS && cpd->nu && !is_cyclic(cpd->nu)) { copy_v2_v2_int(vc.mval, event->mval); cpd->new_point = cpd->acted = cpd->link_handles = make_cyclic_if_endpoints( &vc, cpd->nu, cpd->bezt, cpd->bp); } } else if (!cpd->acted) { if (is_spline_nearby(&vc, op, event, threshold_dist_px)) { cpd->spline_nearby = true; /* If move segment is disabled, then insert point on key press and set * "new_point" to true so that the new point's handles can be controlled. */ if (insert_point && !move_seg) { insert_point_to_segment(&vc, event); cpd->new_point = cpd->acted = cpd->link_handles = true; } } else if (extrude_point) { extrude_points_from_selected_vertices(&vc, event, extrude_handle); cpd->new_point = cpd->acted = cpd->link_handles = true; } } } WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } void CURVE_OT_pen(wmOperatorType *ot) { /* identifiers */ ot->name = "Curve Pen"; ot->idname = "CURVE_OT_pen"; ot->description = "Construct and edit splines"; /* api callbacks */ ot->invoke = curve_pen_invoke; ot->modal = curve_pen_modal; ot->poll = ED_operator_view3d_active; /* flags */ ot->flag = OPTYPE_UNDO; /* properties */ WM_operator_properties_mouse_select(ot); RNA_def_boolean(ot->srna, "extrude_point", false, "Extrude Point", "Add a point connected to the last selected point"); RNA_def_enum(ot->srna, "extrude_handle", prop_handle_types, HD_VECT, "Extrude Handle Type", "Type of the extruded handle"); RNA_def_boolean(ot->srna, "delete_point", false, "Delete Point", "Delete an existing point"); RNA_def_boolean( ot->srna, "insert_point", false, "Insert Point", "Insert Point into a curve segment"); RNA_def_boolean(ot->srna, "move_segment", false, "Move Segment", "Delete an existing point"); RNA_def_boolean( ot->srna, "select_point", false, "Select Point", "Select a point or its handles"); RNA_def_boolean(ot->srna, "move_point", false, "Move Point", "Move a point or its handles"); RNA_def_boolean(ot->srna, "close_spline", true, "Close Spline", "Make a spline cyclic by clicking endpoints"); RNA_def_enum(ot->srna, "close_spline_method", prop_close_spline_method, OFF, "Close Spline Method", "The condition for close spline to activate"); RNA_def_boolean( ot->srna, "toggle_vector", false, "Toggle Vector", "Toggle between Vector and Auto handles"); RNA_def_boolean(ot->srna, "cycle_handle_type", false, "Cycle Handle Type", "Cycle between all four handle types"); }