Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/hair')
-rw-r--r--source/blender/editors/hair/CMakeLists.txt56
-rw-r--r--source/blender/editors/hair/SConscript54
-rw-r--r--source/blender/editors/hair/hair_cursor.c109
-rw-r--r--source/blender/editors/hair/hair_edit.c459
-rw-r--r--source/blender/editors/hair/hair_intern.h124
-rw-r--r--source/blender/editors/hair/hair_mirror.c210
-rw-r--r--source/blender/editors/hair/hair_object_cachelib.c104
-rw-r--r--source/blender/editors/hair/hair_object_particles.c101
-rw-r--r--source/blender/editors/hair/hair_ops.c143
-rw-r--r--source/blender/editors/hair/hair_select.c502
-rw-r--r--source/blender/editors/hair/hair_stroke.c498
-rw-r--r--source/blender/editors/hair/hair_undo.c190
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);
+ }
+}