diff options
Diffstat (limited to 'source/blender/editors/hair/hair_edit.c')
-rw-r--r-- | source/blender/editors/hair/hair_edit.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/source/blender/editors/hair/hair_edit.c b/source/blender/editors/hair/hair_edit.c new file mode 100644 index 00000000000..e4aad528f33 --- /dev/null +++ b/source/blender/editors/hair/hair_edit.c @@ -0,0 +1,459 @@ +/* + * ***** 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_edit.c + * \ingroup edhair + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" +#include "BLI_math.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_brush.h" +#include "BKE_cdderivedmesh.h" +#include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_paint.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_view3d.h" + +#include "hair_intern.h" +#include "paint_intern.h" + +int hair_edit_poll(bContext *C) +{ + Object *obact; + + obact = CTX_data_active_object(C); + if ((obact && obact->mode & OB_MODE_HAIR_EDIT) && CTX_wm_region_view3d(C)) { + return true; + } + + return false; +} + +bool hair_use_mirror_x(Object *ob) +{ + if (ob->type == OB_MESH) + return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X; + else + return false; +} + +bool hair_use_mirror_topology(Object *ob) +{ + if (ob->type == OB_MESH) + return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_TOPO; + else + return false; +} + + +/* ==== BMesh utilities ==== */ + +void hair_bm_min_max(BMEditStrands *edit, float min[3], float max[3]) +{ + BMVert *v; + BMIter iter; + + if (edit->bm->totvert > 0) { + INIT_MINMAX(min, max); + + BM_ITER_MESH(v, &iter, edit->bm, BM_VERTS_OF_MESH) { + minmax_v3v3_v3(min, max, v->co); + } + } + else { + zero_v3(min); + zero_v3(max); + } +} + + +/* ==== edit mode toggle ==== */ + +int hair_edit_toggle_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + + if (ob == NULL) + return false; + if (CTX_data_edit_object(C)) + return false; + + return (ob->data && !((ID *)ob->data)->lib && ED_hair_object_has_hair_particle_data(ob)) || + ED_hair_object_has_hair_cache_data(ob); +} + +static void toggle_hair_cursor(bContext *C, bool enable) +{ + wmWindowManager *wm = CTX_wm_manager(C); + Scene *scene = CTX_data_scene(C); + HairEditSettings *settings = &scene->toolsettings->hair_edit; + + if (enable) { + hair_edit_cursor_start(C, hair_edit_toggle_poll); + } + else { + if (settings->paint_cursor) { + WM_paint_cursor_end(wm, settings->paint_cursor); + settings->paint_cursor = NULL; + } + } +} + +static int hair_edit_toggle_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + const int mode_flag = OB_MODE_HAIR_EDIT; + const bool is_mode_set = (ob->mode & mode_flag) != 0; + + if (!is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (!is_mode_set) { + if (ED_hair_object_init_particle_edit(scene, ob)) { + /* particle edit */ + } + else if (ED_hair_object_init_cache_edit(ob)) { + /* strands cache edit */ + } + + ob->mode |= mode_flag; + + toggle_hair_cursor(C, true); + WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL); + } + else { + if (ED_hair_object_apply_particle_edit(ob)) { + /* particle edit */ + } + else if (ED_hair_object_apply_cache_edit(ob)) { + /* strands cache edit */ + } + ob->mode &= ~mode_flag; + + toggle_hair_cursor(C, false); + WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL); + } + + DAG_id_tag_update(&ob->id, OB_RECALC_DATA); + + return OPERATOR_FINISHED; +} + +void HAIR_OT_hair_edit_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Hair Edit Toggle"; + ot->idname = "HAIR_OT_hair_edit_toggle"; + ot->description = "Toggle hair edit mode"; + + /* api callbacks */ + ot->exec = hair_edit_toggle_exec; + ot->poll = hair_edit_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + + +/* ==== brush stroke ==== */ + +void hair_init_viewdata(bContext *C, HairViewData *viewdata) +{ + View3D *v3d; + bool has_zbuf; + + view3d_set_viewcontext(C, &viewdata->vc); + + v3d = viewdata->vc.v3d; + has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT); + + view3d_get_transformation(viewdata->vc.ar, viewdata->vc.rv3d, NULL, &viewdata->mats); + + if (has_zbuf) { + if (v3d->flag & V3D_INVALID_BACKBUF) { + /* needed or else the draw matrix can be incorrect */ + view3d_operator_needs_opengl(C); + + ED_view3d_backbuf_validate(&viewdata->vc); + /* we may need to force an update here by setting the rv3d as dirty + * for now it seems ok, but take care!: + * rv3d->depths->dirty = 1; */ + ED_view3d_depth_update(viewdata->vc.ar); + } + } +} + +typedef struct HairStroke { + Scene *scene; + Object *ob; + BMEditStrands *edit; + + bool first; + float lastmouse[2]; + float zfac; + + float smoothdir[2]; +} HairStroke; + +static int hair_stroke_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + BMEditStrands *edit = BKE_editstrands_from_object(ob); + ARegion *ar = CTX_wm_region(C); + + HairStroke *stroke; + float min[3], max[3], center[3]; + + /* set the 'distance factor' for grabbing (used in comb etc) */ + hair_bm_min_max(edit, min, max); + mid_v3_v3v3(center, min, max); + + stroke = MEM_callocN(sizeof(HairStroke), "HairStroke"); + stroke->first = true; + op->customdata = stroke; + + stroke->scene = scene; + stroke->ob = ob; + stroke->edit = edit; + + stroke->zfac = ED_view3d_calc_zfac(ar->regiondata, center, NULL); + + return 1; +} + +static bool hair_stroke_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) +{ + HairStroke *stroke = op->customdata; + Scene *scene = stroke->scene; + Object *ob = stroke->ob; + BMEditStrands *edit = stroke->edit; + HairEditSettings *settings = &scene->toolsettings->hair_edit; + ARegion *ar = CTX_wm_region(C); + const float smoothfac = 0.9f; /* XXX should this be configurable? */ + + float mouse[2], mdelta[2], zvec[3], delta_max; + int totsteps, step; + HairToolData tool_data; + bool updated = false; + + RNA_float_get_array(itemptr, "mouse", mouse); + + if (stroke->first) { + copy_v2_v2(stroke->lastmouse, mouse); + zero_v2(stroke->smoothdir); + stroke->first = false; + } + + if (!settings->brush) + return false; + + sub_v2_v2v2(mdelta, mouse, stroke->lastmouse); + delta_max = max_ff(fabsf(mdelta[0]), fabsf(mdelta[1])); + + totsteps = delta_max / (0.2f * BKE_brush_size_get(scene, settings->brush)) + 1; + mul_v2_fl(mdelta, 1.0f / (float)totsteps); + + /* low-pass filter to smooth out jittery pixel increments in the direction */ + interp_v2_v2v2(stroke->smoothdir, mdelta, stroke->smoothdir, smoothfac); + + hair_init_viewdata(C, &tool_data.viewdata); + tool_data.scene = scene; + tool_data.ob = ob; + tool_data.edit = edit; + tool_data.settings = settings; + + invert_m4_m4(tool_data.imat, ob->obmat); + copy_v2_v2(tool_data.mval, mouse); + tool_data.mdepth = stroke->zfac; + + zvec[0] = 0.0f; zvec[1] = 0.0f; zvec[2] = stroke->zfac; + ED_view3d_win_to_3d(ar, zvec, mouse, tool_data.loc); + ED_view3d_win_to_delta(ar, stroke->smoothdir, tool_data.delta, stroke->zfac); + /* tools work in object space */ + mul_m4_v3(tool_data.imat, tool_data.loc); + mul_mat3_m4_v3(tool_data.imat, tool_data.delta); + + for (step = 0; step < totsteps; ++step) { + bool step_updated = hair_brush_step(&tool_data); + + if (step_updated) + BKE_editstrands_solve_constraints(ob, edit, NULL); + + updated |= step_updated; + } + + copy_v2_v2(stroke->lastmouse, mouse); + + return updated; +} + +static void hair_stroke_exit(wmOperator *op) +{ + HairStroke *stroke = op->customdata; + MEM_freeN(stroke); +} + +static int hair_stroke_exec(bContext *C, wmOperator *op) +{ + HairStroke *stroke = op->customdata; + Object *ob = stroke->ob; + + bool updated = false; + + if (!hair_stroke_init(C, op)) + return OPERATOR_CANCELLED; + + RNA_BEGIN (op->ptr, itemptr, "stroke") + { + updated |= hair_stroke_apply(C, op, &itemptr); + } + RNA_END; + + if (updated) { + DAG_id_tag_update(&ob->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + } + + hair_stroke_exit(op); + + return OPERATOR_FINISHED; +} + +static void hair_stroke_apply_event(bContext *C, wmOperator *op, const wmEvent *event) +{ + HairStroke *stroke = op->customdata; + Object *ob = stroke->ob; + + PointerRNA itemptr; + float mouse[2]; + bool updated = false; + + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + + /* fill in stroke */ + RNA_collection_add(op->ptr, "stroke", &itemptr); + + RNA_float_set_array(&itemptr, "mouse", mouse); + + /* apply */ + updated |= hair_stroke_apply(C, op, &itemptr); + + if (updated) { + DAG_id_tag_update(&ob->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob); + } + else { + /* even if nothing was changed, still trigger redraw + * for brush drawing during the modal operator + */ + WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, ob); + } +} + +static int hair_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!hair_stroke_init(C, op)) + return OPERATOR_CANCELLED; + + hair_stroke_apply_event(C, op, event); + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int hair_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + switch (event->type) { + case LEFTMOUSE: + case MIDDLEMOUSE: + case RIGHTMOUSE: // XXX hardcoded + hair_stroke_exit(op); + return OPERATOR_FINISHED; + case MOUSEMOVE: + hair_stroke_apply_event(C, op, event); + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static void hair_stroke_cancel(bContext *UNUSED(C), wmOperator *op) +{ + hair_stroke_exit(op); +} + +void HAIR_OT_stroke(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Hair Stroke"; + ot->idname = "HAIR_OT_stroke"; + ot->description = "Use a stroke tool on hair strands"; + + /* api callbacks */ + ot->exec = hair_stroke_exec; + ot->invoke = hair_stroke_invoke; + ot->modal = hair_stroke_modal; + ot->cancel = hair_stroke_cancel; + ot->poll = hair_edit_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING; + + /* properties */ + RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); +} |