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:
authorLukas Tönne <lukas.toenne@gmail.com>2016-12-28 19:30:58 +0300
committerLukas Tönne <lukas.toenne@gmail.com>2016-12-28 19:30:58 +0300
commit6ecab6dd8e48d564a2b43e0e81e79d079e8b4c77 (patch)
tree618e2d24eb34a05a81f726dd52eb2b7468e9296d /source/blender/editors/physics
parent605263177b8eea24c1449e4dbf0138175ec3dddf (diff)
Revert particle system and point cache removal in blender2.8 branch.
This reverts commit 5aa19be91263a249ffae75573e3b32f24269d890 and b4a721af694817fa921b119df83d33ede7d7fed0. Due to postponement of particle system rewrite it was decided to put particle code back into the 2.8 branch for the time being.
Diffstat (limited to 'source/blender/editors/physics')
-rw-r--r--source/blender/editors/physics/CMakeLists.txt4
-rw-r--r--source/blender/editors/physics/particle_boids.c371
-rw-r--r--source/blender/editors/physics/particle_edit.c4923
-rw-r--r--source/blender/editors/physics/particle_object.c1244
-rw-r--r--source/blender/editors/physics/physics_intern.h67
-rw-r--r--source/blender/editors/physics/physics_ops.c152
-rw-r--r--source/blender/editors/physics/physics_pointcache.c469
7 files changed, 7227 insertions, 3 deletions
diff --git a/source/blender/editors/physics/CMakeLists.txt b/source/blender/editors/physics/CMakeLists.txt
index e4513c14413..898422dac51 100644
--- a/source/blender/editors/physics/CMakeLists.txt
+++ b/source/blender/editors/physics/CMakeLists.txt
@@ -38,8 +38,12 @@ set(INC_SYS
set(SRC
dynamicpaint_ops.c
+ particle_boids.c
+ particle_edit.c
+ particle_object.c
physics_fluid.c
physics_ops.c
+ physics_pointcache.c
rigidbody_constraint.c
rigidbody_object.c
rigidbody_world.c
diff --git a/source/blender/editors/physics/particle_boids.c b/source/blender/editors/physics/particle_boids.c
new file mode 100644
index 00000000000..14b12497c4a
--- /dev/null
+++ b/source/blender/editors/physics/particle_boids.c
@@ -0,0 +1,371 @@
+/*
+ * ***** 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) 2009 Janne Karhu.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/physics/particle_boids.c
+ * \ingroup edphys
+ */
+
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_particle_types.h"
+
+#include "BLI_listbase.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_boids.h"
+#include "BKE_context.h"
+#include "BKE_depsgraph.h"
+#include "BKE_main.h"
+#include "BKE_particle.h"
+
+#include "RNA_access.h"
+#include "RNA_enum_types.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "physics_intern.h"
+
+/************************ add/del boid rule operators *********************/
+static int rule_add_exec(bContext *C, wmOperator *op)
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ int type= RNA_enum_get(op->ptr, "type");
+
+ BoidRule *rule;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ state = boid_get_current_state(part->boids);
+
+ for (rule=state->rules.first; rule; rule=rule->next)
+ rule->flag &= ~BOIDRULE_CURRENT;
+
+ rule = boid_new_rule(type);
+ rule->flag |= BOIDRULE_CURRENT;
+
+ BLI_addtail(&state->rules, rule);
+
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_rule_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Boid Rule";
+ ot->description = "Add a boid rule to the current boid state";
+ ot->idname = "BOID_OT_rule_add";
+
+ /* api callbacks */
+ ot->invoke = WM_menu_invoke;
+ ot->exec = rule_add_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_boidrule_type_items, 0, "Type", "");
+}
+static int rule_del_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidRule *rule;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ state = boid_get_current_state(part->boids);
+
+ for (rule=state->rules.first; rule; rule=rule->next) {
+ if (rule->flag & BOIDRULE_CURRENT) {
+ BLI_remlink(&state->rules, rule);
+ MEM_freeN(rule);
+ break;
+ }
+ }
+ rule = state->rules.first;
+
+ if (rule)
+ rule->flag |= BOIDRULE_CURRENT;
+
+ DAG_relations_tag_update(bmain);
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_rule_del(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Boid Rule";
+ ot->idname = "BOID_OT_rule_del";
+ ot->description = "Delete current boid rule";
+
+ /* api callbacks */
+ ot->exec = rule_del_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move up/down boid rule operators *********************/
+static int rule_move_up_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidRule *rule;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ state = boid_get_current_state(part->boids);
+ for (rule = state->rules.first; rule; rule=rule->next) {
+ if (rule->flag & BOIDRULE_CURRENT && rule->prev) {
+ BLI_remlink(&state->rules, rule);
+ BLI_insertlinkbefore(&state->rules, rule->prev, rule);
+
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_rule_move_up(wmOperatorType *ot)
+{
+ ot->name = "Move Up Boid Rule";
+ ot->description = "Move boid rule up in the list";
+ ot->idname = "BOID_OT_rule_move_up";
+
+ ot->exec = rule_move_up_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int rule_move_down_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidRule *rule;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ state = boid_get_current_state(part->boids);
+ for (rule = state->rules.first; rule; rule=rule->next) {
+ if (rule->flag & BOIDRULE_CURRENT && rule->next) {
+ BLI_remlink(&state->rules, rule);
+ BLI_insertlinkafter(&state->rules, rule->next, rule);
+
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_rule_move_down(wmOperatorType *ot)
+{
+ ot->name = "Move Down Boid Rule";
+ ot->description = "Move boid rule down in the list";
+ ot->idname = "BOID_OT_rule_move_down";
+
+ ot->exec = rule_move_down_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+
+/************************ add/del boid state operators *********************/
+static int state_add_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ for (state=part->boids->states.first; state; state=state->next)
+ state->flag &= ~BOIDSTATE_CURRENT;
+
+ state = boid_new_state(part->boids);
+ state->flag |= BOIDSTATE_CURRENT;
+
+ BLI_addtail(&part->boids->states, state);
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_state_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Boid State";
+ ot->description = "Add a boid state to the particle system";
+ ot->idname = "BOID_OT_state_add";
+
+ /* api callbacks */
+ ot->exec = state_add_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+static int state_del_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ for (state=part->boids->states.first; state; state=state->next) {
+ if (state->flag & BOIDSTATE_CURRENT) {
+ BLI_remlink(&part->boids->states, state);
+ MEM_freeN(state);
+ break;
+ }
+ }
+
+ /* there must be at least one state */
+ if (!part->boids->states.first) {
+ state = boid_new_state(part->boids);
+ BLI_addtail(&part->boids->states, state);
+ }
+ else
+ state = part->boids->states.first;
+
+ state->flag |= BOIDSTATE_CURRENT;
+
+ DAG_relations_tag_update(bmain);
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_state_del(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Boid State";
+ ot->idname = "BOID_OT_state_del";
+ ot->description = "Delete current boid state";
+
+ /* api callbacks */
+ ot->exec = state_del_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move up/down boid state operators *********************/
+static int state_move_up_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidSettings *boids;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ boids = part->boids;
+
+ for (state = boids->states.first; state; state=state->next) {
+ if (state->flag & BOIDSTATE_CURRENT && state->prev) {
+ BLI_remlink(&boids->states, state);
+ BLI_insertlinkbefore(&boids->states, state->prev, state);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_state_move_up(wmOperatorType *ot)
+{
+ ot->name = "Move Up Boid State";
+ ot->description = "Move boid state up in the list";
+ ot->idname = "BOID_OT_state_move_up";
+
+ ot->exec = state_move_up_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int state_move_down_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_settings", &RNA_ParticleSettings);
+ ParticleSettings *part = ptr.data;
+ BoidSettings *boids;
+ BoidState *state;
+
+ if (!part || part->phystype != PART_PHYS_BOIDS)
+ return OPERATOR_CANCELLED;
+
+ boids = part->boids;
+
+ for (state = boids->states.first; state; state=state->next) {
+ if (state->flag & BOIDSTATE_CURRENT && state->next) {
+ BLI_remlink(&boids->states, state);
+ BLI_insertlinkafter(&boids->states, state->next, state);
+ DAG_id_tag_update(&part->id, OB_RECALC_DATA|PSYS_RECALC_RESET);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void BOID_OT_state_move_down(wmOperatorType *ot)
+{
+ ot->name = "Move Down Boid State";
+ ot->description = "Move boid state down in the list";
+ ot->idname = "BOID_OT_state_move_down";
+
+ ot->exec = state_move_down_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c
new file mode 100644
index 00000000000..e22a145b3a6
--- /dev/null
+++ b/source/blender/editors/physics/particle_edit.c
@@ -0,0 +1,4923 @@
+/*
+ * ***** 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) 2007 by Janne Karhu.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/physics/particle_edit.c
+ * \ingroup edphys
+ */
+
+
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_scene_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+
+#include "BLI_math.h"
+#include "BLI_lasso.h"
+#include "BLI_listbase.h"
+#include "BLI_string.h"
+#include "BLI_kdtree.h"
+#include "BLI_rand.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_context.h"
+#include "BKE_depsgraph.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_global.h"
+#include "BKE_object.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_particle.h"
+#include "BKE_report.h"
+#include "BKE_bvhutils.h"
+#include "BKE_pointcache.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "ED_object.h"
+#include "ED_physics.h"
+#include "ED_mesh.h"
+#include "ED_particle.h"
+#include "ED_view3d.h"
+
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "physics_intern.h"
+
+void PE_create_particle_edit(Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys);
+void PTCacheUndo_clear(PTCacheEdit *edit);
+void recalc_lengths(PTCacheEdit *edit);
+void recalc_emitter_field(Object *ob, ParticleSystem *psys);
+void update_world_cos(Object *ob, PTCacheEdit *edit);
+
+#define KEY_K PTCacheEditKey *key; int k
+#define POINT_P PTCacheEditPoint *point; int p
+#define LOOP_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++)
+#define LOOP_VISIBLE_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (!(point->flag & PEP_HIDE))
+#define LOOP_SELECTED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point_is_selected(point))
+#define LOOP_UNSELECTED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (!point_is_selected(point))
+#define LOOP_EDITED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point->flag & PEP_EDIT_RECALC)
+#define LOOP_TAGGED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point->flag & PEP_TAG)
+#define LOOP_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++)
+#define LOOP_VISIBLE_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if (!(key->flag & PEK_HIDE))
+#define LOOP_SELECTED_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if ((key->flag & PEK_SELECT) && !(key->flag & PEK_HIDE))
+#define LOOP_TAGGED_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if (key->flag & PEK_TAG)
+
+#define KEY_WCO ((key->flag & PEK_USE_WCO) ? key->world_co : key->co)
+
+/**************************** utilities *******************************/
+
+int PE_poll(bContext *C)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+
+ if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
+ return 0;
+
+ return (PE_get_current(scene, ob) != NULL);
+}
+
+int PE_hair_poll(bContext *C)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit;
+
+ if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
+ return 0;
+
+ edit= PE_get_current(scene, ob);
+
+ return (edit && edit->psys);
+}
+
+int PE_poll_view3d(bContext *C)
+{
+ ScrArea *sa = CTX_wm_area(C);
+ ARegion *ar = CTX_wm_region(C);
+
+ return (PE_poll(C) &&
+ (sa && sa->spacetype == SPACE_VIEW3D) &&
+ (ar && ar->regiontype == RGN_TYPE_WINDOW));
+}
+
+void PE_free_ptcache_edit(PTCacheEdit *edit)
+{
+ POINT_P;
+
+ if (edit==0) return;
+
+ PTCacheUndo_clear(edit);
+
+ if (edit->points) {
+ LOOP_POINTS {
+ if (point->keys)
+ MEM_freeN(point->keys);
+ }
+
+ MEM_freeN(edit->points);
+ }
+
+ if (edit->mirror_cache)
+ MEM_freeN(edit->mirror_cache);
+
+ if (edit->emitter_cosnos) {
+ MEM_freeN(edit->emitter_cosnos);
+ edit->emitter_cosnos= 0;
+ }
+
+ if (edit->emitter_field) {
+ BLI_kdtree_free(edit->emitter_field);
+ edit->emitter_field= 0;
+ }
+
+ psys_free_path_cache(edit->psys, edit);
+
+ MEM_freeN(edit);
+}
+
+/************************************************/
+/* Edit Mode Helpers */
+/************************************************/
+
+int PE_start_edit(PTCacheEdit *edit)
+{
+ if (edit) {
+ edit->edited = 1;
+ if (edit->psys)
+ edit->psys->flag |= PSYS_EDITED;
+ return 1;
+ }
+
+ return 0;
+}
+
+ParticleEditSettings *PE_settings(Scene *scene)
+{
+ return scene->toolsettings ? &scene->toolsettings->particle : NULL;
+}
+
+static float pe_brush_size_get(const Scene *UNUSED(scene), ParticleBrushData *brush)
+{
+ // here we can enable unified brush size, needs more work...
+ // UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
+ // float size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
+
+ return brush->size * U.pixelsize;
+}
+
+
+/* always gets at least the first particlesystem even if PSYS_CURRENT flag is not set
+ *
+ * note: this function runs on poll, therefor it can runs many times a second
+ * keep it fast! */
+static PTCacheEdit *pe_get_current(Scene *scene, Object *ob, int create)
+{
+ ParticleEditSettings *pset= PE_settings(scene);
+ PTCacheEdit *edit = NULL;
+ ListBase pidlist;
+ PTCacheID *pid;
+
+ if (pset==NULL || ob==NULL)
+ return NULL;
+
+ pset->scene = scene;
+ pset->object = ob;
+
+ BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
+
+ /* in the case of only one editable thing, set pset->edittype accordingly */
+ if (BLI_listbase_is_single(&pidlist)) {
+ pid = pidlist.first;
+ switch (pid->type) {
+ case PTCACHE_TYPE_PARTICLES:
+ pset->edittype = PE_TYPE_PARTICLES;
+ break;
+ case PTCACHE_TYPE_SOFTBODY:
+ pset->edittype = PE_TYPE_SOFTBODY;
+ break;
+ case PTCACHE_TYPE_CLOTH:
+ pset->edittype = PE_TYPE_CLOTH;
+ break;
+ }
+ }
+
+ for (pid=pidlist.first; pid; pid=pid->next) {
+ if (pset->edittype == PE_TYPE_PARTICLES && pid->type == PTCACHE_TYPE_PARTICLES) {
+ ParticleSystem *psys = pid->calldata;
+
+ if (psys->flag & PSYS_CURRENT) {
+ if (psys->part && psys->part->type == PART_HAIR) {
+ if (psys->flag & PSYS_HAIR_DYNAMICS && psys->pointcache->flag & PTCACHE_BAKED) {
+ if (create && !psys->pointcache->edit)
+ PE_create_particle_edit(scene, ob, pid->cache, NULL);
+ edit = pid->cache->edit;
+ }
+ else {
+ if (create && !psys->edit && psys->flag & PSYS_HAIR_DONE)
+ PE_create_particle_edit(scene, ob, NULL, psys);
+ edit = psys->edit;
+ }
+ }
+ else {
+ if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit)
+ PE_create_particle_edit(scene, ob, pid->cache, psys);
+ edit = pid->cache->edit;
+ }
+
+ break;
+ }
+ }
+ else if (pset->edittype == PE_TYPE_SOFTBODY && pid->type == PTCACHE_TYPE_SOFTBODY) {
+ if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
+ pset->flag |= PE_FADE_TIME;
+ // NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
+ PE_create_particle_edit(scene, ob, pid->cache, NULL);
+ }
+ edit = pid->cache->edit;
+ break;
+ }
+ else if (pset->edittype == PE_TYPE_CLOTH && pid->type == PTCACHE_TYPE_CLOTH) {
+ if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
+ pset->flag |= PE_FADE_TIME;
+ // NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
+ PE_create_particle_edit(scene, ob, pid->cache, NULL);
+ }
+ edit = pid->cache->edit;
+ break;
+ }
+ }
+
+ if (edit)
+ edit->pid = *pid;
+
+ BLI_freelistN(&pidlist);
+
+ return edit;
+}
+
+PTCacheEdit *PE_get_current(Scene *scene, Object *ob)
+{
+ return pe_get_current(scene, ob, 0);
+}
+
+PTCacheEdit *PE_create_current(Scene *scene, Object *ob)
+{
+ return pe_get_current(scene, ob, 1);
+}
+
+void PE_current_changed(Scene *scene, Object *ob)
+{
+ if (ob->mode == OB_MODE_PARTICLE_EDIT)
+ PE_create_current(scene, ob);
+}
+
+void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
+{
+ ParticleEditSettings *pset=PE_settings(scene);
+ POINT_P; KEY_K;
+
+
+ if (pset->flag & PE_FADE_TIME && pset->selectmode==SCE_SELECT_POINT) {
+ LOOP_POINTS {
+ LOOP_KEYS {
+ if (fabsf(cfra - *key->time) < pset->fade_frames)
+ key->flag &= ~PEK_HIDE;
+ else {
+ key->flag |= PEK_HIDE;
+ //key->flag &= ~PEK_SELECT;
+ }
+ }
+ }
+ }
+ else {
+ LOOP_POINTS {
+ LOOP_KEYS {
+ key->flag &= ~PEK_HIDE;
+ }
+ }
+ }
+}
+
+static int pe_x_mirror(Object *ob)
+{
+ if (ob->type == OB_MESH)
+ return (((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X);
+
+ return 0;
+}
+
+/****************** common struct passed to callbacks ******************/
+
+typedef struct PEData {
+ ViewContext vc;
+ bglMats mats;
+
+ Scene *scene;
+ Object *ob;
+ DerivedMesh *dm;
+ PTCacheEdit *edit;
+ BVHTreeFromMesh shape_bvh;
+
+ const int *mval;
+ rcti *rect;
+ float rad;
+ float dist;
+ float dval;
+ int select;
+
+ float *dvec;
+ float combfac;
+ float pufffac;
+ float cutfac;
+ float smoothfac;
+ float weightfac;
+ float growfac;
+ int totrekey;
+
+ int invert;
+ int tot;
+ float vec[3];
+
+ int select_action;
+ int select_toggle_action;
+} PEData;
+
+static void PE_set_data(bContext *C, PEData *data)
+{
+ memset(data, 0, sizeof(*data));
+
+ data->scene= CTX_data_scene(C);
+ data->ob= CTX_data_active_object(C);
+ data->edit= PE_get_current(data->scene, data->ob);
+}
+
+static void PE_set_view3d_data(bContext *C, PEData *data)
+{
+ PE_set_data(C, data);
+
+ view3d_set_viewcontext(C, &data->vc);
+ /* note, the object argument means the modelview matrix does not account for the objects matrix, use viewmat rather than (obmat * viewmat) */
+ view3d_get_transformation(data->vc.ar, data->vc.rv3d, NULL, &data->mats);
+
+ if (V3D_IS_ZBUF(data->vc.v3d)) {
+ if (data->vc.v3d->flag & V3D_INVALID_BACKBUF) {
+ /* needed or else the draw matrix can be incorrect */
+ view3d_operator_needs_opengl(C);
+
+ ED_view3d_backbuf_validate(&data->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(data->vc.ar);
+ }
+ }
+}
+
+static bool PE_create_shape_tree(PEData *data, Object *shapeob)
+{
+ DerivedMesh *dm = shapeob->derivedFinal;
+
+ memset(&data->shape_bvh, 0, sizeof(data->shape_bvh));
+
+ if (!dm) {
+ return false;
+ }
+
+ DM_ensure_looptri(dm);
+ return (bvhtree_from_mesh_looptri(&data->shape_bvh, dm, 0.0f, 4, 8) != NULL);
+}
+
+static void PE_free_shape_tree(PEData *data)
+{
+ free_bvhtree_from_mesh(&data->shape_bvh);
+}
+
+/*************************** selection utilities *******************************/
+
+static bool key_test_depth(PEData *data, const float co[3], const int screen_co[2])
+{
+ View3D *v3d= data->vc.v3d;
+ ViewDepths *vd = data->vc.rv3d->depths;
+ double ux, uy, uz;
+ float depth;
+
+ /* nothing to do */
+ if (!V3D_IS_ZBUF(v3d))
+ return true;
+
+ /* used to calculate here but all callers have the screen_co already, so pass as arg */
+#if 0
+ if (ED_view3d_project_int_global(data->vc.ar, co, screen_co,
+ V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK)
+ {
+ return 0;
+ }
+#endif
+
+ gluProject(co[0], co[1], co[2], data->mats.modelview, data->mats.projection,
+ (GLint *)data->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]];
+ }
+ else
+ return 0;
+
+ if ((float)uz - 0.00001f > depth)
+ return 0;
+ else
+ return 1;
+}
+
+static bool key_inside_circle(PEData *data, float rad, const float co[3], float *distance)
+{
+ float dx, dy, dist;
+ int screen_co[2];
+
+ /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
+ if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
+ return 0;
+ }
+
+ dx= data->mval[0] - screen_co[0];
+ dy= data->mval[1] - screen_co[1];
+ dist = sqrtf(dx * dx + dy * dy);
+
+ if (dist > rad)
+ return 0;
+
+ if (key_test_depth(data, co, screen_co)) {
+ if (distance)
+ *distance=dist;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static bool key_inside_rect(PEData *data, const float co[3])
+{
+ int screen_co[2];
+
+ if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
+ return 0;
+ }
+
+ if (screen_co[0] > data->rect->xmin && screen_co[0] < data->rect->xmax &&
+ screen_co[1] > data->rect->ymin && screen_co[1] < data->rect->ymax)
+ {
+ return key_test_depth(data, co, screen_co);
+ }
+
+ return 0;
+}
+
+static bool key_inside_test(PEData *data, const float co[3])
+{
+ if (data->mval)
+ return key_inside_circle(data, data->rad, co, NULL);
+ else
+ return key_inside_rect(data, co);
+}
+
+static bool point_is_selected(PTCacheEditPoint *point)
+{
+ KEY_K;
+
+ if (point->flag & PEP_HIDE)
+ return 0;
+
+ LOOP_SELECTED_KEYS {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************** iterators *******************************/
+
+typedef void (*ForPointFunc)(PEData *data, int point_index);
+typedef void (*ForKeyFunc)(PEData *data, int point_index, int key_index);
+typedef void (*ForKeyMatFunc)(PEData *data, float mat[4][4], float imat[4][4], int point_index, int key_index, PTCacheEditKey *key);
+
+static void for_mouse_hit_keys(PEData *data, ForKeyFunc func, int nearest)
+{
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ PTCacheEdit *edit= data->edit;
+ POINT_P; KEY_K;
+ int nearest_point, nearest_key;
+ float dist= data->rad;
+
+ /* in path select mode we have no keys */
+ if (pset->selectmode==SCE_SELECT_PATH)
+ return;
+
+ nearest_point= -1;
+ nearest_key= -1;
+
+ LOOP_VISIBLE_POINTS {
+ if (pset->selectmode == SCE_SELECT_END) {
+ if (point->totkey) {
+ /* only do end keys */
+ key= point->keys + point->totkey-1;
+
+ if (nearest) {
+ if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
+ nearest_point= p;
+ nearest_key= point->totkey-1;
+ }
+ }
+ else if (key_inside_test(data, KEY_WCO))
+ func(data, p, point->totkey-1);
+ }
+ }
+ else {
+ /* do all keys */
+ LOOP_VISIBLE_KEYS {
+ if (nearest) {
+ if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
+ nearest_point= p;
+ nearest_key= k;
+ }
+ }
+ else if (key_inside_test(data, KEY_WCO))
+ func(data, p, k);
+ }
+ }
+ }
+
+ /* do nearest only */
+ if (nearest && nearest_point > -1)
+ func(data, nearest_point, nearest_key);
+}
+
+static void foreach_mouse_hit_point(PEData *data, ForPointFunc func, int selected)
+{
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ PTCacheEdit *edit= data->edit;
+ POINT_P; KEY_K;
+
+ /* all is selected in path mode */
+ if (pset->selectmode==SCE_SELECT_PATH)
+ selected=0;
+
+ LOOP_VISIBLE_POINTS {
+ if (pset->selectmode==SCE_SELECT_END) {
+ if (point->totkey) {
+ /* only do end keys */
+ key= point->keys + point->totkey - 1;
+
+ if (selected==0 || key->flag & PEK_SELECT)
+ if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist))
+ func(data, p);
+ }
+ }
+ else {
+ /* do all keys */
+ LOOP_VISIBLE_KEYS {
+ if (selected==0 || key->flag & PEK_SELECT) {
+ if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) {
+ func(data, p);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void foreach_mouse_hit_key(PEData *data, ForKeyMatFunc func, int selected)
+{
+ PTCacheEdit *edit = data->edit;
+ ParticleSystem *psys = edit->psys;
+ ParticleSystemModifierData *psmd = NULL;
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ POINT_P; KEY_K;
+ float mat[4][4], imat[4][4];
+
+ unit_m4(mat);
+ unit_m4(imat);
+
+ if (edit->psys)
+ psmd= psys_get_modifier(data->ob, edit->psys);
+
+ /* all is selected in path mode */
+ if (pset->selectmode==SCE_SELECT_PATH)
+ selected= 0;
+
+ LOOP_VISIBLE_POINTS {
+ if (pset->selectmode==SCE_SELECT_END) {
+ if (point->totkey) {
+ /* only do end keys */
+ key= point->keys + point->totkey-1;
+
+ if (selected==0 || key->flag & PEK_SELECT) {
+ if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) {
+ if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
+ psys_mat_hair_to_global(data->ob, psmd->dm_final, psys->part->from, psys->particles + p, mat);
+ invert_m4_m4(imat, mat);
+ }
+
+ func(data, mat, imat, p, point->totkey-1, key);
+ }
+ }
+ }
+ }
+ else {
+ /* do all keys */
+ LOOP_VISIBLE_KEYS {
+ if (selected==0 || key->flag & PEK_SELECT) {
+ if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) {
+ if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
+ psys_mat_hair_to_global(data->ob, psmd->dm_final, psys->part->from, psys->particles + p, mat);
+ invert_m4_m4(imat, mat);
+ }
+
+ func(data, mat, imat, p, k, key);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void foreach_selected_point(PEData *data, ForPointFunc func)
+{
+ PTCacheEdit *edit = data->edit;
+ POINT_P;
+
+ LOOP_SELECTED_POINTS {
+ func(data, p);
+ }
+}
+
+static void foreach_selected_key(PEData *data, ForKeyFunc func)
+{
+ PTCacheEdit *edit = data->edit;
+ POINT_P; KEY_K;
+
+ LOOP_VISIBLE_POINTS {
+ LOOP_SELECTED_KEYS {
+ func(data, p, k);
+ }
+ }
+}
+
+static void foreach_point(PEData *data, ForPointFunc func)
+{
+ PTCacheEdit *edit = data->edit;
+ POINT_P;
+
+ LOOP_POINTS {
+ func(data, p);
+ }
+}
+
+static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
+{
+ ParticleEditSettings *pset= PE_settings(scene);
+ POINT_P; KEY_K;
+ int sel= 0;
+
+ LOOP_VISIBLE_POINTS {
+ if (pset->selectmode==SCE_SELECT_POINT) {
+ LOOP_SELECTED_KEYS {
+ sel++;
+ }
+ }
+ else if (pset->selectmode==SCE_SELECT_END) {
+ if (point->totkey) {
+ key = point->keys + point->totkey - 1;
+ if (key->flag & PEK_SELECT)
+ sel++;
+ }
+ }
+ }
+
+ return sel;
+}
+
+/************************************************/
+/* Particle Edit Mirroring */
+/************************************************/
+
+static void PE_update_mirror_cache(Object *ob, ParticleSystem *psys)
+{
+ PTCacheEdit *edit;
+ ParticleSystemModifierData *psmd;
+ KDTree *tree;
+ KDTreeNearest nearest;
+ HairKey *key;
+ PARTICLE_P;
+ float mat[4][4], co[3];
+ int index, totpart;
+
+ edit= psys->edit;
+ psmd= psys_get_modifier(ob, psys);
+ totpart= psys->totpart;
+
+ if (!psmd->dm_final)
+ return;
+
+ tree= BLI_kdtree_new(totpart);
+
+ /* insert particles into kd tree */
+ LOOP_PARTICLES {
+ key = pa->hair;
+ psys_mat_hair_to_orco(ob, psmd->dm_final, psys->part->from, pa, mat);
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ BLI_kdtree_insert(tree, p, co);
+ }
+
+ BLI_kdtree_balance(tree);
+
+ /* lookup particles and set in mirror cache */
+ if (!edit->mirror_cache)
+ edit->mirror_cache= MEM_callocN(sizeof(int)*totpart, "PE mirror cache");
+
+ LOOP_PARTICLES {
+ key = pa->hair;
+ psys_mat_hair_to_orco(ob, psmd->dm_final, psys->part->from, pa, mat);
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ co[0] = -co[0];
+
+ index= BLI_kdtree_find_nearest(tree, co, &nearest);
+
+ /* this needs a custom threshold still, duplicated for editmode mirror */
+ if (index != -1 && index != p && (nearest.dist <= 0.0002f))
+ edit->mirror_cache[p] = index;
+ else
+ edit->mirror_cache[p] = -1;
+ }
+
+ /* make sure mirrors are in two directions */
+ LOOP_PARTICLES {
+ if (edit->mirror_cache[p]) {
+ index= edit->mirror_cache[p];
+ if (edit->mirror_cache[index] != p)
+ edit->mirror_cache[p] = -1;
+ }
+ }
+
+ BLI_kdtree_free(tree);
+}
+
+static void PE_mirror_particle(Object *ob, DerivedMesh *dm, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
+{
+ HairKey *hkey, *mhkey;
+ PTCacheEditPoint *point, *mpoint;
+ PTCacheEditKey *key, *mkey;
+ PTCacheEdit *edit;
+ float mat[4][4], mmat[4][4], immat[4][4];
+ int i, mi, k;
+
+ edit= psys->edit;
+ i= pa - psys->particles;
+
+ /* find mirrored particle if needed */
+ if (!mpa) {
+ if (!edit->mirror_cache)
+ PE_update_mirror_cache(ob, psys);
+
+ if (!edit->mirror_cache)
+ return; /* something went wrong! */
+
+ mi= edit->mirror_cache[i];
+ if (mi == -1)
+ return;
+ mpa= psys->particles + mi;
+ }
+ else
+ mi= mpa - psys->particles;
+
+ point = edit->points + i;
+ mpoint = edit->points + mi;
+
+ /* make sure they have the same amount of keys */
+ if (pa->totkey != mpa->totkey) {
+ if (mpa->hair) MEM_freeN(mpa->hair);
+ if (mpoint->keys) MEM_freeN(mpoint->keys);
+
+ mpa->hair= MEM_dupallocN(pa->hair);
+ mpa->totkey= pa->totkey;
+ mpoint->keys= MEM_dupallocN(point->keys);
+ mpoint->totkey= point->totkey;
+
+ mhkey= mpa->hair;
+ mkey= mpoint->keys;
+ for (k=0; k<mpa->totkey; k++, mkey++, mhkey++) {
+ mkey->co= mhkey->co;
+ mkey->time= &mhkey->time;
+ mkey->flag &= ~PEK_SELECT;
+ }
+ }
+
+ /* mirror positions and tags */
+ psys_mat_hair_to_orco(ob, dm, psys->part->from, pa, mat);
+ psys_mat_hair_to_orco(ob, dm, psys->part->from, mpa, mmat);
+ invert_m4_m4(immat, mmat);
+
+ hkey=pa->hair;
+ mhkey=mpa->hair;
+ key= point->keys;
+ mkey= mpoint->keys;
+ for (k=0; k<pa->totkey; k++, hkey++, mhkey++, key++, mkey++) {
+ copy_v3_v3(mhkey->co, hkey->co);
+ mul_m4_v3(mat, mhkey->co);
+ mhkey->co[0] = -mhkey->co[0];
+ mul_m4_v3(immat, mhkey->co);
+
+ if (key->flag & PEK_TAG)
+ mkey->flag |= PEK_TAG;
+
+ mkey->length = key->length;
+ }
+
+ if (point->flag & PEP_TAG)
+ mpoint->flag |= PEP_TAG;
+ if (point->flag & PEP_EDIT_RECALC)
+ mpoint->flag |= PEP_EDIT_RECALC;
+}
+
+static void PE_apply_mirror(Object *ob, ParticleSystem *psys)
+{
+ PTCacheEdit *edit;
+ ParticleSystemModifierData *psmd;
+ POINT_P;
+
+ if (!psys)
+ return;
+
+ edit= psys->edit;
+ psmd= psys_get_modifier(ob, psys);
+
+ if (!psmd->dm_final)
+ return;
+
+ if (!edit->mirror_cache)
+ PE_update_mirror_cache(ob, psys);
+
+ if (!edit->mirror_cache)
+ return; /* something went wrong */
+
+ /* we delay settings the PARS_EDIT_RECALC for mirrored particles
+ * to avoid doing mirror twice */
+ LOOP_POINTS {
+ if (point->flag & PEP_EDIT_RECALC) {
+ PE_mirror_particle(ob, psmd->dm_final, psys, psys->particles + p, NULL);
+
+ if (edit->mirror_cache[p] != -1)
+ edit->points[edit->mirror_cache[p]].flag &= ~PEP_EDIT_RECALC;
+ }
+ }
+
+ LOOP_POINTS {
+ if (point->flag & PEP_EDIT_RECALC)
+ if (edit->mirror_cache[p] != -1)
+ edit->points[edit->mirror_cache[p]].flag |= PEP_EDIT_RECALC;
+ }
+}
+
+/************************************************/
+/* Edit Calculation */
+/************************************************/
+/* tries to stop edited particles from going through the emitter's surface */
+static void pe_deflect_emitter(Scene *scene, Object *ob, PTCacheEdit *edit)
+{
+ ParticleEditSettings *pset= PE_settings(scene);
+ ParticleSystem *psys;
+ ParticleSystemModifierData *psmd;
+ POINT_P; KEY_K;
+ int index;
+ float *vec, *nor, dvec[3], dot, dist_1st=0.0f;
+ float hairimat[4][4], hairmat[4][4];
+ const float dist = ED_view3d_select_dist_px() * 0.01f;
+
+ if (edit==NULL || edit->psys==NULL || (pset->flag & PE_DEFLECT_EMITTER)==0 || (edit->psys->flag & PSYS_GLOBAL_HAIR))
+ return;
+
+ psys = edit->psys;
+ psmd = psys_get_modifier(ob, psys);
+
+ if (!psmd->dm_final)
+ return;
+
+ LOOP_EDITED_POINTS {
+ psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles + p, hairmat);
+
+ LOOP_KEYS {
+ mul_m4_v3(hairmat, key->co);
+ }
+
+ LOOP_KEYS {
+ if (k==0) {
+ dist_1st = len_v3v3((key+1)->co, key->co);
+ dist_1st *= dist * pset->emitterdist;
+ }
+ else {
+ index= BLI_kdtree_find_nearest(edit->emitter_field, key->co, NULL);
+
+ vec=edit->emitter_cosnos +index*6;
+ nor=vec+3;
+
+ sub_v3_v3v3(dvec, key->co, vec);
+
+ dot=dot_v3v3(dvec, nor);
+ copy_v3_v3(dvec, nor);
+
+ if (dot>0.0f) {
+ if (dot<dist_1st) {
+ normalize_v3(dvec);
+ mul_v3_fl(dvec, dist_1st-dot);
+ add_v3_v3(key->co, dvec);
+ }
+ }
+ else {
+ normalize_v3(dvec);
+ mul_v3_fl(dvec, dist_1st-dot);
+ add_v3_v3(key->co, dvec);
+ }
+ if (k==1)
+ dist_1st*=1.3333f;
+ }
+ }
+
+ invert_m4_m4(hairimat, hairmat);
+
+ LOOP_KEYS {
+ mul_m4_v3(hairimat, key->co);
+ }
+ }
+}
+/* force set distances between neighboring keys */
+static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit)
+{
+
+ ParticleEditSettings *pset=PE_settings(scene);
+ POINT_P; KEY_K;
+ float dv1[3];
+
+ if (edit==0 || (pset->flag & PE_KEEP_LENGTHS)==0)
+ return;
+
+ if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
+ return;
+
+ LOOP_EDITED_POINTS {
+ LOOP_KEYS {
+ if (k) {
+ sub_v3_v3v3(dv1, key->co, (key - 1)->co);
+ normalize_v3(dv1);
+ mul_v3_fl(dv1, (key - 1)->length);
+ add_v3_v3v3(key->co, (key - 1)->co, dv1);
+ }
+ }
+ }
+}
+/* try to find a nice solution to keep distances between neighboring keys */
+static void pe_iterate_lengths(Scene *scene, PTCacheEdit *edit)
+{
+ ParticleEditSettings *pset=PE_settings(scene);
+ POINT_P;
+ PTCacheEditKey *key;
+ int j, k;
+ float tlen;
+ float dv0[3] = {0.0f, 0.0f, 0.0f};
+ float dv1[3] = {0.0f, 0.0f, 0.0f};
+ float dv2[3] = {0.0f, 0.0f, 0.0f};
+
+ if (edit==0 || (pset->flag & PE_KEEP_LENGTHS)==0)
+ return;
+
+ if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
+ return;
+
+ LOOP_EDITED_POINTS {
+ for (j=1; j<point->totkey; j++) {
+ float mul= 1.0f / (float)point->totkey;
+
+ if (pset->flag & PE_LOCK_FIRST) {
+ key= point->keys + 1;
+ k= 1;
+ dv1[0] = dv1[1] = dv1[2] = 0.0;
+ }
+ else {
+ key= point->keys;
+ k= 0;
+ dv0[0] = dv0[1] = dv0[2] = 0.0;
+ }
+
+ for (; k<point->totkey; k++, key++) {
+ if (k) {
+ sub_v3_v3v3(dv0, (key - 1)->co, key->co);
+ tlen= normalize_v3(dv0);
+ mul_v3_fl(dv0, (mul * (tlen - (key - 1)->length)));
+ }
+
+ if (k < point->totkey - 1) {
+ sub_v3_v3v3(dv2, (key + 1)->co, key->co);
+ tlen= normalize_v3(dv2);
+ mul_v3_fl(dv2, mul * (tlen - key->length));
+ }
+
+ if (k) {
+ add_v3_v3((key-1)->co, dv1);
+ }
+
+ add_v3_v3v3(dv1, dv0, dv2);
+ }
+ }
+ }
+}
+/* set current distances to be kept between neighbouting keys */
+void recalc_lengths(PTCacheEdit *edit)
+{
+ POINT_P; KEY_K;
+
+ if (edit==0)
+ return;
+
+ LOOP_EDITED_POINTS {
+ key= point->keys;
+ for (k=0; k<point->totkey-1; k++, key++) {
+ key->length= len_v3v3(key->co, (key + 1)->co);
+ }
+ }
+}
+
+/* calculate a tree for finding nearest emitter's vertice */
+void recalc_emitter_field(Object *ob, ParticleSystem *psys)
+{
+ DerivedMesh *dm=psys_get_modifier(ob, psys)->dm_final;
+ PTCacheEdit *edit= psys->edit;
+ float *vec, *nor;
+ int i, totface /*, totvert*/;
+
+ if (!dm)
+ return;
+
+ if (edit->emitter_cosnos)
+ MEM_freeN(edit->emitter_cosnos);
+
+ BLI_kdtree_free(edit->emitter_field);
+
+ totface=dm->getNumTessFaces(dm);
+ /*totvert=dm->getNumVerts(dm);*/ /*UNSUED*/
+
+ edit->emitter_cosnos=MEM_callocN(totface*6*sizeof(float), "emitter cosnos");
+
+ edit->emitter_field= BLI_kdtree_new(totface);
+
+ vec=edit->emitter_cosnos;
+ nor=vec+3;
+
+ for (i=0; i<totface; i++, vec+=6, nor+=6) {
+ MFace *mface=dm->getTessFaceData(dm, i, CD_MFACE);
+ MVert *mvert;
+
+ mvert=dm->getVertData(dm, mface->v1, CD_MVERT);
+ copy_v3_v3(vec, mvert->co);
+ VECCOPY(nor, mvert->no);
+
+ mvert=dm->getVertData(dm, mface->v2, CD_MVERT);
+ add_v3_v3v3(vec, vec, mvert->co);
+ VECADD(nor, nor, mvert->no);
+
+ mvert=dm->getVertData(dm, mface->v3, CD_MVERT);
+ add_v3_v3v3(vec, vec, mvert->co);
+ VECADD(nor, nor, mvert->no);
+
+ if (mface->v4) {
+ mvert=dm->getVertData(dm, mface->v4, CD_MVERT);
+ add_v3_v3v3(vec, vec, mvert->co);
+ VECADD(nor, nor, mvert->no);
+
+ mul_v3_fl(vec, 0.25);
+ }
+ else
+ mul_v3_fl(vec, 1.0f / 3.0f);
+
+ normalize_v3(nor);
+
+ BLI_kdtree_insert(edit->emitter_field, i, vec);
+ }
+
+ BLI_kdtree_balance(edit->emitter_field);
+}
+
+static void PE_update_selection(Scene *scene, Object *ob, int useflag)
+{
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ HairKey *hkey;
+ POINT_P; KEY_K;
+
+ /* flag all particles to be updated if not using flag */
+ if (!useflag)
+ LOOP_POINTS
+ point->flag |= PEP_EDIT_RECALC;
+
+ /* flush edit key flag to hair key flag to preserve selection
+ * on save */
+ if (edit->psys) LOOP_POINTS {
+ hkey = edit->psys->particles[p].hair;
+ LOOP_KEYS {
+ hkey->editflag= key->flag;
+ hkey++;
+ }
+ }
+
+ psys_cache_edit_paths(scene, ob, edit, CFRA, G.is_rendering);
+
+
+ /* disable update flag */
+ LOOP_POINTS
+ point->flag &= ~PEP_EDIT_RECALC;
+}
+
+void update_world_cos(Object *ob, PTCacheEdit *edit)
+{
+ ParticleSystem *psys = edit->psys;
+ ParticleSystemModifierData *psmd= psys_get_modifier(ob, psys);
+ POINT_P; KEY_K;
+ float hairmat[4][4];
+
+ if (psys==0 || psys->edit==0 || psmd->dm_final==NULL)
+ return;
+
+ LOOP_POINTS {
+ if (!(psys->flag & PSYS_GLOBAL_HAIR))
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles+p, hairmat);
+
+ LOOP_KEYS {
+ copy_v3_v3(key->world_co, key->co);
+ if (!(psys->flag & PSYS_GLOBAL_HAIR))
+ mul_m4_v3(hairmat, key->world_co);
+ }
+ }
+}
+static void update_velocities(PTCacheEdit *edit)
+{
+ /*TODO: get frs_sec properly */
+ float vec1[3], vec2[3], frs_sec, dfra;
+ POINT_P; KEY_K;
+
+ /* hair doesn't use velocities */
+ if (edit->psys || !edit->points || !edit->points->keys->vel)
+ return;
+
+ frs_sec = edit->pid.flag & PTCACHE_VEL_PER_SEC ? 25.0f : 1.0f;
+
+ LOOP_EDITED_POINTS {
+ LOOP_KEYS {
+ if (k==0) {
+ dfra = *(key+1)->time - *key->time;
+
+ if (dfra <= 0.0f)
+ continue;
+
+ sub_v3_v3v3(key->vel, (key+1)->co, key->co);
+
+ if (point->totkey>2) {
+ sub_v3_v3v3(vec1, (key+1)->co, (key+2)->co);
+ project_v3_v3v3(vec2, vec1, key->vel);
+ sub_v3_v3v3(vec2, vec1, vec2);
+ madd_v3_v3fl(key->vel, vec2, 0.5f);
+ }
+ }
+ else if (k==point->totkey-1) {
+ dfra = *key->time - *(key-1)->time;
+
+ if (dfra <= 0.0f)
+ continue;
+
+ sub_v3_v3v3(key->vel, key->co, (key-1)->co);
+
+ if (point->totkey>2) {
+ sub_v3_v3v3(vec1, (key-2)->co, (key-1)->co);
+ project_v3_v3v3(vec2, vec1, key->vel);
+ sub_v3_v3v3(vec2, vec1, vec2);
+ madd_v3_v3fl(key->vel, vec2, 0.5f);
+ }
+ }
+ else {
+ dfra = *(key+1)->time - *(key-1)->time;
+
+ if (dfra <= 0.0f)
+ continue;
+
+ sub_v3_v3v3(key->vel, (key+1)->co, (key-1)->co);
+ }
+ mul_v3_fl(key->vel, frs_sec/dfra);
+ }
+ }
+}
+
+void PE_update_object(Scene *scene, Object *ob, int useflag)
+{
+ /* use this to do partial particle updates, not usable when adding or
+ * removing, then a full redo is necessary and calling this may crash */
+ ParticleEditSettings *pset= PE_settings(scene);
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ POINT_P;
+
+ if (!edit)
+ return;
+
+ /* flag all particles to be updated if not using flag */
+ if (!useflag)
+ LOOP_POINTS {
+ point->flag |= PEP_EDIT_RECALC;
+ }
+
+ /* do post process on particle edit keys */
+ pe_iterate_lengths(scene, edit);
+ pe_deflect_emitter(scene, ob, edit);
+ PE_apply_lengths(scene, edit);
+ if (pe_x_mirror(ob))
+ PE_apply_mirror(ob, edit->psys);
+ if (edit->psys)
+ update_world_cos(ob, edit);
+ if (pset->flag & PE_AUTO_VELOCITY)
+ update_velocities(edit);
+ PE_hide_keys_time(scene, edit, CFRA);
+
+ /* regenerate path caches */
+ psys_cache_edit_paths(scene, ob, edit, CFRA, G.is_rendering);
+
+ /* disable update flag */
+ LOOP_POINTS {
+ point->flag &= ~PEP_EDIT_RECALC;
+ }
+
+ if (edit->psys)
+ edit->psys->flag &= ~PSYS_HAIR_UPDATED;
+}
+
+/************************************************/
+/* Edit Selections */
+/************************************************/
+
+/*-----selection callbacks-----*/
+
+static void select_key(PEData *data, int point_index, int key_index)
+{
+ PTCacheEdit *edit = data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ PTCacheEditKey *key = point->keys + key_index;
+
+ if (data->select)
+ key->flag |= PEK_SELECT;
+ else
+ key->flag &= ~PEK_SELECT;
+
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void select_keys(PEData *data, int point_index, int UNUSED(key_index))
+{
+ PTCacheEdit *edit = data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ KEY_K;
+
+ LOOP_KEYS {
+ if (data->select)
+ key->flag |= PEK_SELECT;
+ else
+ key->flag &= ~PEK_SELECT;
+ }
+
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void extend_key_select(PEData *data, int point_index, int key_index)
+{
+ PTCacheEdit *edit = data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ PTCacheEditKey *key = point->keys + key_index;
+
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void deselect_key_select(PEData *data, int point_index, int key_index)
+{
+ PTCacheEdit *edit = data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ PTCacheEditKey *key = point->keys + key_index;
+
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void toggle_key_select(PEData *data, int point_index, int key_index)
+{
+ PTCacheEdit *edit = data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ PTCacheEditKey *key = point->keys + key_index;
+
+ key->flag ^= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+/************************ de select all operator ************************/
+
+static void select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action)
+{
+ switch (action) {
+ case SEL_SELECT:
+ if ((key->flag & PEK_SELECT) == 0) {
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ break;
+ case SEL_DESELECT:
+ if (key->flag & PEK_SELECT) {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ break;
+ case SEL_INVERT:
+ if ((key->flag & PEK_SELECT) == 0) {
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ else {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ break;
+ }
+}
+
+static int pe_select_all_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ POINT_P; KEY_K;
+ int action = RNA_enum_get(op->ptr, "action");
+
+ if (action == SEL_TOGGLE) {
+ action = SEL_SELECT;
+ LOOP_VISIBLE_POINTS {
+ LOOP_SELECTED_KEYS {
+ action = SEL_DESELECT;
+ break;
+ }
+
+ if (action == SEL_DESELECT)
+ break;
+ }
+ }
+
+ LOOP_VISIBLE_POINTS {
+ LOOP_VISIBLE_KEYS {
+ select_action_apply(point, key, action);
+ }
+ }
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_all(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "(De)select All";
+ ot->idname = "PARTICLE_OT_select_all";
+ ot->description = "(De)select all particles' keys";
+
+ /* api callbacks */
+ ot->exec = pe_select_all_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ WM_operator_properties_select_all(ot);
+}
+
+/************************ pick select operator ************************/
+
+int PE_mouse_particles(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
+{
+ PEData data;
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ POINT_P; KEY_K;
+
+ if (!PE_start_edit(edit))
+ return OPERATOR_CANCELLED;
+
+ if (!extend && !deselect && !toggle) {
+ LOOP_VISIBLE_POINTS {
+ LOOP_SELECTED_KEYS {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ }
+
+ PE_set_view3d_data(C, &data);
+ data.mval= mval;
+ data.rad = ED_view3d_select_dist_px();
+
+ /* 1 = nearest only */
+ if (extend)
+ for_mouse_hit_keys(&data, extend_key_select, 1);
+ else if (deselect)
+ for_mouse_hit_keys(&data, deselect_key_select, 1);
+ else
+ for_mouse_hit_keys(&data, toggle_key_select, 1);
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+/************************ select root operator ************************/
+
+static void select_root(PEData *data, int point_index)
+{
+ PTCacheEditPoint *point = data->edit->points + point_index;
+ PTCacheEditKey *key = point->keys;
+
+ if (point->flag & PEP_HIDE)
+ return;
+
+ if (data->select_action != SEL_TOGGLE)
+ select_action_apply(point, key, data->select_action);
+ else if (key->flag & PEK_SELECT)
+ data->select_toggle_action = SEL_DESELECT;
+}
+
+static int select_roots_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+ int action = RNA_enum_get(op->ptr, "action");
+
+ PE_set_data(C, &data);
+
+ if (action == SEL_TOGGLE) {
+ data.select_action = SEL_TOGGLE;
+ data.select_toggle_action = SEL_SELECT;
+
+ foreach_point(&data, select_root);
+
+ action = data.select_toggle_action;
+ }
+
+ data.select_action = action;
+ foreach_point(&data, select_root);
+
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_roots(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Roots";
+ ot->idname = "PARTICLE_OT_select_roots";
+ ot->description = "Select roots of all visible particles";
+
+ /* api callbacks */
+ ot->exec = select_roots_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ WM_operator_properties_select_action(ot, SEL_SELECT);
+}
+
+/************************ select tip operator ************************/
+
+static void select_tip(PEData *data, int point_index)
+{
+ PTCacheEditPoint *point = data->edit->points + point_index;
+ PTCacheEditKey *key;
+
+ if (point->totkey == 0) {
+ return;
+ }
+
+ key = &point->keys[point->totkey - 1];
+
+ if (point->flag & PEP_HIDE)
+ return;
+
+ if (data->select_action != SEL_TOGGLE)
+ select_action_apply(point, key, data->select_action);
+ else if (key->flag & PEK_SELECT)
+ data->select_toggle_action = SEL_DESELECT;
+}
+
+static int select_tips_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+ int action = RNA_enum_get(op->ptr, "action");
+
+ PE_set_data(C, &data);
+
+ if (action == SEL_TOGGLE) {
+ data.select_action = SEL_TOGGLE;
+ data.select_toggle_action = SEL_SELECT;
+
+ foreach_point(&data, select_tip);
+
+ action = data.select_toggle_action;
+ }
+
+ data.select_action = action;
+ foreach_point(&data, select_tip);
+
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_tips(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Tips";
+ ot->idname = "PARTICLE_OT_select_tips";
+ ot->description = "Select tips of all visible particles";
+
+ /* api callbacks */
+ ot->exec = select_tips_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ WM_operator_properties_select_action(ot, SEL_SELECT);
+}
+
+/*********************** select random operator ************************/
+
+enum { RAN_HAIR, RAN_POINTS };
+
+static EnumPropertyItem select_random_type_items[] = {
+ {RAN_HAIR, "HAIR", 0, "Hair", ""},
+ {RAN_POINTS, "POINTS", 0, "Points", ""},
+ {0, NULL, 0, NULL, NULL}
+};
+
+static int select_random_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+ int type;
+ Scene *scene;
+ Object *ob;
+
+ /* used by LOOP_VISIBLE_POINTS, LOOP_VISIBLE_KEYS and LOOP_KEYS */
+ PTCacheEdit *edit;
+ PTCacheEditPoint *point;
+ PTCacheEditKey *key;
+ int p;
+ int k;
+
+ const float randfac = RNA_float_get(op->ptr, "percent") / 100.0f;
+ const int seed = WM_operator_properties_select_random_seed_increment_get(op);
+ const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT);
+ RNG *rng;
+
+ type = RNA_enum_get(op->ptr, "type");
+
+ PE_set_data(C, &data);
+ data.select_action = SEL_SELECT;
+ scene = CTX_data_scene(C);
+ ob = CTX_data_active_object(C);
+ edit = PE_get_current(scene, ob);
+
+ rng = BLI_rng_new_srandom(seed);
+
+ switch (type) {
+ case RAN_HAIR:
+ LOOP_VISIBLE_POINTS {
+ int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
+ LOOP_KEYS {
+ select_action_apply (point, key, flag);
+ }
+ }
+ break;
+ case RAN_POINTS:
+ LOOP_VISIBLE_POINTS {
+ LOOP_VISIBLE_KEYS {
+ int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
+ select_action_apply (point, key, flag);
+ }
+ }
+ break;
+ }
+
+ BLI_rng_free(rng);
+
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_random(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Random";
+ ot->idname = "PARTICLE_OT_select_random";
+ ot->description = "Select a randomly distributed set of hair or points";
+
+ /* api callbacks */
+ ot->exec = select_random_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ WM_operator_properties_select_random(ot);
+ ot->prop = RNA_def_enum (ot->srna, "type", select_random_type_items, RAN_HAIR,
+ "Type", "Select either hair or points");
+}
+
+/************************ select linked operator ************************/
+
+static int select_linked_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+ int mval[2];
+ int location[2];
+
+ RNA_int_get_array(op->ptr, "location", location);
+ mval[0] = location[0];
+ mval[1] = location[1];
+
+ PE_set_view3d_data(C, &data);
+ data.mval= mval;
+ data.rad=75.0f;
+ data.select= !RNA_boolean_get(op->ptr, "deselect");
+
+ for_mouse_hit_keys(&data, select_keys, 1); /* nearest only */
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.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 PARTICLE_OT_select_linked(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Linked";
+ ot->idname = "PARTICLE_OT_select_linked";
+ ot->description = "Select nearest particle from mouse pointer";
+
+ /* api callbacks */
+ ot->exec = select_linked_exec;
+ ot->invoke = select_linked_invoke;
+ ot->poll = PE_poll_view3d;
+
+ /* 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 ************************/
+void PE_deselect_all_visible(PTCacheEdit *edit)
+{
+ POINT_P; KEY_K;
+
+ LOOP_VISIBLE_POINTS {
+ LOOP_SELECTED_KEYS {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+}
+
+int PE_border_select(bContext *C, rcti *rect, bool select, bool extend)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ PEData data;
+
+ if (!PE_start_edit(edit))
+ return OPERATOR_CANCELLED;
+
+ if (extend == 0 && select)
+ PE_deselect_all_visible(edit);
+
+ PE_set_view3d_data(C, &data);
+ data.rect= rect;
+ data.select= select;
+
+ for_mouse_hit_keys(&data, select_key, 0);
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+/************************ circle select operator ************************/
+
+int PE_circle_select(bContext *C, int selecting, const int mval[2], float rad)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ PEData data;
+
+ if (!PE_start_edit(edit))
+ return OPERATOR_FINISHED;
+
+ PE_set_view3d_data(C, &data);
+ data.mval= mval;
+ data.rad= rad;
+ data.select= selecting;
+
+ for_mouse_hit_keys(&data, select_key, 0);
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+/************************ lasso select operator ************************/
+
+int PE_lasso_select(bContext *C, const int mcords[][2], const short moves, bool extend, bool select)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ ARegion *ar= CTX_wm_region(C);
+ ParticleEditSettings *pset= PE_settings(scene);
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ ParticleSystem *psys = edit->psys;
+ ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
+ POINT_P; KEY_K;
+ float co[3], mat[4][4];
+ int screen_co[2];
+
+ PEData data;
+
+ unit_m4(mat);
+
+ if (!PE_start_edit(edit))
+ return OPERATOR_CANCELLED;
+
+ if (extend == 0 && select)
+ PE_deselect_all_visible(edit);
+
+ /* only for depths */
+ PE_set_view3d_data(C, &data);
+
+ LOOP_VISIBLE_POINTS {
+ if (edit->psys && !(psys->flag & PSYS_GLOBAL_HAIR))
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles + p, mat);
+
+ if (pset->selectmode==SCE_SELECT_POINT) {
+ LOOP_KEYS {
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ if ((ED_view3d_project_int_global(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) &&
+ BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], IS_CLIPPED) &&
+ key_test_depth(&data, co, screen_co))
+ {
+ if (select) {
+ if (!(key->flag & PEK_SELECT)) {
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ else {
+ if (key->flag & PEK_SELECT) {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ }
+ }
+ }
+ else if (pset->selectmode==SCE_SELECT_END) {
+ if (point->totkey) {
+ key= point->keys + point->totkey - 1;
+
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ if ((ED_view3d_project_int_global(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) &&
+ BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], IS_CLIPPED) &&
+ key_test_depth(&data, co, screen_co))
+ {
+ if (select) {
+ if (!(key->flag & PEK_SELECT)) {
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ else {
+ if (key->flag & PEK_SELECT) {
+ key->flag &= ~PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+/*************************** hide operator **************************/
+
+static int hide_exec(bContext *C, wmOperator *op)
+{
+ Object *ob= CTX_data_active_object(C);
+ Scene *scene= CTX_data_scene(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ POINT_P; KEY_K;
+
+ if (RNA_enum_get(op->ptr, "unselected")) {
+ LOOP_UNSELECTED_POINTS {
+ point->flag |= PEP_HIDE;
+ point->flag |= PEP_EDIT_RECALC;
+
+ LOOP_KEYS
+ key->flag &= ~PEK_SELECT;
+ }
+ }
+ else {
+ LOOP_SELECTED_POINTS {
+ point->flag |= PEP_HIDE;
+ point->flag |= PEP_EDIT_RECALC;
+
+ LOOP_KEYS
+ key->flag &= ~PEK_SELECT;
+ }
+ }
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_hide(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Hide Selected";
+ ot->idname = "PARTICLE_OT_hide";
+ ot->description = "Hide selected particles";
+
+ /* api callbacks */
+ ot->exec = hide_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* props */
+ RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected");
+}
+
+/*************************** reveal operator **************************/
+
+static int reveal_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob= CTX_data_active_object(C);
+ Scene *scene= CTX_data_scene(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ POINT_P; KEY_K;
+
+ LOOP_POINTS {
+ if (point->flag & PEP_HIDE) {
+ point->flag &= ~PEP_HIDE;
+ point->flag |= PEP_EDIT_RECALC;
+
+ LOOP_KEYS
+ key->flag |= PEK_SELECT;
+ }
+ }
+
+ PE_update_selection(scene, ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_reveal(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Reveal";
+ ot->idname = "PARTICLE_OT_reveal";
+ ot->description = "Show hidden particles";
+
+ /* api callbacks */
+ ot->exec = reveal_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ select less operator ************************/
+
+static void select_less_keys(PEData *data, int point_index)
+{
+ PTCacheEdit *edit= data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ KEY_K;
+
+ LOOP_SELECTED_KEYS {
+ if (k==0) {
+ if (((key+1)->flag&PEK_SELECT)==0)
+ key->flag |= PEK_TAG;
+ }
+ else if (k==point->totkey-1) {
+ if (((key-1)->flag&PEK_SELECT)==0)
+ key->flag |= PEK_TAG;
+ }
+ else {
+ if ((((key-1)->flag & (key+1)->flag) & PEK_SELECT)==0)
+ key->flag |= PEK_TAG;
+ }
+ }
+
+ LOOP_KEYS {
+ if (key->flag&PEK_TAG) {
+ key->flag &= ~(PEK_TAG|PEK_SELECT);
+ point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
+ }
+ }
+}
+
+static int select_less_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PEData data;
+
+ PE_set_data(C, &data);
+ foreach_point(&data, select_less_keys);
+
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_less(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Less";
+ ot->idname = "PARTICLE_OT_select_less";
+ ot->description = "Deselect boundary selected keys of each particle";
+
+ /* api callbacks */
+ ot->exec = select_less_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ select more operator ************************/
+
+static void select_more_keys(PEData *data, int point_index)
+{
+ PTCacheEdit *edit= data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ KEY_K;
+
+ LOOP_KEYS {
+ if (key->flag & PEK_SELECT) continue;
+
+ if (k==0) {
+ if ((key+1)->flag&PEK_SELECT)
+ key->flag |= PEK_TAG;
+ }
+ else if (k==point->totkey-1) {
+ if ((key-1)->flag&PEK_SELECT)
+ key->flag |= PEK_TAG;
+ }
+ else {
+ if (((key-1)->flag | (key+1)->flag) & PEK_SELECT)
+ key->flag |= PEK_TAG;
+ }
+ }
+
+ LOOP_KEYS {
+ if (key->flag&PEK_TAG) {
+ key->flag &= ~PEK_TAG;
+ key->flag |= PEK_SELECT;
+ point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
+ }
+ }
+}
+
+static int select_more_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PEData data;
+
+ PE_set_data(C, &data);
+ foreach_point(&data, select_more_keys);
+
+ PE_update_selection(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_SELECTED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_select_more(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select More";
+ ot->idname = "PARTICLE_OT_select_more";
+ ot->description = "Select keys linked to boundary selected keys of each particle";
+
+ /* api callbacks */
+ ot->exec = select_more_exec;
+ ot->poll = PE_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ rekey operator ************************/
+
+static void rekey_particle(PEData *data, int pa_index)
+{
+ PTCacheEdit *edit= data->edit;
+ ParticleSystem *psys= edit->psys;
+ ParticleSimulationData sim= {0};
+ ParticleData *pa= psys->particles + pa_index;
+ PTCacheEditPoint *point = edit->points + pa_index;
+ ParticleKey state;
+ HairKey *key, *new_keys, *okey;
+ PTCacheEditKey *ekey;
+ float dval, sta, end;
+ int k;
+
+ sim.scene= data->scene;
+ sim.ob= data->ob;
+ sim.psys= edit->psys;
+
+ pa->flag |= PARS_REKEY;
+
+ key= new_keys= MEM_callocN(data->totrekey * sizeof(HairKey), "Hair re-key keys");
+
+ okey = pa->hair;
+ /* root and tip stay the same */
+ copy_v3_v3(key->co, okey->co);
+ copy_v3_v3((key + data->totrekey - 1)->co, (okey + pa->totkey - 1)->co);
+
+ sta= key->time= okey->time;
+ end= (key + data->totrekey - 1)->time= (okey + pa->totkey - 1)->time;
+ dval= (end - sta) / (float)(data->totrekey - 1);
+
+ /* interpolate new keys from old ones */
+ for (k=1, key++; k<data->totrekey-1; k++, key++) {
+ state.time= (float)k / (float)(data->totrekey-1);
+ psys_get_particle_on_path(&sim, pa_index, &state, 0);
+ copy_v3_v3(key->co, state.co);
+ key->time= sta + k * dval;
+ }
+
+ /* replace keys */
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+ pa->hair= new_keys;
+
+ point->totkey=pa->totkey=data->totrekey;
+
+
+ if (point->keys)
+ MEM_freeN(point->keys);
+ ekey= point->keys= MEM_callocN(pa->totkey * sizeof(PTCacheEditKey), "Hair re-key edit keys");
+
+ for (k=0, key=pa->hair; k<pa->totkey; k++, key++, ekey++) {
+ ekey->co= key->co;
+ ekey->time= &key->time;
+ ekey->flag |= PEK_SELECT;
+ if (!(psys->flag & PSYS_GLOBAL_HAIR))
+ ekey->flag |= PEK_USE_WCO;
+ }
+
+ pa->flag &= ~PARS_REKEY;
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static int rekey_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+
+ PE_set_data(C, &data);
+
+ data.dval= 1.0f / (float)(data.totrekey-1);
+ data.totrekey= RNA_int_get(op->ptr, "keys_number");
+
+ foreach_selected_point(&data, rekey_particle);
+
+ recalc_lengths(data.edit);
+ PE_update_object(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_rekey(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Rekey";
+ ot->idname = "PARTICLE_OT_rekey";
+ ot->description = "Change the number of keys of selected particles (root and tip keys included)";
+
+ /* api callbacks */
+ ot->exec = rekey_exec;
+ ot->invoke = WM_operator_props_popup;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_int(ot->srna, "keys_number", 2, 2, INT_MAX, "Number of Keys", "", 2, 100);
+}
+
+static void rekey_particle_to_time(Scene *scene, Object *ob, int pa_index, float path_time)
+{
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ParticleSystem *psys;
+ ParticleSimulationData sim= {0};
+ ParticleData *pa;
+ ParticleKey state;
+ HairKey *new_keys, *key;
+ PTCacheEditKey *ekey;
+ int k;
+
+ if (!edit || !edit->psys) return;
+
+ psys = edit->psys;
+
+ sim.scene= scene;
+ sim.ob= ob;
+ sim.psys= psys;
+
+ pa= psys->particles + pa_index;
+
+ pa->flag |= PARS_REKEY;
+
+ key= new_keys= MEM_dupallocN(pa->hair);
+
+ /* interpolate new keys from old ones (roots stay the same) */
+ for (k=1, key++; k < pa->totkey; k++, key++) {
+ state.time= path_time * (float)k / (float)(pa->totkey-1);
+ psys_get_particle_on_path(&sim, pa_index, &state, 0);
+ copy_v3_v3(key->co, state.co);
+ }
+
+ /* replace hair keys */
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+ pa->hair= new_keys;
+
+ /* update edit pointers */
+ for (k=0, key=pa->hair, ekey=edit->points[pa_index].keys; k<pa->totkey; k++, key++, ekey++) {
+ ekey->co= key->co;
+ ekey->time= &key->time;
+ }
+
+ pa->flag &= ~PARS_REKEY;
+}
+
+/************************* utilities **************************/
+
+static int remove_tagged_particles(Object *ob, ParticleSystem *psys, int mirror)
+{
+ PTCacheEdit *edit = psys->edit;
+ ParticleData *pa, *npa=0, *new_pars=0;
+ POINT_P;
+ PTCacheEditPoint *npoint=0, *new_points=0;
+ ParticleSystemModifierData *psmd;
+ int i, new_totpart= psys->totpart, removed= 0;
+
+ if (mirror) {
+ /* mirror tags */
+ psmd= psys_get_modifier(ob, psys);
+
+ LOOP_TAGGED_POINTS {
+ PE_mirror_particle(ob, psmd->dm_final, psys, psys->particles + p, NULL);
+ }
+ }
+
+ LOOP_TAGGED_POINTS {
+ new_totpart--;
+ removed++;
+ }
+
+ if (new_totpart != psys->totpart) {
+ if (new_totpart) {
+ npa= new_pars= MEM_callocN(new_totpart * sizeof(ParticleData), "ParticleData array");
+ npoint= new_points= MEM_callocN(new_totpart * sizeof(PTCacheEditPoint), "PTCacheEditKey array");
+
+ if (ELEM(NULL, new_pars, new_points)) {
+ /* allocation error! */
+ if (new_pars)
+ MEM_freeN(new_pars);
+ if (new_points)
+ MEM_freeN(new_points);
+ return 0;
+ }
+ }
+
+ pa= psys->particles;
+ point= edit->points;
+ for (i=0; i<psys->totpart; i++, pa++, point++) {
+ if (point->flag & PEP_TAG) {
+ if (point->keys)
+ MEM_freeN(point->keys);
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+ }
+ else {
+ memcpy(npa, pa, sizeof(ParticleData));
+ memcpy(npoint, point, sizeof(PTCacheEditPoint));
+ npa++;
+ npoint++;
+ }
+ }
+
+ if (psys->particles) MEM_freeN(psys->particles);
+ psys->particles= new_pars;
+
+ if (edit->points) MEM_freeN(edit->points);
+ edit->points= new_points;
+
+ if (edit->mirror_cache) {
+ MEM_freeN(edit->mirror_cache);
+ edit->mirror_cache= NULL;
+ }
+
+ if (psys->child) {
+ MEM_freeN(psys->child);
+ psys->child= NULL;
+ psys->totchild=0;
+ }
+
+ edit->totpoint= psys->totpart= new_totpart;
+ }
+
+ return removed;
+}
+
+static void remove_tagged_keys(Object *ob, ParticleSystem *psys)
+{
+ PTCacheEdit *edit= psys->edit;
+ ParticleData *pa;
+ HairKey *hkey, *nhkey, *new_hkeys=0;
+ POINT_P; KEY_K;
+ PTCacheEditKey *nkey, *new_keys;
+ ParticleSystemModifierData *psmd;
+ short new_totkey;
+
+ if (pe_x_mirror(ob)) {
+ /* mirror key tags */
+ psmd= psys_get_modifier(ob, psys);
+
+ LOOP_POINTS {
+ LOOP_TAGGED_KEYS {
+ PE_mirror_particle(ob, psmd->dm_final, psys, psys->particles + p, NULL);
+ break;
+ }
+ }
+ }
+
+ LOOP_POINTS {
+ new_totkey= point->totkey;
+ LOOP_TAGGED_KEYS {
+ new_totkey--;
+ }
+ /* we can't have elements with less than two keys*/
+ if (new_totkey < 2)
+ point->flag |= PEP_TAG;
+ }
+ remove_tagged_particles(ob, psys, pe_x_mirror(ob));
+
+ LOOP_POINTS {
+ pa = psys->particles + p;
+ new_totkey= pa->totkey;
+
+ LOOP_TAGGED_KEYS {
+ new_totkey--;
+ }
+
+ if (new_totkey != pa->totkey) {
+ nhkey= new_hkeys= MEM_callocN(new_totkey*sizeof(HairKey), "HairKeys");
+ nkey= new_keys= MEM_callocN(new_totkey*sizeof(PTCacheEditKey), "particle edit keys");
+
+ hkey= pa->hair;
+ LOOP_KEYS {
+ while (key->flag & PEK_TAG && hkey < pa->hair + pa->totkey) {
+ key++;
+ hkey++;
+ }
+
+ if (hkey < pa->hair + pa->totkey) {
+ copy_v3_v3(nhkey->co, hkey->co);
+ nhkey->editflag = hkey->editflag;
+ nhkey->time= hkey->time;
+ nhkey->weight= hkey->weight;
+
+ nkey->co= nhkey->co;
+ nkey->time= &nhkey->time;
+ /* these can be copied from old edit keys */
+ nkey->flag = key->flag;
+ nkey->ftime = key->ftime;
+ nkey->length = key->length;
+ copy_v3_v3(nkey->world_co, key->world_co);
+ }
+ nkey++;
+ nhkey++;
+ hkey++;
+ }
+
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+
+ if (point->keys)
+ MEM_freeN(point->keys);
+
+ pa->hair= new_hkeys;
+ point->keys= new_keys;
+
+ point->totkey= pa->totkey= new_totkey;
+
+ /* flag for recalculating length */
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+}
+
+/************************ subdivide opertor *********************/
+
+/* works like normal edit mode subdivide, inserts keys between neighboring selected keys */
+static void subdivide_particle(PEData *data, int pa_index)
+{
+ PTCacheEdit *edit= data->edit;
+ ParticleSystem *psys= edit->psys;
+ ParticleSimulationData sim= {0};
+ ParticleData *pa= psys->particles + pa_index;
+ PTCacheEditPoint *point = edit->points + pa_index;
+ ParticleKey state;
+ HairKey *key, *nkey, *new_keys;
+ PTCacheEditKey *ekey, *nekey, *new_ekeys;
+
+ int k;
+ short totnewkey=0;
+ float endtime;
+
+ sim.scene= data->scene;
+ sim.ob= data->ob;
+ sim.psys= edit->psys;
+
+ for (k=0, ekey=point->keys; k<pa->totkey-1; k++, ekey++) {
+ if (ekey->flag&PEK_SELECT && (ekey+1)->flag&PEK_SELECT)
+ totnewkey++;
+ }
+
+ if (totnewkey==0) return;
+
+ pa->flag |= PARS_REKEY;
+
+ nkey= new_keys= MEM_callocN((pa->totkey+totnewkey)*(sizeof(HairKey)), "Hair subdivide keys");
+ nekey= new_ekeys= MEM_callocN((pa->totkey+totnewkey)*(sizeof(PTCacheEditKey)), "Hair subdivide edit keys");
+
+ key = pa->hair;
+ endtime= key[pa->totkey-1].time;
+
+ for (k=0, ekey=point->keys; k<pa->totkey-1; k++, key++, ekey++) {
+
+ memcpy(nkey, key, sizeof(HairKey));
+ memcpy(nekey, ekey, sizeof(PTCacheEditKey));
+
+ nekey->co= nkey->co;
+ nekey->time= &nkey->time;
+
+ nkey++;
+ nekey++;
+
+ if (ekey->flag & PEK_SELECT && (ekey+1)->flag & PEK_SELECT) {
+ nkey->time = (key->time + (key + 1)->time) * 0.5f;
+ state.time = (endtime != 0.0f) ? nkey->time / endtime: 0.0f;
+ psys_get_particle_on_path(&sim, pa_index, &state, 0);
+ copy_v3_v3(nkey->co, state.co);
+
+ nekey->co= nkey->co;
+ nekey->time = &nkey->time;
+ nekey->flag |= PEK_SELECT;
+ if (!(psys->flag & PSYS_GLOBAL_HAIR))
+ nekey->flag |= PEK_USE_WCO;
+
+ nekey++;
+ nkey++;
+ }
+ }
+ /*tip still not copied*/
+ memcpy(nkey, key, sizeof(HairKey));
+ memcpy(nekey, ekey, sizeof(PTCacheEditKey));
+
+ nekey->co= nkey->co;
+ nekey->time= &nkey->time;
+
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+ pa->hair= new_keys;
+
+ if (point->keys)
+ MEM_freeN(point->keys);
+ point->keys= new_ekeys;
+
+ point->totkey = pa->totkey = pa->totkey + totnewkey;
+ point->flag |= PEP_EDIT_RECALC;
+ pa->flag &= ~PARS_REKEY;
+}
+
+static int subdivide_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PEData data;
+
+ PE_set_data(C, &data);
+ foreach_point(&data, subdivide_particle);
+
+ recalc_lengths(data.edit);
+ PE_update_object(data.scene, data.ob, 1);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_subdivide(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Subdivide";
+ ot->idname = "PARTICLE_OT_subdivide";
+ ot->description = "Subdivide selected particles segments (adds keys)";
+
+ /* api callbacks */
+ ot->exec = subdivide_exec;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ remove doubles opertor *********************/
+
+static int remove_doubles_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ParticleSystem *psys = edit->psys;
+ ParticleSystemModifierData *psmd;
+ KDTree *tree;
+ KDTreeNearest nearest[10];
+ POINT_P;
+ float mat[4][4], co[3], threshold= RNA_float_get(op->ptr, "threshold");
+ int n, totn, removed, totremoved;
+
+ if (psys->flag & PSYS_GLOBAL_HAIR)
+ return OPERATOR_CANCELLED;
+
+ edit= psys->edit;
+ psmd= psys_get_modifier(ob, psys);
+ totremoved= 0;
+
+ do {
+ removed= 0;
+
+ tree=BLI_kdtree_new(psys->totpart);
+
+ /* insert particles into kd tree */
+ LOOP_SELECTED_POINTS {
+ psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles+p, mat);
+ copy_v3_v3(co, point->keys->co);
+ mul_m4_v3(mat, co);
+ BLI_kdtree_insert(tree, p, co);
+ }
+
+ BLI_kdtree_balance(tree);
+
+ /* tag particles to be removed */
+ LOOP_SELECTED_POINTS {
+ psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles+p, mat);
+ copy_v3_v3(co, point->keys->co);
+ mul_m4_v3(mat, co);
+
+ totn = BLI_kdtree_find_nearest_n(tree, co, nearest, 10);
+
+ for (n=0; n<totn; n++) {
+ /* this needs a custom threshold still */
+ if (nearest[n].index > p && nearest[n].dist < threshold) {
+ if (!(point->flag & PEP_TAG)) {
+ point->flag |= PEP_TAG;
+ removed++;
+ }
+ }
+ }
+ }
+
+ BLI_kdtree_free(tree);
+
+ /* remove tagged particles - don't do mirror here! */
+ remove_tagged_particles(ob, psys, 0);
+ totremoved += removed;
+ } while (removed);
+
+ if (totremoved == 0)
+ return OPERATOR_CANCELLED;
+
+ BKE_reportf(op->reports, RPT_INFO, "Removed %d double particles", totremoved);
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_remove_doubles(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Doubles";
+ ot->idname = "PARTICLE_OT_remove_doubles";
+ ot->description = "Remove selected particles close enough of others";
+
+ /* api callbacks */
+ ot->exec = remove_doubles_exec;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_float(ot->srna, "threshold", 0.0002f, 0.0f, FLT_MAX,
+ "Merge Distance", "Threshold distance withing which particles are removed", 0.00001f, 0.1f);
+}
+
+
+static int weight_set_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ ParticleEditSettings *pset= PE_settings(scene);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ParticleSystem *psys = edit->psys;
+ POINT_P;
+ KEY_K;
+ HairKey *hkey;
+ float weight;
+ ParticleBrushData *brush= &pset->brush[pset->brushtype];
+ float factor= RNA_float_get(op->ptr, "factor");
+
+ weight= brush->strength;
+ edit= psys->edit;
+
+ LOOP_SELECTED_POINTS {
+ ParticleData *pa= psys->particles + p;
+
+ LOOP_SELECTED_KEYS {
+ hkey= pa->hair + k;
+ hkey->weight= interpf(weight, hkey->weight, factor);
+ }
+ }
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_weight_set(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Weight Set";
+ ot->idname = "PARTICLE_OT_weight_set";
+ ot->description = "Set the weight of selected keys";
+
+ /* api callbacks */
+ ot->exec = weight_set_exec;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ RNA_def_float(ot->srna, "factor", 1, 0, 1, "Factor",
+ "Interpolation factor between current brush weight, and keys' weights", 0, 1);
+}
+
+/************************ cursor drawing *******************************/
+
+static void brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata))
+{
+ Scene *scene = CTX_data_scene(C);
+ ParticleEditSettings *pset= PE_settings(scene);
+ ParticleBrushData *brush;
+
+ if (pset->brushtype < 0)
+ return;
+
+ brush= &pset->brush[pset->brushtype];
+
+ if (brush) {
+ glPushMatrix();
+
+ glTranslatef((float)x, (float)y, 0.0f);
+
+ glColor4ub(255, 255, 255, 128);
+ glEnable(GL_LINE_SMOOTH);
+ glEnable(GL_BLEND);
+ glutil_draw_lined_arc(0.0, M_PI*2.0, pe_brush_size_get(scene, brush), 40);
+ glDisable(GL_BLEND);
+ glDisable(GL_LINE_SMOOTH);
+
+ glPopMatrix();
+ }
+}
+
+static void toggle_particle_cursor(bContext *C, int enable)
+{
+ ParticleEditSettings *pset= PE_settings(CTX_data_scene(C));
+
+ if (pset->paintcursor && !enable) {
+ WM_paint_cursor_end(CTX_wm_manager(C), pset->paintcursor);
+ pset->paintcursor = NULL;
+ }
+ else if (enable)
+ pset->paintcursor= WM_paint_cursor_activate(CTX_wm_manager(C), PE_poll_view3d, brush_drawcursor, NULL);
+}
+
+/*************************** delete operator **************************/
+
+enum { DEL_PARTICLE, DEL_KEY };
+
+static EnumPropertyItem delete_type_items[] = {
+ {DEL_PARTICLE, "PARTICLE", 0, "Particle", ""},
+ {DEL_KEY, "KEY", 0, "Key", ""},
+ {0, NULL, 0, NULL, NULL}};
+
+static void set_delete_particle(PEData *data, int pa_index)
+{
+ PTCacheEdit *edit= data->edit;
+
+ edit->points[pa_index].flag |= PEP_TAG;
+}
+
+static void set_delete_particle_key(PEData *data, int pa_index, int key_index)
+{
+ PTCacheEdit *edit= data->edit;
+
+ edit->points[pa_index].keys[key_index].flag |= PEK_TAG;
+}
+
+static int delete_exec(bContext *C, wmOperator *op)
+{
+ PEData data;
+ int type= RNA_enum_get(op->ptr, "type");
+
+ PE_set_data(C, &data);
+
+ if (type == DEL_KEY) {
+ foreach_selected_key(&data, set_delete_particle_key);
+ remove_tagged_keys(data.ob, data.edit->psys);
+ recalc_lengths(data.edit);
+ }
+ else if (type == DEL_PARTICLE) {
+ foreach_selected_point(&data, set_delete_particle);
+ remove_tagged_particles(data.ob, data.edit->psys, pe_x_mirror(data.ob));
+ recalc_lengths(data.edit);
+ }
+
+ DAG_id_tag_update(&data.ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, data.ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_delete(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete";
+ ot->idname = "PARTICLE_OT_delete";
+ ot->description = "Delete selected particles or keys";
+
+ /* api callbacks */
+ ot->exec = delete_exec;
+ ot->invoke = WM_menu_invoke;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ ot->prop = RNA_def_enum(ot->srna, "type", delete_type_items, DEL_PARTICLE, "Type", "Delete a full particle or only keys");
+}
+
+/*************************** mirror operator **************************/
+
+static void PE_mirror_x(Scene *scene, Object *ob, int tagged)
+{
+ Mesh *me= (Mesh *)(ob->data);
+ ParticleSystemModifierData *psmd;
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ParticleSystem *psys = edit->psys;
+ ParticleData *pa, *newpa, *new_pars;
+ PTCacheEditPoint *newpoint, *new_points;
+ POINT_P; KEY_K;
+ HairKey *hkey;
+ int *mirrorfaces = NULL;
+ int rotation, totpart, newtotpart;
+
+ if (psys->flag & PSYS_GLOBAL_HAIR)
+ return;
+
+ psmd= psys_get_modifier(ob, psys);
+ if (!psmd->dm_final)
+ return;
+
+ const bool use_dm_final_indices = (psys->part->use_modifier_stack && !psmd->dm_final->deformedOnly);
+
+ /* NOTE: this is not nice to use tessfaces but hard to avoid since pa->num uses tessfaces */
+ BKE_mesh_tessface_ensure(me);
+
+ /* Note: In case psys uses DM tessface indices, we mirror final DM itself, not orig mesh. Avoids an (impossible)
+ * dm -> orig -> dm tessface indices conversion... */
+ mirrorfaces = mesh_get_x_mirror_faces(ob, NULL, use_dm_final_indices ? psmd->dm_final : NULL);
+
+ if (!edit->mirror_cache)
+ PE_update_mirror_cache(ob, psys);
+
+ totpart= psys->totpart;
+ newtotpart= psys->totpart;
+ LOOP_VISIBLE_POINTS {
+ pa = psys->particles + p;
+
+ if (!tagged) {
+ if (point_is_selected(point)) {
+ if (edit->mirror_cache[p] != -1) {
+ /* already has a mirror, don't need to duplicate */
+ PE_mirror_particle(ob, psmd->dm_final, psys, pa, NULL);
+ continue;
+ }
+ else
+ point->flag |= PEP_TAG;
+ }
+ }
+
+ if ((point->flag & PEP_TAG) && mirrorfaces[pa->num*2] != -1)
+ newtotpart++;
+ }
+
+ if (newtotpart != psys->totpart) {
+ MFace *mtessface = use_dm_final_indices ? psmd->dm_final->getTessFaceArray(psmd->dm_final) : me->mface;
+
+ /* allocate new arrays and copy existing */
+ new_pars= MEM_callocN(newtotpart*sizeof(ParticleData), "ParticleData new");
+ new_points= MEM_callocN(newtotpart*sizeof(PTCacheEditPoint), "PTCacheEditPoint new");
+
+ if (psys->particles) {
+ memcpy(new_pars, psys->particles, totpart*sizeof(ParticleData));
+ MEM_freeN(psys->particles);
+ }
+ psys->particles= new_pars;
+
+ if (edit->points) {
+ memcpy(new_points, edit->points, totpart*sizeof(PTCacheEditPoint));
+ MEM_freeN(edit->points);
+ }
+ edit->points= new_points;
+
+ if (edit->mirror_cache) {
+ MEM_freeN(edit->mirror_cache);
+ edit->mirror_cache= NULL;
+ }
+
+ edit->totpoint= psys->totpart= newtotpart;
+
+ /* create new elements */
+ newpa= psys->particles + totpart;
+ newpoint= edit->points + totpart;
+
+ for (p=0, point=edit->points; p<totpart; p++, point++) {
+ pa = psys->particles + p;
+ const int pa_num = pa->num;
+
+ if (point->flag & PEP_HIDE)
+ continue;
+
+ if (!(point->flag & PEP_TAG) || mirrorfaces[pa_num * 2] == -1)
+ continue;
+
+ /* duplicate */
+ *newpa= *pa;
+ *newpoint= *point;
+ if (pa->hair) newpa->hair= MEM_dupallocN(pa->hair);
+ if (point->keys) newpoint->keys= MEM_dupallocN(point->keys);
+
+ /* rotate weights according to vertex index rotation */
+ rotation= mirrorfaces[pa_num * 2 + 1];
+ newpa->fuv[0] = pa->fuv[2];
+ newpa->fuv[1] = pa->fuv[1];
+ newpa->fuv[2] = pa->fuv[0];
+ newpa->fuv[3] = pa->fuv[3];
+ while (rotation--) {
+ if (mtessface[pa_num].v4) {
+ SHIFT4(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2], newpa->fuv[3]);
+ }
+ else {
+ SHIFT3(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2]);
+ }
+ }
+
+ /* assign face index */
+ /* NOTE: mesh_get_x_mirror_faces generates -1 for non-found mirror, same as DMCACHE_NOTFOUND... */
+ newpa->num = mirrorfaces[pa_num * 2];
+
+ if (use_dm_final_indices) {
+ newpa->num_dmcache = DMCACHE_ISCHILD;
+ }
+ else {
+ newpa->num_dmcache = psys_particle_dm_face_lookup(
+ psmd->dm_final, psmd->dm_deformed, newpa->num, newpa->fuv, NULL);
+ }
+
+ /* update edit key pointers */
+ key= newpoint->keys;
+ for (k=0, hkey=newpa->hair; k<newpa->totkey; k++, hkey++, key++) {
+ key->co= hkey->co;
+ key->time= &hkey->time;
+ }
+
+ /* map key positions as mirror over x axis */
+ PE_mirror_particle(ob, psmd->dm_final, psys, pa, newpa);
+
+ newpa++;
+ newpoint++;
+ }
+ }
+
+ LOOP_POINTS {
+ point->flag &= ~PEP_TAG;
+ }
+
+ MEM_freeN(mirrorfaces);
+}
+
+static int mirror_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+
+ PE_mirror_x(scene, ob, 0);
+
+ update_world_cos(ob, edit);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_mirror(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Mirror";
+ ot->idname = "PARTICLE_OT_mirror";
+ ot->description = "Duplicate and mirror the selected particles along the local X axis";
+
+ /* api callbacks */
+ ot->exec = mirror_exec;
+ ot->poll = PE_hair_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************* brush edit callbacks ********************/
+
+static void brush_comb(PEData *data, float UNUSED(mat[4][4]), float imat[4][4], int point_index, int key_index, PTCacheEditKey *key)
+{
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ float cvec[3], fac;
+
+ if (pset->flag & PE_LOCK_FIRST && key_index == 0) return;
+
+ fac= (float)pow((double)(1.0f - data->dist / data->rad), (double)data->combfac);
+
+ copy_v3_v3(cvec, data->dvec);
+ mul_mat3_m4_v3(imat, cvec);
+ mul_v3_fl(cvec, fac);
+ add_v3_v3(key->co, cvec);
+
+ (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
+}
+
+static void brush_cut(PEData *data, int pa_index)
+{
+ PTCacheEdit *edit = data->edit;
+ ARegion *ar= data->vc.ar;
+ Object *ob= data->ob;
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ ParticleCacheKey *key= edit->pathcache[pa_index];
+ float rad2, cut_time= 1.0;
+ float x0, x1, v0, v1, o0, o1, xo0, xo1, d, dv;
+ int k, cut, keys= (int)pow(2.0, (double)pset->draw_step);
+ int screen_co[2];
+
+ /* blunt scissors */
+ if (BLI_frand() > data->cutfac) return;
+
+ /* don't cut hidden */
+ if (edit->points[pa_index].flag & PEP_HIDE)
+ return;
+
+ if (ED_view3d_project_int_global(ar, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK)
+ return;
+
+ rad2= data->rad * data->rad;
+
+ cut=0;
+
+ x0 = (float)screen_co[0];
+ x1 = (float)screen_co[1];
+
+ o0= (float)data->mval[0];
+ o1= (float)data->mval[1];
+
+ xo0= x0 - o0;
+ xo1= x1 - o1;
+
+ /* check if root is inside circle */
+ if (xo0*xo0 + xo1*xo1 < rad2 && key_test_depth(data, key->co, screen_co)) {
+ cut_time= -1.0f;
+ cut= 1;
+ }
+ else {
+ /* calculate path time closest to root that was inside the circle */
+ for (k=1, key++; k<=keys; k++, key++) {
+
+ if ((ED_view3d_project_int_global(ar, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK) ||
+ key_test_depth(data, key->co, screen_co) == 0)
+ {
+ x0 = (float)screen_co[0];
+ x1 = (float)screen_co[1];
+
+ xo0= x0 - o0;
+ xo1= x1 - o1;
+ continue;
+ }
+
+ v0 = (float)screen_co[0] - x0;
+ v1 = (float)screen_co[1] - x1;
+
+ dv= v0*v0 + v1*v1;
+
+ d= (v0*xo1 - v1*xo0);
+
+ d= dv * rad2 - d*d;
+
+ if (d > 0.0f) {
+ d= sqrtf(d);
+
+ cut_time= -(v0*xo0 + v1*xo1 + d);
+
+ if (cut_time > 0.0f) {
+ cut_time /= dv;
+
+ if (cut_time < 1.0f) {
+ cut_time += (float)(k-1);
+ cut_time /= (float)keys;
+ cut= 1;
+ break;
+ }
+ }
+ }
+
+ x0 = (float)screen_co[0];
+ x1 = (float)screen_co[1];
+
+ xo0= x0 - o0;
+ xo1= x1 - o1;
+ }
+ }
+
+ if (cut) {
+ if (cut_time < 0.0f) {
+ edit->points[pa_index].flag |= PEP_TAG;
+ }
+ else {
+ rekey_particle_to_time(data->scene, ob, pa_index, cut_time);
+ edit->points[pa_index].flag |= PEP_EDIT_RECALC;
+ }
+ }
+}
+
+static void brush_length(PEData *data, int point_index)
+{
+ PTCacheEdit *edit= data->edit;
+ PTCacheEditPoint *point = edit->points + point_index;
+ KEY_K;
+ float dvec[3], pvec[3] = {0.0f, 0.0f, 0.0f};
+
+ LOOP_KEYS {
+ if (k==0) {
+ copy_v3_v3(pvec, key->co);
+ }
+ else {
+ sub_v3_v3v3(dvec, key->co, pvec);
+ copy_v3_v3(pvec, key->co);
+ mul_v3_fl(dvec, data->growfac);
+ add_v3_v3v3(key->co, (key-1)->co, dvec);
+ }
+ }
+
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void brush_puff(PEData *data, int point_index)
+{
+ PTCacheEdit *edit = data->edit;
+ ParticleSystem *psys = edit->psys;
+ PTCacheEditPoint *point = edit->points + point_index;
+ KEY_K;
+ float mat[4][4], imat[4][4];
+
+ float onor_prev[3]; /* previous normal (particle-space) */
+ float ofs_prev[3]; /* accumulate offset for puff_volume (particle-space) */
+ float co_root[3], no_root[3]; /* root location and normal (global-space) */
+ float co_prev[3], co[3]; /* track key coords as we loop (global-space) */
+ float fac = 0.0f, length_accum = 0.0f;
+ bool puff_volume = false;
+ bool changed = false;
+
+ zero_v3(ofs_prev);
+
+ {
+ ParticleEditSettings *pset= PE_settings(data->scene);
+ ParticleBrushData *brush= &pset->brush[pset->brushtype];
+ puff_volume = (brush->flag & PE_BRUSH_DATA_PUFF_VOLUME) != 0;
+ }
+
+ if (psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
+ psys_mat_hair_to_global(data->ob, data->dm, psys->part->from, psys->particles + point_index, mat);
+ invert_m4_m4(imat, mat);
+ }
+ else {
+ unit_m4(mat);
+ unit_m4(imat);
+ }
+
+ LOOP_KEYS {
+ float kco[3];
+
+ if (k==0) {
+ /* find root coordinate and normal on emitter */
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ mul_v3_m4v3(kco, data->ob->imat, co); /* use 'kco' as the object space version of worldspace 'co', ob->imat is set before calling */
+
+ point_index= BLI_kdtree_find_nearest(edit->emitter_field, kco, NULL);
+ if (point_index == -1) return;
+
+ copy_v3_v3(co_root, co);
+ copy_v3_v3(no_root, &edit->emitter_cosnos[point_index * 6 + 3]);
+ mul_mat3_m4_v3(data->ob->obmat, no_root); /* normal into global-space */
+ normalize_v3(no_root);
+
+ if (puff_volume) {
+ copy_v3_v3(onor_prev, no_root);
+ mul_mat3_m4_v3(imat, onor_prev); /* global-space into particle space */
+ normalize_v3(onor_prev);
+ }
+
+ fac= (float)pow((double)(1.0f - data->dist / data->rad), (double)data->pufffac);
+ fac *= 0.025f;
+ if (data->invert)
+ fac= -fac;
+ }
+ else {
+ /* compute position as if hair was standing up straight.
+ * */
+ float length;
+ copy_v3_v3(co_prev, co);
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ length = len_v3v3(co_prev, co);
+ length_accum += length;
+
+ if ((data->select==0 || (key->flag & PEK_SELECT)) && !(key->flag & PEK_HIDE)) {
+ float dco[3]; /* delta temp var */
+
+ madd_v3_v3v3fl(kco, co_root, no_root, length_accum);
+
+ /* blend between the current and straight position */
+ sub_v3_v3v3(dco, kco, co);
+ madd_v3_v3fl(co, dco, fac);
+ /* keep the same distance from the root or we get glitches [#35406] */
+ dist_ensure_v3_v3fl(co, co_root, length_accum);
+
+ /* re-use dco to compare before and after translation and add to the offset */
+ copy_v3_v3(dco, key->co);
+
+ mul_v3_m4v3(key->co, imat, co);
+
+ if (puff_volume) {
+ /* accumulate the total distance moved to apply to unselected
+ * keys that come after */
+ sub_v3_v3v3(ofs_prev, key->co, dco);
+ }
+ changed = true;
+ }
+ else {
+
+ if (puff_volume) {
+#if 0
+ /* this is simple but looks bad, adds annoying kinks */
+ add_v3_v3(key->co, ofs);
+#else
+ /* translate (not rotate) the rest of the hair if its not selected */
+ {
+#if 0 /* kindof works but looks worse then whats below */
+
+ /* Move the unselected point on a vector based on the
+ * hair direction and the offset */
+ float c1[3], c2[3];
+ sub_v3_v3v3(dco, lastco, co);
+ mul_mat3_m4_v3(imat, dco); /* into particle space */
+
+ /* move the point along a vector perpendicular to the
+ * hairs direction, reduces odd kinks, */
+ cross_v3_v3v3(c1, ofs, dco);
+ cross_v3_v3v3(c2, c1, dco);
+ normalize_v3(c2);
+ mul_v3_fl(c2, len_v3(ofs));
+ add_v3_v3(key->co, c2);
+#else
+ /* Move the unselected point on a vector based on the
+ * the normal of the closest geometry */
+ float oco[3], onor[3];
+ copy_v3_v3(oco, key->co);
+ mul_m4_v3(mat, oco);
+ mul_v3_m4v3(kco, data->ob->imat, oco); /* use 'kco' as the object space version of worldspace 'co', ob->imat is set before calling */
+
+ point_index= BLI_kdtree_find_nearest(edit->emitter_field, kco, NULL);
+ if (point_index != -1) {
+ copy_v3_v3(onor, &edit->emitter_cosnos[point_index*6+3]);
+ mul_mat3_m4_v3(data->ob->obmat, onor); /* normal into worldspace */
+ mul_mat3_m4_v3(imat, onor); /* worldspace into particle space */
+ normalize_v3(onor);
+ }
+ else {
+ copy_v3_v3(onor, onor_prev);
+ }
+
+ if (!is_zero_v3(ofs_prev)) {
+ mul_v3_fl(onor, len_v3(ofs_prev));
+
+ add_v3_v3(key->co, onor);
+ }
+
+ copy_v3_v3(onor_prev, onor);
+#endif
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ if (changed)
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+
+static void BKE_brush_weight_get(PEData *data, float UNUSED(mat[4][4]), float UNUSED(imat[4][4]), int point_index, int key_index, PTCacheEditKey *UNUSED(key))
+{
+ /* roots have full weight always */
+ if (key_index) {
+ PTCacheEdit *edit = data->edit;
+ ParticleSystem *psys = edit->psys;
+
+ ParticleData *pa= psys->particles + point_index;
+ pa->hair[key_index].weight = data->weightfac;
+
+ (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
+ }
+}
+
+static void brush_smooth_get(PEData *data, float mat[4][4], float UNUSED(imat[4][4]), int UNUSED(point_index), int key_index, PTCacheEditKey *key)
+{
+ if (key_index) {
+ float dvec[3];
+
+ sub_v3_v3v3(dvec, key->co, (key-1)->co);
+ mul_mat3_m4_v3(mat, dvec);
+ add_v3_v3(data->vec, dvec);
+ data->tot++;
+ }
+}
+
+static void brush_smooth_do(PEData *data, float UNUSED(mat[4][4]), float imat[4][4], int point_index, int key_index, PTCacheEditKey *key)
+{
+ float vec[3], dvec[3];
+
+ if (key_index) {
+ copy_v3_v3(vec, data->vec);
+ mul_mat3_m4_v3(imat, vec);
+
+ sub_v3_v3v3(dvec, key->co, (key-1)->co);
+
+ sub_v3_v3v3(dvec, vec, dvec);
+ mul_v3_fl(dvec, data->smoothfac);
+
+ add_v3_v3(key->co, dvec);
+ }
+
+ (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
+}
+
+/* convert from triangle barycentric weights to quad mean value weights */
+static void intersect_dm_quad_weights(const float v1[3], const float v2[3], const float v3[3], const float v4[3], float w[4])
+{
+ float co[3], vert[4][3];
+
+ copy_v3_v3(vert[0], v1);
+ copy_v3_v3(vert[1], v2);
+ copy_v3_v3(vert[2], v3);
+ copy_v3_v3(vert[3], v4);
+
+ co[0] = v1[0]*w[0] + v2[0]*w[1] + v3[0]*w[2] + v4[0]*w[3];
+ co[1] = v1[1]*w[0] + v2[1]*w[1] + v3[1]*w[2] + v4[1]*w[3];
+ co[2] = v1[2]*w[0] + v2[2]*w[1] + v3[2]*w[2] + v4[2]*w[3];
+
+ interp_weights_poly_v3(w, vert, 4, co);
+}
+
+/* check intersection with a derivedmesh */
+static int particle_intersect_dm(Scene *scene, Object *ob, DerivedMesh *dm,
+ float *vert_cos,
+ const float co1[3], const float co2[3],
+ float *min_d, int *min_face, float *min_w,
+ float *face_minmax, float *pa_minmax,
+ float radius, float *ipoint)
+{
+ MFace *mface= NULL;
+ MVert *mvert= NULL;
+ int i, totface, intersect=0;
+ float cur_d, cur_uv[2], v1[3], v2[3], v3[3], v4[3], min[3], max[3], p_min[3], p_max[3];
+ float cur_ipoint[3];
+
+ if (dm == NULL) {
+ psys_disable_all(ob);
+
+ dm=mesh_get_derived_final(scene, ob, 0);
+ if (dm == NULL)
+ dm=mesh_get_derived_deform(scene, ob, 0);
+
+ psys_enable_all(ob);
+
+ if (dm == NULL)
+ return 0;
+ }
+
+ /* BMESH_ONLY, deform dm may not have tessface */
+ DM_ensure_tessface(dm);
+
+
+ if (pa_minmax==0) {
+ INIT_MINMAX(p_min, p_max);
+ minmax_v3v3_v3(p_min, p_max, co1);
+ minmax_v3v3_v3(p_min, p_max, co2);
+ }
+ else {
+ copy_v3_v3(p_min, pa_minmax);
+ copy_v3_v3(p_max, pa_minmax+3);
+ }
+
+ totface=dm->getNumTessFaces(dm);
+ mface=dm->getTessFaceDataArray(dm, CD_MFACE);
+ mvert=dm->getVertDataArray(dm, CD_MVERT);
+
+ /* lets intersect the faces */
+ for (i=0; i<totface; i++, mface++) {
+ if (vert_cos) {
+ copy_v3_v3(v1, vert_cos+3*mface->v1);
+ copy_v3_v3(v2, vert_cos+3*mface->v2);
+ copy_v3_v3(v3, vert_cos+3*mface->v3);
+ if (mface->v4)
+ copy_v3_v3(v4, vert_cos+3*mface->v4);
+ }
+ else {
+ copy_v3_v3(v1, mvert[mface->v1].co);
+ copy_v3_v3(v2, mvert[mface->v2].co);
+ copy_v3_v3(v3, mvert[mface->v3].co);
+ if (mface->v4)
+ copy_v3_v3(v4, mvert[mface->v4].co);
+ }
+
+ if (face_minmax==0) {
+ INIT_MINMAX(min, max);
+ DO_MINMAX(v1, min, max);
+ DO_MINMAX(v2, min, max);
+ DO_MINMAX(v3, min, max);
+ if (mface->v4)
+ DO_MINMAX(v4, min, max);
+ if (isect_aabb_aabb_v3(min, max, p_min, p_max)==0)
+ continue;
+ }
+ else {
+ copy_v3_v3(min, face_minmax+6*i);
+ copy_v3_v3(max, face_minmax+6*i+3);
+ if (isect_aabb_aabb_v3(min, max, p_min, p_max)==0)
+ continue;
+ }
+
+ if (radius>0.0f) {
+ if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v2, v3, v1, &cur_d, cur_ipoint)) {
+ if (cur_d<*min_d) {
+ *min_d=cur_d;
+ copy_v3_v3(ipoint, cur_ipoint);
+ *min_face=i;
+ intersect=1;
+ }
+ }
+ if (mface->v4) {
+ if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v4, v1, v3, &cur_d, cur_ipoint)) {
+ if (cur_d<*min_d) {
+ *min_d=cur_d;
+ copy_v3_v3(ipoint, cur_ipoint);
+ *min_face=i;
+ intersect=1;
+ }
+ }
+ }
+ }
+ else {
+ if (isect_line_segment_tri_v3(co1, co2, v1, v2, v3, &cur_d, cur_uv)) {
+ if (cur_d<*min_d) {
+ *min_d=cur_d;
+ min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
+ min_w[1] = cur_uv[0];
+ min_w[2] = cur_uv[1];
+ min_w[3] = 0.0f;
+ if (mface->v4)
+ intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
+ *min_face=i;
+ intersect=1;
+ }
+ }
+ if (mface->v4) {
+ if (isect_line_segment_tri_v3(co1, co2, v1, v3, v4, &cur_d, cur_uv)) {
+ if (cur_d<*min_d) {
+ *min_d=cur_d;
+ min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
+ min_w[1] = 0.0f;
+ min_w[2] = cur_uv[0];
+ min_w[3] = cur_uv[1];
+ intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
+ *min_face=i;
+ intersect=1;
+ }
+ }
+ }
+ }
+ }
+ return intersect;
+}
+
+static int brush_add(PEData *data, short number)
+{
+ Scene *scene= data->scene;
+ Object *ob= data->ob;
+ DerivedMesh *dm;
+ PTCacheEdit *edit = data->edit;
+ ParticleSystem *psys= edit->psys;
+ ParticleData *add_pars;
+ ParticleSystemModifierData *psmd= psys_get_modifier(ob, psys);
+ ParticleSimulationData sim= {0};
+ ParticleEditSettings *pset= PE_settings(scene);
+ int i, k, n= 0, totpart= psys->totpart;
+ float mco[2];
+ float dmx, dmy;
+ float co1[3], co2[3], min_d, imat[4][4];
+ float framestep, timestep;
+ short size= pset->brush[PE_BRUSH_ADD].size;
+ short size2= size*size;
+ RNG *rng;
+
+ invert_m4_m4(imat, ob->obmat);
+
+ if (psys->flag & PSYS_GLOBAL_HAIR)
+ return 0;
+
+ add_pars = MEM_callocN(number * sizeof(ParticleData), "ParticleData add");
+
+ rng = BLI_rng_new_srandom(psys->seed+data->mval[0]+data->mval[1]);
+
+ sim.scene= scene;
+ sim.ob= ob;
+ sim.psys= psys;
+ sim.psmd= psmd;
+
+ timestep= psys_get_timestep(&sim);
+
+ if (psys->part->use_modifier_stack || psmd->dm_final->deformedOnly) {
+ dm = psmd->dm_final;
+ }
+ else {
+ dm = psmd->dm_deformed;
+ }
+ BLI_assert(dm);
+
+ for (i=0; i<number; i++) {
+ if (number>1) {
+ dmx = size;
+ dmy = size;
+
+ /* rejection sampling to get points in circle */
+ while (dmx*dmx + dmy*dmy > size2) {
+ dmx= (2.0f*BLI_rng_get_float(rng) - 1.0f)*size;
+ dmy= (2.0f*BLI_rng_get_float(rng) - 1.0f)*size;
+ }
+ }
+ else {
+ dmx = 0.0f;
+ dmy = 0.0f;
+ }
+
+ mco[0] = data->mval[0] + dmx;
+ mco[1] = data->mval[1] + dmy;
+ ED_view3d_win_to_segment(data->vc.ar, data->vc.v3d, mco, co1, co2, true);
+
+ mul_m4_v3(imat, co1);
+ mul_m4_v3(imat, co2);
+ min_d=2.0;
+
+ /* warning, returns the derived mesh face */
+ if (particle_intersect_dm(scene, ob, dm, 0, co1, co2, &min_d, &add_pars[n].num_dmcache, add_pars[n].fuv, 0, 0, 0, 0)) {
+ if (psys->part->use_modifier_stack && !psmd->dm_final->deformedOnly) {
+ add_pars[n].num = add_pars[n].num_dmcache;
+ add_pars[n].num_dmcache = DMCACHE_ISCHILD;
+ }
+ else if (dm == psmd->dm_deformed) {
+ /* Final DM is not same topology as orig mesh, we have to map num_dmcache to real final dm. */
+ add_pars[n].num = add_pars[n].num_dmcache;
+ add_pars[n].num_dmcache = psys_particle_dm_face_lookup(
+ psmd->dm_final, psmd->dm_deformed,
+ add_pars[n].num, add_pars[n].fuv, NULL);
+ }
+ else {
+ add_pars[n].num = add_pars[n].num_dmcache;
+ }
+
+ if (add_pars[n].num != DMCACHE_NOTFOUND) {
+ n++;
+ }
+ }
+ }
+ if (n) {
+ int newtotpart=totpart+n;
+ float hairmat[4][4], cur_co[3];
+ KDTree *tree=0;
+ ParticleData *pa, *new_pars = MEM_callocN(newtotpart*sizeof(ParticleData), "ParticleData new");
+ PTCacheEditPoint *point, *new_points = MEM_callocN(newtotpart*sizeof(PTCacheEditPoint), "PTCacheEditPoint array new");
+ PTCacheEditKey *key;
+ HairKey *hkey;
+
+ /* save existing elements */
+ memcpy(new_pars, psys->particles, totpart * sizeof(ParticleData));
+ memcpy(new_points, edit->points, totpart * sizeof(PTCacheEditPoint));
+
+ /* change old arrays to new ones */
+ if (psys->particles) MEM_freeN(psys->particles);
+ psys->particles= new_pars;
+
+ if (edit->points) MEM_freeN(edit->points);
+ edit->points= new_points;
+
+ if (edit->mirror_cache) {
+ MEM_freeN(edit->mirror_cache);
+ edit->mirror_cache= NULL;
+ }
+
+ /* create tree for interpolation */
+ if (pset->flag & PE_INTERPOLATE_ADDED && psys->totpart) {
+ tree=BLI_kdtree_new(psys->totpart);
+
+ for (i=0, pa=psys->particles; i<totpart; i++, pa++) {
+ psys_particle_on_dm(psmd->dm_final, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, cur_co, 0, 0, 0, 0, 0);
+ BLI_kdtree_insert(tree, i, cur_co);
+ }
+
+ BLI_kdtree_balance(tree);
+ }
+
+ edit->totpoint= psys->totpart= newtotpart;
+
+ /* create new elements */
+ pa = psys->particles + totpart;
+ point = edit->points + totpart;
+
+ for (i=totpart; i<newtotpart; i++, pa++, point++) {
+ memcpy(pa, add_pars + i - totpart, sizeof(ParticleData));
+ pa->hair= MEM_callocN(pset->totaddkey * sizeof(HairKey), "BakeKey key add");
+ key= point->keys= MEM_callocN(pset->totaddkey * sizeof(PTCacheEditKey), "PTCacheEditKey add");
+ point->totkey= pa->totkey= pset->totaddkey;
+
+ for (k=0, hkey=pa->hair; k<pa->totkey; k++, hkey++, key++) {
+ key->co= hkey->co;
+ key->time= &hkey->time;
+
+ if (!(psys->flag & PSYS_GLOBAL_HAIR))
+ key->flag |= PEK_USE_WCO;
+ }
+
+ pa->size= 1.0f;
+ initialize_particle(&sim, pa);
+ reset_particle(&sim, pa, 0.0, 1.0);
+ point->flag |= PEP_EDIT_RECALC;
+ if (pe_x_mirror(ob))
+ point->flag |= PEP_TAG; /* signal for duplicate */
+
+ framestep= pa->lifetime/(float)(pset->totaddkey-1);
+
+ if (tree) {
+ ParticleData *ppa;
+ HairKey *thkey;
+ ParticleKey key3[3];
+ KDTreeNearest ptn[3];
+ int w, maxw;
+ float maxd, totw=0.0, weight[3];
+
+ psys_particle_on_dm(psmd->dm_final, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, co1, 0, 0, 0, 0, 0);
+ maxw = BLI_kdtree_find_nearest_n(tree, co1, ptn, 3);
+
+ maxd= ptn[maxw-1].dist;
+
+ for (w=0; w<maxw; w++) {
+ weight[w] = (float)pow(2.0, (double)(-6.0f * ptn[w].dist / maxd));
+ totw += weight[w];
+ }
+ for (;w<3; w++) {
+ weight[w] = 0.0f;
+ }
+
+ if (totw > 0.0f) {
+ for (w=0; w<maxw; w++)
+ weight[w] /= totw;
+ }
+ else {
+ for (w=0; w<maxw; w++)
+ weight[w] = 1.0f/maxw;
+ }
+
+ ppa= psys->particles+ptn[0].index;
+
+ for (k=0; k<pset->totaddkey; k++) {
+ thkey= (HairKey *)pa->hair + k;
+ thkey->time= pa->time + k * framestep;
+
+ key3[0].time= thkey->time/ 100.0f;
+ psys_get_particle_on_path(&sim, ptn[0].index, key3, 0);
+ mul_v3_fl(key3[0].co, weight[0]);
+
+ /* TODO: interpolating the weight would be nicer */
+ thkey->weight= (ppa->hair+MIN2(k, ppa->totkey-1))->weight;
+
+ if (maxw>1) {
+ key3[1].time= key3[0].time;
+ psys_get_particle_on_path(&sim, ptn[1].index, &key3[1], 0);
+ mul_v3_fl(key3[1].co, weight[1]);
+ add_v3_v3(key3[0].co, key3[1].co);
+
+ if (maxw>2) {
+ key3[2].time= key3[0].time;
+ psys_get_particle_on_path(&sim, ptn[2].index, &key3[2], 0);
+ mul_v3_fl(key3[2].co, weight[2]);
+ add_v3_v3(key3[0].co, key3[2].co);
+ }
+ }
+
+ if (k==0)
+ sub_v3_v3v3(co1, pa->state.co, key3[0].co);
+
+ add_v3_v3v3(thkey->co, key3[0].co, co1);
+
+ thkey->time= key3[0].time;
+ }
+ }
+ else {
+ for (k=0, hkey=pa->hair; k<pset->totaddkey; k++, hkey++) {
+ madd_v3_v3v3fl(hkey->co, pa->state.co, pa->state.vel, k * framestep * timestep);
+ hkey->time += k * framestep;
+ hkey->weight = 1.f - (float)k/(float)(pset->totaddkey-1);
+ }
+ }
+ for (k=0, hkey=pa->hair; k<pset->totaddkey; k++, hkey++) {
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, pa, hairmat);
+ invert_m4_m4(imat, hairmat);
+ mul_m4_v3(imat, hkey->co);
+ }
+ }
+
+ if (tree)
+ BLI_kdtree_free(tree);
+ }
+
+ MEM_freeN(add_pars);
+
+ BLI_rng_free(rng);
+
+ return n;
+}
+
+/************************* brush edit operator ********************/
+
+typedef struct BrushEdit {
+ Scene *scene;
+ Object *ob;
+ PTCacheEdit *edit;
+
+ int first;
+ int lastmouse[2];
+ float zfac;
+
+ /* optional cached view settings to avoid setting on every mousemove */
+ PEData data;
+} BrushEdit;
+
+static int brush_edit_init(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ ParticleEditSettings *pset= PE_settings(scene);
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ARegion *ar= CTX_wm_region(C);
+ BrushEdit *bedit;
+ float min[3], max[3];
+
+ if (pset->brushtype < 0)
+ return 0;
+
+ /* set the 'distance factor' for grabbing (used in comb etc) */
+ INIT_MINMAX(min, max);
+ PE_minmax(scene, min, max);
+ mid_v3_v3v3(min, min, max);
+
+ bedit= MEM_callocN(sizeof(BrushEdit), "BrushEdit");
+ bedit->first= 1;
+ op->customdata= bedit;
+
+ bedit->scene= scene;
+ bedit->ob= ob;
+ bedit->edit= edit;
+
+ bedit->zfac = ED_view3d_calc_zfac(ar->regiondata, min, NULL);
+
+ /* cache view depths and settings for re-use */
+ PE_set_view3d_data(C, &bedit->data);
+
+ return 1;
+}
+
+static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
+{
+ BrushEdit *bedit= op->customdata;
+ Scene *scene= bedit->scene;
+ Object *ob= bedit->ob;
+ PTCacheEdit *edit= bedit->edit;
+ ParticleEditSettings *pset= PE_settings(scene);
+ ParticleSystemModifierData *psmd= edit->psys ? psys_get_modifier(ob, edit->psys) : NULL;
+ ParticleBrushData *brush= &pset->brush[pset->brushtype];
+ ARegion *ar= CTX_wm_region(C);
+ float vec[3], mousef[2];
+ int mval[2];
+ int flip, mouse[2], removed= 0, added=0, selected= 0, tot_steps= 1, step= 1;
+ float dx, dy, dmax;
+ int lock_root = pset->flag & PE_LOCK_FIRST;
+
+ if (!PE_start_edit(edit))
+ return;
+
+ RNA_float_get_array(itemptr, "mouse", mousef);
+ mouse[0] = mousef[0];
+ mouse[1] = mousef[1];
+ flip= RNA_boolean_get(itemptr, "pen_flip");
+
+ if (bedit->first) {
+ bedit->lastmouse[0] = mouse[0];
+ bedit->lastmouse[1] = mouse[1];
+ }
+
+ dx= mouse[0] - bedit->lastmouse[0];
+ dy= mouse[1] - bedit->lastmouse[1];
+
+ mval[0] = mouse[0];
+ mval[1] = mouse[1];
+
+
+ /* disable locking temporatily for disconnected hair */
+ if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
+ pset->flag &= ~PE_LOCK_FIRST;
+
+ if (((pset->brushtype == PE_BRUSH_ADD) ?
+ (sqrtf(dx * dx + dy * dy) > pset->brush[PE_BRUSH_ADD].step) : (dx != 0 || dy != 0)) || bedit->first)
+ {
+ PEData data= bedit->data;
+
+ view3d_operator_needs_opengl(C);
+ selected= (short)count_selected_keys(scene, edit);
+
+ dmax = max_ff(fabsf(dx), fabsf(dy));
+ tot_steps = dmax/(0.2f * pe_brush_size_get(scene, brush)) + 1;
+
+ dx /= (float)tot_steps;
+ dy /= (float)tot_steps;
+
+ for (step = 1; step<=tot_steps; step++) {
+ mval[0] = bedit->lastmouse[0] + step*dx;
+ mval[1] = bedit->lastmouse[1] + step*dy;
+
+ switch (pset->brushtype) {
+ case PE_BRUSH_COMB:
+ {
+ const float mval_f[2] = {dx, dy};
+ data.mval= mval;
+ data.rad= pe_brush_size_get(scene, brush);
+
+ data.combfac= (brush->strength - 0.5f) * 2.0f;
+ if (data.combfac < 0.0f)
+ data.combfac= 1.0f - 9.0f * data.combfac;
+ else
+ data.combfac= 1.0f - data.combfac;
+
+ invert_m4_m4(ob->imat, ob->obmat);
+
+ ED_view3d_win_to_delta(ar, mval_f, vec, bedit->zfac);
+ data.dvec= vec;
+
+ foreach_mouse_hit_key(&data, brush_comb, selected);
+ break;
+ }
+ case PE_BRUSH_CUT:
+ {
+ if (edit->psys && edit->pathcache) {
+ data.mval= mval;
+ data.rad= pe_brush_size_get(scene, brush);
+ data.cutfac= brush->strength;
+
+ if (selected)
+ foreach_selected_point(&data, brush_cut);
+ else
+ foreach_point(&data, brush_cut);
+
+ removed= remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
+ if (pset->flag & PE_KEEP_LENGTHS)
+ recalc_lengths(edit);
+ }
+ else
+ removed= 0;
+
+ break;
+ }
+ case PE_BRUSH_LENGTH:
+ {
+ data.mval= mval;
+
+ data.rad= pe_brush_size_get(scene, brush);
+ data.growfac= brush->strength / 50.0f;
+
+ if (brush->invert ^ flip)
+ data.growfac= 1.0f - data.growfac;
+ else
+ data.growfac= 1.0f + data.growfac;
+
+ foreach_mouse_hit_point(&data, brush_length, selected);
+
+ if (pset->flag & PE_KEEP_LENGTHS)
+ recalc_lengths(edit);
+ break;
+ }
+ case PE_BRUSH_PUFF:
+ {
+ if (edit->psys) {
+ data.dm= psmd->dm_final;
+ data.mval= mval;
+ data.rad= pe_brush_size_get(scene, brush);
+ data.select= selected;
+
+ data.pufffac= (brush->strength - 0.5f) * 2.0f;
+ if (data.pufffac < 0.0f)
+ data.pufffac= 1.0f - 9.0f * data.pufffac;
+ else
+ data.pufffac= 1.0f - data.pufffac;
+
+ data.invert= (brush->invert ^ flip);
+ invert_m4_m4(ob->imat, ob->obmat);
+
+ foreach_mouse_hit_point(&data, brush_puff, selected);
+ }
+ break;
+ }
+ case PE_BRUSH_ADD:
+ {
+ if (edit->psys && edit->psys->part->from==PART_FROM_FACE) {
+ data.mval= mval;
+
+ added= brush_add(&data, brush->count);
+
+ if (pset->flag & PE_KEEP_LENGTHS)
+ recalc_lengths(edit);
+ }
+ else
+ added= 0;
+ break;
+ }
+ case PE_BRUSH_SMOOTH:
+ {
+ data.mval= mval;
+ data.rad= pe_brush_size_get(scene, brush);
+
+ data.vec[0] = data.vec[1] = data.vec[2] = 0.0f;
+ data.tot= 0;
+
+ data.smoothfac= brush->strength;
+
+ invert_m4_m4(ob->imat, ob->obmat);
+
+ foreach_mouse_hit_key(&data, brush_smooth_get, selected);
+
+ if (data.tot) {
+ mul_v3_fl(data.vec, 1.0f / (float)data.tot);
+ foreach_mouse_hit_key(&data, brush_smooth_do, selected);
+ }
+
+ break;
+ }
+ case PE_BRUSH_WEIGHT:
+ {
+ if (edit->psys) {
+ data.dm= psmd->dm_final;
+ data.mval= mval;
+ data.rad= pe_brush_size_get(scene, brush);
+
+ data.weightfac = brush->strength; /* note that this will never be zero */
+
+ foreach_mouse_hit_key(&data, BKE_brush_weight_get, selected);
+ }
+
+ break;
+ }
+ }
+ if ((pset->flag & PE_KEEP_LENGTHS)==0)
+ recalc_lengths(edit);
+
+ if (ELEM(pset->brushtype, PE_BRUSH_ADD, PE_BRUSH_CUT) && (added || removed)) {
+ if (pset->brushtype == PE_BRUSH_ADD && pe_x_mirror(ob))
+ PE_mirror_x(scene, ob, 1);
+
+ update_world_cos(ob, edit);
+ psys_free_path_cache(NULL, edit);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ }
+ else
+ PE_update_object(scene, ob, 1);
+ }
+
+ if (edit->psys) {
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ }
+ else {
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
+ }
+
+ bedit->lastmouse[0] = mouse[0];
+ bedit->lastmouse[1] = mouse[1];
+ bedit->first= 0;
+ }
+
+ pset->flag |= lock_root;
+}
+
+static void brush_edit_exit(wmOperator *op)
+{
+ BrushEdit *bedit= op->customdata;
+
+ MEM_freeN(bedit);
+}
+
+static int brush_edit_exec(bContext *C, wmOperator *op)
+{
+ if (!brush_edit_init(C, op))
+ return OPERATOR_CANCELLED;
+
+ RNA_BEGIN (op->ptr, itemptr, "stroke")
+ {
+ brush_edit_apply(C, op, &itemptr);
+ }
+ RNA_END;
+
+ brush_edit_exit(op);
+
+ return OPERATOR_FINISHED;
+}
+
+static void brush_edit_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ PointerRNA itemptr;
+ float mouse[2];
+
+ VECCOPY2D(mouse, event->mval);
+
+ /* fill in stroke */
+ RNA_collection_add(op->ptr, "stroke", &itemptr);
+
+ RNA_float_set_array(&itemptr, "mouse", mouse);
+ RNA_boolean_set(&itemptr, "pen_flip", event->shift != false); // XXX hardcoded
+
+ /* apply */
+ brush_edit_apply(C, op, &itemptr);
+}
+
+static int brush_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ if (!brush_edit_init(C, op))
+ return OPERATOR_CANCELLED;
+
+ brush_edit_apply_event(C, op, event);
+
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int brush_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ switch (event->type) {
+ case LEFTMOUSE:
+ case MIDDLEMOUSE:
+ case RIGHTMOUSE: // XXX hardcoded
+ brush_edit_exit(op);
+ return OPERATOR_FINISHED;
+ case MOUSEMOVE:
+ brush_edit_apply_event(C, op, event);
+ break;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void brush_edit_cancel(bContext *UNUSED(C), wmOperator *op)
+{
+ brush_edit_exit(op);
+}
+
+void PARTICLE_OT_brush_edit(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Brush Edit";
+ ot->idname = "PARTICLE_OT_brush_edit";
+ ot->description = "Apply a stroke of brush to the particles";
+
+ /* api callbacks */
+ ot->exec = brush_edit_exec;
+ ot->invoke = brush_edit_invoke;
+ ot->modal = brush_edit_modal;
+ ot->cancel = brush_edit_cancel;
+ ot->poll = PE_poll_view3d;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
+
+ /* properties */
+ RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+}
+
+/*********************** cut shape ***************************/
+
+static int shape_cut_poll(bContext *C)
+{
+ if (PE_hair_poll(C)) {
+ Scene *scene = CTX_data_scene(C);
+ ParticleEditSettings *pset = PE_settings(scene);
+
+ if (pset->shape_object && (pset->shape_object->type == OB_MESH)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+typedef struct PointInsideBVH {
+ BVHTreeFromMesh bvhdata;
+ int num_hits;
+} PointInsideBVH;
+
+static void point_inside_bvh_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
+{
+ PointInsideBVH *data = userdata;
+
+ data->bvhdata.raycast_callback(&data->bvhdata, index, ray, hit);
+
+ if (hit->index != -1)
+ ++data->num_hits;
+}
+
+/* true if the point is inside the shape mesh */
+static bool shape_cut_test_point(PEData *data, ParticleCacheKey *key)
+{
+ BVHTreeFromMesh *shape_bvh = &data->shape_bvh;
+ const float dir[3] = {1.0f, 0.0f, 0.0f};
+ PointInsideBVH userdata;
+
+ userdata.bvhdata = data->shape_bvh;
+ userdata.num_hits = 0;
+
+ BLI_bvhtree_ray_cast_all(
+ shape_bvh->tree, key->co, dir, 0.0f, BVH_RAYCAST_DIST_MAX,
+ point_inside_bvh_cb, &userdata);
+
+ /* for any point inside a watertight mesh the number of hits is uneven */
+ return (userdata.num_hits % 2) == 1;
+}
+
+static void shape_cut(PEData *data, int pa_index)
+{
+ PTCacheEdit *edit = data->edit;
+ Object *ob = data->ob;
+ ParticleEditSettings *pset = PE_settings(data->scene);
+ ParticleCacheKey *key;
+
+ bool cut;
+ float cut_time = 1.0;
+ int k, totkeys = 1 << pset->draw_step;
+
+ /* don't cut hidden */
+ if (edit->points[pa_index].flag & PEP_HIDE)
+ return;
+
+ cut = false;
+
+ /* check if root is inside the cut shape */
+ key = edit->pathcache[pa_index];
+ if (!shape_cut_test_point(data, key)) {
+ cut_time = -1.0f;
+ cut = true;
+ }
+ else {
+ for (k = 0; k < totkeys; k++, key++) {
+ BVHTreeRayHit hit;
+ float dir[3];
+ float len;
+
+ sub_v3_v3v3(dir, (key+1)->co, key->co);
+ len = normalize_v3(dir);
+
+ memset(&hit, 0, sizeof(hit));
+ hit.index = -1;
+ hit.dist = len;
+ BLI_bvhtree_ray_cast(data->shape_bvh.tree, key->co, dir, 0.0f, &hit, data->shape_bvh.raycast_callback, &data->shape_bvh);
+ if (hit.index >= 0) {
+ if (hit.dist < len) {
+ cut_time = (hit.dist / len + (float)k) / (float)totkeys;
+ cut = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (cut) {
+ if (cut_time < 0.0f) {
+ edit->points[pa_index].flag |= PEP_TAG;
+ }
+ else {
+ rekey_particle_to_time(data->scene, ob, pa_index, cut_time);
+ edit->points[pa_index].flag |= PEP_EDIT_RECALC;
+ }
+ }
+}
+
+static int shape_cut_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ ParticleEditSettings *pset = PE_settings(scene);
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ Object *shapeob = pset->shape_object;
+ int selected = count_selected_keys(scene, edit);
+ int lock_root = pset->flag & PE_LOCK_FIRST;
+
+ if (!PE_start_edit(edit))
+ return OPERATOR_CANCELLED;
+
+ /* disable locking temporatily for disconnected hair */
+ if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
+ pset->flag &= ~PE_LOCK_FIRST;
+
+ if (edit->psys && edit->pathcache) {
+ PEData data;
+ int removed;
+
+ PE_set_data(C, &data);
+ if (!PE_create_shape_tree(&data, shapeob)) {
+ /* shapeob may not have faces... */
+ return OPERATOR_CANCELLED;
+ }
+
+ if (selected)
+ foreach_selected_point(&data, shape_cut);
+ else
+ foreach_point(&data, shape_cut);
+
+ removed = remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
+ recalc_lengths(edit);
+
+ if (removed) {
+ update_world_cos(ob, edit);
+ psys_free_path_cache(NULL, edit);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ }
+ else
+ PE_update_object(scene, ob, 1);
+
+ if (edit->psys) {
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ }
+ else {
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
+ }
+
+ PE_free_shape_tree(&data);
+ }
+
+ pset->flag |= lock_root;
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_shape_cut(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Shape Cut";
+ ot->idname = "PARTICLE_OT_shape_cut";
+ ot->description = "Cut hair to conform to the set shape object";
+
+ /* api callbacks */
+ ot->exec = shape_cut_exec;
+ ot->poll = shape_cut_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/*********************** undo ***************************/
+
+static void free_PTCacheUndo(PTCacheUndo *undo)
+{
+ PTCacheEditPoint *point;
+ int i;
+
+ for (i=0, point=undo->points; i<undo->totpoint; i++, point++) {
+ if (undo->particles && (undo->particles + i)->hair)
+ MEM_freeN((undo->particles + i)->hair);
+ if (point->keys)
+ MEM_freeN(point->keys);
+ }
+ if (undo->points)
+ MEM_freeN(undo->points);
+
+ if (undo->particles)
+ MEM_freeN(undo->particles);
+
+ BKE_ptcache_free_mem(&undo->mem_cache);
+}
+
+static void make_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo)
+{
+ PTCacheEditPoint *point;
+ int i;
+
+ undo->totpoint= edit->totpoint;
+
+ if (edit->psys) {
+ ParticleData *pa;
+
+ pa= undo->particles= MEM_dupallocN(edit->psys->particles);
+
+ for (i=0; i<edit->totpoint; i++, pa++)
+ pa->hair= MEM_dupallocN(pa->hair);
+
+ undo->psys_flag = edit->psys->flag;
+ }
+ else {
+ PTCacheMem *pm;
+
+ BLI_duplicatelist(&undo->mem_cache, &edit->pid.cache->mem_cache);
+ pm = undo->mem_cache.first;
+
+ for (; pm; pm=pm->next) {
+ for (i=0; i<BPHYS_TOT_DATA; i++)
+ pm->data[i] = MEM_dupallocN(pm->data[i]);
+ }
+ }
+
+ point= undo->points = MEM_dupallocN(edit->points);
+ undo->totpoint = edit->totpoint;
+
+ for (i=0; i<edit->totpoint; i++, point++) {
+ point->keys= MEM_dupallocN(point->keys);
+ /* no need to update edit key->co & key->time pointers here */
+ }
+}
+
+static void get_PTCacheUndo(PTCacheEdit *edit, PTCacheUndo *undo)
+{
+ ParticleSystem *psys = edit->psys;
+ ParticleData *pa;
+ HairKey *hkey;
+ POINT_P; KEY_K;
+
+ LOOP_POINTS {
+ if (psys && psys->particles[p].hair)
+ MEM_freeN(psys->particles[p].hair);
+
+ if (point->keys)
+ MEM_freeN(point->keys);
+ }
+ if (psys && psys->particles)
+ MEM_freeN(psys->particles);
+ if (edit->points)
+ MEM_freeN(edit->points);
+ if (edit->mirror_cache) {
+ MEM_freeN(edit->mirror_cache);
+ edit->mirror_cache= NULL;
+ }
+
+ edit->points= MEM_dupallocN(undo->points);
+ edit->totpoint = undo->totpoint;
+
+ LOOP_POINTS {
+ point->keys= MEM_dupallocN(point->keys);
+ }
+
+ if (psys) {
+ psys->particles= MEM_dupallocN(undo->particles);
+
+ psys->totpart= undo->totpoint;
+
+ LOOP_POINTS {
+ pa = psys->particles + p;
+ hkey= pa->hair = MEM_dupallocN(pa->hair);
+
+ LOOP_KEYS {
+ key->co= hkey->co;
+ key->time= &hkey->time;
+ hkey++;
+ }
+ }
+
+ psys->flag = undo->psys_flag;
+ }
+ else {
+ PTCacheMem *pm;
+ int i;
+
+ BKE_ptcache_free_mem(&edit->pid.cache->mem_cache);
+
+ BLI_duplicatelist(&edit->pid.cache->mem_cache, &undo->mem_cache);
+
+ pm = edit->pid.cache->mem_cache.first;
+
+ for (; pm; pm=pm->next) {
+ for (i=0; i<BPHYS_TOT_DATA; i++)
+ pm->data[i] = MEM_dupallocN(pm->data[i]);
+
+ BKE_ptcache_mem_pointers_init(pm);
+
+ LOOP_POINTS {
+ LOOP_KEYS {
+ if ((int)key->ftime == (int)pm->frame) {
+ key->co = pm->cur[BPHYS_DATA_LOCATION];
+ key->vel = pm->cur[BPHYS_DATA_VELOCITY];
+ key->rot = pm->cur[BPHYS_DATA_ROTATION];
+ key->time = &key->ftime;
+ }
+ }
+ BKE_ptcache_mem_pointers_incr(pm);
+ }
+ }
+ }
+}
+
+void PE_undo_push(Scene *scene, const char *str)
+{
+ PTCacheEdit *edit= PE_get_current(scene, OBACT);
+ PTCacheUndo *undo;
+ int nr;
+
+ if (!edit) return;
+
+ /* remove all undos after (also when curundo==NULL) */
+ while (edit->undo.last != edit->curundo) {
+ undo= edit->undo.last;
+ BLI_remlink(&edit->undo, undo);
+ free_PTCacheUndo(undo);
+ MEM_freeN(undo);
+ }
+
+ /* make new */
+ edit->curundo= undo= MEM_callocN(sizeof(PTCacheUndo), "particle undo file");
+ BLI_strncpy(undo->name, str, sizeof(undo->name));
+ BLI_addtail(&edit->undo, undo);
+
+ /* and limit amount to the maximum */
+ nr= 0;
+ undo= edit->undo.last;
+ while (undo) {
+ nr++;
+ if (nr==U.undosteps) break;
+ undo= undo->prev;
+ }
+ if (undo) {
+ while (edit->undo.first!=undo) {
+ PTCacheUndo *first= edit->undo.first;
+ BLI_remlink(&edit->undo, first);
+ free_PTCacheUndo(first);
+ MEM_freeN(first);
+ }
+ }
+
+ /* copy */
+ make_PTCacheUndo(edit, edit->curundo);
+}
+
+void PE_undo_step(Scene *scene, int step)
+{
+ PTCacheEdit *edit= PE_get_current(scene, OBACT);
+
+ if (!edit) return;
+
+ if (step==0) {
+ get_PTCacheUndo(edit, edit->curundo);
+ }
+ else if (step==1) {
+
+ if (edit->curundo==NULL || edit->curundo->prev==NULL) {
+ /* pass */
+ }
+ else {
+ if (G.debug & G_DEBUG) printf("undo %s\n", edit->curundo->name);
+ edit->curundo= edit->curundo->prev;
+ get_PTCacheUndo(edit, edit->curundo);
+ }
+ }
+ else {
+ /* curundo has to remain current situation! */
+
+ if (edit->curundo==NULL || edit->curundo->next==NULL) {
+ /* pass */
+ }
+ else {
+ get_PTCacheUndo(edit, edit->curundo->next);
+ edit->curundo= edit->curundo->next;
+ if (G.debug & G_DEBUG) printf("redo %s\n", edit->curundo->name);
+ }
+ }
+
+ DAG_id_tag_update(&OBACT->id, OB_RECALC_DATA);
+}
+
+bool PE_undo_is_valid(Scene *scene)
+{
+ PTCacheEdit *edit= PE_get_current(scene, OBACT);
+
+ if (edit) {
+ return (edit->undo.last != edit->undo.first);
+ }
+ return 0;
+}
+
+void PTCacheUndo_clear(PTCacheEdit *edit)
+{
+ PTCacheUndo *undo;
+
+ if (edit==NULL) return;
+
+ undo= edit->undo.first;
+ while (undo) {
+ free_PTCacheUndo(undo);
+ undo= undo->next;
+ }
+ BLI_freelistN(&edit->undo);
+ edit->curundo= NULL;
+}
+
+void PE_undo(Scene *scene)
+{
+ PE_undo_step(scene, 1);
+}
+
+void PE_redo(Scene *scene)
+{
+ PE_undo_step(scene, -1);
+}
+
+void PE_undo_number(Scene *scene, int nr)
+{
+ PTCacheEdit *edit= PE_get_current(scene, OBACT);
+ PTCacheUndo *undo;
+ int a=0;
+
+ for (undo= edit->undo.first; undo; undo= undo->next, a++) {
+ if (a==nr) break;
+ }
+ edit->curundo= undo;
+ PE_undo_step(scene, 0);
+}
+
+
+/* get name of undo item, return null if no item with this index */
+/* if active pointer, set it to 1 if true */
+const char *PE_undo_get_name(Scene *scene, int nr, bool *r_active)
+{
+ PTCacheEdit *edit= PE_get_current(scene, OBACT);
+ PTCacheUndo *undo;
+
+ if (r_active) *r_active = false;
+
+ if (edit) {
+ undo= BLI_findlink(&edit->undo, nr);
+ if (undo) {
+ if (r_active && (undo == edit->curundo)) {
+ *r_active = true;
+ }
+ return undo->name;
+ }
+ }
+ return NULL;
+}
+
+/************************ utilities ******************************/
+
+int PE_minmax(Scene *scene, float min[3], float max[3])
+{
+ Object *ob= OBACT;
+ PTCacheEdit *edit= PE_get_current(scene, ob);
+ ParticleSystem *psys;
+ ParticleSystemModifierData *psmd = NULL;
+ POINT_P; KEY_K;
+ float co[3], mat[4][4];
+ int ok= 0;
+
+ if (!edit) return ok;
+
+ if ((psys = edit->psys))
+ psmd= psys_get_modifier(ob, psys);
+ else
+ unit_m4(mat);
+
+ LOOP_VISIBLE_POINTS {
+ if (psys)
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles+p, mat);
+
+ LOOP_SELECTED_KEYS {
+ copy_v3_v3(co, key->co);
+ mul_m4_v3(mat, co);
+ DO_MINMAX(co, min, max);
+ ok= 1;
+ }
+ }
+
+ if (!ok) {
+ BKE_object_minmax(ob, min, max, true);
+ ok= 1;
+ }
+
+ return ok;
+}
+
+/************************ particle edit toggle operator ************************/
+
+/* initialize needed data for bake edit */
+void PE_create_particle_edit(Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys)
+{
+ PTCacheEdit *edit;
+ ParticleSystemModifierData *psmd = (psys) ? psys_get_modifier(ob, psys) : NULL;
+ POINT_P; KEY_K;
+ ParticleData *pa = NULL;
+ HairKey *hkey;
+ int totpoint;
+
+ /* no psmd->dm happens in case particle system modifier is not enabled */
+ if (!(psys && psmd && psmd->dm_final) && !cache)
+ return;
+
+ if (cache && cache->flag & PTCACHE_DISK_CACHE)
+ return;
+
+ if (psys == NULL && (cache && BLI_listbase_is_empty(&cache->mem_cache)))
+ return;
+
+ edit = (psys) ? psys->edit : cache->edit;
+
+ if (!edit) {
+ totpoint = psys ? psys->totpart : (int)((PTCacheMem *)cache->mem_cache.first)->totpoint;
+
+ edit= MEM_callocN(sizeof(PTCacheEdit), "PE_create_particle_edit");
+ edit->points=MEM_callocN(totpoint*sizeof(PTCacheEditPoint), "PTCacheEditPoints");
+ edit->totpoint = totpoint;
+
+ if (psys && !cache) {
+ psys->edit= edit;
+ edit->psys = psys;
+
+ psys->free_edit= PE_free_ptcache_edit;
+
+ edit->pathcache = NULL;
+ BLI_listbase_clear(&edit->pathcachebufs);
+
+ pa = psys->particles;
+ LOOP_POINTS {
+ point->totkey = pa->totkey;
+ point->keys= MEM_callocN(point->totkey*sizeof(PTCacheEditKey), "ParticleEditKeys");
+ point->flag |= PEP_EDIT_RECALC;
+
+ hkey = pa->hair;
+ LOOP_KEYS {
+ key->co= hkey->co;
+ key->time= &hkey->time;
+ key->flag= hkey->editflag;
+ if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
+ key->flag |= PEK_USE_WCO;
+ hkey->editflag |= PEK_USE_WCO;
+ }
+
+ hkey++;
+ }
+ pa++;
+ }
+ update_world_cos(ob, edit);
+ }
+ else {
+ PTCacheMem *pm;
+ int totframe=0;
+
+ cache->edit= edit;
+ cache->free_edit= PE_free_ptcache_edit;
+ edit->psys = NULL;
+
+ for (pm=cache->mem_cache.first; pm; pm=pm->next)
+ totframe++;
+
+ for (pm=cache->mem_cache.first; pm; pm=pm->next) {
+ LOOP_POINTS {
+ if (BKE_ptcache_mem_pointers_seek(p, pm) == 0)
+ continue;
+
+ if (!point->totkey) {
+ key = point->keys = MEM_callocN(totframe*sizeof(PTCacheEditKey), "ParticleEditKeys");
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ else
+ key = point->keys + point->totkey;
+
+ key->co = pm->cur[BPHYS_DATA_LOCATION];
+ key->vel = pm->cur[BPHYS_DATA_VELOCITY];
+ key->rot = pm->cur[BPHYS_DATA_ROTATION];
+ key->ftime = (float)pm->frame;
+ key->time = &key->ftime;
+ BKE_ptcache_mem_pointers_incr(pm);
+
+ point->totkey++;
+ }
+ }
+ psys = NULL;
+ }
+
+ UI_GetThemeColor3ubv(TH_EDGE_SELECT, edit->sel_col);
+ UI_GetThemeColor3ubv(TH_WIRE, edit->nosel_col);
+
+ recalc_lengths(edit);
+ if (psys && !cache)
+ recalc_emitter_field(ob, psys);
+ PE_update_object(scene, ob, 1);
+
+ PTCacheUndo_clear(edit);
+ PE_undo_push(scene, "Original");
+ }
+}
+
+static int particle_edit_toggle_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+
+ if (ob == NULL || ob->type != OB_MESH)
+ return 0;
+ if (!ob->data || ID_IS_LINKED_DATABLOCK(ob->data))
+ return 0;
+ if (CTX_data_edit_object(C))
+ return 0;
+
+ return (ob->particlesystem.first ||
+ modifiers_findByType(ob, eModifierType_Cloth) ||
+ modifiers_findByType(ob, eModifierType_Softbody));
+}
+
+static int particle_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_PARTICLE_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) {
+ PTCacheEdit *edit;
+ ob->mode |= mode_flag;
+ edit= PE_create_current(scene, ob);
+
+ /* mesh may have changed since last entering editmode.
+ * note, this may have run before if the edit data was just created, so could avoid this and speed up a little */
+ if (edit && edit->psys)
+ recalc_emitter_field(ob, edit->psys);
+
+ toggle_particle_cursor(C, 1);
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_PARTICLE, NULL);
+ }
+ else {
+ ob->mode &= ~mode_flag;
+ toggle_particle_cursor(C, 0);
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_OBJECT, NULL);
+ }
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_particle_edit_toggle(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Particle Edit Toggle";
+ ot->idname = "PARTICLE_OT_particle_edit_toggle";
+ ot->description = "Toggle particle edit mode";
+
+ /* api callbacks */
+ ot->exec = particle_edit_toggle_exec;
+ ot->poll = particle_edit_toggle_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+
+/************************ set editable operator ************************/
+
+static int clear_edited_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob= CTX_data_active_object(C);
+ ParticleSystem *psys = psys_get_current(ob);
+
+ if (psys->edit) {
+ if (psys->edit->edited || 1) {
+ PE_free_ptcache_edit(psys->edit);
+
+ psys->edit = NULL;
+ psys->free_edit = NULL;
+
+ psys->recalc |= PSYS_RECALC_RESET;
+ psys->flag &= ~PSYS_GLOBAL_HAIR;
+ psys->flag &= ~PSYS_EDITED;
+
+ psys_reset(psys, PSYS_RESET_DEPSGRAPH);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ }
+ }
+ else { /* some operation might have protected hair from editing so let's clear the flag */
+ psys->recalc |= PSYS_RECALC_RESET;
+ psys->flag &= ~PSYS_GLOBAL_HAIR;
+ psys->flag &= ~PSYS_EDITED;
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static int clear_edited_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ return WM_operator_confirm_message(C, op, "Lose changes done in particle mode? (no undo)");
+}
+
+void PARTICLE_OT_edited_clear(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Clear Edited";
+ ot->idname = "PARTICLE_OT_edited_clear";
+ ot->description = "Undo all edition performed on the particle system";
+
+ /* api callbacks */
+ ot->exec = clear_edited_exec;
+ ot->poll = particle_edit_toggle_poll;
+ ot->invoke = clear_edited_invoke;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ Unify length operator ************************/
+
+static float calculate_point_length(PTCacheEditPoint *point)
+{
+ float length = 0.0f;
+ KEY_K;
+ LOOP_KEYS {
+ if (k > 0) {
+ length += len_v3v3((key - 1)->co, key->co);
+ }
+ }
+ return length;
+}
+
+static float calculate_average_length(PTCacheEdit *edit)
+{
+ int num_selected = 0;
+ float total_length = 0;
+ POINT_P;
+ LOOP_SELECTED_POINTS {
+ total_length += calculate_point_length(point);
+ ++num_selected;
+ }
+ if (num_selected == 0) {
+ return 0.0f;
+ }
+ return total_length / num_selected;
+}
+
+static void scale_point_factor(PTCacheEditPoint *point, float factor)
+{
+ float orig_prev_co[3], prev_co[3];
+ KEY_K;
+ LOOP_KEYS {
+ if (k == 0) {
+ copy_v3_v3(orig_prev_co, key->co);
+ copy_v3_v3(prev_co, key->co);
+ }
+ else {
+ float new_co[3];
+ float delta[3];
+
+ sub_v3_v3v3(delta, key->co, orig_prev_co);
+ mul_v3_fl(delta, factor);
+ add_v3_v3v3(new_co, prev_co, delta);
+
+ copy_v3_v3(orig_prev_co, key->co);
+ copy_v3_v3(key->co, new_co);
+ copy_v3_v3(prev_co, key->co);
+ }
+ }
+ point->flag |= PEP_EDIT_RECALC;
+}
+
+static void scale_point_to_length(PTCacheEditPoint *point, float length)
+{
+ const float point_length = calculate_point_length(point);
+ if (point_length != 0.0f) {
+ const float factor = length / point_length;
+ scale_point_factor(point, factor);
+ }
+}
+
+static void scale_points_to_length(PTCacheEdit *edit, float length)
+{
+ POINT_P;
+ LOOP_SELECTED_POINTS {
+ scale_point_to_length(point, length);
+ }
+ recalc_lengths(edit);
+}
+
+static int unify_length_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = CTX_data_active_object(C);
+ Scene *scene = CTX_data_scene(C);
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ float average_length = calculate_average_length(edit);
+ if (average_length == 0.0f) {
+ return OPERATOR_CANCELLED;
+ }
+ scale_points_to_length(edit, average_length);
+
+ PE_update_object(scene, ob, 1);
+ if (edit->psys) {
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE|NA_EDITED, ob);
+ }
+ else {
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_unify_length(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Unify Length";
+ ot->idname = "PARTICLE_OT_unify_length";
+ ot->description = "Make selected hair the same length";
+
+ /* api callbacks */
+ ot->exec = unify_length_exec;
+ ot->poll = PE_poll_view3d;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
diff --git a/source/blender/editors/physics/particle_object.c b/source/blender/editors/physics/particle_object.c
new file mode 100644
index 00000000000..4a4474868a2
--- /dev/null
+++ b/source/blender/editors/physics/particle_object.c
@@ -0,0 +1,1244 @@
+/*
+ * ***** 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) 2009 Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/physics/particle_object.c
+ * \ingroup edphys
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_scene_types.h"
+
+#include "BLI_math.h"
+#include "BLI_listbase.h"
+#include "BLI_utildefines.h"
+#include "BLI_string.h"
+
+#include "BKE_context.h"
+#include "BKE_depsgraph.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_global.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+#include "BKE_particle.h"
+#include "BKE_pointcache.h"
+#include "BKE_report.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_particle.h"
+#include "ED_screen.h"
+#include "ED_object.h"
+
+#include "UI_resources.h"
+
+#include "physics_intern.h"
+
+extern void PE_create_particle_edit(Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys);
+extern void PTCacheUndo_clear(PTCacheEdit *edit);
+extern void recalc_lengths(PTCacheEdit *edit);
+extern void recalc_emitter_field(Object *ob, ParticleSystem *psys);
+extern void update_world_cos(Object *ob, PTCacheEdit *edit);
+
+#define KEY_K PTCacheEditKey *key; int k
+#define POINT_P PTCacheEditPoint *point; int p
+#define LOOP_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++)
+#if 0
+#define LOOP_VISIBLE_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (!(point->flag & PEP_HIDE))
+#define LOOP_SELECTED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point_is_selected(point))
+#define LOOP_UNSELECTED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (!point_is_selected(point))
+#define LOOP_EDITED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point->flag & PEP_EDIT_RECALC)
+#define LOOP_TAGGED_POINTS for (p=0, point=edit->points; p<edit->totpoint; p++, point++) if (point->flag & PEP_TAG)
+#endif
+#define LOOP_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++)
+#if 0
+#define LOOP_VISIBLE_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if (!(key->flag & PEK_HIDE))
+#define LOOP_SELECTED_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if ((key->flag & PEK_SELECT) && !(key->flag & PEK_HIDE))
+#define LOOP_TAGGED_KEYS for (k=0, key=point->keys; k<point->totkey; k++, key++) if (key->flag & PEK_TAG)
+
+#define KEY_WCO (key->flag & PEK_USE_WCO ? key->world_co : key->co)
+#endif
+
+static float I[4][4] = {{1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 1.0f}};
+
+/********************** particle system slot operators *********************/
+
+static int particle_system_add_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob= ED_object_context(C);
+ Scene *scene = CTX_data_scene(C);
+
+ if (!scene || !ob)
+ return OPERATOR_CANCELLED;
+
+ object_add_particle_system(scene, ob, NULL);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_particle_system_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Particle System Slot";
+ ot->idname = "OBJECT_OT_particle_system_add";
+ ot->description = "Add a particle system";
+
+ /* api callbacks */
+ ot->poll = ED_operator_object_active_editable;
+ ot->exec = particle_system_add_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int particle_system_remove_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = ED_object_context(C);
+ Scene *scene = CTX_data_scene(C);
+ int mode_orig;
+
+ if (!scene || !ob)
+ return OPERATOR_CANCELLED;
+
+ mode_orig = ob->mode;
+ object_remove_particle_system(scene, ob);
+
+ /* possible this isn't the active object
+ * object_remove_particle_system() clears the mode on the last psys
+ */
+ if (mode_orig & OB_MODE_PARTICLE_EDIT) {
+ if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) {
+ if (scene->basact && scene->basact->object == ob) {
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_OBJECT, NULL);
+ }
+ }
+ }
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_particle_system_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Particle System Slot";
+ ot->idname = "OBJECT_OT_particle_system_remove";
+ ot->description = "Remove the selected particle system";
+
+ /* api callbacks */
+ ot->poll = ED_operator_object_active_editable;
+ ot->exec = particle_system_remove_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/********************** new particle settings operator *********************/
+
+static int psys_poll(bContext *C)
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ return (ptr.data != NULL);
+}
+
+static int new_particle_settings_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain= CTX_data_main(C);
+ ParticleSystem *psys;
+ ParticleSettings *part = NULL;
+ Object *ob;
+ PointerRNA ptr;
+
+ ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+
+ psys = ptr.data;
+
+ /* add or copy particle setting */
+ if (psys->part)
+ part= BKE_particlesettings_copy(bmain, psys->part);
+ else
+ part= psys_new_settings("ParticleSettings", bmain);
+
+ ob= ptr.id.data;
+
+ if (psys->part)
+ id_us_min(&psys->part->id);
+
+ psys->part = part;
+
+ psys_check_boid_data(psys);
+
+ DAG_relations_tag_update(bmain);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_new(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Particle Settings";
+ ot->idname = "PARTICLE_OT_new";
+ ot->description = "Add new particle settings";
+
+ /* api callbacks */
+ ot->exec = new_particle_settings_exec;
+ ot->poll = psys_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/********************** keyed particle target operators *********************/
+
+static int new_particle_target_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ Object *ob = ptr.id.data;
+
+ ParticleTarget *pt;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ pt = psys->targets.first;
+ for (; pt; pt=pt->next)
+ pt->flag &= ~PTARGET_CURRENT;
+
+ pt = MEM_callocN(sizeof(ParticleTarget), "keyed particle target");
+
+ pt->flag |= PTARGET_CURRENT;
+ pt->psys = 1;
+
+ BLI_addtail(&psys->targets, pt);
+
+ DAG_relations_tag_update(bmain);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_new_target(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Particle Target";
+ ot->idname = "PARTICLE_OT_new_target";
+ ot->description = "Add a new particle target";
+
+ /* api callbacks */
+ ot->exec = new_particle_target_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int remove_particle_target_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ Object *ob = ptr.id.data;
+
+ ParticleTarget *pt;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ pt = psys->targets.first;
+ for (; pt; pt=pt->next) {
+ if (pt->flag & PTARGET_CURRENT) {
+ BLI_remlink(&psys->targets, pt);
+ MEM_freeN(pt);
+ break;
+ }
+
+ }
+ pt = psys->targets.last;
+
+ if (pt)
+ pt->flag |= PTARGET_CURRENT;
+
+ DAG_relations_tag_update(bmain);
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_target_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Particle Target";
+ ot->idname = "PARTICLE_OT_target_remove";
+ ot->description = "Remove the selected particle target";
+
+ /* api callbacks */
+ ot->exec = remove_particle_target_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move up particle target operator *********************/
+
+static int target_move_up_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ Object *ob = ptr.id.data;
+ ParticleTarget *pt;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ pt = psys->targets.first;
+ for (; pt; pt=pt->next) {
+ if (pt->flag & PTARGET_CURRENT && pt->prev) {
+ BLI_remlink(&psys->targets, pt);
+ BLI_insertlinkbefore(&psys->targets, pt->prev, pt);
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_target_move_up(wmOperatorType *ot)
+{
+ ot->name = "Move Up Target";
+ ot->idname = "PARTICLE_OT_target_move_up";
+ ot->description = "Move particle target up in the list";
+
+ ot->exec = target_move_up_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move down particle target operator *********************/
+
+static int target_move_down_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ Object *ob = ptr.id.data;
+ ParticleTarget *pt;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+ pt = psys->targets.first;
+ for (; pt; pt=pt->next) {
+ if (pt->flag & PTARGET_CURRENT && pt->next) {
+ BLI_remlink(&psys->targets, pt);
+ BLI_insertlinkafter(&psys->targets, pt->next, pt);
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_target_move_down(wmOperatorType *ot)
+{
+ ot->name = "Move Down Target";
+ ot->idname = "PARTICLE_OT_target_move_down";
+ ot->description = "Move particle target down in the list";
+
+ ot->exec = target_move_down_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move up particle dupliweight operator *********************/
+
+static int dupliob_move_up_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ ParticleSettings *part;
+ ParticleDupliWeight *dw;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ part = psys->part;
+ for (dw=part->dupliweights.first; dw; dw=dw->next) {
+ if (dw->flag & PART_DUPLIW_CURRENT && dw->prev) {
+ BLI_remlink(&part->dupliweights, dw);
+ BLI_insertlinkbefore(&part->dupliweights, dw->prev, dw);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, NULL);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_dupliob_move_up(wmOperatorType *ot)
+{
+ ot->name = "Move Up Dupli Object";
+ ot->idname = "PARTICLE_OT_dupliob_move_up";
+ ot->description = "Move dupli object up in the list";
+
+ ot->exec = dupliob_move_up_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/********************** particle dupliweight operators *********************/
+
+static int copy_particle_dupliob_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ ParticleSettings *part;
+ ParticleDupliWeight *dw;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+ part = psys->part;
+ for (dw=part->dupliweights.first; dw; dw=dw->next) {
+ if (dw->flag & PART_DUPLIW_CURRENT) {
+ dw->flag &= ~PART_DUPLIW_CURRENT;
+ dw = MEM_dupallocN(dw);
+ dw->flag |= PART_DUPLIW_CURRENT;
+ BLI_addhead(&part->dupliweights, dw);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, NULL);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_dupliob_copy(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Copy Particle Dupliob";
+ ot->idname = "PARTICLE_OT_dupliob_copy";
+ ot->description = "Duplicate the current dupliobject";
+
+ /* api callbacks */
+ ot->exec = copy_particle_dupliob_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int remove_particle_dupliob_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ ParticleSettings *part;
+ ParticleDupliWeight *dw;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ part = psys->part;
+ for (dw=part->dupliweights.first; dw; dw=dw->next) {
+ if (dw->flag & PART_DUPLIW_CURRENT) {
+ BLI_remlink(&part->dupliweights, dw);
+ MEM_freeN(dw);
+ break;
+ }
+
+ }
+ dw = part->dupliweights.last;
+
+ if (dw)
+ dw->flag |= PART_DUPLIW_CURRENT;
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_dupliob_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Particle Dupliobject";
+ ot->idname = "PARTICLE_OT_dupliob_remove";
+ ot->description = "Remove the selected dupliobject";
+
+ /* api callbacks */
+ ot->exec = remove_particle_dupliob_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ move down particle dupliweight operator *********************/
+
+static int dupliob_move_down_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem);
+ ParticleSystem *psys= ptr.data;
+ ParticleSettings *part;
+ ParticleDupliWeight *dw;
+
+ if (!psys)
+ return OPERATOR_CANCELLED;
+
+ part = psys->part;
+ for (dw=part->dupliweights.first; dw; dw=dw->next) {
+ if (dw->flag & PART_DUPLIW_CURRENT && dw->next) {
+ BLI_remlink(&part->dupliweights, dw);
+ BLI_insertlinkafter(&part->dupliweights, dw->next, dw);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, NULL);
+ break;
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_dupliob_move_down(wmOperatorType *ot)
+{
+ ot->name = "Move Down Dupli Object";
+ ot->idname = "PARTICLE_OT_dupliob_move_down";
+ ot->description = "Move dupli object down in the list";
+
+ ot->exec = dupliob_move_down_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/************************ connect/disconnect hair operators *********************/
+
+static void disconnect_hair(Scene *scene, Object *ob, ParticleSystem *psys)
+{
+ ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
+ ParticleEditSettings *pset= PE_settings(scene);
+ ParticleData *pa;
+ PTCacheEdit *edit;
+ PTCacheEditPoint *point;
+ PTCacheEditKey *ekey = NULL;
+ HairKey *key;
+ int i, k;
+ float hairmat[4][4];
+
+ if (!ob || !psys || psys->flag & PSYS_GLOBAL_HAIR)
+ return;
+
+ if (!psys->part || psys->part->type != PART_HAIR)
+ return;
+
+ edit = psys->edit;
+ point= edit ? edit->points : NULL;
+
+ for (i=0, pa=psys->particles; i<psys->totpart; i++, pa++) {
+ if (point) {
+ ekey = point->keys;
+ point++;
+ }
+
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, pa, hairmat);
+
+ for (k=0, key=pa->hair; k<pa->totkey; k++, key++) {
+ mul_m4_v3(hairmat, key->co);
+
+ if (ekey) {
+ ekey->flag &= ~PEK_USE_WCO;
+ ekey++;
+ }
+ }
+ }
+
+ psys_free_path_cache(psys, psys->edit);
+
+ psys->flag |= PSYS_GLOBAL_HAIR;
+
+ if (ELEM(pset->brushtype, PE_BRUSH_ADD, PE_BRUSH_PUFF))
+ pset->brushtype = PE_BRUSH_NONE;
+
+ PE_update_object(scene, ob, 0);
+}
+
+static int disconnect_hair_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= ED_object_context(C);
+ ParticleSystem *psys= NULL;
+ const bool all = RNA_boolean_get(op->ptr, "all");
+
+ if (!ob)
+ return OPERATOR_CANCELLED;
+
+ if (all) {
+ for (psys=ob->particlesystem.first; psys; psys=psys->next) {
+ disconnect_hair(scene, ob, psys);
+ }
+ }
+ else {
+ psys = psys_get_current(ob);
+ disconnect_hair(scene, ob, psys);
+ }
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_disconnect_hair(wmOperatorType *ot)
+{
+ ot->name = "Disconnect Hair";
+ ot->description = "Disconnect hair from the emitter mesh";
+ ot->idname = "PARTICLE_OT_disconnect_hair";
+
+ ot->exec = disconnect_hair_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO; /* No REGISTER, redo does not work due to missing update, see T47750. */
+
+ RNA_def_boolean(ot->srna, "all", 0, "All hair", "Disconnect all hair systems from the emitter mesh");
+}
+
+/* from/to_world_space : whether from/to particles are in world or hair space
+ * from/to_mat : additional transform for from/to particles (e.g. for using object space copying)
+ */
+static bool remap_hair_emitter(Scene *scene, Object *ob, ParticleSystem *psys,
+ Object *target_ob, ParticleSystem *target_psys, PTCacheEdit *target_edit,
+ float from_mat[4][4], float to_mat[4][4], bool from_global, bool to_global)
+{
+ ParticleSystemModifierData *target_psmd = psys_get_modifier(target_ob, target_psys);
+ ParticleData *pa, *tpa;
+ PTCacheEditPoint *edit_point;
+ PTCacheEditKey *ekey;
+ BVHTreeFromMesh bvhtree= {NULL};
+ MFace *mface = NULL, *mf;
+ MEdge *medge = NULL, *me;
+ MVert *mvert;
+ DerivedMesh *dm, *target_dm;
+ int numverts;
+ int i, k;
+ float from_ob_imat[4][4], to_ob_imat[4][4];
+ float from_imat[4][4], to_imat[4][4];
+
+ if (!target_psmd->dm_final)
+ return false;
+ if (!psys->part || psys->part->type != PART_HAIR)
+ return false;
+ if (!target_psys->part || target_psys->part->type != PART_HAIR)
+ return false;
+
+ edit_point = target_edit ? target_edit->points : NULL;
+
+ invert_m4_m4(from_ob_imat, ob->obmat);
+ invert_m4_m4(to_ob_imat, target_ob->obmat);
+ invert_m4_m4(from_imat, from_mat);
+ invert_m4_m4(to_imat, to_mat);
+
+ if (target_psmd->dm_final->deformedOnly) {
+ /* we don't want to mess up target_psmd->dm when converting to global coordinates below */
+ dm = target_psmd->dm_final;
+ }
+ else {
+ dm = target_psmd->dm_deformed;
+ }
+ target_dm = target_psmd->dm_final;
+ if (dm == NULL) {
+ return false;
+ }
+ /* don't modify the original vertices */
+ dm = CDDM_copy(dm);
+
+ /* BMESH_ONLY, deform dm may not have tessface */
+ DM_ensure_tessface(dm);
+
+ numverts = dm->getNumVerts(dm);
+ mvert = dm->getVertArray(dm);
+
+ /* convert to global coordinates */
+ for (i=0; i<numverts; i++)
+ mul_m4_v3(to_mat, mvert[i].co);
+
+ if (dm->getNumTessFaces(dm) != 0) {
+ mface = dm->getTessFaceArray(dm);
+ bvhtree_from_mesh_faces(&bvhtree, dm, 0.0, 2, 6);
+ }
+ else if (dm->getNumEdges(dm) != 0) {
+ medge = dm->getEdgeArray(dm);
+ bvhtree_from_mesh_edges(&bvhtree, dm, 0.0, 2, 6);
+ }
+ else {
+ dm->release(dm);
+ return false;
+ }
+
+ for (i = 0, tpa = target_psys->particles, pa = psys->particles;
+ i < target_psys->totpart;
+ i++, tpa++, pa++) {
+
+ float from_co[3];
+ BVHTreeNearest nearest;
+
+ if (from_global)
+ mul_v3_m4v3(from_co, from_ob_imat, pa->hair[0].co);
+ else
+ mul_v3_m4v3(from_co, from_ob_imat, pa->hair[0].world_co);
+ mul_m4_v3(from_mat, from_co);
+
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+
+ BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+
+ if (nearest.index == -1) {
+ if (G.debug & G_DEBUG)
+ printf("No nearest point found for hair root!");
+ continue;
+ }
+
+ if (mface) {
+ float v[4][3];
+
+ mf = &mface[nearest.index];
+
+ copy_v3_v3(v[0], mvert[mf->v1].co);
+ copy_v3_v3(v[1], mvert[mf->v2].co);
+ copy_v3_v3(v[2], mvert[mf->v3].co);
+ if (mf->v4) {
+ copy_v3_v3(v[3], mvert[mf->v4].co);
+ interp_weights_poly_v3(tpa->fuv, v, 4, nearest.co);
+ }
+ else
+ interp_weights_poly_v3(tpa->fuv, v, 3, nearest.co);
+ tpa->foffset = 0.0f;
+
+ tpa->num = nearest.index;
+ tpa->num_dmcache = psys_particle_dm_face_lookup(target_dm, dm, tpa->num, tpa->fuv, NULL);
+ }
+ else {
+ me = &medge[nearest.index];
+
+ tpa->fuv[1] = line_point_factor_v3(nearest.co,
+ mvert[me->v1].co,
+ mvert[me->v2].co);
+ tpa->fuv[0] = 1.0f - tpa->fuv[1];
+ tpa->fuv[2] = tpa->fuv[3] = 0.0f;
+ tpa->foffset = 0.0f;
+
+ tpa->num = nearest.index;
+ tpa->num_dmcache = -1;
+ }
+
+ /* translate hair keys */
+ {
+ HairKey *key, *tkey;
+ float hairmat[4][4], imat[4][4];
+ float offset[3];
+
+ if (to_global)
+ copy_m4_m4(imat, target_ob->obmat);
+ else {
+ /* note: using target_dm here, which is in target_ob object space and has full modifiers */
+ psys_mat_hair_to_object(target_ob, target_dm, target_psys->part->from, tpa, hairmat);
+ invert_m4_m4(imat, hairmat);
+ }
+ mul_m4_m4m4(imat, imat, to_imat);
+
+ /* offset in world space */
+ sub_v3_v3v3(offset, nearest.co, from_co);
+
+ if (edit_point) {
+ for (k=0, key=pa->hair, tkey=tpa->hair, ekey = edit_point->keys; k<tpa->totkey; k++, key++, tkey++, ekey++) {
+ float co_orig[3];
+
+ if (from_global)
+ mul_v3_m4v3(co_orig, from_ob_imat, key->co);
+ else
+ mul_v3_m4v3(co_orig, from_ob_imat, key->world_co);
+ mul_m4_v3(from_mat, co_orig);
+
+ add_v3_v3v3(tkey->co, co_orig, offset);
+
+ mul_m4_v3(imat, tkey->co);
+
+ ekey->flag |= PEK_USE_WCO;
+ }
+
+ edit_point++;
+ }
+ else {
+ for (k=0, key=pa->hair, tkey=tpa->hair; k<tpa->totkey; k++, key++, tkey++) {
+ float co_orig[3];
+
+ if (from_global)
+ mul_v3_m4v3(co_orig, from_ob_imat, key->co);
+ else
+ mul_v3_m4v3(co_orig, from_ob_imat, key->world_co);
+ mul_m4_v3(from_mat, co_orig);
+
+ add_v3_v3v3(tkey->co, co_orig, offset);
+
+ mul_m4_v3(imat, tkey->co);
+ }
+ }
+ }
+ }
+
+ free_bvhtree_from_mesh(&bvhtree);
+ dm->release(dm);
+
+ psys_free_path_cache(target_psys, target_edit);
+
+ PE_update_object(scene, target_ob, 0);
+
+ return true;
+}
+
+static bool connect_hair(Scene *scene, Object *ob, ParticleSystem *psys)
+{
+ bool ok;
+
+ if (!psys)
+ return false;
+
+ ok = remap_hair_emitter(scene, ob, psys, ob, psys, psys->edit, ob->obmat, ob->obmat, psys->flag & PSYS_GLOBAL_HAIR, false);
+ psys->flag &= ~PSYS_GLOBAL_HAIR;
+
+ return ok;
+}
+
+static int connect_hair_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= ED_object_context(C);
+ ParticleSystem *psys= NULL;
+ const bool all = RNA_boolean_get(op->ptr, "all");
+ bool any_connected = false;
+
+ if (!ob)
+ return OPERATOR_CANCELLED;
+
+ if (all) {
+ for (psys=ob->particlesystem.first; psys; psys=psys->next) {
+ any_connected |= connect_hair(scene, ob, psys);
+ }
+ }
+ else {
+ psys = psys_get_current(ob);
+ any_connected |= connect_hair(scene, ob, psys);
+ }
+
+ if (!any_connected) {
+ BKE_report(op->reports, RPT_WARNING,
+ "No hair connected (can't connect hair if particle system modifier is disabled)");
+ return OPERATOR_CANCELLED;
+ }
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_PARTICLE, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_connect_hair(wmOperatorType *ot)
+{
+ ot->name = "Connect Hair";
+ ot->description = "Connect hair to the emitter mesh";
+ ot->idname = "PARTICLE_OT_connect_hair";
+
+ ot->exec = connect_hair_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO; /* No REGISTER, redo does not work due to missing update, see T47750. */
+
+ RNA_def_boolean(ot->srna, "all", 0, "All hair", "Connect all hair systems to the emitter mesh");
+}
+
+/************************ particle system copy operator *********************/
+
+typedef enum eCopyParticlesSpace {
+ PAR_COPY_SPACE_OBJECT = 0,
+ PAR_COPY_SPACE_WORLD = 1,
+} eCopyParticlesSpace;
+
+static void copy_particle_edit(Scene *scene, Object *ob, ParticleSystem *psys, ParticleSystem *psys_from)
+{
+ PTCacheEdit *edit_from = psys_from->edit, *edit;
+ ParticleData *pa;
+ KEY_K;
+ POINT_P;
+
+ if (!edit_from)
+ return;
+
+ edit = MEM_dupallocN(edit_from);
+ edit->psys = psys;
+ psys->edit = edit;
+
+ edit->pathcache = NULL;
+ BLI_listbase_clear(&edit->pathcachebufs);
+
+ edit->emitter_field = NULL;
+ edit->emitter_cosnos = NULL;
+
+ BLI_listbase_clear(&edit->undo);
+ edit->curundo = NULL;
+
+ edit->points = MEM_dupallocN(edit_from->points);
+ pa = psys->particles;
+ LOOP_POINTS {
+ HairKey *hkey = pa->hair;
+
+ point->keys= MEM_dupallocN(point->keys);
+ LOOP_KEYS {
+ key->co = hkey->co;
+ key->time = &hkey->time;
+ key->flag = hkey->editflag;
+ if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
+ key->flag |= PEK_USE_WCO;
+ hkey->editflag |= PEK_USE_WCO;
+ }
+
+ hkey++;
+ }
+
+ pa++;
+ }
+ update_world_cos(ob, edit);
+
+ UI_GetThemeColor3ubv(TH_EDGE_SELECT, edit->sel_col);
+ UI_GetThemeColor3ubv(TH_WIRE, edit->nosel_col);
+
+ recalc_lengths(edit);
+ recalc_emitter_field(ob, psys);
+ PE_update_object(scene, ob, true);
+
+ PTCacheUndo_clear(edit);
+ PE_undo_push(scene, "Original");
+}
+
+static void remove_particle_systems_from_object(Object *ob_to)
+{
+ ModifierData *md, *md_next;
+
+ if (ob_to->type != OB_MESH)
+ return;
+ if (!ob_to->data || ID_IS_LINKED_DATABLOCK(ob_to->data))
+ return;
+
+ for (md = ob_to->modifiers.first; md; md = md_next) {
+ md_next = md->next;
+
+ /* remove all particle system modifiers as well,
+ * these need to sync to the particle system list
+ */
+ if (ELEM(md->type, eModifierType_ParticleSystem, eModifierType_DynamicPaint, eModifierType_Smoke)) {
+ BLI_remlink(&ob_to->modifiers, md);
+ modifier_free(md);
+ }
+ }
+
+ BKE_object_free_particlesystems(ob_to);
+}
+
+/* single_psys_from is optional, if NULL all psys of ob_from are copied */
+static bool copy_particle_systems_to_object(Main *bmain,
+ Scene *scene,
+ Object *ob_from,
+ ParticleSystem *single_psys_from,
+ Object *ob_to,
+ int space,
+ bool duplicate_settings)
+{
+ ModifierData *md;
+ ParticleSystem *psys_start = NULL, *psys, *psys_from;
+ ParticleSystem **tmp_psys;
+ DerivedMesh *final_dm;
+ CustomDataMask cdmask;
+ int i, totpsys;
+
+ if (ob_to->type != OB_MESH)
+ return false;
+ if (!ob_to->data || ID_IS_LINKED_DATABLOCK(ob_to->data))
+ return false;
+
+ /* For remapping we need a valid DM.
+ * Because the modifiers are appended at the end it's safe to use
+ * the final DM of the object without particles.
+ * However, when evaluating the DM all the particle modifiers must be valid,
+ * i.e. have the psys assigned already.
+ * To break this hen/egg problem we create all psys separately first (to collect required customdata masks),
+ * then create the DM, then add them to the object and make the psys modifiers ...
+ */
+ #define PSYS_FROM_FIRST (single_psys_from ? single_psys_from : ob_from->particlesystem.first)
+ #define PSYS_FROM_NEXT(cur) (single_psys_from ? NULL : (cur)->next)
+ totpsys = single_psys_from ? 1 : BLI_listbase_count(&ob_from->particlesystem);
+
+ tmp_psys = MEM_mallocN(sizeof(ParticleSystem*) * totpsys, "temporary particle system array");
+
+ cdmask = 0;
+ for (psys_from = PSYS_FROM_FIRST, i = 0;
+ psys_from;
+ psys_from = PSYS_FROM_NEXT(psys_from), ++i) {
+
+ psys = BKE_object_copy_particlesystem(psys_from);
+ tmp_psys[i] = psys;
+
+ if (psys_start == NULL)
+ psys_start = psys;
+
+ cdmask |= psys_emitter_customdata_mask(psys);
+ }
+ /* to iterate source and target psys in sync,
+ * we need to know where the newly added psys start
+ */
+ psys_start = totpsys > 0 ? tmp_psys[0] : NULL;
+
+ /* get the DM (psys and their modifiers have not been appended yet) */
+ final_dm = mesh_get_derived_final(scene, ob_to, cdmask);
+
+ /* now append psys to the object and make modifiers */
+ for (i = 0, psys_from = PSYS_FROM_FIRST;
+ i < totpsys;
+ ++i, psys_from = PSYS_FROM_NEXT(psys_from)) {
+
+ ParticleSystemModifierData *psmd;
+
+ psys = tmp_psys[i];
+
+ /* append to the object */
+ BLI_addtail(&ob_to->particlesystem, psys);
+
+ /* add a particle system modifier for each system */
+ md = modifier_new(eModifierType_ParticleSystem);
+ psmd = (ParticleSystemModifierData *)md;
+ /* push on top of the stack, no use trying to reproduce old stack order */
+ BLI_addtail(&ob_to->modifiers, md);
+
+ BLI_snprintf(md->name, sizeof(md->name), "ParticleSystem %i", i);
+ modifier_unique_name(&ob_to->modifiers, (ModifierData *)psmd);
+
+ psmd->psys = psys;
+ psmd->dm_final = CDDM_copy(final_dm);
+ CDDM_calc_normals(psmd->dm_final);
+ DM_ensure_tessface(psmd->dm_final);
+
+ if (psys_from->edit)
+ copy_particle_edit(scene, ob_to, psys, psys_from);
+
+ if (duplicate_settings) {
+ id_us_min(&psys->part->id);
+ psys->part = BKE_particlesettings_copy(bmain, psys->part);
+ }
+ }
+ MEM_freeN(tmp_psys);
+
+ /* note: do this after creating DM copies for all the particle system modifiers,
+ * the remapping otherwise makes final_dm invalid!
+ */
+ for (psys = psys_start, psys_from = PSYS_FROM_FIRST, i = 0;
+ psys;
+ psys = psys->next, psys_from = PSYS_FROM_NEXT(psys_from), ++i) {
+
+ float (*from_mat)[4], (*to_mat)[4];
+
+ switch (space) {
+ case PAR_COPY_SPACE_OBJECT:
+ from_mat = I;
+ to_mat = I;
+ break;
+ case PAR_COPY_SPACE_WORLD:
+ from_mat = ob_from->obmat;
+ to_mat = ob_to->obmat;
+ break;
+ default:
+ /* should not happen */
+ from_mat = to_mat = NULL;
+ BLI_assert(false);
+ break;
+ }
+ if (ob_from != ob_to) {
+ remap_hair_emitter(scene, ob_from, psys_from, ob_to, psys, psys->edit, from_mat, to_mat, psys_from->flag & PSYS_GLOBAL_HAIR, psys->flag & PSYS_GLOBAL_HAIR);
+ }
+
+ /* tag for recalc */
+// psys->recalc |= PSYS_RECALC_RESET;
+ }
+
+ #undef PSYS_FROM_FIRST
+ #undef PSYS_FROM_NEXT
+
+ DAG_id_tag_update(&ob_to->id, OB_RECALC_DATA);
+ WM_main_add_notifier(NC_OBJECT | ND_PARTICLE | NA_EDITED, ob_to);
+ return true;
+}
+
+static int copy_particle_systems_poll(bContext *C)
+{
+ Object *ob;
+ if (!ED_operator_object_active_editable(C))
+ return false;
+
+ ob = ED_object_active_context(C);
+ if (BLI_listbase_is_empty(&ob->particlesystem))
+ return false;
+
+ return true;
+}
+
+static int copy_particle_systems_exec(bContext *C, wmOperator *op)
+{
+ const int space = RNA_enum_get(op->ptr, "space");
+ const bool remove_target_particles = RNA_boolean_get(op->ptr, "remove_target_particles");
+ const bool use_active = RNA_boolean_get(op->ptr, "use_active");
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ Object *ob_from = ED_object_active_context(C);
+ ParticleSystem *psys_from = use_active ? CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem).data : NULL;
+
+ int changed_tot = 0;
+ int fail = 0;
+
+ CTX_DATA_BEGIN (C, Object *, ob_to, selected_editable_objects)
+ {
+ if (ob_from != ob_to) {
+ bool changed = false;
+ if (remove_target_particles) {
+ remove_particle_systems_from_object(ob_to);
+ changed = true;
+ }
+ if (copy_particle_systems_to_object(bmain, scene, ob_from, psys_from, ob_to, space, false))
+ changed = true;
+ else
+ fail++;
+
+ if (changed)
+ changed_tot++;
+ }
+ }
+ CTX_DATA_END;
+
+ if ((changed_tot == 0 && fail == 0) || fail) {
+ BKE_reportf(op->reports, RPT_ERROR,
+ "Copy particle systems to selected: %d done, %d failed",
+ changed_tot, fail);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_copy_particle_systems(wmOperatorType *ot)
+{
+ static EnumPropertyItem space_items[] = {
+ {PAR_COPY_SPACE_OBJECT, "OBJECT", 0, "Object", "Copy inside each object's local space"},
+ {PAR_COPY_SPACE_WORLD, "WORLD", 0, "World", "Copy in world space"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ ot->name = "Copy Particle Systems";
+ ot->description = "Copy particle systems from the active object to selected objects";
+ ot->idname = "PARTICLE_OT_copy_particle_systems";
+
+ ot->poll = copy_particle_systems_poll;
+ ot->exec = copy_particle_systems_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_enum(ot->srna, "space", space_items, PAR_COPY_SPACE_OBJECT, "Space", "Space transform for copying from one object to another");
+ RNA_def_boolean(ot->srna, "remove_target_particles", true, "Remove Target Particles", "Remove particle systems on the target objects");
+ RNA_def_boolean(ot->srna, "use_active", false, "Use Active", "Use the active particle system from the context");
+}
+
+static int duplicate_particle_systems_poll(bContext *C)
+{
+ if (!ED_operator_object_active_editable(C)) {
+ return false;
+ }
+ Object *ob = ED_object_active_context(C);
+ if (BLI_listbase_is_empty(&ob->particlesystem)) {
+ return false;
+ }
+ return true;
+}
+
+static int duplicate_particle_systems_exec(bContext *C, wmOperator *op)
+{
+ const bool duplicate_settings = RNA_boolean_get(op->ptr, "use_duplicate_settings");
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = ED_object_active_context(C);
+ ParticleSystem *psys = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem).data;
+ copy_particle_systems_to_object(CTX_data_main(C), scene, ob, psys, ob,
+ PAR_COPY_SPACE_OBJECT, duplicate_settings);
+ return OPERATOR_FINISHED;
+}
+
+void PARTICLE_OT_duplicate_particle_system(wmOperatorType *ot)
+{
+ ot->name = "Duplicate Particle Systems";
+ ot->description = "Duplicate particle system within the active object";
+ ot->idname = "PARTICLE_OT_duplicate_particle_system";
+
+ ot->poll = duplicate_particle_systems_poll;
+ ot->exec = duplicate_particle_systems_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna, "use_duplicate_settings", false, "Duplicate Settings",
+ "Duplicate settings as well, so new particle system uses own settings");
+}
diff --git a/source/blender/editors/physics/physics_intern.h b/source/blender/editors/physics/physics_intern.h
index a5b59feba6b..6b6df15e987 100644
--- a/source/blender/editors/physics/physics_intern.h
+++ b/source/blender/editors/physics/physics_intern.h
@@ -35,6 +35,64 @@
struct wmOperatorType;
+/* particle_edit.c */
+void PARTICLE_OT_select_all(struct wmOperatorType *ot);
+void PARTICLE_OT_select_roots(struct wmOperatorType *ot);
+void PARTICLE_OT_select_tips(struct wmOperatorType *ot);
+void PARTICLE_OT_select_random(struct wmOperatorType *ot);
+void PARTICLE_OT_select_linked(struct wmOperatorType *ot);
+void PARTICLE_OT_select_less(struct wmOperatorType *ot);
+void PARTICLE_OT_select_more(struct wmOperatorType *ot);
+
+void PARTICLE_OT_hide(struct wmOperatorType *ot);
+void PARTICLE_OT_reveal(struct wmOperatorType *ot);
+
+void PARTICLE_OT_rekey(struct wmOperatorType *ot);
+void PARTICLE_OT_subdivide(struct wmOperatorType *ot);
+void PARTICLE_OT_remove_doubles(struct wmOperatorType *ot);
+void PARTICLE_OT_weight_set(struct wmOperatorType *ot);
+void PARTICLE_OT_delete(struct wmOperatorType *ot);
+void PARTICLE_OT_mirror(struct wmOperatorType *ot);
+
+void PARTICLE_OT_brush_edit(struct wmOperatorType *ot);
+
+void PARTICLE_OT_shape_cut(struct wmOperatorType *ot);
+
+void PARTICLE_OT_particle_edit_toggle(struct wmOperatorType *ot);
+void PARTICLE_OT_edited_clear(struct wmOperatorType *ot);
+
+void PARTICLE_OT_unify_length(struct wmOperatorType *ot);
+
+/* particle_object.c */
+void OBJECT_OT_particle_system_add(struct wmOperatorType *ot);
+void OBJECT_OT_particle_system_remove(struct wmOperatorType *ot);
+
+void PARTICLE_OT_new(struct wmOperatorType *ot);
+void PARTICLE_OT_new_target(struct wmOperatorType *ot);
+void PARTICLE_OT_target_remove(struct wmOperatorType *ot);
+void PARTICLE_OT_target_move_up(struct wmOperatorType *ot);
+void PARTICLE_OT_target_move_down(struct wmOperatorType *ot);
+void PARTICLE_OT_connect_hair(struct wmOperatorType *ot);
+void PARTICLE_OT_disconnect_hair(struct wmOperatorType *ot);
+void PARTICLE_OT_copy_particle_systems(struct wmOperatorType *ot);
+void PARTICLE_OT_duplicate_particle_system(struct wmOperatorType *ot);
+
+void PARTICLE_OT_dupliob_copy(struct wmOperatorType *ot);
+void PARTICLE_OT_dupliob_remove(struct wmOperatorType *ot);
+void PARTICLE_OT_dupliob_move_up(struct wmOperatorType *ot);
+void PARTICLE_OT_dupliob_move_down(struct wmOperatorType *ot);
+
+/* particle_boids.c */
+void BOID_OT_rule_add(struct wmOperatorType *ot);
+void BOID_OT_rule_del(struct wmOperatorType *ot);
+void BOID_OT_rule_move_up(struct wmOperatorType *ot);
+void BOID_OT_rule_move_down(struct wmOperatorType *ot);
+
+void BOID_OT_state_add(struct wmOperatorType *ot);
+void BOID_OT_state_del(struct wmOperatorType *ot);
+void BOID_OT_state_move_up(struct wmOperatorType *ot);
+void BOID_OT_state_move_down(struct wmOperatorType *ot);
+
/* physics_fluid.c */
void FLUID_OT_bake(struct wmOperatorType *ot);
@@ -45,6 +103,15 @@ void DPAINT_OT_surface_slot_remove(struct wmOperatorType *ot);
void DPAINT_OT_type_toggle(struct wmOperatorType *ot);
void DPAINT_OT_output_toggle(struct wmOperatorType *ot);
+/* physics_pointcache.c */
+void PTCACHE_OT_bake_all(struct wmOperatorType *ot);
+void PTCACHE_OT_free_bake_all(struct wmOperatorType *ot);
+void PTCACHE_OT_bake(struct wmOperatorType *ot);
+void PTCACHE_OT_free_bake(struct wmOperatorType *ot);
+void PTCACHE_OT_bake_from_cache(struct wmOperatorType *ot);
+void PTCACHE_OT_add(struct wmOperatorType *ot);
+void PTCACHE_OT_remove(struct wmOperatorType *ot);
+
/* rigidbody_object.c */
void RIGIDBODY_OT_object_add(struct wmOperatorType *ot);
void RIGIDBODY_OT_object_remove(struct wmOperatorType *ot);
diff --git a/source/blender/editors/physics/physics_ops.c b/source/blender/editors/physics/physics_ops.c
index d0cb7fd12a9..0c907f19753 100644
--- a/source/blender/editors/physics/physics_ops.c
+++ b/source/blender/editors/physics/physics_ops.c
@@ -42,8 +42,54 @@
/***************************** particles ***********************************/
-static void operatortypes_rigidbody(void)
+static void operatortypes_particle(void)
{
+ WM_operatortype_append(PARTICLE_OT_select_all);
+ WM_operatortype_append(PARTICLE_OT_select_roots);
+ WM_operatortype_append(PARTICLE_OT_select_tips);
+ WM_operatortype_append(PARTICLE_OT_select_random);
+ WM_operatortype_append(PARTICLE_OT_select_linked);
+ WM_operatortype_append(PARTICLE_OT_select_less);
+ WM_operatortype_append(PARTICLE_OT_select_more);
+
+ WM_operatortype_append(PARTICLE_OT_hide);
+ WM_operatortype_append(PARTICLE_OT_reveal);
+
+ WM_operatortype_append(PARTICLE_OT_rekey);
+ WM_operatortype_append(PARTICLE_OT_subdivide);
+ WM_operatortype_append(PARTICLE_OT_remove_doubles);
+ WM_operatortype_append(PARTICLE_OT_weight_set);
+ WM_operatortype_append(PARTICLE_OT_delete);
+ WM_operatortype_append(PARTICLE_OT_mirror);
+
+ WM_operatortype_append(PARTICLE_OT_brush_edit);
+
+ WM_operatortype_append(PARTICLE_OT_shape_cut);
+
+ WM_operatortype_append(PARTICLE_OT_particle_edit_toggle);
+ WM_operatortype_append(PARTICLE_OT_edited_clear);
+
+ WM_operatortype_append(PARTICLE_OT_unify_length);
+
+
+ WM_operatortype_append(OBJECT_OT_particle_system_add);
+ WM_operatortype_append(OBJECT_OT_particle_system_remove);
+
+ WM_operatortype_append(PARTICLE_OT_new);
+ WM_operatortype_append(PARTICLE_OT_new_target);
+ WM_operatortype_append(PARTICLE_OT_target_remove);
+ WM_operatortype_append(PARTICLE_OT_target_move_up);
+ WM_operatortype_append(PARTICLE_OT_target_move_down);
+ WM_operatortype_append(PARTICLE_OT_connect_hair);
+ WM_operatortype_append(PARTICLE_OT_disconnect_hair);
+ WM_operatortype_append(PARTICLE_OT_copy_particle_systems);
+ WM_operatortype_append(PARTICLE_OT_duplicate_particle_system);
+
+ WM_operatortype_append(PARTICLE_OT_dupliob_copy);
+ WM_operatortype_append(PARTICLE_OT_dupliob_remove);
+ WM_operatortype_append(PARTICLE_OT_dupliob_move_up);
+ WM_operatortype_append(PARTICLE_OT_dupliob_move_down);
+
WM_operatortype_append(RIGIDBODY_OT_object_add);
WM_operatortype_append(RIGIDBODY_OT_object_remove);
@@ -61,6 +107,79 @@ static void operatortypes_rigidbody(void)
// WM_operatortype_append(RIGIDBODY_OT_world_export);
}
+static void keymap_particle(wmKeyConfig *keyconf)
+{
+ wmKeyMapItem *kmi;
+ wmKeyMap *keymap;
+
+ keymap = WM_keymap_find(keyconf, "Particle", 0, 0);
+ keymap->poll = PE_poll;
+
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_select_all", AKEY, KM_PRESS, 0, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_INVERT);
+
+ WM_keymap_add_item(keymap, "PARTICLE_OT_select_more", PADPLUSKEY, KM_PRESS, KM_CTRL, 0);
+ WM_keymap_add_item(keymap, "PARTICLE_OT_select_less", PADMINUS, KM_PRESS, KM_CTRL, 0);
+
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_select_linked", LKEY, KM_PRESS, 0, 0);
+ RNA_boolean_set(kmi->ptr, "deselect", false);
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_select_linked", LKEY, KM_PRESS, KM_SHIFT, 0);
+ RNA_boolean_set(kmi->ptr, "deselect", true);
+
+ WM_keymap_add_item(keymap, "PARTICLE_OT_delete", XKEY, KM_PRESS, 0, 0);
+ WM_keymap_add_item(keymap, "PARTICLE_OT_delete", DELKEY, KM_PRESS, 0, 0);
+
+ WM_keymap_add_item(keymap, "PARTICLE_OT_reveal", HKEY, KM_PRESS, KM_ALT, 0);
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_hide", HKEY, KM_PRESS, 0, 0);
+ RNA_boolean_set(kmi->ptr, "unselected", false);
+ kmi = WM_keymap_add_item(keymap, "PARTICLE_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0);
+ RNA_boolean_set(kmi->ptr, "unselected", true);
+
+ /* Shift+LMB behavior first, so it has priority over KM_ANY item below. */
+ kmi = WM_keymap_verify_item(keymap, "VIEW3D_OT_manipulator", LEFTMOUSE, KM_PRESS, KM_SHIFT, 0);
+ RNA_boolean_set(kmi->ptr, "release_confirm", true);
+ RNA_boolean_set(kmi->ptr, "use_planar_constraint", true);
+ /* Using KM_ANY here to allow holding modifiers before starting to transform. */
+ kmi = WM_keymap_add_item(keymap, "VIEW3D_OT_manipulator", LEFTMOUSE, KM_PRESS, KM_ANY, 0);
+ RNA_boolean_set(kmi->ptr, "release_confirm", true);
+ RNA_boolean_set(kmi->ptr, "use_planar_constraint", false);
+
+ WM_keymap_add_item(keymap, "PARTICLE_OT_brush_edit", LEFTMOUSE, KM_PRESS, 0, 0);
+ WM_keymap_add_item(keymap, "PARTICLE_OT_brush_edit", LEFTMOUSE, KM_PRESS, KM_SHIFT, 0);
+
+ /* size radial control */
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0);
+ RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.particle_edit.brush.size");
+
+ /* size radial control */
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0);
+ RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.particle_edit.brush.strength");
+
+ WM_keymap_add_menu(keymap, "VIEW3D_MT_particle_specials", WKEY, KM_PRESS, 0, 0);
+
+ WM_keymap_add_item(keymap, "PARTICLE_OT_weight_set", KKEY, KM_PRESS, KM_SHIFT, 0);
+
+ ED_keymap_proportional_cycle(keyconf, keymap);
+ ED_keymap_proportional_editmode(keyconf, keymap, false);
+}
+
+/******************************* boids *************************************/
+
+static void operatortypes_boids(void)
+{
+ WM_operatortype_append(BOID_OT_rule_add);
+ WM_operatortype_append(BOID_OT_rule_del);
+ WM_operatortype_append(BOID_OT_rule_move_up);
+ WM_operatortype_append(BOID_OT_rule_move_down);
+
+ WM_operatortype_append(BOID_OT_state_add);
+ WM_operatortype_append(BOID_OT_state_del);
+ WM_operatortype_append(BOID_OT_state_move_up);
+ WM_operatortype_append(BOID_OT_state_move_down);
+}
+
/********************************* fluid ***********************************/
static void operatortypes_fluid(void)
@@ -68,6 +187,19 @@ static void operatortypes_fluid(void)
WM_operatortype_append(FLUID_OT_bake);
}
+/**************************** point cache **********************************/
+
+static void operatortypes_pointcache(void)
+{
+ WM_operatortype_append(PTCACHE_OT_bake_all);
+ WM_operatortype_append(PTCACHE_OT_free_bake_all);
+ WM_operatortype_append(PTCACHE_OT_bake);
+ WM_operatortype_append(PTCACHE_OT_free_bake);
+ WM_operatortype_append(PTCACHE_OT_bake_from_cache);
+ WM_operatortype_append(PTCACHE_OT_add);
+ WM_operatortype_append(PTCACHE_OT_remove);
+}
+
/********************************* dynamic paint ***********************************/
static void operatortypes_dynamicpaint(void)
@@ -79,17 +211,31 @@ static void operatortypes_dynamicpaint(void)
WM_operatortype_append(DPAINT_OT_output_toggle);
}
+//static void keymap_pointcache(wmWindowManager *wm)
+//{
+// wmKeyMap *keymap = WM_keymap_find(wm, "Pointcache", 0, 0);
+//
+// WM_keymap_add_item(keymap, "PHYSICS_OT_bake_all", AKEY, KM_PRESS, 0, 0);
+// WM_keymap_add_item(keymap, "PHYSICS_OT_free_all", PADPLUSKEY, KM_PRESS, KM_CTRL, 0);
+// WM_keymap_add_item(keymap, "PHYSICS_OT_bake_particle_system", PADMINUS, KM_PRESS, KM_CTRL, 0);
+// WM_keymap_add_item(keymap, "PHYSICS_OT_free_particle_system", LKEY, KM_PRESS, 0, 0);
+//}
+
/****************************** general ************************************/
void ED_operatortypes_physics(void)
{
- operatortypes_rigidbody();
+ operatortypes_particle();
+ operatortypes_boids();
operatortypes_fluid();
+ operatortypes_pointcache();
operatortypes_dynamicpaint();
}
-void ED_keymap_physics(wmKeyConfig *UNUSED(keyconf))
+void ED_keymap_physics(wmKeyConfig *keyconf)
{
+ keymap_particle(keyconf);
+ //keymap_pointcache(keyconf);
}
diff --git a/source/blender/editors/physics/physics_pointcache.c b/source/blender/editors/physics/physics_pointcache.c
new file mode 100644
index 00000000000..e81aa584586
--- /dev/null
+++ b/source/blender/editors/physics/physics_pointcache.c
@@ -0,0 +1,469 @@
+/*
+ * ***** 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) 2007 by Janne Karhu.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/physics/physics_pointcache.c
+ * \ingroup edphys
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_scene_types.h"
+
+#include "BKE_context.h"
+#include "BKE_screen.h"
+#include "BKE_global.h"
+#include "BKE_main.h"
+#include "BKE_particle.h"
+#include "BKE_pointcache.h"
+
+#include "ED_particle.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "physics_intern.h"
+
+static int ptcache_bake_all_poll(bContext *C)
+{
+ return CTX_data_scene(C) != NULL;
+}
+
+static int ptcache_poll(bContext *C)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ return (ptr.data && ptr.id.data);
+}
+
+typedef struct PointCacheJob {
+ void *owner;
+ short *stop, *do_update;
+ float *progress;
+
+ PTCacheBaker *baker;
+} PointCacheJob;
+
+static void ptcache_job_free(void *customdata)
+{
+ PointCacheJob *job = customdata;
+ MEM_freeN(job->baker);
+ MEM_freeN(job);
+}
+
+static int ptcache_job_break(void *customdata)
+{
+ PointCacheJob *job = customdata;
+
+ if (G.is_break) {
+ return 1;
+ }
+
+ if (job->stop && *(job->stop)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void ptcache_job_update(void *customdata, float progress, int *cancel)
+{
+ PointCacheJob *job = customdata;
+
+ if (ptcache_job_break(job)) {
+ *cancel = 1;
+ }
+
+ *(job->do_update) = true;
+ *(job->progress) = progress;
+}
+
+static void ptcache_job_startjob(void *customdata, short *stop, short *do_update, float *progress)
+{
+ PointCacheJob *job = customdata;
+
+ job->stop = stop;
+ job->do_update = do_update;
+ job->progress = progress;
+
+ G.is_break = false;
+
+ /* XXX annoying hack: needed to prevent data corruption when changing
+ * scene frame in separate threads
+ */
+ G.is_rendering = true;
+ BKE_spacedata_draw_locks(true);
+
+ BKE_ptcache_bake(job->baker);
+
+ *do_update = true;
+ *stop = 0;
+}
+
+static void ptcache_job_endjob(void *customdata)
+{
+ PointCacheJob *job = customdata;
+ Scene *scene = job->baker->scene;
+
+ G.is_rendering = false;
+ BKE_spacedata_draw_locks(false);
+
+ WM_set_locked_interface(G.main->wm.first, false);
+
+ WM_main_add_notifier(NC_SCENE | ND_FRAME, scene);
+ WM_main_add_notifier(NC_OBJECT | ND_POINTCACHE, job->baker->pid.ob);
+}
+
+static void ptcache_free_bake(PointCache *cache)
+{
+ if (cache->edit) {
+ if (!cache->edit->edited || 1) {// XXX okee("Lose changes done in particle mode?")) {
+ PE_free_ptcache_edit(cache->edit);
+ cache->edit = NULL;
+ cache->flag &= ~PTCACHE_BAKED;
+ }
+ }
+ else {
+ cache->flag &= ~PTCACHE_BAKED;
+ }
+}
+
+static PTCacheBaker *ptcache_baker_create(bContext *C, wmOperator *op, bool all)
+{
+ PTCacheBaker *baker = MEM_callocN(sizeof(PTCacheBaker), "PTCacheBaker");
+
+ baker->main = CTX_data_main(C);
+ baker->scene = CTX_data_scene(C);
+ baker->bake = RNA_boolean_get(op->ptr, "bake");
+ baker->render = 0;
+ baker->anim_init = 0;
+ baker->quick_step = 1;
+
+ if (!all) {
+ PointerRNA ptr = CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ Object *ob = ptr.id.data;
+ PointCache *cache = ptr.data;
+
+ ListBase pidlist;
+ BKE_ptcache_ids_from_object(&pidlist, ob, baker->scene, MAX_DUPLI_RECUR);
+
+ for (PTCacheID *pid = pidlist.first; pid; pid = pid->next) {
+ if (pid->cache == cache) {
+ baker->pid = *pid;
+ break;
+ }
+ }
+
+ BLI_freelistN(&pidlist);
+ }
+
+ return baker;
+}
+
+static int ptcache_bake_exec(bContext *C, wmOperator *op)
+{
+ bool all = STREQ(op->type->idname, "PTCACHE_OT_bake_all");
+
+ PTCacheBaker *baker = ptcache_baker_create(C, op, all);
+ BKE_ptcache_bake(baker);
+ MEM_freeN(baker);
+
+ return OPERATOR_FINISHED;
+}
+
+static int ptcache_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ bool all = STREQ(op->type->idname, "PTCACHE_OT_bake_all");
+
+ PointCacheJob *job = MEM_mallocN(sizeof(PointCacheJob), "PointCacheJob");
+ job->baker = ptcache_baker_create(C, op, all);
+ job->baker->bake_job = job;
+ job->baker->update_progress = ptcache_job_update;
+
+ wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_data_scene(C),
+ "Point Cache", WM_JOB_PROGRESS, WM_JOB_TYPE_POINTCACHE);
+
+ WM_jobs_customdata_set(wm_job, job, ptcache_job_free);
+ WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_POINTCACHE, NC_OBJECT | ND_POINTCACHE);
+ WM_jobs_callbacks(wm_job, ptcache_job_startjob, NULL, NULL, ptcache_job_endjob);
+
+ WM_set_locked_interface(CTX_wm_manager(C), true);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+
+ WM_event_add_modal_handler(C, op);
+
+ /* we must run modal until the bake job is done, otherwise the undo push
+ * happens before the job ends, which can lead to race conditions between
+ * the baking and file writing code */
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int ptcache_bake_modal(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ Scene *scene = (Scene *) op->customdata;
+
+ /* no running blender, remove handler and pass through */
+ if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_POINTCACHE)) {
+ return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
+ }
+
+ return OPERATOR_PASS_THROUGH;
+}
+
+static void ptcache_bake_cancel(bContext *C, wmOperator *op)
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+ Scene *scene = (Scene *) op->customdata;
+
+ /* kill on cancel, because job is using op->reports */
+ WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_POINTCACHE);
+}
+
+static int ptcache_free_bake_all_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Scene *scene= CTX_data_scene(C);
+ Base *base;
+ PTCacheID *pid;
+ ListBase pidlist;
+
+ for (base=scene->base.first; base; base= base->next) {
+ BKE_ptcache_ids_from_object(&pidlist, base->object, scene, MAX_DUPLI_RECUR);
+
+ for (pid=pidlist.first; pid; pid=pid->next) {
+ ptcache_free_bake(pid->cache);
+ }
+
+ BLI_freelistN(&pidlist);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, base->object);
+ }
+
+ WM_event_add_notifier(C, NC_SCENE|ND_FRAME, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+void PTCACHE_OT_bake_all(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Bake All Physics";
+ ot->description = "Bake all physics";
+ ot->idname = "PTCACHE_OT_bake_all";
+
+ /* api callbacks */
+ ot->exec = ptcache_bake_exec;
+ ot->invoke = ptcache_bake_invoke;
+ ot->modal = ptcache_bake_modal;
+ ot->cancel = ptcache_bake_cancel;
+ ot->poll = ptcache_bake_all_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna, "bake", 1, "Bake", "");
+}
+void PTCACHE_OT_free_bake_all(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Free All Physics Bakes";
+ ot->idname = "PTCACHE_OT_free_bake_all";
+ ot->description = "Free all baked caches of all objects in the current scene";
+
+ /* api callbacks */
+ ot->exec = ptcache_free_bake_all_exec;
+ ot->poll = ptcache_bake_all_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int ptcache_free_bake_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ PointCache *cache= ptr.data;
+ Object *ob= ptr.id.data;
+
+ ptcache_free_bake(cache);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+static int ptcache_bake_from_cache_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ PointCache *cache= ptr.data;
+ Object *ob= ptr.id.data;
+
+ cache->flag |= PTCACHE_BAKED;
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+void PTCACHE_OT_bake(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Bake Physics";
+ ot->description = "Bake physics";
+ ot->idname = "PTCACHE_OT_bake";
+
+ /* api callbacks */
+ ot->exec = ptcache_bake_exec;
+ ot->invoke = ptcache_bake_invoke;
+ ot->modal = ptcache_bake_modal;
+ ot->cancel = ptcache_bake_cancel;
+ ot->poll = ptcache_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna, "bake", 0, "Bake", "");
+}
+void PTCACHE_OT_free_bake(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Free Physics Bake";
+ ot->description = "Free physics bake";
+ ot->idname = "PTCACHE_OT_free_bake";
+
+ /* api callbacks */
+ ot->exec = ptcache_free_bake_exec;
+ ot->poll = ptcache_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+void PTCACHE_OT_bake_from_cache(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Bake From Cache";
+ ot->description = "Bake from cache";
+ ot->idname = "PTCACHE_OT_bake_from_cache";
+
+ /* api callbacks */
+ ot->exec = ptcache_bake_from_cache_exec;
+ ot->poll = ptcache_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int ptcache_add_new_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Scene *scene = CTX_data_scene(C);
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ Object *ob= ptr.id.data;
+ PointCache *cache= ptr.data;
+ PTCacheID *pid;
+ ListBase pidlist;
+
+ BKE_ptcache_ids_from_object(&pidlist, ob, scene, MAX_DUPLI_RECUR);
+
+ for (pid=pidlist.first; pid; pid=pid->next) {
+ if (pid->cache == cache) {
+ PointCache *cache_new = BKE_ptcache_add(pid->ptcaches);
+ cache_new->step = pid->default_step;
+ *(pid->cache_ptr) = cache_new;
+ break;
+ }
+ }
+
+ BLI_freelistN(&pidlist);
+
+ WM_event_add_notifier(C, NC_SCENE|ND_FRAME, scene);
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+static int ptcache_remove_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "point_cache", &RNA_PointCache);
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= ptr.id.data;
+ PointCache *cache= ptr.data;
+ PTCacheID *pid;
+ ListBase pidlist;
+
+ BKE_ptcache_ids_from_object(&pidlist, ob, scene, MAX_DUPLI_RECUR);
+
+ for (pid=pidlist.first; pid; pid=pid->next) {
+ if (pid->cache == cache) {
+ if (pid->ptcaches->first == pid->ptcaches->last)
+ continue; /* don't delete last cache */
+
+ BLI_remlink(pid->ptcaches, pid->cache);
+ BKE_ptcache_free(pid->cache);
+ *(pid->cache_ptr) = pid->ptcaches->first;
+
+ break;
+ }
+ }
+
+ BLI_freelistN(&pidlist);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_POINTCACHE, ob);
+
+ return OPERATOR_FINISHED;
+}
+void PTCACHE_OT_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add New Cache";
+ ot->description = "Add new cache";
+ ot->idname = "PTCACHE_OT_add";
+
+ /* api callbacks */
+ ot->exec = ptcache_add_new_exec;
+ ot->poll = ptcache_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+void PTCACHE_OT_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete Current Cache";
+ ot->description = "Delete current cache";
+ ot->idname = "PTCACHE_OT_remove";
+
+ /* api callbacks */
+ ot->exec = ptcache_remove_exec;
+ ot->poll = ptcache_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+