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
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')
-rw-r--r--source/blender/editors/animation/anim_channels_defines.c3
-rw-r--r--source/blender/editors/animation/anim_channels_edit.c5
-rw-r--r--source/blender/editors/animation/anim_filter.c72
-rw-r--r--source/blender/editors/include/ED_anim_api.h2
-rw-r--r--source/blender/editors/include/ED_buttons.h1
-rw-r--r--source/blender/editors/include/ED_object.h2
-rw-r--r--source/blender/editors/include/ED_particle.h74
-rw-r--r--source/blender/editors/include/ED_physics.h5
-rw-r--r--source/blender/editors/interface/interface_icons.c2
-rw-r--r--source/blender/editors/interface/interface_templates.c57
-rw-r--r--source/blender/editors/object/object_add.c19
-rw-r--r--source/blender/editors/object/object_edit.c22
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_modifier.c283
-rw-r--r--source/blender/editors/object/object_ops.c1
-rw-r--r--source/blender/editors/object/object_relations.c5
-rw-r--r--source/blender/editors/object/object_select.c41
-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
-rw-r--r--source/blender/editors/render/render_shading.c10
-rw-r--r--source/blender/editors/screen/screen_context.c6
-rw-r--r--source/blender/editors/sculpt_paint/paint_vertex.c16
-rw-r--r--source/blender/editors/space_buttons/buttons_context.c107
-rw-r--r--source/blender/editors/space_buttons/buttons_texture.c50
-rw-r--r--source/blender/editors/space_buttons/space_buttons.c7
-rw-r--r--source/blender/editors/space_file/filesel.c2
-rw-r--r--source/blender/editors/space_info/info_stats.c34
-rw-r--r--source/blender/editors/space_nla/nla_buttons.c1
-rw-r--r--source/blender/editors/space_nla/nla_channels.c3
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c6
-rw-r--r--source/blender/editors/space_outliner/outliner_intern.h2
-rw-r--r--source/blender/editors/space_outliner/outliner_select.c16
-rw-r--r--source/blender/editors/space_outliner/outliner_tree.c9
-rw-r--r--source/blender/editors/space_time/space_time.c162
-rw-r--r--source/blender/editors/space_view3d/drawobject.c1206
-rw-r--r--source/blender/editors/space_view3d/drawvolume.c2
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c1
-rw-r--r--source/blender/editors/space_view3d/view3d_draw_legacy.c5
-rw-r--r--source/blender/editors/space_view3d/view3d_edit.c4
-rw-r--r--source/blender/editors/space_view3d/view3d_header.c2
-rw-r--r--source/blender/editors/space_view3d/view3d_intern.h2
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c12
-rw-r--r--source/blender/editors/transform/transform.c2
-rw-r--r--source/blender/editors/transform/transform.h1
-rw-r--r--source/blender/editors/transform/transform_conversions.c203
-rw-r--r--source/blender/editors/transform/transform_generics.c9
-rw-r--r--source/blender/editors/transform/transform_manipulator.c28
-rw-r--r--source/blender/editors/transform/transform_orientations.c2
-rw-r--r--source/blender/editors/transform/transform_snap.c8
-rw-r--r--source/blender/editors/transform/transform_snap_object.c16
-rw-r--r--source/blender/editors/util/CMakeLists.txt1
-rw-r--r--source/blender/editors/util/undo.c34
57 files changed, 9704 insertions, 90 deletions
diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c
index de6a17e9d41..f05932db1b2 100644
--- a/source/blender/editors/animation/anim_channels_defines.c
+++ b/source/blender/editors/animation/anim_channels_defines.c
@@ -43,6 +43,7 @@
#include "DNA_cachefile_types.h"
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
#include "DNA_screen_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
@@ -314,7 +315,7 @@ static short acf_generic_group_offset(bAnimContext *ac, bAnimListElem *ale)
offset += U.widget_unit;
}
/* materials and particles animdata */
- else if (GS(ale->id->name) == ID_MA)
+ else if (ELEM(GS(ale->id->name), ID_MA, ID_PA))
offset += (short)(0.7f * U.widget_unit);
/* if not in Action Editor mode, action-groups (and their children) must carry some offset too... */
diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c
index ecaadd78dc2..117b8549712 100644
--- a/source/blender/editors/animation/anim_channels_edit.c
+++ b/source/blender/editors/animation/anim_channels_edit.c
@@ -124,6 +124,7 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
@@ -180,6 +181,7 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
@@ -281,6 +283,7 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
@@ -377,6 +380,7 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
@@ -2728,6 +2732,7 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c
index 7bf146aa171..c12a050e9ba 100644
--- a/source/blender/editors/animation/anim_filter.c
+++ b/source/blender/editors/animation/anim_filter.c
@@ -64,6 +64,7 @@
#include "DNA_meta_types.h"
#include "DNA_movieclip_types.h"
#include "DNA_node_types.h"
+#include "DNA_particle_types.h"
#include "DNA_space_types.h"
#include "DNA_sequence_types.h"
#include "DNA_scene_types.h"
@@ -801,6 +802,19 @@ static bAnimListElem *make_new_animlistelem(void *data, short datatype, ID *owne
ale->adt = BKE_animdata_from_id(data);
break;
}
+ case ANIMTYPE_DSPART:
+ {
+ ParticleSettings *part = (ParticleSettings *)ale->data;
+ AnimData *adt = part->adt;
+
+ ale->flag = FILTER_PART_OBJD(part);
+
+ ale->key_data = (adt) ? adt->action : NULL;
+ ale->datatype = ALE_ACT;
+
+ ale->adt = BKE_animdata_from_id(data);
+ break;
+ }
case ANIMTYPE_DSTEX:
{
Tex *tex = (Tex *)data;
@@ -2073,6 +2087,12 @@ static size_t animdata_filter_ds_textures(bAnimContext *ac, ListBase *anim_data,
mtex = (MTex **)(&wo->mtex);
break;
}
+ case ID_PA:
+ {
+ ParticleSettings *part = (ParticleSettings *)owner_id;
+ mtex = (MTex **)(&part->mtex);
+ break;
+ }
default:
{
/* invalid/unsupported option */
@@ -2268,6 +2288,53 @@ static size_t animdata_filter_ds_modifiers(bAnimContext *ac, ListBase *anim_data
/* ............ */
+static size_t animdata_filter_ds_particles(bAnimContext *ac, ListBase *anim_data, bDopeSheet *ads, Object *ob, int filter_mode)
+{
+ ParticleSystem *psys;
+ size_t items = 0;
+
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ ListBase tmp_data = {NULL, NULL};
+ size_t tmp_items = 0;
+
+ /* if no material returned, skip - so that we don't get weird blank entries... */
+ if (ELEM(NULL, psys->part, psys->part->adt))
+ continue;
+
+ /* add particle-system's animation data to temp collection */
+ BEGIN_ANIMFILTER_SUBCHANNELS(FILTER_PART_OBJD(psys->part))
+ {
+ /* particle system's animation data */
+ tmp_items += animfilter_block_data(ac, &tmp_data, ads, (ID *)psys->part, filter_mode);
+
+ /* textures */
+ if (!(ads->filterflag & ADS_FILTER_NOTEX))
+ tmp_items += animdata_filter_ds_textures(ac, &tmp_data, ads, (ID *)psys->part, filter_mode);
+ }
+ END_ANIMFILTER_SUBCHANNELS;
+
+ /* did we find anything? */
+ if (tmp_items) {
+ /* include particle-expand widget first */
+ if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
+ /* check if filtering by active status */
+ if (ANIMCHANNEL_ACTIVEOK(psys->part)) {
+ ANIMCHANNEL_NEW_CHANNEL(psys->part, ANIMTYPE_DSPART, psys->part);
+ }
+ }
+
+ /* now add the list of collected channels */
+ BLI_movelisttolist(anim_data, &tmp_data);
+ BLI_assert(BLI_listbase_is_empty(&tmp_data));
+ items += tmp_items;
+ }
+ }
+
+ /* return the number of items added to the list */
+ return items;
+}
+
+
static size_t animdata_filter_ds_obdata(bAnimContext *ac, ListBase *anim_data, bDopeSheet *ads, Object *ob, int filter_mode)
{
ListBase tmp_data = {NULL, NULL};
@@ -2543,6 +2610,11 @@ static size_t animdata_filter_dopesheet_ob(bAnimContext *ac, ListBase *anim_data
tmp_items += animdata_filter_ds_obdata(ac, &tmp_data, ads, ob, filter_mode);
}
+ /* particles */
+ if ((ob->particlesystem.first) && !(ads->filterflag & ADS_FILTER_NOPART)) {
+ tmp_items += animdata_filter_ds_particles(ac, &tmp_data, ads, ob, filter_mode);
+ }
+
/* grease pencil */
if ((ob->gpd) && !(ads->filterflag & ADS_FILTER_NOGPENCIL)) {
tmp_items += animdata_filter_ds_gpencil(ac, &tmp_data, ads, ob->gpd, filter_mode);
diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h
index 4ca7eaf0943..4a4ab832b28 100644
--- a/source/blender/editors/include/ED_anim_api.h
+++ b/source/blender/editors/include/ED_anim_api.h
@@ -161,6 +161,7 @@ typedef enum eAnim_ChannelType {
ANIMTYPE_DSSKEY,
ANIMTYPE_DSWOR,
ANIMTYPE_DSNTREE,
+ ANIMTYPE_DSPART,
ANIMTYPE_DSMBALL,
ANIMTYPE_DSARM,
ANIMTYPE_DSMESH,
@@ -278,6 +279,7 @@ typedef enum eAnimFilter_Flags {
#define FILTER_CAM_OBJD(ca) (CHECK_TYPE_INLINE(ca, Camera *), ((ca->flag & CAM_DS_EXPAND)))
#define FILTER_CACHEFILE_OBJD(cf) (CHECK_TYPE_INLINE(cf, CacheFile *), ((cf->flag & CACHEFILE_DS_EXPAND)))
#define FILTER_CUR_OBJD(cu) (CHECK_TYPE_INLINE(cu, Curve *), ((cu->flag & CU_DS_EXPAND)))
+#define FILTER_PART_OBJD(part) (CHECK_TYPE_INLINE(part, ParticleSettings *), ((part->flag & PART_DS_EXPAND)))
#define FILTER_MBALL_OBJD(mb) (CHECK_TYPE_INLINE(mb, MetaBall *), ((mb->flag2 & MB_DS_EXPAND)))
#define FILTER_ARM_OBJD(arm) (CHECK_TYPE_INLINE(arm, bArmature *), ((arm->flag & ARM_DS_EXPAND)))
#define FILTER_MESH_OBJD(me) (CHECK_TYPE_INLINE(me, Mesh *), ((me->flag & ME_DS_EXPAND)))
diff --git a/source/blender/editors/include/ED_buttons.h b/source/blender/editors/include/ED_buttons.h
index 636c583b828..64c16605dec 100644
--- a/source/blender/editors/include/ED_buttons.h
+++ b/source/blender/editors/include/ED_buttons.h
@@ -33,6 +33,7 @@
bool ED_texture_context_check_world(const struct bContext *C);
bool ED_texture_context_check_material(const struct bContext *C);
bool ED_texture_context_check_lamp(const struct bContext *C);
+bool ED_texture_context_check_particles(const struct bContext *C);
bool ED_texture_context_check_linestyle(const struct bContext *C);
bool ED_texture_context_check_others(const struct bContext *C);
diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h
index f5c2b1da0cc..04ff5692717 100644
--- a/source/blender/editors/include/ED_object.h
+++ b/source/blender/editors/include/ED_object.h
@@ -190,6 +190,8 @@ bool ED_object_modifier_remove(struct ReportList *reports, struct Main *bmain,
void ED_object_modifier_clear(struct Main *bmain, struct Object *ob);
int ED_object_modifier_move_down(struct ReportList *reports, struct Object *ob, struct ModifierData *md);
int ED_object_modifier_move_up(struct ReportList *reports, struct Object *ob, struct ModifierData *md);
+int ED_object_modifier_convert(struct ReportList *reports, struct Main *bmain, struct Scene *scene,
+ struct Object *ob, struct ModifierData *md);
int ED_object_modifier_apply(struct ReportList *reports, struct Scene *scene,
struct Object *ob, struct ModifierData *md, int mode);
int ED_object_modifier_copy(struct ReportList *reports, struct Object *ob, struct ModifierData *md);
diff --git a/source/blender/editors/include/ED_particle.h b/source/blender/editors/include/ED_particle.h
new file mode 100644
index 00000000000..6cb8c0cfb19
--- /dev/null
+++ b/source/blender/editors/include/ED_particle.h
@@ -0,0 +1,74 @@
+/*
+ * ***** 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 ED_particle.h
+ * \ingroup editors
+ */
+
+#ifndef __ED_PARTICLE_H__
+#define __ED_PARTICLE_H__
+
+struct bContext;
+struct Object;
+struct ParticleEditSettings;
+struct rcti;
+struct PTCacheEdit;
+struct Scene;
+
+/* particle edit mode */
+void PE_free_ptcache_edit(struct PTCacheEdit *edit);
+int PE_start_edit(struct PTCacheEdit *edit);
+
+/* access */
+struct PTCacheEdit *PE_get_current(struct Scene *scene, struct Object *ob);
+struct PTCacheEdit *PE_create_current(struct Scene *scene, struct Object *ob);
+void PE_current_changed(struct Scene *scene, struct Object *ob);
+int PE_minmax(struct Scene *scene, float min[3], float max[3]);
+struct ParticleEditSettings *PE_settings(struct Scene *scene);
+
+/* update calls */
+void PE_hide_keys_time(struct Scene *scene, struct PTCacheEdit *edit, float cfra);
+void PE_update_object(struct Scene *scene, struct Object *ob, int useflag);
+
+/* selection tools */
+int PE_mouse_particles(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle);
+int PE_border_select(struct bContext *C, struct rcti *rect, bool select, bool extend);
+int PE_circle_select(struct bContext *C, int selecting, const int mval[2], float rad);
+int PE_lasso_select(struct bContext *C, const int mcords[][2], const short moves, bool extend, bool select);
+void PE_deselect_all_visible(struct PTCacheEdit *edit);
+
+/* undo */
+void PE_undo_push(struct Scene *scene, const char *str);
+void PE_undo_step(struct Scene *scene, int step);
+void PE_undo(struct Scene *scene);
+void PE_redo(struct Scene *scene);
+bool PE_undo_is_valid(struct Scene *scene);
+void PE_undo_number(struct Scene *scene, int nr);
+const char *PE_undo_get_name(struct Scene *scene, int nr, bool *r_active);
+
+#endif /* __ED_PARTICLE_H__ */
+
diff --git a/source/blender/editors/include/ED_physics.h b/source/blender/editors/include/ED_physics.h
index 511dae088a0..fed842c969e 100644
--- a/source/blender/editors/include/ED_physics.h
+++ b/source/blender/editors/include/ED_physics.h
@@ -39,6 +39,11 @@ struct wmKeyConfig;
struct Scene;
struct Object;
+/* particle_edit.c */
+int PE_poll(struct bContext *C);
+int PE_hair_poll(struct bContext *C);
+int PE_poll_view3d(struct bContext *C);
+
/* rigidbody_object.c */
bool ED_rigidbody_object_add(struct Main *bmain, struct Scene *scene, struct Object *ob, int type, struct ReportList *reports);
void ED_rigidbody_object_remove(struct Main *bmain, struct Scene *scene, struct Object *ob);
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index 3d22a26f34b..0573e8d9c94 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -1360,6 +1360,8 @@ int UI_idcode_icon_get(const int idcode)
return ICON_NODETREE;
case ID_OB:
return ICON_OBJECT_DATA;
+ case ID_PA:
+ return ICON_PARTICLE_DATA;
case ID_PAL:
return ICON_COLOR; /* TODO! this would need its own icon! */
case ID_PC:
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 9527ddc7088..2cdcf7a604d 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -64,6 +64,7 @@
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_packedFile.h"
+#include "BKE_particle.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "BKE_sca.h"
@@ -367,6 +368,7 @@ static const char *template_id_browse_tip(StructRNA *type)
case ID_AC: return N_("Browse Action to be linked");
case ID_NT: return N_("Browse Node Tree to be linked");
case ID_BR: return N_("Browse Brush to be linked");
+ case ID_PA: return N_("Browse Particle Settings to be linked");
case ID_GD: return N_("Browse Grease Pencil Data to be linked");
case ID_MC: return N_("Browse Movie Clip to be linked");
case ID_MSK: return N_("Browse Mask to be linked");
@@ -536,6 +538,7 @@ static void template_ID(
BLT_I18NCONTEXT_ID_ACTION,
BLT_I18NCONTEXT_ID_NODETREE,
BLT_I18NCONTEXT_ID_BRUSH,
+ BLT_I18NCONTEXT_ID_PARTICLESETTINGS,
BLT_I18NCONTEXT_ID_GPENCIL,
BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE,
);
@@ -803,6 +806,16 @@ static void modifiers_convertToReal(bContext *C, void *ob_v, void *md_v)
ED_undo_push(C, "Modifier convert to real");
}
+static int modifier_can_delete(ModifierData *md)
+{
+ /* fluid particle modifier can't be deleted here */
+ if (md->type == eModifierType_ParticleSystem)
+ if (((ParticleSystemModifierData *)md)->psys->part->type == PART_FLUID)
+ return 0;
+
+ return 1;
+}
+
/* Check whether Modifier is a simulation or not, this is used for switching to the physics/particles context tab */
static int modifier_is_simulation(ModifierData *md)
{
@@ -812,6 +825,10 @@ static int modifier_is_simulation(ModifierData *md)
{
return 1;
}
+ /* Particle Tab */
+ else if (md->type == eModifierType_ParticleSystem) {
+ return 2;
+ }
else {
return 0;
}
@@ -926,14 +943,18 @@ static uiLayout *draw_modifier(
UI_block_emboss_set(block, UI_EMBOSS_NONE);
/* When Modifier is a simulation, show button to switch to context rather than the delete button. */
- if (!modifier_is_simulation(md) ||
- STREQ(scene->r.engine, RE_engine_id_BLENDER_GAME))
+ if (modifier_can_delete(md) &&
+ (!modifier_is_simulation(md) ||
+ STREQ(scene->r.engine, RE_engine_id_BLENDER_GAME)))
{
uiItemO(row, "", ICON_X, "OBJECT_OT_modifier_remove");
}
else if (modifier_is_simulation(md) == 1) {
uiItemStringO(row, "", ICON_BUTS, "WM_OT_properties_context_change", "context", "PHYSICS");
}
+ else if (modifier_is_simulation(md) == 2) {
+ uiItemStringO(row, "", ICON_BUTS, "WM_OT_properties_context_change", "context", "PARTICLES");
+ }
UI_block_emboss_set(block, UI_EMBOSS);
}
@@ -948,20 +969,34 @@ static uiLayout *draw_modifier(
/* only here obdata, the rest of modifiers is ob level */
UI_block_lock_set(block, BKE_object_obdata_is_libdata(ob), ERROR_LIBDATA_MESSAGE);
- uiLayoutSetOperatorContext(row, WM_OP_INVOKE_DEFAULT);
- uiItemEnumO(row, "OBJECT_OT_modifier_apply", CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply"),
- 0, "apply_as", MODIFIER_APPLY_DATA);
-
- if (modifier_isSameTopology(md) && !modifier_isNonGeometrical(md)) {
- uiItemEnumO(row, "OBJECT_OT_modifier_apply",
- CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply as Shape Key"),
- 0, "apply_as", MODIFIER_APPLY_SHAPE);
+ if (md->type == eModifierType_ParticleSystem) {
+ ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
+
+ if (!(ob->mode & OB_MODE_PARTICLE_EDIT)) {
+ if (ELEM(psys->part->ren_as, PART_DRAW_GR, PART_DRAW_OB))
+ uiItemO(row, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Convert"), ICON_NONE,
+ "OBJECT_OT_duplicates_make_real");
+ else if (psys->part->ren_as == PART_DRAW_PATH && psys->pathcache)
+ uiItemO(row, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Convert"), ICON_NONE,
+ "OBJECT_OT_modifier_convert");
+ }
+ }
+ else {
+ uiLayoutSetOperatorContext(row, WM_OP_INVOKE_DEFAULT);
+ uiItemEnumO(row, "OBJECT_OT_modifier_apply", CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply"),
+ 0, "apply_as", MODIFIER_APPLY_DATA);
+
+ if (modifier_isSameTopology(md) && !modifier_isNonGeometrical(md)) {
+ uiItemEnumO(row, "OBJECT_OT_modifier_apply",
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply as Shape Key"),
+ 0, "apply_as", MODIFIER_APPLY_SHAPE);
+ }
}
UI_block_lock_clear(block);
UI_block_lock_set(block, ob && ID_IS_LINKED_DATABLOCK(ob), ERROR_LIBDATA_MESSAGE);
- if (!ELEM(md->type, eModifierType_Fluidsim, eModifierType_Softbody,
+ if (!ELEM(md->type, eModifierType_Fluidsim, eModifierType_Softbody, eModifierType_ParticleSystem,
eModifierType_Cloth, eModifierType_Smoke))
{
uiItemO(row, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy"), ICON_NONE,
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index baea0088e1f..f42dafd094c 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -83,6 +83,7 @@
#include "BKE_mesh.h"
#include "BKE_nla.h"
#include "BKE_object.h"
+#include "BKE_particle.h"
#include "BKE_report.h"
#include "BKE_sca.h"
#include "BKE_scene.h"
@@ -2021,6 +2022,24 @@ static Base *object_add_duplicate_internal(Main *bmain, Scene *scene, Base *base
}
}
}
+ if (dupflag & USER_DUP_PSYS) {
+ ParticleSystem *psys;
+ for (psys = obn->particlesystem.first; psys; psys = psys->next) {
+ id = (ID *) psys->part;
+ if (id) {
+ ID_NEW_REMAP_US(psys->part)
+ else {
+ psys->part = ID_NEW_SET(psys->part, BKE_particlesettings_copy(bmain, psys->part));
+ }
+
+ if (dupflag & USER_DUP_ACT) {
+ BKE_animdata_copy_id_action(&psys->part->id, true);
+ }
+
+ id_us_min(id);
+ }
+ }
+ }
id = obn->data;
didit = 0;
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 6b3284fe8b1..111afcdc7a7 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -75,6 +75,7 @@
#include "BKE_mball.h"
#include "BKE_mesh.h"
#include "BKE_object.h"
+#include "BKE_pointcache.h"
#include "BKE_property.h"
#include "BKE_sca.h"
#include "BKE_softbody.h"
@@ -429,9 +430,22 @@ void ED_object_editmode_exit(bContext *C, int flag)
/* freedata only 0 now on file saves and render */
if (freedata) {
+ ListBase pidlist;
+ PTCacheID *pid;
+
/* for example; displist make is different in editmode */
scene->obedit = NULL; // XXX for context
+ /* flag object caches as outdated */
+ BKE_ptcache_ids_from_object(&pidlist, obedit, scene, 0);
+ for (pid = pidlist.first; pid; pid = pid->next) {
+ if (pid->type != PTCACHE_TYPE_PARTICLES) /* particles don't need reset on geometry change */
+ pid->cache->flag |= PTCACHE_OUTDATED;
+ }
+ BLI_freelistN(&pidlist);
+
+ BKE_ptcache_object_reset(scene, obedit, PTCACHE_RESET_OUTDATED);
+
/* also flush ob recalc, doesn't take much overhead, but used for particles */
DAG_id_tag_update(&obedit->id, OB_RECALC_OB | OB_RECALC_DATA);
@@ -1569,9 +1583,13 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
ob = CTX_data_active_object(C);
if (ob) {
+ const bool use_mode_particle_edit = (BLI_listbase_is_empty(&ob->particlesystem) == false) ||
+ (ob->soft != NULL) ||
+ (modifiers_findByType(ob, eModifierType_Cloth) != NULL);
while (input->identifier) {
if ((input->value == OB_MODE_EDIT && OB_TYPE_SUPPORT_EDITMODE(ob->type)) ||
(input->value == OB_MODE_POSE && (ob->type == OB_ARMATURE)) ||
+ (input->value == OB_MODE_PARTICLE_EDIT && use_mode_particle_edit) ||
(ELEM(input->value, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT,
OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT) && (ob->type == OB_MESH)) ||
(input->value == OB_MODE_OBJECT))
@@ -1613,6 +1631,8 @@ static const char *object_mode_op_string(int mode)
return "PAINT_OT_weight_paint_toggle";
if (mode == OB_MODE_TEXTURE_PAINT)
return "PAINT_OT_texture_paint_toggle";
+ if (mode == OB_MODE_PARTICLE_EDIT)
+ return "PARTICLE_OT_particle_edit_toggle";
if (mode == OB_MODE_POSE)
return "OBJECT_OT_posemode_toggle";
if (mode == OB_MODE_GPENCIL)
@@ -1634,7 +1654,7 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode)
switch (ob->type) {
case OB_MESH:
if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT |
- OB_MODE_TEXTURE_PAINT))
+ OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT))
{
return true;
}
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 3a6e585d4c5..9710e4f843d 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -169,6 +169,7 @@ void OBJECT_OT_modifier_remove(struct wmOperatorType *ot);
void OBJECT_OT_modifier_move_up(struct wmOperatorType *ot);
void OBJECT_OT_modifier_move_down(struct wmOperatorType *ot);
void OBJECT_OT_modifier_apply(struct wmOperatorType *ot);
+void OBJECT_OT_modifier_convert(struct wmOperatorType *ot);
void OBJECT_OT_modifier_copy(struct wmOperatorType *ot);
void OBJECT_OT_multires_subdivide(struct wmOperatorType *ot);
void OBJECT_OT_multires_reshape(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c
index 9175bd69a28..b44ddf925a8 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.c
@@ -41,7 +41,6 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_force.h"
-#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_bitmap.h"
@@ -72,6 +71,7 @@
#include "BKE_object_deform.h"
#include "BKE_ocean.h"
#include "BKE_paint.h"
+#include "BKE_particle.h"
#include "BKE_softbody.h"
#include "BKE_editmesh.h"
@@ -111,56 +111,64 @@ ModifierData *ED_object_modifier_add(ReportList *reports, Main *bmain, Scene *sc
}
}
- /* get new modifier data to add */
- new_md = modifier_new(type);
-
- if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
- md = ob->modifiers.first;
-
- while (md && modifierType_getInfo(md->type)->type == eModifierTypeType_OnlyDeform)
- md = md->next;
-
- BLI_insertlinkbefore(&ob->modifiers, md, new_md);
+ if (type == eModifierType_ParticleSystem) {
+ /* don't need to worry about the new modifier's name, since that is set to the number
+ * of particle systems which shouldn't have too many duplicates
+ */
+ new_md = object_add_particle_system(scene, ob, name);
}
- else
- BLI_addtail(&ob->modifiers, new_md);
+ else {
+ /* get new modifier data to add */
+ new_md = modifier_new(type);
+
+ if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
+ md = ob->modifiers.first;
+
+ while (md && modifierType_getInfo(md->type)->type == eModifierTypeType_OnlyDeform)
+ md = md->next;
+
+ BLI_insertlinkbefore(&ob->modifiers, md, new_md);
+ }
+ else
+ BLI_addtail(&ob->modifiers, new_md);
- if (name) {
- BLI_strncpy_utf8(new_md->name, name, sizeof(new_md->name));
- }
+ if (name) {
+ BLI_strncpy_utf8(new_md->name, name, sizeof(new_md->name));
+ }
- /* make sure modifier data has unique name */
+ /* make sure modifier data has unique name */
- modifier_unique_name(&ob->modifiers, new_md);
-
- /* special cases */
- if (type == eModifierType_Softbody) {
- if (!ob->soft) {
- ob->soft = sbNew(scene);
- ob->softflag |= OB_SB_GOAL | OB_SB_EDGES;
- }
- }
- else if (type == eModifierType_Collision) {
- if (!ob->pd)
- ob->pd = object_add_collision_fields(0);
+ modifier_unique_name(&ob->modifiers, new_md);
- ob->pd->deflect = 1;
- }
- else if (type == eModifierType_Surface) {
- /* pass */
- }
- else if (type == eModifierType_Multires) {
- /* set totlvl from existing MDISPS layer if object already had it */
- multiresModifier_set_levels_from_disps((MultiresModifierData *)new_md, ob);
+ /* special cases */
+ if (type == eModifierType_Softbody) {
+ if (!ob->soft) {
+ ob->soft = sbNew(scene);
+ ob->softflag |= OB_SB_GOAL | OB_SB_EDGES;
+ }
+ }
+ else if (type == eModifierType_Collision) {
+ if (!ob->pd)
+ ob->pd = object_add_collision_fields(0);
+
+ ob->pd->deflect = 1;
+ }
+ else if (type == eModifierType_Surface) {
+ /* pass */
+ }
+ else if (type == eModifierType_Multires) {
+ /* set totlvl from existing MDISPS layer if object already had it */
+ multiresModifier_set_levels_from_disps((MultiresModifierData *)new_md, ob);
- if (ob->mode & OB_MODE_SCULPT) {
- /* ensure that grid paint mask layer is created */
- BKE_sculpt_mask_layers_ensure(ob, (MultiresModifierData *)new_md);
+ if (ob->mode & OB_MODE_SCULPT) {
+ /* ensure that grid paint mask layer is created */
+ BKE_sculpt_mask_layers_ensure(ob, (MultiresModifierData *)new_md);
+ }
+ }
+ else if (type == eModifierType_Skin) {
+ /* ensure skin-node customdata exists */
+ BKE_mesh_ensure_skin_customdata(ob->data);
}
- }
- else if (type == eModifierType_Skin) {
- /* ensure skin-node customdata exists */
- BKE_mesh_ensure_skin_customdata(ob->data);
}
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
@@ -272,7 +280,14 @@ static bool object_modifier_remove(Main *bmain, Object *ob, ModifierData *md,
}
/* special cases */
- if (md->type == eModifierType_Softbody) {
+ if (md->type == eModifierType_ParticleSystem) {
+ ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)md;
+
+ BLI_remlink(&ob->particlesystem, psmd->psys);
+ psys_free(ob, psmd->psys);
+ psmd->psys = NULL;
+ }
+ else if (md->type == eModifierType_Softbody) {
if (ob->soft) {
sbFree(ob->soft);
ob->soft = NULL;
@@ -299,6 +314,12 @@ static bool object_modifier_remove(Main *bmain, Object *ob, ModifierData *md,
modifier_skin_customdata_delete(ob);
}
+ if (ELEM(md->type, eModifierType_Softbody, eModifierType_Cloth) &&
+ BLI_listbase_is_empty(&ob->particlesystem))
+ {
+ ob->mode &= ~OB_MODE_PARTICLE_EDIT;
+ }
+
DAG_relations_tag_update(bmain);
BLI_remlink(&ob->modifiers, md);
@@ -390,6 +411,115 @@ int ED_object_modifier_move_down(ReportList *reports, Object *ob, ModifierData *
return 1;
}
+int ED_object_modifier_convert(ReportList *UNUSED(reports), Main *bmain, Scene *scene, Object *ob, ModifierData *md)
+{
+ Object *obn;
+ ParticleSystem *psys;
+ ParticleCacheKey *key, **cache;
+ ParticleSettings *part;
+ Mesh *me;
+ MVert *mvert;
+ MEdge *medge;
+ int a, k, kmax;
+ int totvert = 0, totedge = 0, cvert = 0;
+ int totpart = 0, totchild = 0;
+
+ if (md->type != eModifierType_ParticleSystem) return 0;
+ if (ob && ob->mode & OB_MODE_PARTICLE_EDIT) return 0;
+
+ psys = ((ParticleSystemModifierData *)md)->psys;
+ part = psys->part;
+
+ if (part->ren_as != PART_DRAW_PATH || psys->pathcache == NULL)
+ return 0;
+
+ totpart = psys->totcached;
+ totchild = psys->totchildcache;
+
+ if (totchild && (part->draw & PART_DRAW_PARENT) == 0)
+ totpart = 0;
+
+ /* count */
+ cache = psys->pathcache;
+ for (a = 0; a < totpart; a++) {
+ key = cache[a];
+
+ if (key->segments > 0) {
+ totvert += key->segments + 1;
+ totedge += key->segments;
+ }
+ }
+
+ cache = psys->childcache;
+ for (a = 0; a < totchild; a++) {
+ key = cache[a];
+
+ if (key->segments > 0) {
+ totvert += key->segments + 1;
+ totedge += key->segments;
+ }
+ }
+
+ if (totvert == 0) return 0;
+
+ /* add new mesh */
+ obn = BKE_object_add(bmain, scene, OB_MESH, NULL);
+ me = obn->data;
+
+ me->totvert = totvert;
+ me->totedge = totedge;
+
+ me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, totvert);
+ me->medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, NULL, totedge);
+ me->mface = CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, NULL, 0);
+
+ mvert = me->mvert;
+ medge = me->medge;
+
+ /* copy coordinates */
+ cache = psys->pathcache;
+ for (a = 0; a < totpart; a++) {
+ key = cache[a];
+ kmax = key->segments;
+ for (k = 0; k <= kmax; k++, key++, cvert++, mvert++) {
+ copy_v3_v3(mvert->co, key->co);
+ if (k) {
+ medge->v1 = cvert - 1;
+ medge->v2 = cvert;
+ medge->flag = ME_EDGEDRAW | ME_EDGERENDER | ME_LOOSEEDGE;
+ medge++;
+ }
+ else {
+ /* cheap trick to select the roots */
+ mvert->flag |= SELECT;
+ }
+ }
+ }
+
+ cache = psys->childcache;
+ for (a = 0; a < totchild; a++) {
+ key = cache[a];
+ kmax = key->segments;
+ for (k = 0; k <= kmax; k++, key++, cvert++, mvert++) {
+ copy_v3_v3(mvert->co, key->co);
+ if (k) {
+ medge->v1 = cvert - 1;
+ medge->v2 = cvert;
+ medge->flag = ME_EDGEDRAW | ME_EDGERENDER | ME_LOOSEEDGE;
+ medge++;
+ }
+ else {
+ /* cheap trick to select the roots */
+ mvert->flag |= SELECT;
+ }
+ }
+ }
+
+ DAG_relations_tag_update(bmain);
+
+ return 1;
+}
+
static int modifier_apply_shape(ReportList *reports, Scene *scene, Object *ob, ModifierData *md)
{
const ModifierTypeInfo *mti = modifierType_getInfo(md->type);
@@ -520,6 +650,20 @@ static int modifier_apply_obdata(ReportList *reports, Scene *scene, Object *ob,
return 0;
}
+ /* lattice modifier can be applied to particle system too */
+ if (ob->particlesystem.first) {
+
+ ParticleSystem *psys = ob->particlesystem.first;
+
+ for (; psys; psys = psys->next) {
+
+ if (psys->part->type != PART_HAIR)
+ continue;
+
+ psys_apply_hair_lattice(scene, ob, psys);
+ }
+ }
+
return 1;
}
@@ -728,13 +872,21 @@ ModifierData *edit_modifier_property_get(wmOperator *op, Object *ob, int type)
static int modifier_remove_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
Object *ob = ED_object_active_context(C);
ModifierData *md = edit_modifier_property_get(op, ob, 0);
+ int mode_orig = ob->mode;
if (!md || !ED_object_modifier_remove(op->reports, bmain, ob, md))
return OPERATOR_CANCELLED;
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ /* if cloth/softbody was removed, particle mode could be cleared */
+ 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);
return OPERATOR_FINISHED;
}
@@ -890,6 +1042,47 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot)
edit_modifier_properties(ot);
}
+/************************ convert modifier operator *********************/
+
+static int modifier_convert_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = ED_object_active_context(C);
+ ModifierData *md = edit_modifier_property_get(op, ob, 0);
+
+ if (!md || !ED_object_modifier_convert(op->reports, bmain, scene, ob, md))
+ return OPERATOR_CANCELLED;
+
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int modifier_convert_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (edit_modifier_invoke_properties(C, op))
+ return modifier_convert_exec(C, op);
+ else
+ return OPERATOR_CANCELLED;
+}
+
+void OBJECT_OT_modifier_convert(wmOperatorType *ot)
+{
+ ot->name = "Convert Modifier";
+ ot->description = "Convert particles to a mesh object";
+ ot->idname = "OBJECT_OT_modifier_convert";
+
+ ot->invoke = modifier_convert_invoke;
+ ot->exec = modifier_convert_exec;
+ ot->poll = edit_modifier_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+}
+
/************************ copy modifier operator *********************/
static int modifier_copy_exec(bContext *C, wmOperator *op)
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index 4837ca50105..7e7e1ef182c 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -133,6 +133,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_modifier_move_up);
WM_operatortype_append(OBJECT_OT_modifier_move_down);
WM_operatortype_append(OBJECT_OT_modifier_apply);
+ WM_operatortype_append(OBJECT_OT_modifier_convert);
WM_operatortype_append(OBJECT_OT_modifier_copy);
WM_operatortype_append(OBJECT_OT_multires_subdivide);
WM_operatortype_append(OBJECT_OT_multires_reshape);
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 088ade6963d..d30022c01f8 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -43,6 +43,7 @@
#include "DNA_lattice_types.h"
#include "DNA_material_types.h"
#include "DNA_meta_types.h"
+#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "DNA_object_types.h"
@@ -2214,6 +2215,7 @@ static int make_local_exec(bContext *C, wmOperator *op)
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
AnimData *adt;
+ ParticleSystem *psys;
Material *ma, ***matarar;
Lamp *la;
ID *id;
@@ -2280,6 +2282,9 @@ static int make_local_exec(bContext *C, wmOperator *op)
}
}
+ for (psys = ob->particlesystem.first; psys; psys = psys->next)
+ id_make_local(bmain, &psys->part->id, false, false);
+
adt = BKE_animdata_from_id(&ob->id);
if (adt) BKE_animdata_make_local(adt);
}
diff --git a/source/blender/editors/object/object_select.c b/source/blender/editors/object/object_select.c
index 7db32957b42..f1b7186f8a1 100644
--- a/source/blender/editors/object/object_select.c
+++ b/source/blender/editors/object/object_select.c
@@ -37,7 +37,6 @@
#include "DNA_group_types.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h"
-#include "DNA_object_types.h"
#include "DNA_property_types.h"
#include "DNA_scene_types.h"
#include "DNA_armature_types.h"
@@ -54,6 +53,7 @@
#include "BKE_group.h"
#include "BKE_main.h"
#include "BKE_material.h"
+#include "BKE_particle.h"
#include "BKE_property.h"
#include "BKE_report.h"
#include "BKE_scene.h"
@@ -192,6 +192,7 @@ enum {
OBJECT_SELECT_LINKED_MATERIAL,
OBJECT_SELECT_LINKED_TEXTURE,
OBJECT_SELECT_LINKED_DUPGROUP,
+ OBJECT_SELECT_LINKED_PARTICLE,
OBJECT_SELECT_LINKED_LIBRARY,
OBJECT_SELECT_LINKED_LIBRARY_OBDATA
};
@@ -202,6 +203,7 @@ static EnumPropertyItem prop_select_linked_types[] = {
{OBJECT_SELECT_LINKED_MATERIAL, "MATERIAL", 0, "Material", ""},
{OBJECT_SELECT_LINKED_TEXTURE, "TEXTURE", 0, "Texture", ""},
{OBJECT_SELECT_LINKED_DUPGROUP, "DUPGROUP", 0, "Dupligroup", ""},
+ {OBJECT_SELECT_LINKED_PARTICLE, "PARTICLE", 0, "Particle System", ""},
{OBJECT_SELECT_LINKED_LIBRARY, "LIBRARY", 0, "Library", ""},
{OBJECT_SELECT_LINKED_LIBRARY_OBDATA, "LIBRARY_OBDATA", 0, "Library (Object Data)", ""},
{0, NULL, 0, NULL, NULL}
@@ -311,6 +313,37 @@ static bool object_select_all_by_dup_group(bContext *C, Object *ob)
return changed;
}
+static bool object_select_all_by_particle(bContext *C, Object *ob)
+{
+ ParticleSystem *psys_act = psys_get_current(ob);
+ bool changed = false;
+
+ CTX_DATA_BEGIN (C, Base *, base, visible_bases)
+ {
+ if ((base->flag & SELECT) == 0) {
+ /* loop through other particles*/
+ ParticleSystem *psys;
+
+ for (psys = base->object->particlesystem.first; psys; psys = psys->next) {
+ if (psys->part == psys_act->part) {
+ base->flag |= SELECT;
+ changed = true;
+ break;
+ }
+
+ if (base->flag & SELECT) {
+ break;
+ }
+ }
+
+ base->object->flag = base->flag;
+ }
+ }
+ CTX_DATA_END;
+
+ return changed;
+}
+
static bool object_select_all_by_library(bContext *C, Library *lib)
{
bool changed = false;
@@ -428,6 +461,12 @@ static int object_select_linked_exec(bContext *C, wmOperator *op)
changed = object_select_all_by_dup_group(C, ob);
}
+ else if (nr == OBJECT_SELECT_LINKED_PARTICLE) {
+ if (BLI_listbase_is_empty(&ob->particlesystem))
+ return OPERATOR_CANCELLED;
+
+ changed = object_select_all_by_particle(C, ob);
+ }
else if (nr == OBJECT_SELECT_LINKED_LIBRARY) {
/* do nothing */
changed = object_select_all_by_library(C, ob->id.lib);
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;
+}
+
diff --git a/source/blender/editors/render/render_shading.c b/source/blender/editors/render/render_shading.c
index 7eb2552487b..837573ad175 100644
--- a/source/blender/editors/render/render_shading.c
+++ b/source/blender/editors/render/render_shading.c
@@ -36,6 +36,7 @@
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_world_types.h"
@@ -1774,6 +1775,9 @@ static void copy_mtex_copybuf(ID *id)
mtex = &(((World *)id)->mtex[(int)((World *)id)->texact]);
// mtex= wrld->mtex[(int)wrld->texact]; // TODO
break;
+ case ID_PA:
+ mtex = &(((ParticleSettings *)id)->mtex[(int)((ParticleSettings *)id)->texact]);
+ break;
case ID_LS:
mtex = &(((FreestyleLineStyle *)id)->mtex[(int)((FreestyleLineStyle *)id)->texact]);
break;
@@ -1807,6 +1811,9 @@ static void paste_mtex_copybuf(ID *id)
mtex = &(((World *)id)->mtex[(int)((World *)id)->texact]);
// mtex= wrld->mtex[(int)wrld->texact]; // TODO
break;
+ case ID_PA:
+ mtex = &(((ParticleSettings *)id)->mtex[(int)((ParticleSettings *)id)->texact]);
+ break;
case ID_LS:
mtex = &(((FreestyleLineStyle *)id)->mtex[(int)((FreestyleLineStyle *)id)->texact]);
break;
@@ -1875,6 +1882,7 @@ static int paste_mtex_exec(bContext *C, wmOperator *UNUSED(op))
Material *ma = CTX_data_pointer_get_type(C, "material", &RNA_Material).data;
Lamp *la = CTX_data_pointer_get_type(C, "lamp", &RNA_Lamp).data;
World *wo = CTX_data_pointer_get_type(C, "world", &RNA_World).data;
+ ParticleSystem *psys = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem).data;
FreestyleLineStyle *linestyle = CTX_data_pointer_get_type(C, "line_style", &RNA_FreestyleLineStyle).data;
if (ma)
@@ -1883,6 +1891,8 @@ static int paste_mtex_exec(bContext *C, wmOperator *UNUSED(op))
id = &la->id;
else if (wo)
id = &wo->id;
+ else if (psys)
+ id = &psys->part->id;
else if (linestyle)
id = &linestyle->id;
diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c
index 2cf0a16f236..c165bbfd301 100644
--- a/source/blender/editors/screen/screen_context.c
+++ b/source/blender/editors/screen/screen_context.c
@@ -392,6 +392,12 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult
return 1;
}
+ else if (CTX_data_equals(member, "particle_edit_object")) {
+ if (obact && (obact->mode & OB_MODE_PARTICLE_EDIT))
+ CTX_data_id_pointer_set(result, &obact->id);
+
+ return 1;
+ }
else if (CTX_data_equals(member, "sequences")) {
Editing *ed = BKE_sequencer_editing_get(scene, false);
if (ed) {
diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c
index ef99fedbec0..991025a4d5d 100644
--- a/source/blender/editors/sculpt_paint/paint_vertex.c
+++ b/source/blender/editors/sculpt_paint/paint_vertex.c
@@ -43,6 +43,7 @@
#include "DNA_armature_types.h"
#include "DNA_mesh_types.h"
+#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DNA_brush_types.h"
#include "DNA_object_types.h"
@@ -2421,6 +2422,21 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke)
/* frees prev buffer */
copy_wpaint_prev(ts->wpaint, NULL, 0);
+
+ /* and particles too */
+ if (ob->particlesystem.first) {
+ ParticleSystem *psys;
+ int i;
+
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ for (i = 0; i < PSYS_TOT_VG; i++) {
+ if (psys->vgroup[i] == ob->actdef) {
+ psys->recalc |= PSYS_RECALC_RESET;
+ break;
+ }
+ }
+ }
+ }
DAG_id_tag_update(ob->data, 0);
diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c
index a1ecb1c4f5c..da3364d872d 100644
--- a/source/blender/editors/space_buttons/buttons_context.c
+++ b/source/blender/editors/space_buttons/buttons_context.c
@@ -46,13 +46,13 @@
#include "DNA_world_types.h"
#include "DNA_brush_types.h"
#include "DNA_linestyle_types.h"
-#include "DNA_object_types.h"
#include "BKE_context.h"
#include "BKE_action.h"
#include "BKE_material.h"
#include "BKE_modifier.h"
#include "BKE_paint.h"
+#include "BKE_particle.h"
#include "BKE_screen.h"
#include "BKE_texture.h"
#include "BKE_linestyle.h"
@@ -339,6 +339,34 @@ static int buttons_context_path_pose_bone(ButsContextPath *path)
return 0;
}
+
+static int buttons_context_path_particle(ButsContextPath *path)
+{
+ Object *ob;
+ ParticleSystem *psys;
+ PointerRNA *ptr = &path->ptr[path->len - 1];
+
+ /* if we already have (pinned) particle settings, we're done */
+ if (RNA_struct_is_a(ptr->type, &RNA_ParticleSettings)) {
+ return 1;
+ }
+ /* if we have an object, get the active particle system */
+ if (buttons_context_path_object(path)) {
+ ob = path->ptr[path->len - 1].data;
+
+ if (ob && ob->type == OB_MESH) {
+ psys = psys_get_current(ob);
+
+ RNA_pointer_create(&ob->id, &RNA_ParticleSystem, psys, &path->ptr[path->len]);
+ path->len++;
+ return 1;
+ }
+ }
+
+ /* no path to a particle system possible */
+ return 0;
+}
+
static int buttons_context_path_brush(ButsContextPath *path)
{
Scene *scene;
@@ -393,6 +421,8 @@ static int buttons_context_path_texture(ButsContextPath *path, ButsContextTextur
buttons_context_path_world(path);
else if (GS(id->name) == ID_LA)
buttons_context_path_data(path, OB_LAMP);
+ else if (GS(id->name) == ID_PA)
+ buttons_context_path_particle(path);
else if (GS(id->name) == ID_OB)
buttons_context_path_object(path);
else if (GS(id->name) == ID_LS)
@@ -411,6 +441,7 @@ static int buttons_context_path_texture(ButsContextPath *path, ButsContextTextur
Material *ma;
Lamp *la;
World *wo;
+ ParticleSystem *psys;
FreestyleLineStyle *ls;
Tex *tex;
PointerRNA *ptr = &path->ptr[path->len - 1];
@@ -431,6 +462,28 @@ static int buttons_context_path_texture(ButsContextPath *path, ButsContextTextur
return 1;
}
}
+ /* try particles */
+ else if ((path->tex_ctx == SB_TEXC_PARTICLES) && buttons_context_path_particle(path)) {
+ if (path->ptr[path->len - 1].type == &RNA_ParticleSettings) {
+ ParticleSettings *part = path->ptr[path->len - 1].data;
+
+ tex = give_current_particle_texture(part);
+ RNA_id_pointer_create(&tex->id, &path->ptr[path->len]);
+ path->len++;
+ return 1;
+ }
+ else {
+ psys = path->ptr[path->len - 1].data;
+
+ if (psys && psys->part && GS(psys->part->id.name) == ID_PA) {
+ tex = give_current_particle_texture(psys->part);
+
+ RNA_id_pointer_create(&tex->id, &path->ptr[path->len]);
+ path->len++;
+ return 1;
+ }
+ }
+ }
/* try material */
else if ((path->tex_ctx == SB_TEXC_MATERIAL) && buttons_context_path_material(path, true, false)) {
ma = path->ptr[path->len - 1].data;
@@ -557,6 +610,9 @@ static int buttons_context_path(const bContext *C, ButsContextPath *path, int ma
case BCONTEXT_DATA:
found = buttons_context_path_data(path, -1);
break;
+ case BCONTEXT_PARTICLE:
+ found = buttons_context_path_particle(path);
+ break;
case BCONTEXT_MATERIAL:
found = buttons_context_path_material(path, false, (sbuts->texuser != NULL));
break;
@@ -844,7 +900,14 @@ int buttons_context(const bContext *C, const char *member, bContextDataResult *r
ButsContextTexture *ct = sbuts->texuser;
PointerRNA *ptr;
- if (ct) {
+ /* Particles slots are used in both old and new textures handling. */
+ if ((ptr = get_pointer_type(path, &RNA_ParticleSystem))) {
+ ParticleSettings *part = ((ParticleSystem *)ptr->data)->part;
+
+ if (part)
+ CTX_data_pointer_set(result, &part->id, &RNA_ParticleSettingsTextureSlot, part->mtex[(int)part->texact]);
+ }
+ else if (ct) {
return 0; /* new shading system */
}
else if ((ptr = get_pointer_type(path, &RNA_Material))) {
@@ -900,6 +963,38 @@ int buttons_context(const bContext *C, const char *member, bContextDataResult *r
set_pointer_type(path, result, &RNA_PoseBone);
return 1;
}
+ else if (CTX_data_equals(member, "particle_system")) {
+ set_pointer_type(path, result, &RNA_ParticleSystem);
+ return 1;
+ }
+ else if (CTX_data_equals(member, "particle_system_editable")) {
+ if (PE_poll((bContext *)C))
+ set_pointer_type(path, result, &RNA_ParticleSystem);
+ else
+ CTX_data_pointer_set(result, NULL, &RNA_ParticleSystem, NULL);
+ return 1;
+ }
+ else if (CTX_data_equals(member, "particle_settings")) {
+ /* only available when pinned */
+ PointerRNA *ptr = get_pointer_type(path, &RNA_ParticleSettings);
+
+ if (ptr && ptr->data) {
+ CTX_data_pointer_set(result, ptr->id.data, &RNA_ParticleSettings, ptr->data);
+ return 1;
+ }
+ else {
+ /* get settings from active particle system instead */
+ ptr = get_pointer_type(path, &RNA_ParticleSystem);
+
+ if (ptr && ptr->data) {
+ ParticleSettings *part = ((ParticleSystem *)ptr->data)->part;
+ CTX_data_pointer_set(result, ptr->id.data, &RNA_ParticleSettings, part);
+ return 1;
+ }
+ }
+ set_pointer_type(path, result, &RNA_ParticleSettings);
+ return 1;
+ }
else if (CTX_data_equals(member, "cloth")) {
PointerRNA *ptr = get_pointer_type(path, &RNA_Object);
@@ -1069,6 +1164,14 @@ ID *buttons_context_id_path(const bContext *C)
for (a = path->len - 1; a >= 0; a--) {
ptr = &path->ptr[a];
+ /* pin particle settings instead of system, since only settings are an idblock*/
+ if (sbuts->mainb == BCONTEXT_PARTICLE && sbuts->flag & SB_PIN_CONTEXT) {
+ if (ptr->type == &RNA_ParticleSystem && ptr->data) {
+ ParticleSystem *psys = (ParticleSystem *)ptr->data;
+ return &psys->part->id;
+ }
+ }
+
if (ptr->id.data) {
return ptr->id.data;
}
diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c
index 87cf0e8c9e3..72de7e5c81c 100644
--- a/source/blender/editors/space_buttons/buttons_texture.c
+++ b/source/blender/editors/space_buttons/buttons_texture.c
@@ -46,6 +46,7 @@
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_object_force.h"
+#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -58,6 +59,7 @@
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_paint.h"
+#include "BKE_particle.h"
#include "BKE_scene.h"
#ifdef WITH_FREESTYLE
# include "BKE_freestyle.h"
@@ -96,6 +98,12 @@ bool ED_texture_context_check_lamp(const bContext *C)
return (ob && (ob->type == OB_LAMP));
}
+bool ED_texture_context_check_particles(const bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ return (ob && ob->particlesystem.first);
+}
+
bool ED_texture_context_check_linestyle(const bContext *C)
{
#ifdef WITH_FREESTYLE
@@ -170,6 +178,7 @@ static void set_texture_context(const bContext *C, SpaceButs *sbuts)
bool valid_world = ED_texture_context_check_world(C);
bool valid_material = ED_texture_context_check_material(C);
bool valid_lamp = ED_texture_context_check_lamp(C);
+ bool valid_particles = ED_texture_context_check_particles(C);
bool valid_linestyle = ED_texture_context_check_linestyle(C);
bool valid_others = ED_texture_context_check_others(C);
@@ -183,6 +192,9 @@ static void set_texture_context(const bContext *C, SpaceButs *sbuts)
else if ((sbuts->mainb == BCONTEXT_DATA) && valid_lamp) {
sbuts->texture_context = sbuts->texture_context_prev = SB_TEXC_LAMP;
}
+ else if ((sbuts->mainb == BCONTEXT_PARTICLE) && valid_particles) {
+ sbuts->texture_context = sbuts->texture_context_prev = SB_TEXC_PARTICLES;
+ }
else if ((sbuts->mainb == BCONTEXT_RENDER_LAYER) && valid_linestyle) {
sbuts->texture_context = sbuts->texture_context_prev = SB_TEXC_LINESTYLE;
}
@@ -194,6 +206,7 @@ static void set_texture_context(const bContext *C, SpaceButs *sbuts)
(((sbuts->texture_context_prev == SB_TEXC_WORLD) && valid_world) ||
((sbuts->texture_context_prev == SB_TEXC_MATERIAL) && valid_material) ||
((sbuts->texture_context_prev == SB_TEXC_LAMP) && valid_lamp) ||
+ ((sbuts->texture_context_prev == SB_TEXC_PARTICLES) && valid_particles) ||
((sbuts->texture_context_prev == SB_TEXC_LINESTYLE) && valid_linestyle) ||
((sbuts->texture_context_prev == SB_TEXC_OTHER) && valid_others)))
{
@@ -203,6 +216,7 @@ static void set_texture_context(const bContext *C, SpaceButs *sbuts)
else if (((sbuts->texture_context == SB_TEXC_WORLD) && !valid_world) ||
((sbuts->texture_context == SB_TEXC_MATERIAL) && !valid_material) ||
((sbuts->texture_context == SB_TEXC_LAMP) && !valid_lamp) ||
+ ((sbuts->texture_context == SB_TEXC_PARTICLES) && !valid_particles) ||
((sbuts->texture_context == SB_TEXC_LINESTYLE) && !valid_linestyle) ||
((sbuts->texture_context == SB_TEXC_OTHER) && !valid_others))
{
@@ -214,6 +228,9 @@ static void set_texture_context(const bContext *C, SpaceButs *sbuts)
else if (valid_lamp) {
sbuts->texture_context = SB_TEXC_LAMP;
}
+ else if (valid_particles) {
+ sbuts->texture_context = SB_TEXC_PARTICLES;
+ }
else if (valid_linestyle) {
sbuts->texture_context = SB_TEXC_LINESTYLE;
}
@@ -358,9 +375,31 @@ static void buttons_texture_users_from_context(ListBase *users, const bContext *
buttons_texture_users_find_nodetree(users, &linestyle->id, linestyle->nodetree, N_("Line Style"));
if (ob) {
+ ParticleSystem *psys = psys_get_current(ob);
+ MTex *mtex;
+ int a;
+
/* modifiers */
modifiers_foreachTexLink(ob, buttons_texture_modifier_foreach, users);
+ /* particle systems */
+ if (psys && !limited_mode) {
+ for (a = 0; a < MAX_MTEX; a++) {
+ mtex = psys->part->mtex[a];
+
+ if (mtex) {
+ PointerRNA ptr;
+ PropertyRNA *prop;
+
+ RNA_pointer_create(&psys->part->id, &RNA_ParticleSettingsTextureSlot, mtex, &ptr);
+ prop = RNA_struct_find_property(&ptr, "texture");
+
+ buttons_texture_user_property_add(users, &psys->part->id, ptr, prop, N_("Particles"),
+ RNA_struct_ui_icon(&RNA_ParticleSettings), psys->name);
+ }
+ }
+ }
+
/* field */
if (ob->pd && ob->pd->forcefield == PFIELD_TEXTURE) {
PointerRNA ptr;
@@ -490,6 +529,17 @@ static void template_texture_select(bContext *C, void *user_p, void *UNUSED(arg)
ct->texture = tex;
+ if (user->ptr.type == &RNA_ParticleSettingsTextureSlot) {
+ /* stupid exception for particle systems which still uses influence
+ * from the old texture system, set the active texture slots as well */
+ ParticleSettings *part = user->ptr.id.data;
+ int a;
+
+ for (a = 0; a < MAX_MTEX; a++)
+ if (user->ptr.data == part->mtex[a])
+ part->texact = a;
+ }
+
if (sbuts && tex)
sbuts->preview = 1;
}
diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c
index f91a357504d..e4c23ad74f8 100644
--- a/source/blender/editors/space_buttons/space_buttons.c
+++ b/source/blender/editors/space_buttons/space_buttons.c
@@ -159,6 +159,8 @@ static void buttons_main_region_draw(const bContext *C, ARegion *ar)
ED_region_panels(C, ar, "material", sbuts->mainb, vertical);
else if (sbuts->mainb == BCONTEXT_TEXTURE)
ED_region_panels(C, ar, "texture", sbuts->mainb, vertical);
+ else if (sbuts->mainb == BCONTEXT_PARTICLE)
+ ED_region_panels(C, ar, "particle", sbuts->mainb, vertical);
else if (sbuts->mainb == BCONTEXT_PHYSICS)
ED_region_panels(C, ar, "physics", sbuts->mainb, vertical);
else if (sbuts->mainb == BCONTEXT_BONE)
@@ -279,6 +281,11 @@ static void buttons_area_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *
buttons_area_redraw(sa, BCONTEXT_CONSTRAINT);
buttons_area_redraw(sa, BCONTEXT_BONE_CONSTRAINT);
break;
+ case ND_PARTICLE:
+ if (wmn->action == NA_EDITED)
+ buttons_area_redraw(sa, BCONTEXT_PARTICLE);
+ sbuts->preview = 1;
+ break;
case ND_DRAW:
buttons_area_redraw(sa, BCONTEXT_OBJECT);
buttons_area_redraw(sa, BCONTEXT_DATA);
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index 5eb261890b2..7abe5ff5070 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -214,7 +214,7 @@ short ED_fileselect_set_params(SpaceFile *sfile)
params->filter_id = FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU | FILTER_ID_GD |
FILTER_ID_GR | FILTER_ID_IM | FILTER_ID_LA | FILTER_ID_LS | FILTER_ID_LT | FILTER_ID_MA |
FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | FILTER_ID_OB |
- FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | FILTER_ID_SO |
+ FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | FILTER_ID_SO |
FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF;
if (U.uiflag & USER_HIDE_DOT) {
diff --git a/source/blender/editors/space_info/info_stats.c b/source/blender/editors/space_info/info_stats.c
index 6e156750815..8dc6c4229b2 100644
--- a/source/blender/editors/space_info/info_stats.c
+++ b/source/blender/editors/space_info/info_stats.c
@@ -35,7 +35,6 @@
#include "DNA_group_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
-#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_math.h"
@@ -51,6 +50,7 @@
#include "BKE_DerivedMesh.h"
#include "BKE_key.h"
#include "BKE_paint.h"
+#include "BKE_particle.h"
#include "BKE_editmesh.h"
#include "ED_info.h"
@@ -273,7 +273,37 @@ static void stats_dupli_object(Base *base, Object *ob, SceneStats *stats)
{
if (base->flag & SELECT) stats->totobjsel++;
- if (ob->parent && (ob->parent->transflag & (OB_DUPLIVERTS | OB_DUPLIFACES))) {
+ if (ob->transflag & OB_DUPLIPARTS) {
+ /* Dupli Particles */
+ ParticleSystem *psys;
+ ParticleSettings *part;
+
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ part = psys->part;
+
+ if (part->draw_as == PART_DRAW_OB && part->dup_ob) {
+ int tot = count_particles(psys);
+ stats_object(part->dup_ob, 0, tot, stats);
+ }
+ else if (part->draw_as == PART_DRAW_GR && part->dup_group) {
+ GroupObject *go;
+ int tot, totgroup = 0, cur = 0;
+
+ for (go = part->dup_group->gobject.first; go; go = go->next)
+ totgroup++;
+
+ for (go = part->dup_group->gobject.first; go; go = go->next) {
+ tot = count_particles_mod(psys, totgroup, cur);
+ stats_object(go->ob, 0, tot, stats);
+ cur++;
+ }
+ }
+ }
+
+ stats_object(ob, base->flag & SELECT, 1, stats);
+ stats->totobj++;
+ }
+ else if (ob->parent && (ob->parent->transflag & (OB_DUPLIVERTS | OB_DUPLIFACES))) {
/* Dupli Verts/Faces */
int tot;
diff --git a/source/blender/editors/space_nla/nla_buttons.c b/source/blender/editors/space_nla/nla_buttons.c
index bb6cf568425..3243579f7d0 100644
--- a/source/blender/editors/space_nla/nla_buttons.c
+++ b/source/blender/editors/space_nla/nla_buttons.c
@@ -136,6 +136,7 @@ bool nla_panel_context(const bContext *C, PointerRNA *adt_ptr, PointerRNA *nlt_p
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSNTREE:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
diff --git a/source/blender/editors/space_nla/nla_channels.c b/source/blender/editors/space_nla/nla_channels.c
index 9a8a5df78e4..e9c46e9d04b 100644
--- a/source/blender/editors/space_nla/nla_channels.c
+++ b/source/blender/editors/space_nla/nla_channels.c
@@ -175,6 +175,7 @@ static int mouse_nla_channels(bContext *C, bAnimContext *ac, float x, int channe
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSNTREE:
+ case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
@@ -214,7 +215,7 @@ static int mouse_nla_channels(bContext *C, bAnimContext *ac, float x, int channe
/* offset for start of channel (on LHS of channel-list) */
if (ale->id) {
/* special exception for materials and particles */
- if (GS(ale->id->name) == ID_MA)
+ if (ELEM(GS(ale->id->name), ID_MA, ID_PA))
offset = 21 + NLACHANNEL_BUTTON_WIDTH;
else
offset = 14;
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index fd55f4d4fdc..18f4a02ab72 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -1080,6 +1080,8 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto
UI_icon_draw(x, y, ICON_MODIFIER); break;
case TSE_LINKED_OB:
UI_icon_draw(x, y, ICON_OBJECT_DATA); break;
+ case TSE_LINKED_PSYS:
+ UI_icon_draw(x, y, ICON_PARTICLES); break;
case TSE_MODIFIER:
{
Object *ob = (Object *)tselem->id;
@@ -1107,6 +1109,10 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto
UI_icon_draw(x, y, ICON_MOD_SOFT); break;
case eModifierType_Boolean:
UI_icon_draw(x, y, ICON_MOD_BOOLEAN); break;
+ case eModifierType_ParticleSystem:
+ UI_icon_draw(x, y, ICON_MOD_PARTICLES); break;
+ case eModifierType_ParticleInstance:
+ UI_icon_draw(x, y, ICON_MOD_PARTICLES); break;
case eModifierType_EdgeSplit:
UI_icon_draw(x, y, ICON_MOD_EDGESPLIT); break;
case eModifierType_Array:
diff --git a/source/blender/editors/space_outliner/outliner_intern.h b/source/blender/editors/space_outliner/outliner_intern.h
index c5dfbf1819b..f23c294c488 100644
--- a/source/blender/editors/space_outliner/outliner_intern.h
+++ b/source/blender/editors/space_outliner/outliner_intern.h
@@ -61,7 +61,7 @@ typedef struct TreeElement {
#define TREESTORE_ID_TYPE(_id) \
(ELEM(GS((_id)->name), ID_SCE, ID_LI, ID_OB, ID_ME, ID_CU, ID_MB, ID_NT, ID_MA, ID_TE, ID_IM, ID_LT, ID_LA, ID_CA) || \
- ELEM(GS((_id)->name), ID_KE, ID_WO, ID_SPK, ID_GR, ID_AR, ID_AC, ID_BR, ID_GD, ID_LS) || \
+ ELEM(GS((_id)->name), ID_KE, ID_WO, ID_SPK, ID_GR, ID_AR, ID_AC, ID_BR, ID_PA, ID_GD, ID_LS) || \
ELEM(GS((_id)->name), ID_SCR, ID_WM, ID_TXT, ID_VF, ID_SO, ID_CF, ID_PAL)) /* Only in 'blendfile' mode ... :/ */
/* TreeElement->flag */
diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c
index a9f834c509f..a73e160f357 100644
--- a/source/blender/editors/space_outliner/outliner_select.c
+++ b/source/blender/editors/space_outliner/outliner_select.c
@@ -617,6 +617,20 @@ static eOLDrawState tree_element_active_modifier(
return OL_DRAWSEL_NONE;
}
+static eOLDrawState tree_element_active_psys(
+ bContext *C, Scene *UNUSED(scene), TreeElement *UNUSED(te), TreeStoreElem *tselem, const eOLSetState set)
+{
+ if (set != OL_SETSEL_NONE) {
+ Object *ob = (Object *)tselem->id;
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
+
+// XXX extern_set_butspace(F7KEY, 0);
+ }
+
+ return OL_DRAWSEL_NONE;
+}
+
static int tree_element_active_constraint(
bContext *C, TreeElement *UNUSED(te), TreeStoreElem *tselem, const eOLSetState set)
{
@@ -795,6 +809,8 @@ eOLDrawState tree_element_type_active(
return OL_DRAWSEL_NORMAL;
}
break;
+ case TSE_LINKED_PSYS:
+ return tree_element_active_psys(C, scene, te, tselem, set);
case TSE_POSE_BASE:
return tree_element_active_pose(C, scene, te, tselem, set);
case TSE_POSE_CHANNEL:
diff --git a/source/blender/editors/space_outliner/outliner_tree.c b/source/blender/editors/space_outliner/outliner_tree.c
index d1c75b01157..ec46c5df9a0 100644
--- a/source/blender/editors/space_outliner/outliner_tree.c
+++ b/source/blender/editors/space_outliner/outliner_tree.c
@@ -46,6 +46,7 @@
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
+#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "DNA_sequence_types.h"
@@ -621,6 +622,14 @@ static void outliner_add_object_contents(SpaceOops *soops, TreeElement *te, Tree
else if (md->type == eModifierType_Hook) {
outliner_add_element(soops, &ten->subtree, ((HookModifierData *) md)->object, ten, TSE_LINKED_OB, 0);
}
+ else if (md->type == eModifierType_ParticleSystem) {
+ ParticleSystem *psys = ((ParticleSystemModifierData *) md)->psys;
+ TreeElement *ten_psys;
+
+ ten_psys = outliner_add_element(soops, &ten->subtree, ob, te, TSE_LINKED_PSYS, 0);
+ ten_psys->directdata = psys;
+ ten_psys->name = psys->part->id.name + 2;
+ }
}
}
diff --git a/source/blender/editors/space_time/space_time.c b/source/blender/editors/space_time/space_time.c
index f199820dd10..5e7060d6651 100644
--- a/source/blender/editors/space_time/space_time.c
+++ b/source/blender/editors/space_time/space_time.c
@@ -50,6 +50,7 @@
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_screen.h"
+#include "BKE_pointcache.h"
#include "ED_anim_api.h"
#include "ED_keyframes_draw.h"
@@ -113,6 +114,162 @@ static void time_draw_sfra_efra(Scene *scene, View2D *v2d)
immUnbindProgram();
}
+static void time_draw_cache(SpaceTime *stime, Object *ob, Scene *scene)
+{
+ PTCacheID *pid;
+ ListBase pidlist;
+ SpaceTimeCache *stc = stime->caches.first;
+ const float cache_draw_height = (4.0f * UI_DPI_FAC * U.pixelsize);
+ float yoffs = 0.f;
+
+ if (!(stime->cache_display & TIME_CACHE_DISPLAY) || (!ob))
+ return;
+
+ BKE_ptcache_ids_from_object(&pidlist, ob, scene, 0);
+
+ /* iterate over pointcaches on the active object,
+ * add spacetimecache and vertex array for each */
+ for (pid = pidlist.first; pid; pid = pid->next) {
+ float col[4], *fp;
+ int i, sta = pid->cache->startframe, end = pid->cache->endframe;
+ int len = (end - sta + 1) * 4;
+
+ switch (pid->type) {
+ case PTCACHE_TYPE_SOFTBODY:
+ if (!(stime->cache_display & TIME_CACHE_SOFTBODY)) continue;
+ break;
+ case PTCACHE_TYPE_PARTICLES:
+ if (!(stime->cache_display & TIME_CACHE_PARTICLES)) continue;
+ break;
+ case PTCACHE_TYPE_CLOTH:
+ if (!(stime->cache_display & TIME_CACHE_CLOTH)) continue;
+ break;
+ case PTCACHE_TYPE_SMOKE_DOMAIN:
+ case PTCACHE_TYPE_SMOKE_HIGHRES:
+ if (!(stime->cache_display & TIME_CACHE_SMOKE)) continue;
+ break;
+ case PTCACHE_TYPE_DYNAMICPAINT:
+ if (!(stime->cache_display & TIME_CACHE_DYNAMICPAINT)) continue;
+ break;
+ case PTCACHE_TYPE_RIGIDBODY:
+ if (!(stime->cache_display & TIME_CACHE_RIGIDBODY)) continue;
+ break;
+ }
+
+ if (pid->cache->cached_frames == NULL)
+ continue;
+
+ /* make sure we have stc with correct array length */
+ if (stc == NULL || MEM_allocN_len(stc->array) != len * 2 * sizeof(float)) {
+ if (stc) {
+ MEM_freeN(stc->array);
+ }
+ else {
+ stc = MEM_callocN(sizeof(SpaceTimeCache), "spacetimecache");
+ BLI_addtail(&stime->caches, stc);
+ }
+
+ stc->array = MEM_callocN(len * 2 * sizeof(float), "SpaceTimeCache array");
+ }
+
+ /* fill the vertex array with a quad for each cached frame */
+ for (i = sta, fp = stc->array; i <= end; i++) {
+ if (pid->cache->cached_frames[i - sta]) {
+ fp[0] = (float)i - 0.5f;
+ fp[1] = 0.0;
+ fp += 2;
+
+ fp[0] = (float)i - 0.5f;
+ fp[1] = 1.0;
+ fp += 2;
+
+ fp[0] = (float)i + 0.5f;
+ fp[1] = 1.0;
+ fp += 2;
+
+ fp[0] = (float)i + 0.5f;
+ fp[1] = 0.0;
+ fp += 2;
+ }
+ }
+
+ glPushMatrix();
+ glTranslatef(0.0, (float)V2D_SCROLL_HEIGHT + yoffs, 0.0);
+ glScalef(1.0, cache_draw_height, 0.0);
+
+ switch (pid->type) {
+ case PTCACHE_TYPE_SOFTBODY:
+ col[0] = 1.0; col[1] = 0.4; col[2] = 0.02;
+ col[3] = 0.1;
+ break;
+ case PTCACHE_TYPE_PARTICLES:
+ col[0] = 1.0; col[1] = 0.1; col[2] = 0.02;
+ col[3] = 0.1;
+ break;
+ case PTCACHE_TYPE_CLOTH:
+ col[0] = 0.1; col[1] = 0.1; col[2] = 0.75;
+ col[3] = 0.1;
+ break;
+ case PTCACHE_TYPE_SMOKE_DOMAIN:
+ case PTCACHE_TYPE_SMOKE_HIGHRES:
+ col[0] = 0.2; col[1] = 0.2; col[2] = 0.2;
+ col[3] = 0.1;
+ break;
+ case PTCACHE_TYPE_DYNAMICPAINT:
+ col[0] = 1.0; col[1] = 0.1; col[2] = 0.75;
+ col[3] = 0.1;
+ break;
+ case PTCACHE_TYPE_RIGIDBODY:
+ col[0] = 1.0; col[1] = 0.6; col[2] = 0.0;
+ col[3] = 0.1;
+ break;
+ default:
+ col[0] = 1.0; col[1] = 0.0; col[2] = 1.0;
+ col[3] = 0.1;
+ BLI_assert(0);
+ break;
+ }
+ glColor4fv(col);
+
+ glEnable(GL_BLEND);
+
+ glRectf((float)sta, 0.0, (float)end, 1.0);
+
+ col[3] = 0.4f;
+ if (pid->cache->flag & PTCACHE_BAKED) {
+ col[0] -= 0.4f; col[1] -= 0.4f; col[2] -= 0.4f;
+ }
+ else if (pid->cache->flag & PTCACHE_OUTDATED) {
+ col[0] += 0.4f; col[1] += 0.4f; col[2] += 0.4f;
+ }
+ glColor4fv(col);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(2, GL_FLOAT, 0, stc->array);
+ glDrawArrays(GL_QUADS, 0, (fp - stc->array) / 2);
+ glDisableClientState(GL_VERTEX_ARRAY);
+
+ glDisable(GL_BLEND);
+
+ glPopMatrix();
+
+ yoffs += cache_draw_height;
+
+ stc = stc->next;
+ }
+
+ BLI_freelistN(&pidlist);
+
+ /* free excessive caches */
+ while (stc) {
+ SpaceTimeCache *tmp = stc->next;
+ BLI_remlink(&stime->caches, stc);
+ MEM_freeN(stc->array);
+ MEM_freeN(stc);
+ stc = tmp;
+ }
+}
+
static void time_cache_free(SpaceTime *stime)
{
SpaceTimeCache *stc;
@@ -377,6 +534,7 @@ static void time_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *wmn)
case ND_BONE_ACTIVE:
case ND_POINTCACHE:
case ND_MODIFIER:
+ case ND_PARTICLE:
case ND_KEYS:
ED_area_tag_refresh(sa);
ED_area_tag_redraw(sa);
@@ -451,6 +609,7 @@ static void time_main_region_draw(const bContext *C, ARegion *ar)
/* draw entirely, view changes should be handled here */
Scene *scene = CTX_data_scene(C);
SpaceTime *stime = CTX_wm_space_time(C);
+ Object *obact = CTX_data_active_object(C);
View2D *v2d = &ar->v2d;
View2DGrid *grid;
View2DScrollers *scrollers;
@@ -488,6 +647,9 @@ static void time_main_region_draw(const bContext *C, ARegion *ar)
UI_view2d_view_orthoSpecial(ar, v2d, 1);
ED_markers_draw(C, 0);
+ /* caches */
+ time_draw_cache(stime, obact, scene);
+
/* callback */
UI_view2d_view_ortho(v2d);
ED_region_draw_cb_draw(C, ar, REGION_DRAW_POST_VIEW);
diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c
index 8b2738914d9..46d682233ed 100644
--- a/source/blender/editors/space_view3d/drawobject.c
+++ b/source/blender/editors/space_view3d/drawobject.c
@@ -37,7 +37,6 @@
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
-#include "DNA_object_force.h"
#include "DNA_rigidbody_types.h"
#include "DNA_scene_types.h"
#include "DNA_smoke_types.h"
@@ -72,6 +71,8 @@
#include "BKE_movieclip.h"
#include "BKE_object.h"
#include "BKE_paint.h"
+#include "BKE_particle.h"
+#include "BKE_pointcache.h"
#include "BKE_scene.h"
#include "BKE_subsurf.h"
#include "BKE_unit.h"
@@ -94,6 +95,7 @@
#include "GPU_matrix.h"
#include "ED_mesh.h"
+#include "ED_particle.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_types.h"
@@ -4737,7 +4739,12 @@ static void draw_mesh_fancy(Scene *scene, ARegion *ar, View3D *v3d, RegionView3D
* with the background. */
if ((dflag & DRAW_CONSTCOLOR) == 0) {
- glColor3ubv(ob_wire_col);
+ if (is_obact && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
+ ob_wire_color_blend_theme_id(ob_wire_col, TH_BACK, 0.15f);
+ }
+ else {
+ glColor3ubv(ob_wire_col);
+ }
}
/* If drawing wire and drawtype is not OB_WIRE then we are
@@ -5784,6 +5791,1113 @@ static bool drawDispList(Scene *scene, View3D *v3d, RegionView3D *rv3d, Base *ba
}
/* *********** drawing for particles ************* */
+static void draw_particle_arrays(int draw_as, int totpoint, int ob_dt, int select)
+{
+ /* draw created data arrays */
+ switch (draw_as) {
+ case PART_DRAW_AXIS:
+ case PART_DRAW_CROSS:
+ glDrawArrays(GL_LINES, 0, 6 * totpoint);
+ break;
+ case PART_DRAW_LINE:
+ glDrawArrays(GL_LINES, 0, 2 * totpoint);
+ break;
+ case PART_DRAW_BB:
+ if (ob_dt <= OB_WIRE || select)
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ else
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
+ glDrawArrays(GL_QUADS, 0, 4 * totpoint);
+ break;
+ default:
+ glDrawArrays(GL_POINTS, 0, totpoint);
+ break;
+ }
+}
+static void draw_particle(ParticleKey *state, int draw_as, short draw, float pixsize,
+ float imat[4][4], const float draw_line[2], ParticleBillboardData *bb, ParticleDrawData *pdd)
+{
+ float vec[3], vec2[3];
+ float *vd = NULL;
+ float *cd = NULL;
+ float ma_col[3] = {0.0f, 0.0f, 0.0f};
+
+ /* null only for PART_DRAW_CIRC */
+ if (pdd) {
+ vd = pdd->vd;
+ cd = pdd->cd;
+
+ if (pdd->ma_col) {
+ copy_v3_v3(ma_col, pdd->ma_col);
+ }
+ }
+
+ switch (draw_as) {
+ case PART_DRAW_DOT:
+ {
+ if (vd) {
+ copy_v3_v3(vd, state->co); pdd->vd += 3;
+ }
+ if (cd) {
+ copy_v3_v3(cd, pdd->ma_col);
+ pdd->cd += 3;
+ }
+ break;
+ }
+ case PART_DRAW_CROSS:
+ case PART_DRAW_AXIS:
+ {
+ vec[0] = 2.0f * pixsize;
+ vec[1] = vec[2] = 0.0;
+ mul_qt_v3(state->rot, vec);
+ if (draw_as == PART_DRAW_AXIS) {
+ if (cd) {
+ cd[1] = cd[2] = cd[4] = cd[5] = 0.0;
+ cd[0] = cd[3] = 1.0;
+ cd[6] = cd[8] = cd[9] = cd[11] = 0.0;
+ cd[7] = cd[10] = 1.0;
+ cd[13] = cd[12] = cd[15] = cd[16] = 0.0;
+ cd[14] = cd[17] = 1.0;
+ pdd->cd += 18;
+ }
+
+ copy_v3_v3(vec2, state->co);
+ }
+ else {
+ if (cd) {
+ cd[0] = cd[3] = cd[6] = cd[9] = cd[12] = cd[15] = ma_col[0];
+ cd[1] = cd[4] = cd[7] = cd[10] = cd[13] = cd[16] = ma_col[1];
+ cd[2] = cd[5] = cd[8] = cd[11] = cd[14] = cd[17] = ma_col[2];
+ pdd->cd += 18;
+ }
+ sub_v3_v3v3(vec2, state->co, vec);
+ }
+
+ add_v3_v3(vec, state->co);
+ copy_v3_v3(pdd->vd, vec); pdd->vd += 3;
+ copy_v3_v3(pdd->vd, vec2); pdd->vd += 3;
+
+ vec[1] = 2.0f * pixsize;
+ vec[0] = vec[2] = 0.0;
+ mul_qt_v3(state->rot, vec);
+ if (draw_as == PART_DRAW_AXIS) {
+ copy_v3_v3(vec2, state->co);
+ }
+ else {
+ sub_v3_v3v3(vec2, state->co, vec);
+ }
+
+ add_v3_v3(vec, state->co);
+ copy_v3_v3(pdd->vd, vec); pdd->vd += 3;
+ copy_v3_v3(pdd->vd, vec2); pdd->vd += 3;
+
+ vec[2] = 2.0f * pixsize;
+ vec[0] = vec[1] = 0.0f;
+ mul_qt_v3(state->rot, vec);
+ if (draw_as == PART_DRAW_AXIS) {
+ copy_v3_v3(vec2, state->co);
+ }
+ else {
+ sub_v3_v3v3(vec2, state->co, vec);
+ }
+
+ add_v3_v3(vec, state->co);
+
+ copy_v3_v3(pdd->vd, vec); pdd->vd += 3;
+ copy_v3_v3(pdd->vd, vec2); pdd->vd += 3;
+ break;
+ }
+ case PART_DRAW_LINE:
+ {
+ copy_v3_v3(vec, state->vel);
+ normalize_v3(vec);
+ if (draw & PART_DRAW_VEL_LENGTH)
+ mul_v3_fl(vec, len_v3(state->vel));
+ madd_v3_v3v3fl(pdd->vd, state->co, vec, -draw_line[0]); pdd->vd += 3;
+ madd_v3_v3v3fl(pdd->vd, state->co, vec, draw_line[1]); pdd->vd += 3;
+ if (cd) {
+ cd[0] = cd[3] = ma_col[0];
+ cd[1] = cd[4] = ma_col[1];
+ cd[2] = cd[5] = ma_col[2];
+ pdd->cd += 6;
+ }
+ break;
+ }
+ case PART_DRAW_CIRC:
+ {
+ drawcircball(GL_LINE_LOOP, state->co, pixsize, imat);
+ break;
+ }
+ case PART_DRAW_BB:
+ {
+ float xvec[3], yvec[3], zvec[3], bb_center[3];
+ if (cd) {
+ cd[0] = cd[3] = cd[6] = cd[9] = ma_col[0];
+ cd[1] = cd[4] = cd[7] = cd[10] = ma_col[1];
+ cd[2] = cd[5] = cd[8] = cd[11] = ma_col[2];
+ pdd->cd += 12;
+ }
+
+ copy_v3_v3(bb->vec, state->co);
+ copy_v3_v3(bb->vel, state->vel);
+
+ psys_make_billboard(bb, xvec, yvec, zvec, bb_center);
+
+ add_v3_v3v3(pdd->vd, bb_center, xvec);
+ add_v3_v3(pdd->vd, yvec); pdd->vd += 3;
+
+ sub_v3_v3v3(pdd->vd, bb_center, xvec);
+ add_v3_v3(pdd->vd, yvec); pdd->vd += 3;
+
+ sub_v3_v3v3(pdd->vd, bb_center, xvec);
+ sub_v3_v3v3(pdd->vd, pdd->vd, yvec); pdd->vd += 3;
+
+ add_v3_v3v3(pdd->vd, bb_center, xvec);
+ sub_v3_v3v3(pdd->vd, pdd->vd, yvec); pdd->vd += 3;
+
+ copy_v3_v3(pdd->nd, zvec); pdd->nd += 3;
+ copy_v3_v3(pdd->nd, zvec); pdd->nd += 3;
+ copy_v3_v3(pdd->nd, zvec); pdd->nd += 3;
+ copy_v3_v3(pdd->nd, zvec); pdd->nd += 3;
+ break;
+ }
+ }
+}
+static void draw_particle_data(ParticleSystem *psys, RegionView3D *rv3d,
+ ParticleKey *state, int draw_as,
+ float imat[4][4], ParticleBillboardData *bb, ParticleDrawData *pdd,
+ const float ct, const float pa_size, const float r_tilt, const float pixsize_scale)
+{
+ ParticleSettings *part = psys->part;
+ float pixsize;
+
+ if (psys->parent)
+ mul_m4_v3(psys->parent->obmat, state->co);
+
+ /* create actual particle data */
+ if (draw_as == PART_DRAW_BB) {
+ bb->offset[0] = part->bb_offset[0];
+ bb->offset[1] = part->bb_offset[1];
+ bb->size[0] = part->bb_size[0] * pa_size;
+ if (part->bb_align == PART_BB_VEL) {
+ float pa_vel = len_v3(state->vel);
+ float head = part->bb_vel_head * pa_vel;
+ float tail = part->bb_vel_tail * pa_vel;
+ bb->size[1] = part->bb_size[1] * pa_size + head + tail;
+ /* use offset to adjust the particle center. this is relative to size, so need to divide! */
+ if (bb->size[1] > 0.0f)
+ bb->offset[1] += (head - tail) / bb->size[1];
+ }
+ else {
+ bb->size[1] = part->bb_size[1] * pa_size;
+ }
+ bb->tilt = part->bb_tilt * (1.0f - part->bb_rand_tilt * r_tilt);
+ bb->time = ct;
+ }
+
+ pixsize = ED_view3d_pixel_size(rv3d, state->co) * pixsize_scale;
+
+ draw_particle(state, draw_as, part->draw, pixsize, imat, part->draw_line, bb, pdd);
+}
+/* unified drawing of all new particle systems draw types except dupli ob & group
+ * mostly tries to use vertex arrays for speed
+ *
+ * 1. check that everything is ok & updated
+ * 2. start initializing things
+ * 3. initialize according to draw type
+ * 4. allocate drawing data arrays
+ * 5. start filling the arrays
+ * 6. draw the arrays
+ * 7. clean up
+ */
+static void draw_new_particle_system(Scene *scene, View3D *v3d, RegionView3D *rv3d,
+ Base *base, ParticleSystem *psys,
+ const char ob_dt, const short dflag)
+{
+ Object *ob = base->object;
+ ParticleEditSettings *pset = PE_settings(scene);
+ ParticleSettings *part = psys->part;
+ ParticleData *pars = psys->particles;
+ ParticleData *pa;
+ ParticleKey state, *states = NULL;
+ ParticleBillboardData bb;
+ ParticleSimulationData sim = {NULL};
+ ParticleDrawData *pdd = psys->pdd;
+ Material *ma;
+ float vel[3], imat[4][4];
+ float timestep, pixsize_scale = 1.0f, pa_size, r_tilt, r_length;
+ float pa_time, pa_birthtime, pa_dietime, pa_health, intensity;
+ float cfra;
+ float ma_col[3] = {0.0f, 0.0f, 0.0f};
+ int a, totpart, totpoint = 0, totve = 0, drawn, draw_as, totchild = 0;
+ bool select = (ob->flag & SELECT) != 0, create_cdata = false, need_v = false;
+ GLint polygonmode[2];
+ char numstr[32];
+ unsigned char tcol[4] = {0, 0, 0, 255};
+
+/* 1. */
+ if (part == NULL || !psys_check_enabled(ob, psys, G.is_rendering))
+ return;
+
+ if (pars == NULL) return;
+
+ /* don't draw normal paths in edit mode */
+ if (psys_in_edit_mode(scene, psys) && (pset->flag & PE_DRAW_PART) == 0)
+ return;
+
+ if (part->draw_as == PART_DRAW_REND)
+ draw_as = part->ren_as;
+ else
+ draw_as = part->draw_as;
+
+ if (draw_as == PART_DRAW_NOT)
+ return;
+
+ /* prepare curvemapping tables */
+ if ((psys->part->child_flag & PART_CHILD_USE_CLUMP_CURVE) && psys->part->clumpcurve)
+ curvemapping_changed_all(psys->part->clumpcurve);
+ if ((psys->part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && psys->part->roughcurve)
+ curvemapping_changed_all(psys->part->roughcurve);
+
+/* 2. */
+ sim.scene = scene;
+ sim.ob = ob;
+ sim.psys = psys;
+ sim.psmd = psys_get_modifier(ob, psys);
+
+ if (part->phystype == PART_PHYS_KEYED) {
+ if (psys->flag & PSYS_KEYED) {
+ psys_count_keyed_targets(&sim);
+ if (psys->totkeyed == 0)
+ return;
+ }
+ }
+
+ if (select) {
+ select = false;
+ if (psys_get_current(ob) == psys)
+ select = true;
+ }
+
+ psys->flag |= PSYS_DRAWING;
+
+ if (part->type == PART_HAIR && !psys->childcache)
+ totchild = 0;
+ else
+ totchild = psys->totchild * part->disp / 100;
+
+ ma = give_current_material(ob, part->omat);
+
+ if (v3d->zbuf) glDepthMask(1);
+
+ if ((ma) && (part->draw_col == PART_DRAW_COL_MAT)) {
+ rgb_float_to_uchar(tcol, &(ma->r));
+ copy_v3_v3(ma_col, &ma->r);
+ }
+
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ glColor3ubv(tcol);
+ }
+
+ timestep = psys_get_timestep(&sim);
+
+ if ((base->flag & OB_FROMDUPLI) && (ob->flag & OB_FROMGROUP)) {
+ float mat[4][4];
+ mul_m4_m4m4(mat, ob->obmat, psys->imat);
+ glMultMatrixf(mat);
+ }
+
+ /* needed for text display */
+ invert_m4_m4(ob->imat, ob->obmat);
+
+ totpart = psys->totpart;
+
+ cfra = BKE_scene_frame_get(scene);
+
+ if (draw_as == PART_DRAW_PATH && psys->pathcache == NULL && psys->childcache == NULL)
+ draw_as = PART_DRAW_DOT;
+
+/* 3. */
+ glLineWidth(1.0f);
+
+ switch (draw_as) {
+ case PART_DRAW_DOT:
+ if (part->draw_size)
+ glPointSize(part->draw_size);
+ else
+ glPointSize(2.0); /* default dot size */
+ break;
+ case PART_DRAW_CIRC:
+ /* calculate view aligned matrix: */
+ copy_m4_m4(imat, rv3d->viewinv);
+ normalize_v3(imat[0]);
+ normalize_v3(imat[1]);
+ /* fall-through */
+ case PART_DRAW_CROSS:
+ case PART_DRAW_AXIS:
+ /* lets calculate the scale: */
+
+ if (part->draw_size == 0.0)
+ pixsize_scale = 2.0f;
+ else
+ pixsize_scale = part->draw_size;
+
+ if (draw_as == PART_DRAW_AXIS)
+ create_cdata = 1;
+ break;
+ case PART_DRAW_OB:
+ if (part->dup_ob == NULL)
+ draw_as = PART_DRAW_DOT;
+ else
+ draw_as = 0;
+ break;
+ case PART_DRAW_GR:
+ if (part->dup_group == NULL)
+ draw_as = PART_DRAW_DOT;
+ else
+ draw_as = 0;
+ break;
+ case PART_DRAW_BB:
+ if (v3d->camera == NULL && part->bb_ob == NULL) {
+ printf("Billboards need an active camera or a target object!\n");
+
+ draw_as = part->draw_as = PART_DRAW_DOT;
+
+ if (part->draw_size)
+ glPointSize(part->draw_size);
+ else
+ glPointSize(2.0); /* default dot size */
+ }
+ else if (part->bb_ob)
+ bb.ob = part->bb_ob;
+ else
+ bb.ob = v3d->camera;
+
+ bb.align = part->bb_align;
+ bb.anim = part->bb_anim;
+ bb.lock = part->draw & PART_DRAW_BB_LOCK;
+ break;
+ case PART_DRAW_PATH:
+ break;
+ case PART_DRAW_LINE:
+ need_v = 1;
+ break;
+ }
+ if (part->draw & PART_DRAW_SIZE && part->draw_as != PART_DRAW_CIRC) {
+ copy_m4_m4(imat, rv3d->viewinv);
+ normalize_v3(imat[0]);
+ normalize_v3(imat[1]);
+ }
+
+ if (ELEM(draw_as, PART_DRAW_DOT, PART_DRAW_CROSS, PART_DRAW_LINE) &&
+ (part->draw_col > PART_DRAW_COL_MAT))
+ {
+ create_cdata = 1;
+ }
+
+ if (!create_cdata && pdd && pdd->cdata) {
+ MEM_freeN(pdd->cdata);
+ pdd->cdata = pdd->cd = NULL;
+ }
+
+/* 4. */
+ if (draw_as && ELEM(draw_as, PART_DRAW_PATH, PART_DRAW_CIRC) == 0) {
+ int tot_vec_size = (totpart + totchild) * 3 * sizeof(float);
+ int create_ndata = 0;
+
+ if (!pdd)
+ pdd = psys->pdd = MEM_callocN(sizeof(ParticleDrawData), "ParticleDrawData");
+
+ if (part->draw_as == PART_DRAW_REND && part->trail_count > 1) {
+ tot_vec_size *= part->trail_count;
+ psys_make_temp_pointcache(ob, psys);
+ }
+
+ switch (draw_as) {
+ case PART_DRAW_AXIS:
+ case PART_DRAW_CROSS:
+ tot_vec_size *= 6;
+ if (draw_as != PART_DRAW_CROSS)
+ create_cdata = 1;
+ break;
+ case PART_DRAW_LINE:
+ tot_vec_size *= 2;
+ break;
+ case PART_DRAW_BB:
+ tot_vec_size *= 4;
+ create_ndata = 1;
+ break;
+ }
+
+ if (pdd->tot_vec_size != tot_vec_size)
+ psys_free_pdd(psys);
+
+ if (!pdd->vdata)
+ pdd->vdata = MEM_callocN(tot_vec_size, "particle_vdata");
+ if (create_cdata && !pdd->cdata)
+ pdd->cdata = MEM_callocN(tot_vec_size, "particle_cdata");
+ if (create_ndata && !pdd->ndata)
+ pdd->ndata = MEM_callocN(tot_vec_size, "particle_ndata");
+
+ if (part->draw & PART_DRAW_VEL && draw_as != PART_DRAW_LINE) {
+ if (!pdd->vedata)
+ pdd->vedata = MEM_callocN(2 * (totpart + totchild) * 3 * sizeof(float), "particle_vedata");
+
+ need_v = 1;
+ }
+ else if (pdd->vedata) {
+ /* velocity data not needed, so free it */
+ MEM_freeN(pdd->vedata);
+ pdd->vedata = NULL;
+ }
+
+ pdd->vd = pdd->vdata;
+ pdd->ved = pdd->vedata;
+ pdd->cd = pdd->cdata;
+ pdd->nd = pdd->ndata;
+ pdd->tot_vec_size = tot_vec_size;
+ }
+ else if (psys->pdd) {
+ psys_free_pdd(psys);
+ MEM_freeN(psys->pdd);
+ pdd = psys->pdd = NULL;
+ }
+
+ if (pdd) {
+ pdd->ma_col = ma_col;
+ }
+
+ psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
+
+ /* circles don't use drawdata, so have to add a special case here */
+ if ((pdd || draw_as == PART_DRAW_CIRC) && draw_as != PART_DRAW_PATH) {
+ /* 5. */
+ if (pdd && (pdd->flag & PARTICLE_DRAW_DATA_UPDATED) &&
+ (pdd->vedata || part->draw & (PART_DRAW_SIZE | PART_DRAW_NUM | PART_DRAW_HEALTH)) == 0)
+ {
+ totpoint = pdd->totpoint; /* draw data is up to date */
+ }
+ else {
+ for (a = 0, pa = pars; a < totpart + totchild; a++, pa++) {
+ /* setup per particle individual stuff */
+ if (a < totpart) {
+ if (totchild && (part->draw & PART_DRAW_PARENT) == 0) continue;
+ if (pa->flag & PARS_NO_DISP || pa->flag & PARS_UNEXIST) continue;
+
+ pa_time = (cfra - pa->time) / pa->lifetime;
+ pa_birthtime = pa->time;
+ pa_dietime = pa->dietime;
+ pa_size = pa->size;
+ if (part->phystype == PART_PHYS_BOIDS)
+ pa_health = pa->boid->data.health;
+ else
+ pa_health = -1.0;
+
+ r_tilt = 2.0f * (psys_frand(psys, a + 21) - 0.5f);
+ r_length = psys_frand(psys, a + 22);
+
+ if (part->draw_col > PART_DRAW_COL_MAT) {
+ switch (part->draw_col) {
+ case PART_DRAW_COL_VEL:
+ intensity = len_v3(pa->state.vel) / part->color_vec_max;
+ break;
+ case PART_DRAW_COL_ACC:
+ intensity = len_v3v3(pa->state.vel, pa->prev_state.vel) / ((pa->state.time - pa->prev_state.time) * part->color_vec_max);
+ break;
+ default:
+ intensity = 1.0f; /* should never happen */
+ BLI_assert(0);
+ break;
+ }
+ CLAMP(intensity, 0.0f, 1.0f);
+ weight_to_rgb(ma_col, intensity);
+ }
+ }
+ else {
+ ChildParticle *cpa = &psys->child[a - totpart];
+
+ pa_time = psys_get_child_time(psys, cpa, cfra, &pa_birthtime, &pa_dietime);
+ pa_size = psys_get_child_size(psys, cpa, cfra, NULL);
+
+ pa_health = -1.0;
+
+ r_tilt = 2.0f * (psys_frand(psys, a + 21) - 0.5f);
+ r_length = psys_frand(psys, a + 22);
+ }
+
+ drawn = 0;
+ if (part->draw_as == PART_DRAW_REND && part->trail_count > 1) {
+ float length = part->path_end * (1.0f - part->randlength * r_length);
+ int trail_count = part->trail_count * (1.0f - part->randlength * r_length);
+ float ct = ((part->draw & PART_ABS_PATH_TIME) ? cfra : pa_time) - length;
+ float dt = length / (trail_count ? (float)trail_count : 1.0f);
+ int i = 0;
+
+ ct += dt;
+ for (i = 0; i < trail_count; i++, ct += dt) {
+
+ if (part->draw & PART_ABS_PATH_TIME) {
+ if (ct < pa_birthtime || ct > pa_dietime)
+ continue;
+ }
+ else if (ct < 0.0f || ct > 1.0f)
+ continue;
+
+ state.time = (part->draw & PART_ABS_PATH_TIME) ? -ct : -(pa_birthtime + ct * (pa_dietime - pa_birthtime));
+ psys_get_particle_on_path(&sim, a, &state, need_v);
+
+ draw_particle_data(psys, rv3d,
+ &state, draw_as, imat, &bb, psys->pdd,
+ ct, pa_size, r_tilt, pixsize_scale);
+
+ totpoint++;
+ drawn = 1;
+ }
+ }
+ else {
+ state.time = cfra;
+ if (psys_get_particle_state(&sim, a, &state, 0)) {
+
+ draw_particle_data(psys, rv3d,
+ &state, draw_as, imat, &bb, psys->pdd,
+ pa_time, pa_size, r_tilt, pixsize_scale);
+
+ totpoint++;
+ drawn = 1;
+ }
+ }
+
+ if (drawn) {
+ /* additional things to draw for each particle
+ * (velocity, size and number) */
+ if ((part->draw & PART_DRAW_VEL) && pdd && pdd->vedata) {
+ copy_v3_v3(pdd->ved, state.co);
+ pdd->ved += 3;
+ mul_v3_v3fl(vel, state.vel, timestep);
+ add_v3_v3v3(pdd->ved, state.co, vel);
+ pdd->ved += 3;
+
+ totve++;
+ }
+
+ if (part->draw & PART_DRAW_SIZE) {
+ setlinestyle(3);
+ drawcircball(GL_LINE_LOOP, state.co, pa_size, imat);
+ setlinestyle(0);
+ }
+
+
+ if ((part->draw & PART_DRAW_NUM || part->draw & PART_DRAW_HEALTH) &&
+ (v3d->flag2 & V3D_RENDER_OVERRIDE) == 0)
+ {
+ size_t numstr_len;
+ float vec_txt[3];
+ char *val_pos = numstr;
+ numstr[0] = '\0';
+
+ if (part->draw & PART_DRAW_NUM) {
+ if (a < totpart && (part->draw & PART_DRAW_HEALTH) && (part->phystype == PART_PHYS_BOIDS)) {
+ numstr_len = BLI_snprintf_rlen(val_pos, sizeof(numstr), "%d:%.2f", a, pa_health);
+ }
+ else {
+ numstr_len = BLI_snprintf_rlen(val_pos, sizeof(numstr), "%d", a);
+ }
+ }
+ else {
+ if (a < totpart && (part->draw & PART_DRAW_HEALTH) && (part->phystype == PART_PHYS_BOIDS)) {
+ numstr_len = BLI_snprintf_rlen(val_pos, sizeof(numstr), "%.2f", pa_health);
+ }
+ }
+
+ if (numstr[0]) {
+ /* in path drawing state.co is the end point
+ * use worldspace because object matrix is already applied */
+ mul_v3_m4v3(vec_txt, ob->imat, state.co);
+ view3d_cached_text_draw_add(vec_txt, numstr, numstr_len,
+ 10, V3D_CACHE_TEXT_WORLDSPACE | V3D_CACHE_TEXT_ASCII, tcol);
+ }
+ }
+ }
+ }
+ }
+ }
+/* 6. */
+
+ glGetIntegerv(GL_POLYGON_MODE, polygonmode);
+ glEnableClientState(GL_VERTEX_ARRAY);
+
+ if (draw_as == PART_DRAW_PATH) {
+ ParticleCacheKey **cache, *path;
+ float *cdata2 = NULL;
+
+ /* setup gl flags */
+ if (1) { //ob_dt > OB_WIRE) {
+ glEnableClientState(GL_NORMAL_ARRAY);
+
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ if (part->draw_col == PART_DRAW_COL_MAT)
+ glEnableClientState(GL_COLOR_ARRAY);
+ }
+
+ // XXX test
+ GPU_basic_shader_colors(NULL, NULL, 0.0f, 1.0f);
+ GPU_basic_shader_bind(GPU_SHADER_LIGHTING | GPU_SHADER_USE_COLOR);
+ }
+
+ if (totchild && (part->draw & PART_DRAW_PARENT) == 0)
+ totpart = 0;
+ else if (psys->pathcache == NULL)
+ totpart = 0;
+
+ /* draw actual/parent particles */
+ cache = psys->pathcache;
+ for (a = 0, pa = psys->particles; a < totpart; a++, pa++) {
+ path = cache[a];
+ if (path->segments > 0) {
+ glVertexPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->co);
+
+ if (1) { //ob_dt > OB_WIRE) {
+ glNormalPointer(GL_FLOAT, sizeof(ParticleCacheKey), path->vel);
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ if (part->draw_col == PART_DRAW_COL_MAT) {
+ glColorPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->col);
+ }
+ }
+ }
+
+ glDrawArrays(GL_LINE_STRIP, 0, path->segments + 1);
+ }
+ }
+
+ if (part->type == PART_HAIR) {
+ if (part->draw & PART_DRAW_GUIDE_HAIRS) {
+ DerivedMesh *hair_dm = psys->hair_out_dm;
+
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ for (a = 0, pa = psys->particles; a < totpart; a++, pa++) {
+ if (pa->totkey > 1) {
+ HairKey *hkey = pa->hair;
+
+ glVertexPointer(3, GL_FLOAT, sizeof(HairKey), hkey->world_co);
+
+#if 0 /* XXX use proper theme color here */
+ UI_ThemeColor(TH_NORMAL);
+#else
+ glColor3f(0.58f, 0.67f, 1.0f);
+#endif
+
+ glDrawArrays(GL_LINE_STRIP, 0, pa->totkey);
+ }
+ }
+
+ if (hair_dm) {
+ MVert *mvert = hair_dm->getVertArray(hair_dm);
+ int i;
+
+ glColor3f(0.9f, 0.4f, 0.4f);
+
+ glBegin(GL_LINES);
+ for (a = 0, pa = psys->particles; a < totpart; a++, pa++) {
+ for (i = 1; i < pa->totkey; ++i) {
+ float v1[3], v2[3];
+
+ copy_v3_v3(v1, mvert[pa->hair_index + i - 1].co);
+ copy_v3_v3(v2, mvert[pa->hair_index + i].co);
+
+ mul_m4_v3(ob->obmat, v1);
+ mul_m4_v3(ob->obmat, v2);
+
+ glVertex3fv(v1);
+ glVertex3fv(v2);
+ }
+ }
+ glEnd();
+ }
+
+ glEnableClientState(GL_NORMAL_ARRAY);
+ if ((dflag & DRAW_CONSTCOLOR) == 0)
+ if (part->draw_col == PART_DRAW_COL_MAT)
+ glEnableClientState(GL_COLOR_ARRAY);
+ }
+
+ if (part->draw & PART_DRAW_HAIR_GRID) {
+ ClothModifierData *clmd = psys->clmd;
+ if (clmd) {
+ float *gmin = clmd->hair_grid_min;
+ float *gmax = clmd->hair_grid_max;
+ int *res = clmd->hair_grid_res;
+ int i;
+
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ if (select)
+ UI_ThemeColor(TH_ACTIVE);
+ else
+ UI_ThemeColor(TH_WIRE);
+ glBegin(GL_LINES);
+ glVertex3f(gmin[0], gmin[1], gmin[2]); glVertex3f(gmax[0], gmin[1], gmin[2]);
+ glVertex3f(gmax[0], gmin[1], gmin[2]); glVertex3f(gmax[0], gmax[1], gmin[2]);
+ glVertex3f(gmax[0], gmax[1], gmin[2]); glVertex3f(gmin[0], gmax[1], gmin[2]);
+ glVertex3f(gmin[0], gmax[1], gmin[2]); glVertex3f(gmin[0], gmin[1], gmin[2]);
+
+ glVertex3f(gmin[0], gmin[1], gmax[2]); glVertex3f(gmax[0], gmin[1], gmax[2]);
+ glVertex3f(gmax[0], gmin[1], gmax[2]); glVertex3f(gmax[0], gmax[1], gmax[2]);
+ glVertex3f(gmax[0], gmax[1], gmax[2]); glVertex3f(gmin[0], gmax[1], gmax[2]);
+ glVertex3f(gmin[0], gmax[1], gmax[2]); glVertex3f(gmin[0], gmin[1], gmax[2]);
+
+ glVertex3f(gmin[0], gmin[1], gmin[2]); glVertex3f(gmin[0], gmin[1], gmax[2]);
+ glVertex3f(gmax[0], gmin[1], gmin[2]); glVertex3f(gmax[0], gmin[1], gmax[2]);
+ glVertex3f(gmin[0], gmax[1], gmin[2]); glVertex3f(gmin[0], gmax[1], gmax[2]);
+ glVertex3f(gmax[0], gmax[1], gmin[2]); glVertex3f(gmax[0], gmax[1], gmax[2]);
+ glEnd();
+
+ if (select)
+ UI_ThemeColorShadeAlpha(TH_ACTIVE, 0, -100);
+ else
+ UI_ThemeColorShadeAlpha(TH_WIRE, 0, -100);
+ glEnable(GL_BLEND);
+ glBegin(GL_LINES);
+ for (i = 1; i < res[0] - 1; ++i) {
+ float f = interpf(gmax[0], gmin[0], (float)i / (float)(res[0] - 1));
+ glVertex3f(f, gmin[1], gmin[2]); glVertex3f(f, gmax[1], gmin[2]);
+ glVertex3f(f, gmax[1], gmin[2]); glVertex3f(f, gmax[1], gmax[2]);
+ glVertex3f(f, gmax[1], gmax[2]); glVertex3f(f, gmin[1], gmax[2]);
+ glVertex3f(f, gmin[1], gmax[2]); glVertex3f(f, gmin[1], gmin[2]);
+ }
+ for (i = 1; i < res[1] - 1; ++i) {
+ float f = interpf(gmax[1], gmin[1], (float)i / (float)(res[1] - 1));
+ glVertex3f(gmin[0], f, gmin[2]); glVertex3f(gmax[0], f, gmin[2]);
+ glVertex3f(gmax[0], f, gmin[2]); glVertex3f(gmax[0], f, gmax[2]);
+ glVertex3f(gmax[0], f, gmax[2]); glVertex3f(gmin[0], f, gmax[2]);
+ glVertex3f(gmin[0], f, gmax[2]); glVertex3f(gmin[0], f, gmin[2]);
+ }
+ for (i = 1; i < res[2] - 1; ++i) {
+ float f = interpf(gmax[2], gmin[2], (float)i / (float)(res[2] - 1));
+ glVertex3f(gmin[0], gmin[1], f); glVertex3f(gmax[0], gmin[1], f);
+ glVertex3f(gmax[0], gmin[1], f); glVertex3f(gmax[0], gmax[1], f);
+ glVertex3f(gmax[0], gmax[1], f); glVertex3f(gmin[0], gmax[1], f);
+ glVertex3f(gmin[0], gmax[1], f); glVertex3f(gmin[0], gmin[1], f);
+ }
+ glEnd();
+ glDisable(GL_BLEND);
+
+ glEnableClientState(GL_NORMAL_ARRAY);
+ if ((dflag & DRAW_CONSTCOLOR) == 0)
+ if (part->draw_col == PART_DRAW_COL_MAT)
+ glEnableClientState(GL_COLOR_ARRAY);
+ }
+ }
+ }
+
+ /* draw child particles */
+ cache = psys->childcache;
+ for (a = 0; a < totchild; a++) {
+ path = cache[a];
+ glVertexPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->co);
+
+ if (1) { //ob_dt > OB_WIRE) {
+ glNormalPointer(GL_FLOAT, sizeof(ParticleCacheKey), path->vel);
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ if (part->draw_col == PART_DRAW_COL_MAT) {
+ glColorPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->col);
+ }
+ }
+ }
+
+ glDrawArrays(GL_LINE_STRIP, 0, path->segments + 1);
+ }
+
+ /* restore & clean up */
+ if (1) { //ob_dt > OB_WIRE) {
+ if (part->draw_col == PART_DRAW_COL_MAT)
+ glDisableClientState(GL_COLOR_ARRAY);
+ GPU_basic_shader_bind(GPU_SHADER_USE_COLOR);
+ }
+
+ if (cdata2) {
+ MEM_freeN(cdata2);
+ cdata2 = NULL;
+ }
+
+ if ((part->draw & PART_DRAW_NUM) && (v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) {
+ cache = psys->pathcache;
+
+ for (a = 0, pa = psys->particles; a < totpart; a++, pa++) {
+ float vec_txt[3];
+ size_t numstr_len = BLI_snprintf_rlen(numstr, sizeof(numstr), "%i", a);
+ /* use worldspace because object matrix is already applied */
+ mul_v3_m4v3(vec_txt, ob->imat, cache[a]->co);
+ view3d_cached_text_draw_add(vec_txt, numstr, numstr_len,
+ 10, V3D_CACHE_TEXT_WORLDSPACE | V3D_CACHE_TEXT_ASCII, tcol);
+ }
+ }
+ }
+ else if (pdd && ELEM(draw_as, 0, PART_DRAW_CIRC) == 0) {
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ /* enable point data array */
+ if (pdd->vdata) {
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 0, pdd->vdata);
+ }
+ else
+ glDisableClientState(GL_VERTEX_ARRAY);
+
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ if (select) {
+ UI_ThemeColor(TH_ACTIVE);
+
+ if (part->draw_size)
+ glPointSize(part->draw_size + 2);
+ else
+ glPointSize(4.0);
+
+ glLineWidth(3.0);
+
+ draw_particle_arrays(draw_as, totpoint, ob_dt, 1);
+ }
+
+ /* restore from select */
+ glColor3fv(ma_col);
+ }
+
+ glPointSize(part->draw_size ? part->draw_size : 2.0);
+ glLineWidth(1.0);
+
+ /* enable other data arrays */
+
+ /* billboards are drawn this way */
+ if (pdd->ndata && ob_dt > OB_WIRE) {
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glNormalPointer(GL_FLOAT, 0, pdd->ndata);
+ GPU_basic_shader_colors(NULL, NULL, 0.0f, 1.0f);
+ GPU_basic_shader_bind(GPU_SHADER_LIGHTING | GPU_SHADER_USE_COLOR);
+ }
+
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ if (pdd->cdata) {
+ glEnableClientState(GL_COLOR_ARRAY);
+ glColorPointer(3, GL_FLOAT, 0, pdd->cdata);
+ }
+ }
+
+ draw_particle_arrays(draw_as, totpoint, ob_dt, 0);
+
+ pdd->flag |= PARTICLE_DRAW_DATA_UPDATED;
+ pdd->totpoint = totpoint;
+ }
+
+ if (pdd && pdd->vedata) {
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ glDisableClientState(GL_COLOR_ARRAY);
+ cpack(0xC0C0C0);
+ }
+
+ glVertexPointer(3, GL_FLOAT, 0, pdd->vedata);
+
+ glDrawArrays(GL_LINES, 0, 2 * totve);
+ }
+
+ glPolygonMode(GL_FRONT, polygonmode[0]);
+ glPolygonMode(GL_BACK, polygonmode[1]);
+
+/* 7. */
+
+ GPU_basic_shader_bind(GPU_SHADER_USE_COLOR);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_NORMAL_ARRAY);
+
+ if (states)
+ MEM_freeN(states);
+
+ psys->flag &= ~PSYS_DRAWING;
+
+ /* draw data can't be saved for billboards as they must update to target changes */
+ if (draw_as == PART_DRAW_BB) {
+ psys_free_pdd(psys);
+ pdd->flag &= ~PARTICLE_DRAW_DATA_UPDATED;
+ }
+
+ if (psys->lattice_deform_data) {
+ end_latt_deform(psys->lattice_deform_data);
+ psys->lattice_deform_data = NULL;
+ }
+
+ if (pdd) {
+ /* drop references to stack memory */
+ pdd->ma_col = NULL;
+ }
+
+ if ((base->flag & OB_FROMDUPLI) && (ob->flag & OB_FROMGROUP)) {
+ glLoadMatrixf(rv3d->viewmat);
+ }
+}
+
+static void draw_update_ptcache_edit(Scene *scene, Object *ob, PTCacheEdit *edit)
+{
+ if (edit->psys && edit->psys->flag & PSYS_HAIR_UPDATED)
+ PE_update_object(scene, ob, 0);
+
+ /* create path and child path cache if it doesn't exist already */
+ if (edit->pathcache == NULL)
+ psys_cache_edit_paths(scene, ob, edit, CFRA, G.is_rendering);
+}
+
+static void draw_ptcache_edit(Scene *scene, View3D *v3d, PTCacheEdit *edit)
+{
+ ParticleCacheKey **cache, *path, *pkey;
+ PTCacheEditPoint *point;
+ PTCacheEditKey *key;
+ ParticleEditSettings *pset = PE_settings(scene);
+ int i, k, totpoint = edit->totpoint, timed = (pset->flag & PE_FADE_TIME) ? pset->fade_frames : 0;
+ int totkeys = 1;
+ float sel_col[3];
+ float nosel_col[3];
+ float *pathcol = NULL, *pcol;
+
+ if (edit->pathcache == NULL)
+ return;
+
+ PE_hide_keys_time(scene, edit, CFRA);
+
+ /* opengl setup */
+ if ((v3d->flag & V3D_ZBUF_SELECT) == 0)
+ glDisable(GL_DEPTH_TEST);
+
+ /* get selection theme colors */
+ UI_GetThemeColor3fv(TH_VERTEX_SELECT, sel_col);
+ UI_GetThemeColor3fv(TH_VERTEX, nosel_col);
+
+ /* draw paths */
+ totkeys = (*edit->pathcache)->segments + 1;
+
+ glEnable(GL_BLEND);
+ pathcol = MEM_callocN(totkeys * 4 * sizeof(float), "particle path color data");
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ if (pset->brushtype == PE_BRUSH_WEIGHT)
+ glLineWidth(2.0f);
+
+ cache = edit->pathcache;
+ for (i = 0, point = edit->points; i < totpoint; i++, point++) {
+ path = cache[i];
+ glVertexPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->co);
+
+ if (point->flag & PEP_HIDE) {
+ for (k = 0, pcol = pathcol; k < totkeys; k++, pcol += 4) {
+ copy_v3_v3(pcol, path->col);
+ pcol[3] = 0.25f;
+ }
+
+ glColorPointer(4, GL_FLOAT, 4 * sizeof(float), pathcol);
+ }
+ else if (timed) {
+ for (k = 0, pcol = pathcol, pkey = path; k < totkeys; k++, pkey++, pcol += 4) {
+ copy_v3_v3(pcol, pkey->col);
+ pcol[3] = 1.0f - fabsf((float)(CFRA) -pkey->time) / (float)pset->fade_frames;
+ }
+
+ glColorPointer(4, GL_FLOAT, 4 * sizeof(float), pathcol);
+ }
+ else
+ glColorPointer(3, GL_FLOAT, sizeof(ParticleCacheKey), path->col);
+
+ glDrawArrays(GL_LINE_STRIP, 0, path->segments + 1);
+ }
+
+ if (pathcol) { MEM_freeN(pathcol); pathcol = pcol = NULL; }
+
+
+ /* draw edit vertices */
+ if (pset->selectmode != SCE_SELECT_PATH) {
+ glPointSize(UI_GetThemeValuef(TH_VERTEX_SIZE));
+
+ if (pset->selectmode == SCE_SELECT_POINT) {
+ float *pd = NULL, *pdata = NULL;
+ float *cd = NULL, *cdata = NULL;
+ int totkeys_visible = 0;
+
+ for (i = 0, point = edit->points; i < totpoint; i++, point++)
+ if (!(point->flag & PEP_HIDE))
+ totkeys_visible += point->totkey;
+
+ if (totkeys_visible) {
+ if (edit->points && !(edit->points->keys->flag & PEK_USE_WCO))
+ pd = pdata = MEM_callocN(totkeys_visible * 3 * sizeof(float), "particle edit point data");
+ cd = cdata = MEM_callocN(totkeys_visible * (timed ? 4 : 3) * sizeof(float), "particle edit color data");
+ }
+
+ for (i = 0, point = edit->points; i < totpoint; i++, point++) {
+ if (point->flag & PEP_HIDE)
+ continue;
+
+ for (k = 0, key = point->keys; k < point->totkey; k++, key++) {
+ if (pd) {
+ copy_v3_v3(pd, key->co);
+ pd += 3;
+ }
+
+ if (key->flag & PEK_SELECT) {
+ copy_v3_v3(cd, sel_col);
+ }
+ else {
+ copy_v3_v3(cd, nosel_col);
+ }
+
+ if (timed)
+ *(cd + 3) = 1.0f - fabsf((float)CFRA - *key->time) / (float)pset->fade_frames;
+
+ cd += (timed ? 4 : 3);
+ }
+ }
+ cd = cdata;
+ pd = pdata;
+ for (i = 0, point = edit->points; i < totpoint; i++, point++) {
+ if (point->flag & PEP_HIDE || point->totkey == 0)
+ continue;
+
+ if (point->keys->flag & PEK_USE_WCO)
+ glVertexPointer(3, GL_FLOAT, sizeof(PTCacheEditKey), point->keys->world_co);
+ else
+ glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), pd);
+
+ glColorPointer((timed ? 4 : 3), GL_FLOAT, (timed ? 4 : 3) * sizeof(float), cd);
+
+ glDrawArrays(GL_POINTS, 0, point->totkey);
+
+ pd += pd ? 3 * point->totkey : 0;
+ cd += (timed ? 4 : 3) * point->totkey;
+ }
+ if (pdata) { MEM_freeN(pdata); pd = pdata = NULL; }
+ if (cdata) { MEM_freeN(cdata); cd = cdata = NULL; }
+ }
+ else if (pset->selectmode == SCE_SELECT_END) {
+ glBegin(GL_POINTS);
+ for (i = 0, point = edit->points; i < totpoint; i++, point++) {
+ if ((point->flag & PEP_HIDE) == 0 && point->totkey) {
+ key = point->keys + point->totkey - 1;
+ glColor3fv((key->flag & PEK_SELECT) ? sel_col : nosel_col);
+ /* has to be like this.. otherwise selection won't work, have try glArrayElement later..*/
+ glVertex3fv((key->flag & PEK_USE_WCO) ? key->world_co : key->co);
+ }
+ }
+ glEnd();
+ }
+ }
+
+ glDisable(GL_BLEND);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ if (v3d->zbuf) glEnable(GL_DEPTH_TEST);
+}
static void ob_draw_RE_motion(float com[3], float rotscale[3][3], float itw, float ith, float drw_size)
{
@@ -7459,12 +8573,15 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
Object *ob = base->object;
Curve *cu;
RegionView3D *rv3d = ar->regiondata;
+ unsigned int col = 0;
unsigned char _ob_wire_col[4]; /* dont initialize this */
const unsigned char *ob_wire_col = NULL; /* dont initialize this, use NULL crashes as a way to find invalid use */
bool zbufoff = false, is_paint = false, empty_object = false;
const bool is_obact = (ob == OBACT);
const bool render_override = (v3d->flag2 & V3D_RENDER_OVERRIDE) != 0;
const bool is_picking = (G.f & G_PICKSEL) != 0;
+ const bool has_particles = (ob->particlesystem.first != NULL);
+ bool skip_object = false; /* Draw particles but not their emitter object. */
SmokeModifierData *smd = NULL;
if (ob != scene->obedit) {
@@ -7475,11 +8592,31 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
if (ob->restrictflag & OB_RESTRICT_RENDER)
return;
- if (ob->transflag & (OB_DUPLI & ~OB_DUPLIFRAMES))
+ if (!has_particles && (ob->transflag & (OB_DUPLI & ~OB_DUPLIFRAMES)))
return;
}
}
+ if (has_particles) {
+ /* XXX particles are not safe for simultaneous threaded render */
+ if (G.is_rendering) {
+ return;
+ }
+
+ if (ob->mode == OB_MODE_OBJECT) {
+ ParticleSystem *psys;
+
+ skip_object = render_override;
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ /* Once we have found a psys which renders its emitter object, we are done. */
+ if (psys->part->draw & PART_DRAW_EMITTER) {
+ skip_object = false;
+ break;
+ }
+ }
+ }
+ }
+
if (((base->flag & OB_FROMDUPLI) == 0) &&
(md = modifiers_findByType(ob, eModifierType_Smoke)) &&
(modifier_isEnabled(scene, md, eModifierMode_Realtime)))
@@ -7505,8 +8642,8 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
/* xray delay? */
if ((dflag & DRAW_PICKING) == 0 && (base->flag & OB_FROMDUPLI) == 0 && (v3d->flag2 & V3D_RENDER_SHADOW) == 0) {
- /* sync with master */
- {
+ /* don't do xray in particle mode, need the z-buffer */
+ if (!(ob->mode & OB_MODE_PARTICLE_EDIT)) {
/* xray and transp are set when it is drawing the 2nd/3rd pass */
if (!v3d->xray && !v3d->transp && (ob->dtx & OB_DRAWXRAY) && !(ob->dtx & OB_DRAWTRANSP)) {
ED_view3d_after_add(&v3d->afterdraw_xray, base, dflag);
@@ -7608,8 +8745,7 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
}
}
- /* sync with master */
- {
+ if (!skip_object) {
/* draw outline for selected objects, mesh does itself */
if ((v3d->flag & V3D_SELECT_OUTLINE) && !render_override && ob->type != OB_MESH) {
if (dt > OB_WIRE && (ob->mode & OB_MODE_EDIT) == 0 && (dflag & DRAW_SCENESET) == 0) {
@@ -7758,6 +8894,7 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
if (!render_override) {
drawaxes(rv3d->viewmatob, 1.0, OB_ARROWS, ob_wire_col);
}
+ break;
}
/* TODO Viewport: some elements are being drawn for object selection only */
@@ -7787,10 +8924,63 @@ afterdraw:
}
}
+ /* code for new particle system */
+ if ((ob->particlesystem.first) &&
+ (ob != scene->obedit))
+ {
+ ParticleSystem *psys;
+
+ if ((dflag & DRAW_CONSTCOLOR) == 0) {
+ /* for visibility, also while wpaint */
+ if (col || (ob->flag & SELECT)) {
+ cpack(0xFFFFFF);
+ }
+ }
+ //glDepthMask(GL_FALSE);
+
+ glLoadMatrixf(rv3d->viewmat);
+
+ view3d_cached_text_draw_begin();
+
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ /* run this so that possible child particles get cached */
+ if (ob->mode & OB_MODE_PARTICLE_EDIT && is_obact) {
+ PTCacheEdit *edit = PE_create_current(scene, ob);
+ if (edit && edit->psys == psys)
+ draw_update_ptcache_edit(scene, ob, edit);
+ }
+
+ draw_new_particle_system(scene, v3d, rv3d, base, psys, dt, dflag);
+ }
+ invert_m4_m4(ob->imat, ob->obmat);
+ view3d_cached_text_draw_end(v3d, ar, 0, NULL);
+
+ glMultMatrixf(ob->obmat);
+
+ //glDepthMask(GL_TRUE);
+ if (col) cpack(col);
+ }
+
+ /* draw edit particles last so that they can draw over child particles */
+ if ((dflag & DRAW_PICKING) == 0 &&
+ (!scene->obedit))
+ {
+
+ if (ob->mode & OB_MODE_PARTICLE_EDIT && is_obact) {
+ PTCacheEdit *edit = PE_create_current(scene, ob);
+ if (edit) {
+ glLoadMatrixf(rv3d->viewmat);
+ draw_update_ptcache_edit(scene, ob, edit);
+ draw_ptcache_edit(scene, v3d, edit);
+ glMultMatrixf(ob->obmat);
+ }
+ }
+ }
+
/* draw code for smoke, only draw domains */
if (smd && smd->domain) {
SmokeDomainSettings *sds = smd->domain;
- const bool show_smoke = true; /* XXX was checking cached frame range before */
+ const bool show_smoke = (CFRA >= sds->point_cache[0]->startframe);
float viewnormal[3];
glLoadMatrixf(rv3d->viewmat);
diff --git a/source/blender/editors/space_view3d/drawvolume.c b/source/blender/editors/space_view3d/drawvolume.c
index 584f442bd44..27ecbf83db5 100644
--- a/source/blender/editors/space_view3d/drawvolume.c
+++ b/source/blender/editors/space_view3d/drawvolume.c
@@ -32,7 +32,6 @@
#include "MEM_guardedalloc.h"
-#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_smoke_types.h"
@@ -43,6 +42,7 @@
#include "BKE_DerivedMesh.h"
#include "BKE_texture.h"
+#include "BKE_particle.h"
#include "smoke_API.h"
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index 4526d120923..90fa54c7a16 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -878,6 +878,7 @@ static void view3d_main_region_listener(bScreen *sc, ScrArea *sa, ARegion *ar, w
case ND_MODIFIER:
case ND_CONSTRAINT:
case ND_KEYS:
+ case ND_PARTICLE:
case ND_LOD:
ED_region_tag_redraw(ar);
break;
diff --git a/source/blender/editors/space_view3d/view3d_draw_legacy.c b/source/blender/editors/space_view3d/view3d_draw_legacy.c
index 7193ce1b3cd..639be36d739 100644
--- a/source/blender/editors/space_view3d/view3d_draw_legacy.c
+++ b/source/blender/editors/space_view3d/view3d_draw_legacy.c
@@ -429,6 +429,11 @@ static void backdrawview3d(Scene *scene, wmWindow *win, ARegion *ar, View3D *v3d
{
/* do nothing */
}
+ else if ((base && (base->object->mode & OB_MODE_PARTICLE_EDIT)) &&
+ V3D_IS_ZBUF(v3d))
+ {
+ /* do nothing */
+ }
else if (scene->obedit &&
V3D_IS_ZBUF(v3d))
{
diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c
index 6f45013ce42..620bbf03f32 100644
--- a/source/blender/editors/space_view3d/view3d_edit.c
+++ b/source/blender/editors/space_view3d/view3d_edit.c
@@ -71,6 +71,7 @@
#include "RNA_define.h"
#include "ED_armature.h"
+#include "ED_particle.h"
#include "ED_keyframing.h"
#include "ED_screen.h"
#include "ED_transform.h"
@@ -3078,6 +3079,9 @@ static int viewselected_exec(bContext *C, wmOperator *op)
else if (BKE_paint_select_face_test(ob)) {
ok = paintface_minmax(ob, min, max);
}
+ else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
+ ok = PE_minmax(scene, min, max);
+ }
else if (ob &&
(ob->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)))
{
diff --git a/source/blender/editors/space_view3d/view3d_header.c b/source/blender/editors/space_view3d/view3d_header.c
index 32e265cb981..ab0cae6822b 100644
--- a/source/blender/editors/space_view3d/view3d_header.c
+++ b/source/blender/editors/space_view3d/view3d_header.c
@@ -347,7 +347,7 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
}
/* Manipulators aren't used in paint modes */
- if (ob->mode != OB_MODE_SCULPT) {
+ if (!ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_PARTICLE_EDIT)) {
/* masks aren't used for sculpt and particle painting */
PointerRNA meshptr;
diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h
index 73af8377d41..504a8383a41 100644
--- a/source/blender/editors/space_view3d/view3d_intern.h
+++ b/source/blender/editors/space_view3d/view3d_intern.h
@@ -280,7 +280,7 @@ void ED_view3d_cameracontrol_update(
void ED_view3d_cameracontrol_release(
struct View3DCameraControl *vctrl,
const bool restore);
-struct Object *ED_view3d_cameracontrol_object_get(
+Object *ED_view3d_cameracontrol_object_get(
struct View3DCameraControl *vctrl);
/* view3d_toolbar.c */
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index f77e836461c..3239d07553f 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -85,6 +85,7 @@
#include "ED_armature.h"
#include "ED_curve.h"
+#include "ED_particle.h"
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
@@ -835,6 +836,8 @@ static void view3d_lasso_select(bContext *C, ViewContext *vc,
else if (ob && (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) {
/* pass */
}
+ else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT))
+ PE_lasso_select(C, mcords, moves, extend, select);
else {
do_lasso_select_objects(vc, mcords, moves, extend, select);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene);
@@ -2170,6 +2173,9 @@ static int view3d_borderselect_exec(bContext *C, wmOperator *op)
else if (vc.obact && BKE_paint_select_vert_test(vc.obact)) {
ret = do_paintvert_box_select(&vc, &rect, select, extend);
}
+ else if (vc.obact && vc.obact->mode & OB_MODE_PARTICLE_EDIT) {
+ ret = PE_border_select(C, &rect, select, extend);
+ }
else { /* object mode with none active */
ret = do_object_pose_box_select(C, &vc, &rect, select, extend);
}
@@ -2300,6 +2306,8 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
retval = ED_curve_editfont_select_pick(C, location, extend, deselect, toggle);
}
+ else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT)
+ return PE_mouse_particles(C, location, extend, deselect, toggle);
else if (obact && BKE_paint_select_face_test(obact))
retval = paintface_mouse_select(C, obact, location, extend, deselect, toggle);
else if (BKE_paint_select_vert_test(obact))
@@ -2815,7 +2823,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
RNA_int_get(op->ptr, "y")};
if (CTX_data_edit_object(C) || BKE_paint_select_elem_test(obact) ||
- (obact && (obact->mode & OB_MODE_POSE)) )
+ (obact && (obact->mode & (OB_MODE_PARTICLE_EDIT | OB_MODE_POSE))) )
{
ViewContext vc;
@@ -2837,6 +2845,8 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
}
else if (obact->mode & OB_MODE_POSE)
pose_circle_select(&vc, select, mval, (float)radius);
+ else
+ return PE_circle_select(C, select, mval, (float)radius);
}
else if (obact && obact->mode & OB_MODE_SCULPT) {
return OPERATOR_CANCELLED;
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index 7be17c04670..20c62e91d01 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -42,7 +42,6 @@
#include "DNA_constraint_types.h"
#include "DNA_mask_types.h"
#include "DNA_movieclip_types.h"
-#include "DNA_object_types.h"
#include "DNA_scene_types.h" /* PET modes */
#include "BLI_alloca.h"
@@ -60,6 +59,7 @@
#include "BKE_editmesh_bvh.h"
#include "BKE_context.h"
#include "BKE_constraint.h"
+#include "BKE_particle.h"
#include "BKE_unit.h"
#include "BKE_mask.h"
#include "BKE_report.h"
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index 22d6e7af7fe..a59f9dc43dd 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -624,6 +624,7 @@ void flushTransIntFrameActionData(TransInfo *t);
void flushTransGraphData(TransInfo *t);
void remake_graph_transdata(TransInfo *t, struct ListBase *anim_data);
void flushTransUVs(TransInfo *t);
+void flushTransParticles(TransInfo *t);
bool clipUVTransform(TransInfo *t, float vec[2], const bool resize);
void clipUVData(TransInfo *t);
void flushTransNodes(TransInfo *t);
diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c
index f5b5faa4ad2..ce3d903b8f6 100644
--- a/source/blender/editors/transform/transform_conversions.c
+++ b/source/blender/editors/transform/transform_conversions.c
@@ -50,7 +50,6 @@
#include "DNA_gpencil_types.h"
#include "DNA_movieclip_types.h"
#include "DNA_mask_types.h"
-#include "DNA_object_types.h"
#include "MEM_guardedalloc.h"
@@ -82,7 +81,9 @@
#include "BKE_nla.h"
#include "BKE_node.h"
#include "BKE_object.h"
+#include "BKE_particle.h"
#include "BKE_paint.h"
+#include "BKE_pointcache.h"
#include "BKE_report.h"
#include "BKE_rigidbody.h"
#include "BKE_scene.h"
@@ -95,6 +96,7 @@
#include "ED_anim_api.h"
#include "ED_armature.h"
+#include "ED_particle.h"
#include "ED_image.h"
#include "ED_keyframing.h"
#include "ED_keyframes_edit.h"
@@ -1801,6 +1803,174 @@ static void createTransLatticeVerts(TransInfo *t)
}
}
+/* ******************* particle edit **************** */
+static void createTransParticleVerts(bContext *C, TransInfo *t)
+{
+ TransData *td = NULL;
+ TransDataExtension *tx;
+ Base *base = CTX_data_active_base(C);
+ Object *ob = CTX_data_active_object(C);
+ ParticleEditSettings *pset = PE_settings(t->scene);
+ PTCacheEdit *edit = PE_get_current(t->scene, ob);
+ ParticleSystem *psys = NULL;
+ ParticleSystemModifierData *psmd = NULL;
+ PTCacheEditPoint *point;
+ PTCacheEditKey *key;
+ float mat[4][4];
+ int i, k, transformparticle;
+ int count = 0, hasselected = 0;
+ const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0;
+
+ if (edit == NULL || t->settings->particle.selectmode == SCE_SELECT_PATH) return;
+
+ psys = edit->psys;
+
+ if (psys)
+ psmd = psys_get_modifier(ob, psys);
+
+ base->flag |= BA_HAS_RECALC_DATA;
+
+ for (i = 0, point = edit->points; i < edit->totpoint; i++, point++) {
+ point->flag &= ~PEP_TRANSFORM;
+ transformparticle = 0;
+
+ if ((point->flag & PEP_HIDE) == 0) {
+ for (k = 0, key = point->keys; k < point->totkey; k++, key++) {
+ if ((key->flag & PEK_HIDE) == 0) {
+ if (key->flag & PEK_SELECT) {
+ hasselected = 1;
+ transformparticle = 1;
+ }
+ else if (is_prop_edit)
+ transformparticle = 1;
+ }
+ }
+ }
+
+ if (transformparticle) {
+ count += point->totkey;
+ point->flag |= PEP_TRANSFORM;
+ }
+ }
+
+ /* note: in prop mode we need at least 1 selected */
+ if (hasselected == 0) return;
+
+ t->total = count;
+ td = t->data = MEM_callocN(t->total * sizeof(TransData), "TransObData(Particle Mode)");
+
+ if (t->mode == TFM_BAKE_TIME)
+ tx = t->ext = MEM_callocN(t->total * sizeof(TransDataExtension), "Particle_TransExtension");
+ else
+ tx = t->ext = NULL;
+
+ unit_m4(mat);
+
+ invert_m4_m4(ob->imat, ob->obmat);
+
+ for (i = 0, point = edit->points; i < edit->totpoint; i++, point++) {
+ TransData *head, *tail;
+ head = tail = td;
+
+ if (!(point->flag & PEP_TRANSFORM)) continue;
+
+ if (psys && !(psys->flag & PSYS_GLOBAL_HAIR))
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles + i, mat);
+
+ for (k = 0, key = point->keys; k < point->totkey; k++, key++) {
+ if (key->flag & PEK_USE_WCO) {
+ copy_v3_v3(key->world_co, key->co);
+ mul_m4_v3(mat, key->world_co);
+ td->loc = key->world_co;
+ }
+ else
+ td->loc = key->co;
+
+ copy_v3_v3(td->iloc, td->loc);
+ copy_v3_v3(td->center, td->loc);
+
+ if (key->flag & PEK_SELECT)
+ td->flag |= TD_SELECTED;
+ else if (!is_prop_edit)
+ td->flag |= TD_SKIP;
+
+ unit_m3(td->mtx);
+ unit_m3(td->smtx);
+
+ /* don't allow moving roots */
+ if (k == 0 && pset->flag & PE_LOCK_FIRST && (!psys || !(psys->flag & PSYS_GLOBAL_HAIR)))
+ td->protectflag |= OB_LOCK_LOC;
+
+ td->ob = ob;
+ td->ext = tx;
+ if (t->mode == TFM_BAKE_TIME) {
+ td->val = key->time;
+ td->ival = *(key->time);
+ /* abuse size and quat for min/max values */
+ td->flag |= TD_NO_EXT;
+ if (k == 0) tx->size = NULL;
+ else tx->size = (key - 1)->time;
+
+ if (k == point->totkey - 1) tx->quat = NULL;
+ else tx->quat = (key + 1)->time;
+ }
+
+ td++;
+ if (tx)
+ tx++;
+ tail++;
+ }
+ if (is_prop_edit && head != tail)
+ calc_distanceCurveVerts(head, tail - 1);
+ }
+}
+
+void flushTransParticles(TransInfo *t)
+{
+ Scene *scene = t->scene;
+ Object *ob = OBACT;
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ ParticleSystem *psys = edit->psys;
+ ParticleSystemModifierData *psmd = NULL;
+ PTCacheEditPoint *point;
+ PTCacheEditKey *key;
+ TransData *td;
+ float mat[4][4], imat[4][4], co[3];
+ int i, k;
+ const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0;
+
+ if (psys)
+ psmd = psys_get_modifier(ob, psys);
+
+ /* we do transform in world space, so flush world space position
+ * back to particle local space (only for hair particles) */
+ td = t->data;
+ for (i = 0, point = edit->points; i < edit->totpoint; i++, point++, td++) {
+ if (!(point->flag & PEP_TRANSFORM)) continue;
+
+ if (psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
+ psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles + i, mat);
+ invert_m4_m4(imat, mat);
+
+ for (k = 0, key = point->keys; k < point->totkey; k++, key++) {
+ copy_v3_v3(co, key->world_co);
+ mul_m4_v3(imat, co);
+
+
+ /* optimization for proportional edit */
+ if (!is_prop_edit || !compare_v3v3(key->co, co, 0.0001f)) {
+ copy_v3_v3(key->co, co);
+ point->flag |= PEP_EDIT_RECALC;
+ }
+ }
+ }
+ else
+ point->flag |= PEP_EDIT_RECALC;
+ }
+
+ PE_update_object(scene, OBACT, 1);
+}
+
/* ********************* mesh ****************** */
static bool bmesh_test_dist_add(BMVert *v, BMVert *v_other,
@@ -6145,6 +6315,13 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
else if (t->options & CTX_PAINT_CURVE) {
/* pass */
}
+ else if ((t->scene->basact) &&
+ (ob = t->scene->basact->object) &&
+ (ob->mode & OB_MODE_PARTICLE_EDIT) &&
+ PE_get_current(t->scene, ob))
+ {
+ /* do nothing */
+ }
else { /* Objects */
int i;
@@ -6152,6 +6329,8 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
for (i = 0; i < t->total; i++) {
TransData *td = t->data + i;
+ ListBase pidlist;
+ PTCacheID *pid;
ob = td->ob;
if (td->flag & TD_NOACTION)
@@ -6160,6 +6339,18 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
if (td->flag & TD_SKIP)
continue;
+ /* flag object caches as outdated */
+ BKE_ptcache_ids_from_object(&pidlist, ob, t->scene, MAX_DUPLI_RECUR);
+ for (pid = pidlist.first; pid; pid = pid->next) {
+ if (pid->type != PTCACHE_TYPE_PARTICLES) /* particles don't need reset on geometry change */
+ pid->cache->flag |= PTCACHE_OUTDATED;
+ }
+ BLI_freelistN(&pidlist);
+
+ /* pointcache refresh */
+ if (BKE_ptcache_object_reset(t->scene, ob, PTCACHE_RESET_OUTDATED))
+ DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
/* Needed for proper updating of "quick cached" dynamics. */
/* Creates troubles for moving animated objects without */
/* autokey though, probably needed is an anim sys override? */
@@ -7879,6 +8070,16 @@ void createTransData(bContext *C, TransInfo *t)
}
}
+ else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT) && PE_start_edit(PE_get_current(scene, ob))) {
+ createTransParticleVerts(C, t);
+ t->flag |= T_POINTS;
+
+ if (t->data && t->flag & T_PROP_EDIT) {
+ sort_trans_data(t); // makes selected become first in array
+ set_prop_dist(t, 1);
+ sort_trans_data_dist(t);
+ }
+ }
else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) {
if ((t->options & CTX_PAINT_CURVE) && !ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) {
t->flag |= T_POINTS | T_2D_EDIT;
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index 9e9372c72ea..f78a23be7b8 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -91,6 +91,7 @@
#include "ED_markers.h"
#include "ED_mesh.h"
#include "ED_object.h"
+#include "ED_particle.h"
#include "ED_screen_types.h"
#include "ED_space_api.h"
#include "ED_uvedit.h"
@@ -708,6 +709,8 @@ static void recalcData_spaceclip(TransInfo *t)
/* helper for recalcData() - for object transforms, typically in the 3D view */
static void recalcData_objects(TransInfo *t)
{
+ Base *base = t->scene->basact;
+
if (t->obedit) {
if (ELEM(t->obedit->type, OB_CURVE, OB_SURF)) {
Curve *cu = t->obedit->data;
@@ -893,6 +896,12 @@ static void recalcData_objects(TransInfo *t)
else
BKE_pose_where_is(t->scene, ob);
}
+ else if (base && (base->object->mode & OB_MODE_PARTICLE_EDIT) && PE_get_current(t->scene, base->object)) {
+ if (t->state != TRANS_CANCEL) {
+ applyProject(t);
+ }
+ flushTransParticles(t);
+ }
else {
int i;
diff --git a/source/blender/editors/transform/transform_manipulator.c b/source/blender/editors/transform/transform_manipulator.c
index f6fa464bb93..e1abf34b0f4 100644
--- a/source/blender/editors/transform/transform_manipulator.c
+++ b/source/blender/editors/transform/transform_manipulator.c
@@ -40,7 +40,6 @@
#include "DNA_gpencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
-#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
@@ -55,6 +54,8 @@
#include "BKE_context.h"
#include "BKE_curve.h"
#include "BKE_global.h"
+#include "BKE_particle.h"
+#include "BKE_pointcache.h"
#include "BKE_editmesh.h"
#include "BKE_lattice.h"
#include "BKE_gpencil.h"
@@ -66,6 +67,7 @@
#include "ED_armature.h"
#include "ED_curve.h"
+#include "ED_particle.h"
#include "ED_view3d.h"
#include "ED_gpencil.h"
@@ -558,6 +560,30 @@ static int calc_manipulator_stats(const bContext *C)
else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) {
/* pass */
}
+ else if (ob && ob->mode & OB_MODE_PARTICLE_EDIT) {
+ PTCacheEdit *edit = PE_get_current(scene, ob);
+ PTCacheEditPoint *point;
+ PTCacheEditKey *ek;
+ int k;
+
+ if (edit) {
+ point = edit->points;
+ for (a = 0; a < edit->totpoint; a++, point++) {
+ if (point->flag & PEP_HIDE) continue;
+
+ for (k = 0, ek = point->keys; k < point->totkey; k++, ek++) {
+ if (ek->flag & PEK_SELECT) {
+ calc_tw_center(scene, (ek->flag & PEK_USE_WCO) ? ek->world_co : ek->co);
+ totsel++;
+ }
+ }
+ }
+
+ /* selection center */
+ if (totsel)
+ mul_v3_fl(scene->twcent, 1.0f / (float)totsel); // centroid!
+ }
+ }
else {
/* we need the one selected object, if its not active */
diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c
index 1d6a392aae6..90a4aa3614d 100644
--- a/source/blender/editors/transform/transform_orientations.c
+++ b/source/blender/editors/transform/transform_orientations.c
@@ -1011,7 +1011,7 @@ int getTransformOrientation_ex(const bContext *C, float normal[3], float plane[3
result = ORIENTATION_EDGE;
}
}
- else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) {
+ else if (ob && (ob->mode & (OB_MODE_ALL_PAINT | OB_MODE_PARTICLE_EDIT))) {
/* pass */
}
else {
diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c
index 121a23a7027..f8bb124e943 100644
--- a/source/blender/editors/transform/transform_snap.c
+++ b/source/blender/editors/transform/transform_snap.c
@@ -519,6 +519,8 @@ static void initSnappingMode(TransInfo *t)
{
ToolSettings *ts = t->settings;
Object *obedit = t->obedit;
+ Scene *scene = t->scene;
+ Base *base_act = scene->basact;
if (t->spacetype == SPACE_NODE) {
/* force project off when not supported */
@@ -557,6 +559,12 @@ static void initSnappingMode(TransInfo *t)
t->tsnap.modeSelect = t->tsnap.snap_self ? SNAP_ALL : SNAP_NOT_ACTIVE;
}
}
+ /* Particles edit mode*/
+ else if (t->tsnap.applySnap != NULL && // A snapping function actually exist
+ (obedit == NULL && base_act && base_act->object && base_act->object->mode & OB_MODE_PARTICLE_EDIT))
+ {
+ t->tsnap.modeSelect = SNAP_ALL;
+ }
/* Object mode */
else if (t->tsnap.applySnap != NULL && // A snapping function actually exist
(obedit == NULL) ) // Object Mode
diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c
index c8ccb3772c5..02900d7022c 100644
--- a/source/blender/editors/transform/transform_snap_object.c
+++ b/source/blender/editors/transform/transform_snap_object.c
@@ -1867,7 +1867,23 @@ static bool snapObjectsRay(
unsigned int ob_index = 0;
Object *obedit = use_object_edit_cage ? sctx->scene->obedit : NULL;
+
+ /* Need an exception for particle edit because the base is flagged with BA_HAS_RECALC_DATA
+ * which makes the loop skip it, even the derived mesh will never change
+ *
+ * To solve that problem, we do it first as an exception.
+ * */
Base *base_act = sctx->scene->basact;
+ if (base_act && base_act->object && base_act->object->mode & OB_MODE_PARTICLE_EDIT) {
+ Object *ob = base_act->object;
+
+ retval |= snapObject(
+ sctx, ob, ob->obmat, ob_index++,
+ false, snap_to, mval,
+ ray_origin, ray_start, ray_normal, depth_range,
+ ray_depth, dist_px,
+ r_loc, r_no, r_index, r_ob, r_obmat, r_hit_list);
+ }
bool ignore_object_selected = false, ignore_object_active = false;
switch (snap_select) {
diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt
index 321b1043595..c0b30f93939 100644
--- a/source/blender/editors/util/CMakeLists.txt
+++ b/source/blender/editors/util/CMakeLists.txt
@@ -72,6 +72,7 @@ set(SRC
../include/ED_object.h
../include/ED_outliner.h
../include/ED_paint.h
+ ../include/ED_particle.h
../include/ED_physics.h
../include/ED_render.h
../include/ED_screen.h
diff --git a/source/blender/editors/util/undo.c b/source/blender/editors/util/undo.c
index 7fd67849414..4a9311416b3 100644
--- a/source/blender/editors/util/undo.c
+++ b/source/blender/editors/util/undo.c
@@ -49,6 +49,7 @@
#include "BKE_screen.h"
#include "ED_armature.h"
+#include "ED_particle.h"
#include "ED_curve.h"
#include "ED_gpencil.h"
#include "ED_mball.h"
@@ -97,6 +98,11 @@ void ED_undo_push(bContext *C, const char *str)
else if (obedit->type == OB_ARMATURE)
undo_push_armature(C, str);
}
+ else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) {
+ if (U.undosteps == 0) return;
+
+ PE_undo_push(CTX_data_scene(C), str);
+ }
else if (obact && obact->mode & OB_MODE_SCULPT) {
/* do nothing for now */
}
@@ -172,6 +178,12 @@ static int ed_undo_step(bContext *C, int step, const char *undoname)
else if (obact && obact->mode & OB_MODE_SCULPT) {
ED_undo_paint_step(C, UNDO_PAINT_MESH, step, undoname);
}
+ else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) {
+ if (step == 1)
+ PE_undo(scene);
+ else
+ PE_redo(scene);
+ }
else if (U.uiflag & USER_GLOBALUNDO) {
// note python defines not valid here anymore.
//#ifdef WITH_PYTHON
@@ -284,6 +296,9 @@ bool ED_undo_is_valid(const bContext *C, const char *undoname)
if (ED_undo_paint_is_valid(UNDO_PAINT_MESH, undoname))
return 1;
}
+ else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) {
+ return PE_undo_is_valid(CTX_data_scene(C));
+ }
if (U.uiflag & USER_GLOBALUNDO) {
return BKE_undo_is_valid(undoname);
@@ -443,8 +458,9 @@ void ED_undo_operator_repeat_cb_evt(bContext *C, void *arg_op, int UNUSED(arg_ev
enum {
UNDOSYSTEM_GLOBAL = 1,
UNDOSYSTEM_EDITMODE = 2,
- UNDOSYSTEM_IMAPAINT = 3,
- UNDOSYSTEM_SCULPT = 4,
+ UNDOSYSTEM_PARTICLE = 3,
+ UNDOSYSTEM_IMAPAINT = 4,
+ UNDOSYSTEM_SCULPT = 5,
};
static int get_undo_system(bContext *C)
@@ -470,7 +486,9 @@ static int get_undo_system(bContext *C)
}
else {
if (obact) {
- if (obact->mode & OB_MODE_TEXTURE_PAINT) {
+ if (obact->mode & OB_MODE_PARTICLE_EDIT)
+ return UNDOSYSTEM_PARTICLE;
+ else if (obact->mode & OB_MODE_TEXTURE_PAINT) {
if (!ED_undo_paint_empty(UNDO_PAINT_IMAGE))
return UNDOSYSTEM_IMAPAINT;
}
@@ -496,7 +514,10 @@ static EnumPropertyItem *rna_undo_itemf(bContext *C, int undosys, int *totitem)
while (true) {
const char *name = NULL;
- if (undosys == UNDOSYSTEM_EDITMODE) {
+ if (undosys == UNDOSYSTEM_PARTICLE) {
+ name = PE_undo_get_name(CTX_data_scene(C), i, &active);
+ }
+ else if (undosys == UNDOSYSTEM_EDITMODE) {
name = undo_editmode_get_name(C, i, &active);
}
else if (undosys == UNDOSYSTEM_IMAPAINT) {
@@ -576,7 +597,10 @@ static int undo_history_exec(bContext *C, wmOperator *op)
int undosys = get_undo_system(C);
int item = RNA_int_get(op->ptr, "item");
- if (undosys == UNDOSYSTEM_EDITMODE) {
+ if (undosys == UNDOSYSTEM_PARTICLE) {
+ PE_undo_number(CTX_data_scene(C), item);
+ }
+ else if (undosys == UNDOSYSTEM_EDITMODE) {
undo_editmode_number(C, item + 1);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL);
}