diff options
Diffstat (limited to 'source/blender/blenkernel/intern')
-rw-r--r-- | source/blender/blenkernel/intern/brush.c | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/cache_library.c | 192 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/context.c | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/customdata.c | 29 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/editstrands.c | 267 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/key.c | 409 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_sample.c | 379 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/object.c | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/object_dupli.c | 122 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/particle.c | 13 |
10 files changed, 1263 insertions, 153 deletions
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 307f97f1344..d5c5fb5fe43 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -68,7 +68,7 @@ static void brush_defaults(Brush *brush) brush->blend = 0; brush->flag = 0; - brush->ob_mode = OB_MODE_ALL_PAINT; + brush->ob_mode = OB_MODE_ALL_BRUSH; /* BRUSH SCULPT TOOL SETTINGS */ brush->weight = 1.0f; /* weight of brush 0 - 1.0 */ diff --git a/source/blender/blenkernel/intern/cache_library.c b/source/blender/blenkernel/intern/cache_library.c index b9ac9cd82b7..74bdb027022 100644 --- a/source/blender/blenkernel/intern/cache_library.c +++ b/source/blender/blenkernel/intern/cache_library.c @@ -42,6 +42,7 @@ #include "DNA_cache_library_types.h" #include "DNA_group_types.h" +#include "DNA_key_types.h" #include "DNA_modifier_types.h" #include "DNA_object_force.h" #include "DNA_object_types.h" @@ -56,9 +57,11 @@ #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" #include "BKE_effect.h" #include "BKE_global.h" #include "BKE_group.h" +#include "BKE_key.h" #include "BKE_library.h" #include "BKE_main.h" #include "BKE_modifier.h" @@ -84,7 +87,7 @@ CacheLibrary *BKE_cache_library_add(Main *bmain, const char *name) BLI_snprintf(cachelib->output_filepath, sizeof(cachelib->output_filepath), "//cache/%s.%s", basename, PTC_get_default_archive_extension()); cachelib->source_mode = CACHE_LIBRARY_SOURCE_SCENE; - cachelib->display_mode = CACHE_LIBRARY_DISPLAY_RESULT; + cachelib->display_mode = CACHE_LIBRARY_DISPLAY_MODIFIERS; cachelib->display_flag = CACHE_LIBRARY_DISPLAY_MOTION | CACHE_LIBRARY_DISPLAY_CHILDREN; cachelib->render_flag = CACHE_LIBRARY_RENDER_MOTION | CACHE_LIBRARY_RENDER_CHILDREN; cachelib->eval_mode = CACHE_LIBRARY_EVAL_REALTIME | CACHE_LIBRARY_EVAL_RENDER; @@ -319,7 +322,7 @@ static bool has_active_cache(CacheLibrary *cachelib) } } - if (cachelib->source_mode == CACHE_LIBRARY_SOURCE_CACHE) { + if (ELEM(cachelib->source_mode, CACHE_LIBRARY_SOURCE_CACHE, CACHE_LIBRARY_DISPLAY_MODIFIERS)) { return true; } @@ -342,7 +345,7 @@ static struct PTCReaderArchive *find_active_cache(Scene *scene, CacheLibrary *ca } } - if (!archive && cachelib->source_mode == CACHE_LIBRARY_SOURCE_CACHE) { + if (!archive && ELEM(cachelib->source_mode, CACHE_LIBRARY_SOURCE_CACHE, CACHE_LIBRARY_DISPLAY_MODIFIERS)) { BKE_cache_archive_input_path(cachelib, filename, sizeof(filename)); archive = PTC_open_reader_archive(scene, filename); } @@ -846,7 +849,7 @@ bool BKE_cache_modifier_find_object(DupliCache *dupcache, Object *ob, DupliObjec return true; } -bool BKE_cache_modifier_find_strands(DupliCache *dupcache, Object *ob, int hair_system, DupliObjectData **r_data, Strands **r_strands) +bool BKE_cache_modifier_find_strands(DupliCache *dupcache, Object *ob, int hair_system, DupliObjectData **r_data, Strands **r_strands, const char **r_name) { DupliObjectData *dobdata; ParticleSystem *psys; @@ -875,6 +878,7 @@ bool BKE_cache_modifier_find_strands(DupliCache *dupcache, Object *ob, int hair_ if (r_data) *r_data = dobdata; if (r_strands) *r_strands = strands; + if (r_name) *r_name = psys->name; return true; } @@ -949,7 +953,7 @@ static void hairsim_process(HairSimCacheModifier *hsmd, CacheProcessContext *ctx // if (eval_mode != CACHE_LIBRARY_EVAL_REALTIME) // return; - if (!BKE_cache_modifier_find_strands(data->dupcache, ob, hsmd->hair_system, NULL, &strands)) + if (!BKE_cache_modifier_find_strands(data->dupcache, ob, hsmd->hair_system, NULL, &strands, NULL)) return; /* Note: motion state data should always be created regardless of actual sim. @@ -1110,7 +1114,7 @@ static void shrinkwrap_data_free(ShrinkWrapCacheData *data) } } -static void shrinkwrap_apply_vertex(ShrinkWrapCacheModifier *smd, ShrinkWrapCacheData *data, ShrinkWrapCacheInstance *inst, StrandsVertex *vertex, StrandsMotionState *UNUSED(state)) +static void shrinkwrap_apply_vertex(ShrinkWrapCacheModifier *UNUSED(smd), ShrinkWrapCacheData *data, ShrinkWrapCacheInstance *inst, StrandsVertex *vertex, StrandsMotionState *UNUSED(state)) { // const float *point = state->co; // float *npoint = state->co; @@ -1167,7 +1171,7 @@ static void shrinkwrap_apply(ShrinkWrapCacheModifier *smd, ShrinkWrapCacheData * } } -static void shrinkwrap_process(ShrinkWrapCacheModifier *smd, CacheProcessContext *UNUSED(ctx), CacheProcessData *data, int frame, int frame_prev, eCacheLibrary_EvalMode eval_mode) +static void shrinkwrap_process(ShrinkWrapCacheModifier *smd, CacheProcessContext *UNUSED(ctx), CacheProcessData *data, int UNUSED(frame), int UNUSED(frame_prev), eCacheLibrary_EvalMode UNUSED(eval_mode)) { Object *ob = smd->object; DupliObject *dob; @@ -1177,15 +1181,7 @@ static void shrinkwrap_process(ShrinkWrapCacheModifier *smd, CacheProcessContext ShrinkWrapCacheData shrinkwrap; - /* only perform hair sim once */ - if (eval_mode != CACHE_LIBRARY_EVAL_REALTIME) - return; - - /* skip first step and potential backward steps */ - if (frame <= frame_prev) - return; - - if (!BKE_cache_modifier_find_strands(data->dupcache, ob, smd->hair_system, NULL, &strands)) + if (!BKE_cache_modifier_find_strands(data->dupcache, ob, smd->hair_system, NULL, &strands, NULL)) return; if (!BKE_cache_modifier_find_object(data->dupcache, smd->target, &target_data)) return; @@ -1222,11 +1218,175 @@ CacheModifierTypeInfo cacheModifierType_ShrinkWrap = { /* free */ (CacheModifier_FreeFunc)shrinkwrap_free, }; +/* ------------------------------------------------------------------------- */ + +static void strandskey_init(StrandsKeyCacheModifier *skmd) +{ + skmd->object = NULL; + skmd->hair_system = -1; + + skmd->key = BKE_key_add_ex(NULL, KEY_FROMTYPE_STRANDS); + skmd->key->type = KEY_RELATIVE; +} + +static void strandskey_copy(StrandsKeyCacheModifier *skmd, StrandsKeyCacheModifier *tskmd) +{ + tskmd->key = BKE_key_copy(skmd->key); + + tskmd->edit = NULL; +} + +static void strandskey_free(StrandsKeyCacheModifier *skmd) +{ + BKE_key_free(skmd->key); + + if (skmd->edit) { + BKE_editstrands_free(skmd->edit); + MEM_freeN(skmd->edit); + skmd->edit = NULL; + } +} + +static void strandskey_foreach_id_link(StrandsKeyCacheModifier *skmd, CacheLibrary *cachelib, CacheModifier_IDWalkFunc walk, void *userdata) +{ + walk(userdata, cachelib, &skmd->modifier, (ID **)(&skmd->object)); +} + +static void strandskey_process(StrandsKeyCacheModifier *skmd, CacheProcessContext *UNUSED(ctx), CacheProcessData *data, int UNUSED(frame), int UNUSED(frame_prev), eCacheLibrary_EvalMode UNUSED(eval_mode)) +{ + Object *ob = skmd->object; + Strands *strands; + KeyBlock *actkb; + float *shape; + + if (!BKE_cache_modifier_find_strands(data->dupcache, ob, skmd->hair_system, NULL, &strands, NULL)) + return; + + actkb = BLI_findlink(&skmd->key->block, skmd->shapenr); + shape = BKE_key_evaluate_strands(strands, skmd->key, actkb, skmd->flag & eStrandsKeyCacheModifier_Flag_ShapeLock, NULL); + if (shape) { + StrandsVertex *vert = strands->verts; + int totvert = strands->totverts; + int i; + + float *fp = shape; + for (i = 0; i < totvert; ++i) { + copy_v3_v3(vert->co, fp); + + fp += 3; + ++vert; + } + + MEM_freeN(shape); + } +} + +CacheModifierTypeInfo cacheModifierType_StrandsKey = { + /* name */ "StrandsKey", + /* structName */ "StrandsKeyCacheModifier", + /* structSize */ sizeof(StrandsKeyCacheModifier), + + /* copy */ (CacheModifier_CopyFunc)strandskey_copy, + /* foreachIDLink */ (CacheModifier_ForeachIDLinkFunc)strandskey_foreach_id_link, + /* process */ (CacheModifier_ProcessFunc)strandskey_process, + /* init */ (CacheModifier_InitFunc)strandskey_init, + /* free */ (CacheModifier_FreeFunc)strandskey_free, +}; + +KeyBlock *BKE_cache_modifier_strands_key_insert_key(StrandsKeyCacheModifier *skmd, Strands *strands, const char *name, const bool from_mix) +{ + Key *key = skmd->key; + KeyBlock *kb; + bool newkey = false; + + if (key == NULL) { + key = skmd->key = BKE_key_add_ex(NULL, KEY_FROMTYPE_STRANDS); + key->type = KEY_RELATIVE; + newkey = true; + } + else if (BLI_listbase_is_empty(&key->block)) { + newkey = true; + } + + if (newkey || from_mix == false) { + /* create from mesh */ + kb = BKE_keyblock_add_ctime(key, name, false); + BKE_keyblock_convert_from_strands(strands, key, kb); + } + else { + /* copy from current values */ + KeyBlock *actkb = BLI_findlink(&skmd->key->block, skmd->shapenr); + bool shape_lock = skmd->flag & eStrandsKeyCacheModifier_Flag_ShapeLock; + int totelem; + float *data = BKE_key_evaluate_strands(strands, key, actkb, shape_lock, &totelem); + + /* create new block with prepared data */ + kb = BKE_keyblock_add_ctime(key, name, false); + kb->data = data; + kb->totelem = totelem; + } + + return kb; +} + +bool BKE_cache_modifier_strands_key_get(Object *ob, StrandsKeyCacheModifier **r_skmd, DerivedMesh **r_dm, Strands **r_strands, DupliObjectData **r_dobdata, const char **r_name, float r_mat[4][4]) +{ + CacheLibrary *cachelib = ob->cache_library; + CacheModifier *md; + + if (!cachelib) + return false; + + /* ignore when the object is not actually using the cachelib */ + if (!((ob->transflag & OB_DUPLIGROUP) && ob->dup_group && ob->dup_cache)) + return false; + + for (md = cachelib->modifiers.first; md; md = md->next) { + if (md->type == eCacheModifierType_StrandsKey) { + StrandsKeyCacheModifier *skmd = (StrandsKeyCacheModifier *)md; + DupliObjectData *dobdata; + + if (BKE_cache_modifier_find_strands(ob->dup_cache, skmd->object, skmd->hair_system, &dobdata, r_strands, r_name)) { + if (r_skmd) *r_skmd = skmd; + if (r_dm) *r_dm = dobdata->dm; + if (r_dobdata) *r_dobdata = dobdata; + + /* relative transform from the original hair object to the duplicator local space */ + /* XXX bad hack, common problem: we want to display strand edit data in the place of "the" instance, + * but in fact there can be multiple instances of the same dupli object data, so this is ambiguous ... + * For our basic use case, just pick the first dupli instance, assuming that it's the only one. + * ugh ... + */ + if (r_mat) { + DupliObject *dob; + for (dob = ob->dup_cache->duplilist.first; dob; dob = dob->next) { + if (dob->ob == skmd->object) + break; + } + if (dob) { + /* note: plain duplis from the dupli cache list are relative + * to the duplicator already! (not in world space like final duplis) + */ + copy_m4_m4(r_mat, dob->mat); + } + else + unit_m4(r_mat); + } + + return true; + } + } + } + + return false; +} + void BKE_cache_modifier_init(void) { cache_modifier_type_set(eCacheModifierType_HairSimulation, &cacheModifierType_HairSimulation); cache_modifier_type_set(eCacheModifierType_ForceField, &cacheModifierType_ForceField); cache_modifier_type_set(eCacheModifierType_ShrinkWrap, &cacheModifierType_ShrinkWrap); + cache_modifier_type_set(eCacheModifierType_StrandsKey, &cacheModifierType_StrandsKey); } /* ========================================================================= */ diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index 59f7da83925..b463a1650b7 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -928,6 +928,7 @@ int CTX_data_mode_enum(const bContext *C) else if (ob->mode & OB_MODE_VERTEX_PAINT) return CTX_MODE_PAINT_VERTEX; else if (ob->mode & OB_MODE_TEXTURE_PAINT) return CTX_MODE_PAINT_TEXTURE; else if (ob->mode & OB_MODE_PARTICLE_EDIT) return CTX_MODE_PARTICLE; + else if (ob->mode & OB_MODE_HAIR_EDIT) return CTX_MODE_HAIR; } } @@ -951,6 +952,7 @@ static const char *data_mode_strings[] = { "vertexpaint", "imagepaint", "particlemode", + "hairmode", "objectmode", NULL }; diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index 724ca6de3fc..dac03ff360b 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -55,6 +55,7 @@ #include "BKE_customdata.h" #include "BKE_customdata_file.h" +#include "BKE_editstrands.h" #include "BKE_global.h" #include "BKE_main.h" #include "BKE_mesh_mapping.h" @@ -1324,6 +1325,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { {sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL}, /* 42: CD_FACEMAP */ {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, layerDefault_fmap, NULL}, + /* 43: CD_MSURFACE_SAMPLE */ + {sizeof(MSurfaceSample), "MSurfaceSample", 1, NULL, NULL, NULL, NULL, NULL, NULL}, }; /* note, numbers are from trunk and need updating for bmesh */ @@ -1341,6 +1344,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = { /* 35-36 */ "CDGridPaintMask", "CDMVertSkin", /* 37-38 */ "CDFreestyleEdge", "CDFreestyleFace", /* 39-42 */ "CDMLoopTangent", "CDTessLoopNormal", "CDCustomLoopNormal", "CDFaceMap", + /* 43 */ "CDMSurfaceSample", }; @@ -1378,6 +1382,17 @@ const CustomDataMask CD_MASK_BMESH = const CustomDataMask CD_MASK_FACECORNERS = /* XXX Not used anywhere! */ CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_MTEXPOLY | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_NORMAL | CD_MASK_MLOOPTANGENT; +const CustomDataMask CD_MASK_STRANDS = + CD_MASK_MVERT | CD_MASK_MEDGE | + CD_MASK_MSTICKY | CD_MASK_MDEFORMVERT | CD_MASK_MCOL | + CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR | CD_MASK_MDISPS | + CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE | + CD_MASK_MSURFACE_SAMPLE; +const CustomDataMask CD_MASK_STRANDS_BMESH = + CD_MASK_MSTICKY | CD_MASK_MDEFORMVERT | CD_MASK_PROP_FLT | CD_MASK_PROP_INT | + CD_MASK_PROP_STR | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_MDISPS | + CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE | + CD_MASK_MSURFACE_SAMPLE; const CustomDataMask CD_MASK_EVERYTHING = CD_MASK_MVERT | CD_MASK_MSTICKY /* DEPRECATED */ | CD_MASK_MDEFORMVERT | CD_MASK_MEDGE | CD_MASK_MFACE | CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_ORIGINDEX | CD_MASK_NORMAL /* | CD_MASK_POLYINDEX */ | CD_MASK_PROP_FLT | @@ -1389,7 +1404,7 @@ const CustomDataMask CD_MASK_EVERYTHING = /* BMESH ONLY END */ CD_MASK_PAINT_MASK | CD_MASK_GRID_PAINT_MASK | CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE | CD_MASK_FREESTYLE_FACE | - CD_MASK_MLOOPTANGENT | CD_MASK_TESSLOOPNORMAL | CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_FACEMAP; + CD_MASK_MLOOPTANGENT | CD_MASK_TESSLOOPNORMAL | CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_FACEMAP | CD_MASK_MSURFACE_SAMPLE; static const LayerTypeInfo *layerType_getInfo(int type) { @@ -2838,6 +2853,18 @@ void *CustomData_bmesh_get_layer_n(const CustomData *data, void *block, int n) return POINTER_OFFSET(block, data->layers[n].offset); } +/*Bmesh Custom Data Functions. Should replace editmesh ones with these as well, due to more effecient memory alloc*/ +void *CustomData_bmesh_get_named(const CustomData *data, void *block, int type, const char *name) +{ + int layer_index; + + /* get the layer index of the named layer of type */ + layer_index = CustomData_get_named_layer_index(data, type, name); + if (layer_index == -1) return NULL; + + return (char *)block + data->layers[layer_index].offset; +} + bool CustomData_layer_has_math(const struct CustomData *data, int layer_n) { const LayerTypeInfo *typeInfo = layerType_getInfo(data->layers[layer_n].type); diff --git a/source/blender/blenkernel/intern/editstrands.c b/source/blender/blenkernel/intern/editstrands.c new file mode 100644 index 00000000000..86e81925b58 --- /dev/null +++ b/source/blender/blenkernel/intern/editstrands.c @@ -0,0 +1,267 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) Blender Foundation + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Lukas Toenne + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/blenkernel/intern/editstrands.c + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_math.h" +#include "BLI_mempool.h" + +#include "DNA_cache_library_types.h" +#include "DNA_customdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" +#include "DNA_strands_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_cache_library.h" +#include "BKE_customdata.h" +#include "BKE_cdderivedmesh.h" +#include "BKE_DerivedMesh.h" +#include "BKE_editstrands.h" +#include "BKE_effect.h" +#include "BKE_mesh_sample.h" +#include "BKE_particle.h" + +#include "BPH_strands.h" + +#include "intern/bmesh_strands_conv.h" + +/* mat can be used to transform the dm into another space, + * in case the edited object is not the active object: + * mat = inv(M_act) * M_edit + */ +BMEditStrands *BKE_editstrands_create(BMesh *bm, DerivedMesh *root_dm, float mat[4][4]) +{ + BMEditStrands *es = MEM_callocN(sizeof(BMEditStrands), __func__); + + es->bm = bm; + es->root_dm = CDDM_copy(root_dm); + + if (mat) { + DerivedMesh *dm = es->root_dm; + MVert *mv = dm->getVertArray(dm); + int totvert = dm->getNumVerts(dm), i; + for (i = 0; i < totvert; ++i, ++mv) { + mul_m4_v3(mat, mv->co); + } + } + + return es; +} + +BMEditStrands *BKE_editstrands_copy(BMEditStrands *es) +{ + BMEditStrands *es_copy = MEM_callocN(sizeof(BMEditStrands), __func__); + *es_copy = *es; + + es_copy->bm = BM_mesh_copy(es->bm); + es_copy->root_dm = CDDM_copy(es->root_dm); + + return es_copy; +} + +/** + * \brief Return the BMEditStrands for a given object + */ +BMEditStrands *BKE_editstrands_from_object(Object *ob) +{ + { + ParticleSystem *psys = psys_get_current(ob); + if (psys && psys->hairedit) + return psys->hairedit; + } + + { + StrandsKeyCacheModifier *skmd; + if (BKE_cache_modifier_strands_key_get(ob, &skmd, NULL, NULL, NULL, NULL, NULL)) { + if (skmd->edit) + return skmd->edit; + } + } + + return NULL; +} + +void BKE_editstrands_update_linked_customdata(BMEditStrands *es) +{ + BMesh *bm = es->bm; + + /* this is done for BMEditMesh, but should never exist for strands */ + BLI_assert(!CustomData_has_layer(&bm->pdata, CD_MTEXPOLY)); +} + +/*does not free the BMEditStrands struct itself*/ +void BKE_editstrands_free(BMEditStrands *es) +{ + if (es->bm) + BM_mesh_free(es->bm); + if (es->root_dm) + es->root_dm->release(es->root_dm); +} + +/* === constraints === */ + +BMEditStrandsLocations BKE_editstrands_get_locations(BMEditStrands *edit) +{ + BMesh *bm = edit->bm; + BMEditStrandsLocations locs = MEM_mallocN(3*sizeof(float) * bm->totvert, "editstrands locations"); + + BMVert *v; + BMIter iter; + int i; + + BM_ITER_MESH_INDEX(v, &iter, bm, BM_VERTS_OF_MESH, i) { + copy_v3_v3(locs[i], v->co); + } + + return locs; +} + +void BKE_editstrands_free_locations(BMEditStrandsLocations locs) +{ + MEM_freeN(locs); +} + +void BKE_editstrands_solve_constraints(Object *ob, BMEditStrands *es, BMEditStrandsLocations orig) +{ + BKE_editstrands_ensure(es); + + BPH_strands_solve_constraints(ob, es, orig); +} + +static void editstrands_calc_segment_lengths(BMesh *bm) +{ + BMVert *root, *v, *vprev; + BMIter iter, iter_strand; + int k; + + BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) { + BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) { + if (k > 0) { + float length = len_v3v3(v->co, vprev->co); + BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH, length); + } + vprev = v; + } + } +} + +void BKE_editstrands_ensure(BMEditStrands *es) +{ + BM_strands_cd_flag_ensure(es->bm, 0); + + if (es->flag & BM_STRANDS_DIRTY_SEGLEN) { + editstrands_calc_segment_lengths(es->bm); + + es->flag &= ~BM_STRANDS_DIRTY_SEGLEN; + } +} + + +/* === cache shape key conversion === */ + +BMesh *BKE_cache_strands_to_bmesh(struct Strands *strands, struct Key *key, float mat[4][4], int act_key_nr, DerivedMesh *dm) +{ + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_STRANDS(strands); + BMesh *bm; + + DM_ensure_tessface(dm); + + bm = BM_mesh_create(&allocsize); + BM_strands_bm_from_strands(bm, strands, key, dm, mat, true, act_key_nr); + editstrands_calc_segment_lengths(bm); + + return bm; +} + +struct Strands *BKE_cache_strands_from_bmesh(BMEditStrands *edit, struct Key *key, float mat[4][4], DerivedMesh *dm) +{ + BMesh *bm = edit ? edit->bm : NULL; + Strands *strands = NULL; + + if (bm && dm) { + BVHTreeFromMesh bvhtree = {NULL}; + + DM_ensure_tessface(dm); + + bvhtree_from_mesh_faces(&bvhtree, dm, 0.0, 2, 6); + + strands = BM_strands_bm_to_strands(bm, strands, key, mat, dm, &bvhtree); + + free_bvhtree_from_mesh(&bvhtree); + } + + return strands; +} + + +/* === particle conversion === */ + +BMesh *BKE_particles_to_bmesh(Object *ob, ParticleSystem *psys) +{ + ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys); + + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_PSYS(psys); + BMesh *bm; + + bm = BM_mesh_create(&allocsize); + + if (psmd && psmd->dm) { + DM_ensure_tessface(psmd->dm); + + BM_strands_bm_from_psys(bm, ob, psys, psmd->dm, true, /*psys->shapenr*/ -1); + + editstrands_calc_segment_lengths(bm); + } + + return bm; +} + +void BKE_particles_from_bmesh(Object *ob, ParticleSystem *psys) +{ + ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys); + BMesh *bm = psys->hairedit ? psys->hairedit->bm : NULL; + + if (bm) { + if (psmd && psmd->dm) { + BVHTreeFromMesh bvhtree = {NULL}; + + DM_ensure_tessface(psmd->dm); + + bvhtree_from_mesh_faces(&bvhtree, psmd->dm, 0.0, 2, 6); + + BM_strands_bm_to_psys(bm, ob, psys, psmd->dm, &bvhtree); + + free_bvhtree_from_mesh(&bvhtree); + } + } +} diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index a1d0680785d..23d9c076182 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -52,6 +52,7 @@ #include "DNA_object_types.h" #include "DNA_particle_types.h" #include "DNA_scene_types.h" +#include "DNA_strands_types.h" #include "DNA_texture_types.h" #include "BKE_animsys.h" @@ -66,20 +67,21 @@ #include "BKE_particle.h" #include "BKE_editmesh.h" #include "BKE_scene.h" +#include "BKE_strands.h" #include "RNA_access.h" #include "RE_render_ext.h" -#define KEY_MODE_DUMMY 0 /* use where mode isn't checked for */ -#define KEY_MODE_BPOINT 1 -#define KEY_MODE_BEZTRIPLE 2 - /* old defines from DNA_ipo_types.h for data-type, stored in DNA - don't modify! */ #define IPO_FLOAT 4 #define IPO_BEZTRIPLE 100 #define IPO_BPOINT 101 +#define KEY_MODE_DUMMY 0 /* use where mode isn't checked for */ +#define KEY_MODE_BPOINT 1 +#define KEY_MODE_BEZTRIPLE 2 + void BKE_key_free(Key *key) { KeyBlock *kb; @@ -104,55 +106,70 @@ void BKE_key_free_nolib(Key *key) } } -Key *BKE_key_add(ID *id) /* common function */ +static void key_set_elemstr(ID *id, short fromtype, char *r_elemstr, int *r_elemsize) +{ + /* XXX the code here uses some defines which will soon be deprecated... */ + char elemtype = IPO_FLOAT; + char numelem = 0; + int elemsize = 0; + + switch (fromtype) { + case KEY_FROMTYPE_ID: + if (id) { + switch (GS(id->name)) { + case ID_ME: + numelem = 3; + elemtype = IPO_FLOAT; + elemsize = 12; + break; + case ID_LT: + numelem = 3; + elemtype = IPO_FLOAT; + elemsize = 12; + break; + case ID_CU: + numelem = 4; + elemtype = IPO_BPOINT; + elemsize = 16; + break; + } + } + break; + case KEY_FROMTYPE_STRANDS: + numelem = 3; + elemtype = IPO_FLOAT; + elemsize = 12; + break; + } + + r_elemstr[0] = numelem; + r_elemstr[1] = elemtype; + r_elemstr[2] = 0; + *r_elemsize = elemsize; +} + +Key *BKE_key_add_ex(ID *from, short fromtype) /* common function */ { Key *key; - char *el; key = BKE_libblock_alloc(G.main, ID_KE, "Key"); key->type = KEY_NORMAL; - BKE_key_set_from_id(key, id); + BKE_key_set_from_id(key, from); + key->fromtype = fromtype; key->uidgen = 1; - /* XXX the code here uses some defines which will soon be deprecated... */ - switch (GS(id->name)) { - case ID_ME: - el = key->elemstr; - - el[0] = 3; - el[1] = IPO_FLOAT; - el[2] = 0; - - key->elemsize = 12; - - break; - case ID_LT: - el = key->elemstr; - - el[0] = 3; - el[1] = IPO_FLOAT; - el[2] = 0; - - key->elemsize = 12; - - break; - case ID_CU: - el = key->elemstr; - - el[0] = 4; - el[1] = IPO_BPOINT; - el[2] = 0; - - key->elemsize = 16; - - break; - } + key_set_elemstr(from, fromtype, key->elemstr, &key->elemsize); return key; } +Key *BKE_key_add(ID *id) +{ + return BKE_key_add_ex(id, KEY_FROMTYPE_ID); +} + Key *BKE_key_add_particles(Object *ob, ParticleSystem *psys) /* particles are "special" */ { Key *key; @@ -566,7 +583,7 @@ static char *key_block_get_data(Key *key, KeyBlock *actkb, KeyBlock *kb, char ** if (kb == actkb) { /* this hack makes it possible to edit shape keys in * edit mode with shape keys blending applied */ - if (key->from_extra.type == KEY_OWNER_MESH) { + if (key->from && key->from_extra.type == KEY_OWNER_MESH) { Mesh *me; BMVert *eve; BMIter iter; @@ -598,39 +615,50 @@ static char *key_block_get_data(Key *key, KeyBlock *actkb, KeyBlock *kb, char ** /* currently only the first value of 'ofs' may be set. */ static bool key_pointer_size(const Key *key, const int mode, int *poinsize, int *ofs) { - switch (key->from_extra.type) { - case KEY_OWNER_MESH: - *ofs = sizeof(float) * 3; - *poinsize = *ofs; - break; - case KEY_OWNER_LATTICE: - *ofs = sizeof(float) * 3; - *poinsize = *ofs; - break; - case KEY_OWNER_CURVE: - if (mode == KEY_MODE_BPOINT) { - *ofs = sizeof(float) * 4; - *poinsize = *ofs; - } - else { - ofs[0] = sizeof(float) * 12; - *poinsize = (*ofs) / 3; + switch (key->fromtype) { + case KEY_FROMTYPE_ID: + if (!key->from) + return false; + + switch (GS(key->from->name)) { + case ID_ME: + *ofs = sizeof(float) * 3; + *poinsize = *ofs; + break; + case KEY_OWNER_LATTICE: + *ofs = sizeof(float) * 3; + *poinsize = *ofs; + break; + case KEY_OWNER_CURVE: + if (mode == KEY_MODE_BPOINT) { + *ofs = sizeof(float) * 4; + *poinsize = *ofs; + } + else { + ofs[0] = sizeof(float) * 12; + *poinsize = (*ofs) / 3; + } + break; + case KEY_OWNER_PARTICLES: + *ofs = sizeof(float) * 3; + *poinsize = *ofs; + break; + + default: + BLI_assert(!"invalid 'key->from' ID type"); + return false; } break; - case KEY_OWNER_PARTICLES: + + case KEY_FROMTYPE_STRANDS: *ofs = sizeof(float) * 3; *poinsize = *ofs; break; - - default: - BLI_assert(!"invalid 'key->from' ID type"); - return false; } - return true; } -static void cp_key(const int start, int end, const int tot, char *poin, Key *key, KeyBlock *actkb, KeyBlock *kb, float *weights, const int mode) +static void cp_key(const int start, int end, const int tot, char *poin, Key *key, KeyBlock *actkb, KeyBlock *kb, float *weights, const int mode, void *kref_data) { float ktot = 0.0, kd = 0.0; int elemsize, poinsize = 0, a, *ofsp, ofs[32], flagflo = 0; @@ -657,7 +685,12 @@ static void cp_key(const int start, int end, const int tot, char *poin, Key *key } k1 = key_block_get_data(key, actkb, kb, &freek1); - kref = key_block_get_data(key, actkb, key->refkey, &freekref); + if (kref_data) { + kref = kref_data; + freekref = NULL; + } + else + kref = key_block_get_data(key, actkb, key->refkey, &freekref); /* this exception is needed curves with multiple splines */ if (start != 0) { @@ -760,7 +793,7 @@ static void cp_cu_key(Curve *cu, Key *key, KeyBlock *actkb, KeyBlock *kb, const a1 = max_ii(a, start); a2 = min_ii(a + step, end); - if (a1 < a2) cp_key(a1, a2, tot, out, key, actkb, kb, NULL, KEY_MODE_BPOINT); + if (a1 < a2) cp_key(a1, a2, tot, out, key, actkb, kb, NULL, KEY_MODE_BPOINT, NULL); } else if (nu->bezt) { step = 3 * nu->pntsu; @@ -769,7 +802,7 @@ static void cp_cu_key(Curve *cu, Key *key, KeyBlock *actkb, KeyBlock *kb, const a1 = max_ii(a, start); a2 = min_ii(a + step, end); - if (a1 < a2) cp_key(a1, a2, tot, out, key, actkb, kb, NULL, KEY_MODE_BEZTRIPLE); + if (a1 < a2) cp_key(a1, a2, tot, out, key, actkb, kb, NULL, KEY_MODE_BEZTRIPLE, NULL); } else { step = 0; @@ -777,8 +810,8 @@ static void cp_cu_key(Curve *cu, Key *key, KeyBlock *actkb, KeyBlock *kb, const } } -void BKE_key_evaluate_relative(const int start, int end, const int tot, char *basispoin, Key *key, KeyBlock *actkb, - float **per_keyblock_weights, const int mode) +void BKE_key_evaluate_relative_ex(const int start, int end, const int tot, char *basispoin, Key *key, KeyBlock *actkb, + float **per_keyblock_weights, const int mode, char *refdata) { KeyBlock *kb; int *ofsp, ofs[3], elemsize, b; @@ -803,7 +836,8 @@ void BKE_key_evaluate_relative(const int start, int end, const int tot, char *ba if (mode == KEY_MODE_BEZTRIPLE) elemsize *= 3; /* step 1 init */ - cp_key(start, end, tot, basispoin, key, actkb, key->refkey, NULL, mode); + if (!refdata) + cp_key(start, end, tot, basispoin, key, actkb, key->refkey, NULL, mode, NULL); /* step 2: do it */ @@ -813,17 +847,21 @@ void BKE_key_evaluate_relative(const int start, int end, const int tot, char *ba /* only with value, and no difference allowed */ if (!(kb->flag & KEYBLOCK_MUTE) && icuval != 0.0f && kb->totelem == tot) { - KeyBlock *refb; float weight, *weights = per_keyblock_weights ? per_keyblock_weights[keyblock_index] : NULL; char *freefrom = NULL, *freereffrom = NULL; - /* reference now can be any block */ - refb = BLI_findlink(&key->block, kb->relative); - if (refb == NULL) continue; - poin = basispoin; from = key_block_get_data(key, actkb, kb, &freefrom); - reffrom = key_block_get_data(key, actkb, refb, &freereffrom); + if (refdata) { + reffrom = refdata; + } + else { + /* reference now can be any block */ + KeyBlock *refb = BLI_findlink(&key->block, kb->relative); + if (refb == NULL) continue; + + reffrom = key_block_get_data(key, actkb, refb, &freereffrom); + } poin += start * poinsize; reffrom += key->elemsize * start; // key elemsize yes! @@ -878,6 +916,11 @@ void BKE_key_evaluate_relative(const int start, int end, const int tot, char *ba } } +void BKE_key_evaluate_relative(const int start, int end, const int tot, char *basispoin, Key *key, KeyBlock *actkb, + float **per_keyblock_weights, const int mode) +{ + BKE_key_evaluate_relative_ex(start, end, tot, basispoin, key, actkb, per_keyblock_weights, mode, NULL); +} static void do_key(const int start, int end, const int tot, char *poin, Key *key, KeyBlock *actkb, KeyBlock **k, float *t, const int mode) { @@ -1174,6 +1217,21 @@ static float *get_object_weights_array(Object *ob, char *vgroup, WeightsArrayCac return NULL; } +static float *get_weights_array_strands(Strands *strands, const char *UNUSED(vgroup), bool is_refkey, WeightsArrayCache *UNUSED(cache)) +{ + int totvert = strands->totverts; + + if (is_refkey) { + /* for the refkey, return zero weights, so the refkey actually uses the unmodified data */ + float *weights = MEM_callocN(totvert * sizeof(float), "weights"); + return weights; + } + else { + /* TODO no vgroup support for strands yet */ + return NULL; + } +} + float **BKE_keyblock_get_per_block_object_weights(Object *ob, Key *key, WeightsArrayCache *cache) { KeyBlock *keyblock; @@ -1281,6 +1339,26 @@ float **BKE_keyblock_get_per_block_particle_weights(Object *ob, ParticleSystem * return per_keyblock_weights; } +float **BKE_keyblock_strands_get_per_block_weights(Strands *strands, Key *key, WeightsArrayCache *cache) +{ + KeyBlock *keyblock; + float **per_keyblock_weights; + int keyblock_index; + + per_keyblock_weights = + MEM_mallocN(sizeof(*per_keyblock_weights) * key->totkey, + "per keyblock weights"); + + for (keyblock = key->block.first, keyblock_index = 0; + keyblock; + keyblock = keyblock->next, keyblock_index++) + { + per_keyblock_weights[keyblock_index] = get_weights_array_strands(strands, keyblock->vgroup, keyblock == key->refkey, cache); + } + + return per_keyblock_weights; +} + void BKE_keyblock_free_per_block_weights(Key *key, float **per_keyblock_weights, WeightsArrayCache *cache) { int a; @@ -1329,7 +1407,7 @@ static void do_mesh_key(Object *ob, Key *key, char *out, const int tot) do_key(0, tot, tot, (char *)out, key, actkb, k, t, KEY_MODE_DUMMY); } else { - cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY); + cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY, NULL); } } } @@ -1420,7 +1498,7 @@ static void do_latt_key(Object *ob, Key *key, char *out, const int tot) do_key(0, tot, tot, (char *)out, key, actkb, k, t, KEY_MODE_DUMMY); } else { - cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY); + cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY, NULL); } } @@ -1449,7 +1527,7 @@ static void do_psys_key(Object *ob, ParticleSystem *psys, float cfra, Key *key, if (flag == 0) do_key(0, tot, tot, (char *)out, key, actkb, k, t, KEY_MODE_DUMMY); else - cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY); + cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY, NULL); } } @@ -1529,7 +1607,7 @@ float *BKE_key_evaluate_object_ex( if (OB_TYPE_SUPPORT_VGROUP(ob->type)) { float *weights = get_object_weights_array(ob, kb->vgroup, NULL); - cp_key(0, tot, tot, out, key, actkb, kb, weights, 0); + cp_key(0, tot, tot, out, key, actkb, kb, weights, 0, NULL); if (weights) MEM_freeN(weights); } @@ -1555,6 +1633,103 @@ float *BKE_key_evaluate_object(Object *ob, int *r_totelem) return BKE_key_evaluate_object_ex(ob, r_totelem, NULL, 0); } +static void do_strands_key(Strands *strands, Key *key, KeyBlock *actkb, char *out, const int tot) +{ + KeyBlock *k[4]; + float t[4]; + int flag = 0; + + if (key->type == KEY_RELATIVE) { + WeightsArrayCache cache = {0, NULL}; + float **per_keyblock_weights ; + per_keyblock_weights = BKE_keyblock_strands_get_per_block_weights(strands, key, &cache); + BKE_key_evaluate_relative_ex(0, tot, tot, (char *)out, key, actkb, per_keyblock_weights, KEY_MODE_DUMMY, out); + BKE_keyblock_free_per_block_weights(key, per_keyblock_weights, &cache); + } + else { + const float ctime_scaled = key->ctime / 100.0f; + + flag = setkeys(ctime_scaled, &key->block, k, t, 0); + + if (flag == 0) { + do_key(0, tot, tot, (char *)out, key, actkb, k, t, KEY_MODE_DUMMY); + } + else { + cp_key(0, tot, tot, (char *)out, key, actkb, k[2], NULL, KEY_MODE_DUMMY, out); + } + } +} + +float *BKE_key_evaluate_strands_ex(Strands *strands, Key *key, KeyBlock *actkb, bool lock_shape, int *r_totelem, float *arr, size_t arr_size) +{ + char *out; + int tot = 0, size = 0; + + if (key == NULL || BLI_listbase_is_empty(&key->block)) + return NULL; + + /* compute size of output array */ + tot = strands->totverts; + size = tot * 3 * sizeof(float); + /* if nothing to interpolate, cancel */ + if (tot == 0 || size == 0) + return NULL; + + /* allocate array */ + if (arr == NULL) { + out = MEM_callocN(size, "BKE_key_evaluate_strands out"); + } + else { + if (arr_size != size) { + return NULL; + } + + out = (char *)arr; + } + + if (lock_shape && actkb) { + /* shape locked, copy the locked shape instead of blending */ + float *weights; + + if (actkb->flag & KEYBLOCK_MUTE) + actkb = key->refkey; + + /* XXX weights not supported for strands yet */ + weights = get_weights_array_strands(strands, actkb->vgroup, actkb == key->refkey, NULL); + + cp_key(0, tot, tot, out, key, actkb, actkb, weights, 0, out); + + if (weights) + MEM_freeN(weights); + } + else { + do_strands_key(strands, key, actkb, out, tot); + } + + if (r_totelem) { + *r_totelem = tot; + } + return (float *)out; +} + +float *BKE_key_evaluate_strands(Strands *strands, Key *key, KeyBlock *actkb, bool lock_shape, int *r_totelem) +{ + size_t size = sizeof(float) * 3 * strands->totverts; + float *data = MEM_mallocN(size, "strands shape key data"); + float *result; + float *fp; + int i; + + for (i = 0, fp = data; i < strands->totverts; ++i, fp += 3) + copy_v3_v3(fp, strands->verts[i].co); + + result = BKE_key_evaluate_strands_ex(strands, key, actkb, lock_shape, r_totelem, data, size); + if (result != data) + MEM_freeN(data); + + return result; +} + /* returns key coordinates when key applied, NULL otherwise */ float *BKE_key_evaluate_particles_ex(Object *ob, ParticleSystem *psys, float cfra, int *r_totelem, float *arr, size_t arr_size) @@ -1606,7 +1781,7 @@ float *BKE_key_evaluate_particles_ex(Object *ob, ParticleSystem *psys, float cfr psys->shapenr = 1; } - cp_key(0, tot, tot, out, key, actkb, kb, NULL, 0); + cp_key(0, tot, tot, out, key, actkb, kb, NULL, 0, NULL); } else if (ob->shapeflag & OB_SHAPE_LOCK) { /* shape locked, copy the locked shape instead of blending */ @@ -1623,7 +1798,7 @@ float *BKE_key_evaluate_particles_ex(Object *ob, ParticleSystem *psys, float cfr weights = get_particle_weights_array(ob, psys, kb->name, cfra); - cp_key(0, tot, tot, out, key, actkb, kb, weights, 0); + cp_key(0, tot, tot, out, key, actkb, kb, weights, 0, NULL); if (weights) MEM_freeN(weights); } @@ -2035,6 +2210,55 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me) } } +/************************* Strands ************************/ +void BKE_keyblock_update_from_strands(Strands *strands, KeyBlock *kb) +{ + StrandsVertex *vert; + float (*fp)[3]; + int a, tot; + + BLI_assert(strands->totverts == kb->totelem); + + tot = strands->totverts; + if (tot == 0) return; + + vert = strands->verts; + fp = kb->data; + for (a = 0; a < tot; a++, fp++, vert++) { + copy_v3_v3(*fp, vert->co); + } +} + +void BKE_keyblock_convert_from_strands(Strands *strands, Key *key, KeyBlock *kb) +{ + int tot = strands->totverts; + + if (strands->totverts == 0) return; + + MEM_SAFE_FREE(kb->data); + + kb->data = MEM_mallocN(key->elemsize * tot, __func__); + kb->totelem = tot; + + BKE_keyblock_update_from_strands(strands, kb); +} + +void BKE_keyblock_convert_to_strands(KeyBlock *kb, Strands *strands) +{ + StrandsVertex *vert; + const float (*fp)[3]; + int a, tot; + + vert = strands->verts; + fp = kb->data; + + tot = min_ii(kb->totelem, strands->totverts); + + for (a = 0; a < tot; a++, fp++, vert++) { + copy_v3_v3(vert->co, *fp); + } +} + /************************* raw coords ************************/ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, float (*vertCos)[3]) { @@ -2272,11 +2496,10 @@ void BKE_keyblock_convert_from_hair_keys(struct Object *UNUSED(ob), struct Parti * \param org_index if < 0, current object's active shape will be used as skey to move. * \return true if something was done, else false. */ -bool BKE_keyblock_move(Object *ob, int org_index, int new_index) +bool BKE_keyblock_move_ex(Key *key, int *shapenr, int org_index, int new_index) { - Key *key = BKE_key_from_object(ob); KeyBlock *kb; - const int act_index = ob->shapenr - 1; + const int act_index = *shapenr - 1; const int totkey = key->totkey; int i; bool rev, in_range = false; @@ -2336,13 +2559,13 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) /* Need to update active shape number if it's affected, same principle as for relative indices above. */ if (org_index == act_index) { - ob->shapenr = new_index + 1; + *shapenr = new_index + 1; } else if (act_index < org_index && act_index >= new_index) { - ob->shapenr++; + (*shapenr)++; } else if (act_index > org_index && act_index <= new_index) { - ob->shapenr--; + (*shapenr)--; } /* First key is always refkey, matches interface and BKE_key_sort */ @@ -2351,6 +2574,14 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) return true; } +bool BKE_keyblock_move(Object *ob, int org_index, int new_index) +{ + int shapenr; + bool result = BKE_keyblock_move_ex(BKE_key_from_object(ob), &shapenr, org_index, new_index); + ob->shapenr = shapenr; + return result; +} + /** * Check if given keyblock (as index) is used as basis by others in given key. */ diff --git a/source/blender/blenkernel/intern/mesh_sample.c b/source/blender/blenkernel/intern/mesh_sample.c new file mode 100644 index 00000000000..c36d2adde8b --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_sample.c @@ -0,0 +1,379 @@ +/* + * ***** 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. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/blenkernel/intern/mesh_sample.c + * \ingroup bke + * + * Sample a mesh surface or volume and evaluate samples on deformed meshes. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_key_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_utildefines.h" +#include "BLI_math.h" +#include "BLI_rand.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh_sample.h" +#include "BKE_customdata.h" +#include "BKE_DerivedMesh.h" + +#include "BLI_strict_flags.h" + +/* ==== Evaluate ==== */ + +bool BKE_mesh_sample_eval(DerivedMesh *dm, const MSurfaceSample *sample, float loc[3], float nor[3], float tang[3]) +{ + MVert *mverts = dm->getVertArray(dm); + unsigned int totverts = (unsigned int)dm->getNumVerts(dm); + MVert *v1, *v2, *v3; + + zero_v3(loc); + zero_v3(nor); + zero_v3(tang); + + if (sample->orig_verts[0] >= totverts || + sample->orig_verts[1] >= totverts || + sample->orig_verts[2] >= totverts) + return false; + + v1 = &mverts[sample->orig_verts[0]]; + v2 = &mverts[sample->orig_verts[1]]; + v3 = &mverts[sample->orig_verts[2]]; + + { /* location */ + madd_v3_v3fl(loc, v1->co, sample->orig_weights[0]); + madd_v3_v3fl(loc, v2->co, sample->orig_weights[1]); + madd_v3_v3fl(loc, v3->co, sample->orig_weights[2]); + } + + { /* normal */ + float vnor[3]; + + normal_short_to_float_v3(vnor, v1->no); + madd_v3_v3fl(nor, vnor, sample->orig_weights[0]); + normal_short_to_float_v3(vnor, v2->no); + madd_v3_v3fl(nor, vnor, sample->orig_weights[1]); + normal_short_to_float_v3(vnor, v3->no); + madd_v3_v3fl(nor, vnor, sample->orig_weights[2]); + + normalize_v3(nor); + } + + { /* tangent */ + float edge[3]; + + /* XXX simply using the v1-v2 edge as a tangent vector for now ... + * Eventually mikktspace generated tangents (CD_TANGENT tessface layer) + * should be used for consistency, but requires well-defined tessface + * indices for the mesh surface samples. + */ + + sub_v3_v3v3(edge, v2->co, v1->co); + /* make edge orthogonal to nor */ + madd_v3_v3fl(edge, nor, -dot_v3v3(edge, nor)); + normalize_v3_v3(tang, edge); + } + + return true; +} + +bool BKE_mesh_sample_shapekey(Key *key, KeyBlock *kb, const MSurfaceSample *sample, float loc[3]) +{ + float *v1, *v2, *v3; + + BLI_assert(key->elemsize == 3 * sizeof(float)); + BLI_assert(sample->orig_verts[0] < (unsigned int)kb->totelem); + BLI_assert(sample->orig_verts[1] < (unsigned int)kb->totelem); + BLI_assert(sample->orig_verts[2] < (unsigned int)kb->totelem); + + v1 = (float *)kb->data + sample->orig_verts[0] * 3; + v2 = (float *)kb->data + sample->orig_verts[1] * 3; + v3 = (float *)kb->data + sample->orig_verts[2] * 3; + + zero_v3(loc); + madd_v3_v3fl(loc, v1, sample->orig_weights[0]); + madd_v3_v3fl(loc, v2, sample->orig_weights[1]); + madd_v3_v3fl(loc, v3, sample->orig_weights[2]); + + /* TODO use optional vgroup weights to determine if a shapeky actually affects the sample */ + return true; +} + + +/* ==== Sampling Utilities ==== */ + +BLI_INLINE void mesh_sample_weights_from_loc(MSurfaceSample *sample, DerivedMesh *dm, int face_index, const float loc[3]) +{ + MFace *face = &dm->getTessFaceArray(dm)[face_index]; + unsigned int index[4] = { face->v1, face->v2, face->v3, face->v4 }; + MVert *mverts = dm->getVertArray(dm); + + float *v1 = mverts[face->v1].co; + float *v2 = mverts[face->v2].co; + float *v3 = mverts[face->v3].co; + float *v4 = face->v4 ? mverts[face->v4].co : NULL; + float w[4]; + int tri[3]; + + interp_weights_face_v3_index(tri, w, v1, v2, v3, v4, loc); + + sample->orig_verts[0] = index[tri[0]]; + sample->orig_verts[1] = index[tri[1]]; + sample->orig_verts[2] = index[tri[2]]; + sample->orig_weights[0] = w[tri[0]]; + sample->orig_weights[1] = w[tri[1]]; + sample->orig_weights[2] = w[tri[2]]; +} + +/* ==== Sampling ==== */ + +static bool mesh_sample_store_array_sample(void *vdata, int capacity, int index, const MSurfaceSample *sample) +{ + MSurfaceSample *data = vdata; + if (index >= capacity) + return false; + + data[index] = *sample; + return true; +} + +void BKE_mesh_sample_storage_single(MSurfaceSampleStorage *storage, MSurfaceSample *sample) +{ + /* handled as just a special array case with capacity = 1 */ + storage->store_sample = mesh_sample_store_array_sample; + storage->capacity = 1; + storage->data = sample; + storage->free_data = false; +} + +void BKE_mesh_sample_storage_array(MSurfaceSampleStorage *storage, MSurfaceSample *samples, int capacity) +{ + storage->store_sample = mesh_sample_store_array_sample; + storage->capacity = capacity; + storage->data = samples; + storage->free_data = false; +} + +void BKE_mesh_sample_storage_release(MSurfaceSampleStorage *storage) +{ + if (storage->free_data) + MEM_freeN(storage->data); +} + + +int BKE_mesh_sample_generate_random(MSurfaceSampleStorage *dst, DerivedMesh *dm, unsigned int seed, int totsample) +{ + MFace *mfaces; + int totfaces; + RNG *rng; + MFace *mface; + float a, b; + int i, stored = 0; + + rng = BLI_rng_new(seed); + + DM_ensure_tessface(dm); + mfaces = dm->getTessFaceArray(dm); + totfaces = dm->getNumTessFaces(dm); + + for (i = 0; i < totsample; ++i) { + MSurfaceSample sample = {{0}}; + + mface = &mfaces[BLI_rng_get_int(rng) % totfaces]; + + if (mface->v4 && BLI_rng_get_int(rng) % 2 == 0) { + sample.orig_verts[0] = mface->v3; + sample.orig_verts[1] = mface->v4; + sample.orig_verts[2] = mface->v1; + } + else { + sample.orig_verts[0] = mface->v1; + sample.orig_verts[1] = mface->v2; + sample.orig_verts[2] = mface->v3; + } + + a = BLI_rng_get_float(rng); + b = BLI_rng_get_float(rng); + if (a + b > 1.0f) { + a = 1.0f - a; + b = 1.0f - b; + } + sample.orig_weights[0] = 1.0f - (a + b); + sample.orig_weights[1] = a; + sample.orig_weights[2] = b; + + if (dst->store_sample(dst->data, dst->capacity, i, &sample)) + ++stored; + else + break; + } + + BLI_rng_free(rng); + + return stored; +} + + +static bool sample_bvh_raycast(MSurfaceSample *sample, DerivedMesh *dm, BVHTreeFromMesh *bvhdata, const float ray_start[3], const float ray_end[3]) +{ + BVHTreeRayHit hit; + float ray_normal[3], dist; + + sub_v3_v3v3(ray_normal, ray_end, ray_start); + dist = normalize_v3(ray_normal); + + hit.index = -1; + hit.dist = dist; + + if (BLI_bvhtree_ray_cast(bvhdata->tree, ray_start, ray_normal, 0.0f, + &hit, bvhdata->raycast_callback, bvhdata) >= 0) { + + mesh_sample_weights_from_loc(sample, dm, hit.index, hit.co); + + return true; + } + else + return false; +} + +int BKE_mesh_sample_generate_raycast(MSurfaceSampleStorage *dst, DerivedMesh *dm, MeshSampleRayCallback ray_cb, void *userdata, int totsample) +{ + BVHTreeFromMesh bvhdata; + float ray_start[3], ray_end[3]; + int i, stored = 0; + + DM_ensure_tessface(dm); + + memset(&bvhdata, 0, sizeof(BVHTreeFromMesh)); + bvhtree_from_mesh_faces(&bvhdata, dm, 0.0f, 4, 6); + + if (bvhdata.tree) { + for (i = 0; i < totsample; ++i) { + if (ray_cb(userdata, ray_start, ray_end)) { + MSurfaceSample sample; + if (sample_bvh_raycast(&sample, dm, &bvhdata, ray_start, ray_end)) { + if (dst->store_sample(dst->data, dst->capacity, i, &sample)) + ++stored; + else + break; + } + } + } + } + + free_bvhtree_from_mesh(&bvhdata); + + return stored; +} + +/* ==== Utilities ==== */ + +#include "DNA_particle_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_particle.h" + +bool BKE_mesh_sample_from_particle(MSurfaceSample *sample, ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa) +{ + MVert *mverts; + MFace *mface; + float mapfw[4]; + int mapindex; + float *co1 = NULL, *co2 = NULL, *co3 = NULL, *co4 = NULL; + float vec[3]; + float w[4]; + + if (!psys_get_index_on_dm(psys, dm, pa, &mapindex, mapfw)) + return false; + + mface = dm->getTessFaceData(dm, mapindex, CD_MFACE); + mverts = dm->getVertDataArray(dm, CD_MVERT); + + co1 = mverts[mface->v1].co; + co2 = mverts[mface->v2].co; + co3 = mverts[mface->v3].co; + + if (mface->v4) { + co4 = mverts[mface->v4].co; + + interp_v3_v3v3v3v3(vec, co1, co2, co3, co4, mapfw); + } + else { + interp_v3_v3v3v3(vec, co1, co2, co3, mapfw); + } + + /* test both triangles of the face */ + interp_weights_face_v3(w, co1, co2, co3, NULL, vec); + if (w[0] <= 1.0f && w[1] <= 1.0f && w[2] <= 1.0f) { + sample->orig_verts[0] = mface->v1; + sample->orig_verts[1] = mface->v2; + sample->orig_verts[2] = mface->v3; + + copy_v3_v3(sample->orig_weights, w); + return true; + } + else if (mface->v4) { + interp_weights_face_v3(w, co3, co4, co1, NULL, vec); + sample->orig_verts[0] = mface->v3; + sample->orig_verts[1] = mface->v4; + sample->orig_verts[2] = mface->v1; + + copy_v3_v3(sample->orig_weights, w); + return true; + } + else + return false; +} + +bool BKE_mesh_sample_to_particle(MSurfaceSample *sample, ParticleSystem *UNUSED(psys), DerivedMesh *dm, BVHTreeFromMesh *bvhtree, ParticleData *pa) +{ + BVHTreeNearest nearest; + float vec[3], nor[3], tang[3]; + + BKE_mesh_sample_eval(dm, sample, vec, nor, tang); + + nearest.index = -1; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest(bvhtree->tree, vec, &nearest, bvhtree->nearest_callback, bvhtree); + if (nearest.index >= 0) { + MFace *mface = dm->getTessFaceData(dm, nearest.index, CD_MFACE); + MVert *mverts = dm->getVertDataArray(dm, CD_MVERT); + + float *co1 = mverts[mface->v1].co; + float *co2 = mverts[mface->v2].co; + float *co3 = mverts[mface->v3].co; + float *co4 = mface->v4 ? mverts[mface->v4].co : NULL; + + pa->num = nearest.index; + pa->num_dmcache = DMCACHE_NOTFOUND; + + interp_weights_face_v3(pa->fuv, co1, co2, co3, co4, vec); + pa->foffset = 0.0f; /* XXX any sensible way to reconstruct this? */ + + return true; + } + else + return false; +} diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 35c169f64f2..845f0d9da4e 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -1338,6 +1338,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys) psysn->pathcache = NULL; psysn->childcache = NULL; psysn->edit = NULL; + psysn->hairedit = NULL; psysn->pdd = NULL; psysn->effectors = NULL; psysn->tree = NULL; diff --git a/source/blender/blenkernel/intern/object_dupli.c b/source/blender/blenkernel/intern/object_dupli.c index 24a5f7e849b..3ee6cb9e85d 100644 --- a/source/blender/blenkernel/intern/object_dupli.c +++ b/source/blender/blenkernel/intern/object_dupli.c @@ -62,6 +62,7 @@ #include "BKE_lattice.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_sample.h" #include "BKE_object.h" #include "BKE_particle.h" #include "BKE_scene.h" @@ -1601,13 +1602,56 @@ static int count_hair_verts(ParticleSystem *psys) return numverts; } +static void dupli_strands_data_update(CacheLibrary *cachelib, DupliObjectData *data, + DupliObject *dob, bool calc_strands_base) { + ParticleSystem *psys; + for (psys = dob->ob->particlesystem.first; psys; psys = psys->next) { + if (cachelib->data_types & CACHE_TYPE_HAIR) { + if (psys->part && psys->part->type == PART_HAIR) { + int numstrands = psys->totpart; + int numverts = count_hair_verts(psys); + ParticleData *pa; + HairKey *hkey; + int p, k; + + Strands *strands = BKE_strands_new(numstrands, numverts); + StrandsCurve *scurve = strands->curves; + StrandsVertex *svert = strands->verts; + + for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa) { + float hairmat[4][4]; + psys_mat_hair_to_object(dob->ob, data->dm, psys->part->from, pa, hairmat); + + scurve->numverts = pa->totkey; + copy_m3_m4(scurve->root_matrix, hairmat); + BKE_mesh_sample_from_particle(&scurve->msurf, psys, data->dm, pa); + + for (k = 0, hkey = pa->hair; k < pa->totkey; ++k, ++hkey) { + copy_v3_v3(svert->co, hkey->co); + if (calc_strands_base) + copy_v3_v3(svert->base, hkey->co); + svert->time = hkey->time; + svert->weight = hkey->weight; + ++svert; + } + ++scurve; + } + + BKE_dupli_object_data_add_strands(data, psys->name, strands); + } + } + } +} + typedef struct DupliObjectDataFromGroupState { EvaluationContext *eval_ctx; Scene *scene; + CacheLibrary *cachelib; + bool calc_strands_base; } DupliObjectDataFromGroupState; typedef struct DupliObjectDataFromGroupTask { - Object *object; + DupliObject *dob; DupliObjectData *data; } DupliObjectDataFromGroupTask; @@ -1615,18 +1659,21 @@ static void dupli_object_data_from_group_func(TaskPool *pool, void *taskdata, in { DupliObjectDataFromGroupState *state = (DupliObjectDataFromGroupState *)BLI_task_pool_userdata(pool); DupliObjectDataFromGroupTask *task = (DupliObjectDataFromGroupTask *)taskdata; + Object *object = task->dob->ob; DerivedMesh *dm; if (state->eval_ctx->mode == DAG_EVAL_RENDER) { - dm = mesh_create_derived_render(state->scene, task->object, CD_MASK_BAREMESH); + dm = mesh_create_derived_render(state->scene, object, CD_MASK_BAREMESH); } else { - dm = mesh_create_derived_view(state->scene, task->object, CD_MASK_BAREMESH); + dm = mesh_create_derived_view(state->scene, object, CD_MASK_BAREMESH); } if (dm != NULL) { BKE_dupli_object_data_set_mesh(task->data, dm); } + + dupli_strands_data_update(state->cachelib, task->data, task->dob, state->calc_strands_base); } void BKE_dupli_cache_from_group(Scene *scene, Group *group, CacheLibrary *cachelib, DupliCache *dupcache, EvaluationContext *eval_ctx, bool calc_strands_base) @@ -1651,62 +1698,29 @@ void BKE_dupli_cache_from_group(Scene *scene, Group *group, CacheLibrary *cachel state.eval_ctx = eval_ctx; state.scene = scene; + state.cachelib = cachelib; + state.calc_strands_base = calc_strands_base; task_pool = BLI_task_pool_create(task_scheduler, &state); for (dob = dupcache->duplilist.first; dob; dob = dob->next) { DupliObjectData *data = BKE_dupli_cache_find_data(dupcache, dob->ob); if (!data) { - ParticleSystem *psys; - + bool strands_handled = false; data = dupli_cache_add_object_data(dupcache, dob->ob); - if (cachelib->data_types & CACHE_TYPE_DERIVED_MESH) { if (dob->ob->type == OB_MESH) { /* TODO(sergey): Consider using memory pool instead. */ DupliObjectDataFromGroupTask *task = MEM_mallocN(sizeof(DupliObjectDataFromGroupTask), - "dupcache task"); - task->object = dob->ob; + "dupcache task"); + task->dob = dob; task->data = data; BLI_task_pool_push(task_pool, dupli_object_data_from_group_func, task, true, TASK_PRIORITY_LOW); + /* Task is getting care of strands as well. */ + strands_handled = true; } } - - for (psys = dob->ob->particlesystem.first; psys; psys = psys->next) { - if (cachelib->data_types & CACHE_TYPE_HAIR) { - if (psys->part && psys->part->type == PART_HAIR) { - int numstrands = psys->totpart; - int numverts = count_hair_verts(psys); - ParticleData *pa; - HairKey *hkey; - int p, k; - - Strands *strands = BKE_strands_new(numstrands, numverts); - StrandsCurve *scurve = strands->curves; - StrandsVertex *svert = strands->verts; - - for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa) { - float hairmat[4][4]; - psys_mat_hair_to_object(dob->ob, data->dm, psys->part->from, pa, hairmat); - - scurve->numverts = pa->totkey; - copy_m3_m4(scurve->root_matrix, hairmat); - - for (k = 0, hkey = pa->hair; k < pa->totkey; ++k, ++hkey) { - copy_v3_v3(svert->co, hkey->co); - if (calc_strands_base) - copy_v3_v3(svert->base, hkey->co); - svert->time = hkey->time; - svert->weight = hkey->weight; - - ++svert; - } - - ++scurve; - } - - BKE_dupli_object_data_add_strands(data, psys->name, strands); - } - } + if (!strands_handled) { + dupli_strands_data_update(cachelib, data, dob, calc_strands_base); } } } @@ -1717,12 +1731,26 @@ void BKE_dupli_cache_from_group(Scene *scene, Group *group, CacheLibrary *cachel /* ------------------------------------------------------------------------- */ +static void object_dupli_cache_apply_modifiers(Object *ob, Scene *scene, eCacheLibrary_EvalMode eval_mode) +{ + CacheLibrary *cachelib = ob->cache_library; + int frame = scene->r.cfra; + CacheProcessData process_data; + + process_data.lay = ob->lay; + copy_m4_m4(process_data.mat, ob->obmat); + process_data.dupcache = ob->dup_cache; + + BKE_cache_process_dupli_cache(cachelib, &process_data, scene, ob->dup_group, (float)frame, (float)frame, eval_mode); +} + void BKE_object_dupli_cache_update(Scene *scene, Object *ob, EvaluationContext *eval_ctx, float frame) { const eCacheLibrary_EvalMode eval_mode = eval_ctx->mode == DAG_EVAL_RENDER ? CACHE_LIBRARY_EVAL_RENDER : CACHE_LIBRARY_EVAL_REALTIME; bool is_dupligroup = (ob->transflag & OB_DUPLIGROUP) && ob->dup_group; bool is_cached = ob->cache_library && (ob->cache_library->source_mode == CACHE_LIBRARY_SOURCE_CACHE || ob->cache_library->display_mode == CACHE_LIBRARY_DISPLAY_RESULT); + bool do_modifiers = ob->cache_library && ob->cache_library->display_mode == CACHE_LIBRARY_DISPLAY_MODIFIERS; /* cache is a group duplicator feature only */ if (is_dupligroup && is_cached) { @@ -1745,6 +1773,10 @@ void BKE_object_dupli_cache_update(Scene *scene, Object *ob, EvaluationContext * if (!(ob->cache_library->flag & CACHE_LIBRARY_BAKING)) { /* TODO at this point we could apply animation offset */ BKE_cache_read_dupli_cache(ob->cache_library, ob->dup_cache, scene, ob->dup_group, frame, eval_mode, true); + + if (do_modifiers) { + object_dupli_cache_apply_modifiers(ob, scene, eval_mode); + } } ob->dup_cache->flag &= ~DUPCACHE_FLAG_DIRTY; diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index 34dfa4cb476..97508523060 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -65,6 +65,7 @@ #include "BKE_boids.h" #include "BKE_cloth.h" #include "BKE_colortools.h" +#include "BKE_editstrands.h" #include "BKE_effect.h" #include "BKE_global.h" #include "BKE_group.h" @@ -578,6 +579,11 @@ void psys_free(Object *ob, ParticleSystem *psys) if (psys->edit && psys->free_edit) psys->free_edit(psys->edit); + if (psys->hairedit) { + BKE_editstrands_free(psys->hairedit); + MEM_freeN(psys->hairedit); + psys->hairedit = NULL; + } if (psys->child) { MEM_freeN(psys->child); @@ -1175,6 +1181,11 @@ static int psys_map_index_on_dm(DerivedMesh *dm, int from, int index, int index_ return 1; } +int psys_get_index_on_dm(ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4]) +{ + return psys_map_index_on_dm(dm, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, mapindex, mapfw); +} + /* interprets particle data to get a point on a mesh in object space */ void psys_particle_on_dm(DerivedMesh *dm, int from, int index, int index_dmcache, const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3], @@ -2775,7 +2786,7 @@ void object_remove_particle_system(Scene *UNUSED(scene), Object *ob) if (ob->particlesystem.first) ((ParticleSystem *) ob->particlesystem.first)->flag |= PSYS_CURRENT; else - ob->mode &= ~OB_MODE_PARTICLE_EDIT; + ob->mode &= ~(OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT); DAG_relations_tag_update(G.main); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); |