diff options
Diffstat (limited to 'source/blender/editors/hair')
-rw-r--r-- | source/blender/editors/hair/CMakeLists.txt | 56 | ||||
-rw-r--r-- | source/blender/editors/hair/SConscript | 54 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_cursor.c | 109 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_edit.c | 459 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_intern.h | 124 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_mirror.c | 210 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_object_cachelib.c | 104 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_object_particles.c | 101 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_ops.c | 143 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_select.c | 502 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_stroke.c | 498 | ||||
-rw-r--r-- | source/blender/editors/hair/hair_undo.c | 190 |
12 files changed, 2550 insertions, 0 deletions
diff --git a/source/blender/editors/hair/CMakeLists.txt b/source/blender/editors/hair/CMakeLists.txt new file mode 100644 index 00000000000..2a72ebb9aa4 --- /dev/null +++ b/source/blender/editors/hair/CMakeLists.txt @@ -0,0 +1,56 @@ +# ***** 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. +# +# Contributor(s): Jacques Beaurain. +# +# ***** END GPL LICENSE BLOCK ***** + +set(INC + ../include + ../sculpt_paint + ../../blenfont + ../../blenkernel + ../../blenlib + ../../bmesh + ../../gpu + ../../makesdna + ../../makesrna + ../../windowmanager + ../../../../intern/guardedalloc + ../../../../intern/glew-mx +) + +set(INC_SYS + ${GLEW_INCLUDE_PATH} +) + +set(SRC + hair_cursor.c + hair_edit.c + hair_mirror.c + hair_object_cachelib.c + hair_object_particles.c + hair_ops.c + hair_select.c + hair_stroke.c + hair_undo.c + + hair_intern.h +) + +add_definitions(${GL_DEFINITIONS}) + +blender_add_lib(bf_editor_hair "${SRC}" "${INC}" "${INC_SYS}") diff --git a/source/blender/editors/hair/SConscript b/source/blender/editors/hair/SConscript new file mode 100644 index 00000000000..d34bb48218d --- /dev/null +++ b/source/blender/editors/hair/SConscript @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# ***** 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) 2006, Blender Foundation +# All rights reserved. +# +# The Original Code is: all of this file. +# +# Contributor(s): Nathan Letwory. +# +# ***** END GPL LICENSE BLOCK ***** + +Import ('env') + +sources = env.Glob('*.c') + +incs = [ + '#/intern/guardedalloc', + env['BF_GLEW_INC'], + '#/intern/glew-mx', + '../include', + '../sculpt_paint', + '../../blenfont', + '../../blenkernel', + '../../blenlib', + '../../bmesh', + '../../gpu', + '../../makesdna', + '../../makesrna', + '../../windowmanager', + ] +incs = ' '.join(incs) + +defs = ['BF_GL_DEFINITIONS'] + +if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'linuxcross', 'win64-vc', 'win64-mingw'): + incs += ' ' + env['BF_PTHREADS_INC'] + +env.BlenderLib ( 'bf_editors_hair', sources, Split(incs), defs, libtype=['core'], priority=[344] ) diff --git a/source/blender/editors/hair/hair_cursor.c b/source/blender/editors/hair/hair_cursor.c new file mode 100644 index 00000000000..dadd0cf114e --- /dev/null +++ b/source/blender/editors/hair/hair_cursor.c @@ -0,0 +1,109 @@ +/* + * ***** 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_cursor.c + * \ingroup edhair + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" +#include "BLI_math.h" + +#include "DNA_brush_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_brush.h" +#include "BKE_context.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "ED_view3d.h" + +#include "hair_intern.h" + +static void hair_draw_cursor(bContext *C, int x, int y, void *UNUSED(customdata)) +{ + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + HairEditSettings *settings = &scene->toolsettings->hair_edit; + Brush *brush = settings->brush; + + float final_radius; + float translation[2]; + float outline_alpha, *outline_col; + + if (!brush) + return; + + final_radius = BKE_brush_size_get(scene, brush); + + /* set various defaults */ + translation[0] = x; + translation[1] = y; + outline_alpha = 0.5; + outline_col = brush->add_col; + + /* make lines pretty */ + glEnable(GL_BLEND); + glEnable(GL_LINE_SMOOTH); + + /* set brush color */ + glColor4f(outline_col[0], outline_col[1], outline_col[2], outline_alpha); + + /* draw brush outline */ + glTranslatef(translation[0], translation[1], 0); + + /* draw an inner brush */ + if (ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) { + /* inner at full alpha */ + glutil_draw_lined_arc(0.0, M_PI*2.0f, final_radius * ups->size_pressure_value, 40); + /* outer at half alpha */ + glColor4f(outline_col[0], outline_col[1], outline_col[2], outline_alpha * 0.5f); + } + glutil_draw_lined_arc(0.0, M_PI*2.0f, final_radius, 40); + glTranslatef(-translation[0], -translation[1], 0); + + /* restore GL state */ + glDisable(GL_BLEND); + glDisable(GL_LINE_SMOOTH); +} + +void hair_edit_cursor_start(bContext *C, int (*poll)(bContext *C)) +{ + Scene *scene = CTX_data_scene(C); + HairEditSettings *settings = &scene->toolsettings->hair_edit; + + if (!settings->paint_cursor) + settings->paint_cursor = WM_paint_cursor_activate(CTX_wm_manager(C), poll, hair_draw_cursor, NULL); +} 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", ""); +} diff --git a/source/blender/editors/hair/hair_intern.h b/source/blender/editors/hair/hair_intern.h new file mode 100644 index 00000000000..cc805136ace --- /dev/null +++ b/source/blender/editors/hair/hair_intern.h @@ -0,0 +1,124 @@ +/* + * ***** 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_intern.h + * \ingroup edhair + */ + +#ifndef __HAIR_INTERN_H__ +#define __HAIR_INTERN_H__ + +#include "BIF_glutil.h" + +#include "ED_view3d.h" + +struct ARegion; +struct bContext; +struct wmOperatorType; +struct rcti; + +struct Object; +struct Strands; +struct DupliObjectData; +struct StrandsKeyCacheModifier; +struct DerivedMesh; + +/* hair_edit.c */ +bool hair_use_mirror_x(struct Object *ob); +bool hair_use_mirror_topology(struct Object *ob); + +int hair_edit_toggle_poll(struct bContext *C); +int hair_edit_poll(struct bContext *C); + +void HAIR_OT_hair_edit_toggle(struct wmOperatorType *ot); + +/* hair_select.c */ +void HAIR_OT_select_all(struct wmOperatorType *ot); +void HAIR_OT_select_linked(struct wmOperatorType *ot); + +/* hair_stroke.c */ +void HAIR_OT_stroke(struct wmOperatorType *ot); + +/* hair_object_cachelib.c */ +bool ED_hair_object_has_hair_cache_data(struct Object *ob); +bool ED_hair_object_init_cache_edit(struct Object *ob); +bool ED_hair_object_apply_cache_edit(struct Object *ob); + +/* hair_object_particles.c */ +bool ED_hair_object_has_hair_particle_data(struct Object *ob); +bool ED_hair_object_init_particle_edit(struct Scene *scene, struct Object *ob); +bool ED_hair_object_apply_particle_edit(struct Object *ob); + + +/* ==== Hair Brush ==== */ + +typedef struct HairViewData { + ViewContext vc; + bglMats mats; +} HairViewData; + +void hair_init_viewdata(struct bContext *C, struct HairViewData *viewdata); + +bool hair_test_depth(struct HairViewData *viewdata, const float co[3], const int screen_co[2]); +bool hair_test_vertex_inside_circle(struct HairViewData *viewdata, const float mval[2], float radsq, + struct BMVert *v, float *r_dist); +bool hair_test_edge_inside_circle(struct HairViewData *viewdata, const float mval[2], float radsq, + struct BMVert *v1, struct BMVert *v2, float *r_dist, float *r_lambda); +bool hair_test_vertex_inside_rect(struct HairViewData *viewdata, struct rcti *rect, struct BMVert *v); +bool hair_test_vertex_inside_lasso(struct HairViewData *viewdata, const int mcoords[][2], short moves, struct BMVert *v); + +typedef struct HairToolData { + /* context */ + struct Scene *scene; + struct Object *ob; + struct BMEditStrands *edit; + struct HairEditSettings *settings; + HairViewData viewdata; + + /* view space */ + float mval[2]; /* mouse coordinates */ + float mdepth; /* mouse z depth */ + + /* object space */ + float imat[4][4]; /* obmat inverse */ + float loc[3]; /* start location */ + float delta[3]; /* stroke step */ +} HairToolData; + +bool hair_brush_step(struct HairToolData *data); + +/* ==== Cursor ==== */ + +void hair_edit_cursor_start(struct bContext *C, int (*poll)(struct bContext *C)); + +/* ==== BMesh utilities ==== */ + +struct BMEditStrands; + +void hair_bm_min_max(struct BMEditStrands *edit, float min[3], float max[3]); + +#endif diff --git a/source/blender/editors/hair/hair_mirror.c b/source/blender/editors/hair/hair_mirror.c new file mode 100644 index 00000000000..7f5b14e4b37 --- /dev/null +++ b/source/blender/editors/hair/hair_mirror.c @@ -0,0 +1,210 @@ +/* + * ***** 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_mirror.c + * \ingroup edhair + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" +#include "BLI_math.h" + +#include "BKE_editmesh_bvh.h" +#include "BKE_editstrands.h" + +#include "ED_physics.h" +#include "ED_util.h" + +#include "bmesh.h" + +#include "hair_intern.h" + +/* TODO use_topology is not yet implemented for strands. + * Native strand topology is not very useful for this. + * Instead, the topology of the scalp mesh should be used for finding mirrored strand roots, + * then the arc- or parametric length of a vertex from the root to find mirrored verts. + */ + +#define BM_SEARCH_MAXDIST_MIRR 0.00002f +#define BM_CD_LAYER_ID "__mirror_index" +/** + * \param edit Edit strands. + * \param use_self Allow a vertex to point to its self (middle verts). + * \param use_select Restrict to selected verts. + * \param use_topology Use topology mirror. + * \param maxdist Distance for close point test. + * \param r_index Optional array to write into, as an alternative to a customdata layer (length of total verts). + */ +void ED_strands_mirror_cache_begin_ex(BMEditStrands *edit, const int axis, const bool use_self, const bool use_select, + /* extra args */ + const bool UNUSED(use_topology), float maxdist, int *r_index) +{ + BMesh *bm = edit->bm; + BMIter iter; + BMVert *v; + int cd_vmirr_offset; + int i; + + /* one or the other is used depending if topo is enabled */ + struct BMBVHTree *tree = NULL; + + BM_mesh_elem_table_ensure(bm, BM_VERT); + + if (r_index == NULL) { + const char *layer_id = BM_CD_LAYER_ID; + edit->mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id); + if (edit->mirror_cdlayer == -1) { + BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_INT, layer_id); + edit->mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id); + } + + cd_vmirr_offset = CustomData_get_n_offset(&bm->vdata, CD_PROP_INT, + edit->mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT)); + + bm->vdata.layers[edit->mirror_cdlayer].flag |= CD_FLAG_TEMPORARY; + } + + BM_mesh_elem_index_ensure(bm, BM_VERT); + + tree = BKE_bmbvh_new(edit->bm, NULL, 0, 0, NULL, false); + +#define VERT_INTPTR(_v, _i) r_index ? &r_index[_i] : BM_ELEM_CD_GET_VOID_P(_v, cd_vmirr_offset); + + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + BLI_assert(BM_elem_index_get(v) == i); + + /* temporary for testing, check for selection */ + if (use_select && !BM_elem_flag_test(v, BM_ELEM_SELECT)) { + /* do nothing */ + } + else { + BMVert *v_mirr; + float co[3]; + int *idx = VERT_INTPTR(v, i); + + copy_v3_v3(co, v->co); + co[axis] *= -1.0f; + v_mirr = BKE_bmbvh_find_vert_closest(tree, co, maxdist); + + if (v_mirr && (use_self || (v_mirr != v))) { + const int i_mirr = BM_elem_index_get(v_mirr); + *idx = i_mirr; + idx = VERT_INTPTR(v_mirr, i_mirr); + *idx = i; + } + else { + *idx = -1; + } + } + + } + +#undef VERT_INTPTR + + BKE_bmbvh_free(tree); +} + +void ED_strands_mirror_cache_begin(BMEditStrands *edit, const int axis, + const bool use_self, const bool use_select, + const bool use_topology) +{ + ED_strands_mirror_cache_begin_ex(edit, axis, + use_self, use_select, + /* extra args */ + use_topology, BM_SEARCH_MAXDIST_MIRR, NULL); +} + +BMVert *ED_strands_mirror_get(BMEditStrands *edit, BMVert *v) +{ + const int *mirr = CustomData_bmesh_get_layer_n(&edit->bm->vdata, v->head.data, edit->mirror_cdlayer); + + BLI_assert(edit->mirror_cdlayer != -1); /* invalid use */ + + if (mirr && *mirr >= 0 && *mirr < edit->bm->totvert) { + if (!edit->bm->vtable) { + printf("err: should only be called between " + "ED_strands_mirror_cache_begin and ED_strands_mirror_cache_end\n"); + return NULL; + } + + return edit->bm->vtable[*mirr]; + } + + return NULL; +} + +BMEdge *ED_strands_mirror_get_edge(BMEditStrands *edit, BMEdge *e) +{ + BMVert *v1_mirr = ED_strands_mirror_get(edit, e->v1); + if (v1_mirr) { + BMVert *v2_mirr = ED_strands_mirror_get(edit, e->v2); + if (v2_mirr) { + return BM_edge_exists(v1_mirr, v2_mirr); + } + } + + return NULL; +} + +void ED_strands_mirror_cache_clear(BMEditStrands *edit, BMVert *v) +{ + int *mirr = CustomData_bmesh_get_layer_n(&edit->bm->vdata, v->head.data, edit->mirror_cdlayer); + + BLI_assert(edit->mirror_cdlayer != -1); /* invalid use */ + + if (mirr) { + *mirr = -1; + } +} + +void ED_strands_mirror_cache_end(BMEditStrands *edit) +{ + edit->mirror_cdlayer = -1; +} + +void ED_strands_mirror_apply(BMEditStrands *edit, const int sel_from, const int sel_to) +{ + BMIter iter; + BMVert *v; + + BLI_assert((edit->bm->vtable != NULL) && ((edit->bm->elem_table_dirty & BM_VERT) == 0)); + + BM_ITER_MESH (v, &iter, edit->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT) == sel_from) { + BMVert *mirr = ED_strands_mirror_get(edit, v); + if (mirr) { + if (BM_elem_flag_test(mirr, BM_ELEM_SELECT) == sel_to) { + copy_v3_v3(mirr->co, v->co); + mirr->co[0] *= -1.0f; + } + } + } + } +} diff --git a/source/blender/editors/hair/hair_object_cachelib.c b/source/blender/editors/hair/hair_object_cachelib.c new file mode 100644 index 00000000000..576d8cd2c9d --- /dev/null +++ b/source/blender/editors/hair/hair_object_cachelib.c @@ -0,0 +1,104 @@ +/* + * ***** 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_object_cachelib.c + * \ingroup edhair + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "DNA_cache_library_types.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_strands_types.h" + +#include "BKE_anim.h" +#include "BKE_cache_library.h" +#include "BKE_cdderivedmesh.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_strands.h" + +#include "bmesh.h" + +#include "hair_intern.h" + +bool ED_hair_object_has_hair_cache_data(Object *ob) +{ + return BKE_cache_modifier_strands_key_get(ob, NULL, NULL, NULL, NULL, NULL, NULL); +} + +bool ED_hair_object_init_cache_edit(Object *ob) +{ + StrandsKeyCacheModifier *skmd; + DerivedMesh *dm; + Strands *strands; + float mat[4][4]; + + if (!BKE_cache_modifier_strands_key_get(ob, &skmd, &dm, &strands, NULL, NULL, mat)) + return false; + + if (!skmd->edit) { + BMesh *bm = BKE_cache_strands_to_bmesh(strands, skmd->key, mat, skmd->shapenr, dm); + + skmd->edit = BKE_editstrands_create(bm, dm, mat); + } + + return true; +} + +bool ED_hair_object_apply_cache_edit(Object *ob) +{ + StrandsKeyCacheModifier *skmd; + DerivedMesh *dm; + Strands *strands; + DupliObjectData *dobdata; + const char *name; + float mat[4][4]; + + if (!BKE_cache_modifier_strands_key_get(ob, &skmd, &dm, &strands, &dobdata, &name, mat)) + return false; + + if (skmd->edit) { + Strands *nstrands; + + nstrands = BKE_cache_strands_from_bmesh(skmd->edit, skmd->key, mat, dm); + + BKE_dupli_object_data_add_strands(dobdata, name, nstrands); + + BKE_editstrands_free(skmd->edit); + MEM_freeN(skmd->edit); + skmd->edit = NULL; + } + + return true; +} diff --git a/source/blender/editors/hair/hair_object_particles.c b/source/blender/editors/hair/hair_object_particles.c new file mode 100644 index 00000000000..a6d05327482 --- /dev/null +++ b/source/blender/editors/hair/hair_object_particles.c @@ -0,0 +1,101 @@ +/* + * ***** 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_object_particles.c + * \ingroup edhair + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" +#include "DNA_scene_types.h" + +#include "BKE_cdderivedmesh.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_particle.h" + +#include "bmesh.h" + +#include "hair_intern.h" + +bool ED_hair_object_has_hair_particle_data(Object *ob) +{ + ParticleSystem *psys = psys_get_current(ob); + if (psys && psys->part->type == PART_HAIR) + return true; + + return false; +} + +bool ED_hair_object_init_particle_edit(Scene *scene, Object *ob) +{ + ParticleSystem *psys = psys_get_current(ob); + BMesh *bm; + DerivedMesh *dm; + + if (psys && psys->part->type == PART_HAIR) { + if (!psys->hairedit) { + bm = BKE_particles_to_bmesh(ob, psys); + + if (ob->type == OB_MESH || ob->derivedFinal) + dm = ob->derivedFinal ? ob->derivedFinal : mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); + else + dm = NULL; + + psys->hairedit = BKE_editstrands_create(bm, dm, NULL); + } + return true; + } + + return false; +} + +bool ED_hair_object_apply_particle_edit(Object *ob) +{ + ParticleSystem *psys = psys_get_current(ob); + if (psys && psys->part->type == PART_HAIR) { + if (psys->hairedit) { + BKE_particles_from_bmesh(ob, psys); + psys->flag |= PSYS_EDITED; + + BKE_editstrands_free(psys->hairedit); + MEM_freeN(psys->hairedit); + psys->hairedit = NULL; + } + + return true; + } + + return false; +} diff --git a/source/blender/editors/hair/hair_ops.c b/source/blender/editors/hair/hair_ops.c new file mode 100644 index 00000000000..bb3b027cf20 --- /dev/null +++ b/source/blender/editors/hair/hair_ops.c @@ -0,0 +1,143 @@ +/* + * ***** 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_ops.c + * \ingroup edhair + */ + +#include "BLI_utildefines.h" + +#include "DNA_object_types.h" + +#include "BKE_context.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_physics.h" + +#include "hair_intern.h" +#include "paint_intern.h" + +void ED_operatortypes_hair(void) +{ + WM_operatortype_append(HAIR_OT_hair_edit_toggle); + + WM_operatortype_append(HAIR_OT_select_all); + WM_operatortype_append(HAIR_OT_select_linked); + + WM_operatortype_append(HAIR_OT_stroke); +} + +static int hair_poll(bContext *C) +{ + if (hair_edit_toggle_poll(C)) + if (CTX_data_active_object(C)->mode & OB_MODE_HAIR_EDIT) + return true; + + return false; +} + +static void ed_keymap_hair_brush_switch(wmKeyMap *keymap, const char *mode) +{ + wmKeyMapItem *kmi; + int i; + /* index 0-9 (zero key is tenth), shift key for index 10-19 */ + for (i = 0; i < 20; i++) { + kmi = WM_keymap_add_item(keymap, "BRUSH_OT_active_index_set", + ZEROKEY + ((i + 1) % 10), KM_PRESS, i < 10 ? 0 : KM_SHIFT, 0); + RNA_string_set(kmi->ptr, "mode", mode); + RNA_int_set(kmi->ptr, "index", i); + } +} + +static void ed_keymap_hair_brush_size(wmKeyMap *keymap, const char *UNUSED(path)) +{ + wmKeyMapItem *kmi; + + kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", LEFTBRACKETKEY, KM_PRESS, 0, 0); + RNA_float_set(kmi->ptr, "scalar", 0.9); + + kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", RIGHTBRACKETKEY, KM_PRESS, 0, 0); + RNA_float_set(kmi->ptr, "scalar", 10.0 / 9.0); // 1.1111.... +} + +static void ed_keymap_hair_brush_radial_control(wmKeyMap *keymap, const char *settings, RCFlags flags) +{ + wmKeyMapItem *kmi; + /* only size needs to follow zoom, strength shows fixed size circle */ + int flags_nozoom = flags & (~RC_ZOOM); + int flags_noradial_secondary = flags & (~(RC_SECONDARY_ROTATION | RC_ZOOM)); + + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0); + set_brush_rc_props(kmi->ptr, settings, "size", "use_unified_size", flags); + + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0); + set_brush_rc_props(kmi->ptr, settings, "strength", "use_unified_strength", flags_nozoom); + + if (flags & RC_WEIGHT) { + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", WKEY, KM_PRESS, 0, 0); + set_brush_rc_props(kmi->ptr, settings, "weight", "use_unified_weight", flags_nozoom); + } + + if (flags & RC_ROTATION) { + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0); + set_brush_rc_props(kmi->ptr, settings, "texture_slot.angle", NULL, flags_noradial_secondary); + } + + if (flags & RC_SECONDARY_ROTATION) { + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL | KM_ALT, 0); + set_brush_rc_props(kmi->ptr, settings, "mask_texture_slot.angle", NULL, flags_nozoom); + } +} + +void ED_keymap_hair(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap; + wmKeyMapItem *kmi; + + keymap = WM_keymap_find(keyconf, "Hair", 0, 0); + keymap->poll = hair_poll; + + kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", AKEY, KM_PRESS, 0, 0); + RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE); + kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0); + RNA_enum_set(kmi->ptr, "action", SEL_INVERT); + + kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, 0, 0); + RNA_boolean_set(kmi->ptr, "deselect", false); + kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, KM_SHIFT, 0); + RNA_boolean_set(kmi->ptr, "deselect", true); + + kmi = WM_keymap_add_item(keymap, "HAIR_OT_stroke", LEFTMOUSE, KM_PRESS, 0, 0); + + ed_keymap_hair_brush_switch(keymap, "hair_edit"); + ed_keymap_hair_brush_size(keymap, "tool_settings.hair_edit.brush.size"); + ed_keymap_hair_brush_radial_control(keymap, "hair_edit", 0); +} diff --git a/source/blender/editors/hair/hair_select.c b/source/blender/editors/hair/hair_select.c new file mode 100644 index 00000000000..986e669f3a7 --- /dev/null +++ b/source/blender/editors/hair/hair_select.c @@ -0,0 +1,502 @@ +/* + * ***** 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 <stdlib.h> + +#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; +} diff --git a/source/blender/editors/hair/hair_stroke.c b/source/blender/editors/hair/hair_stroke.c new file mode 100644 index 00000000000..aeb460990db --- /dev/null +++ b/source/blender/editors/hair/hair_stroke.c @@ -0,0 +1,498 @@ +/* + * ***** 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_lasso.h" +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_brush.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_effect.h" +#include "BKE_mesh_sample.h" + +#include "bmesh.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "ED_physics.h" +#include "ED_view3d.h" + +#include "hair_intern.h" + +bool hair_test_depth(HairViewData *viewdata, const float co[3], const int screen_co[2]) +{ + View3D *v3d = viewdata->vc.v3d; + ViewDepths *vd = viewdata->vc.rv3d->depths; + const bool has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT); + + double ux, uy, uz; + float depth; + + /* nothing to do */ + if (!has_zbuf) + return true; + + gluProject(co[0], co[1], co[2], + viewdata->mats.modelview, viewdata->mats.projection, viewdata->mats.viewport, + &ux, &uy, &uz); + + /* check if screen_co is within bounds because brush_cut uses out of screen coords */ + if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) { + BLI_assert(vd && vd->depths); + /* we know its not clipped */ + depth = vd->depths[screen_co[1] * vd->w + screen_co[0]]; + + return ((float)uz - 0.00001f <= depth); + } + + return false; +} + +bool hair_test_vertex_inside_circle(HairViewData *viewdata, const float mval[2], float radsq, BMVert *v, float *r_dist) +{ + float (*obmat)[4] = viewdata->vc.obact->obmat; + float co_world[3]; + float dx, dy, distsq; + int screen_co[2]; + + mul_v3_m4v3(co_world, obmat, v->co); + + /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */ + if (ED_view3d_project_int_global(viewdata->vc.ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) + return false; + + dx = mval[0] - (float)screen_co[0]; + dy = mval[1] - (float)screen_co[1]; + distsq = dx * dx + dy * dy; + + if (distsq > radsq) + return false; + + if (hair_test_depth(viewdata, v->co, screen_co)) { + *r_dist = sqrtf(distsq); + return true; + } + else + return false; +} + +bool hair_test_edge_inside_circle(HairViewData *viewdata, const float mval[2], float radsq, BMVert *v1, BMVert *v2, float *r_dist, float *r_lambda) +{ + float (*obmat)[4] = viewdata->vc.obact->obmat; + float world_co1[3], world_co2[3]; + float dx, dy, distsq; + int screen_co1[2], screen_co2[2], screen_cp[2]; + float lambda, world_cp[3], screen_cpf[2], screen_co1f[2], screen_co2f[2]; + + mul_v3_m4v3(world_co1, obmat, v1->co); + mul_v3_m4v3(world_co2, obmat, v2->co); + + /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */ + if (ED_view3d_project_int_global(viewdata->vc.ar, world_co1, screen_co1, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) + return false; + if (ED_view3d_project_int_global(viewdata->vc.ar, world_co2, screen_co2, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) + return false; + + screen_co1f[0] = screen_co1[0]; + screen_co1f[1] = screen_co1[1]; + screen_co2f[0] = screen_co2[0]; + screen_co2f[1] = screen_co2[1]; + lambda = closest_to_line_v2(screen_cpf, mval, screen_co1f, screen_co2f); + if (lambda < 0.0f || lambda > 1.0f) { + CLAMP(lambda, 0.0f, 1.0f); + interp_v2_v2v2(screen_cpf, screen_co1f, screen_co2f, lambda); + } + + dx = mval[0] - screen_cpf[0]; + dy = mval[1] - screen_cpf[1]; + distsq = dx * dx + dy * dy; + + if (distsq > radsq) + return false; + + interp_v3_v3v3(world_cp, world_co1, world_co2, lambda); + + screen_cp[0] = screen_cpf[0]; + screen_cp[1] = screen_cpf[1]; + if (hair_test_depth(viewdata, world_cp, screen_cp)) { + *r_dist = sqrtf(distsq); + *r_lambda = lambda; + return true; + } + else + return false; +} + +bool hair_test_vertex_inside_rect(HairViewData *viewdata, rcti *rect, BMVert *v) +{ + float (*obmat)[4] = viewdata->vc.obact->obmat; + float co_world[3]; + int screen_co[2]; + + mul_v3_m4v3(co_world, obmat, v->co); + + /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */ + if (ED_view3d_project_int_global(viewdata->vc.ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) + return false; + + if (!BLI_rcti_isect_pt_v(rect, screen_co)) + return false; + + if (hair_test_depth(viewdata, v->co, screen_co)) + return true; + else + return false; +} + +bool hair_test_vertex_inside_lasso(HairViewData *viewdata, const int mcoords[][2], short moves, BMVert *v) +{ + float (*obmat)[4] = viewdata->vc.obact->obmat; + float co_world[3]; + int screen_co[2]; + + mul_v3_m4v3(co_world, obmat, v->co); + + /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */ + if (ED_view3d_project_int_global(viewdata->vc.ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) + return false; + + if (!BLI_lasso_is_point_inside(mcoords, moves, screen_co[0], screen_co[1], IS_CLIPPED)) + return false; + + if (hair_test_depth(viewdata, v->co, screen_co)) + return true; + else + return false; +} + +/* ------------------------------------------------------------------------- */ + +typedef void (*VertexToolCb)(HairToolData *data, void *userdata, BMVert *v, float factor); + +/* apply tool directly to each vertex inside the filter area */ +static int UNUSED_FUNCTION(hair_tool_apply_vertex)(HairToolData *data, VertexToolCb cb, void *userdata) +{ + Scene *scene = data->scene; + Brush *brush = data->settings->brush; + const float rad = BKE_brush_size_get(scene, brush); + const float radsq = rad*rad; + const float threshold = 0.0f; /* XXX could be useful, is it needed? */ + const bool use_mirror = hair_use_mirror_x(data->ob); + + BMVert *v, *v_mirr; + BMIter iter; + int tot = 0; + float dist, factor; + + if (use_mirror) + ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob)); + + BM_ITER_MESH(v, &iter, data->edit->bm, BM_VERTS_OF_MESH) { + if (!hair_test_vertex_inside_circle(&data->viewdata, data->mval, radsq, v, &dist)) + continue; + + factor = 1.0f - dist / rad; + if (factor > threshold) { + cb(data, userdata, v, factor); + ++tot; + + if (use_mirror) { + v_mirr = ED_strands_mirror_get(data->edit, v); + if (v_mirr) + cb(data, userdata, v_mirr, factor); + } + } + } + + /* apply mirror */ + if (use_mirror) + ED_strands_mirror_cache_end(data->edit); + + return tot; +} + +/* ------------------------------------------------------------------------- */ + +typedef void (*EdgeToolCb)(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float edge_param); + +static int hair_tool_apply_strand_edges(HairToolData *data, EdgeToolCb cb, void *userdata, BMVert *root) +{ + Scene *scene = data->scene; + Brush *brush = data->settings->brush; + const float rad = BKE_brush_size_get(scene, brush); + const float radsq = rad*rad; + const float threshold = 0.0f; /* XXX could be useful, is it needed? */ + const bool use_mirror = hair_use_mirror_x(data->ob); + + BMVert *v, *vprev, *v_mirr, *vprev_mirr; + BMIter iter; + int k; + int tot = 0; + + BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, k) { + if (k > 0) { + float dist, lambda; + + if (hair_test_edge_inside_circle(&data->viewdata, data->mval, radsq, vprev, v, &dist, &lambda)) { + float factor = 1.0f - dist / rad; + if (factor > threshold) { + cb(data, userdata, vprev, v, factor, lambda); + ++tot; + + if (use_mirror) { + v_mirr = ED_strands_mirror_get(data->edit, v); + if (vprev_mirr && v_mirr) + cb(data, userdata, vprev_mirr, v_mirr, factor, lambda); + } + } + } + } + + vprev = v; + vprev_mirr = v_mirr; + } + + return tot; +} + +/* apply tool to vertices of edges inside the filter area, + * using the closest edge point for weighting + */ +static int hair_tool_apply_edge(HairToolData *data, EdgeToolCb cb, void *userdata) +{ + BMVert *root; + BMIter iter; + int tot = 0; + + if (hair_use_mirror_x(data->ob)) + ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob)); + + BM_ITER_STRANDS(root, &iter, data->edit->bm, BM_STRANDS_OF_MESH) { + tot += hair_tool_apply_strand_edges(data, cb, userdata, root); + } + + /* apply mirror */ + if (hair_use_mirror_x(data->ob)) + ED_strands_mirror_cache_end(data->edit); + + return tot; +} + +/* ------------------------------------------------------------------------- */ + +typedef struct CombData { + float power; +} CombData; + +static void UNUSED_FUNCTION(hair_vertex_comb)(HairToolData *data, void *userdata, BMVert *v, float factor) +{ + CombData *combdata = userdata; + + float combfactor = powf(factor, combdata->power); + + madd_v3_v3fl(v->co, data->delta, combfactor); +} + +/* Edge-based combing tool: + * Unlike the vertex tool (which simply displaces vertices), the edge tool + * adjusts edge orientations to follow the stroke direction. + */ +static void hair_edge_comb(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float UNUSED(edge_param)) +{ + CombData *combdata = userdata; + float strokedir[3], edge[3], edgedir[3], strokelen, edgelen; + float edge_proj[3]; + + float combfactor = powf(factor, combdata->power); + float effect; + + strokelen = normalize_v3_v3(strokedir, data->delta); + + sub_v3_v3v3(edge, v2->co, v1->co); + edgelen = normalize_v3_v3(edgedir, edge); + if (edgelen == 0.0f) + return; + + /* This factor prevents sudden changes in direction with small stroke lengths. + * The arctan maps the 0..inf range of the length ratio to 0..1 smoothly. + */ + effect = atan(strokelen / edgelen * 4.0f / (0.5f*M_PI)); + + mul_v3_v3fl(edge_proj, strokedir, edgelen); + + interp_v3_v3v3(edge, edge, edge_proj, combfactor * effect); + + add_v3_v3v3(v2->co, v1->co, edge); +} + + +BLI_INLINE void construct_m4_loc_nor_tan(float mat[4][4], const float loc[3], const float nor[3], const float tang[3]) +{ + float cotang[3]; + + cross_v3_v3v3(cotang, nor, tang); + + copy_v3_v3(mat[0], tang); + copy_v3_v3(mat[1], cotang); + copy_v3_v3(mat[2], nor); + copy_v3_v3(mat[3], loc); + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +static void grow_hair(BMEditStrands *edit, MSurfaceSample *sample) +{ + DerivedMesh *dm = edit->root_dm; + const float len = 1.5f; + + float root_mat[4][4]; + BMVert *root, *v; + BMIter iter; + int i; + + { + float co[3], nor[3], tang[3]; + BKE_mesh_sample_eval(dm, sample, co, nor, tang); + construct_m4_loc_nor_tan(root_mat, co, nor, tang); + } + + root = BM_strands_create(edit->bm, 5, true); + + BM_elem_meshsample_data_named_set(&edit->bm->vdata, root, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, sample); + + BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, i) { + float co[3]; + + co[0] = co[1] = 0.0f; + co[2] = len * (float)i / (float)(len - 1); + + mul_m4_v3(root_mat, co); + + copy_v3_v3(v->co, co); + } + + BM_mesh_elem_index_ensure(edit->bm, BM_ALL); +} + +static bool hair_add_ray_cb(void *vdata, float ray_start[3], float ray_end[3]) +{ + HairToolData *data = vdata; + ViewContext *vc = &data->viewdata.vc; + + ED_view3d_win_to_segment(vc->ar, vc->v3d, data->mval, ray_start, ray_end, true); + + mul_m4_v3(data->imat, ray_start); + mul_m4_v3(data->imat, ray_end); + + return true; +} + +static bool hair_get_surface_sample(HairToolData *data, MSurfaceSample *sample) +{ + DerivedMesh *dm = data->edit->root_dm; + + MSurfaceSampleStorage dst; + int tot; + + BKE_mesh_sample_storage_single(&dst, sample); + tot = BKE_mesh_sample_generate_raycast(&dst, dm, hair_add_ray_cb, data, 1); + BKE_mesh_sample_storage_release(&dst); + + return tot > 0; +} + +static bool hair_add(HairToolData *data) +{ + MSurfaceSample sample; + + if (!hair_get_surface_sample(data, &sample)) + return false; + + grow_hair(data->edit, &sample); + + return true; +} + + +bool hair_brush_step(HairToolData *data) +{ + Brush *brush = data->settings->brush; + BrushHairTool hair_tool = brush->hair_tool; + BMEditStrands *edit = data->edit; + int tot = 0; + + switch (hair_tool) { + case HAIR_TOOL_COMB: { + CombData combdata; + combdata.power = (brush->alpha - 0.5f) * 2.0f; + if (combdata.power < 0.0f) + combdata.power = 1.0f - 9.0f * combdata.power; + else + combdata.power = 1.0f - combdata.power; + + /*tot = hair_tool_apply_vertex(data, hair_vertex_comb, &combdata);*/ /* UNUSED */ + tot = hair_tool_apply_edge(data, hair_edge_comb, &combdata); + break; + } + case HAIR_TOOL_CUT: + break; + case HAIR_TOOL_LENGTH: + break; + case HAIR_TOOL_PUFF: + break; + case HAIR_TOOL_ADD: + if (hair_add(data)) + edit->flag |= BM_STRANDS_DIRTY_SEGLEN; + break; + case HAIR_TOOL_SMOOTH: + break; + case HAIR_TOOL_WEIGHT: + break; + } + + return tot > 0; +} diff --git a/source/blender/editors/hair/hair_undo.c b/source/blender/editors/hair/hair_undo.c new file mode 100644 index 00000000000..a58b9c042f8 --- /dev/null +++ b/source/blender/editors/hair/hair_undo.c @@ -0,0 +1,190 @@ +/* + * ***** 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_undo.c + * \ingroup edhair + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_context.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_key.h" +#include "BKE_mesh.h" +#include "BKE_mesh_sample.h" + +#include "ED_physics.h" +#include "ED_util.h" + +#include "bmesh.h" + +#include "hair_intern.h" + +static void *strands_get_edit(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + const int mode_flag = OB_MODE_HAIR_EDIT; + const bool is_mode_set = ((obact->mode & mode_flag) != 0); + + if (obact && is_mode_set) { + BMEditStrands *edit = BKE_editstrands_from_object(obact); + return edit; + } + return NULL; +} + +typedef struct UndoStrands { + Mesh me; /* Mesh supports all the customdata we need, easiest way to implement undo storage */ + int selectmode; + + /** \note + * this isn't a prefect solution, if you edit keys and change shapes this works well (fixing [#32442]), + * but editing shape keys, going into object mode, removing or changing their order, + * then go back into editmode and undo will give issues - where the old index will be out of sync + * with the new object index. + * + * There are a few ways this could be made to work but for now its a known limitation with mixing + * object and editmode operations - Campbell */ + int shapenr; +} UndoStrands; + +/* undo simply makes copies of a bmesh */ +static void *strands_edit_to_undo(void *editv, void *UNUSED(obdata)) +{ + BMEditStrands *edit = editv; +// Mesh *obme = obdata; + + UndoStrands *undo = MEM_callocN(sizeof(UndoStrands), "undo Strands"); + + /* make sure shape keys work */ +// um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL; + + /* BM_mesh_validate(em->bm); */ /* for troubleshooting */ + + BM_mesh_bm_to_me_ex(edit->bm, &undo->me, CD_MASK_STRANDS, false); + + undo->selectmode = edit->bm->selectmode; + undo->shapenr = edit->bm->shapenr; + + return undo; +} + +static void strands_undo_to_edit(void *undov, void *editv, void *UNUSED(obdata)) +{ + UndoStrands *undo = undov; + BMEditStrands *edit = editv, *edit_tmp; + Object *ob = edit->ob; + DerivedMesh *dm = edit->root_dm; + BMesh *bm; +// Key *key = ((Mesh *) obdata)->key; + + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&undo->me); + + edit->bm->shapenr = undo->shapenr; + + bm = BM_mesh_create(&allocsize); + BM_mesh_bm_from_me_ex(bm, &undo->me, CD_MASK_STRANDS_BMESH, false, false, undo->shapenr); + + /* note: have to create the new edit before freeing the old one, + * because it owns the root_dm and we have to copy it before + * it gets released when freeing the old edit. + */ + edit_tmp = BKE_editstrands_create(bm, dm, NULL); + BKE_editstrands_free(edit); + *edit = *edit_tmp; + + bm->selectmode = undo->selectmode; + edit->ob = ob; + +#if 0 + /* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens + * if the active is a basis for any other. */ + if (key && (key->type == KEY_RELATIVE)) { + /* Since we can't add, remove or reorder keyblocks in editmode, it's safe to assume + * shapenr from restored bmesh and keyblock indices are in sync. */ + const int kb_act_idx = ob->shapenr - 1; + + /* If it is, let's patch the current mesh key block to its restored value. + * Else, the offsets won't be computed and it won't matter. */ + if (BKE_keyblock_is_basis(key, kb_act_idx)) { + KeyBlock *kb_act = BLI_findlink(&key->block, kb_act_idx); + + if (kb_act->totelem != undo->me.totvert) { + /* The current mesh has some extra/missing verts compared to the undo, adjust. */ + MEM_SAFE_FREE(kb_act->data); + kb_act->data = MEM_mallocN((size_t)(key->elemsize * bm->totvert), __func__); + kb_act->totelem = undo->me.totvert; + } + + BKE_keyblock_update_from_mesh(&undo->me, kb_act); + } + } +#endif + + ob->shapenr = undo->shapenr; + + MEM_freeN(edit_tmp); +} + +static void strands_free_undo(void *undov) +{ + UndoStrands *undo = undov; + + if (undo->me.key) { + BKE_key_free(undo->me.key); + MEM_freeN(undo->me.key); + } + + BKE_mesh_free(&undo->me, false); + MEM_freeN(undo); +} + +/* and this is all the undo system needs to know */ +void undo_push_strands(bContext *C, const char *name) +{ + /* edit->ob gets out of date and crashes on mesh undo, + * this is an easy way to ensure its OK + * though we could investigate the matter further. */ + Object *obact = CTX_data_active_object(C); + BMEditStrands *edit = BKE_editstrands_from_object(obact); + if (edit) { + edit->ob = obact; + + undo_editmode_push(C, name, CTX_data_active_object, strands_get_edit, strands_free_undo, strands_undo_to_edit, strands_edit_to_undo, NULL); + } +} |