/* * ***** 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) Blender Foundation * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): Lukas Toenne * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/hair/hair_select.c * \ingroup edhair */ #include #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" #include "BLI_math.h" #include "BLI_rect.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_view3d_types.h" #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_editstrands.h" #include "bmesh.h" #include "RNA_access.h" #include "RNA_define.h" #include "WM_api.h" #include "WM_types.h" #include "ED_object.h" #include "ED_physics.h" #include "ED_view3d.h" #include "hair_intern.h" BLI_INLINE bool apply_select_action_flag(BMVert *v, int action) { bool cursel = BM_elem_flag_test_bool(v, BM_ELEM_SELECT); bool newsel; switch (action) { case SEL_SELECT: newsel = true; break; case SEL_DESELECT: newsel = false; break; case SEL_INVERT: newsel = !cursel; break; case SEL_TOGGLE: /* toggle case should be converted to SELECT or DESELECT based on global state */ BLI_assert(false); break; } if (newsel != cursel) { BM_elem_flag_set(v, BM_ELEM_SELECT, newsel); return true; } else return false; } /* poll function */ typedef bool (*PollVertexCb)(void *userdata, struct BMVert *v); /* distance metric function */ typedef bool (*DistanceVertexCb)(void *userdata, struct BMVert *v, float *dist); typedef void (*ActionVertexCb)(void *userdata, struct BMVert *v, int action); static int hair_select_verts_filter(BMEditStrands *edit, HairEditSelectMode select_mode, int action, PollVertexCb cb, void *userdata) { BMesh *bm = edit->bm; BMVert *v; BMIter iter; int tot = 0; bm->selectmode = BM_VERT; switch (select_mode) { case HAIR_SELECT_STRAND: break; case HAIR_SELECT_VERTEX: BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { if (!cb(userdata, v)) continue; if (apply_select_action_flag(v, action)) ++tot; } break; case HAIR_SELECT_TIP: BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { if (!BM_strands_vert_is_tip(v)) continue; if (!cb(userdata, v)) continue; if (apply_select_action_flag(v, action)) ++tot; } break; } BM_mesh_select_mode_flush(bm); return tot; } static bool hair_select_verts_closest(BMEditStrands *edit, HairEditSelectMode select_mode, int action, DistanceVertexCb cb, ActionVertexCb action_cb, void *userdata) { BMesh *bm = edit->bm; BMVert *v; BMIter iter; float dist; BMVert *closest_v = NULL; float closest_dist = FLT_MAX; bm->selectmode = BM_VERT; switch (select_mode) { case HAIR_SELECT_STRAND: break; case HAIR_SELECT_VERTEX: BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { if (!cb(userdata, v, &dist)) continue; if (dist < closest_dist) { closest_v = v; closest_dist = dist; } } break; case HAIR_SELECT_TIP: BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { if (!BM_strands_vert_is_tip(v)) continue; if (!cb(userdata, v, &dist)) continue; if (dist < closest_dist) { closest_v = v; closest_dist = dist; } } break; } if (closest_v) { action_cb(userdata, closest_v, action); BM_mesh_select_mode_flush(bm); return true; } else return false; } static void hair_deselect_all(BMEditStrands *edit) { BMVert *v; BMIter iter; BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { BM_elem_flag_set(v, BM_ELEM_SELECT, false); } } /* ------------------------------------------------------------------------- */ /************************ select/deselect all operator ************************/ static bool poll_vertex_all(void *UNUSED(userdata), struct BMVert *UNUSED(v)) { return true; } static int select_all_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; int action = RNA_enum_get(op->ptr, "action"); if (!edit) return 0; /* toggle action depends on current global selection state */ if (action == SEL_TOGGLE) { if (edit->bm->totvertsel == 0) action = SEL_SELECT; else action = SEL_DESELECT; } hair_select_verts_filter(edit, settings->select_mode, action, poll_vertex_all, NULL); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW | NA_SELECTED, ob); return OPERATOR_FINISHED; } void HAIR_OT_select_all(wmOperatorType *ot) { /* identifiers */ ot->name = "Select/Deselect All"; ot->idname = "HAIR_OT_select_all"; ot->description = "Select/Deselect all hair vertices"; /* api callbacks */ ot->exec = select_all_exec; ot->poll = hair_edit_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; WM_operator_properties_select_all(ot); } /************************ mouse select operator ************************/ typedef struct DistanceVertexCirleData { HairViewData viewdata; float mval[2]; float radsq; } DistanceVertexCirleData; static bool distance_vertex_circle(void *userdata, struct BMVert *v, float *dist) { DistanceVertexCirleData *data = userdata; return hair_test_vertex_inside_circle(&data->viewdata, data->mval, data->radsq, v, dist); } static void closest_vertex_select(void *UNUSED(userdata), struct BMVert *v, int action) { apply_select_action_flag(v, action); } int ED_hair_mouse_select(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; float select_radius = ED_view3d_select_dist_px(); DistanceVertexCirleData data; int action; if (!extend && !deselect && !toggle) { hair_deselect_all(edit); } hair_init_viewdata(C, &data.viewdata); data.mval[0] = mval[0]; data.mval[1] = mval[1]; data.radsq = select_radius * select_radius; if (extend) action = SEL_SELECT; else if (deselect) action = SEL_DESELECT; else action = SEL_INVERT; hair_select_verts_closest(edit, settings->select_mode, action, distance_vertex_circle, closest_vertex_select, &data); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW | NA_SELECTED, ob); return OPERATOR_FINISHED; } /************************ select linked operator ************************/ static void linked_vertices_select(void *UNUSED(userdata), struct BMVert *v, int action) { BMVert *lv; apply_select_action_flag(v, action); for (lv = BM_strands_vert_prev(v); lv; lv = BM_strands_vert_prev(lv)) apply_select_action_flag(lv, action); for (lv = BM_strands_vert_next(v); lv; lv = BM_strands_vert_next(lv)) apply_select_action_flag(lv, action); } static int select_linked_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; float select_radius = ED_view3d_select_dist_px(); DistanceVertexCirleData data; int location[2]; int action; RNA_int_get_array(op->ptr, "location", location); hair_init_viewdata(C, &data.viewdata); data.mval[0] = location[0]; data.mval[1] = location[1]; data.radsq = select_radius * select_radius; action = RNA_boolean_get(op->ptr, "deselect") ? SEL_DESELECT : SEL_SELECT; hair_select_verts_closest(edit, settings->select_mode, action, distance_vertex_circle, linked_vertices_select, &data); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW | NA_SELECTED, ob); return OPERATOR_FINISHED; } static int select_linked_invoke(bContext *C, wmOperator *op, const wmEvent *event) { RNA_int_set_array(op->ptr, "location", event->mval); return select_linked_exec(C, op); } void HAIR_OT_select_linked(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Linked"; ot->idname = "HAIR_OT_select_linked"; ot->description = "Select connected vertices"; /* api callbacks */ ot->exec = select_linked_exec; ot->invoke = select_linked_invoke; ot->poll = hair_edit_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect linked keys rather than selecting them"); RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "", 0, 16384); } /************************ border select operator ************************/ typedef struct PollVertexRectData { HairViewData viewdata; rcti rect; } PollVertexRectData; static bool poll_vertex_inside_rect(void *userdata, struct BMVert *v) { PollVertexRectData *data = userdata; return hair_test_vertex_inside_rect(&data->viewdata, &data->rect, v); } int ED_hair_border_select(bContext *C, rcti *rect, bool select, bool extend) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; PollVertexRectData data; int action; if (!extend && select) hair_deselect_all(edit); hair_init_viewdata(C, &data.viewdata); data.rect = *rect; if (extend) action = SEL_SELECT; else if (select) action = SEL_INVERT; else action = SEL_DESELECT; hair_select_verts_filter(edit, settings->select_mode, action, poll_vertex_inside_rect, &data); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW | NA_SELECTED, ob); return OPERATOR_FINISHED; } /************************ circle select operator ************************/ typedef struct PollVertexCirleData { HairViewData viewdata; float mval[2]; float radsq; } PollVertexCirleData; static bool poll_vertex_inside_circle(void *userdata, struct BMVert *v) { PollVertexCirleData *data = userdata; float dist; return hair_test_vertex_inside_circle(&data->viewdata, data->mval, data->radsq, v, &dist); } int ED_hair_circle_select(bContext *C, bool select, const int mval[2], float radius) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; int action = select ? SEL_SELECT : SEL_DESELECT; PollVertexCirleData data; int tot; if (!edit) return 0; hair_init_viewdata(C, &data.viewdata); data.mval[0] = mval[0]; data.mval[1] = mval[1]; data.radsq = radius * radius; tot = hair_select_verts_filter(edit, settings->select_mode, action, poll_vertex_inside_circle, &data); return tot; } /************************ lasso select operator ************************/ typedef struct PollVertexLassoData { HairViewData viewdata; const int (*mcoords)[2]; short moves; } PollVertexLassoData; static bool poll_vertex_inside_lasso(void *userdata, struct BMVert *v) { PollVertexLassoData *data = userdata; return hair_test_vertex_inside_lasso(&data->viewdata, data->mcoords, data->moves, v); } int ED_hair_lasso_select(bContext *C, const int mcoords[][2], const short moves, bool extend, bool select) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); BMEditStrands *edit = BKE_editstrands_from_object(ob); HairEditSettings *settings = &scene->toolsettings->hair_edit; PollVertexLassoData data; int action; if (!extend && select) hair_deselect_all(edit); hair_init_viewdata(C, &data.viewdata); data.mcoords = mcoords; data.moves = moves; if (extend) action = SEL_SELECT; else if (select) action = SEL_INVERT; else action = SEL_DESELECT; hair_select_verts_filter(edit, settings->select_mode, action, poll_vertex_inside_lasso, &data); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW | NA_SELECTED, ob); return OPERATOR_FINISHED; }