diff options
author | Jacques Lucke <jacques@blender.org> | 2021-05-20 15:41:07 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-05-20 15:41:07 +0300 |
commit | 1895205be3308ef7d0c2553519c1433e9117dc33 (patch) | |
tree | fe4146e10a6ded237e65dedba18a885cebb3c6f4 /source/blender/blenkernel/intern | |
parent | 619b015a46786a4f4057b0dd4d20d27b61d13c0f (diff) | |
parent | 933999db752681ce881760e75befccd17e706f41 (diff) |
Merge branch 'master' into profiler-editorprofiler-editor
Diffstat (limited to 'source/blender/blenkernel/intern')
36 files changed, 2444 insertions, 457 deletions
diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index c63447c6cdd..b40d859754e 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -713,11 +713,13 @@ static float (*get_orco_coords(Object *ob, BMEditMesh *em, int layer, int *free) if (!em) { ClothModifierData *clmd = (ClothModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Cloth); - KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), - clmd->sim_parms->shapekey_rest); + if (clmd) { + KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), + clmd->sim_parms->shapekey_rest); - if (kb && kb->data) { - return (float(*)[3])kb->data; + if (kb && kb->data) { + return (float(*)[3])kb->data; + } } } diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 3f3aa0386e2..a7e36b09516 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -656,7 +656,9 @@ bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name) BLI_strncpy(chan->name, name, sizeof(chan->name)); - chan->custom_scale = 1.0f; + copy_v3_fl(chan->custom_scale_xyz, 1.0f); + zero_v3(chan->custom_translation); + zero_v3(chan->custom_rotation_euler); /* init vars to prevent math errors */ unit_qt(chan->quat); @@ -1235,8 +1237,10 @@ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_f if (pchan->custom) { id_us_plus(&pchan->custom->id); } + copy_v3_v3(pchan->custom_scale_xyz, pchan_from->custom_scale_xyz); + copy_v3_v3(pchan->custom_translation, pchan_from->custom_translation); + copy_v3_v3(pchan->custom_rotation_euler, pchan_from->custom_rotation_euler); - pchan->custom_scale = pchan_from->custom_scale; pchan->drawflag = pchan_from->drawflag; } diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 68de3e93a8e..44b760aefc8 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -947,7 +947,7 @@ static bool nlastrips_path_rename_fix(ID *owner_id, owner_id, prefix, oldName, newName, oldKey, newKey, &strip->act->curves, verify_paths); } /* Ignore own F-Curves, since those are local. */ - /* Check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ is_changed |= nlastrips_path_rename_fix( owner_id, prefix, oldName, newName, oldKey, newKey, &strip->strips, verify_paths); } @@ -1177,7 +1177,7 @@ static bool nlastrips_path_remove_fix(const char *prefix, ListBase *strips) any_removed |= fcurves_path_remove_fix(prefix, &strip->act->curves); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ any_removed |= nlastrips_path_remove_fix(prefix, &strip->strips); } return any_removed; @@ -1245,7 +1245,7 @@ static void nlastrips_apply_all_curves_cb(ID *id, ListBase *strips, AllFCurvesCb fcurves_apply_cb(id, &strip->act->curves, wrapper->func, wrapper->user_data); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ nlastrips_apply_all_curves_cb(id, &strip->strips, wrapper); } } diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 6f4af6f655d..e347306e0ae 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -1040,6 +1040,7 @@ static NlaEvalChannelSnapshot *nlaevalchan_snapshot_new(NlaEvalChannel *nec) nec_snapshot->channel = nec; nec_snapshot->length = length; nlavalidmask_init(&nec_snapshot->blend_domain, length); + nlavalidmask_init(&nec_snapshot->remap_domain, length); return nec_snapshot; } @@ -1050,6 +1051,7 @@ static void nlaevalchan_snapshot_free(NlaEvalChannelSnapshot *nec_snapshot) BLI_assert(!nec_snapshot->is_base); nlavalidmask_free(&nec_snapshot->blend_domain); + nlavalidmask_free(&nec_snapshot->remap_domain); MEM_freeN(nec_snapshot); } @@ -1650,6 +1652,363 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v } /* ---------------------- */ + +/* Assert necs and necs->channel is nonNull. */ +static void nlaevalchan_assert_nonNull(NlaEvalChannelSnapshot *necs) +{ + UNUSED_VARS_NDEBUG(necs); + BLI_assert(necs != NULL && necs->channel != NULL); +} + +/* Assert that the channels given can be blended or combined together. */ +static void nlaevalchan_assert_blendOrcombine_compatible(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + NlaEvalChannelSnapshot *blended_necs) +{ + UNUSED_VARS_NDEBUG(lower_necs, upper_necs, blended_necs); + BLI_assert(!ELEM(NULL, lower_necs, blended_necs)); + BLI_assert(upper_necs == NULL || lower_necs->length == upper_necs->length); + BLI_assert(lower_necs->length == blended_necs->length); +} + +/* Assert that the channels given can be blended or combined together as a quaternion. */ +static void nlaevalchan_assert_blendOrcombine_compatible_quaternion( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + NlaEvalChannelSnapshot *blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, blended_necs); + BLI_assert(lower_necs->length == 4); +} + +static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelSnapshot *src) +{ + memcpy(dst->values, src->values, src->length * sizeof(float)); +} + +/** + * Copies lower necs to blended necs if upper necs is NULL or has zero influence. + * \return true if copied. + */ +static bool nlaevalchan_blendOrcombine_try_copy_lower(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + const bool has_influence = !IS_EQF(upper_influence, 0.0f); + if (upper_necs != NULL && has_influence) { + return false; + } + + nlaevalchan_copy_values(r_blended_necs, lower_necs); + return true; +} + +/** + * Based on blend-mode, blend lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly from lower. + */ +static void nlaevalchan_blend_value(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + const int length = lower_necs->length; + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { + r_blended_necs->values[j] = lower_necs->values[j]; + continue; + } + + r_blended_necs->values[j] = nla_blend_value( + upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence); + } +} + +/** + * Based on mix-mode, provided by one the necs, + * combines lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly from lower. + */ +static void nlaevalchan_combine_value(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + /* Assumes every base is the same. */ + float *base_values = lower_necs->channel->base_snapshot.values; + const int length = lower_necs->length; + const char mix_mode = lower_necs->channel->mix_mode; + + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { + r_blended_necs->values[j] = lower_necs->values[j]; + continue; + } + + r_blended_necs->values[j] = nla_combine_value( + mix_mode, base_values[j], lower_necs->values[j], upper_necs->values[j], upper_influence); + } +} + +/** + * Quaternion combines lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly + * from lower. + */ +static void nlaevalchan_combine_quaternion(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + /** No need to check per index. We limit to all or nothing combining for quaternions. */ + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) { + nlaevalchan_copy_values(r_blended_necs, lower_necs); + return; + } + + nla_combine_quaternion( + lower_necs->values, upper_necs->values, upper_influence, r_blended_necs->values); +} + +/** + * Based on blend-mode and mix-mode, blend lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly + * from lower. + * + * \param lower_necs: Never NULL. + * \param upper_necs: Can be NULL. + * \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode. + * \param upper_influence: Value in range [0, 1]. + * \param upper_necs: Never NULL. + * + */ +static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_nonNull(r_blended_necs); + + switch (upper_blendmode) { + case NLASTRIP_MODE_COMBINE: { + switch (r_blended_necs->channel->mix_mode) { + case NEC_MIX_QUATERNION: { + nlaevalchan_combine_quaternion(lower_necs, upper_necs, upper_influence, r_blended_necs); + return; + } + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + case NEC_MIX_MULTIPLY: { + nlaevalchan_combine_value(lower_necs, upper_necs, upper_influence, r_blended_necs); + return; + } + default: + BLI_assert("Mix mode should've been handled"); + } + return; + } + case NLASTRIP_MODE_ADD: + case NLASTRIP_MODE_SUBTRACT: + case NLASTRIP_MODE_MULTIPLY: + case NLASTRIP_MODE_REPLACE: { + nlaevalchan_blend_value( + lower_necs, upper_necs, upper_blendmode, upper_influence, r_blended_necs); + return; + } + default: + BLI_assert("Blend mode should've been handled"); + } +} + +/** + * Based on blend-mode, solve for the upper values such that when lower blended with upper then we + * get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + */ +static void nlaevalchan_blend_value_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs); + + const int length = lower_necs->length; + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j); + continue; + } + + const bool success = nla_blend_get_inverted_strip_value(upper_blendmode, + lower_necs->values[j], + blended_necs->values[j], + upper_influence, + &r_upper_necs->values[j]); + BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success); + } +} + +/** + * Based on mix-mode, solve for the upper values such that when lower combined with upper then we + * get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + */ +static void nlaevalchan_combine_value_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs); + + /* Assumes every channel's base is the same. */ + float *base_values = lower_necs->channel->base_snapshot.values; + const int length = lower_necs->length; + const char mix_mode = lower_necs->channel->mix_mode; + + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j); + continue; + } + + const bool success = nla_combine_get_inverted_strip_value(mix_mode, + base_values[j], + lower_necs->values[j], + blended_necs->values[j], + upper_influence, + &r_upper_necs->values[j]); + + BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success); + } +} + +/** + * Solve for the upper values such that when lower quaternion combined with upper then we get + * blended values as a result. + * + * All blended values must be in the remap domain. If successfully remapped, then all upper values + * are placed in the remap domain so caller knows the result is usable. + */ +static void nlaevalchan_combine_quaternion_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, r_upper_necs, blended_necs); + + /* Must check each domain index individually in case animator had a non-combine NLA strip with a + * subset of quaternion channels and remapping through any of them failed and thus potentially + * has undefined values. */ + for (int j = 0; j < 4; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, 4); + return; + } + } + + const bool success = nla_combine_quaternion_get_inverted_strip_values( + lower_necs->values, blended_necs->values, upper_influence, r_upper_necs->values); + + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, success, 4); +} + +/** + * Based on blend-mode and mix mode, solve for the upper values such that when lower blended or + * combined with upper then we get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + * + * \param lower_necs: Never NULL. + * \param blended_necs: Never NULL. + * \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode. + * \param upper_influence: Value in range [0, 1]. + * \param r_upper_necs: Never NULL. + */ +static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + + if (IS_EQF(upper_influence, 0.0f)) { + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, r_upper_necs->length); + return; + } + + switch (upper_blendmode) { + case NLASTRIP_MODE_COMBINE: { + switch (r_upper_necs->channel->mix_mode) { + case NEC_MIX_QUATERNION: { + nlaevalchan_combine_quaternion_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_influence, r_upper_necs); + return; + } + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + case NEC_MIX_MULTIPLY: { + nlaevalchan_combine_value_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_influence, r_upper_necs); + return; + } + default: + BLI_assert("Mix mode should've been handled"); + } + return; + } + case NLASTRIP_MODE_ADD: + case NLASTRIP_MODE_SUBTRACT: + case NLASTRIP_MODE_MULTIPLY: + case NLASTRIP_MODE_REPLACE: { + nlaevalchan_blend_value_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_blendmode, upper_influence, r_upper_necs); + return; + } + default: + BLI_assert("Blend mode should've been handled"); + } +} + +/* ---------------------- */ /* F-Modifier stack joining/separation utilities - * should we generalize these for BLI_listbase.h interface? */ @@ -2048,12 +2407,12 @@ static void nla_eval_domain_strips(PointerRNA *ptr, GSet *touched_actions) { LISTBASE_FOREACH (NlaStrip *, strip, strips) { - /* check strip's action */ + /* Check strip's action. */ if (strip->act) { nla_eval_domain_action(ptr, channels, strip->act, touched_actions); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ nla_eval_domain_strips(ptr, channels, &strip->strips, touched_actions); } } @@ -2500,9 +2859,9 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh * Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according * to the given \a upper_blendmode and \a upper_influence. * - * For \a upper_snapshot, blending limited to values in the \a blend_domain. For Replace blendmode, - * this allows the upper snapshot to have a location XYZ channel where only a subset of values are - * blended. + * For \a upper_snapshot, blending limited to values in the \a blend_domain. + * For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel + * where only a subset of values are blended. */ void nlasnapshot_blend(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, @@ -2513,11 +2872,7 @@ void nlasnapshot_blend(NlaEvalData *eval_data, { nlaeval_snapshot_ensure_size(r_blended_snapshot, eval_data->num_channels); - const bool zero_upper_influence = IS_EQF(upper_influence, 0.0f); - LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) { - const int length = nec->base_snapshot.length; - NlaEvalChannelSnapshot *upper_necs = nlaeval_snapshot_get(upper_snapshot, nec->index); NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index); if (upper_necs == NULL && lower_necs == NULL) { @@ -2530,49 +2885,44 @@ void nlasnapshot_blend(NlaEvalData *eval_data, } NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_blended_snapshot, nec); + nlaevalchan_blendOrcombine( + lower_necs, upper_necs, upper_blendmode, upper_influence, result_necs); + } +} - /** Always copy \a lower_snapshot to result, irrelevant of whether \a upper_snapshot has a - * corresponding channel. This only matters when \a lower_snapshot not the same as - * \a r_blended_snapshot. */ - memcpy(result_necs->values, lower_necs->values, length * sizeof(float)); - if (upper_necs == NULL || zero_upper_influence) { +/** + * Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot. + * + * Only channels that exist within \a blended_snapshot are inverted. + * + * For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions. + * Only values within the \a remap_domain are processed. + */ +void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, + NlaEvalSnapshot *lower_snapshot, + NlaEvalSnapshot *blended_snapshot, + const short upper_blendmode, + const float upper_influence, + NlaEvalSnapshot *r_upper_snapshot) +{ + nlaeval_snapshot_ensure_size(r_upper_snapshot, eval_data->num_channels); + + LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) { + NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_get(blended_snapshot, nec->index); + if (blended_necs == NULL) { + /** We assume the caller only wants a subset of channels to be inverted, those that exist + * within \a blended_snapshot. */ continue; } - if (upper_blendmode == NLASTRIP_MODE_COMBINE) { - const int mix_mode = nec->mix_mode; - if (mix_mode == NEC_MIX_QUATERNION) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) { - continue; - } - - nla_combine_quaternion( - lower_necs->values, upper_necs->values, upper_influence, result_necs->values); - } - else { - for (int j = 0; j < length; j++) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { - continue; - } - - result_necs->values[j] = nla_combine_value(mix_mode, - nec->base_snapshot.values[j], - lower_necs->values[j], - upper_necs->values[j], - upper_influence); - } - } + NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index); + if (lower_necs == NULL) { + lower_necs = nlaeval_snapshot_find_channel(lower_snapshot->base, nec); } - else { - for (int j = 0; j < length; j++) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { - continue; - } - result_necs->values[j] = nla_blend_value( - upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence); - } - } + NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_upper_snapshot, nec); + nlaevalchan_blendOrcombine_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_blendmode, upper_influence, result_necs); } } @@ -2670,74 +3020,64 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, return false; } - /* Find the evaluation channel for the NLA stack below current strip. */ + /** Create \a blended_snapshot and fill with input \a values. */ + NlaEvalData *eval_data = &context->lower_eval_data; + NlaEvalSnapshot blended_snapshot; + nlaeval_snapshot_init(&blended_snapshot, eval_data, NULL); + NlaEvalChannelKey key = { .ptr = *prop_ptr, .prop = prop, }; - /** - * Remove lower NLA stack effects. - * - * Using the tweak strip's blended result and the lower snapshot value, we can solve for the - * tweak strip value it must evaluate to. - */ - NlaEvalData *const lower_eval_data = &context->lower_eval_data; - NlaEvalChannel *const lower_nec = nlaevalchan_verify_key(lower_eval_data, NULL, &key); - if ((lower_nec->base_snapshot.length != count)) { + NlaEvalChannel *nec = nlaevalchan_verify_key(eval_data, NULL, &key); + BLI_assert(nec); + if (nec->base_snapshot.length != count) { BLI_assert(!"invalid value count"); + nlaeval_snapshot_free_data(&blended_snapshot); return false; } - /* Invert the blending operation to compute the desired strip values. */ - NlaEvalChannelSnapshot *const lower_nec_snapshot = nlaeval_snapshot_find_channel( - &lower_eval_data->eval_snapshot, lower_nec); + NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_ensure_channel(&blended_snapshot, nec); + memcpy(blended_necs->values, values, sizeof(float) * count); + BLI_bitmap_set_all(blended_necs->remap_domain.ptr, true, count); - float *lower_values = lower_nec_snapshot->values; + /** Remove lower NLA stack effects. */ + nlasnapshot_blend_get_inverted_upper_snapshot(eval_data, + &context->lower_eval_data.eval_snapshot, + &blended_snapshot, + blend_mode, + influence, + &blended_snapshot); - if (blend_mode == NLASTRIP_MODE_COMBINE) { - /* Quaternion combine handles all sub-channels as a unit. */ - if (lower_nec->mix_mode == NEC_MIX_QUATERNION) { - if (r_force_all == NULL) { - return false; - } + /** Write results into \a values. */ + bool successful_remap = true; + if (blended_necs->channel->mix_mode == NEC_MIX_QUATERNION && + blend_mode == NLASTRIP_MODE_COMBINE) { + if (r_force_all != NULL) { *r_force_all = true; - - if (!nla_combine_quaternion_get_inverted_strip_values( - lower_values, values, influence, values)) { - return false; - } + index = -1; } else { - float *base_values = lower_nec->base_snapshot.values; - - for (int i = 0; i < count; i++) { - if (ELEM(index, i, -1)) { - if (!nla_combine_get_inverted_strip_value(lower_nec->mix_mode, - base_values[i], - lower_values[i], - values[i], - influence, - &values[i])) { - return false; - } - } - } + successful_remap = false; } } - else { - for (int i = 0; i < count; i++) { - if (ELEM(index, i, -1)) { - if (!nla_blend_get_inverted_strip_value( - blend_mode, lower_values[i], values[i], influence, &values[i])) { - return false; - } - } + + for (int i = 0; i < count; i++) { + if (!ELEM(index, i, -1)) { + continue; + } + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, i)) { + successful_remap = false; } + + values[i] = blended_necs->values[i]; } - return true; + nlaeval_snapshot_free_data(&blended_snapshot); + + return successful_remap; } /** diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 80992cff34d..f67c2cb4372 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2881,7 +2881,8 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden NULL; if (bb_custom) { float mat[4][4], smat[4][4]; - scale_m4_fl(smat, PCHAN_CUSTOM_DRAW_SIZE(pchan)); + scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan)); + mul_m4_v3(smat, pchan->custom_scale_xyz); mul_m4_series(mat, ob->obmat, pchan_tx->pose_mat, smat); BKE_boundbox_minmax(bb_custom, mat, r_min, r_max); } diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 94c2755d4c6..4504f10967c 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -340,13 +340,22 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, float isect_1[3], isect_2[3]; /* Calculate the intersection point. */ - isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); + int ret = isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); - /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the - * intersection point we want. And it will always intersect as we go from inside to outside - * of the sphere. - */ - copy_v3_v3(r_tail_pos, isect_1); + if (ret > 0) { + /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the + * intersection point we want. And it will always intersect as we go from inside to outside + * of the sphere. + */ + copy_v3_v3(r_tail_pos, isect_1); + } + else { + /* Couldn't find an intersection point. This means that the floating point + * values are too small and thus the intersection check fails. + * So assume that the distance is so small that tail_pos == head_pos. + */ + copy_v3_v3(r_tail_pos, head_pos); + } cur_seg_idx = bp_idx - 2; float prev_seg_len = 0; @@ -360,7 +369,7 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, } /* Convert the point back into the 0-1 interpolation range. */ - const float isect_seg_len = len_v3v3(prev_bp->vec, isect_1); + const float isect_seg_len = len_v3v3(prev_bp->vec, r_tail_pos); const float frac = isect_seg_len / len_v3v3(prev_bp->vec, bp->vec); *r_new_curve_pos = (prev_seg_len + isect_seg_len) / spline_len; @@ -380,7 +389,7 @@ static void splineik_evaluate_bone( { bSplineIKConstraint *ik_data = tree->ik_data; - if (pchan->bone->length == 0.0f) { + if (pchan->bone->length < FLT_EPSILON) { /* Only move the bone position with zero length bones. */ float bone_pos[4], dir[3], rad; BKE_where_on_path(ik_data->tar, state->curve_position, bone_pos, dir, NULL, &rad, NULL); @@ -516,6 +525,25 @@ static void splineik_evaluate_bone( */ cross_v3_v3v3(raxis, rmat[1], spline_vec); + /* Check if the old and new bone direction is parallel to each other. + * If they are, then 'raxis' should be near zero and we will have to get the rotation axis in + * some other way. + */ + float norm = normalize_v3(raxis); + + if (norm < FLT_EPSILON) { + /* Can't use cross product! */ + int order[3] = {0, 1, 2}; + float tmp_axis[3]; + zero_v3(tmp_axis); + + axis_sort_v3(spline_vec, order); + + /* Use the second largest axis as the basis for the rotation axis. */ + tmp_axis[order[1]] = 1.0f; + cross_v3_v3v3(raxis, tmp_axis, spline_vec); + } + rangle = dot_v3v3(rmat[1], spline_vec); CLAMP(rangle, -1.0f, 1.0f); rangle = acosf(rangle); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index c24630c5666..62833e10438 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -45,6 +45,7 @@ using blender::Set; using blender::StringRef; using blender::StringRefNull; using blender::fn::GMutableSpan; +using blender::fn::GSpan; namespace blender::bke { @@ -590,6 +591,105 @@ void NamedLegacyCustomDataProvider::foreach_domain( callback(domain_); } +CustomDataAttributes::CustomDataAttributes() +{ + CustomData_reset(&data); + size_ = 0; +} + +CustomDataAttributes::~CustomDataAttributes() +{ + CustomData_free(&data, size_); +} + +CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other) +{ + size_ = other.size_; + CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, size_); +} + +CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other) +{ + size_ = other.size_; + data = other.data; + CustomData_reset(&other.data); +} + +std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const +{ + BLI_assert(size_ != 0); + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +{ + BLI_assert(size_ != 0); + for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GMutableSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::create_by_move(const blender::StringRef name, + const CustomDataType data_type, + void *buffer) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::remove(const blender::StringRef name) +{ + bool result = false; + for (const int i : IndexRange(data.totlayer)) { + const CustomDataLayer &layer = data.layers[i]; + if (layer.name == name) { + CustomData_free_layer(&data, layer.type, size_, i); + result = true; + } + } + return result; +} + +void CustomDataAttributes::reallocate(const int size) +{ + size_ = size; + CustomData_realloc(&data, size); +} + +bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback, + const AttributeDomain domain) const +{ + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; + if (!callback(layer.name, meta_data)) { + return false; + } + } + return true; +} + } // namespace blender::bke /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index a61e7a2d1d8..54fd3f55c31 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -399,7 +399,8 @@ static void setup_app_data(bContext *C, BKE_lib_override_library_main_resync( bmain, curscene, - bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene)); + bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene), + reports); /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ BKE_lib_override_library_main_operations_create(bmain, true); } diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 4c63c7f05ee..9cafe1124b1 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -16,31 +16,50 @@ #include "BLI_array.hh" #include "BLI_listbase.h" +#include "BLI_map.hh" #include "BLI_span.hh" +#include "BLI_string_ref.hh" #include "DNA_curve_types.h" #include "BKE_curve.h" #include "BKE_spline.hh" +using blender::Array; using blender::float3; using blender::float4x4; +using blender::Map; using blender::Span; +using blender::StringRefNull; -CurveEval *CurveEval::copy() +blender::Span<SplinePtr> CurveEval::splines() const { - CurveEval *new_curve = new CurveEval(); + return splines_; +} - for (SplinePtr &spline : this->splines) { - new_curve->splines.append(spline->copy()); - } +blender::MutableSpan<SplinePtr> CurveEval::splines() +{ + return splines_; +} - return new_curve; +/** + * \warning Call #reallocate on the spline's attributes after adding all splines. + */ +void CurveEval::add_spline(SplinePtr spline) +{ + splines_.append(std::move(spline)); +} + +void CurveEval::remove_splines(blender::IndexMask mask) +{ + for (int i = mask.size() - 1; i >= 0; i--) { + splines_.remove_and_reorder(mask.indices()[i]); + } } void CurveEval::translate(const float3 &translation) { - for (SplinePtr &spline : this->splines) { + for (SplinePtr &spline : this->splines()) { spline->translate(translation); spline->mark_cache_invalid(); } @@ -48,18 +67,52 @@ void CurveEval::translate(const float3 &translation) void CurveEval::transform(const float4x4 &matrix) { - for (SplinePtr &spline : this->splines) { + for (SplinePtr &spline : this->splines()) { spline->transform(matrix); } } void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const { - for (const SplinePtr &spline : this->splines) { + for (const SplinePtr &spline : this->splines()) { spline->bounds_min_max(min, max, use_evaluated); } } +/** + * Return the start indices for each of the curve spline's evaluated points, as if they were part + * of a flattened array. This can be used to facilitate parallelism by avoiding the need to + * accumulate an offset while doing more complex calculations. + * + * \note The result array is one longer than the spline count; the last element is the total size. + */ +blender::Array<int> CurveEval::control_point_offsets() const +{ + Array<int> offsets(splines_.size() + 1); + int offset = 0; + for (const int i : splines_.index_range()) { + offsets[i] = offset; + offset += splines_[i]->size(); + } + offsets.last() = offset; + return offsets; +} + +/** + * Exactly like #control_point_offsets, but uses the number of evaluated points instead. + */ +blender::Array<int> CurveEval::evaluated_point_offsets() const +{ + Array<int> offsets(splines_.size() + 1); + int offset = 0; + for (const int i : splines_.index_range()) { + offsets[i] = offset; + offset += splines_[i]->evaluated_points_size(); + } + offsets.last() = offset; + return offsets; +} + static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type) { switch (dna_handle_type) { @@ -115,8 +168,6 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve)); - curve->splines.reserve(BLI_listbase_count(nurbs)); - /* TODO: Optimize by reserving the correct points size. */ LISTBASE_FOREACH (const Nurb *, nurb, nurbs) { switch (nurb->type) { @@ -134,8 +185,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) bezt.radius, bezt.tilt); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } case CU_NURBS: { @@ -148,8 +199,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } case CU_POLY: { @@ -159,8 +210,8 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { spline->add_point(bp.vec, bp.radius, bp.tilt); } - - curve->splines.append(std::move(spline)); + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); break; } default: { @@ -170,15 +221,52 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) } } + /* Though the curve has no attributes, this is necessary to properly set the custom data size. */ + curve->attributes.reallocate(curve->splines().size()); + /* Note: Normal mode is stored separately in each spline to facilitate combining splines * from multiple curve objects, where the value may be different. */ const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve( dna_curve.twist_mode); - for (SplinePtr &spline : curve->splines) { + for (SplinePtr &spline : curve->splines()) { spline->normal_mode = normal_mode; } return curve; } -/** \} */ +/** + * Check the invariants that curve control point attributes should always uphold, necessary + * because attributes are stored on splines rather than in a flat array on the curve: + * - The same set of attributes exists on every spline. + * - Attributes with the same name have the same type on every spline. + */ +void CurveEval::assert_valid_point_attributes() const +{ +#ifdef DEBUG + if (splines_.size() == 0) { + return; + } + const int layer_len = splines_.first()->attributes.data.totlayer; + Map<StringRefNull, AttributeMetaData> map; + for (const SplinePtr &spline : splines_) { + BLI_assert(spline->attributes.data.totlayer == layer_len); + spline->attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + map.add_or_modify( + name, + [&](AttributeMetaData *map_data) { + /* All unique attribute names should be added on the first spline. */ + BLI_assert(spline == splines_.first()); + *map_data = meta_data; + }, + [&](AttributeMetaData *map_data) { + /* Attributes on different splines should all have the same type. */ + BLI_assert(meta_data == *map_data); + }); + return true; + }, + ATTR_DOMAIN_POINT); + } +#endif +}
\ No newline at end of file diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 4a25b0e9d98..42af3a391ed 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -1702,7 +1702,7 @@ static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface } for (int i = 0; i < totloop; i++) { - rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[mloop[i].v].r); + rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[i].r); } } else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) { diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 0490d577b88..d08681da6ec 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -22,6 +22,12 @@ #include "attribute_access_intern.hh" +using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_GSpan; +using blender::fn::GVMutableArray_For_GMutableSpan; + /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation * \{ */ @@ -39,7 +45,7 @@ GeometryComponent *CurveComponent::copy() const { CurveComponent *new_component = new CurveComponent(); if (curve_ != nullptr) { - new_component->curve_ = curve_->copy(); + new_component->curve_ = new CurveEval(*curve_); new_component->ownership_ = GeometryOwnershipType::Owned; } return new_component; @@ -87,7 +93,7 @@ CurveEval *CurveComponent::get_for_write() { BLI_assert(this->is_mutable()); if (ownership_ == GeometryOwnershipType::ReadOnly) { - curve_ = curve_->copy(); + curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } return curve_; @@ -107,7 +113,7 @@ void CurveComponent::ensure_owns_direct_data() { BLI_assert(this->is_mutable()); if (ownership_ != GeometryOwnershipType::Owned) { - curve_ = curve_->copy(); + curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } } @@ -115,7 +121,7 @@ void CurveComponent::ensure_owns_direct_data() /** \} */ /* -------------------------------------------------------------------- */ -/** \name Attribute Access +/** \name Attribute Access Helper Functions * \{ */ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const @@ -125,23 +131,44 @@ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const } if (domain == ATTR_DOMAIN_POINT) { int total = 0; - for (const SplinePtr &spline : curve_->splines) { + for (const SplinePtr &spline : curve_->splines()) { total += spline->size(); } return total; } if (domain == ATTR_DOMAIN_CURVE) { - return curve_->splines.size(); + return curve_->splines().size(); } return 0; } +static CurveEval *get_curve_from_component_for_write(GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + CurveComponent &curve_component = static_cast<CurveComponent &>(component); + return curve_component.get_for_write(); +} + +static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return curve_component.get_for_read(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Builtin Spline Attributes + * + * Attributes with a value for every spline, stored contiguously or in every spline separately. + * \{ */ + namespace blender::bke { class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); - using UpdateOnWrite = void (*)(Spline &spline); const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; @@ -164,12 +191,10 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { GVArrayPtr try_get_for_read(const GeometryComponent &component) const final { - const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); - const CurveEval *curve = curve_component.get_for_read(); + const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { return {}; } - return as_read_attribute_(*curve); } @@ -178,12 +203,10 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { if (writable_ != Writable) { return {}; } - CurveComponent &curve_component = static_cast<CurveComponent &>(component); - CurveEval *curve = curve_component.get_for_write(); + CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return {}; } - return as_write_attribute_(*curve); } @@ -228,7 +251,7 @@ static void set_spline_resolution(SplinePtr &spline, const int resolution) static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) { return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>( - curve.splines.as_span()); + curve.splines()); } static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) @@ -237,7 +260,7 @@ static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) int, get_spline_resolution, set_spline_resolution>>( - curve.splines.as_mutable_span()); + curve.splines()); } static bool get_cyclic_value(const SplinePtr &spline) @@ -256,19 +279,683 @@ static void set_cyclic_value(SplinePtr &spline, const bool value) static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) { return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>( - curve.splines.as_span()); + curve.splines()); } static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) { return std::make_unique< fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>( - curve.splines.as_mutable_span()); + curve.splines()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Builtin Control Point Attributes + * + * Attributes with a value for every control point. Most of the complexity here is due to the fact + * that we must provide access to the attribute data as if it was a contiguous array when it is + * really stored separately on each spline. That will be inherently rather slow, but these virtual + * array implementations try to make it workable in common situations. + * \{ */ + +namespace { +struct PointIndices { + int spline_index; + int point_index; +}; +} // namespace +static PointIndices lookup_point_indices(Span<int> offsets, const int index) +{ + const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) - + offsets.begin() - 1; + const int index_in_spline = index - offsets[spline_index]; + return {spline_index, index_in_spline}; +} + +template<typename T> +static void point_attribute_materialize(Span<Span<T>> data, + Span<int> offsets, + const IndexMask mask, + MutableSpan<T> r_span) +{ + const int total_size = offsets.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : data.index_range()) { + const int offset = offsets[spline_index]; + const int next_offset = offsets[spline_index + 1]; + initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset); + } + } + else { + int spline_index = 0; + for (const int i : r_span.index_range()) { + const int dst_index = mask[i]; + + while (offsets[spline_index] < dst_index) { + spline_index++; + } + + const int index_in_spline = dst_index - offsets[spline_index]; + r_span[dst_index] = data[spline_index][index_in_spline]; + } + } } +template<typename T> +static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, + Span<int> offsets, + const IndexMask mask, + MutableSpan<T> r_span) +{ + T *dst = r_span.data(); + const int total_size = offsets.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : data.index_range()) { + const int offset = offsets[spline_index]; + const int next_offset = offsets[spline_index + 1]; + uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset); + } + } + else { + int spline_index = 0; + for (const int i : r_span.index_range()) { + const int dst_index = mask[i]; + + while (offsets[spline_index] < dst_index) { + spline_index++; + } + + const int index_in_spline = dst_index - offsets[spline_index]; + new (dst + dst_index) T(data[spline_index][index_in_spline]); + } + } +} + +/** + * Virtual array for any control point data accessed with spans and an offset array. + */ +template<typename T> class VArray_For_SplinePoints : public VArray<T> { + private: + const Array<Span<T>> data_; + Array<int> offsets_; + + public: + VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets) + : VArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return data_[indices.spline_index][indices.point_index]; + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize(data_.as_span(), offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span); + } +}; + +/** + * Mutable virtual array for any control point data accessed with spans and an offset array. + */ +template<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> { + private: + Array<MutableSpan<T>> data_; + Array<int> offsets_; + + public: + VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets) + : VMutableArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return data_[indices.spline_index][indices.point_index]; + } + + void set_impl(const int64_t index, T value) final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + data_[indices.spline_index][indices.point_index] = value; + } + + void set_all_impl(Span<T> src) final + { + for (const int spline_index : data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offsets = offsets_[spline_index + 1]; + data_[spline_index].copy_from(src.slice(offset, next_offsets - offset)); + } + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize_to_uninitialized( + {(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); + } +}; + +template<typename T> GVArrayPtr point_data_gvarray(Array<Span<T>> spans, Array<int> offsets) +{ + return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +template<typename T> +GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> offsets) +{ + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +/** + * Virtual array implementation specifically for control point positions. This is only needed for + * Bezier splines, where adjusting the position also requires adjusting handle positions depending + * on handle types. We pay a small price for this when other spline types are mixed with Bezier. + * + * \note There is no need to check the handle type to avoid changing auto handles, since + * retrieving write access to the position data will mark them for recomputation anyway. + */ +class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { + private: + MutableSpan<SplinePtr> splines_; + Array<int> offsets_; + + public: + VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets) + : VMutableArray<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets)) + { + } + + float3 get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return splines_[indices.spline_index]->positions()[indices.point_index]; + } + + void set_impl(const int64_t index, float3 value) final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + Spline &spline = *splines_[indices.spline_index]; + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) { + const float3 delta = value - bezier_spline->positions()[indices.point_index]; + bezier_spline->handle_positions_left()[indices.point_index] += delta; + bezier_spline->handle_positions_right()[indices.point_index] += delta; + bezier_spline->positions()[indices.point_index] = value; + } + else { + spline.positions()[indices.point_index] = value; + } + } + + void set_all_impl(Span<float3> src) final + { + for (const int spline_index : splines_.index_range()) { + Spline &spline = *splines_[spline_index]; + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) { + MutableSpan<float3> positions = bezier_spline->positions(); + MutableSpan<float3> handle_positions_left = bezier_spline->handle_positions_left(); + MutableSpan<float3> handle_positions_right = bezier_spline->handle_positions_right(); + for (const int i : IndexRange(next_offset - offset)) { + const float3 delta = src[offset + i] - positions[i]; + handle_positions_left[i] += delta; + handle_positions_right[i] += delta; + positions[i] = src[offset + i]; + } + } + else { + spline.positions().copy_from(src.slice(offset, next_offset - offset)); + } + } + } + + /** Utility so we can pass positions to the materialize functions above. */ + Array<Span<float3>> get_position_spans() const + { + Array<Span<float3>> spans(splines_.size()); + for (const int i : spans.index_range()) { + spans[i] = splines_[i]->positions(); + } + return spans; + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = this->get_position_spans(); + point_attribute_materialize(spans.as_span(), offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = this->get_position_spans(); + point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span); + } +}; + +/** + * Provider for any builtin control point attribute that doesn't need + * special handling like access to other arrays in the spline. + */ +template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttributeProvider { + protected: + using GetSpan = Span<T> (*)(const Spline &spline); + using GetMutableSpan = MutableSpan<T> (*)(Spline &spline); + using UpdateOnWrite = void (*)(Spline &spline); + const GetSpan get_span_; + const GetMutableSpan get_mutable_span_; + const UpdateOnWrite update_on_write_; + + public: + BuiltinPointAttributeProvider(std::string attribute_name, + const WritableEnum writable, + const GetSpan get_span, + const GetMutableSpan get_mutable_span, + const UpdateOnWrite update_on_write) + : BuiltinAttributeProvider(std::move(attribute_name), + ATTR_DOMAIN_POINT, + bke::cpp_type_to_custom_data_type(CPPType::get<T>()), + BuiltinAttributeProvider::NonCreatable, + writable, + BuiltinAttributeProvider::NonDeletable), + get_span_(get_span), + get_mutable_span_(get_mutable_span), + update_on_write_(update_on_write) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const override + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + if (splines.size() == 1) { + return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*splines.first())); + } + + Array<int> offsets = curve->control_point_offsets(); + Array<Span<T>> spans(splines.size()); + for (const int i : splines.index_range()) { + spans[i] = get_span_(*splines[i]); + } + + return point_data_gvarray(spans, offsets); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + if (splines.size() == 1) { + return std::make_unique<fn::GVMutableArray_For_GMutableSpan>( + get_mutable_span_(*splines.first())); + } + + Array<int> offsets = curve->control_point_offsets(); + Array<MutableSpan<T>> spans(splines.size()); + for (const int i : splines.index_range()) { + spans[i] = get_mutable_span_(*splines[i]); + if (update_on_write_) { + update_on_write_(*splines[i]); + } + } + + return point_data_gvarray(spans, offsets); + } + + bool try_delete(GeometryComponent &UNUSED(component)) const final + { + return false; + } + + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final + { + return false; + } + + bool exists(const GeometryComponent &component) const final + { + return component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0; + } +}; + +/** + * Special attribute provider for the position attribute. Keeping this separate means we don't + * need to make #BuiltinPointAttributeProvider overly generic, and the special handling for the + * positions is more clear. + */ +class PositionAttributeProvider final : public BuiltinPointAttributeProvider<float3> { + public: + PositionAttributeProvider() + : BuiltinPointAttributeProvider( + "position", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.positions(); }, + [](Spline &spline) { return spline.positions(); }, + [](Spline &spline) { spline.mark_cache_invalid(); }) + { + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + bool curve_has_bezier_spline = false; + for (SplinePtr &spline : curve->splines()) { + if (spline->type() == Spline::Type::Bezier) { + curve_has_bezier_spline = true; + break; + } + } + + /* Use the regular position virtual array when there aren't any Bezier splines + * to avoid the overhead of checking the spline type for every point. */ + if (!curve_has_bezier_spline) { + return BuiltinPointAttributeProvider<float3>::try_get_for_write(component); + } + + /* Changing the positions requires recalculation of cached evaluated data in many cases. + * This could set more specific flags in the future to avoid unnecessary recomputation. */ + for (SplinePtr &spline : curve->splines()) { + spline->mark_cache_invalid(); + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>( + offsets.last(), curve->splines(), std::move(offsets)); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dynamic Control Point Attributes + * + * The dynamic control point attribute implementation is very similar to the builtin attribute + * implementation-- it uses the same virtual array types. In order to work, this code depends on + * the fact that all a curve's splines will have the same attributes and they all have the same + * type. + * \{ */ + +class DynamicPointAttributeProvider final : public DynamicAttributesProvider { + private: + static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | + CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 | + CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL; + + public: + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + Vector<GSpan> spans; /* GSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVArray_For_GSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + ReadAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<Span<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + /* This function is almost the same as #try_get_for_read, but without const. */ + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVMutableArray_For_GMutableSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + WriteAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<MutableSpan<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return false; + } + + bool layer_freed = false; + for (SplinePtr &spline : curve->splines()) { + spline->attributes.remove(attribute_name); + } + return layer_freed; + } + + static GVArrayPtr varray_from_initializer(const AttributeInit &initializer, + const CustomDataType data_type, + const int total_size) + { + switch (initializer.type) { + case AttributeInit::Type::Default: + /* This function shouldn't be called in this case, since there + * is no need to copy anything to the new custom data array. */ + BLI_assert_unreachable(); + return {}; + case AttributeInit::Type::VArray: + return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy(); + case AttributeInit::Type::MoveArray: + return std::make_unique<fn::GVArray_For_GSpan>( + GSpan(*bke::custom_data_type_to_cpp_type(data_type), + static_cast<const AttributeInitMove &>(initializer).data, + total_size)); + } + BLI_assert_unreachable(); + return {}; + } + + bool try_create(GeometryComponent &component, + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const AttributeInit &initializer) const final + { + BLI_assert(this->type_is_supported(data_type)); + if (domain != ATTR_DOMAIN_POINT) { + return false; + } + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + + /* First check the one case that allows us to avoid copying the input data. */ + if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) { + MEM_freeN(source_data); + return false; + } + return true; + } + + /* Otherwise just create a custom data layer on each of the splines. */ + for (const int i : splines.index_range()) { + if (!splines[i]->attributes.create(attribute_name, data_type)) { + /* If attribute creation fails on one of the splines, we cannot leave the custom data + * layers in the previous splines around, so delete them before returning. However, + * this is not an expected case. */ + BLI_assert_unreachable(); + return false; + } + } + + /* With a default initializer type, we can keep the values at their initial values. */ + if (initializer.type == AttributeInit::Type::Default) { + return true; + } + + WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name); + /* We just created the attribute, it should exist. */ + BLI_assert(write_attribute); + + const int total_size = curve->control_point_offsets().last(); + GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, total_size); + /* TODO: When we can call a variant of #set_all with a virtual array argument, + * this theoretically unnecessary materialize step could be removed. */ + GVArray_GSpan source_varray_span{*source_varray}; + write_attribute.varray->set_all(source_varray_span.data()); + + if (initializer.type == AttributeInit::Type::MoveArray) { + MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data); + } + + return true; + } + + bool foreach_attribute(const GeometryComponent &component, + const AttributeForeachCallback callback) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + Span<SplinePtr> splines = curve->splines(); + + /* In a debug build, check that all corresponding custom data layers have the same type. */ + curve->assert_valid_point_attributes(); + + /* Use the first spline as a representative for all the others. */ + splines.first()->attributes.foreach_attribute(callback, ATTR_DOMAIN_POINT); + + return true; + } + + void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final + { + callback(ATTR_DOMAIN_POINT); + } + + bool type_is_supported(CustomDataType data_type) const + { + return ((1ULL << data_type) & supported_types_mask) != 0; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Attribute Provider Declaration + * \{ */ + /** - * In this function all the attribute providers for a curve component are created. Most data - * in this function is statically allocated, because it does not change over time. + * In this function all the attribute providers for a curve component are created. + * Most data in this function is statically allocated, because it does not change over time. */ static ComponentAttributeProviders create_attribute_providers_for_curve() { @@ -284,7 +971,40 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() make_cyclic_read_attribute, make_cyclic_write_attribute); - return ComponentAttributeProviders({&resolution, &cyclic}, {}); + static CustomDataAccessInfo spline_custom_data_access = { + [](GeometryComponent &component) -> CustomData * { + CurveEval *curve = get_curve_from_component_for_write(component); + return curve ? &curve->attributes.data : nullptr; + }, + [](const GeometryComponent &component) -> const CustomData * { + const CurveEval *curve = get_curve_from_component_for_read(component); + return curve ? &curve->attributes.data : nullptr; + }, + nullptr}; + + static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE, + spline_custom_data_access); + + static PositionAttributeProvider position; + + static BuiltinPointAttributeProvider<float> radius( + "radius", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.radii(); }, + [](Spline &spline) { return spline.radii(); }, + nullptr); + + static BuiltinPointAttributeProvider<float> tilt( + "tilt", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.tilts(); }, + [](Spline &spline) { return spline.tilts(); }, + [](Spline &spline) { spline.mark_cache_invalid(); }); + + static DynamicPointAttributeProvider point_custom_data; + + return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, + {&spline_custom_data, &point_custom_data}); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 55025ed5ac9..3b1b7456162 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -56,6 +56,19 @@ void InstancesComponent::reserve(int min_capacity) instance_ids_.reserve(min_capacity); } +/** + * Resize the transform, handles, and ID vectors to the specified capacity. + * + * \note This function should be used carefully, only when it's guaranteed + * that the data will be filled. + */ +void InstancesComponent::resize(int capacity) +{ + instance_reference_handles_.resize(capacity); + instance_transforms_.resize(capacity); + instance_ids_.resize(capacity); +} + void InstancesComponent::clear() { instance_reference_handles_.clear(); @@ -81,6 +94,11 @@ blender::Span<int> InstancesComponent::instance_reference_handles() const return instance_reference_handles_; } +blender::MutableSpan<int> InstancesComponent::instance_reference_handles() +{ + return instance_reference_handles_; +} + blender::MutableSpan<blender::float4x4> InstancesComponent::instance_transforms() { return instance_transforms_; diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index a792e268d5c..9abd00c2b4f 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -15,6 +15,7 @@ */ #include "BKE_geometry_set_instances.hh" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" @@ -361,6 +362,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou int64_t cd_dirty_poly = 0; int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; const int tot_transforms = set_group.transforms.size(); @@ -374,6 +377,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou cd_dirty_poly |= mesh.runtime.cd_dirty_poly; cd_dirty_edge |= mesh.runtime.cd_dirty_edge; cd_dirty_loop |= mesh.runtime.cd_dirty_loop; + for (const int slot_index : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[slot_index]; + materials.add(material); + } } if (convert_points_to_vertices && set.has_pointcloud()) { const PointCloud &pointcloud = *set.get_pointcloud_for_read(); @@ -396,6 +403,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou break; } } + for (const int i : IndexRange(materials.size())) { + Material *material = materials[i]; + BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); + } new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; @@ -409,6 +420,14 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou const GeometrySet &set = set_group.geometry_set; if (set.has_mesh()) { const Mesh &mesh = *set.get_mesh_for_read(); + + Array<int> material_index_map(mesh.totcol); + for (const int i : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[i]; + const int new_material_index = materials.index_of(material); + material_index_map[i] = new_material_index; + } + for (const float4x4 &transform : set_group.transforms) { for (const int i : IndexRange(mesh.totvert)) { const MVert &old_vert = mesh.mvert[i]; @@ -438,6 +457,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; new_poly = old_poly; new_poly.loopstart += loop_offset; + if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh.totcol) { + new_poly.mat_nr = material_index_map[new_poly.mat_nr]; + } + else { + /* The material index was invalid before. */ + new_poly.mat_nr = 0; + } } vert_offset += mesh.totvert; @@ -528,15 +554,26 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp } const CurveEval &source_curve = *set.get_curve_for_read(); - for (const SplinePtr &source_spline : source_curve.splines) { + for (const SplinePtr &source_spline : source_curve.splines()) { for (const float4x4 &transform : set_group.transforms) { SplinePtr new_spline = source_spline->copy(); new_spline->transform(transform); - new_curve->splines.append(std::move(new_spline)); + new_curve->add_spline(std::move(new_spline)); } } } + for (SplinePtr &spline : new_curve->splines()) { + /* Spline instances should have no custom attributes, since they always come + * from original objects which currently do not support custom attributes. + * + * This is only true as long as a #GeometrySet cannot be instanced directly. */ + BLI_assert(spline->attributes.data.totlayer == 0); + UNUSED_VARS_NDEBUG(spline); + } + + new_curve->attributes.reallocate(new_curve->splines().size()); + result.replace(new_curve); } diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 9c84d155330..421cb0ac4f1 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -653,9 +653,13 @@ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe) * \param gpd: Grease pencil data-block * \param name: Name of the layer * \param setactive: Set as active + * \param add_to_header: Used to force the layer added at header * \return Pointer to new layer */ -bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive) +bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, + const char *name, + const bool setactive, + const bool add_to_header) { bGPDlayer *gpl = NULL; bGPDlayer *gpl_active = NULL; @@ -671,14 +675,18 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setacti gpl_active = BKE_gpencil_layer_active_get(gpd); /* Add to data-block. */ - if (gpl_active == NULL) { - BLI_addtail(&gpd->layers, gpl); + if (add_to_header) { + BLI_addhead(&gpd->layers, gpl); } else { - /* if active layer, add after that layer */ - BLI_insertlinkafter(&gpd->layers, gpl_active, gpl); + if (gpl_active == NULL) { + BLI_addtail(&gpd->layers, gpl); + } + else { + /* if active layer, add after that layer */ + BLI_insertlinkafter(&gpd->layers, gpl_active, gpl); + } } - /* annotation vs GP Object behavior is slightly different */ if (gpd->flag & GP_DATA_ANNOTATIONS) { /* set default color of new strokes for this layer */ diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 88d3e917a7a..906d0fb0792 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -515,7 +515,7 @@ void BKE_gpencil_convert_curve(Main *bmain, if (collection != NULL) { gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true); + gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true, false); } } } @@ -523,7 +523,7 @@ void BKE_gpencil_convert_curve(Main *bmain, if (gpl == NULL) { gpl = BKE_gpencil_layer_active_get(gpd); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } } diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 04403e264a4..501ee0c2014 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -2439,7 +2439,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, /* Create Layer and Frame. */ bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name); if (gpl_fill == NULL) { - gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true); + gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false); } bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get( gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); @@ -2492,7 +2492,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, /* Create Layer and Frame. */ bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, element_name); if (gpl_stroke == NULL) { - gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true); + gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false); } bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get( gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index 6b164e6bc50..58715ac2e05 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -503,7 +503,7 @@ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_arraylen) { - LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &src->data.group) { + LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &dest->data.group) { const IDProperty *prop_src = IDP_GetPropertyFromGroup((IDProperty *)src, prop_dst->name); if (prop_src != NULL) { /* check of we should replace? */ diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 368b1c2e66b..2f7e2b41a73 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -735,6 +735,37 @@ int BKE_image_get_tile_from_pos(struct Image *ima, return tile_number; } +/** + * Return the tile_number for the closest UDIM tile. + */ +int BKE_image_find_nearest_tile(const Image *image, const float co[2]) +{ + const float co_floor[2] = {floorf(co[0]), floorf(co[1])}; + /* Distance to the closest UDIM tile. */ + float dist_best_sq = FLT_MAX; + int tile_number_best = -1; + + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + /* Coordinates of the current tile. */ + const float tile_index_co[2] = {tile_index % 10, tile_index / 10}; + + if (equals_v2v2(co_floor, tile_index_co)) { + return tile->tile_number; + } + + /* Distance between co[2] and UDIM tile. */ + const float dist_sq = len_squared_v2v2(tile_index_co, co); + + if (dist_sq < dist_best_sq) { + dist_best_sq = dist_sq; + tile_number_best = tile->tile_number; + } + } + + return tile_number_best; +} + static void image_init_color_management(Image *ima) { ImBuf *ibuf; diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 795a5ad9468..c2e5006cbc1 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -525,7 +525,13 @@ static int id_copy_libmanagement_cb(LibraryIDLinkCallbackData *cb_data) /* Increase used IDs refcount if needed and required. */ if ((data->flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && (cb_flag & IDWALK_CB_USER)) { - id_us_plus(id); + if ((data->flag & LIB_ID_CREATE_NO_MAIN) != 0) { + BLI_assert(cb_data->id_self->tag & LIB_TAG_NO_MAIN); + id_us_plus_no_lib(id); + } + else { + id_us_plus(id); + } } return IDWALK_RET_NOP; @@ -578,7 +584,7 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag) } } - /* Early output is source is NULL. */ + /* Early output if source is NULL. */ if (id == NULL) { return NULL; } @@ -1245,6 +1251,13 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori } BLI_assert(new_id != NULL); + if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) != 0) { + new_id->tag |= LIB_TAG_COPIED_ON_WRITE; + } + else { + new_id->tag &= ~LIB_TAG_COPIED_ON_WRITE; + } + const size_t id_len = BKE_libblock_get_alloc_info(GS(new_id->name), NULL); const size_t id_offset = sizeof(ID); if ((int)id_len - (int)id_offset > 0) { /* signed to allow neg result */ /* XXX ????? */ @@ -1348,12 +1361,12 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) BLI_remlink(lb, id); /* Check if we can actually insert id before or after id_sorting_hint, if given. */ - if (!ELEM(id_sorting_hint, NULL, id)) { + if (!ELEM(id_sorting_hint, NULL, id) && id_sorting_hint->lib == id->lib) { BLI_assert(BLI_findindex(lb, id_sorting_hint) >= 0); ID *id_sorting_hint_next = id_sorting_hint->next; if (BLI_strcasecmp(id_sorting_hint->name, id->name) < 0 && - (id_sorting_hint_next == NULL || + (id_sorting_hint_next == NULL || id_sorting_hint_next->lib != id->lib || BLI_strcasecmp(id_sorting_hint_next->name, id->name) > 0)) { BLI_insertlinkafter(lb, id_sorting_hint, id); return; @@ -1361,7 +1374,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) ID *id_sorting_hint_prev = id_sorting_hint->prev; if (BLI_strcasecmp(id_sorting_hint->name, id->name) > 0 && - (id_sorting_hint_prev == NULL || + (id_sorting_hint_prev == NULL || id_sorting_hint_prev->lib != id->lib || BLI_strcasecmp(id_sorting_hint_prev->name, id->name) < 0)) { BLI_insertlinkbefore(lb, id_sorting_hint, id); return; @@ -1376,16 +1389,33 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) /* Note: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at * once using the same base name), newly inserted items will generally be towards the end * (higher extension numbers). */ - for (idtest = lb->last, item_array_index = ID_SORT_STEP_SIZE - 1; idtest != NULL; - idtest = idtest->prev, item_array_index--) { + bool is_in_library = false; + item_array_index = ID_SORT_STEP_SIZE - 1; + for (idtest = lb->last; idtest != NULL; idtest = idtest->prev) { + if (is_in_library) { + if (idtest->lib != id->lib) { + /* We got out of expected library 'range' in the list, so we are done here and can move on + * to the next step. */ + break; + } + } + else if (idtest->lib == id->lib) { + /* We are entering the expected library 'range' of IDs in the list. */ + is_in_library = true; + } + + if (!is_in_library) { + continue; + } + item_array[item_array_index] = idtest; if (item_array_index == 0) { - if ((idtest->lib == NULL && id->lib != NULL) || - BLI_strcasecmp(idtest->name, id->name) <= 0) { + if (BLI_strcasecmp(idtest->name, id->name) <= 0) { break; } item_array_index = ID_SORT_STEP_SIZE; } + item_array_index--; } /* Step two: we go forward in the selected chunk of items and check all of them, as we know @@ -1397,7 +1427,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) * So we can increment that index in any case. */ for (item_array_index++; item_array_index < ID_SORT_STEP_SIZE; item_array_index++) { idtest = item_array[item_array_index]; - if ((idtest->lib != NULL && id->lib == NULL) || BLI_strcasecmp(idtest->name, id->name) > 0) { + if (BLI_strcasecmp(idtest->name, id->name) > 0) { BLI_insertlinkbefore(lb, idtest, id); break; } @@ -1405,12 +1435,18 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) if (item_array_index == ID_SORT_STEP_SIZE) { if (idtest == NULL) { /* If idtest is NULL here, it means that in the first loop, the last comparison was - * performed exactly on the first item of the list, and that it also failed. In other - * words, all items in the list are greater than inserted one, so we can put it at the - * start of the list. */ - /* Note that BLI_insertlinkafter() would have same behavior in that case, but better be - * explicit here. */ - BLI_addhead(lb, id); + * performed exactly on the first item of the list, and that it also failed. And that the + * second loop was not walked at all. + * + * In other words, if `id` is local, all the items in the list are greater than the inserted + * one, so we can put it at the start of the list. Or, if `id` is linked, it is the first one + * of its library, and we can put it at the very end of the list. */ + if (ID_IS_LINKED(id)) { + BLI_addtail(lb, id); + } + else { + BLI_addhead(lb, id); + } } else { BLI_insertlinkafter(lb, idtest, id); @@ -1678,12 +1714,14 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ */ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) { - bool result; + bool result = false; char name[MAX_ID_NAME - 2]; - /* if library, don't rename */ + /* If library, don't rename, but do ensure proper sorting. */ if (ID_IS_LINKED(id)) { - return false; + id_sort_by_name(lb, id, NULL); + + return result; } /* if no name given, use name of current ID diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc new file mode 100644 index 00000000000..9bfd19ae4d6 --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -0,0 +1,112 @@ +/* + * 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) 2020 by Blender Foundation. + */ +#include "testing/testing.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" + +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "DNA_ID.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +namespace blender::bke::tests { + +struct LibIDMainSortTestContext { + Main *bmain; +}; + +static void test_lib_id_main_sort_init(LibIDMainSortTestContext *ctx) +{ + ctx->bmain = BKE_main_new(); +} + +static void test_lib_id_main_sort_free(LibIDMainSortTestContext *ctx) +{ + BKE_main_free(ctx->bmain); +} + +static void test_lib_id_main_sort_check_order(std::initializer_list<ID *> list) +{ + ID *prev_id = nullptr; + for (ID *id : list) { + EXPECT_EQ(id->prev, prev_id); + if (prev_id != nullptr) { + EXPECT_EQ(prev_id->next, id); + } + prev_id = id; + } + EXPECT_EQ(prev_id->next, nullptr); +} + +TEST(lib_id_main_sort, local_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + EXPECT_TRUE(ctx.bmain->objects.first == id_a); + EXPECT_TRUE(ctx.bmain->objects.last == id_c); + test_lib_id_main_sort_check_order({id_a, id_b, id_c}); + + test_lib_id_main_sort_free(&ctx); +} + +TEST(lib_id_main_sort, linked_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A")); + Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B")); + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + + id_a->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + id_b->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + id_a->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_a); + test_lib_id_main_sort_check_order({id_c, id_b, id_a}); + + id_b->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + test_lib_id_main_sort_free(&ctx); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 84091abd410..8341c5b6e78 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -719,8 +719,19 @@ static void lib_override_library_create_post_process(Main *bmain, if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { if (id_root != NULL && default_instantiating_collection == NULL) { - switch (GS(id_root->name)) { + ID *id_ref = id_root->newid != NULL ? id_root->newid : id_root; + switch (GS(id_ref->name)) { case ID_GR: { + /* Adding the object to a specific collection outside of the root overridden one is a + * fairly bad idea (it breaks the override hierarchy concept). But there is no other + * way to do this currently (we cannot add new collections to overridden root one, + * this is not currently supported). + * Since that will be fairly annoying and noisy, only do that in case the override + * object is not part of any existing collection (i.e. its user count is 0). In + * practice this should never happen I think. */ + if (ID_REAL_USERS(ob_new) != 0) { + continue; + } default_instantiating_collection = BKE_collection_add( bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); /* Hide the collection from viewport and render. */ @@ -731,9 +742,9 @@ static void lib_override_library_create_post_process(Main *bmain, case ID_OB: { /* Add the other objects to one of the collections instantiating the * root object, or scene's master collection if none found. */ - Object *ob_root = (Object *)id_root; + Object *ob_ref = (Object *)id_ref; LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { - if (BKE_collection_has_object(collection, ob_root) && + if (BKE_collection_has_object(collection, ob_ref) && BKE_view_layer_has_collection(view_layer, collection) && !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) { default_instantiating_collection = collection; @@ -867,7 +878,8 @@ bool BKE_lib_override_library_resync(Main *bmain, ID *id_root, Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, - const bool do_post_process) + const bool do_post_process, + ReportList *reports) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); BLI_assert(!ID_IS_LINKED(id_root)); @@ -891,6 +903,19 @@ bool BKE_lib_override_library_resync(Main *bmain, BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* IDs that get fully removed from linked data remain as local overrides (using place-holder + * linked IDs as reference), but they are often not reachable from any current valid local + * override hierarchy anymore. This will ensure they get properly deleted at the end of this + * function. */ + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && + (id->override_library->reference->tag & LIB_TAG_MISSING) != 0 && + /* Unfortunately deleting obdata means deleting their objects too. Since there is no + * guarantee that a valid override object using an obsolete override obdata gets properly + * updated, we ignore those here for now. In practice this should not be a big issue. */ + !OB_DATA_SUPPORT_ID(GS(id->name))) { + id->tag |= LIB_TAG_MISSING; + } + if (id->tag & LIB_TAG_DOIT && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { /* While this should not happen in typical cases (and won't be properly supported here), user * is free to do all kind of very bad things, including having different local overrides of a @@ -1033,6 +1058,7 @@ bool BKE_lib_override_library_resync(Main *bmain, /* Delete old override IDs. * Note that we have to use tagged group deletion here, since ID deletion also uses LIB_TAG_DOIT. * This improves performances anyway, so everything is fine. */ + int user_edited_overrides_deletion_count = 0; FOREACH_MAIN_ID_BEGIN (bmain, id) { if (id->tag & LIB_TAG_DOIT) { /* Note that this works because linked IDs are always after local ones (including overrides), @@ -1057,6 +1083,7 @@ bool BKE_lib_override_library_resync(Main *bmain, id->tag &= ~LIB_TAG_MISSING; CLOG_INFO(&LOG, 2, "Old override %s is being deleted", id->name); } +#if 0 else { /* Otherwise, keep them, user needs to decide whether what to do with them. */ BLI_assert((id->tag & LIB_TAG_DOIT) == 0); @@ -1064,6 +1091,17 @@ bool BKE_lib_override_library_resync(Main *bmain, id->flag |= LIB_LIB_OVERRIDE_RESYNC_LEFTOVER; CLOG_INFO(&LOG, 2, "Old override %s is being kept around as it was user-edited", id->name); } +#else + else { + /* Delete them nevertheless, with fat warning, user needs to decide whether they want to + * save that version of the file (and accept the loss), or not. */ + id->tag |= LIB_TAG_DOIT; + id->tag &= ~LIB_TAG_MISSING; + CLOG_WARN( + &LOG, "Old override %s is being deleted even though it was user-edited", id->name); + user_edited_overrides_deletion_count++; + } +#endif } } FOREACH_MAIN_ID_END; @@ -1074,6 +1112,15 @@ bool BKE_lib_override_library_resync(Main *bmain, */ id_root = id_root_reference->newid; + if (user_edited_overrides_deletion_count > 0) { + BKE_reportf(reports, + RPT_WARNING, + "During resync of data-block %s, %d obsolete overrides were deleted, that had " + "local changes defined by user", + id_root->name + 2, + user_edited_overrides_deletion_count); + } + if (do_post_process) { /* Essentially ensures that potentially new overrides of new objects will be instantiated. */ /* Note: Here 'reference' collection and 'newly added' collection are the same, which is fine @@ -1112,7 +1159,10 @@ bool BKE_lib_override_library_resync(Main *bmain, * Then it will handle the resync of necessary IDs (through calls to * #BKE_lib_override_library_resync). */ -void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer *view_layer) +void BKE_lib_override_library_main_resync(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ReportList *reports) { /* We use a specific collection to gather/store all 'orphaned' override collections and objects * generated by re-sync-process. This avoids putting them in scene's master collection. */ @@ -1228,7 +1278,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * CLOG_INFO(&LOG, 2, "Resyncing %s...", id->name); const bool success = BKE_lib_override_library_resync( - bmain, scene, view_layer, id, override_resync_residual_storage, false, false); + bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); break; } @@ -1619,7 +1669,7 @@ bool BKE_lib_override_library_property_operation_operands_validate( return true; } -/** Check against potential \a bmain. */ +/** Check against potential \a bmain. */ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *reports) { if (id->override_library == NULL) { @@ -1653,7 +1703,7 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList * } } -/** Check against potential \a bmain. */ +/** Check against potential \a bmain. */ void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports) { ID *id; diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 37d47a984cc..73b64e6efb3 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -77,6 +77,7 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "GPU_material.h" @@ -700,6 +701,98 @@ Material *BKE_object_material_get(Object *ob, short act) return ma_p ? *ma_p : NULL; } +static ID *get_evaluated_object_data_with_materials(Object *ob) +{ + ID *data = ob->data; + /* Meshes in edit mode need special handling. */ + if (ob->type == OB_MESH && ob->mode == OB_MODE_EDIT) { + Mesh *mesh = ob->data; + if (mesh->edit_mesh && mesh->edit_mesh->mesh_eval_final) { + data = &mesh->edit_mesh->mesh_eval_final->id; + } + } + return data; +} + +/** + * On evaluated objects the number of materials on an object and its data might go out of sync. + * This is because during evaluation materials can be added/removed on the object data. + * + * For rendering or exporting we generally use the materials on the object data. However, some + * material indices might be overwritten by the object. + */ +Material *BKE_object_material_get_eval(Object *ob, short act) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + const int slot_index = act - 1; + + if (slot_index < 0) { + return NULL; + } + ID *data = get_evaluated_object_data_with_materials(ob); + const short *tot_slots_data_ptr = BKE_id_material_len_p(data); + const int tot_slots_data = tot_slots_data_ptr ? *tot_slots_data_ptr : 0; + if (slot_index >= tot_slots_data) { + return NULL; + } + const int tot_slots_object = ob->totcol; + + Material ***materials_data_ptr = BKE_id_material_array_p(data); + Material **materials_data = materials_data_ptr ? *materials_data_ptr : NULL; + Material **materials_object = ob->mat; + + /* Check if slot is overwritten by object. */ + if (slot_index < tot_slots_object) { + if (ob->matbits) { + if (ob->matbits[slot_index]) { + Material *material = materials_object[slot_index]; + if (material != NULL) { + return material; + } + } + } + } + /* Otherwise use data from object-data. */ + if (slot_index < tot_slots_data) { + Material *material = materials_data[slot_index]; + return material; + } + return NULL; +} + +int BKE_object_material_count_eval(Object *ob) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + ID *id = get_evaluated_object_data_with_materials(ob); + const short *len_p = BKE_id_material_len_p(id); + return len_p ? *len_p : 0; +} + +void BKE_id_material_eval_assign(ID *id, int slot, Material *material) +{ + Material ***materials_ptr = BKE_id_material_array_p(id); + short *len_ptr = BKE_id_material_len_p(id); + if (ELEM(NULL, materials_ptr, len_ptr)) { + BLI_assert_unreachable(); + return; + } + + const int slot_index = slot - 1; + const int old_length = *len_ptr; + + if (slot_index >= old_length) { + /* Need to grow slots array. */ + const int new_length = slot_index + 1; + *materials_ptr = MEM_reallocN(*materials_ptr, sizeof(void *) * new_length); + *len_ptr = new_length; + for (int i = old_length; i < new_length; i++) { + (*materials_ptr)[i] = NULL; + } + } + + (*materials_ptr)[slot_index] = material; +} + Material *BKE_gpencil_material(Object *ob, short act) { Material *ma = BKE_object_material_get(ob, act); @@ -1028,6 +1121,43 @@ void BKE_object_material_remap_calc(Object *ob_dst, Object *ob_src, short *remap BLI_ghash_free(gh_mat_map, NULL, NULL); } +/** + * Copy materials from evaluated geometry to the original geometry of an object. + */ +void BKE_object_material_from_eval_data(Main *bmain, Object *ob_orig, ID *data_eval) +{ + ID *data_orig = ob_orig->data; + + short *orig_totcol = BKE_id_material_len_p(data_orig); + Material ***orig_mat = BKE_id_material_array_p(data_orig); + + short *eval_totcol = BKE_id_material_len_p(data_eval); + Material ***eval_mat = BKE_id_material_array_p(data_eval); + + if (ELEM(NULL, orig_totcol, orig_mat, eval_totcol, eval_mat)) { + return; + } + + /* Remove old materials from original geometry. */ + for (int i = 0; i < *orig_totcol; i++) { + id_us_min(&(*orig_mat)[i]->id); + } + MEM_SAFE_FREE(*orig_mat); + + /* Create new material slots based on materials on evaluated geometry. */ + *orig_totcol = *eval_totcol; + *orig_mat = MEM_callocN(sizeof(void *) * (*eval_totcol), __func__); + for (int i = 0; i < *eval_totcol; i++) { + Material *material_eval = (*eval_mat)[i]; + if (material_eval != NULL) { + Material *material_orig = (Material *)DEG_get_original_id(&material_eval->id); + (*orig_mat)[i] = material_orig; + id_us_plus(&material_orig->id); + } + } + BKE_object_materials_test(bmain, ob_orig, data_orig); +} + /* XXX - this calls many more update calls per object then are needed, could be optimized */ void BKE_object_material_array_assign(Main *bmain, struct Object *ob, diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 1550401cc9c..7a06fd4ea9d 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -162,7 +162,7 @@ static void make_box_from_metaelem(Box *r, const MetaElem *ml) } /** - * Partitions part of mainb array [start, end) along axis s. Returns i, + * Partitions part of #process.mainb array [start, end) along axis s. Returns i, * where centroids of elements in the [start, i) segment lie "on the right side" of div, * and elements in the [i, end) segment lie "on the left" */ @@ -1170,8 +1170,9 @@ static void polygonize(PROCESS *process) /** * Iterates over ALL objects in the scene and all of its sets, including - * making all duplis(not only metas). Copies metas to mainb array. - * Computes bounding boxes for building BVH. */ + * making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array. + * Computes bounding boxes for building BVH. + */ static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob) { Scene *sce_iter = scene; diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index 098d9c420aa..e893a3983bd 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -1211,7 +1211,9 @@ static Mesh *mesh_new_from_mesh(Object *object, Mesh *mesh) return mesh_result; } -static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object *object) +static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, + Object *object, + const bool preserve_origindex) { if (DEG_is_original_id(&object->id)) { return mesh_new_from_mesh(object, (Mesh *)object->data); @@ -1228,16 +1230,23 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object Scene *scene = DEG_get_evaluated_scene(depsgraph); CustomData_MeshMasks mask = CD_MASK_MESH; + if (preserve_origindex) { + mask.vmask |= CD_MASK_ORIGINDEX; + mask.emask |= CD_MASK_ORIGINDEX; + mask.lmask |= CD_MASK_ORIGINDEX; + mask.pmask |= CD_MASK_ORIGINDEX; + } Mesh *result = mesh_create_eval_final(depsgraph, scene, &object_for_eval, &mask); return result; } static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, Object *object, - bool preserve_all_data_layers) + const bool preserve_all_data_layers, + const bool preserve_origindex) { - if (preserve_all_data_layers) { - return mesh_new_from_mesh_object_with_layers(depsgraph, object); + if (preserve_all_data_layers || preserve_origindex) { + return mesh_new_from_mesh_object_with_layers(depsgraph, object, preserve_origindex); } Mesh *mesh_input = object->data; /* If we are in edit mode, use evaluated mesh from edit structure, matching to what @@ -1248,7 +1257,10 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, return mesh_new_from_mesh(object, mesh_input); } -Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers) +Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, + Object *object, + const bool preserve_all_data_layers, + const bool preserve_origindex) { Mesh *new_mesh = NULL; switch (object->type) { @@ -1261,7 +1273,8 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preser new_mesh = mesh_new_from_mball_object(object); break; case OB_MESH: - new_mesh = mesh_new_from_mesh_object(depsgraph, object, preserve_all_data_layers); + new_mesh = mesh_new_from_mesh_object( + depsgraph, object, preserve_all_data_layers, preserve_origindex); break; default: /* Object does not have geometry data. */ @@ -1326,7 +1339,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, { BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH)); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); if (mesh == NULL) { /* Unable to convert the object to a mesh, return an empty one. */ Mesh *mesh_in_bmain = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2); diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 971a2154435..bd729ae5837 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -282,7 +282,7 @@ bool BKE_modifier_is_preview(ModifierData *md) return false; } -ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) +ModifierData *BKE_modifiers_findby_type(const Object *ob, ModifierType type) { LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { if (md->type == type) { @@ -292,7 +292,7 @@ ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) return NULL; } -ModifierData *BKE_modifiers_findby_name(Object *ob, const char *name) +ModifierData *BKE_modifiers_findby_name(const Object *ob, const char *name) { return BLI_findstring(&(ob->modifiers), name, offsetof(ModifierData, name)); } diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 6634c42a274..6405888d5be 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -303,6 +303,16 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -434,6 +444,12 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so case SOCK_COLLECTION: BLO_write_struct(writer, bNodeSocketValueCollection, sock->default_value); break; + case SOCK_TEXTURE: + BLO_write_struct(writer, bNodeSocketValueTexture, sock->default_value); + break; + case SOCK_MATERIAL: + BLO_write_struct(writer, bNodeSocketValueMaterial, sock->default_value); + break; case __SOCK_MESH: case SOCK_CUSTOM: case SOCK_SHADER: @@ -501,6 +517,12 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } + else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) { + BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec); + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_rgb); + } else if (ntree->type == NTREE_SHADER && (node->type == SH_NODE_SCRIPT)) { NodeShaderScript *nss = (NodeShaderScript *)node->storage; if (nss->bytecode) { @@ -676,6 +698,18 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage); break; } + case GEO_NODE_ATTRIBUTE_CURVE_MAP: { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BLO_read_data_address(reader, &data->curve_vec); + if (data->curve_vec) { + BKE_curvemapping_blend_read(reader, data->curve_vec); + } + BLO_read_data_address(reader, &data->curve_rgb); + if (data->curve_rgb) { + BKE_curvemapping_blend_read(reader, data->curve_rgb); + } + break; + } case SH_NODE_SCRIPT: { NodeShaderScript *nss = (NodeShaderScript *)node->storage; BLO_read_data_address(reader, &nss->bytecode); @@ -802,6 +836,16 @@ static void lib_link_node_socket(BlendLibReader *reader, Library *lib, bNodeSock BLO_read_id_address(reader, lib, &default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BLO_read_id_address(reader, lib, &default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BLO_read_id_address(reader, lib, &default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -887,6 +931,16 @@ static void expand_node_socket(BlendExpander *expander, bNodeSocket *sock) BLO_expand(expander, default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BLO_expand(expander, default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BLO_expand(expander, default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1454,6 +1508,16 @@ static void socket_id_user_increment(bNodeSocket *sock) id_us_plus((ID *)default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + id_us_plus((ID *)default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + id_us_plus((ID *)default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1493,6 +1557,20 @@ static void socket_id_user_decrement(bNodeSocket *sock) } break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + if (default_value->value != nullptr) { + id_us_min(&default_value->value->id); + } + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + if (default_value->value != nullptr) { + id_us_min(&default_value->value->id); + } + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1636,6 +1714,10 @@ const char *nodeStaticSocketType(int type, int subtype) return "NodeSocketGeometry"; case SOCK_COLLECTION: return "NodeSocketCollection"; + case SOCK_TEXTURE: + return "NodeSocketTexture"; + case SOCK_MATERIAL: + return "NodeSocketMaterial"; } return nullptr; } @@ -1707,6 +1789,10 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype) return "NodeSocketInterfaceGeometry"; case SOCK_COLLECTION: return "NodeSocketInterfaceCollection"; + case SOCK_TEXTURE: + return "NodeSocketInterfaceTexture"; + case SOCK_MATERIAL: + return "NodeSocketInterfaceMaterial"; } return nullptr; } @@ -2172,6 +2258,17 @@ bNodeTree *ntreeCopyTree_ex_new_pointers(const bNodeTree *ntree, return new_ntree; } +static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket) +{ + int count = 0; + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (ELEM(socket, link->fromsock, link->tosock)) { + count++; + } + } + return count; +} + /* also used via rna api, so we check for proper input output direction */ bNodeLink *nodeAddLink( bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock) @@ -2208,6 +2305,10 @@ bNodeLink *nodeAddLink( ntree->update |= NTREE_UPDATE_LINKS; } + if (link->tosock->flag & SOCK_MULTI_INPUT) { + link->multi_input_socket_index = node_count_links(ntree, link->tosock) - 1; + } + return link; } @@ -4226,7 +4327,7 @@ void ntreeUpdateAllUsers(Main *main, ID *id) if (GS(id->name) == ID_NT) { bNodeTree *ngroup = (bNodeTree *)id; - if (ngroup->type == NTREE_GEOMETRY) { + if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) { LISTBASE_FOREACH (Object *, object, &main->objects) { LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { @@ -4934,6 +5035,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_combine_xyz(); register_node_type_geo_attribute_compare(); register_node_type_geo_attribute_convert(); + register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); @@ -4943,14 +5045,18 @@ static void registerGeometryNodes() register_node_type_geo_attribute_separate_xyz(); register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); + register_node_type_geo_attribute_vector_rotate(); register_node_type_geo_attribute_remove(); register_node_type_geo_boolean(); register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); register_node_type_geo_curve_to_mesh(); + register_node_type_geo_curve_resample(); register_node_type_geo_edge_split(); + register_node_type_geo_input_material(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); + register_node_type_geo_material_assign(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc index cc910bab6ac..7a28fd295fb 100644 --- a/source/blender/blenkernel/intern/node_ui_storage.cc +++ b/source/blender/blenkernel/intern/node_ui_storage.cc @@ -152,6 +152,7 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree, node_error_message_log(ntree, node, message, type); NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + std::lock_guard lock{node_ui_storage.mutex}; node_ui_storage.warnings.append({type, std::move(message)}); } @@ -163,6 +164,7 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree, const CustomDataType data_type) { NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + std::lock_guard lock{node_ui_storage.mutex}; node_ui_storage.attribute_hints.add_as( AvailableAttributeInfo{attribute_name, domain, data_type}); } diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 02be16d1d28..109037bc95a 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -2289,7 +2289,7 @@ Object *BKE_object_add_for_data( void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int flag) { SoftBody *sb = ob_src->soft; - bool tagged_no_main = ob_dst->id.tag & LIB_TAG_NO_MAIN; + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; ob_dst->softflag = ob_src->softflag; if (sb == NULL) { @@ -2330,7 +2330,7 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl sbn->scratch = NULL; - if (!tagged_no_main) { + if (is_orig) { sbn->shared = MEM_dupallocN(sb->shared); sbn->shared->pointcache = BKE_ptcache_copy_list( &sbn->shared->ptcaches, &sb->shared->ptcaches, flag); @@ -5668,7 +5668,7 @@ Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all { BKE_object_to_mesh_clear(object); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); object->runtime.object_as_temp_mesh = mesh; return mesh; } diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c index d2f4d0702ed..9d53dad8d03 100644 --- a/source/blender/blenkernel/intern/ocean.c +++ b/source/blender/blenkernel/intern/ocean.c @@ -911,8 +911,12 @@ void BKE_ocean_init(struct Ocean *o, for (i = 0; i < o->_M; i++) { for (j = 0; j < o->_N; j++) { /* This ensures we get a value tied to the surface location, avoiding dramatic surface - * change with changing resolution. */ - int new_seed = seed + BLI_hash_int_2d(o->_kx[i] * 360.0f, o->_kz[j] * 360.0f); + * change with changing resolution. + * Explicitly cast to signed int first to ensure consistent behavior on all processors, + * since behavior of float to unsigned int cast is undefined in C. */ + const int hash_x = o->_kx[i] * 360.0f; + const int hash_z = o->_kz[j] * 360.0f; + int new_seed = seed + BLI_hash_int_2d(hash_x, hash_z); BLI_rng_seed(rng, new_seed); float r1 = gaussRand(rng); diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 19078446009..2539b990210 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -260,10 +260,12 @@ static RigidBodyOb *rigidbody_copy_object(const Object *ob, const int flag) RigidBodyOb *rboN = NULL; if (ob->rigidbody_object) { + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; + /* just duplicate the whole struct first (to catch all the settings) */ rboN = MEM_dupallocN(ob->rigidbody_object); - if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { + if (is_orig) { /* This is a regular copy, and not a CoW copy for depsgraph evaluation */ rboN->shared = MEM_callocN(sizeof(*rboN->shared), "RigidBodyOb_Shared"); } diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index 3b1230ce3b6..fcc1afbc59b 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -829,13 +829,13 @@ static void calculate_collision_balls(Object *ob) } /* creates new softbody if didn't exist yet, makes new points and springs arrays */ -static void renew_softbody(Scene *scene, Object *ob, int totpoint, int totspring) +static void renew_softbody(Object *ob, int totpoint, int totspring) { SoftBody *sb; int i; short softflag; if (ob->soft == NULL) { - ob->soft = sbNew(scene); + ob->soft = sbNew(); } else { free_softbody_intern(ob->soft); @@ -2679,7 +2679,7 @@ static void springs_from_mesh(Object *ob) } /* makes totally fresh start situation */ -static void mesh_to_softbody(Scene *scene, Object *ob) +static void mesh_to_softbody(Object *ob) { SoftBody *sb; Mesh *me = ob->data; @@ -2697,7 +2697,7 @@ static void mesh_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, me->totvert, totedge); + renew_softbody(ob, me->totvert, totedge); /* we always make body points */ sb = ob->soft; @@ -2909,7 +2909,7 @@ static void makelatticesprings(Lattice *lt, BodySpring *bs, int dostiff, Object } /* makes totally fresh start situation */ -static void lattice_to_softbody(Scene *scene, Object *ob) +static void lattice_to_softbody(Object *ob) { Lattice *lt = ob->data; SoftBody *sb; @@ -2929,7 +2929,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ bp = sb->bpoint; @@ -2972,7 +2972,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* makes totally fresh start situation */ -static void curve_surf_to_softbody(Scene *scene, Object *ob) +static void curve_surf_to_softbody(Object *ob) { Curve *cu = ob->data; SoftBody *sb; @@ -2993,7 +2993,7 @@ static void curve_surf_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ /* set vars now */ @@ -3117,7 +3117,7 @@ static void sb_new_scratch(SoftBody *sb) /* ************ Object level, exported functions *************** */ /* allocates and initializes general main data */ -SoftBody *sbNew(Scene *scene) +SoftBody *sbNew(void) { SoftBody *sb; @@ -3140,12 +3140,6 @@ SoftBody *sbNew(Scene *scene) /*todo backward file compat should copy inspring to inpush while reading old files*/ sb->inpush = 0.5f; - sb->interval = 10; - if (scene != NULL) { - sb->sfra = scene->r.sfra; - sb->efra = scene->r.efra; - } - sb->colball = 0.49f; sb->balldamp = 0.50f; sb->ballstiff = 1.0f; @@ -3577,17 +3571,17 @@ void sbObjectStep(struct Depsgraph *depsgraph, switch (ob->type) { case OB_MESH: - mesh_to_softbody(scene, ob); + mesh_to_softbody(ob); break; case OB_LATTICE: - lattice_to_softbody(scene, ob); + lattice_to_softbody(ob); break; case OB_CURVE: case OB_SURF: - curve_surf_to_softbody(scene, ob); + curve_surf_to_softbody(ob); break; default: - renew_softbody(scene, ob, numVerts, 0); + renew_softbody(ob, numVerts, 0); break; } diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 31c2178fa3f..11620a30948 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -16,11 +16,13 @@ #include "BLI_array.hh" #include "BLI_span.hh" - -#include "FN_generic_virtual_array.hh" +#include "BLI_timeit.hh" #include "BKE_spline.hh" +#include "FN_generic_virtual_array.hh" + +using blender::Array; using blender::float3; using blender::IndexRange; using blender::MutableSpan; @@ -59,7 +61,8 @@ int Spline::evaluated_edges_size() const float Spline::length() const { - return this->evaluated_lengths().last(); + Span<float> lengths = this->evaluated_lengths(); + return (lengths.size() == 0) ? 0 : this->evaluated_lengths().last(); } int Spline::segments_size() const @@ -265,6 +268,72 @@ Spline::LookupResult Spline::lookup_evaluated_length(const float length) const return LookupResult{index, next_index, factor}; } +/** + * Return an array of evenly spaced samples along the length of the spline. The samples are indices + * and factors to the next index encoded in floats. The logic for converting from the float values + * to interpolation data is in #lookup_data_from_index_factor. + */ +Array<float> Spline::sample_uniform_index_factors(const int samples_size) const +{ + const Span<float> lengths = this->evaluated_lengths(); + + BLI_assert(samples_size > 0); + Array<float> samples(samples_size); + + samples[0] = 0.0f; + if (samples_size == 1) { + return samples; + } + + const float total_length = this->length(); + const float sample_length = total_length / (samples_size - (is_cyclic_ ? 0 : 1)); + + /* Store the length at the previous evaluated point in a variable so it can + * start out at zero (the lengths array doesn't contain 0 for the first point). */ + float prev_length = 0.0f; + int i_sample = 1; + for (const int i_evaluated : IndexRange(this->evaluated_edges_size())) { + const float length = lengths[i_evaluated]; + + /* Add every sample that fits in this evaluated edge. */ + while ((sample_length * i_sample) < length && i_sample < samples_size) { + const float factor = (sample_length * i_sample - prev_length) / (length - prev_length); + samples[i_sample] = i_evaluated + factor; + i_sample++; + } + + prev_length = length; + } + + if (!is_cyclic_) { + /* In rare cases this can prevent overflow of the stored index. */ + samples.last() = lengths.size(); + } + + return samples; +} + +Spline::LookupResult Spline::lookup_data_from_index_factor(const float index_factor) const +{ + const int points_len = this->evaluated_points_size(); + + if (is_cyclic_) { + if (index_factor < points_len) { + const int index = std::floor(index_factor); + const int next_index = (index < points_len - 1) ? index + 1 : 0; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 1, 0, 1.0f}; + } + + if (index_factor < points_len - 1) { + const int index = std::floor(index_factor); + const int next_index = index + 1; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 2, points_len - 1, 1.0f}; +} + void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const { Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions(); diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index f62718011da..4be3ba8576e 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -55,6 +55,9 @@ void BezierSpline::set_resolution(const int value) this->mark_cache_invalid(); } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void BezierSpline::add_point(const float3 position, const HandleType handle_type_start, const float3 handle_position_start, @@ -73,6 +76,19 @@ void BezierSpline::add_point(const float3 position, this->mark_cache_invalid(); } +void BezierSpline::resize(const int size) +{ + handle_types_left_.resize(size); + handle_positions_left_.resize(size); + positions_.resize(size); + handle_types_right_.resize(size); + handle_positions_right_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> BezierSpline::positions() { return positions_; @@ -107,10 +123,12 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left() } Span<float3> BezierSpline::handle_positions_left() const { + this->ensure_auto_handles(); return handle_positions_left_; } MutableSpan<float3> BezierSpline::handle_positions_left() { + this->ensure_auto_handles(); return handle_positions_left_; } Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const @@ -123,13 +141,94 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right() } Span<float3> BezierSpline::handle_positions_right() const { + this->ensure_auto_handles(); return handle_positions_right_; } MutableSpan<float3> BezierSpline::handle_positions_right() { + this->ensure_auto_handles(); return handle_positions_right_; } +static float3 previous_position(Span<float3> positions, const bool cyclic, const int i) +{ + if (i == 0) { + if (cyclic) { + return positions[positions.size() - 1]; + } + return 2.0f * positions[i] - positions[i + 1]; + } + return positions[i - 1]; +} + +static float3 next_position(Span<float3> positions, const bool cyclic, const int i) +{ + if (i == positions.size() - 1) { + if (cyclic) { + return positions[0]; + } + return 2.0f * positions[i] - positions[i - 1]; + } + return positions[i + 1]; +} + +/** + * Recalculate all #Auto and #Vector handles with positions automatically + * derived from the neighboring control points. + */ +void BezierSpline::ensure_auto_handles() const +{ + if (!auto_handles_dirty_) { + return; + } + + std::lock_guard lock{auto_handle_mutex_}; + if (!auto_handles_dirty_) { + return; + } + + for (const int i : IndexRange(this->size())) { + if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) { + const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i); + const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i]; + float prev_len = prev_diff.length(); + float next_len = next_diff.length(); + if (prev_len == 0.0f) { + prev_len = 1.0f; + } + if (next_len == 0.0f) { + next_len = 1.0f; + } + const float3 dir = next_diff / next_len + prev_diff / prev_len; + + /* This magic number is unfortunate, but comes from elsewhere in Blender. */ + const float len = dir.length() * 2.5614f; + if (len != 0.0f) { + if (handle_types_left_[i] == HandleType::Auto) { + const float prev_len_clamped = std::min(prev_len, next_len * 5.0f); + handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len); + } + if (handle_types_right_[i] == HandleType::Auto) { + const float next_len_clamped = std::min(next_len, prev_len * 5.0f); + handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len); + } + } + } + + if (handle_types_left_[i] == HandleType::Vector) { + const float3 prev = previous_position(positions_, is_cyclic_, i); + handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f); + } + + if (handle_types_right_[i] == HandleType::Vector) { + const float3 next = next_position(positions_, is_cyclic_, i); + handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f); + } + } + + auto_handles_dirty_ = false; +} + void BezierSpline::translate(const blender::float3 &translation) { for (float3 &position : this->positions()) { @@ -167,9 +266,13 @@ bool BezierSpline::point_is_sharp(const int index) const bool BezierSpline::segment_is_vector(const int index) const { if (index == this->size() - 1) { - BLI_assert(is_cyclic_); - return handle_types_right_.last() == HandleType::Vector && - handle_types_left_.first() == HandleType::Vector; + if (is_cyclic_) { + return handle_types_right_.last() == HandleType::Vector && + handle_types_left_.first() == HandleType::Vector; + } + /* There is actually no segment in this case, but it's nice to avoid + * having a special case for the last segment in calling code. */ + return true; } return handle_types_right_[index] == HandleType::Vector && handle_types_left_[index + 1] == HandleType::Vector; @@ -183,19 +286,13 @@ void BezierSpline::mark_cache_invalid() tangent_cache_dirty_ = true; normal_cache_dirty_ = true; length_cache_dirty_ = true; + auto_handles_dirty_ = true; } int BezierSpline::evaluated_points_size() const { - const int points_len = this->size(); - BLI_assert(points_len > 0); - - const int last_offset = this->control_point_offsets().last(); - if (is_cyclic_ && points_len > 1) { - return last_offset + (this->segment_is_vector(points_len - 1) ? 0 : resolution_); - } - - return last_offset + 1; + BLI_assert(this->size() > 0); + return this->control_point_offsets().last(); } /** @@ -266,8 +363,12 @@ void BezierSpline::evaluate_bezier_segment(const int index, /** * Returns access to a cache of offsets into the evaluated point array for each control point. - * This is important because while most control point edges generate the number of edges specified - * by the resolution, vector segments only generate one edge. + * While most control point edges generate the number of edges specified by the resolution, vector + * segments only generate one edge. + * + * \note The length of the result is one greater than the number of points, so that the last item + * is the total number of evaluated points. This is useful to avoid recalculating the size of the + * last segment everywhere. */ Span<int> BezierSpline::control_point_offsets() const { @@ -281,12 +382,12 @@ Span<int> BezierSpline::control_point_offsets() const } const int points_len = this->size(); - offset_cache_.resize(points_len); + offset_cache_.resize(points_len + 1); MutableSpan<int> offsets = offset_cache_; int offset = 0; - for (const int i : IndexRange(points_len - 1)) { + for (const int i : IndexRange(points_len)) { offsets[i] = offset; offset += this->segment_is_vector(i) ? 1 : resolution_; } @@ -308,7 +409,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets, } const int grain_size = std::max(2048 / resolution, 1); - blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) { + parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) { for (const int i_control_point : range) { const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point]; const float segment_len_inv = 1.0f / segment_len; @@ -319,7 +420,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets, }); if (is_cyclic) { - const int last_segment_len = offsets[size - 1] - offsets[size - 2]; + const int last_segment_len = offsets[size] - offsets[size - 1]; const float last_segment_len_inv = 1.0f / last_segment_len; for (const int i : IndexRange(last_segment_len)) { r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv; @@ -377,25 +478,26 @@ Span<float3> BezierSpline::evaluated_positions() const return evaluated_position_cache_; } + this->ensure_auto_handles(); + + const int size = this->size(); const int eval_size = this->evaluated_points_size(); evaluated_position_cache_.resize(eval_size); MutableSpan<float3> positions = evaluated_position_cache_; Span<int> offsets = this->control_point_offsets(); - BLI_assert(offsets.last() <= eval_size); const int grain_size = std::max(512 / resolution_, 1); - blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) { + parallel_for(IndexRange(size - 1), grain_size, [&](IndexRange range) { for (const int i : range) { this->evaluate_bezier_segment( i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i])); } }); - - const int i_last = this->size() - 1; if (is_cyclic_) { - this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), resolution_)); + this->evaluate_bezier_segment( + size - 1, 0, positions.slice(offsets[size - 1], offsets[size] - offsets[size - 1])); } else { /* Since evaluating the bezier segment doesn't add the final point, @@ -410,7 +512,7 @@ Span<float3> BezierSpline::evaluated_positions() const /** * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary * to interpolate data from control points to evaluated points between them. The next control - * point index result will not overflow the size of the vector. + * point index result will not overflow the size of the control point vectors. */ BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor( const float index_factor) const @@ -458,6 +560,10 @@ blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points( { BLI_assert(source_data.size() == this->size()); + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + const int eval_size = this->evaluated_points_size(); if (eval_size == 1) { return source_data.shallow_copy(); diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 37d1232cfeb..ae691d26cdb 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -65,6 +65,9 @@ void NURBSpline::set_order(const uint8_t value) this->mark_cache_invalid(); } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void NURBSpline::add_point(const float3 position, const float radius, const float tilt, @@ -78,6 +81,16 @@ void NURBSpline::add_point(const float3 position, this->mark_cache_invalid(); } +void NURBSpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + weights_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> NURBSpline::positions() { return positions_; @@ -250,7 +263,7 @@ static void calculate_basis_for_point(const float parameter, MutableSpan<float> basis_buffer, NURBSpline::BasisCache &basis_cache) { - /* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */ + /* Clamp parameter due to floating point inaccuracy. */ const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]); int start = 0; @@ -376,6 +389,10 @@ blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points( { BLI_assert(source_data.size() == this->size()); + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + this->calculate_basis_cache(); Span<BasisCache> weights(basis_cache_); diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index 09c578c0503..5c33b0052fc 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -36,6 +36,9 @@ int PolySpline::size() const return size; } +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ void PolySpline::add_point(const float3 position, const float radius, const float tilt) { positions_.append(position); @@ -44,6 +47,15 @@ void PolySpline::add_point(const float3 position, const float radius, const floa this->mark_cache_invalid(); } +void PolySpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + MutableSpan<float3> PolySpline::positions() { return positions_; diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 7fc9e8cc0ef..39f65d76e3c 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -56,6 +56,7 @@ # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> # include <libavutil/imgutils.h> +# include <libavutil/opt.h> # include <libavutil/rational.h> # include <libavutil/samplefmt.h> # include <libswscale/swscale.h> @@ -80,6 +81,8 @@ typedef struct FFMpegContext { int ffmpeg_preset; /* see eFFMpegPreset */ AVFormatContext *outfile; + AVCodecContext *video_codec; + AVCodecContext *audio_codec; AVStream *video_stream; AVStream *audio_stream; AVFrame *current_frame; /* Image frame in output pixel format. */ @@ -91,10 +94,6 @@ typedef struct FFMpegContext { uint8_t *audio_input_buffer; uint8_t *audio_deinterleave_buffer; int audio_input_samples; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - uint8_t *audio_output_buffer; - int audio_outbuf_size; -# endif double audio_time; bool audio_deinterleave; int audio_sample_size; @@ -141,32 +140,22 @@ static int request_float_audio_buffer(int codec_id) } # ifdef WITH_AUDASPACE + static int write_audio_frame(FFMpegContext *context) { - AVCodecContext *c = NULL; - AVPacket pkt; AVFrame *frame = NULL; - int got_output = 0; - - c = context->audio_stream->codec; - - av_init_packet(&pkt); - pkt.size = 0; - pkt.data = NULL; + AVCodecContext *c = context->audio_codec; AUD_Device_read( context->audio_mixdown_device, context->audio_input_buffer, context->audio_input_samples); context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate; -# ifdef FFMPEG_HAVE_ENCODE_AUDIO2 frame = av_frame_alloc(); - av_frame_unref(frame); frame->pts = context->audio_time / av_q2d(c->time_base); frame->nb_samples = context->audio_input_samples; frame->format = c->sample_fmt; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT + frame->channels = c->channels; frame->channel_layout = c->channel_layout; -# endif if (context->audio_deinterleave) { int channel, i; @@ -194,61 +183,49 @@ static int write_audio_frame(FFMpegContext *context) context->audio_input_samples * c->channels * context->audio_sample_size, 1); - if (avcodec_encode_audio2(c, &pkt, frame, &got_output) < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + int success = 0; - if (!got_output) { - av_frame_free(&frame); - return 0; + int ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send audio frame: %s\n", av_err2str(ret)); + success = -1; } -# else - pkt.size = avcodec_encode_audio(c, - context->audio_output_buffer, - context->audio_outbuf_size, - (short *)context->audio_input_buffer); - if (pkt.size < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + AVPacket *pkt = av_packet_alloc(); - pkt.data = context->audio_output_buffer; - got_output = 1; -# endif + while (ret >= 0) { - if (got_output) { - if (pkt.pts != AV_NOPTS_VALUE) { - pkt.pts = av_rescale_q(pkt.pts, c->time_base, context->audio_stream->time_base); + ret = avcodec_receive_packet(c, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; } - if (pkt.dts != AV_NOPTS_VALUE) { - pkt.dts = av_rescale_q(pkt.dts, c->time_base, context->audio_stream->time_base); + if (ret < 0) { + fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + success = -1; } - if (pkt.duration > 0) { - pkt.duration = av_rescale_q(pkt.duration, c->time_base, context->audio_stream->time_base); + + av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base); + if (pkt->duration > 0) { + pkt->duration = av_rescale_q(pkt->duration, c->time_base, context->audio_stream->time_base); } - pkt.stream_index = context->audio_stream->index; + pkt->stream_index = context->audio_stream->index; - pkt.flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; - if (av_interleaved_write_frame(context->outfile, &pkt) != 0) { - fprintf(stderr, "Error writing audio packet!\n"); - if (frame) { - av_frame_free(&frame); - } - return -1; + int write_ret = av_interleaved_write_frame(context->outfile, pkt); + if (write_ret != 0) { + fprintf(stderr, "Error writing audio packet: %s\n", av_err2str(write_ret)); + success = -1; + break; } - - av_free_packet(&pkt); } - if (frame) { - av_frame_free(&frame); - } + av_packet_free(&pkt); + av_frame_free(&frame); - return 0; + return success; } # endif /* #ifdef WITH_AUDASPACE */ @@ -264,14 +241,15 @@ static AVFrame *alloc_picture(int pix_fmt, int width, int height) if (!f) { return NULL; } - size = avpicture_get_size(pix_fmt, width, height); + size = av_image_get_buffer_size(pix_fmt, width, height, 1); /* allocate the actual picture buffer */ buf = MEM_mallocN(size, "AVFrame buffer"); if (!buf) { free(f); return NULL; } - avpicture_fill((AVPicture *)f, buf, pix_fmt, width, height); + + av_image_fill_arrays(f->data, f->linesize, buf, pix_fmt, width, height, 1); f->format = pix_fmt; f->width = width; f->height = height; @@ -341,58 +319,57 @@ static const char **get_file_extensions(int format) } /* Write a frame to the output file */ -static int write_video_frame( - FFMpegContext *context, const RenderData *rd, int cfra, AVFrame *frame, ReportList *reports) +static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, ReportList *reports) { - int got_output; int ret, success = 1; - AVCodecContext *c = context->video_stream->codec; - AVPacket packet = {0}; + AVPacket *packet = av_packet_alloc(); - av_init_packet(&packet); + AVCodecContext *c = context->video_codec; frame->pts = cfra; - ret = avcodec_encode_video2(c, &packet, frame, &got_output); + ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send video frame: %s\n", av_err2str(ret)); + success = -1; + } - if (ret >= 0 && got_output) { - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets available. */ + break; } - else { - PRINT("Video Frame DTS: not set\n"); + if (ret < 0) { + fprintf(stderr, "Error encoding frame: %s\n", av_err2str(ret)); + break; } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - success = (ret == 0); - } - else if (ret < 0) { - success = 0; + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + if (av_interleaved_write_frame(context->outfile, packet) != 0) { + success = -1; + break; + } } if (!success) { BKE_report(reports, RPT_ERROR, "Error writing frame"); + PRINT("Error writing frame: %s\n", av_err2str(ret)); } + av_packet_free(&packet); + return success; } /* read and encode a frame of audio from the buffer */ -static AVFrame *generate_video_frame(FFMpegContext *context, - const uint8_t *pixels, - ReportList *reports) +static AVFrame *generate_video_frame(FFMpegContext *context, const uint8_t *pixels) { - AVCodecContext *c = context->video_stream->codec; - int height = c->height; + AVCodecParameters *codec = context->video_stream->codecpar; + int height = codec->height; AVFrame *rgb_frame; if (context->img_convert_frame != NULL) { @@ -437,7 +414,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, (const uint8_t *const *)rgb_frame->data, rgb_frame->linesize, 0, - c->height, + codec->height, context->current_frame->data, context->current_frame->linesize); } @@ -445,9 +422,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, return context->current_frame; } -static void set_ffmpeg_property_option(AVCodecContext *c, - IDProperty *prop, - AVDictionary **dictionary) +static void set_ffmpeg_property_option(IDProperty *prop, AVDictionary **dictionary) { char name[128]; char *param; @@ -535,7 +510,7 @@ static void set_ffmpeg_properties(RenderData *rd, for (curr = prop->data.group.first; curr; curr = curr->next) { if (ffmpeg_proprty_valid(c, prop_name, curr)) { - set_ffmpeg_property_option(c, curr, dictionary); + set_ffmpeg_property_option(curr, dictionary); } } } @@ -552,7 +527,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -566,7 +540,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Set up the codec context */ - c = st->codec; + context->video_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->video_codec; c->codec_id = codec_id; c->codec_type = AVMEDIA_TYPE_VIDEO; @@ -649,11 +624,9 @@ static AVStream *alloc_video_stream(FFMpegContext *context, } } - /* Deprecated and not doing anything since July 2015, deleted in recent ffmpeg */ - // c->me_method = ME_EPZS; - codec = avcodec_find_encoder(c->codec_id); if (!codec) { + avcodec_free_context(&c); return NULL; } @@ -713,7 +686,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if ((of->oformat->flags & AVFMT_GLOBALHEADER)) { PRINT("Using global header\n"); - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } /* xasp & yasp got float lately... */ @@ -741,6 +714,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if (avcodec_open2(c, codec, &opts) < 0) { BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); @@ -768,6 +742,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, NULL); } + avcodec_parameters_from_context(st->codecpar, c); + return st; } @@ -779,7 +755,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -791,7 +766,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, } st->id = 1; - c = st->codec; + context->audio_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->audio_codec; c->thread_count = BLI_system_thread_count(); c->thread_type = FF_THREAD_SLICE; @@ -803,7 +779,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->sample_fmt = AV_SAMPLE_FMT_S16; c->channels = rd->ffcodecdata.audio_channels; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT switch (rd->ffcodecdata.audio_channels) { case FFM_CHANNELS_MONO: c->channel_layout = AV_CH_LAYOUT_MONO; @@ -821,7 +796,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->channel_layout = AV_CH_LAYOUT_7POINT1; break; } -# endif if (request_float_audio_buffer(codec_id)) { /* mainly for AAC codec which is experimental */ @@ -832,6 +806,7 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, codec = avcodec_find_encoder(c->codec_id); if (!codec) { // XXX error("Couldn't find a valid audio codec"); + avcodec_free_context(&c); return NULL; } @@ -843,13 +818,13 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, * Float samples in particular are not always supported. */ const enum AVSampleFormat *p = codec->sample_fmts; for (; *p != -1; p++) { - if (*p == st->codec->sample_fmt) { + if (*p == c->sample_fmt) { break; } } if (*p == -1) { /* sample format incompatible with codec. Defaulting to a format known to work */ - st->codec->sample_fmt = codec->sample_fmts[0]; + c->sample_fmt = codec->sample_fmts[0]; } } @@ -858,18 +833,18 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int best = 0; int best_dist = INT_MAX; for (; *p; p++) { - int dist = abs(st->codec->sample_rate - *p); + int dist = abs(c->sample_rate - *p); if (dist < best_dist) { best_dist = dist; best = *p; } } /* best is the closest supported sample rate (same as selected if best_dist == 0) */ - st->codec->sample_rate = best; + c->sample_rate = best; } if (of->oformat->flags & AVFMT_GLOBALHEADER) { - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } set_ffmpeg_properties(rd, c, "audio", &opts); @@ -878,32 +853,25 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, // XXX error("Couldn't initialize audio codec"); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); /* need to prevent floating point exception when using vorbis audio codec, * initialize this value in the same way as it's done in FFmpeg itself (sergey) */ - st->codec->time_base.num = 1; - st->codec->time_base.den = st->codec->sample_rate; - -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_outbuf_size = FF_MIN_BUFFER_SIZE; -# endif + c->time_base.num = 1; + c->time_base.den = c->sample_rate; if (c->frame_size == 0) { /* Used to be if ((c->codec_id >= CODEC_ID_PCM_S16LE) && (c->codec_id <= CODEC_ID_PCM_DVD)) * not sure if that is needed anymore, so let's try out if there are any * complaints regarding some FFmpeg versions users might have. */ - context->audio_input_samples = FF_MIN_BUFFER_SIZE * 8 / c->bits_per_coded_sample / c->channels; + context->audio_input_samples = AV_INPUT_BUFFER_MIN_SIZE * 8 / c->bits_per_coded_sample / + c->channels; } else { context->audio_input_samples = c->frame_size; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (c->frame_size * c->channels * sizeof(int16_t) * 4 > context->audio_outbuf_size) { - context->audio_outbuf_size = c->frame_size * c->channels * sizeof(int16_t) * 4; - } -# endif } context->audio_deinterleave = av_sample_fmt_is_planar(c->sample_fmt); @@ -912,10 +880,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_input_buffer = (uint8_t *)av_malloc(context->audio_input_samples * c->channels * context->audio_sample_size); -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_output_buffer = (uint8_t *)av_malloc(context->audio_outbuf_size); -# endif - if (context->audio_deinterleave) { context->audio_deinterleave_buffer = (uint8_t *)av_malloc( context->audio_input_samples * c->channels * context->audio_sample_size); @@ -923,6 +887,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_time = 0.0f; + avcodec_parameters_from_context(st->codecpar, c); + return st; } /* essential functions -- start, append, end */ @@ -948,7 +914,7 @@ static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float va static void ffmpeg_add_metadata_callback(void *data, const char *propname, char *propvalue, - int len) + int UNUSED(len)) { AVDictionary **metadata = (AVDictionary **)data; av_dict_set(metadata, propname, propvalue, 0); @@ -1039,7 +1005,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, fmt->audio_codec = context->ffmpeg_audio_codec; - BLI_strncpy(of->filename, name, sizeof(of->filename)); + of->url = av_strdup(name); /* set the codec to the user's selection */ switch (context->ffmpeg_type) { case FFMPEG_AVI: @@ -1104,9 +1070,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->video_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Video stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing video stream"); + PRINT("Error initializing video stream"); } goto fail; } @@ -1118,9 +1086,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->audio_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Audio stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing audio stream"); + PRINT("Error initializing audio stream"); } goto fail; } @@ -1128,6 +1098,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!(fmt->flags & AVFMT_NOFILE)) { if (avio_open(&of->pb, name, AVIO_FLAG_WRITE) < 0) { BKE_report(reports, RPT_ERROR, "Could not open file for writing"); + PRINT("Could not open file for writing\n"); goto fail; } } @@ -1137,10 +1108,12 @@ static int start_ffmpeg_impl(FFMpegContext *context, &of->metadata, context->stamp_data, ffmpeg_add_metadata_callback, false); } - if (avformat_write_header(of, NULL) < 0) { + int ret = avformat_write_header(of, NULL); + if (ret < 0) { BKE_report(reports, RPT_ERROR, "Could not initialize streams, probably unsupported codec combination"); + PRINT("Could not write media header: %s\n", av_err2str(ret)); goto fail; } @@ -1155,13 +1128,11 @@ fail: avio_close(of->pb); } - if (context->video_stream && context->video_stream->codec) { - avcodec_close(context->video_stream->codec); + if (context->video_stream) { context->video_stream = NULL; } - if (context->audio_stream && context->audio_stream->codec) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream) { context->audio_stream = NULL; } @@ -1189,46 +1160,36 @@ fail: */ static void flush_ffmpeg(FFMpegContext *context) { - int ret = 0; + AVCodecContext *c = context->video_codec; + AVPacket *packet = av_packet_alloc(); - AVCodecContext *c = context->video_stream->codec; - /* get the delayed frames */ - while (1) { - int got_output; - AVPacket packet = {0}; - av_init_packet(&packet); + avcodec_send_frame(c, NULL); - ret = avcodec_encode_video2(c, &packet, NULL, &got_output); - if (ret < 0) { - fprintf(stderr, "Error encoding delayed frame %d\n", ret); + /* Get the packets frames. */ + int ret = 1; + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets to flush. */ break; } - if (!got_output) { + if (ret < 0) { + fprintf(stderr, "Error encoding delayed frame: %s\n", av_err2str(ret)); break; } - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); - } - else { - PRINT("Video Frame DTS: not set\n"); - } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - if (ret != 0) { - fprintf(stderr, "Error writing delayed frame %d\n", ret); + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + + int write_ret = av_interleaved_write_frame(context->outfile, packet); + if (write_ret != 0) { + fprintf(stderr, "Error writing delayed frame: %s\n", av_err2str(write_ret)); break; } } - avcodec_flush_buffers(context->video_stream->codec); + + av_packet_free(&packet); } /* ********************************************************************** @@ -1326,7 +1287,8 @@ int BKE_ffmpeg_start(void *context_v, success = start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports); # ifdef WITH_AUDASPACE if (context->audio_stream) { - AVCodecContext *c = context->audio_stream->codec; + AVCodecContext *c = context->audio_codec; + AUD_DeviceSpecs specs; specs.channels = c->channels; @@ -1353,10 +1315,6 @@ int BKE_ffmpeg_start(void *context_v, specs.rate = rd->ffcodecdata.audio_mixrate; context->audio_mixdown_device = BKE_sound_mixdown( scene, specs, preview ? rd->psfra : rd->sfra, rd->ffcodecdata.audio_volume); -# ifdef FFMPEG_CODEC_TIME_BASE - c->time_base.den = specs.rate; - c->time_base.num = 1; -# endif } # endif return success; @@ -1397,8 +1355,8 @@ int BKE_ffmpeg_append(void *context_v, // write_audio_frames(frame / (((double)rd->frs_sec) / rd->frs_sec_base)); if (context->video_stream) { - avframe = generate_video_frame(context, (unsigned char *)pixels, reports); - success = (avframe && write_video_frame(context, rd, frame - start_frame, avframe, reports)); + avframe = generate_video_frame(context, (unsigned char *)pixels); + success = (avframe && write_video_frame(context, frame - start_frame, avframe, reports)); if (context->ffmpeg_autosplit) { if (avio_tell(context->outfile->pb) > FFMPEG_AUTOSPLIT_SIZE) { @@ -1427,9 +1385,11 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) context->audio_mixdown_device = NULL; } } +# else + UNUSED_VARS(is_autosplit); # endif - if (context->video_stream && context->video_stream->codec) { + if (context->video_stream) { PRINT("Flushing delayed frames...\n"); flush_ffmpeg(context); } @@ -1440,14 +1400,12 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) /* Close the video codec */ - if (context->video_stream != NULL && context->video_stream->codec != NULL) { - avcodec_close(context->video_stream->codec); + if (context->video_stream != NULL) { PRINT("zero video stream %p\n", context->video_stream); context->video_stream = NULL; } - if (context->audio_stream != NULL && context->audio_stream->codec != NULL) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream != NULL) { context->audio_stream = NULL; } @@ -1466,6 +1424,16 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) avio_close(context->outfile->pb); } } + + if (context->video_codec != NULL) { + avcodec_free_context(&context->video_codec); + context->video_codec = NULL; + } + if (context->audio_codec != NULL) { + avcodec_free_context(&context->audio_codec); + context->audio_codec = NULL; + } + if (context->outfile != NULL) { avformat_free_context(context->outfile); context->outfile = NULL; @@ -1474,12 +1442,6 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) av_free(context->audio_input_buffer); context->audio_input_buffer = NULL; } -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (context->audio_output_buffer != NULL) { - av_free(context->audio_output_buffer); - context->audio_output_buffer = NULL; - } -# endif if (context->audio_deinterleave_buffer != NULL) { av_free(context->audio_deinterleave_buffer); @@ -1559,12 +1521,12 @@ static IDProperty *BKE_ffmpeg_property_add(RenderData *rd, switch (o->type) { case AV_OPT_TYPE_INT: case AV_OPT_TYPE_INT64: - val.i = FFMPEG_DEF_OPT_VAL_INT(o); + val.i = o->default_val.i64; idp_type = IDP_INT; break; case AV_OPT_TYPE_DOUBLE: case AV_OPT_TYPE_FLOAT: - val.f = FFMPEG_DEF_OPT_VAL_DOUBLE(o); + val.f = o->default_val.dbl; idp_type = IDP_FLOAT; break; case AV_OPT_TYPE_STRING: @@ -1706,16 +1668,9 @@ static void ffmpeg_set_expert_options(RenderData *rd) BKE_ffmpeg_property_add_string(rd, "video", "trellis:0"); BKE_ffmpeg_property_add_string(rd, "video", "weightb:1"); -# ifdef FFMPEG_HAVE_DEPRECATED_FLAGS2 - BKE_ffmpeg_property_add_string(rd, "video", "flags2:dct8x8"); - BKE_ffmpeg_property_add_string(rd, "video", "directpred:3"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:fastpskip"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:wpred"); -# else BKE_ffmpeg_property_add_string(rd, "video", "8x8dct:1"); BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1"); BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2"); -# endif } else if (codec_id == AV_CODEC_ID_DNXHD) { if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { @@ -1870,14 +1825,12 @@ bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd) { int codec = rd->ffcodecdata.codec; -# ifdef FFMPEG_FFV1_ALPHA_SUPPORTED - /* Visual Studio 2019 doesn't like #ifdef within ELEM(). */ - if (codec == AV_CODEC_ID_FFV1) { - return true; - } -# endif - - return ELEM(codec, AV_CODEC_ID_QTRLE, AV_CODEC_ID_PNG, AV_CODEC_ID_VP9, AV_CODEC_ID_HUFFYUV); + return ELEM(codec, + AV_CODEC_ID_FFV1, + AV_CODEC_ID_QTRLE, + AV_CODEC_ID_PNG, + AV_CODEC_ID_VP9, + AV_CODEC_ID_HUFFYUV); } void *BKE_ffmpeg_context_create(void) |