diff options
author | Jacques Lucke <jacques@blender.org> | 2021-06-14 13:44:13 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-06-14 13:44:13 +0300 |
commit | dddcf1e9bbf4a6d1f4ff03eaf0cb7e9228b18ec5 (patch) | |
tree | c20defa7efd54c933d20a296abefe567909bb6c0 /source/blender/blenkernel/intern | |
parent | 3b162b7c185d089e93d892169a458d552196b7b6 (diff) | |
parent | c9dc55301cd7903b7ef7c045d337ada29aa809a1 (diff) |
Merge branch 'master' into temp-compact-node-prototypetemp-compact-node-prototype
Diffstat (limited to 'source/blender/blenkernel/intern')
107 files changed, 10027 insertions, 3836 deletions
diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 9f51ef5292f..d4dd7e248d5 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -712,11 +712,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; + } } } @@ -1603,6 +1605,12 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, /* This geometry set contains the non-mesh data that might be generated by modifiers. */ GeometrySet geometry_set_final; + /* Add the initial mesh component, with a copy of the vertex group names from the object, + * since they need to be stored in the geometry set for evaluation. */ + MeshComponent &initial_mesh_component = + geometry_set_final.get_component_for_write<MeshComponent>(); + initial_mesh_component.copy_vertex_group_names_from_object(*ob); + /* Deformed vertex locations array. Deform only modifier need this type of * float array rather than MVert*. Tracked along with mesh_final as an * optimization to avoid copying coordinates back and forth if there are @@ -1853,9 +1861,11 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, BKE_id_free(nullptr, mesh_orco); } - /* Ensure normals calculation below is correct. */ - BLI_assert((mesh_input->flag & ME_AUTOSMOOTH) == (mesh_final->flag & ME_AUTOSMOOTH)); - BLI_assert(mesh_input->smoothresh == mesh_final->smoothresh); + /* Ensure normals calculation below is correct (normal settings have transferred properly). + * However, nodes modifiers might create meshes from scratch or transfer meshes from other + * objects with different settings, and in general it doesn't make sense to guarantee that + * the settings are the same as the original mesh. If necessary, this could become a modifier + * type flag. */ BLI_assert(mesh_input->smoothresh == mesh_cage->smoothresh); /* Compute normals. */ diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index f9c2a4e53ad..a7e36b09516 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -635,7 +635,7 @@ bPoseChannel *BKE_pose_channel_find_name(const bPose *pose, const char *name) * \note Use with care, not on Armature poses but for temporal ones. * \note (currently used for action constraints and in rebuild_pose). */ -bPoseChannel *BKE_pose_channel_verify(bPose *pose, const char *name) +bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name) { bPoseChannel *chan; @@ -656,7 +656,9 @@ bPoseChannel *BKE_pose_channel_verify(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); @@ -815,7 +817,7 @@ void BKE_pose_copy_data_ex(bPose **dst, */ if (outPose->chanbase.first != outPose->chanbase.last) { outPose->chanhash = NULL; - BKE_pose_channels_hash_make(outPose); + BKE_pose_channels_hash_ensure(outPose); } outPose->iksolver = src->iksolver; @@ -945,7 +947,7 @@ bool BKE_pose_channel_in_IK_chain(Object *ob, bPoseChannel *pchan) * Removes the hash for quick lookup of channels, must * be done when adding/removing channels. */ -void BKE_pose_channels_hash_make(bPose *pose) +void BKE_pose_channels_hash_ensure(bPose *pose) { if (!pose->chanhash) { bPoseChannel *pchan; @@ -1191,7 +1193,7 @@ void BKE_pose_free(bPose *pose) * and ID-Props, used when duplicating bones in editmode. * (unlike copy_pose_channel_data which only does posing-related stuff). * - * \note use when copying bones in editmode (on returned value from #BKE_pose_channel_verify) + * \note use when copying bones in editmode (on returned value from #BKE_pose_channel_ensure) */ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_from) { @@ -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; } @@ -1316,30 +1320,6 @@ void BKE_pose_tag_update_constraint_flags(bPose *pose) pose->flag |= POSE_CONSTRAINTS_NEED_UPDATE_FLAGS; } -/* Clears all BONE_UNKEYED flags for every pose channel in every pose - * This should only be called on frame changing, when it is acceptable to - * do this. Otherwise, these flags should not get cleared as poses may get lost. - */ -void framechange_poses_clear_unkeyed(Main *bmain) -{ - Object *ob; - bPose *pose; - bPoseChannel *pchan; - - /* This needs to be done for each object that has a pose */ - /* TODO: proxies may/may not be correctly handled here... (this needs checking) */ - for (ob = bmain->objects.first; ob; ob = ob->id.next) { - /* we only need to do this on objects with a pose */ - if ((pose = ob->pose)) { - for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { - if (pchan->bone) { - pchan->bone->flag &= ~BONE_UNKEYED; - } - } - } - } -} - /* ************************** Bone Groups ************************** */ /* Adds a new bone-group (name may be NULL) */ @@ -1798,7 +1778,7 @@ void what_does_obaction(Object *ob, * allocation and also will make lookup slower. */ if (pose->chanbase.first != pose->chanbase.last) { - BKE_pose_channels_hash_make(pose); + BKE_pose_channels_hash_ensure(pose); } if (pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(pose); diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c new file mode 100644 index 00000000000..c975d2bfb9c --- /dev/null +++ b/source/blender/blenkernel/intern/action_mirror.c @@ -0,0 +1,457 @@ +/* + * 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. + */ + +/** \file + * \ingroup bke + * + * Mirror/Symmetry functions applying to actions. + */ + +#include <math.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_armature_types.h" +#include "DNA_object_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_string_utils.h" +#include "BLI_utildefines.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_fcurve.h" + +#include "DEG_depsgraph.h" + +/* -------------------------------------------------------------------- */ +/** \name Flip the Action (Armature/Pose Objects) + * + * This flips the action using the rest pose (not the evaluated pose). + * + * Details: + * + * - Key-frames are modified in-place, creating new key-frames is not yet supported. + * That could be useful if a user for example only has 2x rotation channels set. + * In practice users typically keyframe all rotation channels or none. + * + * - F-curve modifiers are disabled for evaluation, + * so the values written back to the keyframes don't include modifier offsets. + * + * - Sub-frame key-frames aren't supported, + * this could be added if needed without much trouble. + * + * - F-curves must have a #FCurve.bezt array (sampled curves aren't supported). + * \{ */ + +/** + * This structure is created for each pose channels F-curve, + * an action be evaluated and stored in `fcurve_eval`, + * with the mirrored values written into `bezt_array`. + * + * Store F-curve evaluated values, constructed with a sorted array of rounded keyed-frames, + * passed to #action_flip_pchan_cache_init. + */ +struct FCurve_KeyCache { + /** + * When NULL, ignore this channel. + */ + FCurve *fcurve; + /** + * Cached evaluated F-curve values (without modifiers). + */ + float *fcurve_eval; + /** + * Cached #FCurve.bezt values, NULL when no key-frame exists on this frame. + * + * \note The case where two keyframes round to the same frame isn't supported. + * In this case only the first will be used. + */ + BezTriple **bezt_array; +}; + +/** + * Assign `fkc` path, using a `path` lookup for a single value. + */ +static void action_flip_pchan_cache_fcurve_assign_value(struct FCurve_KeyCache *fkc, + int index, + const char *path, + struct FCurvePathCache *fcache) +{ + FCurve *fcu = BKE_fcurve_pathcache_find(fcache, path, index); + if (fcu && fcu->bezt) { + fkc->fcurve = fcu; + } +} + +/** + * Assign #FCurve_KeyCache.fcurve path, using a `path` lookup for an array. + */ +static void action_flip_pchan_cache_fcurve_assign_array(struct FCurve_KeyCache *fkc, + int fkc_len, + const char *path, + struct FCurvePathCache *fcache) +{ + FCurve **fcurves = alloca(sizeof(*fcurves) * fkc_len); + if (BKE_fcurve_pathcache_find_array(fcache, path, fcurves, fkc_len)) { + for (int i = 0; i < fkc_len; i++) { + if (fcurves[i] && fcurves[i]->bezt) { + fkc[i].fcurve = fcurves[i]; + } + } + } +} + +/** + * Fill in pose channel cache for each frame in `keyed_frames`. + * + * \param keyed_frames: An array of keyed_frames to evaluate, + * note that each frame is rounded to the nearest int. + * \param keyed_frames_len: The length of the `keyed_frames` array. + */ +static void action_flip_pchan_cache_init(struct FCurve_KeyCache *fkc, + const float *keyed_frames, + int keyed_frames_len) +{ + BLI_assert(fkc->fcurve != NULL); + + /* Cache the F-curve values for `keyed_frames`. */ + const int fcurve_flag = fkc->fcurve->flag; + fkc->fcurve->flag |= FCURVE_MOD_OFF; + fkc->fcurve_eval = MEM_mallocN(sizeof(float) * keyed_frames_len, __func__); + for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) { + const float evaltime = keyed_frames[frame_index]; + fkc->fcurve_eval[frame_index] = evaluate_fcurve_only_curve(fkc->fcurve, evaltime); + } + fkc->fcurve->flag = fcurve_flag; + + /* Cache the #BezTriple for `keyed_frames`, or leave as NULL. */ + fkc->bezt_array = MEM_mallocN(sizeof(*fkc->bezt_array) * keyed_frames_len, __func__); + BezTriple *bezt = fkc->fcurve->bezt; + BezTriple *bezt_end = fkc->fcurve->bezt + fkc->fcurve->totvert; + + int frame_index = 0; + while (frame_index < keyed_frames_len) { + const float evaltime = keyed_frames[frame_index]; + const float bezt_time = roundf(bezt->vec[1][0]); + if (bezt_time > evaltime) { + fkc->bezt_array[frame_index++] = NULL; + } + else { + if (bezt_time == evaltime) { + fkc->bezt_array[frame_index++] = bezt; + } + bezt++; + if (bezt == bezt_end) { + break; + } + } + } + /* Clear remaining unset keyed_frames (if-any). */ + while (frame_index < keyed_frames_len) { + fkc->bezt_array[frame_index++] = NULL; + } +} + +/** + */ +static void action_flip_pchan(Object *ob_arm, + const bPoseChannel *pchan, + struct FCurvePathCache *fcache) +{ + /* Begin F-Curve pose channel value extraction. */ + /* Use a fixed buffer size as it's known this can only be at most: + * `pose.bones["{MAXBONENAME}"].rotation_quaternion`. */ + char path_xform[256]; + char pchan_name_esc[sizeof(((bActionChannel *)NULL)->name) * 2]; + BLI_str_escape(pchan_name_esc, pchan->name, sizeof(pchan_name_esc)); + const int path_xform_prefix_len = SNPRINTF(path_xform, "pose.bones[\"%s\"]", pchan_name_esc); + char *path_xform_suffix = path_xform + path_xform_prefix_len; + const int path_xform_suffix_len = sizeof(path_xform) - path_xform_prefix_len; + + /* Lookup and assign all available #FCurve channels, + * unavailable channels are left NULL. */ + + /** + * Structure to store transformation F-curves corresponding to a pose bones transformation. + * Match struct member names from #bPoseChannel so macros avoid repetition. + * + * \note There is no need to read values unless they influence the 4x4 transform matrix, + * and no need to write values back unless they would be changed by a modified matrix. + * So `rotmode` needs to be read, but doesn't need to be written back to. + * + * Most bendy-bone settings don't need to be included either, flipping their RNA paths is enough. + * Although the X/Y settings could make sense to transform, in practice it would only + * work well if the rotation happened to swap X/Y alignment, leave this for now. + */ + struct { + struct FCurve_KeyCache loc[3], eul[3], quat[4], rotAxis[3], rotAngle, size[3], rotmode; + } fkc_pchan = {{{NULL}}}; + +#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index) \ + BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \ + action_flip_pchan_cache_fcurve_assign_value(&fkc_pchan.id, index, path_xform, fcache) + +#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix) \ + BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \ + action_flip_pchan_cache_fcurve_assign_array( \ + fkc_pchan.id, ARRAY_SIZE(fkc_pchan.id), path_xform, fcache) + + FCURVE_ASSIGN_ARRAY(loc, ".location"); + FCURVE_ASSIGN_ARRAY(eul, ".rotation_euler"); + FCURVE_ASSIGN_ARRAY(quat, ".rotation_quaternion"); + FCURVE_ASSIGN_ARRAY(rotAxis, ".rotation_axis_angle"); + FCURVE_ASSIGN_VALUE(rotAngle, ".rotation_axis_angle", 3); + FCURVE_ASSIGN_ARRAY(size, ".scale"); + FCURVE_ASSIGN_VALUE(rotmode, ".rotation_mode", 0); + +#undef FCURVE_ASSIGN_VALUE +#undef FCURVE_ASSIGN_ARRAY + + /* Array of F-curves, for convenient access. */ +#define FCURVE_CHANNEL_LEN (sizeof(fkc_pchan) / sizeof(struct FCurve_KeyCache)) + FCurve *fcurve_array[FCURVE_CHANNEL_LEN]; + int fcurve_array_len = 0; + + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve != NULL) { + fcurve_array[fcurve_array_len++] = fkc->fcurve; + } + } + + /* If this pose has no transform channels, there is nothing to do. */ + if (fcurve_array_len == 0) { + return; + } + + /* Calculate an array of frames used by any of the key-frames in `fcurve_array`. */ + int keyed_frames_len; + const float *keyed_frames = BKE_fcurves_calc_keyed_frames( + fcurve_array, fcurve_array_len, &keyed_frames_len); + + /* Initialize the pose channel curve cache from the F-curve. */ + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve == NULL) { + continue; + } + action_flip_pchan_cache_init(fkc, keyed_frames, keyed_frames_len); + } + + /* X-axis flipping matrix. */ + float flip_mtx[4][4]; + unit_m4(flip_mtx); + flip_mtx[0][0] = -1; + + bPoseChannel *pchan_flip = NULL; + char pchan_name_flip[MAXBONENAME]; + BLI_string_flip_side_name(pchan_name_flip, pchan->name, false, sizeof(pchan_name_flip)); + if (!STREQ(pchan_name_flip, pchan->name)) { + pchan_flip = BKE_pose_channel_find_name(ob_arm->pose, pchan_name_flip); + } + + float arm_mat_inv[4][4]; + invert_m4_m4(arm_mat_inv, pchan_flip ? pchan_flip->bone->arm_mat : pchan->bone->arm_mat); + + /* Now flip the transformation & write it back to the F-curves in `fkc_pchan`. */ + + for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) { + + /* Temporary pose channel to write values into, + * using the `fkc_pchan` values, falling back to the values in the pose channel. */ + bPoseChannel pchan_temp = *pchan; + + /* Load the values into the channel. */ +#define READ_VALUE_FLT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + pchan_temp.id = fkc_pchan.id.fcurve_eval[frame_index]; \ + } \ + ((void)0) + +#define READ_VALUE_INT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + pchan_temp.id = floorf(fkc_pchan.id.fcurve_eval[frame_index] + 0.5f); \ + } \ + ((void)0) + +#define READ_ARRAY_FLT(id) \ + for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \ + READ_VALUE_FLT(id[i]); \ + } \ + ((void)0) + + READ_ARRAY_FLT(loc); + READ_ARRAY_FLT(eul); + READ_ARRAY_FLT(quat); + READ_ARRAY_FLT(rotAxis); + READ_VALUE_FLT(rotAngle); + READ_ARRAY_FLT(size); + READ_VALUE_INT(rotmode); + +#undef READ_ARRAY_FLT +#undef READ_VALUE_FLT +#undef READ_VALUE_INT + + float chan_mat[4][4]; + BKE_pchan_to_mat4(&pchan_temp, chan_mat); + + /* Move to the pose-space. */ + mul_m4_m4m4(chan_mat, pchan->bone->arm_mat, chan_mat); + + /* Flip the matrix. */ + mul_m4_m4m4(chan_mat, chan_mat, flip_mtx); + mul_m4_m4m4(chan_mat, flip_mtx, chan_mat); + + /* Move back to bone-space space, using the flipped bone if it exists. */ + mul_m4_m4m4(chan_mat, arm_mat_inv, chan_mat); + + BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false); + + /* Write the values back to the F-curves. */ +#define WRITE_VALUE_FLT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + BezTriple *bezt = fkc_pchan.id.bezt_array[frame_index]; \ + if (bezt != NULL) { \ + const float delta = pchan_temp.id - bezt->vec[1][1]; \ + bezt->vec[0][1] += delta; \ + bezt->vec[1][1] += delta; \ + bezt->vec[2][1] += delta; \ + } \ + } \ + ((void)0) + +#define WRITE_ARRAY_FLT(id) \ + for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \ + WRITE_VALUE_FLT(id[i]); \ + } \ + ((void)0) + + /* Write the values back the the F-curves. */ + WRITE_ARRAY_FLT(loc); + WRITE_ARRAY_FLT(eul); + WRITE_ARRAY_FLT(quat); + WRITE_ARRAY_FLT(rotAxis); + WRITE_VALUE_FLT(rotAngle); + WRITE_ARRAY_FLT(size); + /* No need to write back 'rotmode' as it can't be transformed. */ + +#undef WRITE_ARRAY_FLT +#undef WRITE_VALUE_FLT + } + + /* Recalculate handles. */ + for (int i = 0; i < fcurve_array_len; i++) { + calchandles_fcurve_ex(fcurve_array[i], 0); + } + + MEM_freeN((void *)keyed_frames); + + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve_eval) { + MEM_freeN(fkc->fcurve_eval); + } + if (fkc->bezt_array) { + MEM_freeN(fkc->bezt_array); + } + } +} + +/** + * Swap all RNA paths left/right. + */ +static void action_flip_pchan_rna_paths(struct bAction *act) +{ + const char *path_pose_prefix = "pose.bones[\""; + const int path_pose_prefix_len = strlen(path_pose_prefix); + + /* Tag curves that have renamed f-curves. */ + LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) { + agrp->flag &= ~AGRP_TEMP; + } + + LISTBASE_FOREACH (FCurve *, fcu, &act->curves) { + if (!STRPREFIX(fcu->rna_path, path_pose_prefix)) { + continue; + } + + const char *name_esc = fcu->rna_path + path_pose_prefix_len; + const char *name_esc_end = BLI_str_escape_find_quote(name_esc); + + /* While unlikely, an RNA path could be malformed. */ + if (UNLIKELY(name_esc_end == NULL)) { + continue; + } + + char name[MAXBONENAME]; + const size_t name_esc_len = (size_t)(name_esc_end - name_esc); + const size_t name_len = BLI_str_unescape(name, name_esc, name_esc_len); + + /* While unlikely, data paths could be constructed that have longer names than + * are currently supported. */ + if (UNLIKELY(name_len >= sizeof(name))) { + continue; + } + + /* When the flipped name differs, perform the rename. */ + char name_flip[MAXBONENAME]; + BLI_string_flip_side_name(name_flip, name, false, sizeof(name_flip)); + if (!STREQ(name_flip, name)) { + char name_flip_esc[MAXBONENAME * 2]; + BLI_str_escape(name_flip_esc, name_flip, sizeof(name_flip_esc)); + char *path_flip = BLI_sprintfN("pose.bones[\"%s%s", name_flip_esc, name_esc_end); + MEM_freeN(fcu->rna_path); + fcu->rna_path = path_flip; + + if (fcu->grp != NULL) { + fcu->grp->flag |= AGRP_TEMP; + } + } + } + + /* Rename tagged groups. */ + LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) { + if ((agrp->flag & AGRP_TEMP) == 0) { + continue; + } + agrp->flag &= ~AGRP_TEMP; + char name_flip[MAXBONENAME]; + BLI_string_flip_side_name(name_flip, agrp->name, false, sizeof(name_flip)); + if (!STREQ(name_flip, agrp->name)) { + STRNCPY(agrp->name, name_flip); + } + } +} + +void BKE_action_flip_with_pose(struct bAction *act, struct Object *ob_arm) +{ + struct FCurvePathCache *fcache = BKE_fcurve_pathcache_create(&act->curves); + int i; + LISTBASE_FOREACH_INDEX (bPoseChannel *, pchan, &ob_arm->pose->chanbase, i) { + action_flip_pchan(ob_arm, pchan, fcache); + } + BKE_fcurve_pathcache_destroy(fcache); + + action_flip_pchan_rna_paths(act); + + DEG_id_tag_update(&act->id, ID_RECALC_COPY_ON_WRITE); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 633d6202222..44b760aefc8 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -287,8 +287,10 @@ bool BKE_animdata_id_is_animated(const struct ID *id) !BLI_listbase_is_empty(&adt->overrides); } -/** Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). */ +/** + * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of + * `IDTypeInfo` structure). + */ void BKE_animdata_foreach_id(AnimData *adt, LibraryForeachIDData *data) { LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) { @@ -352,7 +354,7 @@ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag) } /* duplicate NLA data */ - BKE_nla_tracks_copy(bmain, &dadt->nla_tracks, &adt->nla_tracks, flag); + BKE_nla_tracks_copy_from_adt(bmain, dadt, adt, flag); /* duplicate drivers (F-Curves) */ BKE_fcurves_copy(&dadt->drivers, &adt->drivers); @@ -945,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); } @@ -1175,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; @@ -1243,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_path.c b/source/blender/blenkernel/intern/anim_path.c index 628e54971ce..af2b386a30a 100644 --- a/source/blender/blenkernel/intern/anim_path.c +++ b/source/blender/blenkernel/intern/anim_path.c @@ -42,157 +42,192 @@ static CLG_LogRef LOG = {"bke.anim"}; /* ******************************************************************** */ /* Curve Paths - for curve deforms and/or curve following */ -/** - * Free curve path data - * - * \note Frees the path itself! - * \note This is increasingly inaccurate with non-uniform #BevPoint subdivisions T24633. - */ -void free_path(Path *path) +static int get_bevlist_seg_array_size(const BevList *bl) { - if (path->data) { - MEM_freeN(path->data); + if (bl->poly >= 0) { + /* Cyclic curve. */ + return bl->nr; } - MEM_freeN(path); + + return bl->nr - 1; } -/** - * Calculate a curve-deform path for a curve - * - Only called from displist.c -> #do_makeDispListCurveTypes - */ -void calc_curvepath(Object *ob, ListBase *nurbs) +int BKE_anim_path_get_array_size(const CurveCache *curve_cache) +{ + BLI_assert(curve_cache != NULL); + + BevList *bl = curve_cache->bev.first; + + BLI_assert(bl != NULL && bl->nr > 1); + + return get_bevlist_seg_array_size(bl); +} + +float BKE_anim_path_get_length(const CurveCache *curve_cache) { - BevList *bl; - BevPoint *bevp, *bevpn, *bevpfirst, *bevplast; - PathPoint *pp; - Nurb *nu; - Path *path; - float *fp, *dist, *maxdist, xyz[3]; - float fac, d = 0, fac1, fac2; - int a, tot, cycl = 0; - - /* in a path vertices are with equal differences: path->len = number of verts */ - /* NOW WITH BEVELCURVE!!! */ + const int seg_size = BKE_anim_path_get_array_size(curve_cache); + return curve_cache->anim_path_accum_length[seg_size - 1]; +} +void BKE_anim_path_calc_data(Object *ob) +{ if (ob == NULL || ob->type != OB_CURVE) { return; } - - if (ob->runtime.curve_cache->path) { - free_path(ob->runtime.curve_cache->path); + if (ob->runtime.curve_cache == NULL) { + CLOG_WARN(&LOG, "No curve cache!"); + return; } - ob->runtime.curve_cache->path = NULL; - - /* weak! can only use first curve */ - bl = ob->runtime.curve_cache->bev.first; + /* We only use the first curve. */ + BevList *bl = ob->runtime.curve_cache->bev.first; if (bl == NULL || !bl->nr) { + CLOG_WARN(&LOG, "No bev list data!"); return; } - nu = nurbs->first; + /* Free old data. */ + if (ob->runtime.curve_cache->anim_path_accum_length) { + MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length); + } - ob->runtime.curve_cache->path = path = MEM_callocN(sizeof(Path), "calc_curvepath"); + /* We assume that we have at least two points. + * If there is less than two points in the curve, + * no BevList should have been generated. + */ + BLI_assert(bl->nr > 1); - /* if POLY: last vertice != first vertice */ - cycl = (bl->poly != -1); + const int seg_size = get_bevlist_seg_array_size(bl); + float *len_data = (float *)MEM_mallocN(sizeof(float) * seg_size, "calcpathdist"); + ob->runtime.curve_cache->anim_path_accum_length = len_data; - tot = cycl ? bl->nr : bl->nr - 1; + BevPoint *bp_arr = bl->bevpoints; + float prev_len = 0.0f; + for (int i = 0; i < bl->nr - 1; i++) { + prev_len += len_v3v3(bp_arr[i].vec, bp_arr[i + 1].vec); + len_data[i] = prev_len; + } - path->len = tot + 1; - /* Exception: vector handle paths and polygon paths should be subdivided - * at least a factor resolution. */ - if (path->len < nu->resolu * SEGMENTSU(nu)) { - path->len = nu->resolu * SEGMENTSU(nu); + if (bl->poly >= 0) { + /* Cyclic curve. */ + len_data[seg_size - 1] = prev_len + len_v3v3(bp_arr[0].vec, bp_arr[bl->nr - 1].vec); } +} + +static void get_curve_points_from_idx(const int idx, + const BevList *bl, + const bool is_cyclic, + BevPoint const **r_p0, + BevPoint const **r_p1, + BevPoint const **r_p2, + BevPoint const **r_p3) +{ + BLI_assert(idx >= 0); + BLI_assert(idx < bl->nr - 1 || (is_cyclic && idx < bl->nr)); + BLI_assert(bl->nr > 1); - dist = (float *)MEM_mallocN(sizeof(float) * (tot + 1), "calcpathdist"); + const BevPoint *bp_arr = bl->bevpoints; - /* all lengths in *dist */ - bevp = bevpfirst = bl->bevpoints; - fp = dist; - *fp = 0.0f; - for (a = 0; a < tot; a++) { - fp++; - if (cycl && a == tot - 1) { - sub_v3_v3v3(xyz, bevpfirst->vec, bevp->vec); + /* First segment. */ + if (idx == 0) { + *r_p1 = &bp_arr[0]; + if (is_cyclic) { + *r_p0 = &bp_arr[bl->nr - 1]; } else { - sub_v3_v3v3(xyz, (bevp + 1)->vec, bevp->vec); + *r_p0 = *r_p1; } - *fp = *(fp - 1) + len_v3(xyz); - bevp++; - } + *r_p2 = &bp_arr[1]; - path->totdist = *fp; - - /* the path verts in path->data */ - /* now also with TILT value */ - pp = path->data = (PathPoint *)MEM_callocN(sizeof(PathPoint) * path->len, "pathdata"); - - bevp = bevpfirst; - bevpn = bevp + 1; - bevplast = bevpfirst + (bl->nr - 1); - if (UNLIKELY(bevpn > bevplast)) { - bevpn = cycl ? bevpfirst : bevplast; - } - fp = dist + 1; - maxdist = dist + tot; - fac = 1.0f / ((float)path->len - 1.0f); - fac = fac * path->totdist; - - for (a = 0; a < path->len; a++) { - - d = ((float)a) * fac; - - /* we're looking for location (distance) 'd' in the array */ - if (LIKELY(tot > 0)) { - while ((fp < maxdist) && (d >= *fp)) { - fp++; - if (bevp < bevplast) { - bevp++; - } - bevpn = bevp + 1; - if (UNLIKELY(bevpn > bevplast)) { - bevpn = cycl ? bevpfirst : bevplast; - } - } - - fac1 = (*(fp)-d) / (*(fp) - *(fp - 1)); - fac2 = 1.0f - fac1; + if (bl->nr > 2) { + *r_p3 = &bp_arr[2]; } else { - fac1 = 1.0f; - fac2 = 0.0f; + *r_p3 = *r_p2; } + return; + } - interp_v3_v3v3(pp->vec, bevp->vec, bevpn->vec, fac2); - pp->vec[3] = fac1 * bevp->tilt + fac2 * bevpn->tilt; - pp->radius = fac1 * bevp->radius + fac2 * bevpn->radius; - pp->weight = fac1 * bevp->weight + fac2 * bevpn->weight; - interp_qt_qtqt(pp->quat, bevp->quat, bevpn->quat, fac2); - normalize_qt(pp->quat); + /* Last segment (or next to last in a cyclic curve). */ + if (idx == bl->nr - 2) { + /* The case when the bl->nr == 2 falls in to the "first segment" check above. + * So here we can assume that bl->nr > 2. + */ + *r_p0 = &bp_arr[idx - 1]; + *r_p1 = &bp_arr[idx]; + *r_p2 = &bp_arr[idx + 1]; + + if (is_cyclic) { + *r_p3 = &bp_arr[0]; + } + else { + *r_p3 = *r_p2; + } + return; + } - pp++; + if (idx == bl->nr - 1) { + /* Last segment in a cyclic curve. This should only trigger if the curve is cyclic + * as it gets an extra segment between the end and the start point. */ + *r_p0 = &bp_arr[idx - 1]; + *r_p1 = &bp_arr[idx]; + *r_p2 = &bp_arr[0]; + *r_p3 = &bp_arr[1]; + return; } - MEM_freeN(dist); + /* To get here the curve has to have four curve points or more and idx can't + * be the first or the last segment. + * So we can assume that we can get four points without any special checks. + */ + *r_p0 = &bp_arr[idx - 1]; + *r_p1 = &bp_arr[idx]; + *r_p2 = &bp_arr[idx + 1]; + *r_p3 = &bp_arr[idx + 2]; } -static int interval_test(const int min, const int max, int p1, const int cycl) +static bool binary_search_anim_path(const float *accum_len_arr, + const int seg_size, + const float goal_len, + int *r_idx, + float *r_frac) { - if (cycl) { - p1 = mod_i(p1 - min, (max - min + 1)) + min; - } - else { - if (p1 < min) { - p1 = min; + float left_len, right_len; + int cur_idx = 0, cur_base = 0; + int cur_step = seg_size - 1; + + while (true) { + cur_idx = cur_base + cur_step / 2; + left_len = accum_len_arr[cur_idx]; + right_len = accum_len_arr[cur_idx + 1]; + + if (left_len <= goal_len && right_len > goal_len) { + *r_idx = cur_idx + 1; + *r_frac = (goal_len - left_len) / (right_len - left_len); + return true; } - else if (p1 > max) { - p1 = max; + if (cur_idx == 0) { + /* We ended up at the first segment. The point must be in here. */ + *r_idx = 0; + *r_frac = goal_len / accum_len_arr[0]; + return true; } + + if (UNLIKELY(cur_step == 0)) { + /* This should never happen unless there is something horribly wrong. */ + CLOG_ERROR(&LOG, "Couldn't find any valid point on the animation path!"); + BLI_assert(!"Couldn't find any valid point on the animation path!"); + return false; + } + + if (left_len < goal_len) { + /* Go to the right. */ + cur_base = cur_idx + 1; + cur_step--; + } /* Else, go to the left. */ + + cur_step /= 2; } - return p1; } /** @@ -203,66 +238,70 @@ static int interval_test(const int min, const int max, int p1, const int cycl) * * \return success. */ -bool where_on_path(const Object *ob, - float ctime, - float r_vec[4], - float r_dir[3], - float r_quat[4], - float *r_radius, - float *r_weight) +bool BKE_where_on_path(const Object *ob, + float ctime, + float r_vec[4], + float r_dir[3], + float r_quat[4], + float *r_radius, + float *r_weight) { - Curve *cu; - const Nurb *nu; - const BevList *bl; - const Path *path; - const PathPoint *pp, *p0, *p1, *p2, *p3; - float fac; - float data[4]; - int cycl = 0, s0, s1, s2, s3; - const ListBase *nurbs; - if (ob == NULL || ob->type != OB_CURVE) { return false; } - cu = ob->data; - if (ob->runtime.curve_cache == NULL || ob->runtime.curve_cache->path == NULL || - ob->runtime.curve_cache->path->data == NULL) { - CLOG_WARN(&LOG, "no path!"); - return false; - } - path = ob->runtime.curve_cache->path; - pp = path->data; - - /* test for cyclic */ - bl = ob->runtime.curve_cache->bev.first; - if (!bl) { + Curve *cu = ob->data; + if (ob->runtime.curve_cache == NULL) { + CLOG_WARN(&LOG, "No curve cache!"); return false; } - if (!bl->nr) { + /* We only use the first curve. */ + BevList *bl = ob->runtime.curve_cache->bev.first; + if (bl == NULL || !bl->nr) { + CLOG_WARN(&LOG, "No bev list data!"); return false; } - if (bl->poly > -1) { - cycl = 1; + + /* Test for cyclic curve. */ + const bool is_cyclic = bl->poly >= 0; + + if (is_cyclic) { + /* Wrap the time into a 0.0 - 1.0 range. */ + if (ctime < 0.0f || ctime > 1.0f) { + ctime -= floorf(ctime); + } } - /* values below zero for non-cyclic curves give strange results */ - BLI_assert(cycl || ctime >= 0.0f); + /* The curve points for this ctime value. */ + const BevPoint *p0, *p1, *p2, *p3; - ctime *= (path->len - 1); + float frac; + const int seg_size = get_bevlist_seg_array_size(bl); + const float *accum_len_arr = ob->runtime.curve_cache->anim_path_accum_length; + const float goal_len = ctime * accum_len_arr[seg_size - 1]; - s1 = (int)floor(ctime); - fac = (float)(s1 + 1) - ctime; + /* Are we simply trying to get the start/end point? */ + if (ctime <= 0.0f || ctime >= 1.0f) { + const float clamp_time = clamp_f(ctime, 0.0f, 1.0f); + const int idx = clamp_time * (seg_size - 1); + get_curve_points_from_idx(idx, bl, is_cyclic, &p0, &p1, &p2, &p3); - /* path->len is corrected for cyclic */ - s0 = interval_test(0, path->len - 1 - cycl, s1 - 1, cycl); - s1 = interval_test(0, path->len - 1 - cycl, s1, cycl); - s2 = interval_test(0, path->len - 1 - cycl, s1 + 1, cycl); - s3 = interval_test(0, path->len - 1 - cycl, s1 + 2, cycl); + if (idx == 0) { + frac = goal_len / accum_len_arr[0]; + } + else { + frac = (goal_len - accum_len_arr[idx - 1]) / (accum_len_arr[idx] - accum_len_arr[idx - 1]); + } + } + else { + /* Do binary search to get the correct segment. */ + int idx; + const bool found_idx = binary_search_anim_path(accum_len_arr, seg_size, goal_len, &idx, &frac); - p0 = pp + s0; - p1 = pp + s1; - p2 = pp + s2; - p3 = pp + s3; + if (UNLIKELY(!found_idx)) { + return false; + } + get_curve_points_from_idx(idx, bl, is_cyclic, &p0, &p1, &p2, &p3); + } /* NOTE: commented out for follow constraint * @@ -272,65 +311,68 @@ bool where_on_path(const Object *ob, */ // if (cu->flag & CU_FOLLOW) { - key_curve_tangent_weights(1.0f - fac, data, KEY_BSPLINE); + float w[4]; + + key_curve_tangent_weights(frac, w, KEY_BSPLINE); - interp_v3_v3v3v3v3(r_dir, p0->vec, p1->vec, p2->vec, p3->vec, data); + interp_v3_v3v3v3v3(r_dir, p0->vec, p1->vec, p2->vec, p3->vec, w); /* Make compatible with #vec_to_quat. */ negate_v3(r_dir); //} - nurbs = BKE_curve_editNurbs_get(cu); + const ListBase *nurbs = BKE_curve_editNurbs_get(cu); if (!nurbs) { nurbs = &cu->nurb; } - nu = nurbs->first; + const Nurb *nu = nurbs->first; /* make sure that first and last frame are included in the vectors here */ - if (nu->type == CU_POLY) { - key_curve_position_weights(1.0f - fac, data, KEY_LINEAR); + if (ELEM(nu->type, CU_POLY, CU_BEZIER, CU_NURBS)) { + key_curve_position_weights(frac, w, KEY_LINEAR); } - else if (nu->type == CU_BEZIER) { - key_curve_position_weights(1.0f - fac, data, KEY_LINEAR); - } - else if (s0 == s1 || p2 == p3) { - key_curve_position_weights(1.0f - fac, data, KEY_CARDINAL); + else if (p2 == p3) { + key_curve_position_weights(frac, w, KEY_CARDINAL); } else { - key_curve_position_weights(1.0f - fac, data, KEY_BSPLINE); + key_curve_position_weights(frac, w, KEY_BSPLINE); } r_vec[0] = /* X */ - data[0] * p0->vec[0] + data[1] * p1->vec[0] + data[2] * p2->vec[0] + data[3] * p3->vec[0]; + w[0] * p0->vec[0] + w[1] * p1->vec[0] + w[2] * p2->vec[0] + w[3] * p3->vec[0]; r_vec[1] = /* Y */ - data[0] * p0->vec[1] + data[1] * p1->vec[1] + data[2] * p2->vec[1] + data[3] * p3->vec[1]; + w[0] * p0->vec[1] + w[1] * p1->vec[1] + w[2] * p2->vec[1] + w[3] * p3->vec[1]; r_vec[2] = /* Z */ - data[0] * p0->vec[2] + data[1] * p1->vec[2] + data[2] * p2->vec[2] + data[3] * p3->vec[2]; - r_vec[3] = /* Tilt, should not be needed since we have quat still used */ - data[0] * p0->vec[3] + data[1] * p1->vec[3] + data[2] * p2->vec[3] + data[3] * p3->vec[3]; + w[0] * p0->vec[2] + w[1] * p1->vec[2] + w[2] * p2->vec[2] + w[3] * p3->vec[2]; + + /* Clamp weights to 0-1 as we don't want to extrapolate other values than position. */ + clamp_v4(w, 0.0f, 1.0f); + + /* Tilt, should not be needed since we have quat still used. */ + r_vec[3] = w[0] * p0->tilt + w[1] * p1->tilt + w[2] * p2->tilt + w[3] * p3->tilt; if (r_quat) { float totfac, q1[4], q2[4]; - totfac = data[0] + data[3]; + totfac = w[0] + w[3]; if (totfac > FLT_EPSILON) { - interp_qt_qtqt(q1, p0->quat, p3->quat, data[3] / totfac); + interp_qt_qtqt(q1, p0->quat, p3->quat, w[3] / totfac); } else { copy_qt_qt(q1, p1->quat); } - totfac = data[1] + data[2]; + totfac = w[1] + w[2]; if (totfac > FLT_EPSILON) { - interp_qt_qtqt(q2, p1->quat, p2->quat, data[2] / totfac); + interp_qt_qtqt(q2, p1->quat, p2->quat, w[2] / totfac); } else { copy_qt_qt(q2, p3->quat); } - totfac = data[0] + data[1] + data[2] + data[3]; + totfac = w[0] + w[1] + w[2] + w[3]; if (totfac > FLT_EPSILON) { - interp_qt_qtqt(r_quat, q1, q2, (data[1] + data[2]) / totfac); + interp_qt_qtqt(r_quat, q1, q2, (w[1] + w[2]) / totfac); } else { copy_qt_qt(r_quat, q2); @@ -338,13 +380,11 @@ bool where_on_path(const Object *ob, } if (r_radius) { - *r_radius = data[0] * p0->radius + data[1] * p1->radius + data[2] * p2->radius + - data[3] * p3->radius; + *r_radius = w[0] * p0->radius + w[1] * p1->radius + w[2] * p2->radius + w[3] * p3->radius; } if (r_weight) { - *r_weight = data[0] * p0->weight + data[1] * p1->weight + data[2] * p2->weight + - data[3] * p3->weight; + *r_weight = w[0] * p0->weight + w[1] * p1->weight + w[2] * p2->weight + w[3] * p3->weight; } return true; diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 9a890fd02be..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); } @@ -1605,8 +1607,9 @@ static bool nla_combine_get_inverted_strip_value(const int mix_mode, } } -/** Accumulate quaternion channels for Combine mode according to influence. - * \returns blended_value = lower_values @ strip_values^infl +/** + * Accumulate quaternion channels for Combine mode according to influence. + * \returns `blended_value = lower_values @ strip_values^infl` */ static void nla_combine_quaternion(const float lower_values[4], const float strip_values[4], @@ -1649,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? */ @@ -2047,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); } } @@ -2094,8 +2454,10 @@ static void animsys_evaluate_nla_domain(PointerRNA *ptr, NlaEvalData *channels, /* ---------------------- */ -/** Tweaked strip is evaluated differently from other strips. Adjacent strips are ignored - * and includes a workaround for when user is not editing in place. */ +/** + * Tweaked strip is evaluated differently from other strips. Adjacent strips are ignored + * and includes a workaround for when user is not editing in place. + */ static void animsys_create_tweak_strip(const AnimData *adt, const bool keyframing_to_strip, NlaStrip *r_tweak_strip) @@ -2210,8 +2572,10 @@ static bool is_nlatrack_evaluatable(const AnimData *adt, const NlaTrack *nlt) return true; } -/** Check for special case of non-pushed action being evaluated with no NLA influence (off and no - * strips evaluated) nor NLA interference (ensure NLA not soloing). */ +/** + * Check for special case of non-pushed action being evaluated with no NLA influence (off and no + * strips evaluated) nor NLA interference (ensure NLA not soloing). + */ static bool is_action_track_evaluated_without_nla(const AnimData *adt, const bool any_strip_evaluated) { @@ -2491,12 +2855,13 @@ 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 +/** + * 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, @@ -2507,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) { @@ -2524,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); + } +} + +/** + * 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); - /** 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) { + 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); } } @@ -2664,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/appdir.c b/source/blender/blenkernel/intern/appdir.c index 1075a46e72b..bcfd34ab42f 100644 --- a/source/blender/blenkernel/intern/appdir.c +++ b/source/blender/blenkernel/intern/appdir.c @@ -137,7 +137,7 @@ static char *blender_version_decimal(const int version) { static char version_str[5]; BLI_assert(version < 1000); - BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", version / 100, version % 100); + BLI_snprintf(version_str, sizeof(version_str), "%d.%d", version / 100, version % 100); return version_str; } diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index a1ebec1d756..4ea71922df5 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2444,7 +2444,7 @@ static void pose_proxy_sync(Object *ob, Object *from, int layer_protected) static int rebuild_pose_bone( bPose *pose, Bone *bone, bPoseChannel *parchan, int counter, Bone **r_last_visited_bone_p) { - bPoseChannel *pchan = BKE_pose_channel_verify(pose, bone->name); /* verify checks and/or adds */ + bPoseChannel *pchan = BKE_pose_channel_ensure(pose, bone->name); /* verify checks and/or adds */ pchan->bone = bone; pchan->parent = parchan; @@ -2515,6 +2515,17 @@ void BKE_pchan_rebuild_bbone_handles(bPose *pose, bPoseChannel *pchan) pchan->bbone_next = pose_channel_find_bone(pose, pchan->bone->bbone_next); } +void BKE_pose_channels_clear_with_null_bone(bPose *pose, const bool do_id_user) +{ + LISTBASE_FOREACH_MUTABLE (bPoseChannel *, pchan, &pose->chanbase) { + if (pchan->bone == NULL) { + BKE_pose_channel_free_ex(pchan, do_id_user); + BKE_pose_channels_hash_free(pose); + BLI_freelinkN(&pose->chanbase, pchan); + } + } +} + /** * Only after leave editmode, duplicating, validating older files, library syncing. * @@ -2526,7 +2537,7 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ { Bone *bone; bPose *pose; - bPoseChannel *pchan, *next; + bPoseChannel *pchan; int counter = 0; /* only done here */ @@ -2549,16 +2560,9 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ } /* and a check for garbage */ - for (pchan = pose->chanbase.first; pchan; pchan = next) { - next = pchan->next; - if (pchan->bone == NULL) { - BKE_pose_channel_free_ex(pchan, do_id_user); - BKE_pose_channels_hash_free(pose); - BLI_freelinkN(&pose->chanbase, pchan); - } - } + BKE_pose_channels_clear_with_null_bone(pose, do_id_user); - BKE_pose_channels_hash_make(pose); + BKE_pose_channels_hash_ensure(pose); for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { /* Find the custom B-Bone handles. */ @@ -2877,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)); + rescale_m4(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_deform.c b/source/blender/blenkernel/intern/armature_deform.c index 8711a001e32..bca5503c8d2 100644 --- a/source/blender/blenkernel/intern/armature_deform.c +++ b/source/blender/blenkernel/intern/armature_deform.c @@ -444,7 +444,9 @@ static void armature_vert_task(void *__restrict userdata, armature_vert_task_with_dvert(data, i, dvert); } -static void armature_vert_task_editmesh(void *__restrict userdata, MempoolIterData *iter) +static void armature_vert_task_editmesh(void *__restrict userdata, + MempoolIterData *iter, + const TaskParallelTLS *__restrict UNUSED(tls)) { const ArmatureUserdata *data = userdata; BMVert *v = (BMVert *)iter; @@ -452,7 +454,9 @@ static void armature_vert_task_editmesh(void *__restrict userdata, MempoolIterDa armature_vert_task_with_dvert(data, BM_elem_index_get(v), dvert); } -static void armature_vert_task_editmesh_no_dvert(void *__restrict userdata, MempoolIterData *iter) +static void armature_vert_task_editmesh_no_dvert(void *__restrict userdata, + MempoolIterData *iter, + const TaskParallelTLS *__restrict UNUSED(tls)) { const ArmatureUserdata *data = userdata; BMVert *v = (BMVert *)iter; @@ -593,12 +597,16 @@ static void armature_deform_coords_impl(const Object *ob_arm, * have already been properly set. */ BM_mesh_elem_index_ensure(em_target->bm, BM_VERT); + TaskParallelSettings settings; + BLI_parallel_mempool_settings_defaults(&settings); + if (use_dverts) { - BLI_task_parallel_mempool(em_target->bm->vpool, &data, armature_vert_task_editmesh, true); + BLI_task_parallel_mempool( + em_target->bm->vpool, &data, armature_vert_task_editmesh, &settings); } else { BLI_task_parallel_mempool( - em_target->bm->vpool, &data, armature_vert_task_editmesh_no_dvert, true); + em_target->bm->vpool, &data, armature_vert_task_editmesh_no_dvert, &settings); } } else { diff --git a/source/blender/blenkernel/intern/armature_pose.cc b/source/blender/blenkernel/intern/armature_pose.cc index bb371b16c42..ca11692372b 100644 --- a/source/blender/blenkernel/intern/armature_pose.cc +++ b/source/blender/blenkernel/intern/armature_pose.cc @@ -39,7 +39,7 @@ namespace { using BoneNameSet = blender::Set<std::string>; // Forward declarations. -BoneNameSet pose_apply_find_selected_bones(const bPose *pose); +BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose); void pose_apply_disable_fcurves_for_unselected_bones(bAction *action, const BoneNameSet &selected_bone_names); void pose_apply_restore_fcurves(bAction *action); @@ -54,7 +54,8 @@ void BKE_pose_apply_action(struct Object *ob, return; } - const BoneNameSet selected_bone_names = pose_apply_find_selected_bones(pose); + const bArmature *armature = (bArmature *)ob->data; + const BoneNameSet selected_bone_names = pose_apply_find_selected_bones(armature, pose); const bool limit_to_selected_bones = !selected_bone_names.is_empty(); if (limit_to_selected_bones) { @@ -74,15 +75,14 @@ void BKE_pose_apply_action(struct Object *ob, } namespace { -BoneNameSet pose_apply_find_selected_bones(const bPose *pose) +BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose) { BoneNameSet selected_bone_names; bool all_bones_selected = true; bool no_bones_selected = true; LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) { - const bool is_selected = (pchan->bone->flag & BONE_SELECTED) != 0 && - (pchan->bone->flag & BONE_HIDDEN_P) == 0; + const bool is_selected = PBONE_SELECTED(armature, pchan->bone); all_bones_selected &= is_selected; no_bones_selected &= !is_selected; diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 8f74b8ff054..4504f10967c 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -63,40 +63,40 @@ typedef struct tSplineIK_Tree { bPoseChannel *root; /* bone that is the root node of the chain */ - bConstraint *con; /* constraint for this chain */ - bSplineIKConstraint *ikData; /* constraint settings for this chain */ + bConstraint *con; /* constraint for this chain */ + bSplineIKConstraint *ik_data; /* constraint settings for this chain */ } tSplineIK_Tree; /* ----------- */ -/* Tag the bones in the chain formed by the given bone for IK */ +/* Tag the bones in the chain formed by the given bone for IK. */ static void splineik_init_tree_from_pchan(Scene *UNUSED(scene), Object *UNUSED(ob), bPoseChannel *pchan_tip) { - bPoseChannel *pchan, *pchanRoot = NULL; - bPoseChannel *pchanChain[255]; + bPoseChannel *pchan, *pchan_root = NULL; + bPoseChannel *pchan_chain[255]; bConstraint *con = NULL; - bSplineIKConstraint *ikData = NULL; - float boneLengths[255]; - float totLength = 0.0f; + bSplineIKConstraint *ik_data = NULL; + float bone_lengths[255]; + float totlength = 0.0f; int segcount = 0; - /* find the SplineIK constraint */ + /* Find the SplineIK constraint. */ for (con = pchan_tip->constraints.first; con; con = con->next) { if (con->type == CONSTRAINT_TYPE_SPLINEIK) { - ikData = con->data; + ik_data = con->data; - /* target can only be curve */ - if ((ikData->tar == NULL) || (ikData->tar->type != OB_CURVE)) { + /* Target can only be a curve. */ + if ((ik_data->tar == NULL) || (ik_data->tar->type != OB_CURVE)) { continue; } - /* skip if disabled */ + /* Skip if disabled. */ if ((con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF))) { continue; } - /* otherwise, constraint is ok... */ + /* Otherwise, constraint is ok... */ break; } } @@ -104,102 +104,102 @@ static void splineik_init_tree_from_pchan(Scene *UNUSED(scene), return; } - /* find the root bone and the chain of bones from the root to the tip + /* Find the root bone and the chain of bones from the root to the tip. * NOTE: this assumes that the bones are connected, but that may not be true... */ - for (pchan = pchan_tip; pchan && (segcount < ikData->chainlen); + for (pchan = pchan_tip; pchan && (segcount < ik_data->chainlen); pchan = pchan->parent, segcount++) { - /* store this segment in the chain */ - pchanChain[segcount] = pchan; + /* Store this segment in the chain. */ + pchan_chain[segcount] = pchan; - /* if performing rebinding, calculate the length of the bone */ - boneLengths[segcount] = pchan->bone->length; - totLength += boneLengths[segcount]; + /* If performing rebinding, calculate the length of the bone. */ + bone_lengths[segcount] = pchan->bone->length; + totlength += bone_lengths[segcount]; } if (segcount == 0) { return; } - pchanRoot = pchanChain[segcount - 1]; + pchan_root = pchan_chain[segcount - 1]; - /* perform binding step if required */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { + /* Perform binding step if required. */ + if ((ik_data->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { float segmentLen = (1.0f / (float)segcount); - /* setup new empty array for the points list */ - if (ikData->points) { - MEM_freeN(ikData->points); + /* Setup new empty array for the points list. */ + if (ik_data->points) { + MEM_freeN(ik_data->points); } - ikData->numpoints = ikData->chainlen + 1; - ikData->points = MEM_mallocN(sizeof(float) * ikData->numpoints, "Spline IK Binding"); + ik_data->numpoints = ik_data->chainlen + 1; + ik_data->points = MEM_mallocN(sizeof(float) * ik_data->numpoints, "Spline IK Binding"); - /* bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint) */ - ikData->points[0] = 1.0f; + /* Bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint). */ + ik_data->points[0] = 1.0f; - /* perform binding of the joints to parametric positions along the curve based - * proportion of the total length that each bone occupies + /* Perform binding of the joints to parametric positions along the curve based + * proportion of the total length that each bone occupies. */ for (int i = 0; i < segcount; i++) { - /* 'head' joints, traveling towards the root of the chain - * - 2 methods; the one chosen depends on whether we've got usable lengths + /* 'head' joints, traveling towards the root of the chain. + * - 2 methods; the one chosen depends on whether we've got usable lengths. */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totLength == 0.0f)) { - /* 1) equi-spaced joints */ - ikData->points[i + 1] = ikData->points[i] - segmentLen; + if ((ik_data->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totlength == 0.0f)) { + /* 1) Equi-spaced joints. */ + ik_data->points[i + 1] = ik_data->points[i] - segmentLen; } else { - /* 2) to find this point on the curve, we take a step from the previous joint - * a distance given by the proportion that this bone takes + /* 2) To find this point on the curve, we take a step from the previous joint + * a distance given by the proportion that this bone takes. */ - ikData->points[i + 1] = ikData->points[i] - (boneLengths[i] / totLength); + ik_data->points[i + 1] = ik_data->points[i] - (bone_lengths[i] / totlength); } } - /* spline has now been bound */ - ikData->flag |= CONSTRAINT_SPLINEIK_BOUND; + /* Spline has now been bound. */ + ik_data->flag |= CONSTRAINT_SPLINEIK_BOUND; } - /* disallow negative values (happens with float precision) */ - CLAMP_MIN(ikData->points[segcount], 0.0f); + /* Disallow negative values (happens with float precision). */ + CLAMP_MIN(ik_data->points[segcount], 0.0f); - /* make a new Spline-IK chain, and store it in the IK chains */ + /* Make a new Spline-IK chain, and store it in the IK chains. */ /* TODO: we should check if there is already an IK chain on this, * since that would take precedence... */ { - /* make new tree */ + /* Make a new tree. */ tSplineIK_Tree *tree = MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree"); tree->type = CONSTRAINT_TYPE_SPLINEIK; tree->chainlen = segcount; - tree->totlength = totLength; + tree->totlength = totlength; - /* copy over the array of links to bones in the chain (from tip to root) */ + /* Copy over the array of links to bones in the chain (from tip to root). */ tree->chain = MEM_mallocN(sizeof(bPoseChannel *) * segcount, "SplineIK Chain"); - memcpy(tree->chain, pchanChain, sizeof(bPoseChannel *) * segcount); + memcpy(tree->chain, pchan_chain, sizeof(bPoseChannel *) * segcount); - /* store reference to joint position array */ - tree->points = ikData->points; + /* Store reference to joint position array. */ + tree->points = ik_data->points; - /* store references to different parts of the chain */ - tree->root = pchanRoot; + /* Store references to different parts of the chain. */ + tree->root = pchan_root; tree->con = con; - tree->ikData = ikData; + tree->ik_data = ik_data; - /* AND! link the tree to the root */ - BLI_addtail(&pchanRoot->siktree, tree); + /* AND! Link the tree to the root. */ + BLI_addtail(&pchan_root->siktree, tree); } - /* mark root channel having an IK tree */ - pchanRoot->flag |= POSE_IKSPLINE; + /* Mark root channel having an IK tree. */ + pchan_root->flag |= POSE_IKSPLINE; } -/* Tag which bones are members of Spline IK chains */ +/* Tag which bones are members of Spline IK chains. */ static void splineik_init_tree(Scene *scene, Object *ob, float UNUSED(ctime)) { bPoseChannel *pchan; - /* find the tips of Spline IK chains, - * which are simply the bones which have been tagged as such */ + /* Find the tips of Spline IK chains, + * which are simply the bones which have been tagged as such. */ for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { if (pchan->constflag & PCHAN_HAS_SPLINEIK) { splineik_init_tree_from_pchan(scene, ob, pchan); @@ -213,23 +213,26 @@ typedef struct tSplineIk_EvalState { float curve_position; /* Current position along the curve. */ float curve_scale; /* Global scale to apply to curve positions. */ float locrot_offset[4][4]; /* Bone rotation and location offset inherited from parent. */ + float prev_tail_loc[3]; /* Tail location of the previous bone. */ + float prev_tail_radius; /* Tail curve radius of the previous bone. */ + int prev_tail_seg_idx; /* Curve segment the previous tail bone belongs to. */ } tSplineIk_EvalState; /* Prepare data to evaluate spline IK. */ static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *state) { - bSplineIKConstraint *ikData = tree->ikData; + bSplineIKConstraint *ik_data = tree->ik_data; /* Make sure that the constraint targets are ok, to avoid crashes * in case of a depsgraph bug or dependency cycle. */ - if (ikData->tar == NULL) { + if (ik_data->tar == NULL) { return false; } - CurveCache *cache = ikData->tar->runtime.curve_cache; + CurveCache *cache = ik_data->tar->runtime.curve_cache; - if (ELEM(NULL, cache, cache->path, cache->path->data)) { + if (ELEM(NULL, cache, cache->anim_path_accum_length)) { return false; } @@ -237,97 +240,257 @@ static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *st state->curve_position = 0.0f; state->curve_scale = 1.0f; unit_m4(state->locrot_offset); + zero_v3(state->prev_tail_loc); + state->prev_tail_radius = 1.0f; + state->prev_tail_seg_idx = 0; /* Apply corrections for sensitivity to scaling. */ - if ((ikData->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) { - /* get the current length of the curve */ - /* NOTE: this is assumed to be correct even after the curve was resized */ - float splineLen = cache->path->totdist; + if ((ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) { + /* Get the current length of the curve. */ + /* NOTE: This is assumed to be correct even after the curve was resized. */ + const float spline_len = BKE_anim_path_get_length(cache); - /* calculate the scale factor to multiply all the path values by so that the - * bone chain retains its current length, such that + /* Calculate the scale factor to multiply all the path values by so that the + * bone chain retains its current length, such that: * maxScale * splineLen = totLength */ - state->curve_scale = tree->totlength / splineLen; + state->curve_scale = tree->totlength / spline_len; } return true; } +static void apply_curve_transform( + bSplineIKConstraint *ik_data, Object *ob, float radius, float r_vec[3], float *r_radius) +{ + /* Apply the curve's object-mode transforms to the position + * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root). + */ + if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { + mul_m4_v3(ik_data->tar->obmat, r_vec); + } + + /* Convert the position to pose-space. */ + mul_m4_v3(ob->imat, r_vec); + + /* Set the new radius (it should be the average value). */ + *r_radius = (radius + *r_radius) / 2; +} + +/* This function positions the tail of the bone so that it preserves the length of it. + * The length of the bone can be seen as a sphere radius. + */ +static int position_tail_on_spline(bSplineIKConstraint *ik_data, + const float head_pos[3], + const float sphere_radius, + const int prev_seg_idx, + float r_tail_pos[3], + float *r_new_curve_pos, + float *r_radius) +{ + /* This is using the tessellated curve data. + * So we are working with piece-wise linear curve segments. + * The same method is use in #BKE_where_on_path to get curve location data. */ + const CurveCache *cache = ik_data->tar->runtime.curve_cache; + const BevList *bl = cache->bev.first; + BevPoint *bp = bl->bevpoints; + const float spline_len = BKE_anim_path_get_length(cache); + const float *seg_accum_len = cache->anim_path_accum_length; + + int max_seg_idx = BKE_anim_path_get_array_size(cache) - 1; + + /* Convert our initial intersection point guess to a point index. + * If the curve was a straight line, then pointEnd would be the correct location. + * So make it our first initial guess. + */ + const float guessed_len = *r_new_curve_pos * spline_len; + + BLI_assert(prev_seg_idx >= 0); + + int cur_seg_idx = prev_seg_idx; + while (cur_seg_idx < max_seg_idx && guessed_len > seg_accum_len[cur_seg_idx]) { + cur_seg_idx++; + } + + int bp_idx = cur_seg_idx + 1; + + bp = bp + bp_idx; + bool is_cyclic = bl->poly >= 0; + BevPoint *prev_bp = bp - 1; + + /* Go to the next tessellated curve point until we cross to outside of the sphere. */ + while (len_v3v3(head_pos, bp->vec) < sphere_radius) { + if (bp_idx > max_seg_idx) { + /* We are outside the defined curve. We will now extrapolate the intersection point. */ + break; + } + prev_bp = bp; + if (is_cyclic && bp_idx == max_seg_idx) { + /* Wrap around to the start point. + * Don't set the bp_idx to zero here as we use it to get the segment index later. + */ + bp = bl->bevpoints; + } + else { + bp++; + } + bp_idx++; + } + + float isect_1[3], isect_2[3]; + + /* Calculate the intersection point. */ + int ret = isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); + + 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; + + if (cur_seg_idx < 0) { + cur_seg_idx = 0; + prev_seg_len = 0; + } + else { + prev_seg_len = seg_accum_len[cur_seg_idx]; + } + + /* Convert the point back into the 0-1 interpolation range. */ + 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; + + if (*r_new_curve_pos > 1.0f) { + *r_radius = bp->radius; + } + else { + *r_radius = (1.0f - frac) * prev_bp->radius + frac * bp->radius; + } + + return cur_seg_idx; +} + /* Evaluate spline IK for a given bone. */ static void splineik_evaluate_bone( tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index, tSplineIk_EvalState *state) { - bSplineIKConstraint *ikData = tree->ikData; - float origHead[3], origTail[3], poseHead[3], poseTail[3], basePoseMat[3][3], poseMat[3][3]; - float splineVec[3], scaleFac, radius = 1.0f; - float tailBlendFac = 0.0f; + bSplineIKConstraint *ik_data = tree->ik_data; + + 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); - mul_v3_m4v3(poseHead, state->locrot_offset, pchan->pose_head); - mul_v3_m4v3(poseTail, state->locrot_offset, pchan->pose_tail); + apply_curve_transform(ik_data, ob, rad, bone_pos, &rad); - copy_v3_v3(origHead, poseHead); + copy_v3_v3(pchan->pose_mat[3], bone_pos); + copy_v3_v3(pchan->pose_head, bone_pos); + copy_v3_v3(pchan->pose_tail, bone_pos); + pchan->flag |= POSE_DONE; + return; + } + + float orig_head[3], orig_tail[3], pose_head[3], pose_tail[3]; + float base_pose_mat[3][3], pose_mat[3][3]; + float spline_vec[3], scale_fac, radius = 1.0f; + float tail_blend_fac = 0.0f; + + mul_v3_m4v3(pose_head, state->locrot_offset, pchan->pose_head); + mul_v3_m4v3(pose_tail, state->locrot_offset, pchan->pose_tail); + + copy_v3_v3(orig_head, pose_head); - /* first, adjust the point positions on the curve */ + /* First, adjust the point positions on the curve. */ float curveLen = tree->points[index] - tree->points[index + 1]; - float pointStart = state->curve_position; - float poseScale = len_v3v3(poseHead, poseTail) / pchan->bone->length; - float baseScale = 1.0f; + float bone_len = len_v3v3(pose_head, pose_tail); + float point_start = state->curve_position; + float pose_scale = bone_len / pchan->bone->length; + float base_scale = 1.0f; - if (ikData->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { + if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { /* Carry over the bone Y scale to the curve range. */ - baseScale = poseScale; + base_scale = pose_scale; } - float pointEnd = pointStart + curveLen * baseScale * state->curve_scale; + float point_end = point_start + curveLen * base_scale * state->curve_scale; - state->curve_position = pointEnd; + state->curve_position = point_end; - /* step 1: determine the positions for the endpoints of the bone */ - if (pointStart < 1.0f) { + /* Step 1: determine the positions for the endpoints of the bone. */ + if (point_start < 1.0f) { float vec[4], dir[3], rad; + radius = 0.0f; - /* determine if the bone should still be affected by SplineIK */ - if (pointEnd >= 1.0f) { - /* blending factor depends on the amount of the bone still left on the chain */ - tailBlendFac = (1.0f - pointStart) / (pointEnd - pointStart); + /* Calculate head position. */ + if (point_start == 0.0f) { + /* Start of the path. We have no previous tail position to copy. */ + BKE_where_on_path(ik_data->tar, point_start, vec, dir, NULL, &rad, NULL); } else { - tailBlendFac = 1.0f; + copy_v3_v3(vec, state->prev_tail_loc); + rad = state->prev_tail_radius; } - /* tail endpoint */ - if (where_on_path(ikData->tar, pointEnd, vec, dir, NULL, &rad, NULL)) { - /* apply curve's object-mode transforms to the position - * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root) - */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { - mul_m4_v3(ikData->tar->obmat, vec); + radius = rad; + copy_v3_v3(pose_head, vec); + apply_curve_transform(ik_data, ob, rad, pose_head, &radius); + + /* Calculate tail position. */ + if (ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) { + float sphere_radius; + + if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { + sphere_radius = bone_len; + } + else { + /* Don't take bone scale into account. */ + sphere_radius = pchan->bone->length; } - /* convert the position to pose-space, then store it */ - mul_m4_v3(ob->imat, vec); - copy_v3_v3(poseTail, vec); + /* Calculate the tail position with sphere curve intersection. */ + state->prev_tail_seg_idx = position_tail_on_spline( + ik_data, vec, sphere_radius, state->prev_tail_seg_idx, pose_tail, &point_end, &rad); - /* set the new radius */ - radius = rad; - } + state->prev_tail_radius = rad; + copy_v3_v3(state->prev_tail_loc, pose_tail); - /* head endpoint */ - if (where_on_path(ikData->tar, pointStart, vec, dir, NULL, &rad, NULL)) { - /* apply curve's object-mode transforms to the position - * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root) - */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { - mul_m4_v3(ikData->tar->obmat, vec); + apply_curve_transform(ik_data, ob, rad, pose_tail, &radius); + state->curve_position = point_end; + } + else { + /* Scale to fit curve end position. */ + if (BKE_where_on_path(ik_data->tar, point_end, vec, dir, NULL, &rad, NULL)) { + state->prev_tail_radius = rad; + copy_v3_v3(state->prev_tail_loc, vec); + copy_v3_v3(pose_tail, vec); + apply_curve_transform(ik_data, ob, rad, pose_tail, &radius); } + } - /* store the position, and convert it to pose space */ - mul_m4_v3(ob->imat, vec); - copy_v3_v3(poseHead, vec); - - /* set the new radius (it should be the average value) */ - radius = (radius + rad) / 2; + /* Determine if the bone should still be affected by SplineIK. + * This makes it so that the bone slowly becomes poseable again the further it rolls off the + * curve. When the whole bone has rolled off the curve, the IK constraint will not influence it + * anymore. + */ + if (point_end >= 1.0f) { + /* Blending factor depends on the amount of the bone still left on the chain. */ + tail_blend_fac = (1.0f - point_start) / (point_end - point_start); + } + else { + tail_blend_fac = 1.0f; } } @@ -335,11 +498,8 @@ static void splineik_evaluate_bone( * - splineVec: the vector direction that the spline applies on the bone. * - scaleFac: the factor that the bone length is scaled by to get the desired amount. */ - sub_v3_v3v3(splineVec, poseTail, poseHead); - scaleFac = len_v3(splineVec) / pchan->bone->length; - - /* Extrapolate the full length of the bone as it rolls off the end of the curve. */ - scaleFac = (tailBlendFac < 1e-5f) ? baseScale : scaleFac / tailBlendFac; + sub_v3_v3v3(spline_vec, pose_tail, pose_head); + scale_fac = len_v3(spline_vec) / pchan->bone->length; /* Step 3: compute the shortest rotation needed * to map from the bone rotation to the current axis. @@ -350,83 +510,102 @@ static void splineik_evaluate_bone( float dmat[3][3], rmat[3][3]; float raxis[3], rangle; - /* compute the raw rotation matrix from the bone's current matrix by extracting only the - * orientation-relevant axes, and normalizing them + /* Compute the raw rotation matrix from the bone's current matrix by extracting only the + * orientation-relevant axes, and normalizing them. */ - mul_m3_m4m4(basePoseMat, state->locrot_offset, pchan->pose_mat); - normalize_m3_m3(rmat, basePoseMat); + mul_m3_m4m4(base_pose_mat, state->locrot_offset, pchan->pose_mat); + normalize_m3_m3(rmat, base_pose_mat); /* Also, normalize the orientation imposed by the bone, * now that we've extracted the scale factor. */ - normalize_v3(splineVec); + normalize_v3(spline_vec); + + /* Calculate smallest axis-angle rotation necessary for getting from the + * current orientation of the bone, to the spline-imposed direction. + */ + cross_v3_v3v3(raxis, rmat[1], spline_vec); - /* calculate smallest axis-angle rotation necessary for getting from the - * current orientation of the bone, to the spline-imposed direction + /* 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. */ - cross_v3_v3v3(raxis, rmat[1], splineVec); + 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], splineVec); + rangle = dot_v3v3(rmat[1], spline_vec); CLAMP(rangle, -1.0f, 1.0f); rangle = acosf(rangle); - /* multiply the magnitude of the angle by the influence of the constraint to - * control the influence of the SplineIK effect + /* Multiply the magnitude of the angle by the influence of the constraint to + * control the influence of the SplineIK effect. */ - rangle *= tree->con->enforce * tailBlendFac; + rangle *= tree->con->enforce * tail_blend_fac; - /* construct rotation matrix from the axis-angle rotation found above - * - this call takes care to make sure that the axis provided is a unit vector first + /* Construct rotation matrix from the axis-angle rotation found above. + * - This call takes care to make sure that the axis provided is a unit vector first. */ axis_angle_to_mat3(dmat, raxis, rangle); /* Combine these rotations so that the y-axis of the bone is now aligned as the * spline dictates, while still maintaining roll control from the existing bone animation. */ - mul_m3_m3m3(poseMat, dmat, rmat); + mul_m3_m3m3(pose_mat, dmat, rmat); - /* attempt to reduce shearing, though I doubt this'll really help too much now... */ - normalize_m3(poseMat); + /* Attempt to reduce shearing, though I doubt this'll really help too much now... */ + normalize_m3(pose_mat); - mul_m3_m3m3(basePoseMat, dmat, basePoseMat); + mul_m3_m3m3(base_pose_mat, dmat, base_pose_mat); - /* apply rotation to the accumulated parent transform */ + /* Apply rotation to the accumulated parent transform. */ mul_m4_m3m4(state->locrot_offset, dmat, state->locrot_offset); } - /* step 4: set the scaling factors for the axes */ + /* Step 4: Set the scaling factors for the axes. */ /* Always multiply the y-axis by the scaling factor to get the correct length. */ - mul_v3_fl(poseMat[1], scaleFac); + mul_v3_fl(pose_mat[1], scale_fac); /* After that, apply x/z scaling modes. */ - if (ikData->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) { + if (ik_data->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) { /* First, apply the original scale if enabled. */ - if (ikData->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL || - (ikData->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) { + if (ik_data->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL || + (ik_data->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) { float scale; - /* x-axis scale */ + /* X-axis scale. */ scale = len_v3(pchan->pose_mat[0]); - mul_v3_fl(poseMat[0], scale); - /* z-axis scale */ + mul_v3_fl(pose_mat[0], scale); + /* Z-axis scale. */ scale = len_v3(pchan->pose_mat[2]); - mul_v3_fl(poseMat[2], scale); + mul_v3_fl(pose_mat[2], scale); /* Adjust the scale factor used for volume preservation * to consider the pre-IK scaling as the initial volume. */ - scaleFac /= poseScale; + scale_fac /= pose_scale; } /* Apply volume preservation. */ - switch (ikData->xzScaleMode) { + switch (ik_data->xzScaleMode) { case CONSTRAINT_SPLINEIK_XZS_INVERSE: { - /* old 'volume preservation' method using the inverse scale */ + /* Old 'volume preservation' method using the inverse scale. */ float scale; - /* calculate volume preservation factor which is - * basically the inverse of the y-scaling factor + /* Calculate volume preservation factor which is + * basically the inverse of the y-scaling factor. */ - if (fabsf(scaleFac) != 0.0f) { - scale = 1.0f / fabsf(scaleFac); + if (fabsf(scale_fac) != 0.0f) { + scale = 1.0f / fabsf(scale_fac); /* We need to clamp this within sensible values. */ /* NOTE: these should be fine for now, but should get sanitized in future. */ @@ -436,56 +615,56 @@ static void splineik_evaluate_bone( scale = 1.0f; } - /* apply the scaling */ - mul_v3_fl(poseMat[0], scale); - mul_v3_fl(poseMat[2], scale); + /* Apply the scaling. */ + mul_v3_fl(pose_mat[0], scale); + mul_v3_fl(pose_mat[2], scale); break; } case CONSTRAINT_SPLINEIK_XZS_VOLUMETRIC: { - /* improved volume preservation based on the Stretch To constraint */ + /* Improved volume preservation based on the Stretch To constraint. */ float final_scale; - /* as the basis for volume preservation, we use the inverse scale factor... */ - if (fabsf(scaleFac) != 0.0f) { - /* NOTE: The method here is taken wholesale from the Stretch To constraint */ - float bulge = powf(1.0f / fabsf(scaleFac), ikData->bulge); + /* As the basis for volume preservation, we use the inverse scale factor... */ + if (fabsf(scale_fac) != 0.0f) { + /* NOTE: The method here is taken wholesale from the Stretch To constraint. */ + float bulge = powf(1.0f / fabsf(scale_fac), ik_data->bulge); if (bulge > 1.0f) { - if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) { - float bulge_max = max_ff(ikData->bulge_max, 1.0f); + if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) { + float bulge_max = max_ff(ik_data->bulge_max, 1.0f); float hard = min_ff(bulge, bulge_max); float range = bulge_max - 1.0f; float scale = (range > 0.0f) ? 1.0f / range : 0.0f; float soft = 1.0f + range * atanf((bulge - 1.0f) * scale) / (float)M_PI_2; - bulge = interpf(soft, hard, ikData->bulge_smooth); + bulge = interpf(soft, hard, ik_data->bulge_smooth); } } if (bulge < 1.0f) { - if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) { - float bulge_min = CLAMPIS(ikData->bulge_min, 0.0f, 1.0f); + if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) { + float bulge_min = CLAMPIS(ik_data->bulge_min, 0.0f, 1.0f); float hard = max_ff(bulge, bulge_min); float range = 1.0f - bulge_min; float scale = (range > 0.0f) ? 1.0f / range : 0.0f; float soft = 1.0f - range * atanf((1.0f - bulge) * scale) / (float)M_PI_2; - bulge = interpf(soft, hard, ikData->bulge_smooth); + bulge = interpf(soft, hard, ik_data->bulge_smooth); } } - /* compute scale factor for xz axes from this value */ + /* Compute scale factor for xz axes from this value. */ final_scale = sqrtf(bulge); } else { - /* no scaling, so scale factor is simple */ + /* No scaling, so scale factor is simple. */ final_scale = 1.0f; } /* Apply the scaling (assuming normalized scale). */ - mul_v3_fl(poseMat[0], final_scale); - mul_v3_fl(poseMat[2], final_scale); + mul_v3_fl(pose_mat[0], final_scale); + mul_v3_fl(pose_mat[2], final_scale); break; } } @@ -494,49 +673,49 @@ static void splineik_evaluate_bone( /* Finally, multiply the x and z scaling by the radius of the curve too, * to allow automatic scales to get tweaked still. */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) { - mul_v3_fl(poseMat[0], radius); - mul_v3_fl(poseMat[2], radius); + if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) { + mul_v3_fl(pose_mat[0], radius); + mul_v3_fl(pose_mat[2], radius); } /* Blend the scaling of the matrix according to the influence. */ - sub_m3_m3m3(poseMat, poseMat, basePoseMat); - madd_m3_m3m3fl(poseMat, basePoseMat, poseMat, tree->con->enforce * tailBlendFac); + sub_m3_m3m3(pose_mat, pose_mat, base_pose_mat); + madd_m3_m3m3fl(pose_mat, base_pose_mat, pose_mat, tree->con->enforce * tail_blend_fac); - /* step 5: set the location of the bone in the matrix */ - if (ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) { - /* when the 'no-root' option is affected, the chain can retain - * the shape but be moved elsewhere + /* Step 5: Set the location of the bone in the matrix. */ + if (ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) { + /* When the 'no-root' option is affected, the chain can retain + * the shape but be moved elsewhere. */ - copy_v3_v3(poseHead, origHead); + copy_v3_v3(pose_head, orig_head); } else if (tree->con->enforce < 1.0f) { - /* when the influence is too low - * - blend the positions for the 'root' bone - * - stick to the parent for any other + /* When the influence is too low: + * - Blend the positions for the 'root' bone. + * - Stick to the parent for any other. */ if (index < tree->chainlen - 1) { - copy_v3_v3(poseHead, origHead); + copy_v3_v3(pose_head, orig_head); } else { - interp_v3_v3v3(poseHead, origHead, poseHead, tree->con->enforce); + interp_v3_v3v3(pose_head, orig_head, pose_head, tree->con->enforce); } } - /* finally, store the new transform */ - copy_m4_m3(pchan->pose_mat, poseMat); - copy_v3_v3(pchan->pose_mat[3], poseHead); - copy_v3_v3(pchan->pose_head, poseHead); + /* Finally, store the new transform. */ + copy_m4_m3(pchan->pose_mat, pose_mat); + copy_v3_v3(pchan->pose_mat[3], pose_head); + copy_v3_v3(pchan->pose_head, pose_head); - mul_v3_mat3_m4v3(origTail, state->locrot_offset, pchan->pose_tail); + mul_v3_mat3_m4v3(orig_tail, state->locrot_offset, pchan->pose_tail); - /* recalculate tail, as it's now outdated after the head gets adjusted above! */ + /* Recalculate tail, as it's now outdated after the head gets adjusted above! */ BKE_pose_where_is_bone_tail(pchan); - /* update the offset in the accumulated parent transform */ - sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, origTail); + /* Update the offset in the accumulated parent transform. */ + sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, orig_tail); - /* done! */ + /* Done! */ pchan->flag |= POSE_DONE; } @@ -559,8 +738,8 @@ static void splineik_execute_tree( if (splineik_evaluate_init(tree, &state)) { /* Walk over each bone in the chain, calculating the effects of spline IK - * - the chain is traversed in the opposite order to storage order (i.e. parent to children) - * so that dependencies are correct + * - the chain is traversed in the opposite order to storage order + * (i.e. parent to children) so that dependencies are correct */ for (int i = tree->chainlen - 1; i >= 0; i--) { bPoseChannel *pchan = tree->chain[i]; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 05da47aed8e..8bbb3014dac 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -34,7 +34,7 @@ #include "CLG_log.h" -#include "NOD_node_tree_multi_function.hh" +#include "NOD_type_conversions.hh" #include "attribute_access_intern.hh" @@ -44,194 +44,13 @@ using blender::float3; using blender::Set; using blender::StringRef; using blender::StringRefNull; -using blender::bke::ReadAttributePtr; -using blender::bke::WriteAttributePtr; using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_SingleValue; namespace blender::bke { -/* -------------------------------------------------------------------- */ -/** \name Attribute Accessor implementations - * \{ */ - -ReadAttribute::~ReadAttribute() -{ - if (array_is_temporary_ && array_buffer_ != nullptr) { - cpp_type_.destruct_n(array_buffer_, size_); - MEM_freeN(array_buffer_); - } -} - -fn::GSpan ReadAttribute::get_span() const -{ - if (size_ == 0) { - return fn::GSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - std::lock_guard lock{span_mutex_}; - if (array_buffer_ == nullptr) { - this->initialize_span(); - } - } - return fn::GSpan(cpp_type_, array_buffer_, size_); -} - -void ReadAttribute::initialize_span() const -{ - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - for (const int i : IndexRange(size_)) { - this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } -} - -WriteAttribute::~WriteAttribute() -{ - if (array_should_be_applied_) { - CLOG_ERROR(&LOG, "Forgot to call apply_span."); - } - if (array_is_temporary_ && array_buffer_ != nullptr) { - cpp_type_.destruct_n(array_buffer_, size_); - MEM_freeN(array_buffer_); - } -} - -/** - * Get a mutable span that can be modified. When all modifications to the attribute are done, - * #apply_span should be called. */ -fn::GMutableSpan WriteAttribute::get_span() -{ - if (size_ == 0) { - return fn::GMutableSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - this->initialize_span(false); - } - array_should_be_applied_ = true; - return fn::GMutableSpan(cpp_type_, array_buffer_, size_); -} - -fn::GMutableSpan WriteAttribute::get_span_for_write_only() -{ - if (size_ == 0) { - return fn::GMutableSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - this->initialize_span(true); - } - array_should_be_applied_ = true; - return fn::GMutableSpan(cpp_type_, array_buffer_, size_); -} - -void WriteAttribute::initialize_span(const bool write_only) -{ - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(element_size * size_, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - if (write_only) { - /* This does nothing for trivial types, but is necessary for general correctness. */ - cpp_type_.construct_default_n(array_buffer_, size_); - } - else { - for (const int i : IndexRange(size_)) { - this->get(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } - } -} - -void WriteAttribute::apply_span() -{ - this->apply_span_if_necessary(); - array_should_be_applied_ = false; -} - -void WriteAttribute::apply_span_if_necessary() -{ - /* Only works when the span has been initialized beforehand. */ - BLI_assert(array_buffer_ != nullptr); - - const int element_size = cpp_type_.size(); - for (const int i : IndexRange(size_)) { - this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } -} - -/* This is used by the #OutputAttributePtr class. */ -class TemporaryWriteAttribute final : public WriteAttribute { - public: - GMutableSpan data; - GeometryComponent &component; - std::string final_name; - - TemporaryWriteAttribute(AttributeDomain domain, - GMutableSpan data, - GeometryComponent &component, - std::string final_name) - : WriteAttribute(domain, data.type(), data.size()), - data(data), - component(component), - final_name(std::move(final_name)) - { - } - - ~TemporaryWriteAttribute() override - { - if (data.data() != nullptr) { - cpp_type_.destruct_n(data.data(), data.size()); - MEM_freeN(data.data()); - } - } - - void get_internal(const int64_t index, void *r_value) const override - { - data.type().copy_to_uninitialized(data[index], r_value); - } - - void set_internal(const int64_t index, const void *value) override - { - data.type().copy_to_initialized(value, data[index]); - } - - void initialize_span(const bool UNUSED(write_only)) override - { - array_buffer_ = data.data(); - array_is_temporary_ = false; - } - - void apply_span_if_necessary() override - { - /* Do nothing, because the span contains the attribute itself already. */ - } -}; - -class ConvertedReadAttribute final : public ReadAttribute { - private: - const CPPType &from_type_; - const CPPType &to_type_; - ReadAttributePtr base_attribute_; - const nodes::DataTypeConversions &conversions_; - - public: - ConvertedReadAttribute(ReadAttributePtr base_attribute, const CPPType &to_type) - : ReadAttribute(base_attribute->domain(), to_type, base_attribute->size()), - from_type_(base_attribute->cpp_type()), - to_type_(to_type), - base_attribute_(std::move(base_attribute)), - conversions_(nodes::get_implicit_type_conversions()) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); - base_attribute_->get(index, buffer); - conversions_.convert(from_type_, to_type_, buffer, r_value); - } -}; - -/** \} */ - const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) { switch (type) { @@ -244,7 +63,7 @@ const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType ty case CD_PROP_INT32: return &CPPType::get<int>(); case CD_PROP_COLOR: - return &CPPType::get<Color4f>(); + return &CPPType::get<ColorGeometry4f>(); case CD_PROP_BOOL: return &CPPType::get<bool>(); default: @@ -267,7 +86,7 @@ CustomDataType cpp_type_to_custom_data_type(const blender::fn::CPPType &type) if (type.is<int>()) { return CD_PROP_INT32; } - if (type.is<Color4f>()) { + if (type.is<ColorGeometry4f>()) { return CD_PROP_COLOR; } if (type.is<bool>()) { @@ -327,10 +146,8 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_ static int attribute_domain_priority(const AttributeDomain domain) { switch (domain) { -#if 0 case ATTR_DOMAIN_CURVE: return 0; -#endif case ATTR_DOMAIN_FACE: return 1; case ATTR_DOMAIN_EDGE: @@ -366,7 +183,27 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) return highest_priority_domain; } -ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read( +void OutputAttribute::save() +{ + save_has_been_called_ = true; + if (optional_span_varray_.has_value()) { + optional_span_varray_->save(); + } + if (save_) { + save_(*this); + } +} + +OutputAttribute::~OutputAttribute() +{ + if (!save_has_been_called_) { + if (varray_) { + std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; + } + } +} + +GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read( const GeometryComponent &component) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -382,7 +219,7 @@ ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read( return as_read_attribute_(data, domain_size); } -WriteAttributePtr BuiltinCustomDataLayerProvider::try_get_for_write( +GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write( GeometryComponent &component) const { if (writable_ != Writable) { @@ -428,7 +265,43 @@ bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) co return delete_success; } -bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component) const +static bool add_custom_data_layer_from_attribute_init(CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ + switch (initializer.type) { + case AttributeInit::Type::Default: { + void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size); + return data != nullptr; + } + case AttributeInit::Type::VArray: { + void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size); + if (data == nullptr) { + return false; + } + const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray; + varray->materialize_to_uninitialized(IndexRange(varray->size()), data); + return true; + } + case AttributeInit::Type::MoveArray: { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + void *data = CustomData_add_layer( + &custom_data, data_type, CD_ASSIGN, source_data, domain_size); + if (data == nullptr) { + MEM_freeN(source_data); + return false; + } + return true; + } + } + + BLI_assert_unreachable(); + return false; +} + +bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, + const AttributeInit &initializer) const { if (createable_ != Creatable) { return false; @@ -441,10 +314,10 @@ bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component) co /* Exists already. */ return false; } + const int domain_size = component.attribute_domain_size(domain_); - const void *data = CustomData_add_layer( - custom_data, stored_type_, CD_DEFAULT, nullptr, domain_size); - const bool success = data != nullptr; + const bool success = add_custom_data_layer_from_attribute_init( + *custom_data, stored_type_, domain_size, initializer); if (success) { custom_data_access_.update_custom_data_pointers(component); } @@ -461,7 +334,7 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) return data != nullptr; } -ReadAttributePtr CustomDataAttributeProvider::try_get_for_read( +ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( const GeometryComponent &component, const StringRef attribute_name) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -484,7 +357,7 @@ ReadAttributePtr CustomDataAttributeProvider::try_get_for_read( case CD_PROP_INT32: return this->layer_to_read_attribute<int>(layer, domain_size); case CD_PROP_COLOR: - return this->layer_to_read_attribute<Color4f>(layer, domain_size); + return this->layer_to_read_attribute<ColorGeometry4f>(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_read_attribute<bool>(layer, domain_size); default: @@ -494,7 +367,7 @@ ReadAttributePtr CustomDataAttributeProvider::try_get_for_read( return {}; } -WriteAttributePtr CustomDataAttributeProvider::try_get_for_write( +WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( GeometryComponent &component, const StringRef attribute_name) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); @@ -518,7 +391,7 @@ WriteAttributePtr CustomDataAttributeProvider::try_get_for_write( case CD_PROP_INT32: return this->layer_to_write_attribute<int>(layer, domain_size); case CD_PROP_COLOR: - return this->layer_to_write_attribute<Color4f>(layer, domain_size); + return this->layer_to_write_attribute<ColorGeometry4f>(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_write_attribute<bool>(layer, domain_size); default: @@ -546,10 +419,52 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, return false; } +static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name, + CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + + switch (initializer.type) { + case AttributeInit::Type::Default: { + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + return data != nullptr; + } + case AttributeInit::Type::VArray: { + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + if (data == nullptr) { + return false; + } + const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray; + varray->materialize_to_uninitialized(IndexRange(varray->size()), data); + return true; + } + case AttributeInit::Type::MoveArray: { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c); + if (data == nullptr) { + MEM_freeN(source_data); + return false; + } + return true; + } + } + + BLI_assert_unreachable(); + return false; +} + bool CustomDataAttributeProvider::try_create(GeometryComponent &component, const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) const + const CustomDataType data_type, + const AttributeInit &initializer) const { if (domain_ != domain) { return false; @@ -567,10 +482,8 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, } } const int domain_size = component.attribute_domain_size(domain_); - char attribute_name_c[MAX_NAME]; - attribute_name.copy(attribute_name_c); - CustomData_add_layer_named( - custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + add_named_custom_data_layer_from_attribute_init( + attribute_name, *custom_data, data_type, domain_size, initializer); return true; } @@ -593,7 +506,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com return true; } -ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read( +ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( const GeometryComponent &component, const StringRef attribute_name) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -604,14 +517,14 @@ ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read( if (layer.type == stored_type_) { if (layer.name == attribute_name) { const int domain_size = component.attribute_domain_size(domain_); - return as_read_attribute_(layer.data, domain_size); + return {as_read_attribute_(layer.data, domain_size), domain_}; } } } return {}; } -WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write( +WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( GeometryComponent &component, const StringRef attribute_name) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); @@ -628,7 +541,7 @@ WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write( if (data_old != data_new) { custom_data_access_.update_custom_data_pointers(component); } - return as_write_attribute_(layer.data, domain_size); + return {as_write_attribute_(layer.data, domain_size), domain_}; } } } @@ -680,6 +593,142 @@ 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); +} + +CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes &other) +{ + if (this != &other) { + CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, other.size_); + size_ = other.size_; + } + + return *this; +} + +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 {}; +} + +/** + * Return a virtual array for a stored attribute, or a single value virtual array with the default + * value if the attribute doesn't exist. If no default value is provided, the default value for the + * type will be used. + */ +GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name, + const CustomDataType data_type, + const void *default_value) const +{ + const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + + std::optional<GSpan> attribute = this->get_for_read(name); + if (!attribute) { + const int domain_size = this->size_; + return std::make_unique<GVArray_For_SingleValue>( + *type, domain_size, (default_value == nullptr) ? type->default_value() : default_value); + } + + if (attribute->type() == *type) { + return std::make_unique<GVArray_For_GSpan>(*attribute); + } + const blender::nodes::DataTypeConversions &conversions = + blender::nodes::get_implicit_type_conversions(); + return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type); +} + +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +{ + /* If this assert hits, it most likely means that #reallocate was not called at some point. */ + 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 /* -------------------------------------------------------------------- */ @@ -706,7 +755,17 @@ int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain return 0; } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read( +bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_name) const +{ + using namespace blender::bke; + const ComponentAttributeProviders *providers = this->get_attribute_providers(); + if (providers == nullptr) { + return false; + } + return providers->builtin_attribute_providers().contains_as(attribute_name); +} + +blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( const StringRef attribute_name) const { using namespace blender::bke; @@ -717,11 +776,11 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read( const BuiltinAttributeProvider *builtin_provider = providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); if (builtin_provider != nullptr) { - return builtin_provider->try_get_for_read(*this); + return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - ReadAttributePtr attribute = dynamic_provider->try_get_for_read(*this, attribute_name); + ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name); if (attribute) { return attribute; } @@ -729,16 +788,19 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read( return {}; } -ReadAttributePtr GeometryComponent::attribute_try_adapt_domain( - ReadAttributePtr attribute, const AttributeDomain new_domain) const +std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_domain( + std::unique_ptr<blender::fn::GVArray> varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const { - if (attribute && attribute->domain() == new_domain) { - return attribute; + if (from_domain == to_domain) { + return varray; } return {}; } -WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef attribute_name) +blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write( + const StringRef attribute_name) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); @@ -748,11 +810,11 @@ WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef const BuiltinAttributeProvider *builtin_provider = providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); if (builtin_provider != nullptr) { - return builtin_provider->try_get_for_write(*this); + return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - WriteAttributePtr attribute = dynamic_provider->try_get_for_write(*this, attribute_name); + WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name); if (attribute) { return attribute; } @@ -782,7 +844,8 @@ bool GeometryComponent::attribute_try_delete(const StringRef attribute_name) bool GeometryComponent::attribute_try_create(const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) + const CustomDataType data_type, + const AttributeInit &initializer) { using namespace blender::bke; if (attribute_name.is_empty()) { @@ -801,17 +864,36 @@ bool GeometryComponent::attribute_try_create(const StringRef attribute_name, if (builtin_provider->data_type() != data_type) { return false; } - return builtin_provider->try_create(*this); + return builtin_provider->try_create(*this, initializer); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - if (dynamic_provider->try_create(*this, attribute_name, domain, data_type)) { + if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) { return true; } } return false; } +bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef attribute_name, + const AttributeInit &initializer) +{ + using namespace blender::bke; + if (attribute_name.is_empty()) { + return false; + } + const ComponentAttributeProviders *providers = this->get_attribute_providers(); + if (providers == nullptr) { + return false; + } + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); + if (builtin_provider == nullptr) { + return false; + } + return builtin_provider->try_create(*this, initializer); +} + Set<std::string> GeometryComponent::attribute_names() const { Set<std::string> attributes; @@ -822,12 +904,16 @@ Set<std::string> GeometryComponent::attribute_names() const return attributes; } -void GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const +/** + * \return False if the callback explicitly returned false at any point, otherwise true, + * meaning the callback made it all the way through. + */ +bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { - return; + return true; } /* Keep track handled attribute names to make sure that we do not return the same name twice. */ @@ -838,7 +924,7 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac if (provider->exists(*this)) { AttributeMetaData meta_data{provider->domain(), provider->data_type()}; if (!callback(provider->name(), meta_data)) { - return; + return false; } handled_attribute_names.add_new(provider->name()); } @@ -852,271 +938,292 @@ void GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac return true; }); if (!continue_loop) { - return; + return false; } } + + return true; } bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (attribute) { return true; } return false; } -static ReadAttributePtr try_adapt_data_type(ReadAttributePtr attribute, - const blender::fn::CPPType &to_type) +std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data( + const StringRef attribute_name) const { - const blender::fn::CPPType &from_type = attribute->cpp_type(); - if (from_type == to_type) { - return attribute; - } + std::optional<AttributeMetaData> result{std::nullopt}; + this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { + if (attribute_name == name) { + result = meta_data; + return false; + } + return true; + }); + return result; +} +static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type( + std::unique_ptr<blender::fn::GVArray> varray, const blender::fn::CPPType &to_type) +{ const blender::nodes::DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); - if (!conversions.is_convertible(from_type, to_type)) { - return {}; - } - - return std::make_unique<blender::bke::ConvertedReadAttribute>(std::move(attribute), to_type); + return conversions.try_convert(std::move(varray), to_type); } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read( +std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read( const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } - if (attribute->domain() != domain) { - attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); - if (!attribute) { + std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray); + if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) { + varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain); + if (!varray) { return {}; } } const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); - if (attribute->cpp_type() != *cpp_type) { - attribute = try_adapt_data_type(std::move(attribute), *cpp_type); - if (!attribute) { + if (varray->type() != *cpp_type) { + varray = try_adapt_data_type(std::move(varray), *cpp_type); + if (!varray) { return {}; } } - return attribute; + return varray; } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read(const StringRef attribute_name, - const AttributeDomain domain) const +std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read( + const StringRef attribute_name, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { return {}; } - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } - if (attribute->domain() != domain) { - attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); - if (!attribute) { - return {}; - } + if (attribute.domain != domain) { + return this->attribute_try_adapt_domain(std::move(attribute.varray), attribute.domain, domain); } - return attribute; + return std::move(attribute.varray); } -ReadAttributePtr GeometryComponent::attribute_get_for_read(const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) const +blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( + const blender::StringRef attribute_name, const CustomDataType data_type) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name, domain, data_type); - if (attribute) { - return attribute; + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + if (!attribute) { + return {}; } - return this->attribute_get_constant_for_read(domain, data_type, default_value); -} - -blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read( - const AttributeDomain domain, const CustomDataType data_type, const void *value) const -{ - BLI_assert(this->attribute_domain_supported(domain)); - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - if (value == nullptr) { - value = cpp_type->default_value(); + const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(type != nullptr); + if (attribute.varray->type() == *type) { + return attribute; } - const int domain_size = this->attribute_domain_size(domain); - return std::make_unique<blender::bke::ConstantReadAttribute>( - domain, domain_size, *cpp_type, value); + const blender::nodes::DataTypeConversions &conversions = + blender::nodes::get_implicit_type_conversions(); + return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain}; } -blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read_converted( +std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read( + const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType in_data_type, - const CustomDataType out_data_type, - const void *value) const + const CustomDataType data_type, + const void *default_value) const { - BLI_assert(this->attribute_domain_supported(domain)); - if (value == nullptr || in_data_type == out_data_type) { - return this->attribute_get_constant_for_read(domain, out_data_type, value); + std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read( + attribute_name, domain, data_type); + if (varray) { + return varray; + } + const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + if (default_value == nullptr) { + default_value = type->default_value(); } - - const blender::fn::CPPType *in_cpp_type = blender::bke::custom_data_type_to_cpp_type( - in_data_type); - const blender::fn::CPPType *out_cpp_type = blender::bke::custom_data_type_to_cpp_type( - out_data_type); - BLI_assert(in_cpp_type != nullptr); - BLI_assert(out_cpp_type != nullptr); - - const blender::nodes::DataTypeConversions &conversions = - blender::nodes::get_implicit_type_conversions(); - BLI_assert(conversions.is_convertible(*in_cpp_type, *out_cpp_type)); - - void *out_value = alloca(out_cpp_type->size()); - conversions.convert(*in_cpp_type, *out_cpp_type, value, out_value); - const int domain_size = this->attribute_domain_size(domain); - blender::bke::ReadAttributePtr attribute = std::make_unique<blender::bke::ConstantReadAttribute>( - domain, domain_size, *out_cpp_type, out_value); - - out_cpp_type->destruct(out_value); - return attribute; + return std::make_unique<blender::fn::GVArray_For_SingleValue>(*type, domain_size, default_value); } -OutputAttributePtr GeometryComponent::attribute_try_get_for_output(const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) -{ - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - - WriteAttributePtr attribute = this->attribute_try_get_for_write(attribute_name); +class GVMutableAttribute_For_OutputAttribute + : public blender::fn::GVMutableArray_For_GMutableSpan { + public: + GeometryComponent *component; + std::string final_name; - /* If the attribute doesn't exist, make a new one with the correct type. */ - if (!attribute) { - this->attribute_try_create(attribute_name, domain, data_type); - attribute = this->attribute_try_get_for_write(attribute_name); - if (attribute && default_value != nullptr) { - void *data = attribute->get_span_for_write_only().data(); - cpp_type->fill_initialized(default_value, data, attribute->size()); - attribute->apply_span(); - } - return OutputAttributePtr(std::move(attribute)); + GVMutableAttribute_For_OutputAttribute(GMutableSpan data, + GeometryComponent &component, + std::string final_name) + : blender::fn::GVMutableArray_For_GMutableSpan(data), + component(&component), + final_name(std::move(final_name)) + { } - /* If an existing attribute has a matching domain and type, just use that. */ - if (attribute->domain() == domain && attribute->cpp_type() == *cpp_type) { - return OutputAttributePtr(std::move(attribute)); + ~GVMutableAttribute_For_OutputAttribute() override + { + type_->destruct_n(data_, size_); + MEM_freeN(data_); } +}; - /* Otherwise create a temporary buffer to use before saving the new attribute. */ - return OutputAttributePtr(*this, domain, attribute_name, data_type); -} - -/* Construct from an attribute that already exists in the geometry component. */ -OutputAttributePtr::OutputAttributePtr(WriteAttributePtr attribute) - : attribute_(std::move(attribute)) +static void save_output_attribute(blender::bke::OutputAttribute &output_attribute) { -} + using namespace blender; + using namespace blender::fn; + using namespace blender::bke; -/* Construct a temporary attribute that has to replace an existing one later on. */ -OutputAttributePtr::OutputAttributePtr(GeometryComponent &component, - AttributeDomain domain, - std::string final_name, - CustomDataType data_type) -{ - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); + GVMutableAttribute_For_OutputAttribute &varray = + dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray()); - const int domain_size = component.attribute_domain_size(domain); - void *buffer = MEM_malloc_arrayN(domain_size, cpp_type->size(), __func__); - GMutableSpan new_span{*cpp_type, buffer, domain_size}; + GeometryComponent &component = *varray.component; + const StringRefNull name = varray.final_name; + const AttributeDomain domain = output_attribute.domain(); + const CustomDataType data_type = output_attribute.custom_data_type(); + const CPPType &cpp_type = output_attribute.cpp_type(); - /* Copy converted values from conflicting attribute, in case the value is read. - * TODO: An optimization could be to not do this, when the caller says that the attribute will - * only be written. */ - ReadAttributePtr src_attribute = component.attribute_get_for_read( - final_name, domain, data_type, nullptr); - for (const int i : blender::IndexRange(domain_size)) { - src_attribute->get(i, new_span[i]); + component.attribute_try_delete(name); + if (!component.attribute_try_create( + varray.final_name, domain, data_type, AttributeInitDefault())) { + CLOG_WARN(&LOG, + "Could not create the '%s' attribute with type '%s'.", + name.c_str(), + cpp_type.name().c_str()); + return; + } + WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name); + BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer); + for (const int i : IndexRange(varray.size())) { + varray.get(i, buffer); + write_attribute.varray->set_by_relocate(i, buffer); } - - attribute_ = std::make_unique<blender::bke::TemporaryWriteAttribute>( - domain, new_span, component, std::move(final_name)); } -/* Store the computed attribute. If it was stored from the beginning already, nothing is done. This - * might delete another attribute with the same name. */ -void OutputAttributePtr::save() +static blender::bke::OutputAttribute create_output_attribute( + GeometryComponent &component, + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const bool ignore_old_values, + const void *default_value) { - if (!attribute_) { - CLOG_WARN(&LOG, "Trying to save an attribute that does not exist anymore."); - return; + using namespace blender; + using namespace blender::fn; + using namespace blender::bke; + + if (attribute_name.is_empty()) { + return {}; } - blender::bke::TemporaryWriteAttribute *attribute = - dynamic_cast<blender::bke::TemporaryWriteAttribute *>(attribute_.get()); + const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); - if (attribute == nullptr) { - /* The attribute is saved already. */ - attribute_.reset(); - return; + if (component.attribute_is_builtin(attribute_name)) { + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + if (default_value) { + const int64_t domain_size = component.attribute_domain_size(domain); + const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; + component.attribute_try_create_builtin(attribute_name, + AttributeInitVArray(&default_varray)); + } + else { + component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); + } + attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + /* Builtin attribute does not exist and can't be created. */ + return {}; + } + } + if (attribute.domain != domain) { + /* Builtin attribute is on different domain. */ + return {}; + } + GVMutableArrayPtr varray = std::move(attribute.varray); + if (varray->type() == *cpp_type) { + /* Builtin attribute matches exactly. */ + return OutputAttribute(std::move(varray), domain, {}, ignore_old_values); + } + /* Builtin attribute is on the same domain but has a different data type. */ + varray = conversions.try_convert(std::move(varray), *cpp_type); + return OutputAttribute(std::move(varray), domain, {}, ignore_old_values); } - StringRefNull name = attribute->final_name; - const blender::fn::CPPType &cpp_type = attribute->cpp_type(); + const int domain_size = component.attribute_domain_size(domain); - /* Delete an existing attribute with the same name if necessary. */ - attribute->component.attribute_try_delete(name); + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + if (default_value) { + const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; + component.attribute_try_create( + attribute_name, domain, data_type, AttributeInitVArray(&default_varray)); + } + else { + component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault()); + } - if (!attribute->component.attribute_try_create( - name, attribute_->domain(), attribute_->custom_data_type())) { - /* Cannot create the target attribute for some reason. */ - CLOG_WARN(&LOG, - "Creating the '%s' attribute with type '%s' failed.", - name.c_str(), - cpp_type.name().c_str()); - attribute_.reset(); - return; + attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + /* Can't create the attribute. */ + return {}; + } + } + if (attribute.domain == domain && attribute.varray->type() == *cpp_type) { + /* Existing generic attribute matches exactly. */ + return OutputAttribute(std::move(attribute.varray), domain, {}, ignore_old_values); } - WriteAttributePtr new_attribute = attribute->component.attribute_try_get_for_write(name); - - GMutableSpan temp_span = attribute->data; - GMutableSpan new_span = new_attribute->get_span_for_write_only(); - BLI_assert(temp_span.size() == new_span.size()); - - /* Currently we copy over the attribute. In the future we want to reuse the buffer. */ - cpp_type.move_to_initialized_n(temp_span.data(), new_span.data(), new_span.size()); - new_attribute->apply_span(); + /* Allocate a new array that lives next to the existing attribute. It will overwrite the existing + * attribute after processing is done. */ + void *data = MEM_mallocN_aligned( + cpp_type->size() * domain_size, cpp_type->alignment(), __func__); + if (ignore_old_values) { + /* This does nothing for trivially constructible types, but is necessary for correctness. */ + cpp_type->construct_default_n(data, domain); + } + else { + /* Fill the temporary array with values from the existing attribute. */ + GVArrayPtr old_varray = component.attribute_get_for_read( + attribute_name, domain, data_type, default_value); + old_varray->materialize_to_uninitialized(IndexRange(domain_size), data); + } + GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>( + GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name); - attribute_.reset(); + return OutputAttribute(std::move(varray), domain, save_output_attribute, true); } -OutputAttributePtr::~OutputAttributePtr() +blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output( + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) { - if (attribute_) { - CLOG_ERROR(&LOG, "Forgot to call #save or #apply_span_and_save."); - } + return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value); } -/* Utility function to call #apply_span and #save in the right order. */ -void OutputAttributePtr::apply_span_and_save() +blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) { - BLI_assert(attribute_); - attribute_->apply_span(); - this->save(); + return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr); } - -/** \} */ diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 806d10e9e89..b3a795faa30 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -24,166 +24,8 @@ namespace blender::bke { -class ConstantReadAttribute final : public ReadAttribute { - private: - void *value_; - - public: - ConstantReadAttribute(AttributeDomain domain, - const int64_t size, - const CPPType &type, - const void *value) - : ReadAttribute(domain, type, size) - { - value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); - type.copy_to_uninitialized(value, value_); - } - - ~ConstantReadAttribute() override - { - this->cpp_type_.destruct(value_); - MEM_freeN(value_); - } - - void get_internal(const int64_t UNUSED(index), void *r_value) const override - { - this->cpp_type_.copy_to_uninitialized(value_, r_value); - } - - void initialize_span() const override - { - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - cpp_type_.fill_uninitialized(value_, array_buffer_, size_); - } -}; - -template<typename T> class ArrayReadAttribute final : public ReadAttribute { - private: - Span<T> data_; - - public: - ArrayReadAttribute(AttributeDomain domain, Span<T> data) - : ReadAttribute(domain, CPPType::get<T>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void initialize_span() const override - { - /* The data will not be modified, so this const_cast is fine. */ - array_buffer_ = const_cast<T *>(data_.data()); - array_is_temporary_ = false; - } -}; - -template<typename T> class OwnedArrayReadAttribute final : public ReadAttribute { - private: - Array<T> data_; - - public: - OwnedArrayReadAttribute(AttributeDomain domain, Array<T> data) - : ReadAttribute(domain, CPPType::get<T>(), data.size()), data_(std::move(data)) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void initialize_span() const override - { - /* The data will not be modified, so this const_cast is fine. */ - array_buffer_ = const_cast<T *>(data_.data()); - array_is_temporary_ = false; - } -}; - -template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> -class DerivedArrayReadAttribute final : public ReadAttribute { - private: - Span<StructT> data_; - - public: - DerivedArrayReadAttribute(AttributeDomain domain, Span<StructT> data) - : ReadAttribute(domain, CPPType::get<ElemT>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - const StructT &struct_value = data_[index]; - const ElemT value = GetFunc(struct_value); - new (r_value) ElemT(value); - } -}; - -template<typename T> class ArrayWriteAttribute final : public WriteAttribute { - private: - MutableSpan<T> data_; - - public: - ArrayWriteAttribute(AttributeDomain domain, MutableSpan<T> data) - : WriteAttribute(domain, CPPType::get<T>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void set_internal(const int64_t index, const void *value) override - { - data_[index] = *reinterpret_cast<const T *>(value); - } - - void initialize_span(const bool UNUSED(write_only)) override - { - array_buffer_ = data_.data(); - array_is_temporary_ = false; - } - - void apply_span_if_necessary() override - { - /* Do nothing, because the span contains the attribute itself already. */ - } -}; - -template<typename StructT, - typename ElemT, - ElemT (*GetFunc)(const StructT &), - void (*SetFunc)(StructT &, const ElemT &)> -class DerivedArrayWriteAttribute final : public WriteAttribute { - private: - MutableSpan<StructT> data_; - - public: - DerivedArrayWriteAttribute(AttributeDomain domain, MutableSpan<StructT> data) - : WriteAttribute(domain, CPPType::get<ElemT>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - const StructT &struct_value = data_[index]; - const ElemT value = GetFunc(struct_value); - new (r_value) ElemT(value); - } - - void set_internal(const int64_t index, const void *value) override - { - StructT &struct_value = data_[index]; - const ElemT &typed_value = *reinterpret_cast<const ElemT *>(value); - SetFunc(struct_value, typed_value); - } -}; +using fn::GVArrayPtr; +using fn::GVMutableArrayPtr; /** * Utility to group together multiple functions that are used to access custom data on geometry @@ -244,10 +86,11 @@ class BuiltinAttributeProvider { { } - virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component) const = 0; - virtual WriteAttributePtr try_get_for_write(GeometryComponent &component) const = 0; + virtual GVArrayPtr try_get_for_read(const GeometryComponent &component) const = 0; + virtual GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const = 0; virtual bool try_delete(GeometryComponent &component) const = 0; - virtual bool try_create(GeometryComponent &UNUSED(component)) const = 0; + virtual bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const = 0; virtual bool exists(const GeometryComponent &component) const = 0; StringRefNull name() const @@ -272,15 +115,16 @@ class BuiltinAttributeProvider { */ class DynamicAttributesProvider { public: - virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const = 0; - virtual WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const = 0; + virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const = 0; + virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const = 0; virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component), const StringRef UNUSED(attribute_name), const AttributeDomain UNUSED(domain), - const CustomDataType UNUSED(data_type)) const + const CustomDataType UNUSED(data_type), + const AttributeInit &UNUSED(initializer)) const { /* Some providers should not create new attributes. */ return false; @@ -309,18 +153,19 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final; bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; bool try_create(GeometryComponent &component, const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) const final; + const CustomDataType data_type, + const AttributeInit &initializer) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; @@ -332,18 +177,21 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { private: template<typename T> - ReadAttributePtr layer_to_read_attribute(const CustomDataLayer &layer, - const int domain_size) const + ReadAttributeLookup layer_to_read_attribute(const CustomDataLayer &layer, + const int domain_size) const { - return std::make_unique<ArrayReadAttribute<T>>( - domain_, Span(static_cast<const T *>(layer.data), domain_size)); + return {std::make_unique<fn::GVArray_For_Span<T>>( + Span(static_cast<const T *>(layer.data), domain_size)), + domain_}; } template<typename T> - WriteAttributePtr layer_to_write_attribute(CustomDataLayer &layer, const int domain_size) const + WriteAttributeLookup layer_to_write_attribute(CustomDataLayer &layer, + const int domain_size) const { - return std::make_unique<ArrayWriteAttribute<T>>( - domain_, MutableSpan(static_cast<T *>(layer.data), domain_size)); + return {std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>( + MutableSpan(static_cast<T *>(layer.data), domain_size)), + domain_}; } bool type_is_supported(CustomDataType data_type) const @@ -357,8 +205,8 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { */ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { private: - using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); - using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size); + using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); const AttributeDomain domain_; const CustomDataType attribute_type_; const CustomDataType stored_type_; @@ -382,10 +230,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final; + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final; bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; @@ -398,8 +246,8 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { * the #MVert struct, but is exposed as float3 attribute. */ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { - using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); - using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size); + using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); using UpdateOnRead = void (*)(const GeometryComponent &component); using UpdateOnWrite = void (*)(GeometryComponent &component); const CustomDataType stored_type_; @@ -430,10 +278,10 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component) const final; + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final; + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final; bool try_delete(GeometryComponent &component) const final; - bool try_create(GeometryComponent &component) const final; + bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final; bool exists(const GeometryComponent &component) const final; }; diff --git a/source/blender/blenkernel/intern/attribute_math.cc b/source/blender/blenkernel/intern/attribute_math.cc index 4ff3a6ceff5..5cdf329effb 100644 --- a/source/blender/blenkernel/intern/attribute_math.cc +++ b/source/blender/blenkernel/intern/attribute_math.cc @@ -18,18 +18,21 @@ namespace blender::attribute_math { -Color4fMixer::Color4fMixer(MutableSpan<Color4f> output_buffer, Color4f default_color) +ColorGeometryMixer::ColorGeometryMixer(MutableSpan<ColorGeometry4f> output_buffer, + ColorGeometry4f default_color) : buffer_(output_buffer), default_color_(default_color), total_weights_(output_buffer.size(), 0.0f) { - buffer_.fill(Color4f(0, 0, 0, 0)); + buffer_.fill(ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); } -void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float weight) +void ColorGeometryMixer::mix_in(const int64_t index, + const ColorGeometry4f &color, + const float weight) { BLI_assert(weight >= 0.0f); - Color4f &output_color = buffer_[index]; + ColorGeometry4f &output_color = buffer_[index]; output_color.r += color.r * weight; output_color.g += color.g * weight; output_color.b += color.b * weight; @@ -37,11 +40,11 @@ void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float total_weights_[index] += weight; } -void Color4fMixer::finalize() +void ColorGeometryMixer::finalize() { for (const int64_t i : buffer_.index_range()) { const float weight = total_weights_[i]; - Color4f &output_color = buffer_[i]; + ColorGeometry4f &output_color = buffer_[i]; if (weight > 0.0f) { const float weight_inv = 1.0f / weight; output_color.r *= weight_inv; diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index e8879cdda8f..e84b485c466 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -132,7 +132,7 @@ static void blender_version_init(void) BLI_snprintf(blender_version_string, ARRAY_SIZE(blender_version_string), - "%d.%02d.%d%s", + "%d.%01d.%d%s", BLENDER_VERSION / 100, BLENDER_VERSION % 100, BLENDER_VERSION_PATCH, diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index efbf19c7381..54fd3f55c31 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -399,7 +399,10 @@ 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/brush.c b/source/blender/blenkernel/intern/brush.c index 13ba1957a32..fdf9cf21b85 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -574,8 +574,8 @@ bool BKE_brush_delete(Main *bmain, Brush *brush) if (brush->id.tag & LIB_TAG_INDIRECT) { return false; } - if (BKE_library_ID_is_indirectly_used(bmain, brush) && ID_REAL_USERS(brush) <= 1 && - ID_EXTRA_USERS(brush) == 0) { + if (ID_REAL_USERS(brush) <= 1 && ID_EXTRA_USERS(brush) == 0 && + BKE_library_ID_is_indirectly_used(bmain, brush)) { return false; } @@ -989,6 +989,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_smoothfac = 0.1f; brush->gpencil_settings->draw_smoothlvl = 1; brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->dilate_pixels = 1; brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; @@ -1399,13 +1400,11 @@ void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool r } /* Set default Draw brush. */ - if (reset || brush_prev == NULL) { - BKE_paint_brush_set(paint, deft_draw); + if ((reset == false) && (brush_prev != NULL)) { + BKE_paint_brush_set(paint, brush_prev); } else { - if (brush_prev != NULL) { - BKE_paint_brush_set(paint, brush_prev); - } + BKE_paint_brush_set(paint, deft_draw); } } diff --git a/source/blender/blenkernel/intern/bvhutils.c b/source/blender/blenkernel/intern/bvhutils.c index 790fb128c7c..bc63e423c09 100644 --- a/source/blender/blenkernel/intern/bvhutils.c +++ b/source/blender/blenkernel/intern/bvhutils.c @@ -1555,6 +1555,10 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data, bvh_cache_type, bvh_cache_p, mesh_eval_mutex); + + if (looptri_mask != NULL) { + MEM_freeN(looptri_mask); + } } else { /* Setup BVHTreeFromMesh */ diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index ac23f9012a7..aa51ee0017e 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -694,8 +694,7 @@ Collection *BKE_collection_duplicate(Main *bmain, const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ if (ID_IS_LINKED(collection)) { @@ -726,8 +725,7 @@ Collection *BKE_collection_duplicate(Main *bmain, #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_collection_sync(bmain); } @@ -1007,7 +1005,7 @@ Collection *BKE_collection_object_find(Main *bmain, return NULL; } -bool BKE_collection_is_empty(Collection *collection) +bool BKE_collection_is_empty(const Collection *collection) { return BLI_listbase_is_empty(&collection->gobject) && BLI_listbase_is_empty(&collection->children); @@ -1150,6 +1148,8 @@ bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob) BKE_main_collection_sync(bmain); } + DEG_id_tag_update(&collection->id, ID_RECALC_GEOMETRY); + return true; } @@ -1201,6 +1201,8 @@ bool BKE_collection_object_remove(Main *bmain, BKE_main_collection_sync(bmain); } + DEG_id_tag_update(&collection->id, ID_RECALC_GEOMETRY); + return true; } @@ -1302,41 +1304,50 @@ static void collection_missing_parents_remove(Collection *collection) * * \note caller must ensure #BKE_main_collection_sync_remap() is called afterwards! * - * \param collection: may be \a NULL, + * \param parent_collection: The collection owning the pointers that were remapped. May be \a NULL, + * in which case whole \a bmain database of collections is checked. + * \param child_collection: The collection that was remapped to another pointer. May be \a NULL, * in which case whole \a bmain database of collections is checked. */ -void BKE_collections_child_remove_nulls(Main *bmain, Collection *collection) +void BKE_collections_child_remove_nulls(Main *bmain, + Collection *parent_collection, + Collection *child_collection) { - if (collection == NULL) { - /* We need to do the checks in two steps when more than one collection may be involved, - * otherwise we can miss some cases... - * Also, master collections are not in bmain, so we also need to loop over scenes. - */ - for (collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { - collection_null_children_remove(collection); + if (child_collection == NULL) { + if (parent_collection != NULL) { + collection_null_children_remove(parent_collection); } - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { - collection_null_children_remove(scene->master_collection); + else { + /* We need to do the checks in two steps when more than one collection may be involved, + * otherwise we can miss some cases... + * Also, master collections are not in bmain, so we also need to loop over scenes. + */ + for (child_collection = bmain->collections.first; child_collection != NULL; + child_collection = child_collection->id.next) { + collection_null_children_remove(child_collection); + } + for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + collection_null_children_remove(scene->master_collection); + } } - for (collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { - collection_missing_parents_remove(collection); + for (child_collection = bmain->collections.first; child_collection != NULL; + child_collection = child_collection->id.next) { + collection_missing_parents_remove(child_collection); } for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { collection_missing_parents_remove(scene->master_collection); } } else { - for (CollectionParent *parent = collection->parents.first, *parent_next; parent; + for (CollectionParent *parent = child_collection->parents.first, *parent_next; parent; parent = parent_next) { parent_next = parent->next; collection_null_children_remove(parent->collection); - if (!collection_find_child(parent->collection, collection)) { - BLI_freelinkN(&collection->parents, parent); + if (!collection_find_child(parent->collection, child_collection)) { + BLI_freelinkN(&child_collection->parents, parent); } } } @@ -1412,7 +1423,8 @@ static bool collection_instance_find_recursive(Collection *collection, } LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) { - if (collection_instance_find_recursive(collection_child->collection, instance_collection)) { + if (collection_child->collection != NULL && + collection_instance_find_recursive(collection_child->collection, instance_collection)) { return true; } } @@ -1631,6 +1643,13 @@ void BKE_collection_parent_relations_rebuild(Collection *collection) continue; } + /* Can happen when remapping data partially out-of-Main (during advanced ID management + * operations like lib-override resync e.g.). */ + if ((child->collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) { + continue; + } + + BLI_assert(collection_find_parent(child->collection, collection) == NULL); CollectionParent *cparent = MEM_callocN(sizeof(CollectionParent), __func__); cparent->collection = collection; BLI_addtail(&child->collection->parents, cparent); @@ -1639,10 +1658,19 @@ void BKE_collection_parent_relations_rebuild(Collection *collection) static void collection_parents_rebuild_recursive(Collection *collection) { + /* A same collection may be child of several others, no need to process it more than once. */ + if ((collection->tag & COLLECTION_TAG_RELATION_REBUILD) == 0) { + return; + } + BKE_collection_parent_relations_rebuild(collection); collection->tag &= ~COLLECTION_TAG_RELATION_REBUILD; for (CollectionChild *child = collection->children.first; child != NULL; child = child->next) { + /* See comment above in `BKE_collection_parent_relations_rebuild`. */ + if ((collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) { + continue; + } collection_parents_rebuild_recursive(child->collection); } } @@ -1666,6 +1694,8 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain) /* This function can be called from readfile.c, when this pointer is not guaranteed to be NULL. */ if (scene->master_collection != NULL) { + BLI_assert(BLI_listbase_is_empty(&scene->master_collection->parents)); + scene->master_collection->tag |= COLLECTION_TAG_RELATION_REBUILD; collection_parents_rebuild_recursive(scene->master_collection); } } @@ -2015,12 +2045,10 @@ static void scene_collections_array(Scene *scene, BLI_assert(collection != NULL); scene_collection_callback(collection, scene_collections_count, r_collections_array_len); - if (*r_collections_array_len == 0) { - return; - } + BLI_assert(*r_collections_array_len > 0); - Collection **array = MEM_mallocN(sizeof(Collection *) * (*r_collections_array_len), - "CollectionArray"); + Collection **array = MEM_malloc_arrayN( + *r_collections_array_len, sizeof(Collection *), "CollectionArray"); *r_collections_array = array; scene_collection_callback(collection, scene_collections_build_array, &array); } @@ -2035,8 +2063,9 @@ void BKE_scene_collections_iterator_begin(BLI_Iterator *iter, void *data_in) CollectionsIteratorData *data = MEM_callocN(sizeof(CollectionsIteratorData), __func__); data->scene = scene; + + BLI_ITERATOR_INIT(iter); iter->data = data; - iter->valid = true; scene_collections_array(scene, (Collection ***)&data->array, &data->tot); BLI_assert(data->tot != 0); @@ -2078,16 +2107,22 @@ typedef struct SceneObjectsIteratorData { BLI_Iterator scene_collection_iter; } SceneObjectsIteratorData; -void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in) +static void scene_objects_iterator_begin(BLI_Iterator *iter, Scene *scene, GSet *visited_objects) { - Scene *scene = data_in; SceneObjectsIteratorData *data = MEM_callocN(sizeof(SceneObjectsIteratorData), __func__); + + BLI_ITERATOR_INIT(iter); iter->data = data; - /* lookup list ot make sure each object is object called once */ - data->visited = BLI_gset_ptr_new(__func__); + /* Lookup list to make sure that each object is only processed once. */ + if (visited_objects != NULL) { + data->visited = visited_objects; + } + else { + data->visited = BLI_gset_ptr_new(__func__); + } - /* we wrap the scenecollection iterator here to go over the scene collections */ + /* We wrap the scenecollection iterator here to go over the scene collections. */ BKE_scene_collections_iterator_begin(&data->scene_collection_iter, scene); Collection *collection = data->scene_collection_iter.current; @@ -2096,6 +2131,13 @@ void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in) BKE_scene_objects_iterator_next(iter); } +void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in) +{ + Scene *scene = data_in; + + scene_objects_iterator_begin(iter, scene, NULL); +} + /** * Ensures we only get each object once, even when included in several collections. */ @@ -2149,9 +2191,36 @@ void BKE_scene_objects_iterator_end(BLI_Iterator *iter) SceneObjectsIteratorData *data = iter->data; if (data) { BKE_scene_collections_iterator_end(&data->scene_collection_iter); - BLI_gset_free(data->visited, NULL); + if (data->visited != NULL) { + BLI_gset_free(data->visited, NULL); + } MEM_freeN(data); } } +/** + * Generate a new GSet (or extend given `objects_gset` if not NULL) with all objects referenced by + * all collections of given `scene`. + * + * \note: This will include objects without a base currently (because they would belong to excluded + * collections only e.g.). + */ +GSet *BKE_scene_objects_as_gset(Scene *scene, GSet *objects_gset) +{ + BLI_Iterator iter; + scene_objects_iterator_begin(&iter, scene, objects_gset); + while (iter.valid) { + BKE_scene_objects_iterator_next(&iter); + } + + /* `return_gset` is either given `objects_gset` (if non-NULL), or the GSet allocated by the + * iterator. Either way, we want to get it back, and prevent `BKE_scene_objects_iterator_end` + * from freeing it. */ + GSet *return_gset = ((SceneObjectsIteratorData *)iter.data)->visited; + ((SceneObjectsIteratorData *)iter.data)->visited = NULL; + BKE_scene_objects_iterator_end(&iter); + + return return_gset; +} + /** \} */ diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index d04a27adec8..826c79c3764 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -1463,30 +1463,20 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph), * currently for paths to work it needs to go through the bevlist/displist system (ton) */ - if (ct->tar->runtime.curve_cache && ct->tar->runtime.curve_cache->path && - ct->tar->runtime.curve_cache->path->data) { + if (ct->tar->runtime.curve_cache && ct->tar->runtime.curve_cache->anim_path_accum_length) { float quat[4]; if ((data->followflag & FOLLOWPATH_STATIC) == 0) { /* animated position along curve depending on time */ - Nurb *nu = cu->nurb.first; curvetime = cu->ctime - data->offset; /* ctime is now a proper var setting of Curve which gets set by Animato like any other var * that's animated, but this will only work if it actually is animated... * * we divide the curvetime calculated in the previous step by the length of the path, - * to get a time factor, which then gets clamped to lie within 0.0 - 1.0 range. */ + * to get a time factor. */ curvetime /= cu->pathlen; - if (nu && nu->flagu & CU_NURB_CYCLIC) { - /* If the curve is cyclic, enable looping around if the time is - * outside the bounds 0..1 */ - if ((curvetime < 0.0f) || (curvetime > 1.0f)) { - curvetime -= floorf(curvetime); - } - } - else { - /* The curve is not cyclic, so clamp to the begin/end points. */ + if (cu->flag & CU_PATH_CLAMP) { CLAMP(curvetime, 0.0f, 1.0f); } } @@ -1495,13 +1485,13 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph), curvetime = data->offset_fac; } - if (where_on_path(ct->tar, - curvetime, - vec, - dir, - (data->followflag & FOLLOWPATH_FOLLOW) ? quat : NULL, - &radius, - NULL)) { /* quat_pt is quat or NULL*/ + if (BKE_where_on_path(ct->tar, + curvetime, + vec, + dir, + (data->followflag & FOLLOWPATH_FOLLOW) ? quat : NULL, + &radius, + NULL)) { /* quat_pt is quat or NULL*/ float totmat[4][4]; unit_m4(totmat); @@ -1645,10 +1635,28 @@ static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *UN float eul[3]; float size[3]; + /* This constraint is based on euler rotation math, which doesn't work well with shear. + * The Y axis is chosen as the main one because constraints are most commonly used on bones. + * This also allows using the constraint to simply remove shear. */ + orthogonalize_m4_stable(cob->matrix, 1, false); + + /* Only do the complex processing if some limits are actually enabled. */ + if (!(data->flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT))) { + return; + } + + /* Select the Euler rotation order, defaulting to the owner value. */ + short rot_order = cob->rotOrder; + + if (data->euler_order != CONSTRAINT_EULER_AUTO) { + rot_order = data->euler_order; + } + + /* Decompose the matrix using the specified order. */ copy_v3_v3(loc, cob->matrix[3]); mat4_to_size(size, cob->matrix); - mat4_to_eulO(eul, cob->rotOrder, cob->matrix); + mat4_to_eulO(eul, rot_order, cob->matrix); /* constraint data uses radians internally */ @@ -1681,7 +1689,7 @@ static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *UN } } - loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, cob->rotOrder); + loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, rot_order); } static bConstraintTypeInfo CTI_ROTLIMIT = { @@ -2850,7 +2858,7 @@ static void actcon_get_tarmat(struct Depsgraph *depsgraph, * including rotation order, otherwise this fails. */ pchan = cob->pchan; - tchan = BKE_pose_channel_verify(&pose, pchan->name); + tchan = BKE_pose_channel_ensure(&pose, pchan->name); tchan->rotmode = pchan->rotmode; /* evaluate action using workob (it will only set the PoseChannel in question) */ @@ -3784,8 +3792,7 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar BKE_object_minmax(ct->tar, curveMin, curveMax, true); /* get targetmatrix */ - if (data->tar->runtime.curve_cache && data->tar->runtime.curve_cache->path && - data->tar->runtime.curve_cache->path->data) { + if (data->tar->runtime.curve_cache && data->tar->runtime.curve_cache->anim_path_accum_length) { float vec[4], dir[3], totmat[4][4]; float curvetime; short clamp_axis; @@ -3869,7 +3876,7 @@ static void clampto_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar } /* 3. position on curve */ - if (where_on_path(ct->tar, curvetime, vec, dir, NULL, NULL, NULL)) { + if (BKE_where_on_path(ct->tar, curvetime, vec, dir, NULL, NULL, NULL)) { unit_m4(totmat); copy_v3_v3(totmat[3], vec); diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index cbf7a4483c0..81830f5bb61 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -80,7 +80,17 @@ struct bContext { struct ARegion *menu; struct wmGizmoGroup *gizmo_group; struct bContextStore *store; - const char *operator_poll_msg; /* reason for poll failing */ + + /* Operator poll. */ + /** + * Store the reason the poll function fails (static string, not allocated). + * For more advanced formatting use `operator_poll_msg_dyn_params`. + */ + const char *operator_poll_msg; + /** + * Store values to dynamically to create the string (called when a tool-tip is shown). + */ + struct bContextPollMsgDyn_Params operator_poll_msg_dyn_params; } wm; /* data context */ @@ -113,11 +123,16 @@ bContext *CTX_copy(const bContext *C) { bContext *newC = MEM_dupallocN((void *)C); + memset(&newC->wm.operator_poll_msg_dyn_params, 0, sizeof(newC->wm.operator_poll_msg_dyn_params)); + return newC; } void CTX_free(bContext *C) { + /* This may contain a dynamically allocated message, free. */ + CTX_wm_operator_poll_msg_clear(C); + MEM_freeN(C); } @@ -1003,13 +1018,45 @@ void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup) C->wm.gizmo_group = gzgroup; } +void CTX_wm_operator_poll_msg_clear(bContext *C) +{ + struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params; + if (params->free_fn != NULL) { + params->free_fn(C, params->user_data); + } + params->get_fn = NULL; + params->free_fn = NULL; + params->user_data = NULL; + + C->wm.operator_poll_msg = NULL; +} void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg) { + CTX_wm_operator_poll_msg_clear(C); + C->wm.operator_poll_msg = msg; } -const char *CTX_wm_operator_poll_msg_get(bContext *C) +void CTX_wm_operator_poll_msg_set_dynamic(bContext *C, + const struct bContextPollMsgDyn_Params *params) { + CTX_wm_operator_poll_msg_clear(C); + + C->wm.operator_poll_msg_dyn_params = *params; +} + +const char *CTX_wm_operator_poll_msg_get(bContext *C, bool *r_free) +{ + struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params; + if (params->get_fn != NULL) { + char *msg = params->get_fn(C, params->user_data); + if (msg != NULL) { + *r_free = true; + } + return msg; + } + + *r_free = false; return IFACE_(C->wm.operator_poll_msg); } diff --git a/source/blender/blenkernel/intern/cryptomatte.cc b/source/blender/blenkernel/intern/cryptomatte.cc index bc89fa85cea..1ff0ca92306 100644 --- a/source/blender/blenkernel/intern/cryptomatte.cc +++ b/source/blender/blenkernel/intern/cryptomatte.cc @@ -54,7 +54,7 @@ struct CryptomatteSession { /* Layer names in order of creation. */ blender::Vector<std::string> layer_names; - CryptomatteSession(); + CryptomatteSession() = default; CryptomatteSession(const Main *bmain); CryptomatteSession(StampData *stamp_data); CryptomatteSession(const Scene *scene); @@ -67,10 +67,6 @@ struct CryptomatteSession { #endif }; -CryptomatteSession::CryptomatteSession() -{ -} - CryptomatteSession::CryptomatteSession(const Main *bmain) { if (!BLI_listbase_is_empty(&bmain->objects)) { @@ -425,6 +421,9 @@ static bool from_manifest(CryptomatteLayer &layer, blender::StringRefNull manife } const int quoted_hash_len = quoted_string_len_(ref); + if (quoted_hash_len < 2) { + return false; + } const int hash_len = quoted_hash_len - 2; CryptomatteHash hash = CryptomatteHash::from_hex_encoded(ref.substr(1, hash_len)); ref = ref.drop_prefix(quoted_hash_len); diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index f3551c98f07..65cdb8503a4 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -414,6 +414,7 @@ void BKE_curve_init(Curve *cu, const short curve_type) cu->tb[0].w = cu->tb[0].h = 0.0; } else if (cu->type == OB_SURF) { + cu->flag |= CU_3D; cu->resolv = 4; } cu->bevel_profile = NULL; @@ -462,24 +463,19 @@ short BKE_curve_type_get(const Curve *cu) return type; } -void BKE_curve_curve_dimension_update(Curve *cu) +void BKE_curve_dimension_update(Curve *cu) { ListBase *nurbs = BKE_curve_nurbs_get(cu); + bool is_2d = CU_IS_2D(cu); - if (cu->flag & CU_3D) { - LISTBASE_FOREACH (Nurb *, nu, nurbs) { - nu->flag &= ~CU_2D; + LISTBASE_FOREACH (Nurb *, nu, nurbs) { + if (is_2d) { + BKE_nurb_project_2d(nu); } - } - else { - LISTBASE_FOREACH (Nurb *, nu, nurbs) { - nu->flag |= CU_2D; - BKE_nurb_test_2d(nu); - /* since the handles are moved they need to be auto-located again */ - if (nu->type == CU_BEZIER) { - BKE_nurb_handles_calc(nu); - } + /* since the handles are moved they need to be auto-located again */ + if (nu->type == CU_BEZIER) { + BKE_nurb_handles_calc(nu); } } } @@ -489,7 +485,10 @@ void BKE_curve_type_test(Object *ob) ob->type = BKE_curve_type_get(ob->data); if (ob->type == OB_CURVE) { - BKE_curve_curve_dimension_update((Curve *)ob->data); + Curve *cu = ob->data; + if (CU_IS_2D(cu)) { + BKE_curve_dimension_update(cu); + } } } @@ -596,11 +595,11 @@ bool BKE_nurbList_index_get_co(ListBase *nurb, const int index, float r_co[3]) return false; } -int BKE_nurbList_verts_count(ListBase *nurb) +int BKE_nurbList_verts_count(const ListBase *nurb) { int tot = 0; - LISTBASE_FOREACH (Nurb *, nu, nurb) { + LISTBASE_FOREACH (const Nurb *, nu, nurb) { if (nu->bezt) { tot += 3 * nu->pntsu; } @@ -612,7 +611,7 @@ int BKE_nurbList_verts_count(ListBase *nurb) return tot; } -int BKE_nurbList_verts_count_without_handles(ListBase *nurb) +int BKE_nurbList_verts_count_without_handles(const ListBase *nurb) { int tot = 0; @@ -745,16 +744,12 @@ void BKE_nurbList_duplicate(ListBase *lb1, const ListBase *lb2) } } -void BKE_nurb_test_2d(Nurb *nu) +void BKE_nurb_project_2d(Nurb *nu) { BezTriple *bezt; BPoint *bp; int a; - if ((nu->flag & CU_2D) == 0) { - return; - } - if (nu->type == CU_BEZIER) { a = nu->pntsu; bezt = nu->bezt; @@ -779,7 +774,7 @@ void BKE_nurb_test_2d(Nurb *nu) * if use_radius is truth, minmax will take points' radius into account, * which will make boundbox closer to beveled curve. */ -void BKE_nurb_minmax(Nurb *nu, bool use_radius, float min[3], float max[3]) +void BKE_nurb_minmax(const Nurb *nu, bool use_radius, float min[3], float max[3]) { BezTriple *bezt; BPoint *bp; @@ -1928,7 +1923,7 @@ static int cu_isectLL(const float v1[3], return 0; } -static bool bevelinside(BevList *bl1, BevList *bl2) +static bool bevelinside(const BevList *bl1, const BevList *bl2) { /* is bl2 INSIDE bl1 ? with left-right method and "lambda's" */ /* returns '1' if correct hole */ @@ -2052,8 +2047,8 @@ static void calc_bevel_sin_cos( *r_cosa = x3 / t02; } -static void tilt_bezpart(BezTriple *prevbezt, - BezTriple *bezt, +static void tilt_bezpart(const BezTriple *prevbezt, + const BezTriple *bezt, Nurb *nu, float *tilt_array, float *radius_array, @@ -2061,7 +2056,7 @@ static void tilt_bezpart(BezTriple *prevbezt, int resolu, int stride) { - BezTriple *pprev, *next, *last; + const BezTriple *pprev, *next, *last; float fac, dfac, t[4]; int a; @@ -2712,8 +2707,9 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) continue; } - /* check if we will calculate tilt data */ - do_tilt = CU_DO_TILT(cu, nu); + /* Tilt, as the rotation angle of curve control points, is only calculated for 3D curves, + * (since this transformation affects the 3D space). */ + do_tilt = (cu->flag & CU_3D) != 0; /* Normal display uses the radius, better just to calculate them. */ do_radius = CU_DO_RADIUS(cu, nu); @@ -2756,7 +2752,6 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) bevp->tilt = bp->tilt; bevp->radius = bp->radius; bevp->weight = bp->weight; - bevp->split_tag = true; bp++; if (seglen != NULL && len != 0) { *seglen = len_v3v3(bevp->vec, bp->vec); @@ -2828,7 +2823,6 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) bevp->tilt = prevbezt->tilt; bevp->radius = prevbezt->radius; bevp->weight = prevbezt->weight; - bevp->split_tag = true; bevp->dupe_tag = false; bevp++; bl->nr++; @@ -2879,21 +2873,6 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) sizeof(BevPoint)); } - /* indicate with handlecodes double points */ - if (prevbezt->h1 == prevbezt->h2) { - if (ELEM(prevbezt->h1, 0, HD_VECT)) { - bevp->split_tag = true; - } - } - else { - if (ELEM(prevbezt->h1, 0, HD_VECT)) { - bevp->split_tag = true; - } - else if (ELEM(prevbezt->h2, 0, HD_VECT)) { - bevp->split_tag = true; - } - } - /* seglen */ if (seglen != NULL) { *seglen = 0; @@ -3142,7 +3121,7 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) } /* turning direction */ - if ((cu->flag & CU_3D) == 0) { + if (CU_IS_2D(cu)) { sd = sortdata; for (a = 0; a < poly; a++, sd++) { if (sd->bl->hole == sd->dir) { @@ -3162,7 +3141,7 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render) } /* STEP 4: 2D-COSINES or 3D ORIENTATION */ - if ((cu->flag & CU_3D) == 0) { + if (CU_IS_2D(cu)) { /* 2D Curves */ LISTBASE_FOREACH (BevList *, bl, bev) { if (bl->nr < 2) { @@ -3213,7 +3192,6 @@ static void calchandleNurb_intern(BezTriple *bezt, float pt[3]; float dvec_a[3], dvec_b[3]; float len, len_a, len_b; - float len_ratio; const float eps = 1e-5; /* assume normal handle until we check */ @@ -3265,8 +3243,6 @@ static void calchandleNurb_intern(BezTriple *bezt, len_b = 1.0f; } - len_ratio = len_a / len_b; - if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) || ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) { /* auto */ float tvec[3]; tvec[0] = dvec_b[0] / len_b + dvec_a[0] / len_a; @@ -3399,7 +3375,7 @@ static void calchandleNurb_intern(BezTriple *bezt, len_b = 1.0f; } - len_ratio = len_a / len_b; + const float len_ratio = len_a / len_b; if (bezt->f1 & handle_sel_flag) { /* order of calculation */ if (ELEM(bezt->h2, HD_ALIGN, HD_ALIGN_DOUBLESIDE)) { /* aligned */ @@ -4681,12 +4657,12 @@ void BKE_nurb_direction_switch(Nurb *nu) } } -void BKE_curve_nurbs_vert_coords_get(ListBase *lb, float (*vert_coords)[3], int vert_len) +void BKE_curve_nurbs_vert_coords_get(const ListBase *lb, float (*vert_coords)[3], int vert_len) { float *co = vert_coords[0]; - LISTBASE_FOREACH (Nurb *, nu, lb) { + LISTBASE_FOREACH (const Nurb *, nu, lb) { if (nu->type == CU_BEZIER) { - BezTriple *bezt = nu->bezt; + const BezTriple *bezt = nu->bezt; for (int i = 0; i < nu->pntsu; i++, bezt++) { copy_v3_v3(co, bezt->vec[0]); co += 3; @@ -4697,7 +4673,7 @@ void BKE_curve_nurbs_vert_coords_get(ListBase *lb, float (*vert_coords)[3], int } } else { - BPoint *bp = nu->bp; + const BPoint *bp = nu->bp; for (int i = 0; i < nu->pntsu * nu->pntsv; i++, bp++) { copy_v3_v3(co, bp->vec); co += 3; @@ -4708,7 +4684,7 @@ void BKE_curve_nurbs_vert_coords_get(ListBase *lb, float (*vert_coords)[3], int UNUSED_VARS_NDEBUG(vert_len); } -float (*BKE_curve_nurbs_vert_coords_alloc(ListBase *lb, int *r_vert_len))[3] +float (*BKE_curve_nurbs_vert_coords_alloc(const ListBase *lb, int *r_vert_len))[3] { const int vert_len = BKE_nurbList_verts_count(lb); float(*vert_coords)[3] = MEM_malloc_arrayN(vert_len, sizeof(*vert_coords), __func__); @@ -4747,9 +4723,7 @@ void BKE_curve_nurbs_vert_coords_apply_with_mat4(ListBase *lb, } if (constrain_2d) { - if (nu->flag & CU_2D) { - BKE_nurb_test_2d(nu); - } + BKE_nurb_project_2d(nu); } calchandlesNurb_intern(nu, SELECT, true); @@ -4785,24 +4759,22 @@ void BKE_curve_nurbs_vert_coords_apply(ListBase *lb, } if (constrain_2d) { - if (nu->flag & CU_2D) { - BKE_nurb_test_2d(nu); - } + BKE_nurb_project_2d(nu); } calchandlesNurb_intern(nu, SELECT, true); } } -float (*BKE_curve_nurbs_key_vert_coords_alloc(ListBase *lb, float *key, int *r_vert_len))[3] +float (*BKE_curve_nurbs_key_vert_coords_alloc(const ListBase *lb, float *key, int *r_vert_len))[3] { int vert_len = BKE_nurbList_verts_count(lb); float(*cos)[3] = MEM_malloc_arrayN(vert_len, sizeof(*cos), __func__); float *co = cos[0]; - LISTBASE_FOREACH (Nurb *, nu, lb) { + LISTBASE_FOREACH (const Nurb *, nu, lb) { if (nu->type == CU_BEZIER) { - BezTriple *bezt = nu->bezt; + const BezTriple *bezt = nu->bezt; for (int i = 0; i < nu->pntsu; i++, bezt++) { copy_v3_v3(co, &key[0]); @@ -4815,7 +4787,7 @@ float (*BKE_curve_nurbs_key_vert_coords_alloc(ListBase *lb, float *key, int *r_v } } else { - BPoint *bp = nu->bp; + const BPoint *bp = nu->bp; for (int i = 0; i < nu->pntsu * nu->pntsv; i++, bp++) { copy_v3_v3(co, key); @@ -5233,7 +5205,7 @@ bool BKE_curve_minmax(Curve *cu, bool use_radius, float min[3], float max[3]) use_radius = false; } /* Do bounding box based on splines. */ - LISTBASE_FOREACH (Nurb *, nu, nurb_lb) { + LISTBASE_FOREACH (const Nurb *, nu, nurb_lb) { BKE_nurb_minmax(nu, use_radius, min, max); } const bool result = (BLI_listbase_is_empty(nurb_lb) == false); @@ -5429,12 +5401,12 @@ void BKE_curve_material_index_remove(Curve *cu, int index) } } -bool BKE_curve_material_index_used(Curve *cu, int index) +bool BKE_curve_material_index_used(const Curve *cu, int index) { const int curvetype = BKE_curve_type_get(cu); if (curvetype == OB_FONT) { - struct CharInfo *info = cu->strinfo; + const struct CharInfo *info = cu->strinfo; for (int i = cu->len_char32 - 1; i >= 0; i--, info++) { if (info->mat_nr == index) { return true; @@ -5442,7 +5414,7 @@ bool BKE_curve_material_index_used(Curve *cu, int index) } } else { - LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { + LISTBASE_FOREACH (const Nurb *, nu, &cu->nurb) { if (nu->mat_nr == index) { return true; } diff --git a/source/blender/blenkernel/intern/curve_bevel.c b/source/blender/blenkernel/intern/curve_bevel.c index 911a98cb607..7f2cdfa59d3 100644 --- a/source/blender/blenkernel/intern/curve_bevel.c +++ b/source/blender/blenkernel/intern/curve_bevel.c @@ -56,7 +56,9 @@ static CurveBevelFillType curve_bevel_get_fill_type(const Curve *curve) return (curve->flag & CU_FRONT) ? FRONT : BACK; } -static void bevel_quarter_fill(Curve *curve, float *quarter_coords_x, float *quarter_coords_y) +static void bevel_quarter_fill(const Curve *curve, + float *quarter_coords_x, + float *quarter_coords_y) { if (curve->bevel_mode == CU_BEV_MODE_ROUND) { float angle = 0.0f; @@ -83,7 +85,7 @@ static void bevel_quarter_fill(Curve *curve, float *quarter_coords_x, float *qua } } -static void curve_bevel_make_extrude_and_fill(Curve *cu, +static void curve_bevel_make_extrude_and_fill(const Curve *cu, ListBase *disp, const bool use_extrude, const CurveBevelFillType fill_type) @@ -193,7 +195,7 @@ static void curve_bevel_make_extrude_and_fill(Curve *cu, } } -static void curve_bevel_make_full_circle(Curve *cu, ListBase *disp) +static void curve_bevel_make_full_circle(const Curve *cu, ListBase *disp) { const int nr = 4 + 2 * cu->bevresol; @@ -218,7 +220,7 @@ static void curve_bevel_make_full_circle(Curve *cu, ListBase *disp) } } -static void curve_bevel_make_only_extrude(Curve *cu, ListBase *disp) +static void curve_bevel_make_only_extrude(const Curve *cu, ListBase *disp) { DispList *dl = MEM_callocN(sizeof(DispList), __func__); dl->verts = MEM_malloc_arrayN(2, sizeof(float[3]), __func__); @@ -235,7 +237,7 @@ static void curve_bevel_make_only_extrude(Curve *cu, ListBase *disp) fp[5] = cu->ext1; } -static void curve_bevel_make_from_object(Curve *cu, ListBase *disp) +static void curve_bevel_make_from_object(const Curve *cu, ListBase *disp) { if (cu->bevobj == NULL) { return; @@ -287,15 +289,13 @@ static void curve_bevel_make_from_object(Curve *cu, ListBase *disp) } } -void BKE_curve_bevel_make(Object *ob, ListBase *disp) +ListBase BKE_curve_bevel_make(const Curve *curve) { - Curve *curve = ob->data; - - BLI_listbase_clear(disp); + ListBase bevel_shape = {NULL, NULL}; if (curve->bevel_mode == CU_BEV_MODE_OBJECT) { if (curve->bevobj != NULL) { - curve_bevel_make_from_object(curve, disp); + curve_bevel_make_from_object(curve, &bevel_shape); } } else { @@ -303,18 +303,20 @@ void BKE_curve_bevel_make(Object *ob, ListBase *disp) const bool use_bevel = curve->ext2 != 0.0f; /* Pass. */ if (use_extrude && !use_bevel) { - curve_bevel_make_only_extrude(curve, disp); + curve_bevel_make_only_extrude(curve, &bevel_shape); } else if (use_extrude || use_bevel) { CurveBevelFillType fill_type = curve_bevel_get_fill_type(curve); if (!use_extrude && fill_type == FULL && curve->bevel_mode == CU_BEV_MODE_ROUND) { - curve_bevel_make_full_circle(curve, disp); + curve_bevel_make_full_circle(curve, &bevel_shape); } else { /* The general case for nonzero extrusion or an incomplete loop. */ - curve_bevel_make_extrude_and_fill(curve, disp, use_extrude, fill_type); + curve_bevel_make_extrude_and_fill(curve, &bevel_shape, use_extrude, fill_type); } } } + + return bevel_shape; } diff --git a/source/blender/blenkernel/intern/curve_convert.c b/source/blender/blenkernel/intern/curve_convert.c index 988ddb3bbc0..5bcce9c339e 100644 --- a/source/blender/blenkernel/intern/curve_convert.c +++ b/source/blender/blenkernel/intern/curve_convert.c @@ -44,7 +44,7 @@ static Curve *curve_from_font_object(Object *object, Depsgraph *depsgraph) new_curve->type = OB_CURVE; new_curve->flag &= ~CU_3D; - BKE_curve_curve_dimension_update(new_curve); + BKE_curve_dimension_update(new_curve); return new_curve; } diff --git a/source/blender/blenkernel/intern/curve_deform.c b/source/blender/blenkernel/intern/curve_deform.c index 63da7c1dd11..10c6d2213ff 100644 --- a/source/blender/blenkernel/intern/curve_deform.c +++ b/source/blender/blenkernel/intern/curve_deform.c @@ -68,75 +68,7 @@ static void init_curve_deform(const Object *ob_curve, const Object *ob_target, C } /** - * This makes sure we can extend for non-cyclic. - * - * \return Success. - */ -static bool where_on_path_deform(const Object *ob_curve, - float ctime, - float r_vec[4], - float r_dir[3], - float r_quat[4], - float *r_radius) -{ - BevList *bl; - float ctime1; - int cycl = 0; - - /* test for cyclic */ - bl = ob_curve->runtime.curve_cache->bev.first; - if (!bl->nr) { - return false; - } - if (bl->poly > -1) { - cycl = 1; - } - - if (cycl == 0) { - ctime1 = CLAMPIS(ctime, 0.0f, 1.0f); - } - else { - ctime1 = ctime; - } - - /* vec needs 4 items */ - if (where_on_path(ob_curve, ctime1, r_vec, r_dir, r_quat, r_radius, NULL)) { - - if (cycl == 0) { - Path *path = ob_curve->runtime.curve_cache->path; - float dvec[3]; - - if (ctime < 0.0f) { - sub_v3_v3v3(dvec, path->data[1].vec, path->data[0].vec); - mul_v3_fl(dvec, ctime * (float)path->len); - add_v3_v3(r_vec, dvec); - if (r_quat) { - copy_qt_qt(r_quat, path->data[0].quat); - } - if (r_radius) { - *r_radius = path->data[0].radius; - } - } - else if (ctime > 1.0f) { - sub_v3_v3v3(dvec, path->data[path->len - 1].vec, path->data[path->len - 2].vec); - mul_v3_fl(dvec, (ctime - 1.0f) * (float)path->len); - add_v3_v3(r_vec, dvec); - if (r_quat) { - copy_qt_qt(r_quat, path->data[path->len - 1].quat); - } - if (r_radius) { - *r_radius = path->data[path->len - 1].radius; - } - /* weight - not used but could be added */ - } - } - return true; - } - return false; -} - -/** - * For each point, rotate & translate to curve use path, since it has constant distances. + * For each point, rotate & translate to curve. * * \param co: local coord, result local too. * \param r_quat: returns quaternion for rotation, @@ -155,7 +87,7 @@ static bool calc_curve_deform( return false; } - if (ob_curve->runtime.curve_cache->path == NULL) { + if (ob_curve->runtime.curve_cache->anim_path_accum_length == NULL) { return false; /* happens on append, cyclic dependencies and empty curves */ } @@ -172,8 +104,10 @@ static bool calc_curve_deform( } } else { - if (LIKELY(ob_curve->runtime.curve_cache->path->totdist > FLT_EPSILON)) { - fac = -(co[index] - cd->dmax[index]) / (ob_curve->runtime.curve_cache->path->totdist); + CurveCache *cc = ob_curve->runtime.curve_cache; + float totdist = BKE_anim_path_get_length(cc); + if (LIKELY(totdist > FLT_EPSILON)) { + fac = -(co[index] - cd->dmax[index]) / totdist; } else { fac = 0.0f; @@ -192,8 +126,10 @@ static bool calc_curve_deform( } } else { - if (LIKELY(ob_curve->runtime.curve_cache->path->totdist > FLT_EPSILON)) { - fac = +(co[index] - cd->dmin[index]) / (ob_curve->runtime.curve_cache->path->totdist); + CurveCache *cc = ob_curve->runtime.curve_cache; + float totdist = BKE_anim_path_get_length(cc); + if (LIKELY(totdist > FLT_EPSILON)) { + fac = +(co[index] - cd->dmin[index]) / totdist; } else { fac = 0.0f; @@ -201,7 +137,7 @@ static bool calc_curve_deform( } } - if (where_on_path_deform(ob_curve, fac, loc, dir, new_quat, &radius)) { /* returns OK */ + if (BKE_where_on_path(ob_curve, fac, loc, dir, new_quat, &radius, NULL)) { /* returns OK */ float quat[4], cent[3]; if (cd->no_rot_axis) { /* set by caller */ diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc new file mode 100644 index 00000000000..9cafe1124b1 --- /dev/null +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -0,0 +1,272 @@ +/* + * 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. + */ + +#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; + +blender::Span<SplinePtr> CurveEval::splines() const +{ + return splines_; +} + +blender::MutableSpan<SplinePtr> CurveEval::splines() +{ + return splines_; +} + +/** + * \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()) { + spline->translate(translation); + spline->mark_cache_invalid(); + } +} + +void CurveEval::transform(const float4x4 &matrix) +{ + 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()) { + 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) { + case HD_FREE: + return BezierSpline::HandleType::Free; + case HD_AUTO: + return BezierSpline::HandleType::Auto; + case HD_VECT: + return BezierSpline::HandleType::Vector; + case HD_ALIGN: + return BezierSpline::HandleType::Align; + case HD_AUTO_ANIM: + return BezierSpline::HandleType::Auto; + case HD_ALIGN_DOUBLESIDE: + return BezierSpline::HandleType::Align; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static Spline::NormalCalculationMode normal_mode_from_dna_curve(const int twist_mode) +{ + switch (twist_mode) { + case CU_TWIST_Z_UP: + return Spline::NormalCalculationMode::ZUp; + case CU_TWIST_MINIMUM: + return Spline::NormalCalculationMode::Minimum; + case CU_TWIST_TANGENT: + return Spline::NormalCalculationMode::Tangent; + } + BLI_assert_unreachable(); + return Spline::NormalCalculationMode::Minimum; +} + +static NURBSpline::KnotsMode knots_mode_from_dna_nurb(const short flag) +{ + switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) { + case CU_NURB_ENDPOINT: + return NURBSpline::KnotsMode::EndPoint; + case CU_NURB_BEZIER: + return NURBSpline::KnotsMode::Bezier; + default: + return NURBSpline::KnotsMode::Normal; + } + + BLI_assert_unreachable(); + return NURBSpline::KnotsMode::Normal; +} + +std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + + const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve)); + + /* TODO: Optimize by reserving the correct points size. */ + LISTBASE_FOREACH (const Nurb *, nurb, nurbs) { + switch (nurb->type) { + case CU_BEZIER: { + std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BezTriple &bezt : Span(nurb->bezt, nurb->pntsu)) { + spline->add_point(bezt.vec[1], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h1), + bezt.vec[0], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h2), + bezt.vec[2], + bezt.radius, + bezt.tilt); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + case CU_NURBS: { + std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + spline->set_order(nurb->orderu); + spline->knots_mode = knots_mode_from_dna_nurb(nurb->flagu); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + case CU_POLY: { + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + } + + /* 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()) { + 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/curveprofile.c b/source/blender/blenkernel/intern/curveprofile.c index 752e0d4dfcf..00cdc7b3031 100644 --- a/source/blender/blenkernel/intern/curveprofile.c +++ b/source/blender/blenkernel/intern/curveprofile.c @@ -1015,7 +1015,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, { const float total_length = BKE_curveprofile_total_length(profile); const float segment_length = total_length / n_segments; - float length_travelled = 0.0f; float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0); float distance_to_previous_table_point = 0.0f; int i_table = 0; @@ -1029,7 +1028,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, for (int i = 1; i < n_segments; i++) { /* Travel over all of the points that fit inside this segment. */ while (distance_to_next_table_point < segment_left) { - length_travelled += distance_to_next_table_point; segment_left -= distance_to_next_table_point; i_table++; distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, i_table); @@ -1057,7 +1055,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, /* We sampled in between this table point and the next, so the next travel step is smaller. */ distance_to_next_table_point -= segment_left; distance_to_previous_table_point += segment_left; - length_travelled += segment_left; segment_left = segment_length; } } diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index d8d9675b42b..710dfdaf137 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -1226,8 +1226,6 @@ static void layerInterp_mvert_skin(const void **sources, int count, void *dest) { - MVertSkin *vs_dst = dest; - float radius[3]; zero_v3(radius); @@ -1239,7 +1237,7 @@ static void layerInterp_mvert_skin(const void **sources, } /* Delay writing to the destination in case dest is in sources. */ - vs_dst = dest; + MVertSkin *vs_dst = dest; copy_v3_v3(vs_dst->radius, radius); vs_dst->flag &= ~MVERT_SKIN_ROOT; } diff --git a/source/blender/blenkernel/intern/customdata_file.c b/source/blender/blenkernel/intern/customdata_file.c index 470c2f2d246..314d5f4ff82 100644 --- a/source/blender/blenkernel/intern/customdata_file.c +++ b/source/blender/blenkernel/intern/customdata_file.c @@ -24,13 +24,13 @@ #include "MEM_guardedalloc.h" +#include "BLI_endian_defines.h" #include "BLI_endian_switch.h" #include "BLI_fileops.h" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BKE_customdata_file.h" -#include "BKE_global.h" /************************* File Format Definitions ***************************/ @@ -167,7 +167,7 @@ static bool cdf_read_header(CDataFile *cdf) offset += header->structbytes; header->structbytes = sizeof(CDataFileHeader); - if (fseek(f, offset, SEEK_SET) != 0) { + if (BLI_fseek(f, offset, SEEK_SET) != 0) { return false; } @@ -201,7 +201,7 @@ static bool cdf_read_header(CDataFile *cdf) mesh->structbytes = sizeof(CDataFileMeshHeader); } - if (fseek(f, offset, SEEK_SET) != 0) { + if (BLI_fseek(f, offset, SEEK_SET) != 0) { return false; } @@ -233,7 +233,7 @@ static bool cdf_read_header(CDataFile *cdf) offset += layer->structbytes; layer->structbytes = sizeof(CDataFileLayer); - if (fseek(f, offset, SEEK_SET) != 0) { + if (BLI_fseek(f, offset, SEEK_SET) != 0) { return false; } } @@ -321,7 +321,7 @@ bool cdf_read_layer(CDataFile *cdf, CDataFileLayer *blay) offset += cdf->layer[a].datasize; } - return (fseek(cdf->readf, offset, SEEK_SET) == 0); + return (BLI_fseek(cdf->readf, offset, SEEK_SET) == 0); } bool cdf_read_data(CDataFile *cdf, unsigned int size, void *data) diff --git a/source/blender/blenkernel/intern/deform.c b/source/blender/blenkernel/intern/deform.c index 7832f3cd882..e6ef569d4b9 100644 --- a/source/blender/blenkernel/intern/deform.c +++ b/source/blender/blenkernel/intern/deform.c @@ -1130,11 +1130,13 @@ static void vgroups_datatransfer_interp(const CustomDataTransferLayerMap *laymap MDeformWeight *dw_dst = BKE_defvert_find_index(data_dst, idx_dst); float weight_src = 0.0f, weight_dst = 0.0f; + bool has_dw_sources = false; if (sources) { for (i = count; i--;) { for (j = data_src[i]->totweight; j--;) { if ((dw_src = &data_src[i]->dw[j])->def_nr == idx_src) { weight_src += dw_src->weight * weights[i]; + has_dw_sources = true; break; } } @@ -1152,7 +1154,14 @@ static void vgroups_datatransfer_interp(const CustomDataTransferLayerMap *laymap CLAMP(weight_src, 0.0f, 1.0f); - if (!dw_dst) { + /* Do not create a destination MDeformWeight data if we had no sources at all. */ + if (!has_dw_sources) { + BLI_assert(weight_src == 0.0f); + if (dw_dst) { + dw_dst->weight = weight_src; + } + } + else if (!dw_dst) { BKE_defvert_add_index_notest(data_dst, idx_dst, weight_src); } else { diff --git a/source/blender/blenkernel/intern/displist.c b/source/blender/blenkernel/intern/displist.cc index d4581991566..70355f3883d 100644 --- a/source/blender/blenkernel/intern/displist.c +++ b/source/blender/blenkernel/intern/displist.cc @@ -21,9 +21,9 @@ * \ingroup bke */ -#include <math.h> -#include <stdio.h> -#include <string.h> +#include <cmath> +#include <cstdio> +#include <cstring> #include "MEM_guardedalloc.h" @@ -34,6 +34,7 @@ #include "DNA_vfont_types.h" #include "BLI_bitmap.h" +#include "BLI_index_range.hh" #include "BLI_linklist.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -60,9 +61,11 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" +using blender::IndexRange; + static void boundbox_displist_object(Object *ob); -void BKE_displist_elem_free(DispList *dl) +static void displist_elem_free(DispList *dl) { if (dl) { if (dl->verts) { @@ -74,9 +77,6 @@ void BKE_displist_elem_free(DispList *dl) if (dl->index) { MEM_freeN(dl->index); } - if (dl->bevel_split) { - MEM_freeN(dl->bevel_split); - } MEM_freeN(dl); } } @@ -85,26 +85,11 @@ void BKE_displist_free(ListBase *lb) { DispList *dl; - while ((dl = BLI_pophead(lb))) { - BKE_displist_elem_free(dl); + while ((dl = (DispList *)BLI_pophead(lb))) { + displist_elem_free(dl); } } -DispList *BKE_displist_find_or_create(ListBase *lb, int type) -{ - LISTBASE_FOREACH (DispList *, dl, lb) { - if (dl->type == type) { - return dl; - } - } - - DispList *dl = MEM_callocN(sizeof(DispList), "find_disp"); - dl->type = type; - BLI_addtail(lb, dl); - - return dl; -} - DispList *BKE_displist_find(ListBase *lb, int type) { LISTBASE_FOREACH (DispList *, dl, lb) { @@ -113,12 +98,12 @@ DispList *BKE_displist_find(ListBase *lb, int type) } } - return NULL; + return nullptr; } -bool BKE_displist_has_faces(ListBase *lb) +bool BKE_displist_has_faces(const ListBase *lb) { - LISTBASE_FOREACH (DispList *, dl, lb) { + LISTBASE_FOREACH (const DispList *, dl, lb) { if (ELEM(dl->type, DL_INDEX3, DL_INDEX4, DL_SURF)) { return true; } @@ -127,20 +112,16 @@ bool BKE_displist_has_faces(ListBase *lb) return false; } -void BKE_displist_copy(ListBase *lbn, ListBase *lb) +void BKE_displist_copy(ListBase *lbn, const ListBase *lb) { BKE_displist_free(lbn); LISTBASE_FOREACH (const DispList *, dl, lb) { - DispList *dln = MEM_dupallocN(dl); + DispList *dln = (DispList *)MEM_dupallocN(dl); BLI_addtail(lbn, dln); - dln->verts = MEM_dupallocN(dl->verts); - dln->nors = MEM_dupallocN(dl->nors); - dln->index = MEM_dupallocN(dl->index); - - if (dl->bevel_split) { - dln->bevel_split = MEM_dupallocN(dl->bevel_split); - } + dln->verts = (float *)MEM_dupallocN(dl->verts); + dln->nors = (float *)MEM_dupallocN(dl->nors); + dln->index = (int *)MEM_dupallocN(dl->index); } } @@ -153,8 +134,8 @@ void BKE_displist_normals_add(ListBase *lb) LISTBASE_FOREACH (DispList *, dl, lb) { if (dl->type == DL_INDEX3) { - if (dl->nors == NULL) { - dl->nors = MEM_callocN(sizeof(float[3]), "dlnors"); + if (dl->nors == nullptr) { + dl->nors = (float *)MEM_callocN(sizeof(float[3]), "dlnors"); if (dl->flag & DL_BACK_CURVE) { dl->nors[2] = -1.0f; @@ -165,8 +146,8 @@ void BKE_displist_normals_add(ListBase *lb) } } else if (dl->type == DL_SURF) { - if (dl->nors == NULL) { - dl->nors = MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors"); + if (dl->nors == nullptr) { + dl->nors = (float *)MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors"); vdata = dl->verts; ndata = dl->nors; @@ -215,9 +196,9 @@ void BKE_displist_normals_add(ListBase *lb) } } -void BKE_displist_count(ListBase *lb, int *totvert, int *totface, int *tottri) +void BKE_displist_count(const ListBase *lb, int *totvert, int *totface, int *tottri) { - LISTBASE_FOREACH (DispList *, dl, lb) { + LISTBASE_FOREACH (const DispList *, dl, lb) { int vert_tot = 0; int face_tot = 0; int tri_tot = 0; @@ -258,7 +239,8 @@ void BKE_displist_count(ListBase *lb, int *totvert, int *totface, int *tottri) } } -bool BKE_displist_surfindex_get(DispList *dl, int a, int *b, int *p1, int *p2, int *p3, int *p4) +bool BKE_displist_surfindex_get( + const DispList *dl, int a, int *b, int *p1, int *p2, int *p3, int *p4) { if ((dl->flag & DL_CYCL_V) == 0 && a == (dl->parts) - 1) { return false; @@ -344,9 +326,9 @@ static void curve_to_displist(const Curve *cu, * and resolution > 1. */ const bool use_cyclic_sample = is_cyclic && (samples_len != 2); - DispList *dl = MEM_callocN(sizeof(DispList), __func__); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); /* Add one to the length because of 'BKE_curve_forward_diff_bezier'. */ - dl->verts = MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = samples_len; @@ -399,8 +381,8 @@ static void curve_to_displist(const Curve *cu, } else if (nu->type == CU_NURBS) { const int len = (resolution * SEGMENTSU(nu)); - DispList *dl = MEM_callocN(sizeof(DispList), __func__); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = len; @@ -408,12 +390,12 @@ static void curve_to_displist(const Curve *cu, dl->charidx = nu->charidx; dl->type = is_cyclic ? DL_POLY : DL_SEGM; - BKE_nurb_makeCurve(nu, dl->verts, NULL, NULL, NULL, resolution, sizeof(float[3])); + BKE_nurb_makeCurve(nu, dl->verts, nullptr, nullptr, nullptr, resolution, sizeof(float[3])); } else if (nu->type == CU_POLY) { const int len = nu->pntsu; - DispList *dl = MEM_callocN(sizeof(DispList), __func__); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = len; @@ -441,7 +423,7 @@ void BKE_displist_fill(const ListBase *dispbase, const float normal_proj[3], const bool flip_normal) { - if (dispbase == NULL) { + if (dispbase == nullptr) { return; } if (BLI_listbase_is_empty(dispbase)) { @@ -477,14 +459,14 @@ void BKE_displist_fill(const ListBase *dispbase, sf_ctx.poly_nr++; /* Make verts and edges. */ - ScanFillVert *sf_vert = NULL; - ScanFillVert *sf_vert_last = NULL; - ScanFillVert *sf_vert_new = NULL; + ScanFillVert *sf_vert = nullptr; + ScanFillVert *sf_vert_last = nullptr; + ScanFillVert *sf_vert_new = nullptr; for (int i = 0; i < dl->nr; i++) { sf_vert_last = sf_vert; sf_vert = BLI_scanfill_vert_add(&sf_ctx, &dl->verts[3 * i]); totvert++; - if (sf_vert_last == NULL) { + if (sf_vert_last == nullptr) { sf_vert_new = sf_vert; } else { @@ -492,7 +474,7 @@ void BKE_displist_fill(const ListBase *dispbase, } } - if (sf_vert != NULL && sf_vert_new != NULL) { + if (sf_vert != nullptr && sf_vert_new != nullptr) { BLI_scanfill_edge_add(&sf_ctx, sf_vert, sf_vert_new); } } @@ -509,7 +491,7 @@ void BKE_displist_fill(const ListBase *dispbase, const int triangles_len = BLI_scanfill_calc_ex(&sf_ctx, scanfill_flag, normal_proj); if (totvert != 0 && triangles_len != 0) { - DispList *dlnew = MEM_callocN(sizeof(DispList), "filldisplist"); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), "filldisplist"); dlnew->type = DL_INDEX3; dlnew->flag = (dl_flag_accum & (DL_BACK_CURVE | DL_FRONT_CURVE)); dlnew->rt = (dl_rt_accum & CU_SMOOTH); @@ -517,8 +499,8 @@ void BKE_displist_fill(const ListBase *dispbase, dlnew->nr = totvert; dlnew->parts = triangles_len; - dlnew->index = MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex"); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * totvert, "dlverts"); + dlnew->index = (int *)MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex"); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * totvert, "dlverts"); /* vert data */ int i; @@ -557,16 +539,16 @@ void BKE_displist_fill(const ListBase *dispbase, static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) { - ListBase front = {NULL, NULL}; - ListBase back = {NULL, NULL}; + ListBase front = {nullptr, nullptr}; + ListBase back = {nullptr, nullptr}; LISTBASE_FOREACH (const DispList *, dl, dispbase) { if (dl->type == DL_SURF) { if ((dl->flag & DL_CYCL_V) && (dl->flag & DL_CYCL_U) == 0) { if ((cu->flag & CU_BACK) && (dl->flag & DL_BACK_CURVE)) { - DispList *dlnew = MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); BLI_addtail(&front, dlnew); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; dlnew->parts = 1; dlnew->type = DL_POLY; @@ -583,9 +565,9 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) } } if ((cu->flag & CU_FRONT) && (dl->flag & DL_FRONT_CURVE)) { - DispList *dlnew = MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); BLI_addtail(&back, dlnew); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; dlnew->parts = 1; dlnew->type = DL_POLY; @@ -615,7 +597,7 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) BKE_displist_fill(dispbase, dispbase, z_up, false); } -static void curve_to_filledpoly(Curve *cu, ListBase *UNUSED(nurb), ListBase *dispbase) +static void curve_to_filledpoly(const Curve *cu, ListBase *dispbase) { if (!CU_DO_2DFILL(cu)) { return; @@ -635,18 +617,21 @@ static void curve_to_filledpoly(Curve *cu, ListBase *UNUSED(nurb), ListBase *dis * - first point left, last point right * - based on subdivided points in original curve, not on points in taper curve (still) */ -static float displist_calc_taper(Depsgraph *depsgraph, Scene *scene, Object *taperobj, float fac) +static float displist_calc_taper(Depsgraph *depsgraph, + const Scene *scene, + Object *taperobj, + float fac) { - DispList *dl; - - if (taperobj == NULL || taperobj->type != OB_CURVE) { + if (taperobj == nullptr || taperobj->type != OB_CURVE) { return 1.0; } - dl = taperobj->runtime.curve_cache ? taperobj->runtime.curve_cache->disp.first : NULL; - if (dl == NULL) { + DispList *dl = taperobj->runtime.curve_cache ? + (DispList *)taperobj->runtime.curve_cache->disp.first : + nullptr; + if (dl == nullptr) { BKE_displist_make_curveTypes(depsgraph, scene, taperobj, false, false); - dl = taperobj->runtime.curve_cache->disp.first; + dl = (DispList *)taperobj->runtime.curve_cache->disp.first; } if (dl) { float minx, dx, *fp; @@ -678,9 +663,9 @@ static float displist_calc_taper(Depsgraph *depsgraph, Scene *scene, Object *tap } float BKE_displist_calc_taper( - Depsgraph *depsgraph, Scene *scene, Object *taperobj, int cur, int tot) + Depsgraph *depsgraph, const Scene *scene, Object *taperobj, int cur, int tot) { - float fac = ((float)cur) / (float)(tot - 1); + const float fac = ((float)cur) / (float)(tot - 1); return displist_calc_taper(depsgraph, scene, taperobj, fac); } @@ -696,7 +681,8 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) BKE_displist_free(&(ob->runtime.curve_cache->disp)); } else { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for MBall"); + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for MBall"); } BKE_mball_polygonize(depsgraph, scene, ob, &ob->runtime.curve_cache->disp); @@ -704,7 +690,7 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) object_deform_mball(ob, &ob->runtime.curve_cache->disp); - /* NOP for MBALLs anyway... */ + /* No-op for MBALLs anyway... */ boundbox_displist_object(ob); } } @@ -720,30 +706,22 @@ void BKE_displist_make_mball_forRender(Depsgraph *depsgraph, object_deform_mball(ob, dispbase); } -static ModifierData *curve_get_tessellate_point(Scene *scene, - Object *ob, +static ModifierData *curve_get_tessellate_point(const Scene *scene, + const Object *ob, const bool for_render, const bool editmode) { VirtualModifierData virtualModifierData; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - ModifierData *pretessellatePoint; - int required_mode; - - if (for_render) { - required_mode = eModifierMode_Render; - } - else { - required_mode = eModifierMode_Realtime; - } + ModifierMode required_mode = for_render ? eModifierMode_Render : eModifierMode_Realtime; if (editmode) { - required_mode |= eModifierMode_Editmode; + required_mode = (ModifierMode)((int)required_mode | eModifierMode_Editmode); } - pretessellatePoint = NULL; + ModifierData *pretessellatePoint = nullptr; for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; @@ -757,8 +735,7 @@ static ModifierData *curve_get_tessellate_point(Scene *scene, /* this modifiers are moving point of tessellation automatically * (some of them even can't be applied on tessellated curve), set flag - * for information button in modifier's header - */ + * for information button in modifier's header. */ md->mode |= eModifierMode_ApplyOnSpline; } else if (md->mode & eModifierMode_ApplyOnSpline) { @@ -769,48 +746,39 @@ static ModifierData *curve_get_tessellate_point(Scene *scene, return pretessellatePoint; } -/* Return true if any modifier was applied. */ +/** + * \return True if any modifier was applied. + */ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, - Scene *scene, + const Scene *scene, Object *ob, ListBase *source_nurb, ListBase *target_nurb, const bool for_render) { - VirtualModifierData virtualModifierData; - ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - ModifierData *pretessellatePoint; - Curve *cu = ob->data; - int numElems = 0, numVerts = 0; - const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); - ModifierApplyFlag apply_flag = 0; - float(*deformedVerts)[3] = NULL; - float *keyVerts = NULL; - int required_mode; - bool modified = false; + const Curve *cu = (const Curve *)ob->data; BKE_modifiers_clear_errors(ob); + const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); + ModifierMode required_mode = for_render ? eModifierMode_Render : eModifierMode_Realtime; if (editmode) { - apply_flag |= MOD_APPLY_USECACHE; + required_mode = (ModifierMode)((int)required_mode | eModifierMode_Editmode); } - if (for_render) { - apply_flag |= MOD_APPLY_RENDER; - required_mode = eModifierMode_Render; - } - else { - required_mode = eModifierMode_Realtime; - } - - const ModifierEvalContext mectx = {depsgraph, ob, apply_flag}; - - pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); + ModifierApplyFlag apply_flag = (ModifierApplyFlag)0; if (editmode) { - required_mode |= eModifierMode_Editmode; + apply_flag = MOD_APPLY_USECACHE; + } + if (for_render) { + apply_flag = MOD_APPLY_RENDER; } + float *keyVerts = nullptr; + float(*deformedVerts)[3] = nullptr; + int numVerts = 0; if (!editmode) { + int numElems = 0; keyVerts = BKE_key_evaluate_object(ob, &numElems); if (keyVerts) { @@ -824,9 +792,15 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, } } + const ModifierEvalContext mectx = {depsgraph, ob, apply_flag}; + ModifierData *pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); + bool modified = false; + if (pretessellatePoint) { - for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + VirtualModifierData virtualModifierData; + for (ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); md; + md = md->next) { + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; @@ -839,7 +813,7 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, deformedVerts = BKE_curve_nurbs_vert_coords_alloc(source_nurb, &numVerts); } - mti->deformVerts(md, &mectx, NULL, deformedVerts, numVerts); + mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts); modified = true; if (md == pretessellatePoint) { @@ -864,18 +838,16 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[3] { - float(*allverts)[3], *fp; - *r_vert_len = 0; LISTBASE_FOREACH (DispList *, dl, dispbase) { *r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr; } - allverts = MEM_mallocN(sizeof(float[3]) * (*r_vert_len), "displist_vert_coords_alloc allverts"); - fp = (float *)allverts; + float(*allverts)[3] = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__); + float *fp = (float *)allverts; LISTBASE_FOREACH (DispList *, dl, dispbase) { - int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); + const int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); memcpy(fp, dl->verts, sizeof(float) * ofs); fp += ofs; } @@ -883,11 +855,9 @@ static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[ return allverts; } -static void displist_vert_coords_apply(ListBase *dispbase, float (*allverts)[3]) +static void displist_vert_coords_apply(ListBase *dispbase, const float (*allverts)[3]) { - const float *fp; - - fp = (float *)allverts; + const float *fp = (float *)allverts; LISTBASE_FOREACH (DispList *, dl, dispbase) { int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); memcpy(dl->verts, fp, sizeof(float) * ofs); @@ -896,70 +866,62 @@ static void displist_vert_coords_apply(ListBase *dispbase, float (*allverts)[3]) } static void curve_calc_modifiers_post(Depsgraph *depsgraph, - Scene *scene, + const Scene *scene, Object *ob, - ListBase *nurb, ListBase *dispbase, - Mesh **r_final, const bool for_render, - const bool force_mesh_conversion) + const bool force_mesh_conversion, + Mesh **r_final) { - VirtualModifierData virtualModifierData; - ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - ModifierData *pretessellatePoint; - Curve *cu = ob->data; - int required_mode = 0, totvert = 0; + const Curve *cu = (const Curve *)ob->data; + const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); - Mesh *modified = NULL, *mesh_applied; - float(*vertCos)[3] = NULL; - int useCache = !for_render; - ModifierApplyFlag apply_flag = 0; + const bool use_cache = !for_render; - if (for_render) { - apply_flag |= MOD_APPLY_RENDER; - required_mode = eModifierMode_Render; - } - else { - required_mode = eModifierMode_Realtime; + ModifierApplyFlag apply_flag = for_render ? MOD_APPLY_RENDER : (ModifierApplyFlag)0; + ModifierMode required_mode = for_render ? eModifierMode_Render : eModifierMode_Realtime; + if (editmode) { + required_mode = (ModifierMode)((int)required_mode | eModifierMode_Editmode); } const ModifierEvalContext mectx_deform = { - depsgraph, ob, editmode ? apply_flag | MOD_APPLY_USECACHE : apply_flag}; + depsgraph, ob, editmode ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag}; const ModifierEvalContext mectx_apply = { - depsgraph, ob, useCache ? apply_flag | MOD_APPLY_USECACHE : apply_flag}; - - pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); + depsgraph, + ob, + use_cache ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag}; - if (editmode) { - required_mode |= eModifierMode_Editmode; - } + ModifierData *pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); - if (pretessellatePoint) { - md = pretessellatePoint->next; - } + VirtualModifierData virtualModifierData; + ModifierData *md = pretessellatePoint == nullptr ? + BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData) : + pretessellatePoint->next; if (r_final && *r_final) { - BKE_id_free(NULL, *r_final); + BKE_id_free(nullptr, *r_final); } + Mesh *modified = nullptr; + float(*vertCos)[3] = nullptr; for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; } /* If we need normals, no choice, have to convert to mesh now. */ - bool need_normal = mti->dependsOnNormals != NULL && mti->dependsOnNormals(md); + const bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md); /* XXX 2.8 : now that batch cache is stored inside the ob->data * we need to create a Mesh for each curve that uses modifiers. */ - if (modified == NULL /* && need_normal */) { - if (vertCos != NULL) { + if (modified == nullptr /* && need_normal */) { + if (vertCos != nullptr) { displist_vert_coords_apply(dispbase, vertCos); } if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, nurb, dispbase); + curve_to_filledpoly(cu, dispbase); } modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); @@ -968,6 +930,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (mti->type == eModifierTypeType_OnlyDeform || (mti->type == eModifierTypeType_DeformOrConstruct && !modified)) { if (modified) { + int totvert = 0; if (!vertCos) { vertCos = BKE_mesh_vert_coords_alloc(modified, &totvert); } @@ -977,26 +940,26 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, mti->deformVerts(md, &mectx_deform, modified, vertCos, totvert); } else { + int totvert = 0; if (!vertCos) { vertCos = displist_vert_coords_alloc(dispbase, &totvert); } - mti->deformVerts(md, &mectx_deform, NULL, vertCos, totvert); + mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert); } } else { if (!r_final) { - /* makeDisplistCurveTypes could be used for beveling, where derived mesh + /* makeDisplistCurveTypes could be used for beveling, where mesh * is totally unnecessary, so we could stop modifiers applying - * when we found constructive modifier but derived mesh is unwanted result - */ + * when we found constructive modifier but mesh is unwanted. */ break; } if (modified) { if (vertCos) { Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE); - BKE_id_free(NULL, modified); + nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); + BKE_id_free(nullptr, modified); modified = temp_mesh; BKE_mesh_vert_coords_apply(modified, vertCos); @@ -1008,7 +971,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, } if (ELEM(ob->type, OB_CURVE, OB_FONT) && (cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, nurb, dispbase); + curve_to_filledpoly(cu, dispbase); } modified = BKE_mesh_new_nomain_from_curve_displist(ob, dispbase); @@ -1017,19 +980,17 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (vertCos) { /* Vertex coordinates were applied to necessary data, could free it */ MEM_freeN(vertCos); - vertCos = NULL; + vertCos = nullptr; } if (need_normal) { BKE_mesh_ensure_normals(modified); } - mesh_applied = mti->modifyMesh(md, &mectx_apply, modified); + Mesh *mesh_applied = mti->modifyMesh(md, &mectx_apply, modified); if (mesh_applied) { - /* Modifier returned a new derived mesh */ - - if (modified && modified != mesh_applied) { /* Modifier */ - BKE_id_free(NULL, modified); + if (modified && modified != mesh_applied) { + BKE_id_free(nullptr, modified); } modified = mesh_applied; } @@ -1038,8 +999,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (vertCos) { if (modified) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE); - BKE_id_free(NULL, modified); + Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( + nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); + BKE_id_free(nullptr, modified); modified = temp_mesh; BKE_mesh_vert_coords_apply(modified, vertCos); @@ -1050,7 +1012,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, else { displist_vert_coords_apply(dispbase, vertCos); MEM_freeN(vertCos); - vertCos = NULL; + vertCos = nullptr; } } @@ -1078,39 +1040,37 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, BKE_mesh_ensure_normals(modified); /* Special tweaks, needed since neither BKE_mesh_new_nomain_from_template() nor - * BKE_mesh_new_nomain_from_curve_displist() properly duplicate mat info... - */ + * BKE_mesh_new_nomain_from_curve_displist() properly duplicate mat info... */ BLI_strncpy(modified->id.name, cu->id.name, sizeof(modified->id.name)); *((short *)modified->id.name) = ID_ME; MEM_SAFE_FREE(modified->mat); /* Set flag which makes it easier to see what's going on in a debugger. */ modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT; - modified->mat = MEM_dupallocN(cu->mat); + modified->mat = (Material **)MEM_dupallocN(cu->mat); modified->totcol = cu->totcol; (*r_final) = modified; } else { - (*r_final) = NULL; + (*r_final) = nullptr; } } - else if (modified != NULL) { + else if (modified != nullptr) { /* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */ - BKE_id_free(NULL, modified); + BKE_id_free(nullptr, modified); } } static void displist_surf_indices(DispList *dl) { - int a, b, p1, p2, p3, p4; - int *index; + int b, p1, p2, p3, p4; dl->totindex = 0; - index = dl->index = MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1), - "index array nurbs"); + int *index = dl->index = (int *)MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1), + "index array nurbs"); - for (a = 0; a < dl->parts; a++) { + for (int a = 0; a < dl->parts; a++) { if (BKE_displist_surfindex_get(dl, a, &b, &p1, &p2, &p3, &p4) == 0) { break; @@ -1132,28 +1092,25 @@ static void displist_surf_indices(DispList *dl) } } -void BKE_displist_make_surf(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - ListBase *dispbase, - Mesh **r_final, - const bool for_render, - const bool for_orco) +static void displist_make_surf(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + ListBase *dispbase, + Mesh **r_final, + const bool for_render, + const bool for_orco) { - ListBase nubase = {NULL, NULL}; - Curve *cu = ob->data; - DispList *dl; - float *data; - int len; - bool force_mesh_conversion = false; + ListBase nubase = {nullptr, nullptr}; + const Curve *cu = (const Curve *)ob->data; if (!for_render && cu->editnurb) { - BKE_nurbList_duplicate(&nubase, BKE_curve_editNurbs_get(cu)); + BKE_nurbList_duplicate(&nubase, BKE_curve_editNurbs_get(const_cast<Curve *>(cu))); } else { BKE_nurbList_duplicate(&nubase, &cu->nurb); } + bool force_mesh_conversion = false; if (!for_orco) { force_mesh_conversion = BKE_curve_calc_modifiers_pre( depsgraph, scene, ob, &nubase, &nubase, for_render); @@ -1164,34 +1121,23 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, continue; } - int resolu = nu->resolu, resolv = nu->resolv; - - if (for_render) { - if (cu->resolu_ren) { - resolu = cu->resolu_ren; - } - if (cu->resolv_ren) { - resolv = cu->resolv_ren; - } - } + const int resolu = (for_render && cu->resolu_ren) ? cu->resolu_ren : nu->resolu; + const int resolv = (for_render && cu->resolv_ren) ? cu->resolv_ren : nu->resolv; if (nu->pntsv == 1) { - len = SEGMENTSU(nu) * resolu; + const int len = SEGMENTSU(nu) * resolu; - dl = MEM_callocN(sizeof(DispList), "makeDispListsurf"); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf"); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(dispbase, dl); dl->parts = 1; dl->nr = len; dl->col = nu->mat_nr; dl->charidx = nu->charidx; + dl->rt = nu->flag; - /* dl->rt will be used as flag for render face and */ - /* CU_2D conflicts with R_NOPUNOFLIP */ - dl->rt = nu->flag & ~CU_2D; - - data = dl->verts; + float *data = dl->verts; if (nu->flagu & CU_NURB_CYCLIC) { dl->type = DL_POLY; } @@ -1199,23 +1145,20 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, dl->type = DL_SEGM; } - BKE_nurb_makeCurve(nu, data, NULL, NULL, NULL, resolu, sizeof(float[3])); + BKE_nurb_makeCurve(nu, data, nullptr, nullptr, nullptr, resolu, sizeof(float[3])); } else { - len = (nu->pntsu * resolu) * (nu->pntsv * resolv); + const int len = (nu->pntsu * resolu) * (nu->pntsv * resolv); - dl = MEM_callocN(sizeof(DispList), "makeDispListsurf"); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf"); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(dispbase, dl); dl->col = nu->mat_nr; dl->charidx = nu->charidx; + dl->rt = nu->flag; - /* dl->rt will be used as flag for render face and */ - /* CU_2D conflicts with R_NOPUNOFLIP */ - dl->rt = nu->flag & ~CU_2D; - - data = dl->verts; + float *data = dl->verts; dl->type = DL_SURF; dl->parts = (nu->pntsu * resolu); /* in reverse, because makeNurbfaces works that way */ @@ -1237,7 +1180,7 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, if (!for_orco) { BKE_nurbList_duplicate(&ob->runtime.curve_cache->deformed_nurbs, &nubase); curve_calc_modifiers_post( - depsgraph, scene, ob, &nubase, dispbase, r_final, for_render, force_mesh_conversion); + depsgraph, scene, ob, dispbase, for_render, force_mesh_conversion, r_final); } BKE_nurbList_free(&nubase); @@ -1262,7 +1205,7 @@ static void rotateBevelPiece(const Curve *cu, vec[1] = fp[2]; vec[2] = 0.0; - if (nbevp == NULL) { + if (nbevp == nullptr) { copy_v3_v3(data, bevp->vec); copy_qt_qt(quat, bevp->quat); } @@ -1280,7 +1223,7 @@ static void rotateBevelPiece(const Curve *cu, else { float sina, cosa; - if (nbevp == NULL) { + if (nbevp == nullptr) { copy_v3_v3(data, bevp->vec); sina = bevp->sina; cosa = bevp->cosa; @@ -1304,12 +1247,13 @@ static void rotateBevelPiece(const Curve *cu, *r_data = data; } -static void fillBevelCap(Nurb *nu, DispList *dlb, float *prev_fp, ListBase *dispbase) +static void fillBevelCap(const Nurb *nu, + const DispList *dlb, + const float *prev_fp, + ListBase *dispbase) { - DispList *dl; - - dl = MEM_callocN(sizeof(DispList), "makeDispListbev2"); - dl->verts = MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev2"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts"); memcpy(dl->verts, prev_fp, sizeof(float[3]) * dlb->nr); dl->type = DL_POLY; @@ -1318,18 +1262,15 @@ static void fillBevelCap(Nurb *nu, DispList *dlb, float *prev_fp, ListBase *disp dl->nr = dlb->nr; dl->col = nu->mat_nr; dl->charidx = nu->charidx; - - /* dl->rt will be used as flag for render face and */ - /* CU_2D conflicts with R_NOPUNOFLIP */ - dl->rt = nu->flag & ~CU_2D; + dl->rt = nu->flag; BLI_addtail(dispbase, dl); } static void calc_bevfac_segment_mapping( - BevList *bl, float bevfac, float spline_length, int *r_bev, float *r_blend) + const BevList *bl, float bevfac, float spline_length, int *r_bev, float *r_blend) { - float normlen, normsum = 0.0f; + float normsum = 0.0f; float *seglen = bl->seglen; int *segbevcount = bl->segbevcount; int bevcount = 0, nr = bl->nr; @@ -1338,7 +1279,7 @@ static void calc_bevfac_segment_mapping( *r_bev = (int)bev_fl; while (bevcount < nr - 1) { - normlen = *seglen / spline_length; + float normlen = *seglen / spline_length; if (normsum + normlen > bevfac) { bev_fl = bevcount + (bevfac - normsum) / normlen * *segbevcount; *r_bev = (int)bev_fl; @@ -1353,7 +1294,7 @@ static void calc_bevfac_segment_mapping( } static void calc_bevfac_spline_mapping( - BevList *bl, float bevfac, float spline_length, int *r_bev, float *r_blend) + const BevList *bl, float bevfac, float spline_length, int *r_bev, float *r_blend) { const float len_target = bevfac * spline_length; BevPoint *bevp = bl->bevpoints; @@ -1375,7 +1316,7 @@ static void calc_bevfac_spline_mapping( } static void calc_bevfac_mapping_default( - BevList *bl, int *r_start, float *r_firstblend, int *r_steps, float *r_lastblend) + const BevList *bl, int *r_start, float *r_firstblend, int *r_steps, float *r_lastblend) { *r_start = 0; *r_steps = bl->nr; @@ -1383,9 +1324,9 @@ static void calc_bevfac_mapping_default( *r_lastblend = 1.0f; } -static void calc_bevfac_mapping(Curve *cu, - BevList *bl, - Nurb *nu, +static void calc_bevfac_mapping(const Curve *cu, + const BevList *bl, + const Nurb *nu, int *r_start, float *r_firstblend, int *r_steps, @@ -1463,14 +1404,14 @@ static void calc_bevfac_mapping(Curve *cu, } static void do_makeDispListCurveTypes(Depsgraph *depsgraph, - Scene *scene, + const Scene *scene, Object *ob, ListBase *dispbase, const bool for_render, const bool for_orco, Mesh **r_final) { - Curve *cu = ob->data; + const Curve *cu = (const Curve *)ob->data; /* we do allow duplis... this is only displist on curve level */ if (!ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)) { @@ -1478,259 +1419,246 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, } if (ob->type == OB_SURF) { - BKE_displist_make_surf(depsgraph, scene, ob, dispbase, r_final, for_render, for_orco); - } - else if (ELEM(ob->type, OB_CURVE, OB_FONT)) { - ListBase dlbev; - ListBase nubase = {NULL, NULL}; - bool force_mesh_conversion = false; - - BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); - - /* We only re-evaluate path if evaluation is not happening for orco. - * If the calculation happens for orco, we should never free data which - * was needed before and only not needed for orco calculation. - */ - if (!for_orco) { - if (ob->runtime.curve_cache->path) { - free_path(ob->runtime.curve_cache->path); - } - ob->runtime.curve_cache->path = NULL; - } + displist_make_surf(depsgraph, scene, ob, dispbase, r_final, for_render, for_orco); + return; + } - if (ob->type == OB_FONT) { - BKE_vfont_to_curve_nubase(ob, FO_EDIT, &nubase); - } - else { - BKE_nurbList_duplicate(&nubase, BKE_curve_nurbs_get(cu)); - } + ListBase nubase = {nullptr, nullptr}; + bool force_mesh_conversion = false; + + BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); - if (!for_orco) { - force_mesh_conversion = BKE_curve_calc_modifiers_pre( - depsgraph, scene, ob, &nubase, &nubase, for_render); + /* We only re-evaluate path if evaluation is not happening for orco. + * If the calculation happens for orco, we should never free data which + * was needed before and only not needed for orco calculation. */ + if (!for_orco) { + if (ob->runtime.curve_cache->anim_path_accum_length) { + MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length); } + ob->runtime.curve_cache->anim_path_accum_length = nullptr; + } - BKE_curve_bevelList_make(ob, &nubase, for_render); + if (ob->type == OB_FONT) { + BKE_vfont_to_curve_nubase(ob, FO_EDIT, &nubase); + } + else { + BKE_nurbList_duplicate(&nubase, BKE_curve_nurbs_get(const_cast<Curve *>(cu))); + } - /* If curve has no bevel will return nothing */ - BKE_curve_bevel_make(ob, &dlbev); + if (!for_orco) { + force_mesh_conversion = BKE_curve_calc_modifiers_pre( + depsgraph, scene, ob, &nubase, &nubase, for_render); + } - /* no bevel or extrude, and no width correction? */ - if (!dlbev.first && cu->width == 1.0f) { - curve_to_displist(cu, &nubase, for_render, dispbase); - } - else { - const float widfac = cu->width - 1.0f; - BevList *bl = ob->runtime.curve_cache->bev.first; - Nurb *nu = nubase.first; + BKE_curve_bevelList_make(ob, &nubase, for_render); - for (; bl && nu; bl = bl->next, nu = nu->next) { - float *data; + /* If curve has no bevel will return nothing */ + ListBase dlbev = BKE_curve_bevel_make(cu); - if (bl->nr == 0) { /* blank bevel lists can happen */ - continue; - } + /* no bevel or extrude, and no width correction? */ + if (BLI_listbase_is_empty(&dlbev) && cu->width == 1.0f) { + curve_to_displist(cu, &nubase, for_render, dispbase); + } + else { + const float widfac = cu->width - 1.0f; - /* exception handling; curve without bevel or extrude, with width correction */ - if (BLI_listbase_is_empty(&dlbev)) { - DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev"); - dl->verts = MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts"); - BLI_addtail(dispbase, dl); + BevList *bl = (BevList *)ob->runtime.curve_cache->bev.first; + Nurb *nu = (Nurb *)nubase.first; + for (; bl && nu; bl = bl->next, nu = nu->next) { + float *data; - if (bl->poly != -1) { - dl->type = DL_POLY; - } - else { - dl->type = DL_SEGM; - dl->flag = (DL_FRONT_CURVE | DL_BACK_CURVE); - } + if (bl->nr == 0) { /* blank bevel lists can happen */ + continue; + } - dl->parts = 1; - dl->nr = bl->nr; - dl->col = nu->mat_nr; - dl->charidx = nu->charidx; + /* exception handling; curve without bevel or extrude, with width correction */ + if (BLI_listbase_is_empty(&dlbev)) { + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts"); + BLI_addtail(dispbase, dl); - /* dl->rt will be used as flag for render face and */ - /* CU_2D conflicts with R_NOPUNOFLIP */ - dl->rt = nu->flag & ~CU_2D; - - int a = dl->nr; - BevPoint *bevp = bl->bevpoints; - data = dl->verts; - while (a--) { - data[0] = bevp->vec[0] + widfac * bevp->sina; - data[1] = bevp->vec[1] + widfac * bevp->cosa; - data[2] = bevp->vec[2]; - bevp++; - data += 3; - } + if (bl->poly != -1) { + dl->type = DL_POLY; } else { - ListBase bottom_capbase = {NULL, NULL}; - ListBase top_capbase = {NULL, NULL}; - float bottom_no[3] = {0.0f}; - float top_no[3] = {0.0f}; - float first_blend = 0.0f, last_blend = 0.0f; - int start, steps = 0; - - if (nu->flagu & CU_NURB_CYCLIC) { - calc_bevfac_mapping_default(bl, &start, &first_blend, &steps, &last_blend); - } - else { - if (fabsf(cu->bevfac2 - cu->bevfac1) < FLT_EPSILON) { - continue; - } - - calc_bevfac_mapping(cu, bl, nu, &start, &first_blend, &steps, &last_blend); - } + dl->type = DL_SEGM; + dl->flag = (DL_FRONT_CURVE | DL_BACK_CURVE); + } - LISTBASE_FOREACH (DispList *, dlb, &dlbev) { - /* for each part of the bevel use a separate displblock */ - DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev1"); - dl->verts = data = MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts"); - BLI_addtail(dispbase, dl); + dl->parts = 1; + dl->nr = bl->nr; + dl->col = nu->mat_nr; + dl->charidx = nu->charidx; + dl->rt = nu->flag; - dl->type = DL_SURF; + int a = dl->nr; + BevPoint *bevp = bl->bevpoints; + data = dl->verts; + while (a--) { + data[0] = bevp->vec[0] + widfac * bevp->sina; + data[1] = bevp->vec[1] + widfac * bevp->cosa; + data[2] = bevp->vec[2]; + bevp++; + data += 3; + } + } + else { + ListBase bottom_capbase = {nullptr, nullptr}; + ListBase top_capbase = {nullptr, nullptr}; + float bottom_no[3] = {0.0f}; + float top_no[3] = {0.0f}; + float first_blend = 0.0f, last_blend = 0.0f; + int start, steps = 0; + + if (nu->flagu & CU_NURB_CYCLIC) { + calc_bevfac_mapping_default(bl, &start, &first_blend, &steps, &last_blend); + } + else { + if (fabsf(cu->bevfac2 - cu->bevfac1) < FLT_EPSILON) { + continue; + } - dl->flag = dlb->flag & (DL_FRONT_CURVE | DL_BACK_CURVE); - if (dlb->type == DL_POLY) { - dl->flag |= DL_CYCL_U; - } - if ((bl->poly >= 0) && (steps > 2)) { - dl->flag |= DL_CYCL_V; - } + calc_bevfac_mapping(cu, bl, nu, &start, &first_blend, &steps, &last_blend); + } - dl->parts = steps; - dl->nr = dlb->nr; - dl->col = nu->mat_nr; - dl->charidx = nu->charidx; + LISTBASE_FOREACH (DispList *, dlb, &dlbev) { + /* for each part of the bevel use a separate displblock */ + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev1"); + dl->verts = data = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts"); + BLI_addtail(dispbase, dl); - /* dl->rt will be used as flag for render face and */ - /* CU_2D conflicts with R_NOPUNOFLIP */ - dl->rt = nu->flag & ~CU_2D; + dl->type = DL_SURF; - dl->bevel_split = BLI_BITMAP_NEW(steps, "bevel_split"); + dl->flag = dlb->flag & (DL_FRONT_CURVE | DL_BACK_CURVE); + if (dlb->type == DL_POLY) { + dl->flag |= DL_CYCL_U; + } + if ((bl->poly >= 0) && (steps > 2)) { + dl->flag |= DL_CYCL_V; + } - /* for each point of poly make a bevel piece */ - BevPoint *bevp_first = bl->bevpoints; - BevPoint *bevp_last = &bl->bevpoints[bl->nr - 1]; - BevPoint *bevp = &bl->bevpoints[start]; - for (int i = start, a = 0; a < steps; i++, bevp++, a++) { - float radius_factor = 1.0; - float *cur_data = data; + dl->parts = steps; + dl->nr = dlb->nr; + dl->col = nu->mat_nr; + dl->charidx = nu->charidx; + dl->rt = nu->flag; + + /* for each point of poly make a bevel piece */ + BevPoint *bevp_first = bl->bevpoints; + BevPoint *bevp_last = &bl->bevpoints[bl->nr - 1]; + BevPoint *bevp = &bl->bevpoints[start]; + for (int i = start, a = 0; a < steps; i++, bevp++, a++) { + float radius_factor = 1.0; + float *cur_data = data; + + if (cu->taperobj == nullptr) { + radius_factor = bevp->radius; + } + else { + float taper_factor; + if (cu->flag & CU_MAP_TAPER) { + float len = (steps - 3) + first_blend + last_blend; - if (cu->taperobj == NULL) { - radius_factor = bevp->radius; - } - else { - float taper_factor; - if (cu->flag & CU_MAP_TAPER) { - float len = (steps - 3) + first_blend + last_blend; - - if (a == 0) { - taper_factor = 0.0f; - } - else if (a == steps - 1) { - taper_factor = 1.0f; - } - else { - taper_factor = ((float)a - (1.0f - first_blend)) / len; - } + if (a == 0) { + taper_factor = 0.0f; + } + else if (a == steps - 1) { + taper_factor = 1.0f; } else { - float len = bl->nr - 1; - taper_factor = (float)i / len; - - if (a == 0) { - taper_factor += (1.0f - first_blend) / len; - } - else if (a == steps - 1) { - taper_factor -= (1.0f - last_blend) / len; - } + taper_factor = ((float)a - (1.0f - first_blend)) / len; } + } + else { + float len = bl->nr - 1; + taper_factor = (float)i / len; - radius_factor = displist_calc_taper(depsgraph, scene, cu->taperobj, taper_factor); - - if (cu->taper_radius_mode == CU_TAPER_RADIUS_MULTIPLY) { - radius_factor *= bevp->radius; + if (a == 0) { + taper_factor += (1.0f - first_blend) / len; } - else if (cu->taper_radius_mode == CU_TAPER_RADIUS_ADD) { - radius_factor += bevp->radius; + else if (a == steps - 1) { + taper_factor -= (1.0f - last_blend) / len; } } - if (bevp->split_tag) { - BLI_BITMAP_ENABLE(dl->bevel_split, a); - } + radius_factor = displist_calc_taper(depsgraph, scene, cu->taperobj, taper_factor); - /* rotate bevel piece and write in data */ - if ((a == 0) && (bevp != bevp_last)) { - rotateBevelPiece( - cu, bevp, bevp + 1, dlb, 1.0f - first_blend, widfac, radius_factor, &data); + if (cu->taper_radius_mode == CU_TAPER_RADIUS_MULTIPLY) { + radius_factor *= bevp->radius; } - else if ((a == steps - 1) && (bevp != bevp_first)) { - rotateBevelPiece( - cu, bevp, bevp - 1, dlb, 1.0f - last_blend, widfac, radius_factor, &data); - } - else { - rotateBevelPiece(cu, bevp, NULL, dlb, 0.0f, widfac, radius_factor, &data); + else if (cu->taper_radius_mode == CU_TAPER_RADIUS_ADD) { + radius_factor += bevp->radius; } + } - if ((cu->flag & CU_FILL_CAPS) && !(nu->flagu & CU_NURB_CYCLIC)) { - if (a == 1) { - fillBevelCap(nu, dlb, cur_data - 3 * dlb->nr, &bottom_capbase); - copy_v3_v3(bottom_no, bevp->dir); - } - if (a == steps - 1) { - fillBevelCap(nu, dlb, cur_data, &top_capbase); - negate_v3_v3(top_no, bevp->dir); - } - } + /* rotate bevel piece and write in data */ + if ((a == 0) && (bevp != bevp_last)) { + rotateBevelPiece( + cu, bevp, bevp + 1, dlb, 1.0f - first_blend, widfac, radius_factor, &data); + } + else if ((a == steps - 1) && (bevp != bevp_first)) { + rotateBevelPiece( + cu, bevp, bevp - 1, dlb, 1.0f - last_blend, widfac, radius_factor, &data); + } + else { + rotateBevelPiece(cu, bevp, nullptr, dlb, 0.0f, widfac, radius_factor, &data); } - /* gl array drawing: using indices */ - displist_surf_indices(dl); + if ((cu->flag & CU_FILL_CAPS) && !(nu->flagu & CU_NURB_CYCLIC)) { + if (a == 1) { + fillBevelCap(nu, dlb, cur_data - 3 * dlb->nr, &bottom_capbase); + copy_v3_v3(bottom_no, bevp->dir); + } + if (a == steps - 1) { + fillBevelCap(nu, dlb, cur_data, &top_capbase); + negate_v3_v3(top_no, bevp->dir); + } + } } - if (bottom_capbase.first) { - BKE_displist_fill(&bottom_capbase, dispbase, bottom_no, false); - BKE_displist_fill(&top_capbase, dispbase, top_no, false); - BKE_displist_free(&bottom_capbase); - BKE_displist_free(&top_capbase); - } + /* gl array drawing: using indices */ + displist_surf_indices(dl); } - } - BKE_displist_free(&dlbev); - } - if (!(cu->flag & CU_DEFORM_FILL)) { - curve_to_filledpoly(cu, &nubase, dispbase); + if (bottom_capbase.first) { + BKE_displist_fill(&bottom_capbase, dispbase, bottom_no, false); + BKE_displist_fill(&top_capbase, dispbase, top_no, false); + BKE_displist_free(&bottom_capbase); + BKE_displist_free(&top_capbase); + } + } } + BKE_displist_free(&dlbev); + } - if (!for_orco) { - if ((cu->flag & CU_PATH) || - DEG_get_eval_flags_for_id(depsgraph, &ob->id) & DAG_EVAL_NEED_CURVE_PATH) { - calc_curvepath(ob, &nubase); - } + if (!(cu->flag & CU_DEFORM_FILL)) { + curve_to_filledpoly(cu, dispbase); + } - BKE_nurbList_duplicate(&ob->runtime.curve_cache->deformed_nurbs, &nubase); - curve_calc_modifiers_post( - depsgraph, scene, ob, &nubase, dispbase, r_final, for_render, force_mesh_conversion); + if (!for_orco) { + if ((cu->flag & CU_PATH) || + DEG_get_eval_flags_for_id(depsgraph, &ob->id) & DAG_EVAL_NEED_CURVE_PATH) { + BKE_anim_path_calc_data(ob); } - if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) { - curve_to_filledpoly(cu, &nubase, dispbase); - } + BKE_nurbList_duplicate(&ob->runtime.curve_cache->deformed_nurbs, &nubase); + curve_calc_modifiers_post( + depsgraph, scene, ob, dispbase, for_render, force_mesh_conversion, r_final); + } - BKE_nurbList_free(&nubase); + if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) { + curve_to_filledpoly(cu, dispbase); } + + BKE_nurbList_free(&nubase); } -void BKE_displist_make_curveTypes( - Depsgraph *depsgraph, Scene *scene, Object *ob, const bool for_render, const bool for_orco) +void BKE_displist_make_curveTypes(Depsgraph *depsgraph, + const Scene *scene, + Object *ob, + const bool for_render, + const bool for_orco) { - ListBase *dispbase; - /* The same check for duplis as in do_makeDispListCurveTypes. * Happens when curve used for constraint/bevel was converted to mesh. * check there is still needed for render displist and orco displists. */ @@ -1741,15 +1669,16 @@ void BKE_displist_make_curveTypes( BKE_object_free_derived_caches(ob); if (!ob->runtime.curve_cache) { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for curve types"); + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for curve types"); } - dispbase = &(ob->runtime.curve_cache->disp); + ListBase *dispbase = &(ob->runtime.curve_cache->disp); - Mesh *mesh_eval = NULL; + Mesh *mesh_eval = nullptr; do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval); - if (mesh_eval != NULL) { + if (mesh_eval != nullptr) { BKE_object_eval_assign_data(ob, &mesh_eval->id, true); } @@ -1757,32 +1686,32 @@ void BKE_displist_make_curveTypes( } void BKE_displist_make_curveTypes_forRender(Depsgraph *depsgraph, - Scene *scene, + const Scene *scene, Object *ob, ListBase *dispbase, - Mesh **r_final, - const bool for_orco) + const bool for_orco, + Mesh **r_final) { - if (ob->runtime.curve_cache == NULL) { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); + if (ob->runtime.curve_cache == nullptr) { + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for Curve"); } do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, true, for_orco, r_final); } -void BKE_displist_minmax(ListBase *dispbase, float min[3], float max[3]) +void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) { - const float *vert; - int a, tot = 0; - int doit = 0; + bool doit = false; - LISTBASE_FOREACH (DispList *, dl, dispbase) { - tot = (dl->type == DL_INDEX3) ? dl->nr : dl->nr * dl->parts; - vert = dl->verts; - for (a = 0; a < tot; a++, vert += 3) { - minmax_v3v3_v3(min, max, vert); + LISTBASE_FOREACH (const DispList *, dl, dispbase) { + const int tot = (dl->type == DL_INDEX3) ? dl->nr : dl->nr * dl->parts; + for (const int i : IndexRange(tot)) { + minmax_v3v3_v3(min, max, &dl->verts[i]); + } + if (tot != 0) { + doit = true; } - doit |= (tot != 0); } if (!doit) { @@ -1797,12 +1726,11 @@ static void boundbox_displist_object(Object *ob) { if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { /* Curve's BB is already calculated as a part of modifier stack, - * here we only calculate object BB based on final display list. - */ + * here we only calculate object BB based on final display list. */ /* object's BB is calculated from final displist */ - if (ob->runtime.bb == NULL) { - ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "boundbox"); + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "boundbox"); } Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); 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/editmesh.c b/source/blender/blenkernel/intern/editmesh.c index 0fa3bb29ccd..472de1f3c77 100644 --- a/source/blender/blenkernel/intern/editmesh.c +++ b/source/blender/blenkernel/intern/editmesh.c @@ -127,9 +127,10 @@ static void editmesh_tessface_calc_intern(BMEditMesh *em) } em->looptris = looptris; + em->tottri = looptris_tot; /* after allocating the em->looptris, we're ready to tessellate */ - BM_mesh_calc_tessellation(em->bm, em->looptris, &em->tottri); + BM_mesh_calc_tessellation(em->bm, em->looptris); } void BKE_editmesh_looptri_calc(BMEditMesh *em) @@ -148,6 +149,14 @@ void BKE_editmesh_looptri_calc(BMEditMesh *em) #endif } +void BKE_editmesh_looptri_calc_with_partial(BMEditMesh *em, struct BMPartialUpdate *bmpinfo) +{ + BLI_assert(em->tottri == poly_to_tri_count(em->bm->totface, em->bm->totloop)); + BLI_assert(em->looptris != NULL); + + BM_mesh_calc_tessellation_with_partial(em->bm, em->looptris, bmpinfo); +} + void BKE_editmesh_free_derivedmesh(BMEditMesh *em) { if (em->mesh_eval_cage) { diff --git a/source/blender/blenkernel/intern/editmesh_tangent.c b/source/blender/blenkernel/intern/editmesh_tangent.c index d849f4ab37d..088a2087a96 100644 --- a/source/blender/blenkernel/intern/editmesh_tangent.c +++ b/source/blender/blenkernel/intern/editmesh_tangent.c @@ -362,7 +362,7 @@ void BKE_editmesh_loop_tangent_calc(BMEditMesh *em, /* Calculation */ if (em->tottri != 0) { TaskPool *task_pool; - task_pool = BLI_task_pool_create(NULL, TASK_PRIORITY_LOW); + task_pool = BLI_task_pool_create(NULL, TASK_PRIORITY_LOW, TASK_ISOLATION_ON); tangent_mask_curr = 0; /* Calculate tangent layers */ diff --git a/source/blender/blenkernel/intern/effect.c b/source/blender/blenkernel/intern/effect.c index 4104b6080c5..e39749225ea 100644 --- a/source/blender/blenkernel/intern/effect.c +++ b/source/blender/blenkernel/intern/effect.c @@ -161,13 +161,13 @@ static void precalculate_effector(struct Depsgraph *depsgraph, EffectorCache *ef if (eff->pd->forcefield == PFIELD_GUIDE && eff->ob->type == OB_CURVE) { Curve *cu = eff->ob->data; if (cu->flag & CU_PATH) { - if (eff->ob->runtime.curve_cache == NULL || eff->ob->runtime.curve_cache->path == NULL || - eff->ob->runtime.curve_cache->path->data == NULL) { + if (eff->ob->runtime.curve_cache == NULL || + eff->ob->runtime.curve_cache->anim_path_accum_length == NULL) { BKE_displist_make_curveTypes(depsgraph, eff->scene, eff->ob, false, false); } - if (eff->ob->runtime.curve_cache->path && eff->ob->runtime.curve_cache->path->data) { - where_on_path( + if (eff->ob->runtime.curve_cache->anim_path_accum_length) { + BKE_where_on_path( eff->ob, 0.0, eff->guide_loc, eff->guide_dir, NULL, &eff->guide_radius, NULL); mul_m4_v3(eff->ob->obmat, eff->guide_loc); mul_mat3_m4_v3(eff->ob->obmat, eff->guide_dir); diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index 8e1fa9732ea..68ed3c239ef 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -35,7 +35,9 @@ #include "BLI_blenlib.h" #include "BLI_easing.h" +#include "BLI_ghash.h" #include "BLI_math.h" +#include "BLI_sort_utils.h" #include "BKE_anim_data.h" #include "BKE_animsys.h" @@ -179,8 +181,10 @@ void BKE_fcurves_copy(ListBase *dst, ListBase *src) } } -/** Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). */ +/** + * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of + * `IDTypeInfo` structure). + */ void BKE_fcurve_foreach_id(FCurve *fcu, LibraryForeachIDData *data) { ChannelDriver *driver = fcu->driver; @@ -290,6 +294,12 @@ FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_i return NULL; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name FCurve Iteration + * \{ */ + /* Quick way to loop over all fcurves of a given 'path'. */ FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[]) { @@ -829,6 +839,56 @@ bool BKE_fcurve_calc_range( return foundvert; } +/** + * Return an array of keyed frames, rounded to `interval`. + * + * \param interval: Set to 1.0 to round to whole keyframes, 0.5 for in-between key-frames, etc. + * + * \note An interval of zero could be supported (this implies no rounding at all), + * however this risks very small differences in float values being treated as separate keyframes. + */ +float *BKE_fcurves_calc_keyed_frames_ex(FCurve **fcurve_array, + int fcurve_array_len, + const float interval, + int *r_frames_len) +{ + /* Use `1e-3f` as the smallest possible value since these are converted to integers + * and we can be sure `MAXFRAME / 1e-3f < INT_MAX` as it's around half the size. */ + const double interval_db = max_ff(interval, 1e-3f); + GSet *frames_unique = BLI_gset_int_new(__func__); + for (int fcurve_index = 0; fcurve_index < fcurve_array_len; fcurve_index++) { + const FCurve *fcu = fcurve_array[fcurve_index]; + for (int i = 0; i < fcu->totvert; i++) { + const BezTriple *bezt = &fcu->bezt[i]; + const double value = round((double)bezt->vec[1][0] / interval_db); + BLI_assert(value > INT_MIN && value < INT_MAX); + BLI_gset_add(frames_unique, POINTER_FROM_INT((int)value)); + } + } + + const size_t frames_len = BLI_gset_len(frames_unique); + float *frames = MEM_mallocN(sizeof(*frames) * frames_len, __func__); + + GSetIterator gs_iter; + int i = 0; + GSET_ITER_INDEX (gs_iter, frames_unique, i) { + const int value = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); + frames[i] = (double)value * interval_db; + } + BLI_gset_free(frames_unique, NULL); + + qsort(frames, frames_len, sizeof(*frames), BLI_sortutil_cmp_float); + *r_frames_len = frames_len; + return frames; +} + +float *BKE_fcurves_calc_keyed_frames(FCurve **fcurve_array, + int fcurve_array_len, + int *r_frames_len) +{ + return BKE_fcurves_calc_keyed_frames_ex(fcurve_array, fcurve_array_len, 1.0f, r_frames_len); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1450,7 +1510,8 @@ bool test_time_fcurve(FCurve *fcu) /** \name F-Curve Calculations * \{ */ -/* The length of each handle is not allowed to be more +/** + * The length of each handle is not allowed to be more * than the horizontal distance between (v1-v4). * This is to prevent curve loops. * @@ -1497,9 +1558,12 @@ void BKE_fcurve_correct_bezpart(const float v1[2], float v2[2], float v3[2], con } } -/** Find roots of cubic equation (c0 x³ + c1 x² + c2 x + c3) +/** + * Find roots of cubic equation (c0 x^3 + c1 x^2 + c2 x + c3) * \return number of roots in `o`. - * NOTE: it is up to the caller to allocate enough memory for `o`. */ + * + * \note it is up to the caller to allocate enough memory for `o`. + */ static int solve_cubic(double c0, double c1, double c2, double c3, float *o) { double a, b, c, p, q, d, t, phi; diff --git a/source/blender/blenkernel/intern/fcurve_cache.c b/source/blender/blenkernel/intern/fcurve_cache.c new file mode 100644 index 00000000000..8142b871edd --- /dev/null +++ b/source/blender/blenkernel/intern/fcurve_cache.c @@ -0,0 +1,189 @@ +/* + * 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. + */ + +/** \file + * \ingroup bke + * + * Cache F-Curve look-ups. + */ + +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" + +#include "BLI_ghash.h" +#include "BLI_listbase.h" + +#include "BKE_fcurve.h" + +/* -------------------------------------------------------------------- */ +/** \name F-Curve Path Cache + * + * Cache for finding curves by RNA path & array index. + * \{ */ + +struct FCurvePathCache_Span { + /** Index in the #FCurvePathCache.fcurve_array indicating the start of the span. */ + uint index; + /** Number of items in the span in #FCurvePathCache.fcurve_array that share an RNA path. */ + uint len; +}; + +struct FCurvePathCache { + /** All curves sorted by (#FCurve.rna_path, #FCurve.array_index) */ + FCurve **fcurve_array; + uint fcurve_array_len; + /** Storage for values of `span_from_rna_path`. */ + struct FCurvePathCache_Span *span_table; + /** Map `FCurve.rna_path` to elements in #FCurvePathCache.span_table */ + GHash *span_from_rna_path; +}; + +/** + * #qsort callback for an #FCurve array. + */ +static int fcurve_cmp_for_cache(const void *fcu_a_p, const void *fcu_b_p) +{ + const FCurve *fcu_a = *((const FCurve **)fcu_a_p); + const FCurve *fcu_b = *((const FCurve **)fcu_b_p); + const int cmp = strcmp(fcu_a->rna_path, fcu_b->rna_path); + if (cmp != 0) { + return cmp; + } + if (fcu_a->array_index < fcu_b->array_index) { + return -1; + } + if (fcu_a->array_index > fcu_b->array_index) { + return 1; + } + return 0; +} + +struct FCurvePathCache *BKE_fcurve_pathcache_create(ListBase *list) +{ + const uint fcurve_array_len = BLI_listbase_count(list); + FCurve **fcurve_array = MEM_mallocN(sizeof(*fcurve_array) * fcurve_array_len, __func__); + uint i; + LISTBASE_FOREACH_INDEX (FCurve *, fcu, list, i) { + fcurve_array[i] = fcu; + } + qsort(fcurve_array, fcurve_array_len, sizeof(FCurve *), fcurve_cmp_for_cache); + + /* Allow for the case no F-curves share an RNA-path, otherwise this is over-allocated. + * Although in practice it's likely to only be 3-4x as large as is needed + * (with transform channels for e.g.). */ + struct FCurvePathCache_Span *span_table = MEM_mallocN(sizeof(*span_table) * fcurve_array_len, + __func__); + + /* May over reserve, harmless. */ + GHash *span_from_rna_path = BLI_ghash_str_new_ex(__func__, fcurve_array_len); + uint span_index = 0; + i = 0; + while (i < fcurve_array_len) { + uint i_end; + for (i_end = i + 1; i_end < fcurve_array_len; i_end++) { + /* As the indices are sorted, we know a decrease means a new RNA path is found. */ + if (fcurve_array[i]->array_index > fcurve_array[i_end]->array_index) { + BLI_assert(!STREQ(fcurve_array[i]->rna_path, fcurve_array[i_end]->rna_path)); + break; + } + if (!STREQ(fcurve_array[i]->rna_path, fcurve_array[i_end]->rna_path)) { + break; + } + } + + struct FCurvePathCache_Span *span = &span_table[span_index++]; + span->index = i; + span->len = i_end - i; + BLI_ghash_insert(span_from_rna_path, fcurve_array[i]->rna_path, span); + i = i_end; + } + + struct FCurvePathCache *fcache = MEM_callocN(sizeof(struct FCurvePathCache), __func__); + fcache->fcurve_array = fcurve_array; + fcache->fcurve_array_len = fcurve_array_len; + fcache->span_table = span_table; + fcache->span_from_rna_path = span_from_rna_path; + + return fcache; +} + +void BKE_fcurve_pathcache_destroy(struct FCurvePathCache *fcache) +{ + MEM_freeN(fcache->fcurve_array); + MEM_freeN(fcache->span_table); + BLI_ghash_free(fcache->span_from_rna_path, NULL, NULL); + MEM_freeN(fcache); +} + +FCurve *BKE_fcurve_pathcache_find(struct FCurvePathCache *fcache, + const char *rna_path, + const int array_index) +{ + const struct FCurvePathCache_Span *span = BLI_ghash_lookup(fcache->span_from_rna_path, rna_path); + if (span == NULL) { + return NULL; + } + + FCurve **fcurve = fcache->fcurve_array + span->index; + const uint len = span->len; + for (int i = 0; i < len; i++) { + if (fcurve[i]->array_index == array_index) { + return fcurve[i]; + } + /* As these are sorted, early exit. */ + if (fcurve[i]->array_index > array_index) { + break; + } + } + return NULL; +} + +/** + * Fill in an array of F-Curve, leave NULL when not found. + * + * \return The number of F-Curves found. + */ +int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache, + const char *rna_path, + FCurve **fcurve_result, + int fcurve_result_len) +{ + memset(fcurve_result, 0x0, sizeof(*fcurve_result) * fcurve_result_len); + + const struct FCurvePathCache_Span *span = BLI_ghash_lookup(fcache->span_from_rna_path, rna_path); + if (span == NULL) { + return 0; + } + + int found = 0; + FCurve **fcurve = fcache->fcurve_array + span->index; + const uint len = span->len; + for (int i = 0; i < len; i++) { + /* As these are sorted, early exit. */ + if ((uint)fcurve[i]->array_index > (uint)fcurve_result_len) { + break; + } + fcurve_result[fcurve[i]->array_index] = fcurve[i]; + found += 1; + } + return found; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 851d8aae378..493a267c2f0 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -623,7 +623,8 @@ static void clamp_bounds_in_domain(FluidDomainSettings *fds, static bool is_static_object(Object *ob) { /* Check if the object has modifiers that might make the object "dynamic". */ - ModifierData *md = ob->modifiers.first; + VirtualModifierData virtualModifierData; + ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); for (; md; md = md->next) { if (ELEM(md->type, eModifierType_Cloth, @@ -631,7 +632,8 @@ static bool is_static_object(Object *ob) eModifierType_Explode, eModifierType_Ocean, eModifierType_ShapeKey, - eModifierType_Softbody)) { + eModifierType_Softbody, + eModifierType_Nodes)) { return false; } } diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index febc9e24c9f..92cc3c763b6 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -447,7 +447,6 @@ static void build_underline(Curve *cu, nu2->resolu = cu->resolu; nu2->bezt = NULL; nu2->knotsu = nu2->knotsv = NULL; - nu2->flag = CU_2D; nu2->charidx = charidx + 1000; if (mat_nr > 0) { nu2->mat_nr = mat_nr - 1; @@ -1276,7 +1275,7 @@ static bool vfont_to_curve(Object *ob, if (cu->textoncurve && cu->textoncurve->type == OB_CURVE) { BLI_assert(cu->textoncurve->runtime.curve_cache != NULL); if (cu->textoncurve->runtime.curve_cache != NULL && - cu->textoncurve->runtime.curve_cache->path != NULL) { + cu->textoncurve->runtime.curve_cache->anim_path_accum_length != NULL) { float distfac, imat[4][4], imat3[3][3], cmat[3][3]; float minx, maxx; float timeofs, sizefac; @@ -1310,7 +1309,8 @@ static bool vfont_to_curve(Object *ob, /* length correction */ const float chartrans_size_x = maxx - minx; if (chartrans_size_x != 0.0f) { - const float totdist = cu->textoncurve->runtime.curve_cache->path->totdist; + const CurveCache *cc = cu->textoncurve->runtime.curve_cache; + const float totdist = BKE_anim_path_get_length(cc); distfac = (sizefac * totdist) / chartrans_size_x; distfac = (distfac > 1.0f) ? (1.0f / distfac) : 1.0f; } @@ -1364,8 +1364,8 @@ static bool vfont_to_curve(Object *ob, /* calc the right loc AND the right rot separately */ /* vec, tvec need 4 items */ - where_on_path(cu->textoncurve, ctime, vec, tvec, NULL, NULL, NULL); - where_on_path(cu->textoncurve, ctime + dtime, tvec, rotvec, NULL, NULL, NULL); + BKE_where_on_path(cu->textoncurve, ctime, vec, tvec, NULL, NULL, NULL); + BKE_where_on_path(cu->textoncurve, ctime + dtime, tvec, rotvec, NULL, NULL, NULL); mul_v3_fl(vec, sizefac); diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc new file mode 100644 index 00000000000..de8dc355557 --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -0,0 +1,1199 @@ +/* + * 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. + */ + +#include "DNA_ID_enums.h" +#include "DNA_curve_types.h" + +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" +#include "BKE_curve.h" +#include "BKE_geometry_set.hh" +#include "BKE_lib_id.h" +#include "BKE_spline.hh" + +#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::GVArrayPtr; +using blender::fn::GVMutableArray_For_GMutableSpan; + +/* -------------------------------------------------------------------- */ +/** \name Geometry Component Implementation + * \{ */ + +CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE) +{ +} + +CurveComponent::~CurveComponent() +{ + this->clear(); +} + +GeometryComponent *CurveComponent::copy() const +{ + CurveComponent *new_component = new CurveComponent(); + if (curve_ != nullptr) { + new_component->curve_ = new CurveEval(*curve_); + new_component->ownership_ = GeometryOwnershipType::Owned; + } + return new_component; +} + +void CurveComponent::clear() +{ + BLI_assert(this->is_mutable()); + if (curve_ != nullptr) { + if (ownership_ == GeometryOwnershipType::Owned) { + delete curve_; + } + if (curve_for_render_ != nullptr) { + BKE_id_free(nullptr, curve_for_render_); + curve_for_render_ = nullptr; + } + + curve_ = nullptr; + } +} + +bool CurveComponent::has_curve() const +{ + return curve_ != nullptr; +} + +/* Clear the component and replace it with the new curve. */ +void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership) +{ + BLI_assert(this->is_mutable()); + this->clear(); + curve_ = curve; + ownership_ = ownership; +} + +CurveEval *CurveComponent::release() +{ + BLI_assert(this->is_mutable()); + CurveEval *curve = curve_; + curve_ = nullptr; + return curve; +} + +const CurveEval *CurveComponent::get_for_read() const +{ + return curve_; +} + +CurveEval *CurveComponent::get_for_write() +{ + BLI_assert(this->is_mutable()); + if (ownership_ == GeometryOwnershipType::ReadOnly) { + curve_ = new CurveEval(*curve_); + ownership_ = GeometryOwnershipType::Owned; + } + return curve_; +} + +bool CurveComponent::is_empty() const +{ + return curve_ == nullptr; +} + +bool CurveComponent::owns_direct_data() const +{ + return ownership_ == GeometryOwnershipType::Owned; +} + +void CurveComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); + if (ownership_ != GeometryOwnershipType::Owned) { + curve_ = new CurveEval(*curve_); + ownership_ = GeometryOwnershipType::Owned; + } +} + +/** + * Create empty curve data used for rendering the spline's wire edges. + * \note See comment on #curve_for_render_ for further explanation. + */ +const Curve *CurveComponent::get_curve_for_render() const +{ + if (curve_ == nullptr) { + return nullptr; + } + if (curve_for_render_ != nullptr) { + return curve_for_render_; + } + std::lock_guard lock{curve_for_render_mutex_}; + if (curve_for_render_ != nullptr) { + return curve_for_render_; + } + + curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU, nullptr); + curve_for_render_->curve_eval = curve_; + + return curve_for_render_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Attribute Access Helper Functions + * \{ */ + +int CurveComponent::attribute_domain_size(const AttributeDomain domain) const +{ + if (curve_ == nullptr) { + return 0; + } + if (domain == ATTR_DOMAIN_POINT) { + int total = 0; + for (const SplinePtr &spline : curve_->splines()) { + total += spline->size(); + } + return total; + } + if (domain == ATTR_DOMAIN_CURVE) { + return curve_->splines().size(); + } + return 0; +} + +namespace blender::bke { + +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}; +} + +/** + * Mix together all of a spline's control point values. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<typename T> +static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, + const VArray<T> &old_values, + MutableSpan<T> r_values) +{ + const int splines_len = curve.splines().size(); + Array<int> offsets = curve.control_point_offsets(); + BLI_assert(r_values.size() == splines_len); + attribute_math::DefaultMixer<T> mixer(r_values); + + for (const int i_spline : IndexRange(splines_len)) { + const int spline_offset = offsets[i_spline]; + const int spline_point_len = offsets[i_spline + 1] - spline_offset; + for (const int i_point : IndexRange(spline_point_len)) { + const T value = old_values[spline_offset + i_point]; + mixer.mix_in(i_spline, value); + } + } + + mixer.finalize(); +} + +static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray) +{ + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { + Array<T> values(curve.splines().size()); + adapt_curve_domain_point_to_spline_impl<T>(curve, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + } + }); + return new_varray; +} + +/** + * A virtual array implementation for the conversion of spline attributes to control point + * attributes. The goal is to avoid copying the spline value for every one of its control points + * unless it is necessary (in that case the materialize functions will be called). + */ +template<typename T> class VArray_For_SplineToPoint final : public VArray<T> { + /* Store existing data materialized if it was not already a span. This is expected + * to be worth it because a single spline's value will likely be accessed many times. */ + VArray_Span<T> original_data_; + Array<int> offsets_; + + public: + VArray_For_SplineToPoint(const VArray<T> &original_varray, Array<int> offsets) + : VArray<T>(offsets.last()), original_data_(original_varray), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return original_data_[indices.spline_index]; + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + const int total_size = offsets_.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : original_data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + r_span.slice(offset, next_offset - offset).fill(original_data_[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + while (offsets_[spline_index] < dst_index) { + spline_index++; + } + r_span[dst_index] = original_data_[spline_index]; + } + } + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + 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 : original_data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + uninitialized_fill_n(dst + offset, next_offset - offset, original_data_[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + while (offsets_[spline_index] < dst_index) { + spline_index++; + } + new (dst + dst_index) T(original_data_[spline_index]); + } + } + } +}; + +static GVArrayPtr adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArrayPtr varray) +{ + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + using T = decltype(dummy); + + Array<int> offsets = curve.control_point_offsets(); + new_varray = std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplineToPoint<T>>>( + offsets.last(), *varray->typed<T>(), std::move(offsets)); + }); + return new_varray; +} + +} // namespace blender::bke + +GVArrayPtr CurveComponent::attribute_try_adapt_domain(GVArrayPtr varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const +{ + if (!varray) { + return {}; + } + if (varray->size() == 0) { + return {}; + } + if (from_domain == to_domain) { + return varray; + } + + if (from_domain == ATTR_DOMAIN_POINT && to_domain == ATTR_DOMAIN_CURVE) { + return blender::bke::adapt_curve_domain_point_to_spline(*curve_, std::move(varray)); + } + if (from_domain == ATTR_DOMAIN_CURVE && to_domain == ATTR_DOMAIN_POINT) { + return blender::bke::adapt_curve_domain_spline_to_point(*curve_, std::move(varray)); + } + + return {}; +} + +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); + const AsReadAttribute as_read_attribute_; + const AsWriteAttribute as_write_attribute_; + + public: + BuiltinSplineAttributeProvider(std::string attribute_name, + const CustomDataType attribute_type, + const WritableEnum writable, + const AsReadAttribute as_read_attribute, + const AsWriteAttribute as_write_attribute) + : BuiltinAttributeProvider(std::move(attribute_name), + ATTR_DOMAIN_CURVE, + attribute_type, + BuiltinAttributeProvider::NonCreatable, + writable, + BuiltinAttributeProvider::NonDeletable), + as_read_attribute_(as_read_attribute), + as_write_attribute_(as_write_attribute) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + return as_read_attribute_(*curve); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + if (writable_ != Writable) { + return {}; + } + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + return as_write_attribute_(*curve); + } + + 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_CURVE) != 0; + } +}; + +static int get_spline_resolution(const SplinePtr &spline) +{ + if (const BezierSpline *bezier_spline = dynamic_cast<const BezierSpline *>(spline.get())) { + return bezier_spline->resolution(); + } + if (const NURBSpline *nurb_spline = dynamic_cast<const NURBSpline *>(spline.get())) { + return nurb_spline->resolution(); + } + return 1; +} + +static void set_spline_resolution(SplinePtr &spline, const int resolution) +{ + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) { + bezier_spline->set_resolution(std::max(resolution, 1)); + } + if (NURBSpline *nurb_spline = dynamic_cast<NURBSpline *>(spline.get())) { + nurb_spline->set_resolution(std::max(resolution, 1)); + } +} + +static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>( + curve.splines()); +} + +static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) +{ + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr, + int, + get_spline_resolution, + set_spline_resolution>>( + curve.splines()); +} + +static bool get_cyclic_value(const SplinePtr &spline) +{ + return spline->is_cyclic(); +} + +static void set_cyclic_value(SplinePtr &spline, const bool value) +{ + if (spline->is_cyclic() != value) { + spline->set_cyclic(value); + spline->mark_cache_invalid(); + } +} + +static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>( + 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()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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. + * \{ */ + +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]; + r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + 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 dst_index : mask) { + 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. + */ +static ComponentAttributeProviders create_attribute_providers_for_curve() +{ + static BuiltinSplineAttributeProvider resolution("resolution", + CD_PROP_INT32, + BuiltinAttributeProvider::Writable, + make_resolution_read_attribute, + make_resolution_write_attribute); + + static BuiltinSplineAttributeProvider cyclic("cyclic", + CD_PROP_BOOL, + BuiltinAttributeProvider::Writable, + make_cyclic_read_attribute, + make_cyclic_write_attribute); + + 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 + +const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const +{ + static blender::bke::ComponentAttributeProviders providers = + blender::bke::create_attribute_providers_for_curve(); + return &providers; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 68c551645d2..3b1b7456162 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -42,70 +42,116 @@ InstancesComponent::InstancesComponent() : GeometryComponent(GEO_COMPONENT_TYPE_ GeometryComponent *InstancesComponent::copy() const { InstancesComponent *new_component = new InstancesComponent(); - new_component->transforms_ = transforms_; - new_component->instanced_data_ = instanced_data_; + new_component->instance_reference_handles_ = instance_reference_handles_; + new_component->instance_transforms_ = instance_transforms_; + new_component->instance_ids_ = instance_ids_; + new_component->references_ = references_; return new_component; } +void InstancesComponent::reserve(int min_capacity) +{ + instance_reference_handles_.reserve(min_capacity); + instance_transforms_.reserve(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() { - instanced_data_.clear(); - transforms_.clear(); + instance_reference_handles_.clear(); + instance_transforms_.clear(); + instance_ids_.clear(); + + references_.clear(); } -void InstancesComponent::add_instance(Object *object, float4x4 transform, const int id) +void InstancesComponent::add_instance(const int instance_handle, + const float4x4 &transform, + const int id) { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_OBJECT; - data.data.object = object; - this->add_instance(data, transform, id); + BLI_assert(instance_handle >= 0); + BLI_assert(instance_handle < references_.size()); + instance_reference_handles_.append(instance_handle); + instance_transforms_.append(transform); + instance_ids_.append(id); } -void InstancesComponent::add_instance(Collection *collection, float4x4 transform, const int id) +blender::Span<int> InstancesComponent::instance_reference_handles() const { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_COLLECTION; - data.data.collection = collection; - this->add_instance(data, transform, id); + return instance_reference_handles_; } -void InstancesComponent::add_instance(InstancedData data, float4x4 transform, const int id) +blender::MutableSpan<int> InstancesComponent::instance_reference_handles() { - instanced_data_.append(data); - transforms_.append(transform); - ids_.append(id); + return instance_reference_handles_; } -Span<InstancedData> InstancesComponent::instanced_data() const +blender::MutableSpan<blender::float4x4> InstancesComponent::instance_transforms() +{ + return instance_transforms_; +} +blender::Span<blender::float4x4> InstancesComponent::instance_transforms() const { - return instanced_data_; + return instance_transforms_; } -Span<float4x4> InstancesComponent::transforms() const +blender::MutableSpan<int> InstancesComponent::instance_ids() +{ + return instance_ids_; +} +blender::Span<int> InstancesComponent::instance_ids() const { - return transforms_; + return instance_ids_; } -Span<int> InstancesComponent::ids() const +/** + * Returns a handle for the given reference. + * If the reference exists already, the handle of the existing reference is returned. + * Otherwise a new handle is added. + */ +int InstancesComponent::add_reference(InstanceReference reference) { - return ids_; + return references_.index_of_or_add_as(reference); } -MutableSpan<float4x4> InstancesComponent::transforms() +blender::Span<InstanceReference> InstancesComponent::references() const { - return transforms_; + return references_; } int InstancesComponent::instances_amount() const { - const int size = instanced_data_.size(); - BLI_assert(transforms_.size() == size); - return size; + return instance_transforms_.size(); } bool InstancesComponent::is_empty() const { - return transforms_.size() == 0; + return this->instance_reference_handles_.size() == 0; +} + +bool InstancesComponent::owns_direct_data() const +{ + /* The object and collection instances are not direct data. Instance transforms are direct data + * and are always owned. Therefore, instance components always own all their direct data. */ + return true; +} + +void InstancesComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); } static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) @@ -164,8 +210,8 @@ static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) blender::Span<int> InstancesComponent::almost_unique_ids() const { std::lock_guard lock(almost_unique_ids_mutex_); - if (almost_unique_ids_.size() != ids_.size()) { - almost_unique_ids_ = generate_unique_instance_ids(ids_); + if (almost_unique_ids_.size() != instance_ids_.size()) { + almost_unique_ids_ = generate_unique_instance_ids(instance_ids_); } return almost_unique_ids_; } diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 4fb7bd6a9bd..42f3a854aec 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -32,7 +32,7 @@ /* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); -using blender::bke::ReadAttributePtr; +using blender::fn::GVArray; /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation @@ -157,6 +157,20 @@ bool MeshComponent::is_empty() const return mesh_ == nullptr; } +bool MeshComponent::owns_direct_data() const +{ + return ownership_ == GeometryOwnershipType::Owned; +} + +void MeshComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); + if (ownership_ != GeometryOwnershipType::Owned) { + mesh_ = BKE_mesh_copy_for_eval(mesh_, false); + ownership_ = GeometryOwnershipType::Owned; + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -187,14 +201,14 @@ namespace blender::bke { template<typename T> static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, - const TypedReadAttribute<T> &attribute, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); attribute_math::DefaultMixer<T> mixer(r_values); for (const int loop_index : IndexRange(mesh.totloop)) { - const T value = attribute[loop_index]; + const T value = old_values[loop_index]; const MLoop &loop = mesh.mloop[loop_index]; const int point_index = loop.v; mixer.mix_in(point_index, value); @@ -202,55 +216,49 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { /* We compute all interpolated values at once, because for this interpolation, one has to * iterate over all loops anyway. */ Array<T> values(mesh.totvert); - adapt_mesh_domain_corner_to_point_impl<T>(mesh, *attribute, values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, - const TypedReadAttribute<T> &attribute, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); for (const int loop_index : IndexRange(mesh.totloop)) { const int vertex_index = mesh.mloop[loop_index].v; - r_values[loop_index] = attribute[vertex_index]; + r_values[loop_index] = old_values[vertex_index]; } } -static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); /* It is not strictly necessary to compute the value for all corners here. Instead one could * lazily lookup the mesh topology when a specific index accessed. This can be more efficient * when an algorithm only accesses very few of the corner values. However, for the algorithms * we currently have, precomputing the array is fine. Also, it is easier to implement. */ Array<T> values(mesh.totloop); - adapt_mesh_domain_point_to_corner_impl<T>(mesh, *attribute, values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_CORNER, - std::move(values)); + adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); }); - return new_attribute; + return new_varray; } /** @@ -260,7 +268,7 @@ static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -277,26 +285,23 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); - adapt_mesh_domain_corner_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -318,26 +323,23 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); - adapt_mesh_domain_corner_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); @@ -356,26 +358,23 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_face_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); - adapt_mesh_domain_face_to_point_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); @@ -387,26 +386,23 @@ void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, } } -static ReadAttributePtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); - adapt_mesh_domain_face_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -423,21 +419,18 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); - adapt_mesh_domain_face_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -447,7 +440,7 @@ static ReadAttributePtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -464,21 +457,18 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_point_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); - adapt_mesh_domain_point_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_point_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -488,7 +478,7 @@ static ReadAttributePtr adapt_mesh_domain_point_to_face(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -503,26 +493,23 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); - adapt_mesh_domain_point_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_point_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); @@ -533,7 +520,7 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, /* For every corner, mix the values from the adjacent edges on the face. */ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const int loop_index_prev = (loop_index - 1) % poly.totloop; + const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; const MLoop &loop = mesh.mloop[loop_index]; const MLoop &loop_prev = mesh.mloop[loop_index_prev]; mixer.mix_in(loop_index, old_values[loop.e]); @@ -544,26 +531,23 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); - adapt_mesh_domain_edge_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); @@ -579,21 +563,18 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); - adapt_mesh_domain_edge_to_point_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -603,7 +584,7 @@ static ReadAttributePtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -620,87 +601,85 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); - adapt_mesh_domain_edge_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } } // namespace blender::bke -ReadAttributePtr MeshComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, - const AttributeDomain new_domain) const +blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( + blender::fn::GVArrayPtr varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const { - if (!attribute) { + if (!varray) { return {}; } - if (attribute->size() == 0) { + if (varray->size() == 0) { return {}; } - const AttributeDomain old_domain = attribute->domain(); - if (old_domain == new_domain) { - return attribute; + if (from_domain == to_domain) { + return varray; } - switch (old_domain) { + switch (from_domain) { case ATTR_DOMAIN_CORNER: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_POINT: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_FACE: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_EDGE: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(varray)); default: break; } @@ -729,28 +708,31 @@ static const Mesh *get_mesh_from_component_for_read(const GeometryComponent &com namespace blender::bke { -static float3 get_vertex_position(const MVert &vert) +template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> +static GVArrayPtr make_derived_read_attribute(const void *data, const int domain_size) { - return float3(vert.co); + return std::make_unique<fn::GVArray_For_DerivedSpan<StructT, ElemT, GetFunc>>( + Span<StructT>((const StructT *)data, domain_size)); } -static void set_vertex_position(MVert &vert, const float3 &position) +template<typename StructT, + typename ElemT, + ElemT (*GetFunc)(const StructT &), + void (*SetFunc)(StructT &, ElemT)> +static GVMutableArrayPtr make_derived_write_attribute(void *data, const int domain_size) { - copy_v3_v3(vert.co, position); + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>>( + MutableSpan<StructT>((StructT *)data, domain_size)); } -static ReadAttributePtr make_vertex_position_read_attribute(const void *data, - const int domain_size) +static float3 get_vertex_position(const MVert &vert) { - return std::make_unique<DerivedArrayReadAttribute<MVert, float3, get_vertex_position>>( - ATTR_DOMAIN_POINT, Span<MVert>((const MVert *)data, domain_size)); + return float3(vert.co); } -static WriteAttributePtr make_vertex_position_write_attribute(void *data, const int domain_size) +static void set_vertex_position(MVert &vert, float3 position) { - return std::make_unique< - DerivedArrayWriteAttribute<MVert, float3, get_vertex_position, set_vertex_position>>( - ATTR_DOMAIN_POINT, MutableSpan<MVert>((MVert *)data, domain_size)); + copy_v3_v3(vert.co, position); } static void tag_normals_dirty_when_writing_position(GeometryComponent &component) @@ -766,92 +748,45 @@ static int get_material_index(const MPoly &mpoly) return static_cast<int>(mpoly.mat_nr); } -static void set_material_index(MPoly &mpoly, const int &index) +static void set_material_index(MPoly &mpoly, int index) { mpoly.mat_nr = static_cast<short>(std::clamp(index, 0, SHRT_MAX)); } -static ReadAttributePtr make_material_index_read_attribute(const void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayReadAttribute<MPoly, int, get_material_index>>( - ATTR_DOMAIN_FACE, Span<MPoly>((const MPoly *)data, domain_size)); -} - -static WriteAttributePtr make_material_index_write_attribute(void *data, const int domain_size) -{ - return std::make_unique< - DerivedArrayWriteAttribute<MPoly, int, get_material_index, set_material_index>>( - ATTR_DOMAIN_FACE, MutableSpan<MPoly>((MPoly *)data, domain_size)); -} - static bool get_shade_smooth(const MPoly &mpoly) { return mpoly.flag & ME_SMOOTH; } -static void set_shade_smooth(MPoly &mpoly, const bool &value) +static void set_shade_smooth(MPoly &mpoly, bool value) { SET_FLAG_FROM_TEST(mpoly.flag, value, ME_SMOOTH); } -static ReadAttributePtr make_shade_smooth_read_attribute(const void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayReadAttribute<MPoly, bool, get_shade_smooth>>( - ATTR_DOMAIN_FACE, Span<MPoly>((const MPoly *)data, domain_size)); -} - -static WriteAttributePtr make_shade_smooth_write_attribute(void *data, const int domain_size) -{ - return std::make_unique< - DerivedArrayWriteAttribute<MPoly, bool, get_shade_smooth, set_shade_smooth>>( - ATTR_DOMAIN_FACE, MutableSpan<MPoly>((MPoly *)data, domain_size)); -} - static float2 get_loop_uv(const MLoopUV &uv) { return float2(uv.uv); } -static void set_loop_uv(MLoopUV &uv, const float2 &co) +static void set_loop_uv(MLoopUV &uv, float2 co) { copy_v2_v2(uv.uv, co); } -static ReadAttributePtr make_uvs_read_attribute(const void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayReadAttribute<MLoopUV, float2, get_loop_uv>>( - ATTR_DOMAIN_CORNER, Span((const MLoopUV *)data, domain_size)); -} - -static WriteAttributePtr make_uvs_write_attribute(void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayWriteAttribute<MLoopUV, float2, get_loop_uv, set_loop_uv>>( - ATTR_DOMAIN_CORNER, MutableSpan((MLoopUV *)data, domain_size)); -} - -static Color4f get_loop_color(const MLoopCol &col) +static ColorGeometry4f get_loop_color(const MLoopCol &col) { - Color4f value; - rgba_uchar_to_float(value, &col.r); - return value; + ColorGeometry4b encoded_color = ColorGeometry4b(col.r, col.g, col.b, col.a); + ColorGeometry4f linear_color = encoded_color.decode(); + return linear_color; } -static void set_loop_color(MLoopCol &col, const Color4f &value) +static void set_loop_color(MLoopCol &col, ColorGeometry4f linear_color) { - rgba_float_to_uchar(&col.r, value); -} - -static ReadAttributePtr make_vertex_color_read_attribute(const void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayReadAttribute<MLoopCol, Color4f, get_loop_color>>( - ATTR_DOMAIN_CORNER, Span((const MLoopCol *)data, domain_size)); -} - -static WriteAttributePtr make_vertex_color_write_attribute(void *data, const int domain_size) -{ - return std::make_unique< - DerivedArrayWriteAttribute<MLoopCol, Color4f, get_loop_color, set_loop_color>>( - ATTR_DOMAIN_CORNER, MutableSpan((MLoopCol *)data, domain_size)); + ColorGeometry4b encoded_color = linear_color.encode(); + col.r = encoded_color.r; + col.g = encoded_color.g; + col.b = encoded_color.b; + col.a = encoded_color.a; } static float get_crease(const MEdge &edge) @@ -859,83 +794,62 @@ static float get_crease(const MEdge &edge) return edge.crease / 255.0f; } -static void set_crease(MEdge &edge, const float &value) +static void set_crease(MEdge &edge, float value) { edge.crease = round_fl_to_uchar_clamp(value * 255.0f); } -static ReadAttributePtr make_crease_read_attribute(const void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayReadAttribute<MEdge, float, get_crease>>( - ATTR_DOMAIN_EDGE, Span((const MEdge *)data, domain_size)); -} - -static WriteAttributePtr make_crease_write_attribute(void *data, const int domain_size) -{ - return std::make_unique<DerivedArrayWriteAttribute<MEdge, float, get_crease, set_crease>>( - ATTR_DOMAIN_EDGE, MutableSpan((MEdge *)data, domain_size)); -} - -class VertexWeightWriteAttribute final : public WriteAttribute { +class VMutableArray_For_VertexWeights final : public VMutableArray<float> { private: MDeformVert *dverts_; const int dvert_index_; public: - VertexWeightWriteAttribute(MDeformVert *dverts, const int totvert, const int dvert_index) - : WriteAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), - dverts_(dverts), - dvert_index_(dvert_index) + VMutableArray_For_VertexWeights(MDeformVert *dverts, const int totvert, const int dvert_index) + : VMutableArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) { } - void get_internal(const int64_t index, void *r_value) const override + float get_impl(const int64_t index) const override { - get_internal(dverts_, dvert_index_, index, r_value); + return get_internal(dverts_, dvert_index_, index); } - void set_internal(const int64_t index, const void *value) override + void set_impl(const int64_t index, const float value) override { MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); - weight->weight = *reinterpret_cast<const float *>(value); + weight->weight = value; } - static void get_internal(const MDeformVert *dverts, - const int dvert_index, - const int64_t index, - void *r_value) + static float get_internal(const MDeformVert *dverts, const int dvert_index, const int64_t index) { if (dverts == nullptr) { - *(float *)r_value = 0.0f; - return; + return 0.0f; } const MDeformVert &dvert = dverts[index]; for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { if (weight.def_nr == dvert_index) { - *(float *)r_value = weight.weight; - return; + return weight.weight; } } - *(float *)r_value = 0.0f; + return 0.0f; } }; -class VertexWeightReadAttribute final : public ReadAttribute { +class VArray_For_VertexWeights final : public VArray<float> { private: const MDeformVert *dverts_; const int dvert_index_; public: - VertexWeightReadAttribute(const MDeformVert *dverts, const int totvert, const int dvert_index) - : ReadAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), - dverts_(dverts), - dvert_index_(dvert_index) + VArray_For_VertexWeights(const MDeformVert *dverts, const int totvert, const int dvert_index) + : VArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) { } - void get_internal(const int64_t index, void *r_value) const override + float get_impl(const int64_t index) const override { - VertexWeightWriteAttribute::get_internal(dverts_, dvert_index_, index, r_value); + return VMutableArray_For_VertexWeights::get_internal(dverts_, dvert_index_, index); } }; @@ -944,8 +858,8 @@ class VertexWeightReadAttribute final : public ReadAttribute { */ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); @@ -957,15 +871,17 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } if (mesh == nullptr || mesh->dvert == nullptr) { static const float default_value = 0.0f; - return std::make_unique<ConstantReadAttribute>( - ATTR_DOMAIN_POINT, mesh->totvert, CPPType::get<float>(), &default_value); + return {std::make_unique<fn::GVArray_For_SingleValueRef>( + CPPType::get<float>(), mesh->totvert, &default_value), + ATTR_DOMAIN_POINT}; } - return std::make_unique<VertexWeightReadAttribute>( - mesh->dvert, mesh->totvert, vertex_group_index); + return {std::make_unique<fn::GVArray_For_EmbeddedVArray<float, VArray_For_VertexWeights>>( + mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), + ATTR_DOMAIN_POINT}; } - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); MeshComponent &mesh_component = static_cast<MeshComponent &>(component); @@ -986,8 +902,11 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); } - return std::make_unique<blender::bke::VertexWeightWriteAttribute>( - mesh->dvert, mesh->totvert, vertex_group_index); + return { + std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float, VMutableArray_For_VertexWeights>>( + mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), + ATTR_DOMAIN_POINT}; } bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final @@ -1049,7 +968,7 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final { const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); const Mesh *mesh = mesh_component.get_for_read(); @@ -1062,8 +981,8 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { CustomData_has_layer(&mesh->pdata, CD_NORMAL)) { const void *data = CustomData_get_layer(&mesh->pdata, CD_NORMAL); - return std::make_unique<ArrayReadAttribute<float3>>( - ATTR_DOMAIN_FACE, Span<float3>((const float3 *)data, mesh->totpoly)); + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, mesh->totpoly)); } Array<float3> normals(mesh->totpoly); @@ -1072,10 +991,10 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { BKE_mesh_calc_poly_normal(poly, &mesh->mloop[poly->loopstart], mesh->mvert, normals[i]); } - return std::make_unique<OwnedArrayReadAttribute<float3>>(ATTR_DOMAIN_FACE, std::move(normals)); + return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); } - WriteAttributePtr try_get_for_write(GeometryComponent &UNUSED(component)) const final + GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final { return {}; } @@ -1085,7 +1004,8 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { return false; } - bool try_create(GeometryComponent &UNUSED(component)) const final + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final { return false; } @@ -1136,69 +1056,75 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() #undef MAKE_CONST_CUSTOM_DATA_GETTER #undef MAKE_MUTABLE_CUSTOM_DATA_GETTER - static BuiltinCustomDataLayerProvider position("position", - ATTR_DOMAIN_POINT, - CD_PROP_FLOAT3, - CD_MVERT, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - point_access, - make_vertex_position_read_attribute, - make_vertex_position_write_attribute, - tag_normals_dirty_when_writing_position); + static BuiltinCustomDataLayerProvider position( + "position", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT3, + CD_MVERT, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + point_access, + make_derived_read_attribute<MVert, float3, get_vertex_position>, + make_derived_write_attribute<MVert, float3, get_vertex_position, set_vertex_position>, + tag_normals_dirty_when_writing_position); static NormalAttributeProvider normal; - static BuiltinCustomDataLayerProvider material_index("material_index", - ATTR_DOMAIN_FACE, - CD_PROP_INT32, - CD_MPOLY, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - face_access, - make_material_index_read_attribute, - make_material_index_write_attribute, - nullptr); - - static BuiltinCustomDataLayerProvider shade_smooth("shade_smooth", - ATTR_DOMAIN_FACE, - CD_PROP_BOOL, - CD_MPOLY, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - face_access, - make_shade_smooth_read_attribute, - make_shade_smooth_write_attribute, - nullptr); - - static BuiltinCustomDataLayerProvider crease("crease", - ATTR_DOMAIN_EDGE, - CD_PROP_FLOAT, - CD_MEDGE, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - edge_access, - make_crease_read_attribute, - make_crease_write_attribute, - nullptr); - - static NamedLegacyCustomDataProvider uvs(ATTR_DOMAIN_CORNER, - CD_PROP_FLOAT2, - CD_MLOOPUV, - corner_access, - make_uvs_read_attribute, - make_uvs_write_attribute); - - static NamedLegacyCustomDataProvider vertex_colors(ATTR_DOMAIN_CORNER, - CD_PROP_COLOR, - CD_MLOOPCOL, - corner_access, - make_vertex_color_read_attribute, - make_vertex_color_write_attribute); + static BuiltinCustomDataLayerProvider material_index( + "material_index", + ATTR_DOMAIN_FACE, + CD_PROP_INT32, + CD_MPOLY, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + face_access, + make_derived_read_attribute<MPoly, int, get_material_index>, + make_derived_write_attribute<MPoly, int, get_material_index, set_material_index>, + nullptr); + + static BuiltinCustomDataLayerProvider shade_smooth( + "shade_smooth", + ATTR_DOMAIN_FACE, + CD_PROP_BOOL, + CD_MPOLY, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + face_access, + make_derived_read_attribute<MPoly, bool, get_shade_smooth>, + make_derived_write_attribute<MPoly, bool, get_shade_smooth, set_shade_smooth>, + nullptr); + + static BuiltinCustomDataLayerProvider crease( + "crease", + ATTR_DOMAIN_EDGE, + CD_PROP_FLOAT, + CD_MEDGE, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + edge_access, + make_derived_read_attribute<MEdge, float, get_crease>, + make_derived_write_attribute<MEdge, float, get_crease, set_crease>, + nullptr); + + static NamedLegacyCustomDataProvider uvs( + ATTR_DOMAIN_CORNER, + CD_PROP_FLOAT2, + CD_MLOOPUV, + corner_access, + make_derived_read_attribute<MLoopUV, float2, get_loop_uv>, + make_derived_write_attribute<MLoopUV, float2, get_loop_uv, set_loop_uv>); + + static NamedLegacyCustomDataProvider vertex_colors( + ATTR_DOMAIN_CORNER, + CD_PROP_COLOR, + CD_MLOOPCOL, + corner_access, + make_derived_read_attribute<MLoopCol, ColorGeometry4f, get_loop_color>, + make_derived_write_attribute<MLoopCol, ColorGeometry4f, get_loop_color, set_loop_color>); static VertexGroupsAttributeProvider vertex_groups; static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index 32c4ee8a6a6..6c4af7a6d23 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -107,6 +107,20 @@ bool PointCloudComponent::is_empty() const return pointcloud_ == nullptr; } +bool PointCloudComponent::owns_direct_data() const +{ + return ownership_ == GeometryOwnershipType::Owned; +} + +void PointCloudComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); + if (ownership_ != GeometryOwnershipType::Owned) { + pointcloud_ = BKE_pointcloud_copy_for_eval(pointcloud_, false); + ownership_ = GeometryOwnershipType::Owned; + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -126,16 +140,17 @@ int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) con namespace blender::bke { -template<typename T, AttributeDomain Domain> -static ReadAttributePtr make_array_read_attribute(const void *data, const int domain_size) +template<typename T> +static GVArrayPtr make_array_read_attribute(const void *data, const int domain_size) { - return std::make_unique<ArrayReadAttribute<T>>(Domain, Span<T>((const T *)data, domain_size)); + return std::make_unique<fn::GVArray_For_Span<T>>(Span<T>((const T *)data, domain_size)); } -template<typename T, AttributeDomain Domain> -static WriteAttributePtr make_array_write_attribute(void *data, const int domain_size) +template<typename T> +static GVMutableArrayPtr make_array_write_attribute(void *data, const int domain_size) { - return std::make_unique<ArrayWriteAttribute<T>>(Domain, MutableSpan<T>((T *)data, domain_size)); + return std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>( + MutableSpan<T>((T *)data, domain_size)); } /** @@ -165,30 +180,28 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() }, update_custom_data_pointers}; - static BuiltinCustomDataLayerProvider position( - "position", - ATTR_DOMAIN_POINT, - CD_PROP_FLOAT3, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - point_access, - make_array_read_attribute<float3, ATTR_DOMAIN_POINT>, - make_array_write_attribute<float3, ATTR_DOMAIN_POINT>, - nullptr); - static BuiltinCustomDataLayerProvider radius( - "radius", - ATTR_DOMAIN_POINT, - CD_PROP_FLOAT, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Creatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::Deletable, - point_access, - make_array_read_attribute<float, ATTR_DOMAIN_POINT>, - make_array_write_attribute<float, ATTR_DOMAIN_POINT>, - nullptr); + static BuiltinCustomDataLayerProvider position("position", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT3, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + point_access, + make_array_read_attribute<float3>, + make_array_write_attribute<float3>, + nullptr); + static BuiltinCustomDataLayerProvider radius("radius", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Creatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::Deletable, + point_access, + make_array_read_attribute<float>, + make_array_write_attribute<float>, + nullptr); static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); return ComponentAttributeProviders({&position, &radius}, {&point_custom_data}); } diff --git a/source/blender/blenkernel/intern/geometry_component_volume.cc b/source/blender/blenkernel/intern/geometry_component_volume.cc index fd2327e0bf5..94ed07a63de 100644 --- a/source/blender/blenkernel/intern/geometry_component_volume.cc +++ b/source/blender/blenkernel/intern/geometry_component_volume.cc @@ -97,4 +97,18 @@ Volume *VolumeComponent::get_for_write() return volume_; } +bool VolumeComponent::owns_direct_data() const +{ + return ownership_ == GeometryOwnershipType::Owned; +} + +void VolumeComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); + if (ownership_ != GeometryOwnershipType::Owned) { + volume_ = BKE_volume_copy_for_eval(volume_, false); + ownership_ = GeometryOwnershipType::Owned; + } +} + /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index a09c3f5f3d3..3d85118deee 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -24,6 +24,7 @@ #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DNA_collection_types.h" @@ -49,10 +50,6 @@ GeometryComponent::GeometryComponent(GeometryComponentType type) : type_(type) { } -GeometryComponent ::~GeometryComponent() -{ -} - GeometryComponent *GeometryComponent::create(GeometryComponentType component_type) { switch (component_type) { @@ -64,6 +61,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ return new InstancesComponent(); case GEO_COMPONENT_TYPE_VOLUME: return new VolumeComponent(); + case GEO_COMPONENT_TYPE_CURVE: + return new CurveComponent(); } BLI_assert_unreachable(); return nullptr; @@ -182,6 +181,15 @@ void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma if (mesh != nullptr) { BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max); } + const Volume *volume = this->get_volume_for_read(); + if (volume != nullptr) { + BKE_volume_min_max(volume, *r_min, *r_max); + } + const CurveEval *curve = this->get_curve_for_read(); + if (curve != nullptr) { + /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */ + curve->bounds_min_max(*r_min, *r_max, true); + } } std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) @@ -205,6 +213,25 @@ uint64_t GeometrySet::hash() const return reinterpret_cast<uint64_t>(this); } +/* Remove all geometry components from the geometry set. */ +void GeometrySet::clear() +{ + components_.clear(); +} + +/* Make sure that the geometry can be cached. This does not ensure ownership of object/collection + * instances. */ +void GeometrySet::ensure_owns_direct_data() +{ + for (GeometryComponentType type : components_.keys()) { + const GeometryComponent *component = this->get_component_for_read(type); + if (!component->owns_direct_data()) { + GeometryComponent &component_for_write = this->get_component_for_write(type); + component_for_write.ensure_owns_direct_data(); + } + } +} + /* Returns a read-only mesh or null. */ const Mesh *GeometrySet::get_mesh_for_read() const { @@ -233,6 +260,13 @@ const Volume *GeometrySet::get_volume_for_read() const return (component == nullptr) ? nullptr : component->get_for_read(); } +/* Returns a read-only curve or null. */ +const CurveEval *GeometrySet::get_curve_for_read() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return (component == nullptr) ? nullptr : component->get_for_read(); +} + /* Returns true when the geometry set has a point cloud component that has a point cloud. */ bool GeometrySet::has_pointcloud() const { @@ -254,6 +288,13 @@ bool GeometrySet::has_volume() const return component != nullptr && component->has_volume(); } +/* Returns true when the geometry set has a curve component that has a curve. */ +bool GeometrySet::has_curve() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return component != nullptr && component->has_curve(); +} + /* Create a new geometry set that only contains the given mesh. */ GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -273,6 +314,15 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud, return geometry_set; } +/* Create a new geometry set that only contains the given curve. */ +GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + GeometrySet geometry_set; + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); + return geometry_set; +} + /* Clear the existing mesh and replace it with the given one. */ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -280,6 +330,13 @@ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) component.replace(mesh, ownership); } +/* Clear the existing curve and replace it with the given one. */ +void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); +} + /* Clear the existing point cloud and replace with the given one. */ void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership) { @@ -287,6 +344,13 @@ void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipTy pointcloud_component.replace(pointcloud, ownership); } +/* Clear the existing volume and replace with the given one. */ +void GeometrySet::replace_volume(Volume *volume, GeometryOwnershipType ownership) +{ + VolumeComponent &volume_component = this->get_component_for_write<VolumeComponent>(); + volume_component.replace(volume, ownership); +} + /* Returns a mutable mesh or null. No ownership is transferred. */ Mesh *GeometrySet::get_mesh_for_write() { @@ -308,6 +372,13 @@ Volume *GeometrySet::get_volume_for_write() return component.get_for_write(); } +/* Returns a mutable curve or null. No ownership is transferred. */ +CurveEval *GeometrySet::get_curve_for_write() +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + return component.get_for_write(); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -324,19 +395,4 @@ bool BKE_geometry_set_has_instances(const GeometrySet *geometry_set) return geometry_set->get_component_for_read<InstancesComponent>() != nullptr; } -int BKE_geometry_set_instances(const GeometrySet *geometry_set, - float (**r_transforms)[4][4], - const int **r_almost_unique_ids, - InstancedData **r_instanced_data) -{ - const InstancesComponent *component = geometry_set->get_component_for_read<InstancesComponent>(); - if (component == nullptr) { - return 0; - } - *r_transforms = (float(*)[4][4])component->transforms().data(); - *r_instanced_data = (InstancedData *)component->instanced_data().data(); - *r_almost_unique_ids = (const int *)component->almost_unique_ids().data(); - return component->instances_amount(); -} - /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 24a402ac545..847df75c8cb 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -15,10 +15,12 @@ */ #include "BKE_geometry_set_instances.hh" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_collection_types.h" #include "DNA_mesh_types.h" @@ -36,30 +38,55 @@ static void geometry_set_collect_recursive_collection(const Collection &collecti const float4x4 &transform, Vector<GeometryInstanceGroup> &r_sets); +static void add_final_mesh_as_geometry_component(const Object &object, GeometrySet &geometry_set) +{ + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(&const_cast<Object &>(object), + false); + + if (mesh != nullptr) { + BKE_mesh_wrapper_ensure_mdata(mesh); + + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly); + mesh_component.copy_vertex_group_names_from_object(object); + } +} + +static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set) +{ + BLI_assert(object.type == OB_CURVE); + if (object.data != nullptr) { + std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(Curve *)object.data); + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + curve_component.replace(curve.release(), GeometryOwnershipType::Owned); + } +} + /** * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ static GeometrySet object_get_geometry_set_for_read(const Object &object) { - /* Objects evaluated with a nodes modifier will have a geometry set already. */ + if (object.type == OB_MESH && object.mode == OB_MODE_EDIT) { + GeometrySet geometry_set; + if (object.runtime.geometry_set_eval != nullptr) { + /* `geometry_set_eval` only contains non-mesh components, see `editbmesh_build_data`. */ + geometry_set = *object.runtime.geometry_set_eval; + } + add_final_mesh_as_geometry_component(object, geometry_set); + return geometry_set; + } if (object.runtime.geometry_set_eval != nullptr) { return *object.runtime.geometry_set_eval; } /* Otherwise, construct a new geometry set with the component based on the object type. */ - GeometrySet new_geometry_set; - + GeometrySet geometry_set; if (object.type == OB_MESH) { - Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object( - &const_cast<Object &>(object), false); - - if (mesh != nullptr) { - BKE_mesh_wrapper_ensure_mdata(mesh); - - MeshComponent &mesh_component = new_geometry_set.get_component_for_write<MeshComponent>(); - mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly); - mesh_component.copy_vertex_group_names_from_object(object); - } + add_final_mesh_as_geometry_component(object, geometry_set); + } + else if (object.type == OB_CURVE) { + add_curve_data_as_geometry_component(object, geometry_set); } /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the @@ -68,7 +95,7 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) /* TODO: Add volume support. */ /* Return by value since there is not always an existing geometry set owned elsewhere to use. */ - return new_geometry_set; + return geometry_set; } static void geometry_set_collect_recursive_collection_instance( @@ -123,21 +150,28 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, const InstancesComponent &instances_component = *geometry_set.get_component_for_read<InstancesComponent>(); - Span<float4x4> transforms = instances_component.transforms(); - Span<InstancedData> instances = instances_component.instanced_data(); - for (const int i : instances.index_range()) { - const InstancedData &data = instances[i]; + Span<float4x4> transforms = instances_component.instance_transforms(); + Span<int> handles = instances_component.instance_reference_handles(); + Span<InstanceReference> references = instances_component.references(); + for (const int i : transforms.index_range()) { + const InstanceReference &reference = references[handles[i]]; const float4x4 instance_transform = transform * transforms[i]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - BLI_assert(data.data.object != nullptr); - const Object &object = *data.data.object; - geometry_set_collect_recursive_object(object, instance_transform, r_sets); - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - BLI_assert(data.data.collection != nullptr); - const Collection &collection = *data.data.collection; - geometry_set_collect_recursive_collection_instance(collection, instance_transform, r_sets); + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + geometry_set_collect_recursive_object(object, instance_transform, r_sets); + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + geometry_set_collect_recursive_collection_instance( + collection, instance_transform, r_sets); + break; + } + case InstanceReference::Type::None: { + break; + } } } } @@ -153,22 +187,140 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, * * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ -Vector<GeometryInstanceGroup> geometry_set_gather_instances(const GeometrySet &geometry_set) +void geometry_set_gather_instances(const GeometrySet &geometry_set, + Vector<GeometryInstanceGroup> &r_instance_groups) { - Vector<GeometryInstanceGroup> result_vector; - float4x4 unit_transform; unit_m4(unit_transform.values); - geometry_set_collect_recursive(geometry_set, unit_transform, result_vector); + geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups); +} - return result_vector; +static bool collection_instance_attribute_foreach(const Collection &collection, + const AttributeForeachCallback callback, + const int limit, + int &count); + +static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit, + int &count); + +static bool object_instance_attribute_foreach(const Object &object, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object); + if (!instances_attribute_foreach_recursive(instance_geometry_set, callback, limit, count)) { + return false; + } + + if (object.type == OB_EMPTY) { + const Collection *collection_instance = object.instance_collection; + if (collection_instance != nullptr) { + if (!collection_instance_attribute_foreach(*collection_instance, callback, limit, count)) { + return false; + } + } + } + return true; +} + +static bool collection_instance_attribute_foreach(const Collection &collection, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) { + BLI_assert(collection_object->ob != nullptr); + const Object &object = *collection_object->ob; + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + } + LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) { + BLI_assert(collection_child->collection != nullptr); + const Collection &collection = *collection_child->collection; + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + } + return true; } -void gather_attribute_info(Map<std::string, AttributeKind> &attributes, - Span<GeometryComponentType> component_types, - Span<GeometryInstanceGroup> set_groups, - const Set<std::string> &ignored_attributes) +/** + * \return True if the recursive iteration should continue, false if the limit is reached or the + * callback has returned false indicating it should stop. + */ +static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit, + int &count) +{ + for (const GeometryComponent *component : geometry_set.get_components_for_read()) { + if (!component->attribute_foreach(callback)) { + return false; + } + } + + /* Now that this this geometry set is visited, increase the count and check with the limit. */ + if (limit > 0 && count++ > limit) { + return false; + } + + const InstancesComponent *instances_component = + geometry_set.get_component_for_read<InstancesComponent>(); + if (instances_component == nullptr) { + return true; + } + + for (const InstanceReference &reference : instances_component->references()) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + const Object &object = reference.object(); + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + break; + } + case InstanceReference::Type::Collection: { + const Collection &collection = reference.collection(); + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + break; + } + case InstanceReference::Type::None: { + break; + } + } + } + + return true; +} + +/** + * Call the callback on all of this geometry set's components, including geometry sets from + * instances and recursive instances. This is necessary to access available attributes without + * making all of the set's geometry real. + * + * \param limit: The total number of geometry sets to visit before returning early. This is used + * to avoid looking through too many geometry sets recursively, as an explicit tradeoff in favor + * of performance at the cost of visiting every unique attribute. + */ +void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, + const AttributeForeachCallback callback, + const int limit) +{ + int count = 0; + instances_attribute_foreach_recursive(geometry_set, callback, limit, count); +} + +void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, + Span<GeometryComponentType> component_types, + const Set<std::string> &ignored_attributes, + Map<std::string, AttributeKind> &r_attributes) { for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; @@ -192,7 +344,7 @@ void gather_attribute_info(Map<std::string, AttributeKind> &attributes, {attribute_kind->data_type, meta_data.data_type}); }; - attributes.add_or_modify(name, add_info, modify_info); + r_attributes.add_or_modify(name, add_info, modify_info); return true; }); } @@ -210,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(); @@ -223,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(); @@ -245,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; @@ -258,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]; @@ -287,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; @@ -295,6 +472,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou poly_offset += mesh.totpoly; } } + + const float3 point_normal{0.0f, 0.0f, 1.0f}; + short point_normal_short[3]; + normal_float_to_short_v3(point_normal_short, point_normal); + if (convert_points_to_vertices && set.has_pointcloud()) { const PointCloud &pointcloud = *set.get_pointcloud_for_read(); for (const float4x4 &transform : set_group.transforms) { @@ -303,6 +485,7 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou const float3 old_position = pointcloud.co[i]; const float3 new_position = transform * old_position; copy_v3_v3(new_vert.co, new_position); + memcpy(&new_vert.no, point_normal_short, sizeof(point_normal_short)); } vert_offset += pointcloud.totpoint; } @@ -324,13 +507,15 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output); BLI_assert(cpp_type != nullptr); - result.attribute_try_create(entry.key, domain_output, data_type_output); - WriteAttributePtr write_attribute = result.attribute_try_get_for_write(name); - if (!write_attribute || &write_attribute->cpp_type() != cpp_type || - write_attribute->domain() != domain_output) { + result.attribute_try_create( + entry.key, domain_output, data_type_output, AttributeInitDefault()); + WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name); + if (!write_attribute || &write_attribute.varray->type() != cpp_type || + write_attribute.domain != domain_output) { continue; } - fn::GMutableSpan dst_span = write_attribute->get_span_for_write_only(); + + fn::GVMutableArray_GSpan dst_span{*write_attribute.varray}; int offset = 0; for (const GeometryInstanceGroup &set_group : set_groups) { @@ -342,11 +527,11 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, if (domain_size == 0) { continue; /* Domain size is 0, so no need to increment the offset. */ } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read( + GVArrayPtr source_attribute = component.attribute_try_get_for_read( name, domain_output, data_type_output); if (source_attribute) { - fn::GSpan src_span = source_attribute->get_span(); + fn::GVArray_GSpan src_span{*source_attribute}; const void *src_buffer = src_span.data(); for (const int UNUSED(i) : set_group.transforms.index_range()) { void *dst_buffer = dst_span[offset]; @@ -361,8 +546,48 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, } } - write_attribute->apply_span(); + dst_span.save(); + } +} + +static CurveEval *join_curve_splines(Span<GeometryInstanceGroup> set_groups) +{ + Vector<SplinePtr> new_splines; + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + if (!set.has_curve()) { + continue; + } + + const CurveEval &source_curve = *set.get_curve_for_read(); + 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_splines.append(std::move(new_spline)); + } + } + } + if (new_splines.is_empty()) { + return nullptr; + } + + CurveEval *new_curve = new CurveEval(); + for (SplinePtr &new_spline : new_splines) { + 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()); + return new_curve; } static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, @@ -386,10 +611,11 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, /* Don't copy attributes that are stored directly in the mesh data structs. */ Map<std::string, AttributeKind> attributes; - gather_attribute_info(attributes, - component_types, - set_groups, - {"position", "material_index", "normal", "shade_smooth", "crease"}); + geometry_set_gather_instances_attribute_info( + set_groups, + component_types, + {"position", "material_index", "normal", "shade_smooth", "crease"}, + attributes); join_attributes( set_groups, component_types, attributes, static_cast<GeometryComponent &>(dst_component)); } @@ -413,7 +639,8 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou PointCloud *pointcloud = BKE_pointcloud_new_nomain(totpoint); dst_component.replace(pointcloud); Map<std::string, AttributeKind> attributes; - gather_attribute_info(attributes, {GEO_COMPONENT_TYPE_POINT_CLOUD}, set_groups, {}); + geometry_set_gather_instances_attribute_info( + set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {}, attributes); join_attributes(set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, attributes, @@ -425,8 +652,17 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups, { /* Not yet supported. Joining volume grids with the same name requires resampling of at least * one of the grids. The cell size of the resulting volume has to be determined somehow. */ - VolumeComponent &dst_component = result.get_component_for_write<VolumeComponent>(); - UNUSED_VARS(set_groups, dst_component); + UNUSED_VARS(set_groups, result); +} + +static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result) +{ + CurveEval *curve = join_curve_splines(set_groups); + if (curve == nullptr) { + return; + } + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + dst_component.replace(curve); } GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set) @@ -436,7 +672,8 @@ GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_s } GeometrySet new_geometry_set = geometry_set; - Vector<GeometryInstanceGroup> set_groups = geometry_set_gather_instances(geometry_set); + Vector<GeometryInstanceGroup> set_groups; + geometry_set_gather_instances(geometry_set, set_groups); join_instance_groups_mesh(set_groups, true, new_geometry_set); /* Remove all instances, even though some might contain other non-mesh data. We can't really * keep only non-mesh instances in general. */ @@ -454,10 +691,12 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) GeometrySet new_geometry_set; - Vector<GeometryInstanceGroup> set_groups = geometry_set_gather_instances(geometry_set); + Vector<GeometryInstanceGroup> set_groups; + geometry_set_gather_instances(geometry_set, set_groups); join_instance_groups_mesh(set_groups, false, new_geometry_set); join_instance_groups_pointcloud(set_groups, new_geometry_set); join_instance_groups_volume(set_groups, new_geometry_set); + join_instance_groups_curve(set_groups, new_geometry_set); return new_geometry_set; } diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index f7b895c7ca7..6d1476485ca 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -222,7 +222,7 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd) /* relink palettes (old palettes deprecated, only to convert old files) */ BLO_read_list(reader, &gpd->palettes); if (gpd->palettes.first != NULL) { - LISTBASE_FOREACH (Palette *, palette, &gpd->palettes) { + LISTBASE_FOREACH (bGPDpalette *, palette, &gpd->palettes) { BLO_read_list(reader, &palette->colors); } } @@ -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 */ @@ -1293,7 +1301,8 @@ bGPDframe *BKE_gpencil_layer_frame_find(bGPDlayer *gpl, int cframe) return NULL; } -/** Get the appropriate gp-frame from a given layer +/** + * Get the appropriate gp-frame from a given layer * - this sets the layer's actframe var (if allowed to) * - extension beyond range (if first gp-frame is after all frame in interest and cannot add) * @@ -2615,6 +2624,11 @@ static bool gpencil_is_layer_mask(ViewLayer *view_layer, bGPdata *gpd, bGPDlayer continue; } + /* Skip if masks are disabled for this view layer. */ + if (gpl->flag & GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER) { + continue; + } + LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) { if (STREQ(gpl_mask->info, mask->name)) { return true; @@ -2658,6 +2672,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, bGPDframe *act_gpf = gpl->actframe; bGPDframe *sta_gpf = act_gpf; bGPDframe *end_gpf = act_gpf ? act_gpf->next : NULL; + float prev_opacity = gpl->opacity; if (gpl->flag & GP_LAYER_HIDE) { continue; @@ -2673,9 +2688,12 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, * This is used only in final render and never in Viewport. */ if ((view_layer != NULL) && (gpl->viewlayername[0] != '\0') && (!STREQ(view_layer->name, gpl->viewlayername))) { - /* If the layer is used as mask, cannot be filtered or the masking system - * will crash because needs the mask layer in the draw pipeline. */ - if (!gpencil_is_layer_mask(view_layer, gpd, gpl)) { + /* Do not skip masks when rendering the view-layer so that it can still be used to clip + * other layers. Instead set their opacity to zero. */ + if (gpencil_is_layer_mask(view_layer, gpd, gpl)) { + gpl->opacity = 0.0f; + } + else { continue; } } @@ -2770,6 +2788,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, if (layer_cb) { layer_cb(gpl, act_gpf, NULL, thunk); } + gpl->opacity = prev_opacity; continue; } @@ -2807,6 +2826,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, /* If layer solo mode and Paint mode, only keyframes with data are displayed. */ if (GPENCIL_PAINT_MODE(gpd) && (gpl->flag & GP_LAYER_SOLO_MODE) && (act_gpf->framenum != cfra)) { + gpl->opacity = prev_opacity; continue; } @@ -2817,6 +2837,9 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, stroke_cb(gpl, act_gpf, gps, thunk); } } + + /* Restore the opacity in case it was overwritten (used to hide masks in render). */ + gpl->opacity = prev_opacity; } } 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 5d8dd99b3ae..7f839650f33 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -530,14 +530,23 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, /** * Backbone stretch similar to Freestyle. - * \param gps: Stroke to sample - * \param dist: Distance of one segment - * \param tip_length: Ignore tip jittering, set zero to use default value. + * \param gps: Stroke to sample. + * \param dist: Distance of one segment. + * \param overshoot_fac: How exact is the follow curve algorithm. + * \param mode: Affect to Start, End or Both extremes (0->Both, 1->Start, 2->End) */ -bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float tip_length) +bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, + const float dist, + const float overshoot_fac, + const short mode) { +#define BOTH 0 +#define START 1 +#define END 2 + bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt; - float threshold = (tip_length == 0 ? 0.001f : tip_length); + int i; + float threshold = (overshoot_fac == 0 ? 0.001f : overshoot_fac); if (gps->totpoints < 2 || dist < FLT_EPSILON) { return false; @@ -547,33 +556,36 @@ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float t second_last = &pt[gps->totpoints - 2]; next_pt = &pt[1]; - float len1 = 0.0f; - float len2 = 0.0f; - - int i = 1; - while (len1 < threshold && gps->totpoints > i) { - next_pt = &pt[i]; - len1 = len_v3v3(&next_pt->x, &pt->x); - i++; - } + if (mode == BOTH || mode == START) { + float len1 = 0.0f; + i = 1; + while (len1 < threshold && gps->totpoints > i) { + next_pt = &pt[i]; + len1 = len_v3v3(&next_pt->x, &pt->x); + i++; + } + float extend1 = (len1 + dist) / len1; + float result1[3]; - i = 2; - while (len2 < threshold && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - len2 = len_v3v3(&last_pt->x, &second_last->x); - i++; + interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); + copy_v3_v3(&pt->x, result1); } - float extend1 = (len1 + dist) / len1; - float extend2 = (len2 + dist) / len2; - - float result1[3], result2[3]; + if (mode == BOTH || mode == END) { + float len2 = 0.0f; + i = 2; + while (len2 < threshold && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + len2 = len_v3v3(&last_pt->x, &second_last->x); + i++; + } - interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); - interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); + float extend2 = (len2 + dist) / len2; + float result2[3]; + interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); - copy_v3_v3(&pt->x, result1); - copy_v3_v3(&last_pt->x, result2); + copy_v3_v3(&last_pt->x, result2); + } return true; } @@ -702,48 +714,64 @@ bool BKE_gpencil_stroke_split(bGPdata *gpd, * Shrink the stroke by length. * \param gps: Stroke to shrink * \param dist: delta length + * \param mode: 1->Start, 2->End */ -bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) +bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mode) { +#define START 1 +#define END 2 + bGPDspoint *pt = gps->points, *second_last; int i; - if (gps->totpoints < 2 || dist < FLT_EPSILON) { + if (gps->totpoints < 2) { + if (gps->totpoints == 1) { + second_last = &pt[1]; + if (len_v3v3(&second_last->x, &pt->x) < dist) { + BKE_gpencil_stroke_trim_points(gps, 0, 0); + return true; + } + } + return false; } second_last = &pt[gps->totpoints - 2]; - float len1, this_len1, cut_len1; - float len2, this_len2, cut_len2; - int index_start, index_end; - - len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f; - - i = 1; - while (len1 < dist && gps->totpoints > i - 1) { - this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x); - len1 += this_len1; - cut_len1 = len1 - dist; - i++; + float len1, cut_len1; + float len2, cut_len2; + len1 = len2 = cut_len1 = cut_len2 = 0.0f; + + int index_start = 0; + int index_end = 0; + if (mode == START) { + i = 0; + index_end = gps->totpoints - 1; + while (len1 < dist && gps->totpoints > i + 1) { + len1 += len_v3v3(&pt[i].x, &pt[i + 1].x); + cut_len1 = len1 - dist; + i++; + } + index_start = i - 1; } - index_start = i - 2; - i = 2; - while (len2 < dist && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - this_len2 = len_v3v3(&second_last[1].x, &second_last->x); - len2 += this_len2; - cut_len2 = len2 - dist; - i++; + if (mode == END) { + index_start = 0; + i = 2; + while (len2 < dist && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + len2 += len_v3v3(&second_last[1].x, &second_last->x); + cut_len2 = len2 - dist; + i++; + } + index_end = gps->totpoints - i + 2; } - index_end = gps->totpoints - i + 2; - if (len1 < dist || len2 < dist || index_end <= index_start) { + if (index_end <= index_start) { index_start = index_end = 0; /* empty stroke */ } - if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) { + if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < dist)) { index_start = index_end = 0; /* no length left to cut */ } @@ -753,22 +781,8 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) return false; } - pt = gps->points; - - float cut1 = cut_len1 / this_len1; - float cut2 = cut_len2 / this_len2; - - float result1[3], result2[3]; - - interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1); - interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2); - - copy_v3_v3(&pt[0].x, result1); - copy_v3_v3(&pt[gps->totpoints - 1].x, result2); - return true; } - /** * Apply smooth position to stroke point. * \param gps: Stroke to smooth @@ -1050,8 +1064,21 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, normalize_v3(locx); normalize_v3(locy); + /* Calculate last point first. */ + const bGPDspoint *pt_last = &points[totpoints - 1]; + float tmp[3]; + sub_v3_v3v3(tmp, &pt_last->x, &pt0->x); + + points2d[totpoints - 1][0] = dot_v3v3(tmp, locx); + points2d[totpoints - 1][1] = dot_v3v3(tmp, locy); + + /* Calculate the scalar cross product of the 2d points. */ + float cross = 0.0f; + float *co_curr; + float *co_prev = (float *)&points2d[totpoints - 1]; + /* Get all points in local space */ - for (int i = 0; i < totpoints; i++) { + for (int i = 0; i < totpoints - 1; i++) { const bGPDspoint *pt = &points[i]; float loc[3]; @@ -1060,10 +1087,15 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, points2d[i][0] = dot_v3v3(loc, locx); points2d[i][1] = dot_v3v3(loc, locy); + + /* Calculate cross product. */ + co_curr = (float *)&points2d[i][0]; + cross += (co_curr[0] - co_prev[0]) * (co_curr[1] + co_prev[1]); + co_prev = (float *)&points2d[i][0]; } - /* Concave (-1), Convex (1), or Auto-detect (0)? */ - *r_direction = (int)locy[2]; + /* Concave (-1), Convex (1) */ + *r_direction = (cross >= 0.0f) ? 1 : -1; } /** @@ -2421,7 +2453,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); @@ -2433,7 +2465,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, Material *ma = BKE_object_material_get(ob_mesh, mp->mat_nr + 1); make_element_name( ob_mesh->id.name + 2, (ma != NULL) ? ma->id.name + 2 : "Fill", 64, element_name); - mat_idx = gpencil_material_find_index_by_name(ob_gp, element_name); + mat_idx = BKE_gpencil_material_find_index_by_name_prefix(ob_gp, element_name); if (mat_idx == -1) { float color[4]; if (ma != NULL) { @@ -2474,7 +2506,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); @@ -3663,7 +3695,7 @@ static int generate_perimeter_cap(const float point[4], /** * Calculate the perimeter (outline) of a stroke as list of tPerimeterPoint. - * \param subdivisions: Number of subdivions for the start and end caps + * \param subdivisions: Number of subdivisions for the start and end caps * \return: list of tPerimeterPoint */ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, @@ -3710,7 +3742,7 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, copy_v3_v3(first_next_pt, &first_next->x); copy_v3_v3(last_prev_pt, &last_prev->x); - /* edgecase if single point */ + /* Edge-case if single point. */ if (gps->totpoints == 1) { first_next_pt[0] += 1.0f; last_prev_pt[0] -= 1.0f; @@ -3784,7 +3816,7 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, negate_v2(vec_miter_right); float angle = dot_v2v2(vec_next, nvec_prev); - /* add two points if angle is close to beeing straight */ + /* Add two points if angle is close to being straight. */ if (fabsf(angle) < 0.0001f) { normalize_v2_length(nvec_prev, radius); normalize_v2_length(nvec_next, radius); @@ -3910,7 +3942,7 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, /** * Calculates the perimeter of a stroke projected from the view and * returns it as a new stroke. - * \param subdivisions: Number of subdivions for the start and end caps + * \param subdivisions: Number of subdivisions for the start and end caps * \return: bGPDstroke pointer to stroke perimeter */ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d, diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 6f1896f055a..16386cac029 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -616,7 +616,8 @@ static int gpencil_remap_time_get(Depsgraph *depsgraph, Scene *scene, Object *ob return remap_cfra; } -/** Get the current frame re-timed with time modifiers. +/** + * Get the current frame re-timed with time modifiers. * \param depsgraph: Current depsgraph. * \param scene: Current scene * \param ob: Grease pencil object @@ -746,7 +747,8 @@ void BKE_gpencil_prepare_eval_data(Depsgraph *depsgraph, Scene *scene, Object *o BKE_gpencil_update_orig_pointers(ob_orig, ob); } -/** Calculate gpencil modifiers. +/** + * Calculate gpencil modifiers. * \param depsgraph: Current depsgraph * \param scene: Current scene * \param ob: Grease pencil object @@ -755,9 +757,9 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) { bGPdata *gpd = (bGPdata *)ob->data; const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); + const bool is_curve_edit = (bool)(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) && !is_render); + const bool is_multiedit = (bool)(GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) && !is_render); const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); 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/image_gen.c b/source/blender/blenkernel/intern/image_gen.c index ceb13c4955e..1a0cc8c2924 100644 --- a/source/blender/blenkernel/intern/image_gen.c +++ b/source/blender/blenkernel/intern/image_gen.c @@ -69,10 +69,11 @@ static void image_buf_fill_color_slice( } } -static void image_buf_fill_color_thread_do(void *data_v, int start_scanline, int num_scanlines) +static void image_buf_fill_color_thread_do(void *data_v, int scanline) { FillColorThreadData *data = (FillColorThreadData *)data_v; - size_t offset = ((size_t)start_scanline) * data->width * 4; + const int num_scanlines = 1; + size_t offset = ((size_t)scanline) * data->width * 4; unsigned char *rect = (data->rect != NULL) ? (data->rect + offset) : NULL; float *rect_float = (data->rect_float != NULL) ? (data->rect_float + offset) : NULL; image_buf_fill_color_slice(rect, rect_float, data->width, num_scanlines, data->color); @@ -197,13 +198,14 @@ typedef struct FillCheckerThreadData { int width; } FillCheckerThreadData; -static void image_buf_fill_checker_thread_do(void *data_v, int start_scanline, int num_scanlines) +static void image_buf_fill_checker_thread_do(void *data_v, int scanline) { FillCheckerThreadData *data = (FillCheckerThreadData *)data_v; - size_t offset = ((size_t)start_scanline) * data->width * 4; + size_t offset = ((size_t)scanline) * data->width * 4; + const int num_scanlines = 1; unsigned char *rect = (data->rect != NULL) ? (data->rect + offset) : NULL; float *rect_float = (data->rect_float != NULL) ? (data->rect_float + offset) : NULL; - image_buf_fill_checker_slice(rect, rect_float, data->width, num_scanlines, start_scanline); + image_buf_fill_checker_slice(rect, rect_float, data->width, num_scanlines, scanline); } void BKE_image_buf_fill_checker(unsigned char *rect, float *rect_float, int width, int height) @@ -444,16 +446,15 @@ typedef struct FillCheckerColorThreadData { int width, height; } FillCheckerColorThreadData; -static void checker_board_color_prepare_thread_do(void *data_v, - int start_scanline, - int num_scanlines) +static void checker_board_color_prepare_thread_do(void *data_v, int scanline) { FillCheckerColorThreadData *data = (FillCheckerColorThreadData *)data_v; - size_t offset = ((size_t)data->width) * start_scanline * 4; + const int num_scanlines = 1; + size_t offset = ((size_t)data->width) * scanline * 4; unsigned char *rect = (data->rect != NULL) ? (data->rect + offset) : NULL; float *rect_float = (data->rect_float != NULL) ? (data->rect_float + offset) : NULL; checker_board_color_prepare_slice( - rect, rect_float, data->width, num_scanlines, start_scanline, data->height); + rect, rect_float, data->width, num_scanlines, scanline, data->height); } void BKE_image_buf_fill_checker_color(unsigned char *rect, diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c index 8847b88d6f2..bb7495437bb 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.c @@ -408,17 +408,19 @@ static GPUTexture *image_get_gpu_texture(Image *ima, store_premultiplied, limit_gl_texture_size); - GPU_texture_wrap_mode(*tex, true, false); + if (*tex) { + GPU_texture_wrap_mode(*tex, true, false); - if (GPU_mipmap_enabled()) { - GPU_texture_generate_mipmap(*tex); - if (ima) { - ima->gpuflag |= IMA_GPU_MIPMAP_COMPLETE; + if (GPU_mipmap_enabled()) { + GPU_texture_generate_mipmap(*tex); + if (ima) { + ima->gpuflag |= IMA_GPU_MIPMAP_COMPLETE; + } + GPU_texture_mipmap_mode(*tex, true, true); + } + else { + GPU_texture_mipmap_mode(*tex, false, true); } - GPU_texture_mipmap_mode(*tex, true, true); - } - else { - GPU_texture_mipmap_mode(*tex, false, true); } } @@ -427,7 +429,9 @@ static GPUTexture *image_get_gpu_texture(Image *ima, BKE_image_release_ibuf(ima, ibuf_intern, NULL); } - GPU_texture_orig_size_set(*tex, ibuf_intern->x, ibuf_intern->y); + if (*tex) { + GPU_texture_orig_size_set(*tex, ibuf_intern->x, ibuf_intern->y); + } return *tex; } diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index f2893e162cb..073276b7011 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -105,6 +105,11 @@ static void shapekey_foreach_id(ID *id, LibraryForeachIDData *data) BKE_LIB_FOREACHID_PROCESS_ID(data, key->from, IDWALK_CB_LOOPBACK); } +static ID *shapekey_owner_get(Main *UNUSED(bmain), ID *id) +{ + return ((Key *)id)->from; +} + static void shapekey_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Key *key = (Key *)id; @@ -216,7 +221,9 @@ IDTypeInfo IDType_ID_KE = { .make_local = NULL, .foreach_id = shapekey_foreach_id, .foreach_cache = NULL, - .owner_get = NULL, /* Could have one actually? */ + /* A bit weird, due to shapekeys not being strictly speaking embedded data... But they also + * share a lot with those (non linkable, only ever used by one owner ID, etc.). */ + .owner_get = shapekey_owner_get, .blend_write = shapekey_blend_write, .blend_read_data = shapekey_blend_read_data, diff --git a/source/blender/blenkernel/intern/lattice.c b/source/blender/blenkernel/intern/lattice.c index 7c451051727..1357424d5ff 100644 --- a/source/blender/blenkernel/intern/lattice.c +++ b/source/blender/blenkernel/intern/lattice.c @@ -94,6 +94,7 @@ static void lattice_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const i } lattice_dst->editlatt = NULL; + lattice_dst->batch_cache = NULL; } static void lattice_free_data(ID *id) @@ -540,10 +541,12 @@ void BKE_lattice_vert_coords_apply(Lattice *lt, const float (*vert_coords)[3]) void BKE_lattice_modifiers_calc(struct Depsgraph *depsgraph, Scene *scene, Object *ob) { + BKE_object_free_derived_caches(ob); + if (ob->runtime.curve_cache == NULL) { + ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for lattice"); + } + Lattice *lt = ob->data; - /* Get vertex coordinates from the original copy; - * otherwise we get already-modified coordinates. */ - Object *ob_orig = DEG_get_original_object(ob); VirtualModifierData virtualModifierData; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); float(*vert_coords)[3] = NULL; @@ -551,13 +554,6 @@ void BKE_lattice_modifiers_calc(struct Depsgraph *depsgraph, Scene *scene, Objec const bool is_editmode = (lt->editlatt != NULL); const ModifierEvalContext mectx = {depsgraph, ob, 0}; - if (ob->runtime.curve_cache) { - BKE_displist_free(&ob->runtime.curve_cache->disp); - } - else { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for lattice"); - } - for (; md; md = md->next) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); @@ -577,49 +573,33 @@ void BKE_lattice_modifiers_calc(struct Depsgraph *depsgraph, Scene *scene, Objec continue; } - if (!vert_coords) { - Lattice *lt_orig = ob_orig->data; - if (lt_orig->editlatt) { - lt_orig = lt_orig->editlatt->latt; - } - vert_coords = BKE_lattice_vert_coords_alloc(lt_orig, &numVerts); + if (vert_coords == NULL) { + /* Get either the edit-mode or regular lattice, whichever is in use now. */ + const Lattice *effective_lattice = BKE_object_get_lattice(ob); + vert_coords = BKE_lattice_vert_coords_alloc(effective_lattice, &numVerts); } + mti->deformVerts(md, &mectx, NULL, vert_coords, numVerts); } - if (ob->id.tag & LIB_TAG_COPIED_ON_WRITE) { - if (vert_coords) { - BKE_lattice_vert_coords_apply(ob->data, vert_coords); - MEM_freeN(vert_coords); - } + if (vert_coords == NULL) { + return; } - else { - /* Displist won't do anything; this is just for posterity's sake until we remove it. */ - if (!vert_coords) { - Lattice *lt_orig = ob_orig->data; - if (lt_orig->editlatt) { - lt_orig = lt_orig->editlatt->latt; - } - vert_coords = BKE_lattice_vert_coords_alloc(lt_orig, &numVerts); - } - - DispList *dl = MEM_callocN(sizeof(*dl), "lt_dl"); - dl->type = DL_VERTS; - dl->parts = 1; - dl->nr = numVerts; - dl->verts = (float *)vert_coords; - BLI_addtail(&ob->runtime.curve_cache->disp, dl); + Lattice *lt_eval = BKE_object_get_evaluated_lattice(ob); + if (lt_eval == NULL) { + BKE_id_copy_ex(NULL, <->id, (ID **)<_eval, LIB_ID_COPY_LOCALIZE); + BKE_object_eval_assign_data(ob, <_eval->id, true); } + + BKE_lattice_vert_coords_apply(lt_eval, vert_coords); + MEM_freeN(vert_coords); } struct MDeformVert *BKE_lattice_deform_verts_get(const struct Object *oblatt) { - Lattice *lt = (Lattice *)oblatt->data; BLI_assert(oblatt->type == OB_LATTICE); - if (lt->editlatt) { - lt = lt->editlatt->latt; - } + Lattice *lt = BKE_object_get_lattice(oblatt); return lt->dvert; } diff --git a/source/blender/blenkernel/intern/lattice_deform.c b/source/blender/blenkernel/intern/lattice_deform.c index 2651042939f..a8126cb5538 100644 --- a/source/blender/blenkernel/intern/lattice_deform.c +++ b/source/blender/blenkernel/intern/lattice_deform.c @@ -47,6 +47,7 @@ #include "BKE_key.h" #include "BKE_lattice.h" #include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_deform.h" @@ -69,7 +70,7 @@ typedef struct LatticeDeformData { LatticeDeformData *BKE_lattice_deform_data_create(const Object *oblatt, const Object *ob) { /* we make an array with all differences */ - Lattice *lt = oblatt->data; + Lattice *lt = BKE_object_get_lattice(oblatt); BPoint *bp; DispList *dl = oblatt->runtime.curve_cache ? BKE_displist_find(&oblatt->runtime.curve_cache->disp, DL_VERTS) : @@ -83,9 +84,6 @@ LatticeDeformData *BKE_lattice_deform_data_create(const Object *oblatt, const Ob float latmat[4][4]; LatticeDeformData *lattice_deform_data; - if (lt->editlatt) { - lt = lt->editlatt->latt; - } bp = lt->def; const int32_t num_points = lt->pntsu * lt->pntsv * lt->pntsw; @@ -322,7 +320,9 @@ static void lattice_deform_vert_task(void *__restrict userdata, lattice_deform_vert_with_dvert(data, index, data->dvert ? &data->dvert[index] : NULL); } -static void lattice_vert_task_editmesh(void *__restrict userdata, MempoolIterData *iter) +static void lattice_vert_task_editmesh(void *__restrict userdata, + MempoolIterData *iter, + const TaskParallelTLS *__restrict UNUSED(tls)) { const LatticeDeformUserdata *data = userdata; BMVert *v = (BMVert *)iter; @@ -330,7 +330,9 @@ static void lattice_vert_task_editmesh(void *__restrict userdata, MempoolIterDat lattice_deform_vert_with_dvert(data, BM_elem_index_get(v), dvert); } -static void lattice_vert_task_editmesh_no_dvert(void *__restrict userdata, MempoolIterData *iter) +static void lattice_vert_task_editmesh_no_dvert(void *__restrict userdata, + MempoolIterData *iter, + const TaskParallelTLS *__restrict UNUSED(tls)) { const LatticeDeformUserdata *data = userdata; BMVert *v = (BMVert *)iter; @@ -399,12 +401,16 @@ static void lattice_deform_coords_impl(const Object *ob_lattice, * have already been properly set. */ BM_mesh_elem_index_ensure(em_target->bm, BM_VERT); + TaskParallelSettings settings; + BLI_parallel_mempool_settings_defaults(&settings); + if (cd_dvert_offset != -1) { - BLI_task_parallel_mempool(em_target->bm->vpool, &data, lattice_vert_task_editmesh, true); + BLI_task_parallel_mempool( + em_target->bm->vpool, &data, lattice_vert_task_editmesh, &settings); } else { BLI_task_parallel_mempool( - em_target->bm->vpool, &data, lattice_vert_task_editmesh_no_dvert, true); + em_target->bm->vpool, &data, lattice_vert_task_editmesh_no_dvert, &settings); } } else { diff --git a/source/blender/blenkernel/intern/lattice_deform_test.cc b/source/blender/blenkernel/intern/lattice_deform_test.cc index f08d0349598..a7cd5c36ec2 100644 --- a/source/blender/blenkernel/intern/lattice_deform_test.cc +++ b/source/blender/blenkernel/intern/lattice_deform_test.cc @@ -51,6 +51,7 @@ static void test_lattice_deform_init(LatticeDeformTestContext *ctx, ctx->coords[index][2] = (rng->get_float() - 0.5f) * 10; } IDType_ID_LT.init_data(&ctx->lattice.id); + strcpy(ctx->lattice.id.name, "LTLattice"); IDType_ID_OB.init_data(&ctx->ob_lattice.id); ctx->ob_lattice.type = OB_LATTICE; ctx->ob_lattice.data = &ctx->lattice; diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index af921307bfb..f26b85338f0 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -164,7 +164,7 @@ static void lib_id_clear_library_data_ex(Main *bmain, ID *id) id->tag &= ~(LIB_TAG_INDIRECT | LIB_TAG_EXTERN); id->flag &= ~LIB_INDIRECT_WEAK_LINK; if (id_in_mainlist) { - if (BKE_id_new_name_validate(which_libbase(bmain, GS(id->name)), id, NULL)) { + if (BKE_id_new_name_validate(which_libbase(bmain, GS(id->name)), id, NULL, false)) { bmain->is_memfile_undo_written = false; } } @@ -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; } @@ -827,7 +833,9 @@ void BKE_libblock_management_main_add(Main *bmain, void *idv) ListBase *lb = which_libbase(bmain, GS(id->name)); BKE_main_lock(bmain); BLI_addtail(lb, id); - BKE_id_new_name_validate(lb, id, NULL); + /* We need to allow adding extra datablocks into libraries too, e.g. to support generating new + * overrides for recursive resync. */ + BKE_id_new_name_validate(lb, id, NULL, true); /* alphabetic insertion: is in new_id */ id->tag &= ~(LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT); bmain->is_memfile_undo_written = false; @@ -983,7 +991,7 @@ void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb) } for (i = 0; i < lb_len; i++) { if (!BLI_gset_add(gset, id_array[i]->name + 2)) { - BKE_id_new_name_validate(lb, id_array[i], NULL); + BKE_id_new_name_validate(lb, id_array[i], NULL, false); } } BLI_gset_free(gset, NULL); @@ -1086,7 +1094,7 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl BKE_main_lock(bmain); BLI_addtail(lb, id); - BKE_id_new_name_validate(lb, id, name); + BKE_id_new_name_validate(lb, id, name, false); bmain->is_memfile_undo_written = false; /* alphabetic insertion: is in new_id */ BKE_main_unlock(bmain); @@ -1215,14 +1223,6 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || bmain != NULL); BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_NO_ALLOCATE) == 0); BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_LOCAL) == 0); - if (!is_private_id_data) { - /* When we are handling private ID data, we might still want to manage usercounts, even - * though that ID data-block is actually outside of Main... */ - BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 || - (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) != 0); - } - /* Never implicitly copy shapekeys when generating temp data outside of Main database. */ - BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 || (flag & LIB_ID_COPY_SHAPEKEY) == 0); /* 'Private ID' data handling. */ if ((bmain != NULL) && is_private_id_data) { @@ -1245,6 +1245,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 ????? */ @@ -1264,9 +1271,7 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori new_id->properties = IDP_CopyProperty_ex(id->properties, copy_data_flag); } - /* We may need our own flag to control that at some point, but for now 'no main' one should be - * good enough. */ - if ((orig_flag & LIB_ID_CREATE_NO_MAIN) == 0) { + if ((orig_flag & LIB_ID_COPY_NO_LIB_OVERRIDE) == 0) { if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) { /* We do not want to copy existing override rules here, as they would break the proper * remapping between IDs. Proper overrides rules will be re-generated anyway. */ @@ -1350,12 +1355,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; @@ -1363,7 +1368,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; @@ -1378,16 +1383,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 @@ -1399,7 +1421,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; } @@ -1407,12 +1429,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); @@ -1531,7 +1559,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ * and that current one is not. */ bool is_valid = false; for (id_test = lb->first; id_test; id_test = id_test->next) { - if (id != id_test && !ID_IS_LINKED(id_test)) { + if (id != id_test && id_test->lib == id->lib) { if (id_test->name[2] == final_name[0] && STREQ(final_name, id_test->name + 2)) { /* We expect final_name to not be already used, so this is a failure. */ is_valid = false; @@ -1587,7 +1615,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ for (id_test = lb->first; id_test; id_test = id_test->next) { char base_name_test[MAX_ID_NAME - 2]; int number_test; - if ((id != id_test) && !ID_IS_LINKED(id_test) && (name[0] == id_test->name[2]) && + if ((id != id_test) && (id_test->lib == id->lib) && (name[0] == id_test->name[2]) && (ELEM(id_test->name[base_name_len + 2], '.', '\0')) && STREQLEN(name, id_test->name + 2, base_name_len) && (BLI_split_name_num(base_name_test, &number_test, id_test->name + 2, '.') == @@ -1676,16 +1704,21 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ * * Only for local IDs (linked ones already have a unique ID in their library). * + * \param do_linked_data if true, also ensure a unique name in case the given \a id is linked + * (otherwise, just ensure that it is properly sorted). + * * \return true if a new name had to be created. */ -bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) +bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const bool do_linked_data) { - bool result; + bool result = false; char name[MAX_ID_NAME - 2]; - /* if library, don't rename */ - if (ID_IS_LINKED(id)) { - return false; + /* If library, don't rename (unless explicitly required), but do ensure proper sorting. */ + if (!do_linked_data && ID_IS_LINKED(id)) { + id_sort_by_name(lb, id, NULL); + + return result; } /* if no name given, use name of current ID @@ -1726,7 +1759,7 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) } /* next to indirect usage in read/writefile also in editobject.c scene.c */ -void BKE_main_id_clear_newpoins(Main *bmain) +void BKE_main_id_newptr_and_tag_clear(Main *bmain) { ID *id; @@ -2140,7 +2173,7 @@ void BKE_library_make_local(Main *bmain, TIMEIT_VALUE_PRINT(make_local); #endif - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BLI_memarena_free(linklist_mem); #ifdef DEBUG_TIME @@ -2165,9 +2198,9 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) /* search for id */ idtest = BLI_findstring(lb, name + 2, offsetof(ID, name) + 2); - if (idtest != NULL) { + if (idtest != NULL && !ID_IS_LINKED(idtest)) { /* BKE_id_new_name_validate also takes care of sorting. */ - BKE_id_new_name_validate(lb, idtest, NULL); + BKE_id_new_name_validate(lb, idtest, NULL, false); bmain->is_memfile_undo_written = false; } } @@ -2177,8 +2210,9 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) */ void BKE_libblock_rename(Main *bmain, ID *id, const char *name) { + BLI_assert(!ID_IS_LINKED(id)); ListBase *lb = which_libbase(bmain, GS(id->name)); - if (BKE_id_new_name_validate(lb, id, name)) { + if (BKE_id_new_name_validate(lb, id, name, false)) { bmain->is_memfile_undo_written = false; } } 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..8e21ae88aa6 --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -0,0 +1,173 @@ +/* + * 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 "BLI_string.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) +{ + BKE_idtype_init(); + 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); +} + +TEST(lib_id_main_unique_name, 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")); + test_lib_id_main_sort_check_order({id_a, id_b, id_c}); + + BLI_strncpy(id_c->name, id_a->name, sizeof(id_c->name)); + BKE_id_new_name_validate(&ctx.bmain->objects, id_c, NULL, false); + EXPECT_TRUE(strcmp(id_c->name + 2, "OB_A.001") == 0); + EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + EXPECT_TRUE(ctx.bmain->objects.first == id_a); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_a, id_c, id_b}); + + test_lib_id_main_sort_free(&ctx); +} + +TEST(lib_id_main_unique_name, 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); + BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name)); + BKE_id_new_name_validate(&ctx.bmain->objects, id_b, NULL, true); + EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A.001") == 0); + EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + 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_b->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name)); + BKE_id_new_name_validate(&ctx.bmain->objects, id_b, NULL, true); + EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A") == 0); + EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + 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 2ee4f1597be..6b2ffa3b944 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -48,6 +48,7 @@ #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_node.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -80,6 +81,19 @@ static void lib_override_library_property_clear(IDOverrideLibraryProperty *op); static void lib_override_library_property_operation_clear( IDOverrideLibraryPropertyOperation *opop); +/** Get override data for a given ID. Needed because of our beloved shape keys snowflake. */ +BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id) +{ + if (id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) { + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); + if (id_type->owner_get != NULL) { + return id_type->owner_get(bmain, id)->override_library; + } + BLI_assert(!"IDTypeInfo of liboverride-embedded ID with no owner getter"); + } + return id->override_library; +} + /** Initialize empty overriding of \a reference_id by \a local_id. */ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id) { @@ -118,7 +132,7 @@ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id) /** Shalow or deep copy of a whole override from \a src_id to \a dst_id. */ void BKE_lib_override_library_copy(ID *dst_id, const ID *src_id, const bool do_full_copy) { - BLI_assert(ID_IS_OVERRIDE_LIBRARY(src_id)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY(src_id) || ID_IS_OVERRIDE_LIBRARY_TEMPLATE(src_id)); if (dst_id->override_library != NULL) { if (src_id->override_library == NULL) { @@ -194,9 +208,17 @@ void BKE_lib_override_library_free(struct IDOverrideLibrary **override, const bo *override = NULL; } -static ID *lib_override_library_create_from(Main *bmain, ID *reference_id) +static ID *lib_override_library_create_from(Main *bmain, + ID *reference_id, + const int lib_id_copy_flags) { - ID *local_id = BKE_id_copy(bmain, reference_id); + /* Note: We do not want to copy possible override data from reference here (whether it is an + * override template, or already an override of some other ref data). */ + ID *local_id = BKE_id_copy_ex(bmain, + reference_id, + NULL, + LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE | + lib_id_copy_flags); if (local_id == NULL) { return NULL; @@ -218,16 +240,25 @@ static ID *lib_override_library_create_from(Main *bmain, ID *reference_id) return local_id; } -/** Check if given ID has some override rules that actually indicate the user edited it. +/** + * Check if given ID has some override rules that actually indicate the user edited it. * - * TODO: This could be simplified by storing a flag in IDOverrideLibrary during the diffing - * process? */ + * TODO: This could be simplified by storing a flag in #IDOverrideLibrary during the diffing + * process? + */ bool BKE_lib_override_library_is_user_edited(struct ID *id) { if (!ID_IS_OVERRIDE_LIBRARY(id)) { return false; } + /* A bit weird, but those embedded IDs are handled by their owner ID anyway, so we can just + * assume they are never user-edited, actual proper detection will happen from their owner check. + */ + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + return false; + } + LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) { LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) { if ((opop->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) != 0) { @@ -252,7 +283,7 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, BLI_assert(reference_id != NULL); BLI_assert(reference_id->lib != NULL); - ID *local_id = lib_override_library_create_from(bmain, reference_id); + ID *local_id = lib_override_library_create_from(bmain, reference_id, 0); if (do_tagged_remap) { Key *reference_key, *local_key = NULL; @@ -297,9 +328,17 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, * main. You can add more local IDs to be remapped to use new overriding ones by setting their * LIB_TAG_DOIT tag. * + * \param reference_library the library from which the linked data being overridden come from + * (i.e. the library of the linked reference ID). + * + * \param do_no_main Create the new override data outside of Main database. Used for resyncing of + * linked overrides. + * * \return \a true on success, \a false otherwise. */ -bool BKE_lib_override_library_create_from_tag(Main *bmain) +bool BKE_lib_override_library_create_from_tag(Main *bmain, + const Library *reference_library, + const bool do_no_main) { ID *reference_id; bool success = true; @@ -309,7 +348,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Get all IDs we want to override. */ FOREACH_MAIN_ID_BEGIN (bmain, reference_id) { - if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib != NULL && + if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib == reference_library && BKE_idtype_idcode_is_linkable(GS(reference_id->name))) { todo_id_iter = MEM_callocN(sizeof(*todo_id_iter), __func__); todo_id_iter->data = reference_id; @@ -321,10 +360,16 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Override the IDs. */ for (todo_id_iter = todo_ids.first; todo_id_iter != NULL; todo_id_iter = todo_id_iter->next) { reference_id = todo_id_iter->data; + + /* If `newid` is already set, assume it has been handled by calling code. + * Only current use case: re-using proxy ID when converting to liboverride. */ if (reference_id->newid == NULL) { - /* If `newid` is already set, assume it has been handled by calling code. - * Only current use case: re-using proxy ID when converting to liboverride. */ - if ((reference_id->newid = lib_override_library_create_from(bmain, reference_id)) == NULL) { + /* Note: `no main` case is used during resync procedure, to support recursive resync. + * This requires extra care further down the resync process, + * see: #BKE_lib_override_library_resync. */ + reference_id->newid = lib_override_library_create_from( + bmain, reference_id, do_no_main ? LIB_ID_CREATE_NO_MAIN : 0); + if (reference_id->newid == NULL) { success = false; break; } @@ -364,23 +409,43 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Still checking the whole Main, that way we can tag other local IDs as needing to be * remapped to use newly created overriding IDs, if needed. */ - FOREACH_MAIN_ID_BEGIN (bmain, other_id) { - if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib == NULL) { - /* Note that using ID_REMAP_SKIP_INDIRECT_USAGE below is superfluous, as we only remap - * local IDs usages anyway. */ + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* In case we created new overrides as 'no main', they are not accessible directly in this + * loop, but we can get to them through their reference's `newid` pointer. */ + if (do_no_main && id->lib == reference_id->lib && id->newid != NULL) { + other_id = id->newid; + /* Otherwise we cannot properly distinguish between IDs that are actually from the + * linked library (and should not be remapped), and IDs that are overrides re-generated + * from the reference from the linked library, and must therefore be remapped. + * + * This is reset afterwards at the end of this loop. */ + other_id->lib = NULL; + } + else { + other_id = id; + } + + /* If other ID is a linked one, but not from the same library as our reference, then we + * consider we should also remap it, as part of recursive resync. */ + if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib != reference_id->lib && + other_id != local_id) { BKE_libblock_relink_ex(bmain, other_id, reference_id, local_id, - ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY); + ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT); if (reference_key != NULL) { BKE_libblock_relink_ex(bmain, other_id, &reference_key->id, &local_key->id, - ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY); + ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT); } } + if (other_id != id) { + other_id->lib = reference_id->lib; + } } FOREACH_MAIN_ID_END; } @@ -404,6 +469,8 @@ typedef struct LibOverrideGroupTagData { ID *id_root; uint tag; uint missing_tag; + /* Whether we are looping on override data, or their references (linked) one. */ + bool is_override; } LibOverrideGroupTagData; /* Tag all IDs in dependency relationships within an override hierarchy/group. @@ -416,6 +483,7 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa { Main *bmain = data->bmain; ID *id = data->id_root; + const bool is_override = data->is_override; MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id); BLI_assert(entry != NULL); @@ -430,19 +498,23 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } /* We only consider IDs from the same library. */ ID *to_id = *to_id_entry->id_pointer.to; - if (to_id != NULL && to_id->lib == id->lib) { - LibOverrideGroupTagData sub_data = *data; - sub_data.id_root = to_id; - if (lib_override_hierarchy_dependencies_recursive_tag(&sub_data)) { - id->tag |= data->tag; - } + if (to_id == NULL || to_id->lib != id->lib || + (is_override && !ID_IS_OVERRIDE_LIBRARY(to_id))) { + /* IDs from different libraries, or non-override IDs in case we are processing overrides, are + * both barriers of dependency. */ + continue; + } + LibOverrideGroupTagData sub_data = *data; + sub_data.id_root = to_id; + if (lib_override_hierarchy_dependencies_recursive_tag(&sub_data)) { + id->tag |= data->tag; } } @@ -454,6 +526,7 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat Main *bmain = data->bmain; ID *id_owner = data->id_root; BLI_assert(ID_IS_LINKED(id_owner)); + BLI_assert(!data->is_override); const uint tag = data->tag; const uint missing_tag = data->missing_tag; @@ -471,10 +544,8 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -521,6 +592,7 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) { Main *bmain = data->bmain; ID *id_root = data->id_root; + BLI_assert(!data->is_override); if ((id_root->tag & LIB_TAG_MISSING)) { id_root->tag |= data->missing_tag; @@ -547,11 +619,12 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) } } -static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data) +static void lib_override_overrides_group_tag_recursive(LibOverrideGroupTagData *data) { Main *bmain = data->bmain; ID *id_owner = data->id_root; BLI_assert(ID_IS_OVERRIDE_LIBRARY(id_owner)); + BLI_assert(data->is_override); const uint tag = data->tag; const uint missing_tag = data->missing_tag; @@ -569,10 +642,8 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -580,45 +651,37 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data if (ELEM(to_id, NULL, id_owner)) { continue; } - if (!ID_IS_OVERRIDE_LIBRARY(to_id) || ID_IS_LINKED(to_id)) { + if (!ID_IS_OVERRIDE_LIBRARY(to_id) || (to_id->lib != id_owner->lib)) { continue; } - /* Do not tag 'virtual' overrides (shape keys here, as we already rejected embedded case - * above). */ - if (ID_IS_OVERRIDE_LIBRARY_REAL(to_id)) { - Library *reference_lib = NULL; - if (GS(id_owner->name) == ID_KE) { - reference_lib = ((Key *)id_owner)->from->override_library->reference->lib; - } - else { - reference_lib = id_owner->override_library->reference->lib; - } - if (to_id->override_library->reference->lib != reference_lib) { - /* We do not override data-blocks from other libraries, nor do we process them. */ - continue; - } + Library *reference_lib = lib_override_get(bmain, id_owner)->reference->lib; + ID *to_id_reference = lib_override_get(bmain, to_id)->reference; + if (to_id_reference->lib != reference_lib) { + /* We do not override data-blocks from other libraries, nor do we process them. */ + continue; + } - if (to_id->override_library->reference->tag & LIB_TAG_MISSING) { - to_id->tag |= missing_tag; - } - else { - to_id->tag |= tag; - } + if (to_id_reference->tag & LIB_TAG_MISSING) { + to_id->tag |= missing_tag; + } + else { + to_id->tag |= tag; } /* Recursively process the dependencies. */ LibOverrideGroupTagData sub_data = *data; sub_data.id_root = to_id; - lib_override_local_group_tag_recursive(&sub_data); + lib_override_overrides_group_tag_recursive(&sub_data); } } /* This will tag all override IDs of an override group defined by the given `id_root`. */ -static void lib_override_local_group_tag(LibOverrideGroupTagData *data) +static void lib_override_overrides_group_tag(LibOverrideGroupTagData *data) { ID *id_root = data->id_root; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root) && !ID_IS_LINKED(id_root)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); + BLI_assert(data->is_override); if ((id_root->override_library->reference->tag & LIB_TAG_MISSING)) { id_root->tag |= data->missing_tag; @@ -628,14 +691,17 @@ static void lib_override_local_group_tag(LibOverrideGroupTagData *data) } /* Tag all local overrides in id_root's group. */ - lib_override_local_group_tag_recursive(data); + lib_override_overrides_group_tag_recursive(data); } static bool lib_override_library_create_do(Main *bmain, ID *id_root) { BKE_main_relations_create(bmain, 0); - LibOverrideGroupTagData data = { - .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING}; + LibOverrideGroupTagData data = {.bmain = bmain, + .id_root = id_root, + .tag = LIB_TAG_DOIT, + .missing_tag = LIB_TAG_MISSING, + .is_override = false}; lib_override_linked_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); @@ -643,19 +709,7 @@ static bool lib_override_library_create_do(Main *bmain, ID *id_root) BKE_main_relations_free(bmain); - return BKE_lib_override_library_create_from_tag(bmain); -} - -BLI_INLINE bool lib_override_library_create_post_process_object_is_instantiated( - ViewLayer *view_layer, Object *object, const bool is_resync) -{ - /* We cannot rely on check for object being actually instantiated in resync case, because often - * the overridden collection is 'excluded' from the current view-layer. - * - * Fallback to a basic user-count check then, this is weak (since it could lead to some object - * not being instantiated at all), but it should work fine in most common cases. */ - return ((is_resync && ID_REAL_USERS(object) >= 1) || - (!is_resync && BKE_view_layer_base_find(view_layer, object) != NULL)); + return BKE_lib_override_library_create_from_tag(bmain, id_root->lib, false); } static void lib_override_library_create_post_process(Main *bmain, @@ -666,15 +720,28 @@ static void lib_override_library_create_post_process(Main *bmain, Collection *residual_storage, const bool is_resync) { + /* NOTE: We only care about local IDs here, if a linked object is not instantiated in any way we + * do not do anything about it. */ + BKE_main_collection_sync(bmain); - if (id_root->newid != NULL) { + /* We create a set of all objects referenced into the scene by its hierarchy of collections. + * NOTE: This is different that the list of bases, since objects in excluded collections etc. + * won't have a base, but are still considered as instanced from our point of view. */ + GSet *all_objects_in_scene = BKE_scene_objects_as_gset(scene, NULL); + + /* Instantiating the root collection or object should never be needed in resync case, since the + * old override would be remapped to the new one. */ + if (!is_resync && id_root != NULL && id_root->newid != NULL && !ID_IS_LINKED(id_root->newid)) { switch (GS(id_root->name)) { case ID_GR: { Object *ob_reference = id_reference != NULL && GS(id_reference->name) == ID_OB ? (Object *)id_reference : NULL; Collection *collection_new = ((Collection *)id_root->newid); + if (is_resync && BKE_collection_is_in_scene(collection_new)) { + break; + } if (ob_reference != NULL) { BKE_collection_add_from_object(bmain, scene, ob_reference, collection_new); } @@ -688,43 +755,16 @@ static void lib_override_library_create_post_process(Main *bmain, bmain, scene, ((Collection *)id_root), collection_new); } - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection_new, ob_new) { - if (ob_new != NULL && ob_new->id.override_library != NULL) { - if (ob_reference != NULL) { - Base *base = BKE_view_layer_base_find(view_layer, ob_new); - if (!lib_override_library_create_post_process_object_is_instantiated( - view_layer, ob_new, is_resync)) { - BKE_collection_object_add_from(bmain, scene, ob_reference, ob_new); - base = BKE_view_layer_base_find(view_layer, ob_new); - DEG_id_tag_update_ex( - bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); - } + BLI_assert(BKE_collection_is_in_scene(collection_new)); - if (ob_new == (Object *)ob_reference->id.newid && base != NULL) { - /* TODO: is setting active needed? */ - BKE_view_layer_base_select_and_set_active(view_layer, base); - } - } - else if (!lib_override_library_create_post_process_object_is_instantiated( - view_layer, ob_new, is_resync)) { - BKE_collection_object_add(bmain, collection_new, ob_new); - DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); - } - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + all_objects_in_scene = BKE_scene_objects_as_gset(scene, all_objects_in_scene); break; } case ID_OB: { Object *ob_new = (Object *)id_root->newid; - if (!lib_override_library_create_post_process_object_is_instantiated( - view_layer, ob_new, is_resync)) { - if (is_resync && residual_storage != NULL) { - BKE_collection_object_add(bmain, residual_storage, ob_new); - } - else { - BKE_collection_object_add_from(bmain, scene, (Object *)id_root, ob_new); - } + if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { + BKE_collection_object_add_from(bmain, scene, (Object *)id_root, ob_new); + all_objects_in_scene = BKE_scene_objects_as_gset(scene, all_objects_in_scene); } break; } @@ -734,57 +774,71 @@ static void lib_override_library_create_post_process(Main *bmain, } /* We need to ensure all new overrides of objects are properly instantiated. */ + Collection *default_instantiating_collection = residual_storage; LISTBASE_FOREACH (Object *, ob, &bmain->objects) { Object *ob_new = (Object *)ob->id.newid; - if (ob_new != NULL) { - BLI_assert(ob_new->id.override_library != NULL && - ob_new->id.override_library->reference == &ob->id); - - Collection *default_instantiating_collection = residual_storage; - if (!lib_override_library_create_post_process_object_is_instantiated( - view_layer, ob_new, is_resync)) { - if (default_instantiating_collection == NULL) { - switch (GS(id_root->name)) { - case ID_GR: { - default_instantiating_collection = BKE_collection_add( - bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); - /* Hide the collection from viewport and render. */ - default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT | - COLLECTION_RESTRICT_RENDER; - break; + if (ob_new == NULL || ob_new->id.lib != NULL) { + continue; + } + + BLI_assert(ob_new->id.override_library != NULL && + ob_new->id.override_library->reference == &ob->id); + + if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { + if (id_root != NULL && default_instantiating_collection == NULL) { + 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; } - 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; - LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { - if (BKE_collection_has_object(collection, ob_root) && - BKE_view_layer_has_collection(view_layer, collection) && - !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) { - default_instantiating_collection = collection; - } - } - if (default_instantiating_collection == NULL) { - default_instantiating_collection = scene->master_collection; + default_instantiating_collection = BKE_collection_add( + bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); + /* Hide the collection from viewport and render. */ + default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT | + COLLECTION_RESTRICT_RENDER; + break; + } + 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_ref = (Object *)id_ref; + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + 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; } - break; } - default: - BLI_assert(0); + break; } + default: + BLI_assert(0); } - - BKE_collection_object_add(bmain, default_instantiating_collection, ob_new); - DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); } + if (default_instantiating_collection == NULL) { + default_instantiating_collection = scene->master_collection; + } + + BKE_collection_object_add(bmain, default_instantiating_collection, ob_new); + DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); } } + + BLI_gset_free(all_objects_in_scene, NULL); } /** * Advanced 'smart' function to create fully functional overrides. * - * \note Currently it only does special things if given \a id_root is an object of collection, more + * \note Currently it only does special things if given \a id_root is an object or collection, more * specific behaviors may be added in the future for other ID types. * * \note It will override all IDs tagged with \a LIB_TAG_DOIT, and it does not clear that tag at @@ -794,22 +848,35 @@ static void lib_override_library_create_post_process(Main *bmain, * \param id_reference: Some reference ID used to do some post-processing after overrides have been * created, may be NULL. Typically, the Empty object instantiating the linked collection we * override, currently. + * \param r_id_root_override if not NULL, the override generated for the given \a id_root. * \return true if override was successfully created. */ -bool BKE_lib_override_library_create( - Main *bmain, Scene *scene, ViewLayer *view_layer, ID *id_root, ID *id_reference) +bool BKE_lib_override_library_create(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ID *id_root, + ID *id_reference, + ID **r_id_root_override) { + if (r_id_root_override != NULL) { + *r_id_root_override = NULL; + } + const bool success = lib_override_library_create_do(bmain, id_root); if (!success) { return success; } + if (r_id_root_override != NULL) { + *r_id_root_override = id_root->newid; + } + lib_override_library_create_post_process( bmain, scene, view_layer, id_root, id_reference, NULL, false); /* Cleanup. */ - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ @@ -819,6 +886,22 @@ bool BKE_lib_override_library_create( } /** + * Create a library override template. + */ +bool BKE_lib_override_library_template_create(struct ID *id) +{ + if (ID_IS_LINKED(id)) { + return false; + } + if (ID_IS_OVERRIDE_LIBRARY(id)) { + return false; + } + + BKE_lib_override_library_init(id, NULL); + return true; +} + +/** * Convert a given proxy object into a library override. * * \note This is a thin wrapper around \a BKE_lib_override_library_create, only extra work is to @@ -858,7 +941,7 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain, DEG_id_tag_update(&ob_proxy->id, ID_RECALC_COPY_ON_WRITE); - return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference); + return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference, NULL); } /** @@ -873,20 +956,25 @@ bool BKE_lib_override_library_resync(Main *bmain, ViewLayer *view_layer, ID *id_root, Collection *override_resync_residual_storage, - const bool do_hierarchy_enforce) + const bool do_hierarchy_enforce, + const bool do_post_process, + ReportList *reports) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); - BLI_assert(!ID_IS_LINKED(id_root)); ID *id_root_reference = id_root->override_library->reference; BKE_main_relations_create(bmain, 0); - LibOverrideGroupTagData data = { - .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING}; - lib_override_local_group_tag(&data); + LibOverrideGroupTagData data = {.bmain = bmain, + .id_root = id_root, + .tag = LIB_TAG_DOIT, + .missing_tag = LIB_TAG_MISSING, + .is_override = true}; + lib_override_overrides_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); data.id_root = id_root_reference; + data.is_override = false; lib_override_linked_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); @@ -897,12 +985,48 @@ bool BKE_lib_override_library_resync(Main *bmain, BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (id->tag & LIB_TAG_DOIT && ID_IS_OVERRIDE_LIBRARY_REAL(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->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(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 * same linked ID in a same hierarchy. */ - if (!BLI_ghash_haskey(linkedref_to_old_override, id->override_library->reference)) { - BLI_ghash_insert(linkedref_to_old_override, id->override_library->reference, id); + IDOverrideLibrary *id_override_library = lib_override_get(bmain, id); + ID *reference_id = id_override_library->reference; + if (GS(reference_id->name) != GS(id->name)) { + switch (GS(id->name)) { + case ID_KE: + reference_id = (ID *)BKE_key_from_id(reference_id); + break; + case ID_GR: + BLI_assert(GS(reference_id->name) == ID_SCE); + reference_id = (ID *)((Scene *)reference_id)->master_collection; + break; + case ID_NT: + reference_id = (ID *)ntreeFromID(id); + break; + default: + break; + } + } + BLI_assert(GS(reference_id->name) == GS(id->name)); + + if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { + BLI_ghash_insert(linkedref_to_old_override, reference_id, id); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + continue; + } if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) { /* We have an override, but now it does not seem to be necessary to override that ID * anymore. Check if there are some actual overrides from the user, otherwise assume @@ -941,7 +1065,8 @@ bool BKE_lib_override_library_resync(Main *bmain, /* Note that this call also remaps all pointers of tagged IDs from old override IDs to new * override IDs (including within the old overrides themselves, since those are tagged too * above). */ - const bool success = BKE_lib_override_library_create_from_tag(bmain); + const bool success = BKE_lib_override_library_create_from_tag( + bmain, id_root_reference->lib, true); if (!success) { return success; @@ -950,55 +1075,104 @@ bool BKE_lib_override_library_resync(Main *bmain, ListBase *lb; FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { - if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_new = id->newid; ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); BLI_assert((id_override_new->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0); + /* We need to 'move back' newly created override into its proper library (since it was + * duplicated from the reference ID with 'no main' option, it should currently be the same + * as the reference ID one). */ + BLI_assert(/*id_override_new->lib == NULL || */ id_override_new->lib == id->lib); + BLI_assert(id_override_old == NULL || id_override_old->lib == id_root->lib); + id_override_new->lib = id_root->lib; + /* Remap step below will tag directly linked ones properly as needed. */ + if (ID_IS_LINKED(id_override_new)) { + id_override_new->tag |= LIB_TAG_INDIRECT; + } + if (id_override_old != NULL) { /* Swap the names between old override ID and new one. */ char id_name_buf[MAX_ID_NAME]; memcpy(id_name_buf, id_override_old->name, sizeof(id_name_buf)); memcpy(id_override_old->name, id_override_new->name, sizeof(id_override_old->name)); memcpy(id_override_new->name, id_name_buf, sizeof(id_override_new->name)); - /* Note that this is a very efficient way to keep BMain IDs ordered as expected after - * swapping their names. - * However, one has to be very careful with this when iterating over the listbase at the - * same time. Here it works because we only execute this code when we are in the linked - * IDs, which are always *after* all local ones, and we only affect local IDs. */ - BLI_listbase_swaplinks(lb, id_override_old, id_override_new); - - /* Remap the whole local IDs to use the new override. */ - BKE_libblock_remap( - bmain, id_override_old, id_override_new, ID_REMAP_SKIP_INDIRECT_USAGE); - - /* Copy over overrides rules from old override ID to new one. */ - BLI_duplicatelist(&id_override_new->override_library->properties, - &id_override_old->override_library->properties); - for (IDOverrideLibraryProperty * - op_new = id_override_new->override_library->properties.first, - *op_old = id_override_old->override_library->properties.first; - op_new; - op_new = op_new->next, op_old = op_old->next) { - lib_override_library_property_copy(op_new, op_old); + + BLI_insertlinkreplace(lb, id_override_old, id_override_new); + id_override_old->tag |= LIB_TAG_NO_MAIN; + id_override_new->tag &= ~LIB_TAG_NO_MAIN; + + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)); + + /* Copy over overrides rules from old override ID to new one. */ + BLI_duplicatelist(&id_override_new->override_library->properties, + &id_override_old->override_library->properties); + IDOverrideLibraryProperty *op_new = + id_override_new->override_library->properties.first; + IDOverrideLibraryProperty *op_old = + id_override_old->override_library->properties.first; + for (; op_new; op_new = op_new->next, op_old = op_old->next) { + lib_override_library_property_copy(op_new, op_old); + } } } + else { + /* Add to proper main list, ensure unique name for local ID, sort, and clear relevant + * tags. */ + BKE_libblock_management_main_add(bmain, id_override_new); + } } } FOREACH_MAIN_LISTBASE_ID_END; } FOREACH_MAIN_LISTBASE_END; + /* We need to remap old to new override usages in a separate loop, after all new overrides have + * been added to Main. */ + FOREACH_MAIN_ID_BEGIN (bmain, id) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { + ID *id_override_new = id->newid; + ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); + + if (id_override_old != NULL) { + /* Remap all IDs to use the new override. */ + BKE_libblock_remap(bmain, id_override_old, id_override_new, 0); + /* Remap no-main override IDs we just created too. */ + GHashIterator linkedref_to_old_override_iter; + GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) { + ID *id_override_old_iter = BLI_ghashIterator_getValue(&linkedref_to_old_override_iter); + if (id_override_old_iter->tag & LIB_TAG_NO_MAIN) { + BKE_libblock_relink_ex(bmain, + id_override_old_iter, + id_override_old, + id_override_new, + ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); + } + } + } + } + } + FOREACH_MAIN_ID_END; + + BKE_main_collection_sync(bmain); + /* We need to apply override rules in a separate loop, after all ID pointers have been properly * remapped, and all new local override IDs have gotten their proper original names, otherwise * override operations based on those ID names would fail. */ FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_new = id->newid; + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) { + continue; + } ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); - if (id_override_old != NULL) { + if (id_override_old == NULL) { + continue; + } + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)) { /* Apply rules on new override ID using old one as 'source' data. */ /* Note that since we already remapped ID pointers in old override IDs to new ones, we * can also apply ID pointer override rules safely here. */ @@ -1032,29 +1206,44 @@ bool BKE_lib_override_library_resync(Main *bmain, RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS : RNA_OVERRIDE_APPLY_FLAG_NOP); } + + /* Once overrides have been properly 'transferred' from old to new ID, we can clear ID usages + * of the old one. + * This is necessary in case said old ID is not in Main anymore. */ + BKE_libblock_relink_ex(bmain, + id_override_old, + NULL, + NULL, + ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); + id_override_old->tag |= LIB_TAG_NO_USER_REFCOUNT; } } FOREACH_MAIN_ID_END; /* 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. */ + * 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), - * so we will only ever tag an old override ID after we have already checked it in this loop, - * hence we cannot untag it later. */ - if (id->newid != NULL && ID_IS_LINKED(id)) { + /* Note that this works because linked IDs are always after local ones (including + * overrides), so we will only ever tag an old override ID after we have already checked it + * in this loop, hence we cannot untag it later. */ + if (id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); if (id_override_old != NULL) { id->newid->tag &= ~LIB_TAG_DOIT; id_override_old->tag |= LIB_TAG_DOIT; + if (id_override_old->tag & LIB_TAG_NO_MAIN) { + BKE_id_free(bmain, id_override_old); + } } } id->tag &= ~LIB_TAG_DOIT; } - /* Also deal with old overrides that went missing in new linked data. */ + /* Also deal with old overrides that went missing in new linked data - only for real local + * overrides for now, not those who are linked. */ else if (id->tag & LIB_TAG_MISSING && !ID_IS_LINKED(id)) { BLI_assert(ID_IS_OVERRIDE_LIBRARY(id)); if (!BKE_lib_override_library_is_user_edited(id)) { @@ -1063,15 +1252,32 @@ 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); id_fake_user_set(id); + 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; + + /* Cleanup, many pointers in this GHash are already invalid now. */ + BLI_ghash_free(linkedref_to_old_override, NULL, NULL); + BKE_id_multi_tagged_delete(bmain); /* At this point, `id_root` has very likely been deleted, we need to update it to its new @@ -1079,64 +1285,161 @@ bool BKE_lib_override_library_resync(Main *bmain, */ id_root = id_root_reference->newid; - /* 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 - * since we already relinked old root override collection to new resync'ed one above. So this - * call is not expected to instantiate this new resync'ed collection anywhere, just to ensure - * that we do not have any stray objects. */ - lib_override_library_create_post_process(bmain, - scene, - view_layer, - id_root_reference, - id_root, - override_resync_residual_storage, - true); + 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 + * since we already relinked old root override collection to new resync'ed one above. So this + * call is not expected to instantiate this new resync'ed collection anywhere, just to ensure + * that we do not have any stray objects. */ + lib_override_library_create_post_process(bmain, + scene, + view_layer, + id_root_reference, + id_root, + override_resync_residual_storage, + true); + } /* Cleanup. */ - BLI_ghash_free(linkedref_to_old_override, NULL, NULL); - - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); /* That one should not be needed in fact. */ - /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ - BKE_lib_override_library_main_operations_create(bmain, true); - return success; } -/** - * Detect and handle required resync of overrides data, when relations between reference linked IDs - * have changed. +/* Also tag ancestors overrides for resync. * - * This is a fairly complex and costly operation, typically it should be called after - * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases. + * WARNING: Expects `bmain` to have valid relation data. * - * This function will first detect the remaining cases requiring a resync (namely, either when an - * existing linked ID that did not require to be overridden before now would be, or when new IDs - * are added to the hierarchy). + * NOTE: Related to `lib_override_library_main_resync_find_root_recurse` below. * - * Then it will handle the resync of necessary IDs (through calls to - * #BKE_lib_override_library_resync). + * TODO: This is a sub-optimal, simple solution. At some point, we should rather find a way to + * resync a set of 'sub-roots' overrides, instead of having to 'go back' to the real root and + * resync the whole hierarchy. */ -void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer *view_layer) +static void lib_override_resync_tagging_finalize_recurse(Main *bmain, + ID *id, + const int library_indirect_level) { - /* 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. */ -#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS" - Collection *override_resync_residual_storage = BLI_findstring( - &bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2); - if (override_resync_residual_storage != NULL && - override_resync_residual_storage->id.lib != NULL) { - override_resync_residual_storage = NULL; + if (id->lib != NULL && id->lib->temp_index > library_indirect_level) { + CLOG_ERROR( + &LOG, + "While processing indirect level %d, ID %s from lib %s of indirect level %d detected " + "as needing resync.", + library_indirect_level, + id->name, + id->lib->filepath, + id->lib->temp_index); } - if (override_resync_residual_storage == NULL) { - override_resync_residual_storage = BKE_collection_add( - bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME); - /* Hide the collection from viewport and render. */ - override_resync_residual_storage->flag |= COLLECTION_RESTRICT_VIEWPORT | - COLLECTION_RESTRICT_RENDER; + + MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id); + BLI_assert(entry != NULL); + + if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) { + /* This ID has already been processed. */ + return; + } + /* This way we won't process again that ID, should we encounter it again through another + * relationship hierarchy. */ + entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED; + + for (MainIDRelationsEntryItem *entry_item = entry->from_ids; entry_item != NULL; + entry_item = entry_item->next) { + if (entry_item->usage_flag & + (IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE | IDWALK_CB_LOOPBACK)) { + continue; + } + ID *id_from = entry_item->id_pointer.from; + + /* Case where this ID pointer was to a linked ID, that now needs to be overridden. */ + if (id_from != id && ID_IS_OVERRIDE_LIBRARY_REAL(id_from) && id_from->lib == id->lib) { + id_from->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + CLOG_INFO(&LOG, + 4, + "ID %s (%p) now tagged as needing resync because they use %s (%p) that needs to " + "be overridden", + id_from->name, + id_from->lib, + id->name, + id->lib); + lib_override_resync_tagging_finalize_recurse(bmain, id_from, library_indirect_level); + } + } +} + +/* Ensures parent collection (or objects) in the same override group are also tagged for resync. + * + * This is needed since otherwise, some (new) ID added in one sub-collection might be used in + * another unrelated sub-collection, if 'root' collection is not resynced separated resync of those + * sub-collections would be unaware that this is the same ID, and would re-generate several + * overrides for it. + * + * NOTE: Related to `lib_override_resync_tagging_finalize` above. + */ +static ID *lib_override_library_main_resync_find_root_recurse(ID *id, int *level) +{ + (*level)++; + ID *return_id = id; + + switch (GS(id->name)) { + case ID_GR: { + /* Find the highest valid collection in the parenting hierarchy. + * Note that in practice, in any decent common case there is only one well defined root + * collection anyway. */ + int max_level = *level; + Collection *collection = (Collection *)id; + LISTBASE_FOREACH (CollectionParent *, collection_parent_iter, &collection->parents) { + Collection *collection_parent = collection_parent_iter->collection; + if (ID_IS_OVERRIDE_LIBRARY_REAL(collection_parent) && + collection_parent->id.lib == id->lib) { + int tmp_level = *level; + ID *tmp_id = lib_override_library_main_resync_find_root_recurse(&collection_parent->id, + &tmp_level); + if (tmp_level > max_level) { + max_level = tmp_level; + return_id = tmp_id; + } + } + } + break; + } + case ID_OB: { + Object *object = (Object *)id; + if (object->parent != NULL && ID_IS_OVERRIDE_LIBRARY_REAL(object->parent) && + object->parent->id.lib == id->lib) { + return_id = lib_override_library_main_resync_find_root_recurse(&object->parent->id, level); + } + break; + } + default: + break; } + return return_id; +} + +/* Ensure resync of all overrides at one level of indirect usage. + * + * We need to handle each level independently, since an override at level n may be affected by + * other overrides from level n + 1 etc. (i.e. from linked overrides it may use). + */ +static void lib_override_library_main_resync_on_library_indirect_level( + Main *bmain, + Scene *scene, + ViewLayer *view_layer, + Collection *override_resync_residual_storage, + const int library_indirect_level, + ReportList *reports) +{ BKE_main_relations_create(bmain, 0); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); @@ -1148,7 +1451,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * * those used by current existing overrides. */ ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { continue; } if (id->tag & (LIB_TAG_DOIT | LIB_TAG_MISSING)) { @@ -1159,7 +1462,8 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * LibOverrideGroupTagData data = {.bmain = bmain, .id_root = id->override_library->reference, .tag = LIB_TAG_DOIT, - .missing_tag = LIB_TAG_MISSING}; + .missing_tag = LIB_TAG_MISSING, + .is_override = false}; lib_override_linked_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); lib_override_hierarchy_dependencies_recursive_tag(&data); @@ -1169,13 +1473,15 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * /* Now check existing overrides, those needing resync will be the one either already tagged as * such, or the one using linked data that is now tagged as needing override. */ + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { continue; } if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) { - CLOG_INFO(&LOG, 4, "ID %s was already tagged as needing resync", id->name); + CLOG_INFO(&LOG, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib); + lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level); continue; } @@ -1184,21 +1490,23 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * for (MainIDRelationsEntryItem *entry_item = entry->to_ids; entry_item != NULL; entry_item = entry_item->next) { - if (entry_item->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + if (entry_item->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) { continue; } ID *id_to = *entry_item->id_pointer.to; /* Case where this ID pointer was to a linked ID, that now needs to be overridden. */ - if (ID_IS_LINKED(id_to) && (id_to->tag & LIB_TAG_DOIT) != 0) { + if (ID_IS_LINKED(id_to) && (id_to->lib != id->lib) && (id_to->tag & LIB_TAG_DOIT) != 0) { id->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; CLOG_INFO(&LOG, 3, - "ID %s now tagged as needing resync because they use linked %s that now needs " - "to be overridden", + "ID %s (%p) now tagged as needing resync because they use linked %s (%p) that " + "now needs to be overridden", id->name, - id_to->name); + id->lib, + id_to->name, + id_to->lib); + lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level); break; } } @@ -1213,21 +1521,31 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * * `FOREACH_MAIN_ID_BEGIN/END` here, and need special multi-loop processing. */ bool do_continue = true; while (do_continue) { - ListBase *lb; do_continue = false; + ListBase *lb; FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { - if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0) { + if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0 || + (ID_IS_LINKED(id) && id->lib->temp_index < library_indirect_level) || + (!ID_IS_LINKED(id) && library_indirect_level != 0)) { continue; } + + int level = 0; + /* In complex non-supported cases, with several different override hierarchies sharing + * relations between each-other, we may end up not actually updating/replacing the given + * root id (see e.g. pro/shots/110_rextoria/110_0150_A/110_0150_A.anim.blend of sprites + * project repository, r2687). + * This can lead to infinite loop here, at least avoid this. */ + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + id = lib_override_library_main_resync_find_root_recurse(id, &level); + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); - if (ID_IS_LINKED(id)) { - continue; - } do_continue = true; - CLOG_INFO(&LOG, 2, "Resyncing %s...", id->name); + + CLOG_INFO(&LOG, 2, "Resyncing %s (%p)...", id->name, id->lib); const bool success = BKE_lib_override_library_resync( - bmain, scene, view_layer, id, override_resync_residual_storage, false); + bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); break; } @@ -1238,6 +1556,117 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * } FOREACH_MAIN_LISTBASE_END; } +} + +static int lib_override_sort_libraries_func(LibraryIDLinkCallbackData *cb_data) +{ + if (cb_data->cb_flag & IDWALK_CB_LOOPBACK) { + return IDWALK_RET_NOP; + } + ID *id_owner = cb_data->id_owner; + ID *id = *cb_data->id_pointer; + if (id != NULL && ID_IS_LINKED(id) && id->lib != id_owner->lib) { + const int owner_library_indirect_level = id_owner->lib != NULL ? id_owner->lib->temp_index : 0; + if (owner_library_indirect_level > 10000) { + CLOG_ERROR( + &LOG, + "Levels of indirect usages of libraries is way too high, skipping further building " + "loops (Involves at least '%s' and '%s')", + id_owner->lib->filepath, + id->lib->filepath); + BLI_assert(0); + return IDWALK_RET_NOP; + } + + if (owner_library_indirect_level >= id->lib->temp_index) { + id->lib->temp_index = owner_library_indirect_level + 1; + *(bool *)cb_data->user_data = true; + } + } + return IDWALK_RET_NOP; +} + +/** Define the `temp_index` of libraries from their highest level of indirect usage. + * + * E.g. if lib_a uses lib_b, lib_c and lib_d, and lib_b also uses lib_d, then lib_a has an index of + * 1, lib_b and lib_c an index of 2, and lib_d an index of 3. */ +static int lib_override_libraries_index_define(Main *bmain) +{ + LISTBASE_FOREACH (Library *, library, &bmain->libraries) { + /* index 0 is reserved for local data. */ + library->temp_index = 1; + } + bool do_continue = true; + while (do_continue) { + do_continue = false; + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + BKE_library_foreach_ID_link( + bmain, id, lib_override_sort_libraries_func, &do_continue, IDWALK_READONLY); + } + FOREACH_MAIN_ID_END; + } + + int library_indirect_level_max = 0; + LISTBASE_FOREACH (Library *, library, &bmain->libraries) { + if (library->temp_index > library_indirect_level_max) { + library_indirect_level_max = library->temp_index; + } + } + return library_indirect_level_max; +} + +/** + * Detect and handle required resync of overrides data, when relations between reference linked IDs + * have changed. + * + * This is a fairly complex and costly operation, typically it should be called after + * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases. + * + * This function will first detect the remaining cases requiring a resync (namely, either when an + * existing linked ID that did not require to be overridden before now would be, or when new IDs + * are added to the hierarchy). + * + * 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, + 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. */ +#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS" + Collection *override_resync_residual_storage = BLI_findstring( + &bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2); + if (override_resync_residual_storage != NULL && + override_resync_residual_storage->id.lib != NULL) { + override_resync_residual_storage = NULL; + } + if (override_resync_residual_storage == NULL) { + override_resync_residual_storage = BKE_collection_add( + bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME); + /* Hide the collection from viewport and render. */ + override_resync_residual_storage->flag |= COLLECTION_RESTRICT_VIEWPORT | + COLLECTION_RESTRICT_RENDER; + } + + int library_indirect_level = lib_override_libraries_index_define(bmain); + while (library_indirect_level >= 0) { + /* Update overrides from each indirect level separately. */ + lib_override_library_main_resync_on_library_indirect_level(bmain, + scene, + view_layer, + override_resync_residual_storage, + library_indirect_level, + reports); + library_indirect_level--; + } + + /* Essentially ensures that potentially new overrides of new objects will be instantiated. */ + lib_override_library_create_post_process( + bmain, scene, view_layer, NULL, NULL, override_resync_residual_storage, true); if (BKE_collection_is_empty(override_resync_residual_storage)) { BKE_collection_delete(bmain, override_resync_residual_storage, true); @@ -1258,9 +1687,12 @@ void BKE_lib_override_library_delete(Main *bmain, ID *id_root) /* Tag all library overrides in the chains of dependencies from the given root one. */ BKE_main_relations_create(bmain, 0); - LibOverrideGroupTagData data = { - .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING}; - lib_override_local_group_tag(&data); + LibOverrideGroupTagData data = {.bmain = bmain, + .id_root = id_root, + .tag = LIB_TAG_DOIT, + .missing_tag = LIB_TAG_MISSING, + .is_override = true}; + lib_override_overrides_group_tag(&data); BKE_main_relations_free(bmain); @@ -1614,7 +2046,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) { @@ -1648,7 +2080,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; @@ -1903,10 +2335,11 @@ bool BKE_lib_override_library_main_operations_create(Main *bmain, const bool for } struct LibOverrideOpCreateData create_pool_data = {.bmain = bmain, .changed = false}; - TaskPool *task_pool = BLI_task_pool_create(&create_pool_data, TASK_PRIORITY_HIGH); + TaskPool *task_pool = BLI_task_pool_create( + &create_pool_data, TASK_PRIORITY_HIGH, TASK_ISOLATION_ON); FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (ID_IS_OVERRIDE_LIBRARY_REAL(id) && + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && (force_auto || (id->tag & LIB_TAG_OVERRIDE_LIBRARY_AUTOREFRESH))) { /* Usual issue with pose, it's quiet rare but sometimes they may not be up to date when this * function is called. */ @@ -2049,8 +2482,8 @@ static void lib_override_library_id_hierarchy_recursive_reset(Main *bmain, ID *i for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } @@ -2409,8 +2842,10 @@ ID *BKE_lib_override_library_operations_store_start(Main *bmain, return storage_id; } -/** Restore given ID modified by \a BKE_lib_override_library_operations_store_start, to its - * original state. */ +/** + * Restore given ID modified by #BKE_lib_override_library_operations_store_start, to its + * original state. + */ void BKE_lib_override_library_operations_store_end( OverrideLibraryStorage *UNUSED(override_storage), ID *local) { diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index e33743eb36b..b748061ef8a 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -56,11 +56,19 @@ typedef struct LibraryForeachIDData { */ ID *self_id; + /** Flags controlling the behavior of the 'foreach id' looping code. */ int flag; + /** Generic flags to be passed to all callback calls for current processed data. */ int cb_flag; + /** Callback flags that are forbidden for all callback calls for current processed data. */ int cb_flag_clear; + + /* Function to call for every ID pointers of current processed data, and its opaque user data + * pointer. */ LibraryIDLinkCallback callback; void *user_data; + /** Store the returned value from the callback, to decide how to continue the processing of ID + * pointers for current data. */ int status; /* To handle recursion. */ @@ -73,13 +81,25 @@ bool BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int if (!(data->status & IDWALK_STOP)) { const int flag = data->flag; ID *old_id = *id_pp; - const int callback_return = data->callback(&(struct LibraryIDLinkCallbackData){ - .user_data = data->user_data, - .bmain = data->bmain, - .id_owner = data->owner_id, - .id_self = data->self_id, - .id_pointer = id_pp, - .cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear)}); + + /* Update the callback flags with the ones defined (or forbidden) in `data` by the generic + * caller code. */ + cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear); + + /* Update the callback flags with some extra information regarding overrides: all 'loopback', + * 'internal', 'embedded' etc. ID pointers are never overridable. */ + if (cb_flag & (IDWALK_CB_INTERNAL | IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | + IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + cb_flag |= IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE; + } + + const int callback_return = data->callback( + &(struct LibraryIDLinkCallbackData){.user_data = data->user_data, + .bmain = data->bmain, + .id_owner = data->owner_id, + .id_self = data->self_id, + .id_pointer = id_pp, + .cb_flag = cb_flag}); if (flag & IDWALK_READONLY) { BLI_assert(*(id_pp) == old_id); } @@ -132,7 +152,10 @@ void BKE_lib_query_idpropertiesForeachIDLink_callback(IDProperty *id_prop, void BLI_assert(id_prop->type == IDP_ID); LibraryForeachIDData *data = (LibraryForeachIDData *)user_data; - BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, IDWALK_CB_USER); + const int cb_flag = IDWALK_CB_USER | ((id_prop->flag & IDP_FLAG_OVERRIDABLE_LIBRARY) ? + 0 : + IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); + BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, cb_flag); } bool BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp) @@ -221,9 +244,10 @@ static void library_foreach_ID_link(Main *bmain, * (the node tree), but re-use those generated for the 'owner' ID (the material). */ if (inherit_data == NULL) { data.cb_flag = ID_IS_LINKED(id) ? IDWALK_CB_INDIRECT_USAGE : 0; - /* When an ID is not in Main database, it should never refcount IDs it is using. - * Exceptions: NodeTrees (yeah!) directly used by Materials. */ - data.cb_flag_clear = (id->tag & LIB_TAG_NO_MAIN) ? IDWALK_CB_USER | IDWALK_CB_USER_ONE : 0; + /* When an ID is defined as not refcounting its ID usages, it should never do it. */ + data.cb_flag_clear = (id->tag & LIB_TAG_NO_USER_REFCOUNT) ? + IDWALK_CB_USER | IDWALK_CB_USER_ONE : + 0; } else { data.cb_flag = inherit_data->cb_flag; diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 1f597bbb9a6..2641208897e 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -137,6 +137,7 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) (id_remap_data->flag & ID_REMAP_FORCE_NEVER_NULL_USAGE) == 0); const bool skip_reference = (id_remap_data->flag & ID_REMAP_SKIP_OVERRIDE_LIBRARY) != 0; const bool skip_never_null = (id_remap_data->flag & ID_REMAP_SKIP_NEVER_NULL_USAGE) != 0; + const bool force_user_refcount = (id_remap_data->flag & ID_REMAP_FORCE_USER_REFCOUNT) != 0; #ifdef DEBUG_PRINT printf( @@ -203,16 +204,16 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) } } if (cb_flag & IDWALK_CB_USER) { - /* NOTE: We don't user-count IDs which are not in the main database. + /* NOTE: by default we don't user-count IDs which are not in the main database. * This is because in certain conditions we can have data-blocks in * the main which are referencing data-blocks outside of it. * For example, BKE_mesh_new_from_object() called on an evaluated * object will cause such situation. */ - if ((old_id->tag & LIB_TAG_NO_MAIN) == 0) { + if (force_user_refcount || (old_id->tag & LIB_TAG_NO_MAIN) == 0) { id_us_min(old_id); } - if (new_id != NULL && (new_id->tag & LIB_TAG_NO_MAIN) == 0) { + if (new_id != NULL && (force_user_refcount || (new_id->tag & LIB_TAG_NO_MAIN) == 0)) { /* We do not want to handle LIB_TAG_INDIRECT/LIB_TAG_EXTERN here. */ new_id->us++; } @@ -303,6 +304,7 @@ static void libblock_remap_data_postprocess_object_update(Main *bmain, /* Can be called with both old_collection and new_collection being NULL, * this means we have to check whole Main database then. */ static void libblock_remap_data_postprocess_collection_update(Main *bmain, + Collection *owner_collection, Collection *UNUSED(old_collection), Collection *new_collection) { @@ -311,7 +313,7 @@ static void libblock_remap_data_postprocess_collection_update(Main *bmain, * and BKE_main_collection_sync_remap() does not tolerate any of those, so for now always check * whole existing collections for NULL pointers. * I'd consider optimizing that whole collection remapping process a TODO for later. */ - BKE_collections_child_remove_nulls(bmain, NULL /*old_collection*/); + BKE_collections_child_remove_nulls(bmain, owner_collection, NULL /*old_collection*/); } else { /* Temp safe fix, but a "tad" brute force... We should probably be able to use parents from @@ -523,7 +525,7 @@ void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const break; case ID_GR: libblock_remap_data_postprocess_collection_update( - bmain, (Collection *)old_id, (Collection *)new_id); + bmain, NULL, (Collection *)old_id, (Collection *)new_id); break; case ID_ME: case ID_CU: @@ -628,6 +630,12 @@ void BKE_libblock_relink_ex( switch (GS(id->name)) { case ID_SCE: case ID_GR: { + /* Note: here we know which collection we have affected, so at lest for NULL children + * detection we can only process that one. + * This is also a required fix in case `id` would not be in Main anymore, which can happen + * e.g. when called from `id_delete`. */ + Collection *owner_collection = (GS(id->name) == ID_GR) ? (Collection *)id : + ((Scene *)id)->master_collection; if (old_id) { switch (GS(old_id->name)) { case ID_OB: @@ -636,7 +644,7 @@ void BKE_libblock_relink_ex( break; case ID_GR: libblock_remap_data_postprocess_collection_update( - bmain, (Collection *)old_id, (Collection *)new_id); + bmain, owner_collection, (Collection *)old_id, (Collection *)new_id); break; default: break; @@ -644,7 +652,7 @@ void BKE_libblock_relink_ex( } else { /* No choice but to check whole objects/collections. */ - libblock_remap_data_postprocess_collection_update(bmain, NULL, NULL); + libblock_remap_data_postprocess_collection_update(bmain, owner_collection, NULL, NULL); libblock_remap_data_postprocess_object_update(bmain, NULL, NULL); } break; diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index dc678f248c9..39cc5737ca2 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -538,10 +538,12 @@ ListBase *which_libbase(Main *bmain, short type) * This is useful for generic traversal of all the blocks in a #Main (by traversing all the lists * in turn), without worrying about block types. * + * \param lb: Array of lists #INDEX_ID_MAX in length. + * * \note The order of each ID type #ListBase in the array is determined by the `INDEX_ID_<IDTYPE>` * enum definitions in `DNA_ID.h`. See also the #FOREACH_MAIN_ID_BEGIN macro in `BKE_main.h` */ -int set_listbasepointers(Main *bmain, ListBase *lb[INDEX_ID_MAX]) +int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/]) { /* Libraries may be accessed from pretty much any other ID. */ lb[INDEX_ID_LI] = &(bmain->libraries); diff --git a/source/blender/blenkernel/intern/mask.c b/source/blender/blenkernel/intern/mask.c index 3a3ad9ef051..a3f122115d8 100644 --- a/source/blender/blenkernel/intern/mask.c +++ b/source/blender/blenkernel/intern/mask.c @@ -1893,10 +1893,17 @@ static void interp_weights_uv_v2_calc(float r_uv[2], const float pt_a[2], const float pt_b[2]) { + const float segment_len = len_v2v2(pt_a, pt_b); + if (segment_len == 0.0f) { + r_uv[0] = 1.0f; + r_uv[1] = 0.0f; + return; + } + float pt_on_line[2]; r_uv[0] = closest_to_line_v2(pt_on_line, pt, pt_a, pt_b); - r_uv[1] = (len_v2v2(pt_on_line, pt) / len_v2v2(pt_a, pt_b)) * + r_uv[1] = (len_v2v2(pt_on_line, pt) / segment_len) * /* This line only sets the sign. */ ((line_point_side_v2(pt_a, pt_b, pt) < 0.0f) ? -1.0f : 1.0f); } diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 37d47a984cc..c5060e16e4d 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,114 @@ 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) +{ + BLI_assert(slot >= 1); + 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; +} + +/** + * Add an empty material slot if the id has no material slots. This material slot allows the + * material to be overwritten by object-linked materials. + */ +void BKE_id_material_eval_ensure_default_slot(ID *id) +{ + short *len_ptr = BKE_id_material_len_p(id); + if (len_ptr == NULL) { + return; + } + if (*len_ptr == 0) { + BKE_id_material_eval_assign(id, 1, NULL); + } +} + Material *BKE_gpencil_material(Object *ob, short act) { Material *ma = BKE_object_material_get(ob, act); @@ -860,12 +969,6 @@ void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act act = 1; } - /* prevent crashing when using accidentally */ - BLI_assert(!ID_IS_LINKED(ob)); - if (ID_IS_LINKED(ob)) { - return; - } - /* test arraylens */ totcolp = BKE_object_material_len_p(ob); @@ -1028,6 +1131,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, @@ -1367,16 +1507,16 @@ void BKE_texpaint_slots_refresh_object(Scene *scene, struct Object *ob) } struct FindTexPaintNodeData { - bNode *node; - short iter_index; - short index; + Image *ima; + bNode *r_node; }; static bool texpaint_slot_node_find_cb(bNode *node, void *userdata) { struct FindTexPaintNodeData *find_data = userdata; - if (find_data->iter_index++ == find_data->index) { - find_data->node = node; + Image *ima = (Image *)node->id; + if (find_data->ima == ima) { + find_data->r_node = node; return false; } @@ -1385,10 +1525,10 @@ static bool texpaint_slot_node_find_cb(bNode *node, void *userdata) bNode *BKE_texpaint_slot_material_find_node(Material *ma, short texpaint_slot) { - struct FindTexPaintNodeData find_data = {NULL, 0, texpaint_slot}; + struct FindTexPaintNodeData find_data = {ma->texpaintslot[texpaint_slot].ima, NULL}; ntree_foreach_texnode_recursive(ma->nodetree, texpaint_slot_node_find_cb, &find_data); - return find_data.node; + return find_data.r_node; } /* r_col = current value, col = new value, (fac == 0) is no change */ diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 1550401cc9c..bb46c7b16c0 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; @@ -1435,7 +1436,7 @@ void BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBa if (process.totelem > 0) { build_bvh_spatial(&process, &process.metaball_bvh, 0, process.totelem, &process.allbb); - /* Don't polygonize meta-balls with too high resolution (base mball to small) + /* Don't polygonize meta-balls with too high resolution (base mball too small) * note: Eps was 0.0001f but this was giving problems for blood animation for * the open movie "Sintel", using 0.00001f. */ if (ob->scale[0] > 0.00001f * (process.allbb.max[0] - process.allbb.min[0]) || diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c index ddbc7e7d1ef..0b843c3a97a 100644 --- a/source/blender/blenkernel/intern/mesh.c +++ b/source/blender/blenkernel/intern/mesh.c @@ -1521,12 +1521,12 @@ void BKE_mesh_transform(Mesh *me, const float mat[4][4], bool do_keys) void BKE_mesh_translate(Mesh *me, const float offset[3], const bool do_keys) { - MVert *mvert = CustomData_duplicate_referenced_layer(&me->vdata, CD_MVERT, me->totvert); + CustomData_duplicate_referenced_layer(&me->vdata, CD_MVERT, me->totvert); /* If the referenced layer has been re-allocated need to update pointers stored in the mesh. */ BKE_mesh_update_customdata_pointers(me, false); int i = me->totvert; - for (mvert = me->mvert; i--; mvert++) { + for (MVert *mvert = me->mvert; i--; mvert++) { add_v3_v3(mvert->co, offset); } diff --git a/source/blender/blenkernel/intern/mesh_boolean_convert.cc b/source/blender/blenkernel/intern/mesh_boolean_convert.cc index 824f791d400..cfb1c192afe 100644 --- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc +++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc @@ -28,9 +28,10 @@ #include "BKE_customdata.h" #include "BKE_material.h" #include "BKE_mesh.h" -#include "BKE_mesh_boolean_convert.h" +#include "BKE_mesh_boolean_convert.hh" #include "BLI_alloca.h" +#include "BLI_array.hh" #include "BLI_float2.hh" #include "BLI_float4x4.hh" #include "BLI_math.h" @@ -51,8 +52,9 @@ constexpr int estimated_max_facelen = 100; /* Used for initial size of some Vect * so this is a hack to clean up such matrices. * Would be better to change the transformation code itself. */ -static void clean_obmat(float4x4 &cleaned, const float4x4 &mat) +static float4x4 clean_obmat(const float4x4 &mat) { + float4x4 cleaned; const float fuzz = 1e-6f; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { @@ -69,6 +71,7 @@ static void clean_obmat(float4x4 &cleaned, const float4x4 &mat) cleaned.values[i][j] = f; } } + return cleaned; } /* `MeshesToIMeshInfo` keeps track of information used when combining a number @@ -92,10 +95,10 @@ class MeshesToIMeshInfo { Array<Face *> mesh_to_imesh_face; /* Transformation matrix to transform a coordinate in the corresponding * Mesh to the local space of the first Mesh. */ - Array<float4x4> to_obj0; + Array<float4x4> to_target_transform; /* For each input mesh, how to remap the material slot numbers to * the material slots in the first mesh. */ - Array<const short *> material_remaps; + Span<Array<short>> material_remaps; /* Total number of input mesh vertices. */ int tot_meshes_verts; /* Total number of input mesh edges. */ @@ -228,7 +231,8 @@ const MEdge *MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index, return medge; } -/** Convert all of the meshes in `meshes` to an `IMesh` and return that. +/** + * Convert all of the meshes in `meshes` to an `IMesh` and return that. * All of the coordinates are transformed into the local space of the * first Mesh. To do this transformation, we also need the transformation * obmats corresponding to the Meshes, so they are in the `obmats` argument. @@ -241,7 +245,8 @@ const MEdge *MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index, */ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, Span<const float4x4 *> obmats, - Span<const short *> material_remaps, + Span<Array<short>> material_remaps, + const float4x4 &target_transform, IMeshArena &arena, MeshesToIMeshInfo *r_info) { @@ -270,8 +275,8 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, r_info->mesh_vert_offset = Array<int>(nmeshes); r_info->mesh_edge_offset = Array<int>(nmeshes); r_info->mesh_poly_offset = Array<int>(nmeshes); - r_info->to_obj0 = Array<float4x4>(nmeshes); - r_info->material_remaps = Array<const short *>(nmeshes); + r_info->to_target_transform = Array<float4x4>(nmeshes); + r_info->material_remaps = material_remaps; int v = 0; int e = 0; int f = 0; @@ -283,19 +288,10 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, Vector<int, estimated_max_facelen> face_edge_orig; /* To convert the coordinates of objects 1, 2, etc. into the local space - * of object 0, we multiply each object's `obmat` by the inverse of - * object 0's `obmat`. Exact Boolean works better if these matrices - * are 'cleaned' -- see the comment for the `clean_obmat` function, above. */ - float4x4 obj0_mat; - float4x4 inv_obj0_mat; - if (obmats[0] == nullptr) { - unit_m4(obj0_mat.values); - unit_m4(inv_obj0_mat.values); - } - else { - clean_obmat(obj0_mat, *obmats[0]); - invert_m4_m4(inv_obj0_mat.values, obj0_mat.values); - } + * of the target. We multiply each object's `obmat` by the inverse of the + * target matrix. Exact Boolean works better if these matrices are 'cleaned' + * -- see the comment for the `clean_obmat` function, above. */ + const float4x4 inv_target_mat = clean_obmat(target_transform).inverted(); /* For each input `Mesh`, make `Vert`s and `Face`s for the corresponding * `MVert`s and `MPoly`s, and keep track of the original indices (using the @@ -303,45 +299,34 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, * When making `Face`s, we also put in the original indices for `MEdge`s that * make up the `MPoly`s using the same scheme. */ for (int mi : meshes.index_range()) { - float4x4 objn_to_obj0_mat; const Mesh *me = meshes[mi]; - if (mi == 0) { - r_info->mesh_vert_offset[mi] = 0; - r_info->mesh_edge_offset[mi] = 0; - r_info->mesh_poly_offset[mi] = 0; - unit_m4(r_info->to_obj0[0].values); - r_info->material_remaps[0] = nullptr; - } - else { - r_info->mesh_vert_offset[mi] = v; - r_info->mesh_edge_offset[mi] = e; - r_info->mesh_poly_offset[mi] = f; - /* Get matrix that transforms a coordinate in objects[mi]'s local space - * to object[0]'s local space.*/ - float4x4 objn_mat; - if (obmats[mi] == nullptr) { - unit_m4(objn_mat.values); - } - else { - clean_obmat(objn_mat, *obmats[mi]); - } - objn_to_obj0_mat = inv_obj0_mat * objn_mat; - r_info->to_obj0[mi] = objn_to_obj0_mat; - if (mi < material_remaps.size()) { - r_info->material_remaps[mi] = material_remaps[mi]; - } - else { - r_info->material_remaps[mi] = nullptr; + r_info->mesh_vert_offset[mi] = v; + r_info->mesh_edge_offset[mi] = e; + r_info->mesh_poly_offset[mi] = f; + /* Get matrix that transforms a coordinate in objects[mi]'s local space + * to the target space space.*/ + const float4x4 objn_mat = (obmats[mi] == nullptr) ? float4x4::identity() : + clean_obmat(*obmats[mi]); + r_info->to_target_transform[mi] = inv_target_mat * objn_mat; + + /* Skip the matrix multiplication for each point when there is no transform for a mesh, + * for example when the first mesh is already in the target space. (Note the logic directly + * above, which uses an identity matrix with a null input transform). */ + if (obmats[mi] == nullptr) { + for (const MVert &vert : Span(me->mvert, me->totvert)) { + const float3 co = float3(vert.co); + r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); + ++v; } } - for (int vi = 0; vi < me->totvert; ++vi) { - float3 co = me->mvert[vi].co; - if (mi > 0) { - co = objn_to_obj0_mat * co; + else { + for (const MVert &vert : Span(me->mvert, me->totvert)) { + const float3 co = r_info->to_target_transform[mi] * float3(vert.co); + r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); + ++v; } - r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); - ++v; } + for (const MPoly &poly : Span(me->mpoly, me->totpoly)) { int flen = poly.totloop; face_vert.clear(); @@ -403,14 +388,14 @@ static void copy_poly_attributes(Mesh *dest_mesh, const Mesh *orig_me, int mp_index, int index_in_orig_me, - const short *material_remap) + Span<short> material_remap) { mp->mat_nr = orig_mp->mat_nr; if (mp->mat_nr >= dest_mesh->totcol) { mp->mat_nr = 0; } else { - if (material_remap) { + if (material_remap.size() > 0) { short mat_nr = material_remap[orig_mp->mat_nr]; if (mat_nr >= 0 && mat_nr < dest_mesh->totcol) { mp->mat_nr = mat_nr; @@ -596,7 +581,7 @@ static void copy_or_interp_loop_attributes(Mesh *dest_mesh, cos_2d = (float(*)[2])BLI_array_alloca(cos_2d, orig_mp->totloop); weights = Array<float>(orig_mp->totloop); src_blocks_ofs = Array<const void *>(orig_mp->totloop); - get_poly2d_cos(orig_me, orig_mp, cos_2d, mim.to_obj0[orig_me_index], axis_mat); + get_poly2d_cos(orig_me, orig_mp, cos_2d, mim.to_target_transform[orig_me_index], axis_mat); } CustomData *target_cd = &dest_mesh->ldata; for (int i = 0; i < mp->totloop; ++i) { @@ -654,7 +639,8 @@ static void copy_or_interp_loop_attributes(Mesh *dest_mesh, } } -/** Make sure that there are custom data layers in the target mesh +/** + * Make sure that there are custom data layers in the target mesh * corresponding to all target layers in all of the operands after the first. * (The target should already have layers for those in the first operand mesh). * Edges done separately -- will have to be done later, after edges are made. @@ -689,7 +675,8 @@ static void merge_edge_customdata_layers(Mesh *target, MeshesToIMeshInfo &mim) } } -/** Convert the output IMesh im to a Blender Mesh, +/** + * Convert the output IMesh im to a Blender Mesh, * using the information in mim to get all the attributes right. */ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim) @@ -743,8 +730,15 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim) ++cur_loop_index; } - copy_poly_attributes( - result, mp, orig_mp, orig_me, fi, index_in_orig_me, mim.material_remaps[orig_me_index]); + copy_poly_attributes(result, + mp, + orig_mp, + orig_me, + fi, + index_in_orig_me, + (mim.material_remaps.size() > 0) ? + mim.material_remaps[orig_me_index].as_span() : + Span<short>()); copy_or_interp_loop_attributes(result, f, mp, orig_mp, orig_me, orig_me_index, mim); } @@ -778,29 +772,40 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim) return result; } +#endif // WITH_GMP + /** - * Do Exact Boolean directly, without a round trip through #BMesh. - * The Mesh operands are in `meshes`, with corresponding transforms in in `obmats`. + * Do a mesh boolean operation directly on meshes (without going back and forth to BMesh). + * \param meshes: An array of of Mesh pointers. + * \param obmats: An array of pointers to the obmat matrices that transform local + * coordinates to global ones. It is allowed for the pointers to be null, meaning the + * transformation is the identity. + * \param material_remaps: An array of pointers to arrays of maps from material slot numbers in the + * corresponding mesh to the material slot in the first mesh. It is OK for material_remaps or any + * of its constituent arrays to be empty. */ -static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, - Span<const float4x4 *> obmats, - Span<const short *> material_remaps, - const bool use_self, - const bool hole_tolerant, - const BoolOpType boolean_mode) +Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, + Span<const float4x4 *> obmats, + const float4x4 &target_transform, + Span<Array<short>> material_remaps, + const bool use_self, + const bool hole_tolerant, + const int boolean_mode) { - const int dbg_level = 0; +#ifdef WITH_GMP BLI_assert(meshes.size() == obmats.size()); - const int meshes_len = meshes.size(); - if (meshes_len <= 0) { + BLI_assert(material_remaps.size() == 0 || material_remaps.size() == meshes.size()); + if (meshes.size() <= 0) { return nullptr; } + + const int dbg_level = 0; if (dbg_level > 0) { - std::cout << "\nDIRECT_MESH_INTERSECT, nmeshes = " << meshes_len << "\n"; + std::cout << "\nDIRECT_MESH_INTERSECT, nmeshes = " << meshes.size() << "\n"; } MeshesToIMeshInfo mim; IMeshArena arena; - IMesh m_in = meshes_to_imesh(meshes, obmats, material_remaps, arena, &mim); + IMesh m_in = meshes_to_imesh(meshes, obmats, material_remaps, target_transform, arena, &mim); std::function<int(int)> shape_fn = [&mim](int f) { for (int mi = 0; mi < mim.mesh_poly_offset.size() - 1; ++mi) { if (f < mim.mesh_poly_offset[mi + 1]) { @@ -809,61 +814,25 @@ static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, } return static_cast<int>(mim.mesh_poly_offset.size()) - 1; }; - IMesh m_out = boolean_mesh( - m_in, boolean_mode, meshes_len, shape_fn, use_self, hole_tolerant, nullptr, &arena); - if (dbg_level > 1) { + IMesh m_out = boolean_mesh(m_in, + static_cast<BoolOpType>(boolean_mode), + meshes.size(), + shape_fn, + use_self, + hole_tolerant, + nullptr, + &arena); + if (dbg_level > 0) { std::cout << m_out; write_obj_mesh(m_out, "m_out"); } return imesh_to_mesh(&m_out, mim); -} - +#else // WITH_GMP + UNUSED_VARS( + meshes, obmats, material_remaps, target_transform, use_self, hole_tolerant, boolean_mode); + return nullptr; #endif // WITH_GMP -} // namespace blender::meshintersect - -extern "C" { - -#ifdef WITH_GMP -/* Do a mesh boolean directly on meshes (without going back and forth to BMesh). - * The \a meshes argument is an array of \a meshes_len of Mesh pointers. - * The \a obmats argument is an array of \a meshes_len of pointers to the obmat - * The \a material_remaps is an array of pointers to arrays of maps from material - * slot numbers in the corresponding mesh to the material slot in the first mesh. - * It is OK for material_remaps or any of its constituent arrays to be NULL. - * matrices that transform local coordinates to global ones. It is allowed - * for the pointers to be nullptr, meaning the transformation is the identity. */ -Mesh *BKE_mesh_boolean(const Mesh **meshes, - const float (*obmats[])[4][4], - const short **material_remaps, - const int meshes_len, - const bool use_self, - const bool hole_tolerant, - const int boolean_mode) -{ - const blender::float4x4 **transforms = (const blender::float4x4 **)obmats; - return blender::meshintersect::direct_mesh_boolean( - blender::Span(meshes, meshes_len), - blender::Span(transforms, meshes_len), - blender::Span(material_remaps, material_remaps ? meshes_len : 0), - use_self, - hole_tolerant, - static_cast<blender::meshintersect::BoolOpType>(boolean_mode)); -} - -#else -Mesh *BKE_mesh_boolean(const Mesh **UNUSED(meshes), - const float (*obmats[])[4][4], - const short **UNUSED(material_remaps), - const int UNUSED(meshes_len), - const bool UNUSED(use_self), - const bool UNUSED(hole_tolerant), - const int UNUSED(boolean_mode)) -{ - UNUSED_VARS(obmats); - return NULL; } -#endif - -} // extern "C" +} // namespace blender::meshintersect diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index 385fd473e63..c698d95ed8d 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -1027,11 +1027,15 @@ static Object *object_for_curve_to_mesh_create(Object *object) * * Note that there are extra fields in there like bevel and path, but those are not needed during * conversion, so they are not copied to save unnecessary allocations. */ - if (object->runtime.curve_cache != NULL) { + if (temp_object->runtime.curve_cache == NULL) { temp_object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for curve types"); + } + + if (object->runtime.curve_cache != NULL) { BKE_displist_copy(&temp_object->runtime.curve_cache->disp, &object->runtime.curve_cache->disp); } + /* Constructive modifiers will use mesh to store result. */ if (object->runtime.data_eval != NULL) { BKE_id_copy_ex( @@ -1057,16 +1061,25 @@ static Object *object_for_curve_to_mesh_create(Object *object) return temp_object; } +/** + * Populate `object->runtime.curve_cache` which is then used to create the mesh. + */ static void curve_to_mesh_eval_ensure(Object *object) { - if (object->runtime.curve_cache == NULL) { - object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); - } Curve *curve = (Curve *)object->data; Curve remapped_curve = *curve; Object remapped_object = *object; + BKE_object_runtime_reset(&remapped_object); + remapped_object.data = &remapped_curve; + if (object->runtime.curve_cache == NULL) { + object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); + } + + /* Temporarily share the curve-cache with the temporary object, owned by `object`. */ + remapped_object.runtime.curve_cache = object->runtime.curve_cache; + /* Clear all modifiers for the bevel object. * * This is because they can not be reliably evaluated for an original object (at least because @@ -1078,6 +1091,7 @@ static void curve_to_mesh_eval_ensure(Object *object) if (remapped_curve.bevobj != NULL) { bevel_object = *remapped_curve.bevobj; BLI_listbase_clear(&bevel_object.modifiers); + BKE_object_runtime_reset(&bevel_object); remapped_curve.bevobj = &bevel_object; } @@ -1086,6 +1100,7 @@ static void curve_to_mesh_eval_ensure(Object *object) if (remapped_curve.taperobj != NULL) { taper_object = *remapped_curve.taperobj; BLI_listbase_clear(&taper_object.modifiers); + BKE_object_runtime_reset(&taper_object); remapped_curve.taperobj = &taper_object; } @@ -1098,7 +1113,7 @@ static void curve_to_mesh_eval_ensure(Object *object) * Brecht says hold off with that. */ Mesh *mesh_eval = NULL; BKE_displist_make_curveTypes_forRender( - NULL, NULL, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval, false); + NULL, NULL, &remapped_object, &remapped_object.runtime.curve_cache->disp, false, &mesh_eval); /* Note: this is to be consistent with `BKE_displist_make_curveTypes()`, however that is not a * real issue currently, code here is broken in more than one way, fix(es) will be done @@ -1107,8 +1122,12 @@ static void curve_to_mesh_eval_ensure(Object *object) BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true); } - BKE_object_free_curve_cache(&bevel_object); - BKE_object_free_curve_cache(&taper_object); + /* Owned by `object` & needed by the caller to create the mesh. */ + remapped_object.runtime.curve_cache = NULL; + + BKE_object_runtime_free_data(&remapped_object); + BKE_object_runtime_free_data(&taper_object); + BKE_object_runtime_free_data(&taper_object); } static Mesh *mesh_new_from_curve_type_object(Object *object) @@ -1192,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); @@ -1209,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 @@ -1229,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) { @@ -1242,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. */ @@ -1307,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); @@ -1535,7 +1567,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, * check whether it is still true with Mesh */ Mesh tmp = *mesh_dst; int totvert, totedge /*, totface */ /* UNUSED */, totloop, totpoly; - int did_shapekeys = 0; + bool did_shapekeys = false; eCDAllocType alloctype = CD_DUPLICATE; if (take_ownership /* && dm->type == DM_TYPE_CDDM && dm->needsFree */) { @@ -1590,7 +1622,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, } shapekey_layers_to_keyblocks(mesh_src, mesh_dst, uid); - did_shapekeys = 1; + did_shapekeys = true; } /* copy texture space */ @@ -1619,13 +1651,18 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, totedge); } if (!CustomData_has_layer(&tmp.pdata, CD_MPOLY)) { - /* TODO(Sybren): assignment to tmp.mxxx is probably not necessary due to the - * BKE_mesh_update_customdata_pointers() call below. */ - tmp.mloop = (alloctype == CD_ASSIGN) ? mesh_src->mloop : MEM_dupallocN(mesh_src->mloop); - tmp.mpoly = (alloctype == CD_ASSIGN) ? mesh_src->mpoly : MEM_dupallocN(mesh_src->mpoly); - - CustomData_add_layer(&tmp.ldata, CD_MLOOP, CD_ASSIGN, tmp.mloop, tmp.totloop); - CustomData_add_layer(&tmp.pdata, CD_MPOLY, CD_ASSIGN, tmp.mpoly, tmp.totpoly); + CustomData_add_layer(&tmp.ldata, + CD_MLOOP, + CD_ASSIGN, + (alloctype == CD_ASSIGN) ? mesh_src->mloop : + MEM_dupallocN(mesh_src->mloop), + tmp.totloop); + CustomData_add_layer(&tmp.pdata, + CD_MPOLY, + CD_ASSIGN, + (alloctype == CD_ASSIGN) ? mesh_src->mpoly : + MEM_dupallocN(mesh_src->mpoly), + tmp.totpoly); } /* object had got displacement layer, should copy this layer to save sculpted data */ @@ -1634,15 +1671,16 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, if (totloop == mesh_dst->totloop) { MDisps *mdisps = CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS); CustomData_add_layer(&tmp.ldata, CD_MDISPS, alloctype, mdisps, totloop); + if (alloctype == CD_ASSIGN) { + /* Assign NULL to prevent double-free. */ + CustomData_set_layer(&mesh_dst->ldata, CD_MDISPS, NULL); + } } } /* yes, must be before _and_ after tessellate */ BKE_mesh_update_customdata_pointers(&tmp, false); - /* since 2.65 caller must do! */ - // BKE_mesh_tessface_calc(&tmp); - CustomData_free(&mesh_dst->vdata, mesh_dst->totvert); CustomData_free(&mesh_dst->edata, mesh_dst->totedge); CustomData_free(&mesh_dst->fdata, mesh_dst->totface); diff --git a/source/blender/blenkernel/intern/mesh_evaluate.c b/source/blender/blenkernel/intern/mesh_evaluate.c index eee1b763be5..826094420a7 100644 --- a/source/blender/blenkernel/intern/mesh_evaluate.c +++ b/source/blender/blenkernel/intern/mesh_evaluate.c @@ -1715,7 +1715,8 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, loop_split_generator(NULL, &common_data); } else { - TaskPool *task_pool = BLI_task_pool_create(&common_data, TASK_PRIORITY_HIGH); + TaskPool *task_pool = BLI_task_pool_create( + &common_data, TASK_PRIORITY_HIGH, TASK_ISOLATION_ON); loop_split_generator(task_pool, &common_data); @@ -2351,7 +2352,7 @@ float BKE_mesh_calc_poly_uv_area(const MPoly *mpoly, const MLoopUV *uv_array) } /* finally calculate the area */ - area = area_poly_v2((const float(*)[2])vertexcos, (unsigned int)mpoly->totloop); + area = area_poly_v2(vertexcos, (uint)mpoly->totloop); return area; } @@ -2566,7 +2567,7 @@ bool BKE_mesh_center_median_from_polys(const Mesh *me, float r_cent[3]) const MLoop *mloop = me->mloop; const MVert *mvert = me->mvert; zero_v3(r_cent); - for (mpoly = me->mpoly; i--; mpoly++) { + for (; i--; mpoly++) { int loopend = mpoly->loopstart + mpoly->totloop; for (int j = mpoly->loopstart; j < loopend; j++) { add_v3_v3(r_cent, mvert[mloop[j].v].co); diff --git a/source/blender/blenkernel/intern/mesh_fair.cc b/source/blender/blenkernel/intern/mesh_fair.cc index ac6dd96ed90..2a364b183b2 100644 --- a/source/blender/blenkernel/intern/mesh_fair.cc +++ b/source/blender/blenkernel/intern/mesh_fair.cc @@ -172,7 +172,7 @@ class FairingContext { } /* Early return, nothing to do. */ - if (num_affected_vertices == 0 || num_affected_vertices == totvert_) { + if (ELEM(num_affected_vertices, 0, totvert_)) { return; } diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c index a22b52d68d5..3d30c218fba 100644 --- a/source/blender/blenkernel/intern/mesh_mirror.c +++ b/source/blender/blenkernel/intern/mesh_mirror.c @@ -51,7 +51,7 @@ Mesh *BKE_mesh_mirror_bisect_on_mirror_plane_for_modifier(MirrorModifierData *mm (axis == 1 && mmd->flag & MOD_MIR_BISECT_FLIP_AXIS_Y) || (axis == 2 && mmd->flag & MOD_MIR_BISECT_FLIP_AXIS_Z)); - const float bisect_distance = 0.001f; + const float bisect_distance = mmd->bisect_threshold; Mesh *result; BMesh *bm; @@ -183,6 +183,19 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, if (do_bisect) { copy_v3_v3(plane_co, itmp[3]); copy_v3_v3(plane_no, itmp[axis]); + + /* Account for non-uniform scale in `ob`, see: T87592. */ + float ob_scale[3] = { + len_squared_v3(ob->obmat[0]), + len_squared_v3(ob->obmat[1]), + len_squared_v3(ob->obmat[2]), + }; + /* Scale to avoid precision loss with extreme values. */ + const float ob_scale_max = max_fff(UNPACK3(ob_scale)); + if (LIKELY(ob_scale_max != 0.0f)) { + mul_v3_fl(ob_scale, 1.0f / ob_scale_max); + mul_v3_v3(plane_no, ob_scale); + } } } else if (do_bisect) { diff --git a/source/blender/blenkernel/intern/mesh_remap.c b/source/blender/blenkernel/intern/mesh_remap.c index f3b29171762..58d2a24f15b 100644 --- a/source/blender/blenkernel/intern/mesh_remap.c +++ b/source/blender/blenkernel/intern/mesh_remap.c @@ -2399,8 +2399,7 @@ void BKE_mesh_remap_calc_polys_from_mesh(const int mode, } tot_rays *= tot_rays; - poly_area_2d_inv = area_poly_v2((const float(*)[2])poly_vcos_2d, - (unsigned int)mp->totloop); + poly_area_2d_inv = area_poly_v2(poly_vcos_2d, (uint)mp->totloop); /* In case we have a null-area degenerated poly... */ poly_area_2d_inv = 1.0f / max_ff(poly_area_2d_inv, 1e-9f); diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc new file mode 100644 index 00000000000..91c9951ae89 --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -0,0 +1,158 @@ +/* + * 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. + */ + +#include "BKE_attribute_math.hh" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +namespace blender::bke::mesh_surface_sample { + +static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); + return {looptris, looptris_len}; +} + +template<typename T> +BLI_NOINLINE static void sample_point_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + const Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + + const T v0 = data_in[v0_index]; + const T v1 = data_in[v1_index]; + const T v2 = data_in[v2_index]; + + const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); + data_out[i] = interpolated_value; + } +} + +void sample_point_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_out.size() == bary_coords.size()); + BLI_assert(data_in.size() == mesh.totvert); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_point_attribute<T>( + mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +template<typename T> +BLI_NOINLINE static void sample_corner_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int loop_index_0 = looptri.tri[0]; + const int loop_index_1 = looptri.tri[1]; + const int loop_index_2 = looptri.tri[2]; + + const T v0 = data_in[loop_index_0]; + const T v1 = data_in[loop_index_1]; + const T v2 = data_in[loop_index_2]; + + const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); + data_out[i] = interpolated_value; + } +} + +void sample_corner_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_out.size() == bary_coords.size()); + BLI_assert(data_in.size() == mesh.totloop); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_corner_attribute<T>( + mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +template<typename T> +void sample_face_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : data_out.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const int poly_index = looptri.poly; + data_out[i] = data_in[poly_index]; + } +} + +void sample_face_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_in.size() == mesh.totpoly); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +} // namespace blender::bke::mesh_surface_sample diff --git a/source/blender/blenkernel/intern/mesh_tangent.c b/source/blender/blenkernel/intern/mesh_tangent.c index 2e22e521a13..6a7ff0851f5 100644 --- a/source/blender/blenkernel/intern/mesh_tangent.c +++ b/source/blender/blenkernel/intern/mesh_tangent.c @@ -656,7 +656,7 @@ void BKE_mesh_calc_loop_tangent_ex(const MVert *mvert, /* Calculation */ if (looptri_len != 0) { - TaskPool *task_pool = BLI_task_pool_create(NULL, TASK_PRIORITY_LOW); + TaskPool *task_pool = BLI_task_pool_create(NULL, TASK_PRIORITY_LOW, TASK_ISOLATION_ON); tangent_mask_curr = 0; /* Calculate tangent layers */ diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 34b7c4234ec..e60f0102b9a 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -281,7 +281,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) { @@ -291,7 +291,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)); } @@ -729,7 +729,6 @@ Object *BKE_modifiers_is_deformed_by_armature(Object *ob) ArmatureGpencilModifierData *agmd = NULL; GpencilModifierData *gmd = BKE_gpencil_modifiers_get_virtual_modifierlist( ob, &gpencilvirtualModifierData); - gmd = ob->greasepencil_modifiers.first; /* return the first selected armature, this lets us use multiple armatures */ for (; gmd; gmd = gmd->next) { @@ -749,7 +748,6 @@ Object *BKE_modifiers_is_deformed_by_armature(Object *ob) VirtualModifierData virtualModifierData; ArmatureModifierData *amd = NULL; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - md = ob->modifiers.first; /* return the first selected armature, this lets us use multiple armatures */ for (; md; md = md->next) { diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index 9c2cd03dbc2..0f2f56f4f2b 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -950,7 +950,7 @@ static void movieclip_load_get_size(MovieClip *clip) int width, height; MovieClipUser user = {0}; - user.framenr = 1; + user.framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, 1); BKE_movieclip_get_size(clip, &user, &width, &height); if (width && height) { diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 63b14c30b3c..92e21183acb 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -254,7 +254,7 @@ NlaTrack *BKE_nlatrack_copy(Main *bmain, * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... * flags in BKE_lib_id.h */ -void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int flag) +void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const int flag) { NlaTrack *nlt, *nlt_d; @@ -275,6 +275,54 @@ void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int fl } } +/* Set adt_dest->actstrip to the strip with the same index as adt_source->actstrip. */ +static void update_active_strip(AnimData *adt_dest, + NlaTrack *track_dest, + const AnimData *adt_source, + NlaTrack *track_source) +{ + BLI_assert(BLI_listbase_count(&track_source->strips) == BLI_listbase_count(&track_dest->strips)); + + NlaStrip *strip_dest = track_dest->strips.first; + LISTBASE_FOREACH (NlaStrip *, strip_source, &track_source->strips) { + if (strip_source == adt_source->actstrip) { + adt_dest->actstrip = strip_dest; + } + + strip_dest = strip_dest->next; + } +} + +/* Set adt_dest->act_track to the track with the same index as adt_source->act_track. */ +static void update_active_track(AnimData *adt_dest, const AnimData *adt_source) +{ + BLI_assert(BLI_listbase_count(&adt_source->nla_tracks) == + BLI_listbase_count(&adt_dest->nla_tracks)); + + NlaTrack *track_dest = adt_dest->nla_tracks.first; + LISTBASE_FOREACH (NlaTrack *, track_source, &adt_source->nla_tracks) { + if (track_source == adt_source->act_track) { + adt_dest->act_track = track_dest; + /* Assumption: the active strip is on the active track. */ + update_active_strip(adt_dest, track_dest, adt_source, track_source); + } + + track_dest = track_dest->next; + } +} + +void BKE_nla_tracks_copy_from_adt(Main *bmain, + AnimData *adt_dest, + const AnimData *adt_source, + const int flag) +{ + adt_dest->act_track = NULL; + adt_dest->actstrip = NULL; + + BKE_nla_tracks_copy(bmain, &adt_dest->nla_tracks, &adt_source->nla_tracks, flag); + update_active_track(adt_dest, adt_source); +} + /* Adding ------------------------------------------- */ /* Add a NLA Track to the given AnimData @@ -434,8 +482,10 @@ NlaStrip *BKE_nla_add_soundstrip(Main *bmain, Scene *scene, Speaker *speaker) return strip; } -/** Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). */ +/** + * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of + * `IDTypeInfo` structure). + */ void BKE_nla_strip_foreach_id(NlaStrip *strip, LibraryForeachIDData *data) { BKE_LIB_FOREACHID_PROCESS(data, strip->act, IDWALK_CB_USER); @@ -1380,8 +1430,10 @@ static void nlastrip_fix_resize_overlaps(NlaStrip *strip) } } -/** Recalculate the start and end frames for the strip to match the bounds of its action such that - * the overall NLA animation result is unchanged. */ +/** + * Recalculate the start and end frames for the strip to match the bounds of its action such that + * the overall NLA animation result is unchanged. + */ void BKE_nlastrip_recalculate_bounds_sync_action(NlaStrip *strip) { float prev_actstart; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d06e4030117..1c82218fc65 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: @@ -497,10 +513,16 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) if (node->storage) { /* could be handlerized at some point, now only 1 exception still */ - if ((ntree->type == NTREE_SHADER) && + if ((ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY)) && 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); @@ -778,6 +812,13 @@ static void lib_link_node_socket(BlendLibReader *reader, Library *lib, bNodeSock { IDP_BlendReadLib(reader, sock->prop); + /* This can happen for all socket types when a file is saved in an older version of Blender than + * it was originally created in (T86298). Some socket types still require a default value. The + * default value of those sockets will be created in `ntreeSetTypes`. */ + if (sock->default_value == nullptr) { + return; + } + switch ((eNodeSocketDatatype)sock->type) { case SOCK_OBJECT: { bNodeSocketValueObject *default_value = (bNodeSocketValueObject *)sock->default_value; @@ -795,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: @@ -880,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: @@ -1307,8 +1368,8 @@ void nodeUnregisterType(bNodeType *nt) bool nodeTypeUndefined(bNode *node) { return (node->typeinfo == &NodeTypeUndefined) || - (node->type == NODE_GROUP && node->id && ID_IS_LINKED(node->id) && - (node->id->tag & LIB_TAG_MISSING)); + ((node->type == NODE_GROUP || node->type == NODE_CUSTOM_GROUP) && node->id && + ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING)); } GHashIterator *nodeTypeGetIterator(void) @@ -1364,7 +1425,9 @@ GHashIterator *nodeSocketTypeGetIterator(void) return BLI_ghashIterator_new(nodesockettypes_hash); } -struct bNodeSocket *nodeFindSocket(const bNode *node, int in_out, const char *identifier) +struct bNodeSocket *nodeFindSocket(const bNode *node, + eNodeSocketInOut in_out, + const char *identifier) { const ListBase *sockets = (in_out == SOCK_IN) ? &node->inputs : &node->outputs; LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { @@ -1445,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: @@ -1484,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: @@ -1521,7 +1608,7 @@ void nodeModifySocketType( bNodeSocket *nodeAddSocket(bNodeTree *ntree, bNode *node, - int in_out, + eNodeSocketInOut in_out, const char *idname, const char *identifier, const char *name) @@ -1543,7 +1630,7 @@ bNodeSocket *nodeAddSocket(bNodeTree *ntree, bNodeSocket *nodeInsertSocket(bNodeTree *ntree, bNode *node, - int in_out, + eNodeSocketInOut in_out, const char *idname, bNodeSocket *next_sock, const char *identifier, @@ -1575,6 +1662,8 @@ const char *nodeStaticSocketType(int type, int subtype) return "NodeSocketFloatAngle"; case PROP_TIME: return "NodeSocketFloatTime"; + case PROP_TIME_ABSOLUTE: + return "NodeSocketFloatTimeAbsolute"; case PROP_DISTANCE: return "NodeSocketFloatDistance"; case PROP_NONE: @@ -1627,6 +1716,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; } @@ -1646,6 +1739,8 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype) return "NodeSocketInterfaceFloatAngle"; case PROP_TIME: return "NodeSocketInterfaceFloatTime"; + case PROP_TIME_ABSOLUTE: + return "NodeSocketInterfaceFloatTimeAbsolute"; case PROP_DISTANCE: return "NodeSocketInterfaceFloatDistance"; case PROP_NONE: @@ -1698,13 +1793,17 @@ 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; } bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree, bNode *node, - int in_out, + eNodeSocketInOut in_out, int type, int subtype, const char *identifier, @@ -1724,7 +1823,7 @@ bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree, bNodeSocket *nodeInsertStaticSocket(bNodeTree *ntree, bNode *node, - int in_out, + eNodeSocketInOut in_out, int type, int subtype, bNodeSocket *next_sock, @@ -1998,7 +2097,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type) /* do an extra poll here, because some int types are used * for multiple node types, this helps find the desired type */ - if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree))) { + const char *disabled_hint; + if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) { idname = ntype->idname; break; } @@ -2162,6 +2262,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) @@ -2198,6 +2309,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; } @@ -3087,10 +3202,12 @@ void ntreeSetOutput(bNodeTree *ntree) * might be different for editor or for "real" use... */ } -/** Get address of potential nodetree pointer of given ID. +/** + * Get address of potential node-tree pointer of given ID. * * \warning Using this function directly is potentially dangerous, if you don't know or are not - * sure, please use `ntreeFromID()` instead. */ + * sure, please use `ntreeFromID()` instead. + */ bNodeTree **BKE_ntree_ptr_from_id(ID *id) { switch (GS(id->name)) { @@ -3220,13 +3337,11 @@ void ntreeLocalMerge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) /* ************ NODE TREE INTERFACE *************** */ static bNodeSocket *make_socket_interface(bNodeTree *ntree, - int in_out, + eNodeSocketInOut in_out, const char *idname, const char *name) { bNodeSocketType *stype = nodeSocketTypeFind(idname); - int own_index = ntree->cur_index++; - if (stype == nullptr) { return nullptr; } @@ -3238,7 +3353,7 @@ static bNodeSocket *make_socket_interface(bNodeTree *ntree, sock->type = SOCK_CUSTOM; /* int type undefined by default */ /* assign new unique index */ - own_index = ntree->cur_index++; + const int own_index = ntree->cur_index++; /* use the own_index as socket identifier */ if (in_out == SOCK_IN) { BLI_snprintf(sock->identifier, MAX_NAME, "Input_%d", own_index); @@ -3256,7 +3371,9 @@ static bNodeSocket *make_socket_interface(bNodeTree *ntree, return sock; } -bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, int in_out, const char *identifier) +bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, + eNodeSocketInOut in_out, + const char *identifier) { ListBase *sockets = (in_out == SOCK_IN) ? &ntree->inputs : &ntree->outputs; LISTBASE_FOREACH (bNodeSocket *, iosock, sockets) { @@ -3268,7 +3385,7 @@ bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, int in_out, const char * } bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, - int in_out, + eNodeSocketInOut in_out, const char *idname, const char *name) { @@ -3284,8 +3401,11 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, return iosock; } -bNodeSocket *ntreeInsertSocketInterface( - bNodeTree *ntree, int in_out, const char *idname, bNodeSocket *next_sock, const char *name) +bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, + eNodeSocketInOut in_out, + const char *idname, + bNodeSocket *next_sock, + const char *name) { bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { @@ -3304,7 +3424,7 @@ struct bNodeSocket *ntreeAddSocketInterfaceFromSocket(bNodeTree *ntree, bNodeSocket *from_sock) { bNodeSocket *iosock = ntreeAddSocketInterface( - ntree, from_sock->in_out, from_sock->idname, from_sock->name); + ntree, static_cast<eNodeSocketInOut>(from_sock->in_out), from_sock->idname, from_sock->name); if (iosock) { if (iosock->typeinfo->interface_from_socket) { iosock->typeinfo->interface_from_socket(ntree, iosock, from_node, from_sock); @@ -3319,7 +3439,11 @@ struct bNodeSocket *ntreeInsertSocketInterfaceFromSocket(bNodeTree *ntree, bNodeSocket *from_sock) { bNodeSocket *iosock = ntreeInsertSocketInterface( - ntree, from_sock->in_out, from_sock->idname, next_sock, from_sock->name); + ntree, + static_cast<eNodeSocketInOut>(from_sock->in_out), + from_sock->idname, + next_sock, + from_sock->name); if (iosock) { if (iosock->typeinfo->interface_from_socket) { iosock->typeinfo->interface_from_socket(ntree, iosock, from_node, from_sock); @@ -4205,7 +4329,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) { @@ -4387,15 +4511,17 @@ static void node_type_base_defaults(bNodeType *ntype) } /* allow this node for any tree type */ -static bool node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(ntree)) +static bool node_poll_default(bNodeType *UNUSED(ntype), + bNodeTree *UNUSED(ntree), + const char **UNUSED(disabled_hint)) { return true; } /* use the basic poll function */ -static bool node_poll_instance_default(bNode *node, bNodeTree *ntree) +static bool node_poll_instance_default(bNode *node, bNodeTree *ntree, const char **disabled_hint) { - return node->typeinfo->poll(node->typeinfo, ntree); + return node->typeinfo->poll(node->typeinfo, ntree, disabled_hint); } /* NOLINTNEXTLINE: readability-function-size */ @@ -4614,7 +4740,9 @@ void node_type_internal_links(bNodeType *ntype, /* callbacks for undefined types */ -static bool node_undefined_poll(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(nodetree)) +static bool node_undefined_poll(bNodeType *UNUSED(ntype), + bNodeTree *UNUSED(nodetree), + const char **UNUSED(r_disabled_hint)) { /* this type can not be added deliberately, it's just a placeholder */ return false; @@ -4693,6 +4821,7 @@ static void registerCompositNodes() register_node_type_cmp_defocus(); register_node_type_cmp_sunbeams(); register_node_type_cmp_denoise(); + register_node_type_cmp_antialiasing(); register_node_type_cmp_valtorgb(); register_node_type_cmp_rgbtobw(); @@ -4903,31 +5032,46 @@ static void registerGeometryNodes() register_node_type_geo_group(); register_node_type_geo_align_rotation_to_vector(); + register_node_type_geo_attribute_clamp(); register_node_type_geo_attribute_color_ramp(); 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(); register_node_type_geo_attribute_mix(); register_node_type_geo_attribute_proximity(); register_node_type_geo_attribute_randomize(); 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_convex_hull(); + register_node_type_geo_curve_length(); + register_node_type_geo_curve_to_mesh(); + register_node_type_geo_curve_resample(); + register_node_type_geo_delete_geometry(); 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_material_replace(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); register_node_type_geo_mesh_primitive_cylinder(); + register_node_type_geo_mesh_primitive_grid(); register_node_type_geo_mesh_primitive_ico_sphere(); register_node_type_geo_mesh_primitive_line(); - register_node_type_geo_mesh_primitive_plane(); register_node_type_geo_mesh_primitive_uv_sphere(); + register_node_type_geo_mesh_to_curve(); register_node_type_geo_object_info(); register_node_type_geo_point_distribute(); register_node_type_geo_point_instance(); @@ -4937,8 +5081,10 @@ static void registerGeometryNodes() register_node_type_geo_point_translate(); register_node_type_geo_points_to_volume(); register_node_type_geo_sample_texture(); + register_node_type_geo_select_by_material(); register_node_type_geo_subdivide(); register_node_type_geo_subdivision_surface(); + register_node_type_geo_switch(); register_node_type_geo_transform(); register_node_type_geo_triangulate(); register_node_type_geo_volume_to_mesh(); diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc index f2a152ac00d..e5e9f00c7c3 100644 --- a/source/blender/blenkernel/intern/node_ui_storage.cc +++ b/source/blender/blenkernel/intern/node_ui_storage.cc @@ -39,7 +39,7 @@ using blender::Vector; * bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */ static std::mutex global_ui_storage_mutex; -static void ui_storage_ensure(bNodeTree &ntree) +static NodeTreeUIStorage &ui_storage_ensure(bNodeTree &ntree) { /* As an optimization, only acquire a lock if the UI storage doesn't exist, * because it only needs to be allocated once for every node tree. */ @@ -50,6 +50,7 @@ static void ui_storage_ensure(bNodeTree &ntree) ntree.ui_storage = new NodeTreeUIStorage(); } } + return *ntree.ui_storage; } const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C, @@ -90,7 +91,7 @@ void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree, { NodeTreeUIStorage *ui_storage = ntree.ui_storage; if (ui_storage != nullptr) { - std::lock_guard<std::mutex> lock(ui_storage->context_map_mutex); + std::lock_guard<std::mutex> lock(ui_storage->mutex); ui_storage->context_map.remove(context); } } @@ -126,20 +127,14 @@ static void node_error_message_log(bNodeTree &ntree, } } -static NodeUIStorage &node_ui_storage_ensure(bNodeTree &ntree, +static NodeUIStorage &node_ui_storage_ensure(NodeTreeUIStorage &locked_ui_storage, const NodeTreeEvaluationContext &context, const bNode &node) { - ui_storage_ensure(ntree); - NodeTreeUIStorage &ui_storage = *ntree.ui_storage; - - std::lock_guard<std::mutex> lock(ui_storage.context_map_mutex); Map<std::string, NodeUIStorage> &node_tree_ui_storage = - ui_storage.context_map.lookup_or_add_default(context); - + locked_ui_storage.context_map.lookup_or_add_default(context); NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as( StringRef(node.name)); - return node_ui_storage; } @@ -149,9 +144,12 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree, const NodeWarningType type, std::string message) { + NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree); + std::lock_guard lock{ui_storage.mutex}; + node_error_message_log(ntree, node, message, type); - NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node); node_ui_storage.warnings.append({type, std::move(message)}); } @@ -162,7 +160,10 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree, const AttributeDomain domain, const CustomDataType data_type) { - NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); - node_ui_storage.attribute_hints.add_as(attribute_name, - AvailableAttributeInfo{domain, data_type}); + NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree); + std::lock_guard lock{ui_storage.mutex}; + + NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node); + 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 b07c4b22c39..b73f6a5b78c 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -311,8 +311,8 @@ static void object_free_data(ID *id) /* Free runtime curves data. */ if (ob->runtime.curve_cache) { BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); - if (ob->runtime.curve_cache->path) { - free_path(ob->runtime.curve_cache->path); + if (ob->runtime.curve_cache->anim_path_accum_length) { + MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length); } MEM_freeN(ob->runtime.curve_cache); ob->runtime.curve_cache = NULL; @@ -1188,8 +1188,8 @@ void BKE_object_free_curve_cache(Object *ob) if (ob->runtime.curve_cache) { BKE_displist_free(&ob->runtime.curve_cache->disp); BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); - if (ob->runtime.curve_cache->path) { - free_path(ob->runtime.curve_cache->path); + if (ob->runtime.curve_cache->anim_path_accum_length) { + MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length); } BKE_nurbList_free(&ob->runtime.curve_cache->deformed_nurbs); MEM_freeN(ob->runtime.curve_cache); @@ -1332,12 +1332,9 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) if (ob->type == OB_HAIR) { return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); } - if (ob->type == OB_POINTCLOUD) { + if (ELEM(ob->type, OB_POINTCLOUD, OB_VOLUME)) { return (mti->modifyGeometrySet != NULL); } - if (ob->type == OB_VOLUME) { - return (mti->modifyVolume != NULL); - } if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { if (ob->type == OB_LATTICE && (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly) == 0) { return false; @@ -1359,8 +1356,9 @@ static bool object_modifier_type_copy_check(ModifierType md_type) return !ELEM(md_type, eModifierType_Hook, eModifierType_Collision); } -/** Find a `psys` matching given `psys_src` in `ob_dst` (i.e. sharing the same ParticleSettings - * ID), or add one, and return valid `psys` from `ob_dst`. +/** + * Find a `psys` matching given `psys_src` in `ob_dst` (i.e. sharing the same ParticleSettings ID), + * or add one, and return valid `psys` from `ob_dst`. * * \note Order handling is fairly weak here. This code assumes that it is called **before** the * modifier using the psys is actually copied, and that this copied modifier will be added at the @@ -1392,7 +1390,8 @@ static ParticleSystem *object_copy_modifier_particle_system_ensure(Main *bmain, return psys_dst; } -/** Copy a single modifier. +/** + * Copy a single modifier. * * \note **Do not** use this function to copy a whole modifier stack (see note below too). Use * `BKE_object_modifier_stack_copy` instead. @@ -1400,7 +1399,8 @@ static ParticleSystem *object_copy_modifier_particle_system_ensure(Main *bmain, * \note Complex modifiers relaying on other data (like e.g. dynamic paint or fluid using particle * systems) are not always 100% 'correctly' copied here, since we have to use heuristics to decide * which particle system to use or add in `ob_dst`, and it's placement in the stack, etc. If used - * more than once, this function should preferably be called in stack order. */ + * more than once, this function should preferably be called in stack order. + */ bool BKE_object_copy_modifier( Main *bmain, Scene *scene, Object *ob_dst, const Object *ob_src, ModifierData *md_src) { @@ -1500,10 +1500,12 @@ bool BKE_object_copy_modifier( return true; } -/** Copy a single GPencil modifier. +/** + * Copy a single GPencil modifier. * * \note **Do not** use this function to copy a whole modifier stack. Use - * `BKE_object_modifier_stack_copy` instead. */ + * `BKE_object_modifier_stack_copy` instead. + */ bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, GpencilModifierData *gmd_src) { BLI_assert(ob_dst->type == OB_GPENCIL); @@ -1756,6 +1758,10 @@ void BKE_object_free_derived_caches(Object *ob) BKE_geometry_set_free(ob->runtime.geometry_set_eval); ob->runtime.geometry_set_eval = NULL; } + if (ob->runtime.geometry_set_previews != NULL) { + BLI_ghash_free(ob->runtime.geometry_set_previews, NULL, (GHashValFreeFP)BKE_geometry_set_free); + ob->runtime.geometry_set_previews = NULL; + } } void BKE_object_free_caches(Object *object) @@ -1806,6 +1812,24 @@ void BKE_object_free_caches(Object *object) } } +/* Can be called from multiple threads. */ +void BKE_object_preview_geometry_set_add(Object *ob, + const uint64_t key, + struct GeometrySet *geometry_set) +{ + static ThreadMutex mutex = BLI_MUTEX_INITIALIZER; + BLI_mutex_lock(&mutex); + if (ob->runtime.geometry_set_previews == NULL) { + ob->runtime.geometry_set_previews = BLI_ghash_int_new(__func__); + } + BLI_ghash_reinsert(ob->runtime.geometry_set_previews, + POINTER_FROM_UINT(key), + geometry_set, + NULL, + (GHashValFreeFP)BKE_geometry_set_free); + BLI_mutex_unlock(&mutex); +} + /** * Actual check for internal data, not context or flags. */ @@ -2265,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) { @@ -2306,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); @@ -2345,7 +2369,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f BLI_listbase_clear(&psysn->pathcachebufs); BLI_listbase_clear(&psysn->childcachebufs); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* XXX Disabled, fails when evaluating depsgraph after copying ID with no main for preview * creation. */ // BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0); @@ -2598,8 +2622,8 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src) * * \note This function does not do any remapping to new IDs, caller must do it * (\a #BKE_libblock_relink_to_newid()). - * \note Caller MUST free \a newid pointers itself (#BKE_main_id_clear_newpoins()) and call updates - * of DEG too (#DAG_relations_tag_update()). + * \note Caller MUST free \a newid pointers itself (#BKE_main_id_newptr_and_tag_clear()) and call + * updates of DEG too (#DAG_relations_tag_update()). */ Object *BKE_object_duplicate(Main *bmain, Object *ob, @@ -2609,8 +2633,7 @@ Object *BKE_object_duplicate(Main *bmain, const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ if (ID_IS_LINKED(ob)) { @@ -2749,8 +2772,7 @@ Object *BKE_object_duplicate(Main *bmain, #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); } if (obn->type == OB_ARMATURE) { @@ -3244,7 +3266,7 @@ static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4]) if (par->runtime.curve_cache == NULL) { return false; } - if (par->runtime.curve_cache->path == NULL) { + if (par->runtime.curve_cache->anim_path_accum_length == NULL) { return false; } @@ -3260,12 +3282,16 @@ static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4]) else { ctime = cu->ctime; } - CLAMP(ctime, 0.0f, 1.0f); + + if (cu->flag & CU_PATH_CLAMP) { + CLAMP(ctime, 0.0f, 1.0f); + } unit_m4(r_mat); /* vec: 4 items! */ - if (where_on_path(par, ctime, vec, dir, (cu->flag & CU_FOLLOW) ? quat : NULL, &radius, NULL)) { + if (BKE_where_on_path( + par, ctime, vec, dir, (cu->flag & CU_FOLLOW) ? quat : NULL, &radius, NULL)) { if (cu->flag & CU_FOLLOW) { quat_apply_track(quat, ob->trackflag, ob->upflag); normalize_qt(quat); @@ -4108,7 +4134,7 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph, const bool use_hidden) { bool ok = false; - if ((ob->transflag & OB_DUPLI) == 0) { + if ((ob->transflag & OB_DUPLI) == 0 && ob->runtime.geometry_set_eval == NULL) { return ok; } @@ -4319,7 +4345,7 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph, } /* Speed optimization for animation lookups. */ if (ob->pose != NULL) { - BKE_pose_channels_hash_make(ob->pose); + BKE_pose_channels_hash_ensure(ob->pose); if (ob->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(ob->pose); } @@ -4353,8 +4379,6 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph, BKE_object_handle_data_update(depsgraph, scene, ob); } - ob->id.recalc &= ID_RECALC_ALL; - object_handle_update_proxy(depsgraph, scene, ob, do_proxy_update); } @@ -4474,6 +4498,37 @@ Mesh *BKE_object_get_original_mesh(Object *object) return result; } +Lattice *BKE_object_get_lattice(const Object *object) +{ + ID *data = object->data; + if (data == NULL || GS(data->name) != ID_LT) { + return NULL; + } + + Lattice *lt = (Lattice *)data; + if (lt->editlatt) { + return lt->editlatt->latt; + } + + return lt; +} + +Lattice *BKE_object_get_evaluated_lattice(const Object *object) +{ + ID *data_eval = object->runtime.data_eval; + + if (data_eval == NULL || GS(data_eval->name) != ID_LT) { + return NULL; + } + + Lattice *lt_eval = (Lattice *)data_eval; + if (lt_eval->editlatt) { + return lt_eval->editlatt->latt; + } + + return lt_eval; +} + static int pc_cmp(const void *a, const void *b) { const LinkData *ad = a, *bd = b; @@ -5061,6 +5116,20 @@ void BKE_object_runtime_reset_on_copy(Object *object, const int UNUSED(flag)) } /** + * The function frees memory used by the runtime data, but not the runtime field itself. + * + * All runtime data is cleared to ensure it's not used again, + * in keeping with other `_free_data(..)` functions. + */ +void BKE_object_runtime_free_data(Object *object) +{ + /* Currently this is all that's needed. */ + BKE_object_free_derived_caches(object); + + BKE_object_runtime_reset(object); +} + +/** * Find an associated armature object. */ static Object *obrel_armature_find(Object *ob) @@ -5601,7 +5670,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/object_dupli.c b/source/blender/blenkernel/intern/object_dupli.cc index 632e7519f05..768fa9373c1 100644 --- a/source/blender/blenkernel/intern/object_dupli.c +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -21,18 +21,22 @@ * \ingroup bke */ -#include <limits.h> -#include <stddef.h> -#include <stdlib.h> +#include <climits> +#include <cstddef> +#include <cstdlib> #include "MEM_guardedalloc.h" #include "BLI_listbase.h" #include "BLI_string_utf8.h" -#include "BLI_alloca.h" +#include "BLI_array.hh" +#include "BLI_float3.hh" +#include "BLI_float4x4.hh" #include "BLI_math.h" #include "BLI_rand.h" +#include "BLI_span.hh" +#include "BLI_vector.hh" #include "DNA_anim_types.h" #include "DNA_collection_types.h" @@ -48,6 +52,7 @@ #include "BKE_editmesh_cache.h" #include "BKE_font.h" #include "BKE_geometry_set.h" +#include "BKE_geometry_set.hh" #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_lattice.h" @@ -65,11 +70,17 @@ #include "BLI_hash.h" #include "BLI_strict_flags.h" +using blender::Array; +using blender::float3; +using blender::float4x4; +using blender::Span; +using blender::Vector; + /* -------------------------------------------------------------------- */ /** \name Internal Duplicate Context * \{ */ -typedef struct DupliContext { +struct DupliContext { Depsgraph *depsgraph; /** XXX child objects are selected from this group if set, could be nicer. */ Collection *collection; @@ -81,6 +92,13 @@ typedef struct DupliContext { Object *object; float space_mat[4][4]; + /** + * A stack that contains all the "parent" objects of a particular instance when recursive + * instancing is used. This is used to prevent objects from instancing themselves accidentally. + * Use a vector instead of a stack because we want to use the #contains method. + */ + Vector<Object *> *instance_stack; + int persistent_id[MAX_DUPLI_RECUR]; int level; @@ -88,12 +106,12 @@ typedef struct DupliContext { /** Result containers. */ ListBase *duplilist; /* Legacy doubly-linked list. */ -} DupliContext; +}; -typedef struct DupliGenerator { +struct DupliGenerator { short type; /* Dupli Type, see members of #OB_DUPLI. */ void (*make_duplis)(const DupliContext *ctx); -} DupliGenerator; +}; static const DupliGenerator *get_dupli_generator(const DupliContext *ctx); @@ -104,15 +122,17 @@ static void init_context(DupliContext *r_ctx, Depsgraph *depsgraph, Scene *scene, Object *ob, - const float space_mat[4][4]) + const float space_mat[4][4], + Vector<Object *> &instance_stack) { r_ctx->depsgraph = depsgraph; r_ctx->scene = scene; r_ctx->view_layer = DEG_get_evaluated_view_layer(depsgraph); - r_ctx->collection = NULL; + r_ctx->collection = nullptr; r_ctx->object = ob; r_ctx->obedit = OBEDIT_FROM_OBACT(ob); + r_ctx->instance_stack = &instance_stack; if (space_mat) { copy_m4_m4(r_ctx->space_mat, space_mat); } @@ -123,7 +143,7 @@ static void init_context(DupliContext *r_ctx, r_ctx->gen = get_dupli_generator(r_ctx); - r_ctx->duplilist = NULL; + r_ctx->duplilist = nullptr; } /** @@ -141,6 +161,7 @@ static void copy_dupli_context( } r_ctx->object = ob; + r_ctx->instance_stack = ctx->instance_stack; if (mat) { mul_m4_m4m4(r_ctx->space_mat, (float(*)[4])ctx->space_mat, mat); } @@ -165,11 +186,11 @@ static DupliObject *make_dupli(const DupliContext *ctx, /* Add a #DupliObject instance to the result container. */ if (ctx->duplilist) { - dob = MEM_callocN(sizeof(DupliObject), "dupli object"); + dob = (DupliObject *)MEM_callocN(sizeof(DupliObject), "dupli object"); BLI_addtail(ctx->duplilist, dob); } else { - return NULL; + return nullptr; } dob->ob = ob; @@ -226,12 +247,19 @@ static void make_recursive_duplis(const DupliContext *ctx, const float space_mat[4][4], int index) { + if (ctx->instance_stack->contains(ob)) { + /* Avoid recursive instances. */ + printf("Warning: '%s' object is trying to instance itself.\n", ob->id.name + 2); + return; + } /* Simple preventing of too deep nested collections with #MAX_DUPLI_RECUR. */ if (ctx->level < MAX_DUPLI_RECUR) { DupliContext rctx; copy_dupli_context(&rctx, ctx, ob, space_mat, index); if (rctx.gen) { + ctx->instance_stack->append(ob); rctx.gen->make_duplis(&rctx); + ctx->instance_stack->remove_last(); } } } @@ -242,7 +270,7 @@ static void make_recursive_duplis(const DupliContext *ctx, /** \name Internal Child Duplicates (Used by Other Functions) * \{ */ -typedef void (*MakeChildDuplisFunc)(const DupliContext *ctx, void *userdata, Object *child); +using MakeChildDuplisFunc = void (*)(const DupliContext *ctx, void *userdata, Object *child); static bool is_child(const Object *ob, const Object *parent) { @@ -270,7 +298,7 @@ static void make_child_duplis(const DupliContext *ctx, FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (ctx->collection, ob, mode) { if ((ob != ctx->obedit) && is_child(ob, parent)) { DupliContext pctx; - copy_dupli_context(&pctx, ctx, ctx->object, NULL, _base_id); + copy_dupli_context(&pctx, ctx, ctx->object, nullptr, _base_id); /* Meta-balls have a different dupli handling. */ if (ob->type != OB_MBALL) { @@ -282,13 +310,13 @@ static void make_child_duplis(const DupliContext *ctx, FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; } else { - int baseid = 0; + int baseid; ViewLayer *view_layer = ctx->view_layer; - for (Base *base = view_layer->object_bases.first; base; base = base->next, baseid++) { + LISTBASE_FOREACH_INDEX (Base *, base, &view_layer->object_bases, baseid) { Object *ob = base->object; if ((ob != ctx->obedit) && is_child(ob, parent)) { DupliContext pctx; - copy_dupli_context(&pctx, ctx, ctx->object, NULL, baseid); + copy_dupli_context(&pctx, ctx, ctx->object, nullptr, baseid); /* Meta-balls have a different dupli-handling. */ if (ob->type != OB_MBALL) { @@ -316,30 +344,30 @@ static Mesh *mesh_data_from_duplicator_object(Object *ob, BMEditMesh *em = BKE_editmesh_from_object(ob); Mesh *me_eval; - *r_em = NULL; - *r_vert_coords = NULL; - if (r_vert_normals != NULL) { - *r_vert_normals = NULL; + *r_em = nullptr; + *r_vert_coords = nullptr; + if (r_vert_normals != nullptr) { + *r_vert_normals = nullptr; } /* We do not need any render-specific handling anymore, depsgraph takes care of that. */ /* NOTE: Do direct access to the evaluated mesh: this function is used * during meta balls evaluation. But even without those all the objects * which are needed for correct instancing are already evaluated. */ - if (em != NULL) { + if (em != nullptr) { /* Note that this will only show deformation if #eModifierMode_OnCage is enabled. * We could change this but it matches 2.7x behavior. */ me_eval = em->mesh_eval_cage; - if ((me_eval == NULL) || (me_eval->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH)) { - EditMeshData *emd = me_eval ? me_eval->runtime.edit_data : NULL; + if ((me_eval == nullptr) || (me_eval->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH)) { + EditMeshData *emd = me_eval ? me_eval->runtime.edit_data : nullptr; /* Only assign edit-mesh in the case we can't use `me_eval`. */ *r_em = em; - me_eval = NULL; + me_eval = nullptr; - if ((emd != NULL) && (emd->vertexCos != NULL)) { + if ((emd != nullptr) && (emd->vertexCos != nullptr)) { *r_vert_coords = emd->vertexCos; - if (r_vert_normals != NULL) { + if (r_vert_normals != nullptr) { BKE_editmesh_cache_ensure_vert_normals(em, emd); *r_vert_normals = emd->vertexNos; } @@ -364,7 +392,7 @@ static void make_duplis_collection(const DupliContext *ctx) Collection *collection; float collection_mat[4][4]; - if (ob->instance_collection == NULL) { + if (ob->instance_collection == nullptr) { return; } collection = ob->instance_collection; @@ -404,7 +432,7 @@ static const DupliGenerator gen_dupli_collection = { * \{ */ /** Values shared between different mesh types. */ -typedef struct VertexDupliData_Params { +struct VertexDupliData_Params { /** * It's important we use this context instead of the `ctx` passed into #make_child_duplis * since these won't match in the case of recursion. @@ -412,23 +440,23 @@ typedef struct VertexDupliData_Params { const DupliContext *ctx; bool use_rotation; -} VertexDupliData_Params; +}; -typedef struct VertexDupliData_Mesh { +struct VertexDupliData_Mesh { VertexDupliData_Params params; int totvert; const MVert *mvert; const float (*orco)[3]; -} VertexDupliData_Mesh; +}; -typedef struct VertexDupliData_EditMesh { +struct VertexDupliData_EditMesh { VertexDupliData_Params params; BMEditMesh *em; - /* Can be NULL. */ + /* Can be nullptr. */ const float (*vert_coords)[3]; const float (*vert_normals)[3]; @@ -440,7 +468,7 @@ typedef struct VertexDupliData_EditMesh { * edit-mesh to be converted into a mesh. */ bool has_orco; -} VertexDupliData_EditMesh; +}; /** * \param no: The direction, @@ -505,7 +533,7 @@ static void make_child_duplis_verts_from_mesh(const DupliContext *ctx, void *userdata, Object *inst_ob) { - VertexDupliData_Mesh *vdd = userdata; + VertexDupliData_Mesh *vdd = (VertexDupliData_Mesh *)userdata; const bool use_rotation = vdd->params.use_rotation; const MVert *mvert = vdd->mvert; @@ -519,7 +547,8 @@ static void make_child_duplis_verts_from_mesh(const DupliContext *ctx, const MVert *mv = mvert; for (int i = 0; i < totvert; i++, mv++) { const float *co = mv->co; - const float no[3] = {UNPACK3(mv->no)}; + float no[3]; + normal_short_to_float_v3(no, mv->no); DupliObject *dob = vertex_dupli(vdd->params.ctx, inst_ob, child_imat, i, co, no, use_rotation); if (vdd->orco) { copy_v3_v3(dob->orco, vdd->orco[i]); @@ -531,7 +560,7 @@ static void make_child_duplis_verts_from_editmesh(const DupliContext *ctx, void *userdata, Object *inst_ob) { - VertexDupliData_EditMesh *vdd = userdata; + VertexDupliData_EditMesh *vdd = (VertexDupliData_EditMesh *)userdata; BMEditMesh *em = vdd->em; const bool use_rotation = vdd->params.use_rotation; @@ -549,9 +578,9 @@ static void make_child_duplis_verts_from_editmesh(const DupliContext *ctx, BM_ITER_MESH_INDEX (v, &iter, em->bm, BM_VERTS_OF_MESH, i) { const float *co, *no; - if (vert_coords != NULL) { + if (vert_coords != nullptr) { co = vert_coords[i]; - no = vert_normals ? vert_normals[i] : NULL; + no = vert_normals ? vert_normals[i] : nullptr; } else { co = v->co; @@ -571,37 +600,34 @@ static void make_duplis_verts(const DupliContext *ctx) const bool use_rotation = parent->transflag & OB_DUPLIROT; /* Gather mesh info. */ - BMEditMesh *em = NULL; - const float(*vert_coords)[3] = NULL; - const float(*vert_normals)[3] = NULL; + BMEditMesh *em = nullptr; + const float(*vert_coords)[3] = nullptr; + const float(*vert_normals)[3] = nullptr; Mesh *me_eval = mesh_data_from_duplicator_object( - parent, &em, &vert_coords, use_rotation ? &vert_normals : NULL); - if (em == NULL && me_eval == NULL) { + parent, &em, &vert_coords, use_rotation ? &vert_normals : nullptr); + if (em == nullptr && me_eval == nullptr) { return; } - VertexDupliData_Params vdd_params = { - .ctx = ctx, - .use_rotation = use_rotation, - }; + VertexDupliData_Params vdd_params{ctx, use_rotation}; + + if (em != nullptr) { + VertexDupliData_EditMesh vdd{}; + vdd.params = vdd_params; + vdd.em = em; + vdd.vert_coords = vert_coords; + vdd.vert_normals = vert_normals; + vdd.has_orco = (vert_coords != nullptr); - if (em != NULL) { - VertexDupliData_EditMesh vdd = { - .params = vdd_params, - .em = em, - .vert_coords = vert_coords, - .vert_normals = vert_normals, - .has_orco = (vert_coords != NULL), - }; make_child_duplis(ctx, &vdd, make_child_duplis_verts_from_editmesh); } else { - VertexDupliData_Mesh vdd = { - .params = vdd_params, - .totvert = me_eval->totvert, - .mvert = me_eval->mvert, - .orco = CustomData_get_layer(&me_eval->vdata, CD_ORCO), - }; + VertexDupliData_Mesh vdd{}; + vdd.params = vdd_params; + vdd.totvert = me_eval->totvert; + vdd.mvert = me_eval->mvert; + vdd.orco = (const float(*)[3])CustomData_get_layer(&me_eval->vdata, CD_ORCO); + make_child_duplis(ctx, &vdd, make_child_duplis_verts_from_mesh); } } @@ -620,34 +646,31 @@ static const DupliGenerator gen_dupli_verts = { static Object *find_family_object( Main *bmain, const char *family, size_t family_len, unsigned int ch, GHash *family_gh) { - Object **ob_pt; - Object *ob; void *ch_key = POINTER_FROM_UINT(ch); + Object **ob_pt; if ((ob_pt = (Object **)BLI_ghash_lookup_p(family_gh, ch_key))) { - ob = *ob_pt; + return *ob_pt; } - else { - char ch_utf8[7]; - size_t ch_utf8_len; - ch_utf8_len = BLI_str_utf8_from_unicode(ch, ch_utf8); - ch_utf8[ch_utf8_len] = '\0'; - ch_utf8_len += 1; /* Compare with null terminator. */ + char ch_utf8[7]; + size_t ch_utf8_len; - for (ob = bmain->objects.first; ob; ob = ob->id.next) { - if (STREQLEN(ob->id.name + 2 + family_len, ch_utf8, ch_utf8_len)) { - if (STREQLEN(ob->id.name + 2, family, family_len)) { - break; - } + ch_utf8_len = BLI_str_utf8_from_unicode(ch, ch_utf8); + ch_utf8[ch_utf8_len] = '\0'; + ch_utf8_len += 1; /* Compare with null terminator. */ + + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (STREQLEN(ob->id.name + 2 + family_len, ch_utf8, ch_utf8_len)) { + if (STREQLEN(ob->id.name + 2, family, family_len)) { + /* Inserted value can be nullptr, just to save searches in future. */ + BLI_ghash_insert(family_gh, ch_key, ob); + return ob; } } - - /* Inserted value can be NULL, just to save searches in future. */ - BLI_ghash_insert(family_gh, ch_key, ob); } - return ob; + return nullptr; } static void make_duplis_font(const DupliContext *ctx) @@ -656,11 +679,11 @@ static void make_duplis_font(const DupliContext *ctx) GHash *family_gh; Object *ob; Curve *cu; - struct CharTrans *ct, *chartransdata = NULL; + struct CharTrans *ct, *chartransdata = nullptr; float vec[3], obmat[4][4], pmat[4][4], fsize, xof, yof; int text_len, a; size_t family_len; - const char32_t *text = NULL; + const char32_t *text = nullptr; bool text_free = false; /* Font dupli-verts not supported inside collections. */ @@ -673,13 +696,13 @@ static void make_duplis_font(const DupliContext *ctx) /* In `par` the family name is stored, use this to find the other objects. */ BKE_vfont_to_curve_ex( - par, par->data, FO_DUPLI, NULL, &text, &text_len, &text_free, &chartransdata); + par, (Curve *)par->data, FO_DUPLI, nullptr, &text, &text_len, &text_free, &chartransdata); - if (text == NULL || chartransdata == NULL) { + if (text == nullptr || chartransdata == nullptr) { return; } - cu = par->data; + cu = (Curve *)par->data; fsize = cu->fsize; xof = cu->xof; yof = cu->yof; @@ -733,7 +756,7 @@ static void make_duplis_font(const DupliContext *ctx) MEM_freeN((void *)text); } - BLI_ghash_free(family_gh, NULL, NULL); + BLI_ghash_free(family_gh, nullptr, nullptr); MEM_freeN(chartransdata); } @@ -754,11 +777,11 @@ static void make_child_duplis_pointcloud(const DupliContext *ctx, Object *child) { const Object *parent = ctx->object; - const PointCloud *pointcloud = parent->data; + const PointCloud *pointcloud = (PointCloud *)parent->data; const float(*co)[3] = pointcloud->co; const float *radius = pointcloud->radius; - const float(*rotation)[4] = NULL; /* TODO: add optional rotation attribute. */ - const float(*orco)[3] = NULL; /* TODO: add optional texture coordinate attribute. */ + const float(*rotation)[4] = nullptr; /* TODO: add optional rotation attribute. */ + const float(*orco)[3] = nullptr; /* TODO: add optional texture coordinate attribute. */ /* Relative transform from parent to child space. */ float child_imat[4][4]; @@ -797,7 +820,7 @@ static void make_child_duplis_pointcloud(const DupliContext *ctx, static void make_duplis_pointcloud(const DupliContext *ctx) { - make_child_duplis(ctx, NULL, make_child_duplis_pointcloud); + make_child_duplis(ctx, nullptr, make_child_duplis_pointcloud); } static const DupliGenerator gen_dupli_verts_pointcloud = { @@ -813,43 +836,44 @@ static const DupliGenerator gen_dupli_verts_pointcloud = { static void make_duplis_instances_component(const DupliContext *ctx) { - float(*instance_offset_matrices)[4][4]; - InstancedData *instanced_data; - const int *almost_unique_ids; - const int amount = BKE_geometry_set_instances(ctx->object->runtime.geometry_set_eval, - &instance_offset_matrices, - &almost_unique_ids, - &instanced_data); + const InstancesComponent *component = + ctx->object->runtime.geometry_set_eval->get_component_for_read<InstancesComponent>(); + if (component == nullptr) { + return; + } - for (int i = 0; i < amount; i++) { - InstancedData *data = &instanced_data[i]; + Span<float4x4> instance_offset_matrices = component->instance_transforms(); + Span<int> instance_reference_handles = component->instance_reference_handles(); + Span<int> almost_unique_ids = component->almost_unique_ids(); + Span<InstanceReference> references = component->references(); + for (int64_t i : instance_offset_matrices.index_range()) { + const InstanceReference &reference = references[instance_reference_handles[i]]; const int id = almost_unique_ids[i]; - if (data->type == INSTANCE_DATA_TYPE_OBJECT) { - Object *object = data->data.object; - if (object != NULL) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); float matrix[4][4]; - mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i]); - make_dupli(ctx, object, matrix, id); + mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values); + make_dupli(ctx, &object, matrix, id); float space_matrix[4][4]; - mul_m4_m4m4(space_matrix, instance_offset_matrices[i], object->imat); + mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat); mul_m4_m4_pre(space_matrix, ctx->object->obmat); - make_recursive_duplis(ctx, object, space_matrix, id); + make_recursive_duplis(ctx, &object, space_matrix, id); + break; } - } - else if (data->type == INSTANCE_DATA_TYPE_COLLECTION) { - Collection *collection = data->data.collection; - if (collection != NULL) { + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); float collection_matrix[4][4]; unit_m4(collection_matrix); - sub_v3_v3(collection_matrix[3], collection->instance_offset); - mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i]); + sub_v3_v3(collection_matrix[3], collection.instance_offset); + mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values); mul_m4_m4_pre(collection_matrix, ctx->object->obmat); eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); - FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (collection, object, mode) { + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) { if (object == ctx->object) { continue; } @@ -861,6 +885,10 @@ static void make_duplis_instances_component(const DupliContext *ctx) make_recursive_duplis(ctx, object, collection_matrix, id); } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; + break; + } + case InstanceReference::Type::None: { + break; } } } @@ -878,7 +906,7 @@ static const DupliGenerator gen_dupli_instances_component = { * \{ */ /** Values shared between different mesh types. */ -typedef struct FaceDupliData_Params { +struct FaceDupliData_Params { /** * It's important we use this context instead of the `ctx` passed into #make_child_duplis * since these won't match in the case of recursion. @@ -886,9 +914,9 @@ typedef struct FaceDupliData_Params { const DupliContext *ctx; bool use_scale; -} FaceDupliData_Params; +}; -typedef struct FaceDupliData_Mesh { +struct FaceDupliData_Mesh { FaceDupliData_Params params; int totface; @@ -897,53 +925,50 @@ typedef struct FaceDupliData_Mesh { const MVert *mvert; const float (*orco)[3]; const MLoopUV *mloopuv; -} FaceDupliData_Mesh; +}; -typedef struct FaceDupliData_EditMesh { +struct FaceDupliData_EditMesh { FaceDupliData_Params params; BMEditMesh *em; bool has_orco, has_uvs; int cd_loop_uv_offset; - /* Can be NULL. */ + /* Can be nullptr. */ const float (*vert_coords)[3]; -} FaceDupliData_EditMesh; +}; -static void get_dupliface_transform_from_coords(const float coords[][3], - const int coords_len, +static void get_dupliface_transform_from_coords(Span<float3> coords, const bool use_scale, const float scale_fac, float r_mat[4][4]) { - float loc[3], quat[4], scale, size[3]; - /* Location. */ - { - const float w = 1.0f / (float)coords_len; - zero_v3(loc); - for (int i = 0; i < coords_len; i++) { - madd_v3_v3fl(loc, coords[i], w); - } + float3 location(0); + for (const float3 &coord : coords) { + location += coord; } + location *= 1.0f / (float)coords.size(); + /* Rotation. */ - { - float f_no[3]; - cross_poly_v3(f_no, coords, (uint)coords_len); - normalize_v3(f_no); - tri_to_quat_ex(quat, coords[0], coords[1], coords[2], f_no); - } + float quat[4]; + + float3 f_no; + cross_poly_v3(f_no, (const float(*)[3])coords.data(), (uint)coords.size()); + f_no.normalize(); + tri_to_quat_ex(quat, coords[0], coords[1], coords[2], f_no); + /* Scale. */ + float scale; if (use_scale) { - const float area = area_poly_v3(coords, (uint)coords_len); + const float area = area_poly_v3((const float(*)[3])coords.data(), (uint)coords.size()); scale = sqrtf(area) * scale_fac; } else { scale = 1.0f; } - size[0] = size[1] = size[2] = scale; - loc_quat_size_to_mat4(r_mat, loc, quat, size); + loc_quat_size_to_mat4(r_mat, location, quat, float3(scale)); } static DupliObject *face_dupli(const DupliContext *ctx, @@ -952,14 +977,13 @@ static DupliObject *face_dupli(const DupliContext *ctx, const int index, const bool use_scale, const float scale_fac, - const float (*coords)[3], - const int coords_len) + Span<float3> coords) { float obmat[4][4]; float space_mat[4][4]; /* `obmat` is transform to face. */ - get_dupliface_transform_from_coords(coords, coords_len, use_scale, scale_fac, obmat); + get_dupliface_transform_from_coords(coords, use_scale, scale_fac, obmat); /* Make offset relative to inst_ob using relative child transform. */ mul_mat3_m4_v3(child_imat, obmat[3]); @@ -987,7 +1011,6 @@ static DupliObject *face_dupli(const DupliContext *ctx, return dob; } -/** Wrap #face_dupli, needed since we can't #alloca in a loop. */ static DupliObject *face_dupli_from_mesh(const DupliContext *ctx, Object *inst_ob, const float child_imat[4][4], @@ -1001,17 +1024,16 @@ static DupliObject *face_dupli_from_mesh(const DupliContext *ctx, const MVert *mvert) { const int coords_len = mpoly->totloop; - float(*coords)[3] = BLI_array_alloca(coords, (size_t)coords_len); + Array<float3, 64> coords(coords_len); const MLoop *ml = mloopstart; for (int i = 0; i < coords_len; i++, ml++) { - copy_v3_v3(coords[i], mvert[ml->v].co); + coords[i] = float3(mvert[ml->v].co); } - return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords, coords_len); + return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords); } -/** Wrap #face_dupli, needed since we can't #alloca in a loop. */ static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx, Object *inst_ob, const float child_imat[4][4], @@ -1024,12 +1046,12 @@ static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx, const float (*vert_coords)[3]) { const int coords_len = f->len; - float(*coords)[3] = BLI_array_alloca(coords, (size_t)coords_len); + Array<float3, 64> coords(coords_len); BMLoop *l_first, *l_iter; int i = 0; l_iter = l_first = BM_FACE_FIRST_LOOP(f); - if (vert_coords != NULL) { + if (vert_coords != nullptr) { do { copy_v3_v3(coords[i++], vert_coords[BM_elem_index_get(l_iter->v)]); } while ((l_iter = l_iter->next) != l_first); @@ -1040,14 +1062,14 @@ static DupliObject *face_dupli_from_editmesh(const DupliContext *ctx, } while ((l_iter = l_iter->next) != l_first); } - return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords, coords_len); + return face_dupli(ctx, inst_ob, child_imat, index, use_scale, scale_fac, coords); } static void make_child_duplis_faces_from_mesh(const DupliContext *ctx, void *userdata, Object *inst_ob) { - FaceDupliData_Mesh *fdd = userdata; + FaceDupliData_Mesh *fdd = (FaceDupliData_Mesh *)userdata; const MPoly *mpoly = fdd->mpoly, *mp; const MLoop *mloop = fdd->mloop; const MVert *mvert = fdd->mvert; @@ -1087,7 +1109,7 @@ static void make_child_duplis_faces_from_editmesh(const DupliContext *ctx, void *userdata, Object *inst_ob) { - FaceDupliData_EditMesh *fdd = userdata; + FaceDupliData_EditMesh *fdd = (FaceDupliData_EditMesh *)userdata; BMEditMesh *em = fdd->em; float child_imat[4][4]; int a; @@ -1097,7 +1119,7 @@ static void make_child_duplis_faces_from_editmesh(const DupliContext *ctx, const float(*vert_coords)[3] = fdd->vert_coords; - BLI_assert((vert_coords == NULL) || (em->bm->elem_index_dirty & BM_VERT) == 0); + BLI_assert((vert_coords == nullptr) || (em->bm->elem_index_dirty & BM_VERT) == 0); invert_m4_m4(inst_ob->imat, inst_ob->obmat); /* Relative transform from parent to child space. */ @@ -1127,44 +1149,41 @@ static void make_duplis_faces(const DupliContext *ctx) Object *parent = ctx->object; /* Gather mesh info. */ - BMEditMesh *em = NULL; - const float(*vert_coords)[3] = NULL; - Mesh *me_eval = mesh_data_from_duplicator_object(parent, &em, &vert_coords, NULL); - if (em == NULL && me_eval == NULL) { + BMEditMesh *em = nullptr; + const float(*vert_coords)[3] = nullptr; + Mesh *me_eval = mesh_data_from_duplicator_object(parent, &em, &vert_coords, nullptr); + if (em == nullptr && me_eval == nullptr) { return; } - FaceDupliData_Params fdd_params = { - .ctx = ctx, - .use_scale = parent->transflag & OB_DUPLIFACES_SCALE, - }; + FaceDupliData_Params fdd_params = {ctx, (parent->transflag & OB_DUPLIFACES_SCALE) != 0}; - if (em != NULL) { + if (em != nullptr) { const int uv_idx = CustomData_get_render_layer(&em->bm->ldata, CD_MLOOPUV); - FaceDupliData_EditMesh fdd = { - .params = fdd_params, - .em = em, - .vert_coords = vert_coords, - .has_orco = (vert_coords != NULL), - .has_uvs = (uv_idx != -1), - .cd_loop_uv_offset = (uv_idx != -1) ? - CustomData_get_n_offset(&em->bm->ldata, CD_MLOOPUV, uv_idx) : - -1, - }; + FaceDupliData_EditMesh fdd{}; + fdd.params = fdd_params; + fdd.em = em; + fdd.vert_coords = vert_coords; + fdd.has_orco = (vert_coords != nullptr); + fdd.has_uvs = (uv_idx != -1); + fdd.cd_loop_uv_offset = (uv_idx != -1) ? + CustomData_get_n_offset(&em->bm->ldata, CD_MLOOPUV, uv_idx) : + -1; make_child_duplis(ctx, &fdd, make_child_duplis_faces_from_editmesh); } else { const int uv_idx = CustomData_get_render_layer(&me_eval->ldata, CD_MLOOPUV); - FaceDupliData_Mesh fdd = { - .params = fdd_params, - .totface = me_eval->totpoly, - .mpoly = me_eval->mpoly, - .mloop = me_eval->mloop, - .mvert = me_eval->mvert, - .mloopuv = (uv_idx != -1) ? CustomData_get_layer_n(&me_eval->ldata, CD_MLOOPUV, uv_idx) : - NULL, - .orco = CustomData_get_layer(&me_eval->vdata, CD_ORCO), - }; + FaceDupliData_Mesh fdd{}; + fdd.params = fdd_params; + fdd.totface = me_eval->totpoly; + fdd.mpoly = me_eval->mpoly; + fdd.mloop = me_eval->mloop; + fdd.mvert = me_eval->mvert; + fdd.mloopuv = (uv_idx != -1) ? (const MLoopUV *)CustomData_get_layer_n( + &me_eval->ldata, CD_MLOOPUV, uv_idx) : + nullptr; + fdd.orco = (const float(*)[3])CustomData_get_layer(&me_eval->vdata, CD_ORCO); + make_child_duplis(ctx, &fdd, make_child_duplis_faces_from_mesh); } } @@ -1187,12 +1206,11 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); bool for_render = mode == DAG_EVAL_RENDER; - Object *ob = NULL, **oblist = NULL; + Object *ob = nullptr, **oblist = nullptr; DupliObject *dob; - ParticleDupliWeight *dw; ParticleSettings *part; ParticleData *pa; - ChildParticle *cpa = NULL; + ChildParticle *cpa = nullptr; ParticleKey state; ParticleCacheKey *cache; float ctime, scale = 1.0f; @@ -1202,13 +1220,13 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem int no_draw_flag = PARS_UNEXIST; - if (psys == NULL) { + if (psys == nullptr) { return; } part = psys->part; - if (part == NULL) { + if (part == nullptr) { return; } @@ -1228,7 +1246,7 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem if ((for_render || part->draw_as == PART_DRAW_REND) && ELEM(part->ren_as, PART_DRAW_OB, PART_DRAW_GR)) { - ParticleSimulationData sim = {NULL}; + ParticleSimulationData sim = {nullptr}; sim.depsgraph = ctx->depsgraph; sim.scene = scene; sim.ob = par; @@ -1239,12 +1257,12 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem /* First check for loops (particle system object used as dupli-object). */ if (part->ren_as == PART_DRAW_OB) { - if (ELEM(part->instance_object, NULL, par)) { + if (ELEM(part->instance_object, nullptr, par)) { return; } } else { /* #PART_DRAW_GR. */ - if (part->instance_collection == NULL) { + if (part->instance_collection == nullptr) { return; } @@ -1285,8 +1303,7 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem if (part->ren_as == PART_DRAW_GR) { if (use_collection_count) { psys_find_group_weights(part); - - for (dw = part->instance_weights.first; dw; dw = dw->next) { + LISTBASE_FOREACH (ParticleDupliWeight *, dw, &part->instance_weights) { FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN ( part->instance_collection, object, mode) { if (dw->ob == object) { @@ -1306,11 +1323,12 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; } - oblist = MEM_callocN((size_t)totcollection * sizeof(Object *), "dupcollection object list"); + oblist = (Object **)MEM_callocN((size_t)totcollection * sizeof(Object *), + "dupcollection object list"); if (use_collection_count) { a = 0; - for (dw = part->instance_weights.first; dw; dw = dw->next) { + LISTBASE_FOREACH (ParticleDupliWeight *, dw, &part->instance_weights) { FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN ( part->instance_collection, object, mode) { if (dw->ob == object) { @@ -1363,7 +1381,7 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem #if 0 /* UNUSED */ pa_num = a; #endif - size = psys_get_child_size(psys, cpa, ctime, NULL); + size = psys_get_child_size(psys, cpa, ctime, nullptr); } /* Some hair paths might be non-existent so they can't be used for duplication. */ @@ -1394,11 +1412,11 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem /* Hair we handle separate and compute transform based on hair keys. */ if (a < totpart) { cache = psys->pathcache[a]; - psys_get_dupli_path_transform(&sim, pa, NULL, cache, pamat, &scale); + psys_get_dupli_path_transform(&sim, pa, nullptr, cache, pamat, &scale); } else { cache = psys->childcache[a - totpart]; - psys_get_dupli_path_transform(&sim, NULL, cpa, cache, pamat, &scale); + psys_get_dupli_path_transform(&sim, nullptr, cpa, cache, pamat, &scale); } copy_v3_v3(pamat[3], cache->co); @@ -1506,20 +1524,18 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem if (psys->lattice_deform_data) { BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; + psys->lattice_deform_data = nullptr; } } static void make_duplis_particles(const DupliContext *ctx) { - ParticleSystem *psys; - int psysid; - /* Particle system take up one level in id, the particles another. */ - for (psys = ctx->object->particlesystem.first, psysid = 0; psys; psys = psys->next, psysid++) { + int psysid; + LISTBASE_FOREACH_INDEX (ParticleSystem *, psys, &ctx->object->particlesystem, psysid) { /* Particles create one more level for persistent `psys` index. */ DupliContext pctx; - copy_dupli_context(&pctx, ctx, ctx->object, NULL, psysid); + copy_dupli_context(&pctx, ctx, ctx->object, nullptr, psysid); make_duplis_particle_system(&pctx, psys); } } @@ -1540,17 +1556,17 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) int transflag = ctx->object->transflag; int restrictflag = ctx->object->restrictflag; - if ((transflag & OB_DUPLI) == 0 && ctx->object->runtime.geometry_set_eval == NULL) { - return NULL; + if ((transflag & OB_DUPLI) == 0 && ctx->object->runtime.geometry_set_eval == nullptr) { + return nullptr; } /* Should the dupli's be generated for this object? - Respect restrict flags. */ if (DEG_get_mode(ctx->depsgraph) == DAG_EVAL_RENDER ? (restrictflag & OB_RESTRICT_RENDER) : (restrictflag & OB_RESTRICT_VIEWPORT)) { - return NULL; + return nullptr; } - if (ctx->object->runtime.geometry_set_eval != NULL) { + if (ctx->object->runtime.geometry_set_eval != nullptr) { if (BKE_geometry_set_has_instances(ctx->object->runtime.geometry_set_eval)) { return &gen_dupli_instances_component; } @@ -1579,7 +1595,7 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) return &gen_dupli_collection; } - return NULL; + return nullptr; } /** \} */ @@ -1593,9 +1609,11 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) */ ListBase *object_duplilist(Depsgraph *depsgraph, Scene *sce, Object *ob) { - ListBase *duplilist = MEM_callocN(sizeof(ListBase), "duplilist"); + ListBase *duplilist = (ListBase *)MEM_callocN(sizeof(ListBase), "duplilist"); DupliContext ctx; - init_context(&ctx, depsgraph, sce, ob, NULL); + Vector<Object *> instance_stack; + instance_stack.append(ob); + init_context(&ctx, depsgraph, sce, ob, nullptr, instance_stack); if (ctx.gen) { ctx.duplilist = duplilist; ctx.gen->make_duplis(&ctx); diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c index b1ae4abd9bb..e6909127503 100644 --- a/source/blender/blenkernel/intern/object_update.c +++ b/source/blender/blenkernel/intern/object_update.c @@ -176,6 +176,13 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o CustomData_MeshMasks cddata_masks = scene->customdata_mask; CustomData_MeshMasks_update(&cddata_masks, &CD_MASK_BAREMESH); + /* Custom attributes should not be removed automatically. They might be used by the render + * engine or scripts. They can still be removed explicitly using geometry nodes. */ + cddata_masks.vmask |= CD_MASK_PROP_ALL; + cddata_masks.emask |= CD_MASK_PROP_ALL; + cddata_masks.fmask |= CD_MASK_PROP_ALL; + cddata_masks.pmask |= CD_MASK_PROP_ALL; + cddata_masks.lmask |= CD_MASK_PROP_ALL; /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). * Due to Line Art implementation, edge marks should also be shown in viewport. */ #ifdef WITH_FREESTYLE diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c index d2f4d0702ed..9b9ed0adcf4 100644 --- a/source/blender/blenkernel/intern/ocean.c +++ b/source/blender/blenkernel/intern/ocean.c @@ -663,7 +663,7 @@ void BKE_ocean_simulate(struct Ocean *o, float t, float scale, float chop_amount osd.scale = scale; osd.chop_amount = chop_amount; - pool = BLI_task_pool_create(&osd, TASK_PRIORITY_HIGH); + pool = BLI_task_pool_create(&osd, TASK_PRIORITY_HIGH, TASK_ISOLATION_ON); BLI_rw_mutex_lock(&o->oceanmutex, THREAD_LOCK_WRITE); @@ -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/particle.c b/source/blender/blenkernel/intern/particle.c index e50b321900a..3ae5d039125 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -101,6 +101,8 @@ static void particle_settings_init(ID *id) MEMCPY_STRUCT_AFTER(particle_settings, DNA_struct_default_get(ParticleSettings), id); particle_settings->effector_weights = BKE_effector_add_weights(NULL); + particle_settings->pd = BKE_partdeflect_new(PFIELD_NULL); + particle_settings->pd2 = BKE_partdeflect_new(PFIELD_NULL); } static void particle_settings_copy_data(Main *UNUSED(bmain), @@ -2417,14 +2419,15 @@ int do_guides(Depsgraph *depsgraph, cu = (Curve *)eff->ob->data; if (pd->flag & PFIELD_GUIDE_PATH_ADD) { - if (where_on_path( + if (BKE_where_on_path( eff->ob, data->strength * guidetime, guidevec, guidedir, NULL, &radius, &weight) == 0) { return 0; } } else { - if (where_on_path(eff->ob, guidetime, guidevec, guidedir, NULL, &radius, &weight) == 0) { + if (BKE_where_on_path(eff->ob, guidetime, guidevec, guidedir, NULL, &radius, &weight) == + 0) { return 0; } } @@ -3176,7 +3179,7 @@ void psys_cache_child_paths(ParticleSimulationData *sim, return; } - task_pool = BLI_task_pool_create(&ctx, TASK_PRIORITY_LOW); + task_pool = BLI_task_pool_create(&ctx, TASK_PRIORITY_LOW, TASK_ISOLATION_ON); totchild = ctx.totchild; totparent = ctx.totparent; diff --git a/source/blender/blenkernel/intern/particle_distribute.c b/source/blender/blenkernel/intern/particle_distribute.c index ad617b4198b..6cae6cd6fa2 100644 --- a/source/blender/blenkernel/intern/particle_distribute.c +++ b/source/blender/blenkernel/intern/particle_distribute.c @@ -1330,7 +1330,7 @@ static void distribute_particles_on_dm(ParticleSimulationData *sim, int from) return; } - task_pool = BLI_task_pool_create(&ctx, TASK_PRIORITY_LOW); + task_pool = BLI_task_pool_create(&ctx, TASK_PRIORITY_LOW, TASK_ISOLATION_ON); totpart = (from == PART_FROM_CHILD ? sim->psys->totchild : sim->psys->totpart); psys_tasks_create(&ctx, 0, totpart, &tasks, &numtasks); diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c index c727a144c87..149e345e501 100644 --- a/source/blender/blenkernel/intern/particle_system.c +++ b/source/blender/blenkernel/intern/particle_system.c @@ -3312,13 +3312,11 @@ static MDeformVert *hair_set_pinning(MDeformVert *dvert, float weight) static void hair_create_input_mesh(ParticleSimulationData *sim, int totpoint, int totedge, - Mesh **r_mesh, - ClothHairData **r_hairdata) + Mesh **r_mesh) { ParticleSystem *psys = sim->psys; ParticleSettings *part = psys->part; Mesh *mesh; - ClothHairData *hairdata; MVert *mvert; MEdge *medge; MDeformVert *dvert; @@ -3339,9 +3337,8 @@ static void hair_create_input_mesh(ParticleSimulationData *sim, medge = mesh->medge; dvert = mesh->dvert; - hairdata = *r_hairdata; - if (!hairdata) { - *r_hairdata = hairdata = MEM_mallocN(sizeof(ClothHairData) * totpoint, "hair data"); + if (psys->clmd->hairdata == NULL) { + psys->clmd->hairdata = MEM_mallocN(sizeof(ClothHairData) * totpoint, "hair data"); } /* calculate maximum segment length */ @@ -3493,7 +3490,7 @@ static void do_hair_dynamics(ParticleSimulationData *sim) } } - hair_create_input_mesh(sim, totpoint, totedge, &psys->hair_in_mesh, &psys->clmd->hairdata); + hair_create_input_mesh(sim, totpoint, totedge, &psys->hair_in_mesh); if (psys->hair_out_mesh) { BKE_id_free(NULL, psys->hair_out_mesh); @@ -4912,9 +4909,12 @@ void particle_system_update(struct Depsgraph *depsgraph, sim.psmd->flag |= eParticleSystemFlag_Pars; } + ParticleTexture ptex; + LOOP_EXISTING_PARTICLES { - pa->size = part->size; + psys_get_texture(&sim, pa, &ptex, PAMAP_SIZE, cfra); + pa->size = part->size * ptex.size; if (part->randsize > 0.0f) { pa->size *= 1.0f - part->randsize * psys_frand(psys, p + 1); } diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index 63bc8753fc7..948b57578dc 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.h +++ b/source/blender/blenkernel/intern/pbvh_intern.h @@ -187,31 +187,31 @@ int BB_widest_axis(const BB *bb); void pbvh_grow_nodes(PBVH *bvh, int totnode); bool ray_face_intersection_quad(const float ray_start[3], struct IsectRayPrecalc *isect_precalc, - const float *t0, - const float *t1, - const float *t2, - const float *t3, + const float t0[3], + const float t1[3], + const float t2[3], + const float t3[3], float *depth); bool ray_face_intersection_tri(const float ray_start[3], struct IsectRayPrecalc *isect_precalc, - const float *t0, - const float *t1, - const float *t2, + const float t0[3], + const float t1[3], + const float t2[3], float *depth); bool ray_face_nearest_quad(const float ray_start[3], const float ray_normal[3], - const float *t0, - const float *t1, - const float *t2, - const float *t3, + const float t0[3], + const float t1[3], + const float t2[3], + const float t3[3], float *r_depth, float *r_dist_sq); bool ray_face_nearest_tri(const float ray_start[3], const float ray_normal[3], - const float *t0, - const float *t1, - const float *t2, + const float t0[3], + const float t1[3], + const float t2[3], float *r_depth, float *r_dist_sq); diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c index 17434ee8023..be206f8a642 100644 --- a/source/blender/blenkernel/intern/pointcache.c +++ b/source/blender/blenkernel/intern/pointcache.c @@ -1805,7 +1805,7 @@ int BKE_ptcache_mem_pointers_seek(int point_index, PTCacheMem *pm, void *cur[BPH } for (i = 0; i < BPHYS_TOT_DATA; i++) { - cur[i] = data_types & (1 << i) ? (char *)pm->data[i] + index * ptcache_data_size[i] : NULL; + cur[i] = (data_types & (1 << i)) ? (char *)pm->data[i] + index * ptcache_data_size[i] : NULL; } return 1; diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 19078446009..21b86aa8148 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"); } @@ -1520,7 +1522,7 @@ void BKE_rigidbody_remove_object(Main *bmain, Scene *scene, Object *ob, const bo if (rbw) { /* remove object from array */ - if (rbw && rbw->objects) { + if (rbw->objects) { for (i = 0; i < rbw->numbodies; i++) { if (rbw->objects[i] == ob) { rbw->objects[i] = NULL; diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index de3e1023b08..86d4c03d51a 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -742,7 +742,8 @@ static void scene_foreach_id(ID *id, LibraryForeachIDData *data) BKE_LIB_FOREACHID_PROCESS(data, view_layer->mat_override, IDWALK_CB_USER); LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - BKE_LIB_FOREACHID_PROCESS(data, base->object, IDWALK_CB_NOP); + BKE_LIB_FOREACHID_PROCESS( + data, base->object, IDWALK_CB_NOP | IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); } scene_foreach_layer_collection(data, &view_layer->layer_collections); @@ -1984,8 +1985,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) const bool is_subprocess = false; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and * duplicate all expected linked data. */ if (ID_IS_LINKED(sce)) { @@ -2026,8 +2026,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_collection_sync(bmain); } @@ -2517,6 +2516,18 @@ int BKE_scene_orientation_slot_get_index(const TransformOrientationSlot *orient_ orient_slot->type; } +int BKE_scene_orientation_get_index(Scene *scene, int slot_index) +{ + TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get(scene, slot_index); + return BKE_scene_orientation_slot_get_index(orient_slot); +} + +int BKE_scene_orientation_get_index_from_flag(Scene *scene, int flag) +{ + TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get_from_flag(scene, flag); + return BKE_scene_orientation_slot_get_index(orient_slot); +} + /** \} */ static bool check_rendered_viewport_visible(Main *bmain) @@ -2624,6 +2635,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on Scene *scene = DEG_get_input_scene(depsgraph); ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); + bool used_multiple_passes = false; bool run_callbacks = DEG_id_type_any_updated(depsgraph); if (run_callbacks) { @@ -2648,7 +2660,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on bmain, &scene->id, depsgraph, BKE_CB_EVT_DEPSGRAPH_UPDATE_POST); /* It is possible that the custom callback modified scene and removed some IDs from the main - * database. In this case DEG_ids_clear_recalc() will crash because it iterates over all IDs + * database. In this case DEG_editors_update() will crash because it iterates over all IDs * which depsgraph was built for. * * The solution is to update relations prior to this call, avoiding access to freed IDs. @@ -2660,10 +2672,6 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on * If there are no relations changed by the callback this call will do nothing. */ DEG_graph_relations_update(depsgraph); } - /* Inform editors about possible changes. */ - DEG_ids_check_recalc(bmain, depsgraph, scene, view_layer, false); - /* Clear recalc flags. */ - DEG_ids_clear_recalc(bmain, depsgraph); /* If user callback did not tag anything for update we can skip second iteration. * Otherwise we update scene once again, but without running callbacks to bring @@ -2672,8 +2680,22 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on break; } + /* Clear recalc flags for second pass, but back them up for editors update. */ + const bool backup = true; + DEG_ids_clear_recalc(depsgraph, backup); + used_multiple_passes = true; run_callbacks = false; } + + /* Inform editors about changes, using recalc flags from both passes. */ + if (used_multiple_passes) { + DEG_ids_restore_recalc(depsgraph); + } + const bool is_time_update = false; + DEG_editors_update(depsgraph, is_time_update); + + const bool backup = false; + DEG_ids_clear_recalc(depsgraph, backup); } void BKE_scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain) @@ -2687,11 +2709,11 @@ void BKE_scene_graph_evaluated_ensure(Depsgraph *depsgraph, Main *bmain) } /* applies changes right away, does all sets too */ -void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph) +void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool clear_recalc) { Scene *scene = DEG_get_input_scene(depsgraph); - ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); Main *bmain = DEG_get_bmain(depsgraph); + bool used_multiple_passes = false; /* Keep this first. */ BKE_callback_exec_id(bmain, &scene->id, BKE_CB_EVT_FRAME_CHANGE_PRE); @@ -2724,24 +2746,44 @@ void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph) BKE_callback_exec_id_depsgraph(bmain, &scene->id, depsgraph, BKE_CB_EVT_FRAME_CHANGE_POST); /* NOTE: Similar to this case in scene_graph_update_tagged(). Need to ensure that - * DEG_ids_clear_recalc() doesn't access freed memory of possibly removed ID. */ + * DEG_editors_update() doesn't access freed memory of possibly removed ID. */ DEG_graph_relations_update(depsgraph); } - /* Inform editors about possible changes. */ - DEG_ids_check_recalc(bmain, depsgraph, scene, view_layer, true); - /* clear recalc flags */ - DEG_ids_clear_recalc(bmain, depsgraph); - /* If user callback did not tag anything for update we can skip second iteration. * Otherwise we update scene once again, but without running callbacks to bring * scene to a fully evaluated state with user modifications taken into account. */ if (DEG_is_fully_evaluated(depsgraph)) { break; } + + /* Clear recalc flags for second pass, but back them up for editors update. */ + const bool backup = true; + DEG_ids_clear_recalc(depsgraph, backup); + used_multiple_passes = true; + } + + /* Inform editors about changes, using recalc flags from both passes. */ + if (used_multiple_passes) { + DEG_ids_restore_recalc(depsgraph); + } + + const bool is_time_update = true; + DEG_editors_update(depsgraph, is_time_update); + + /* Clear recalc flags, can be skipped for e.g. renderers that will read these + * and clear the flags later. */ + if (clear_recalc) { + const bool backup = false; + DEG_ids_clear_recalc(depsgraph, backup); } } +void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph) +{ + BKE_scene_graph_update_for_newframe_ex(depsgraph, true); +} + /** * Ensures given scene/view_layer pair has a valid, up-to-date depsgraph. * @@ -3450,6 +3492,9 @@ static Depsgraph **scene_ensure_depsgraph_p(Main *bmain, Scene *scene, ViewLayer BLI_snprintf(name, sizeof(name), "%s :: %s", scene->id.name, view_layer->name); DEG_debug_name_set(*depsgraph_ptr, name); + /* These viewport depsgraphs communicate changes to the editors. */ + DEG_enable_editors_update(*depsgraph_ptr); + return depsgraph_ptr; } @@ -3496,8 +3541,8 @@ GHash *BKE_scene_undo_depsgraphs_extract(Main *bmain) for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { if (scene->depsgraph_hash == NULL) { - /* In some cases, e.g. when undo has to perform multiple steps at once, no depsgraph will be - * built so this pointer may be NULL. */ + /* In some cases, e.g. when undo has to perform multiple steps at once, no depsgraph will + * be built so this pointer may be NULL. */ continue; } for (ViewLayer *view_layer = scene->view_layers.first; view_layer != NULL; @@ -3742,9 +3787,7 @@ void BKE_scene_eval_sequencer_sequences(Depsgraph *depsgraph, Scene *scene) SEQ_ALL_BEGIN (scene->ed, seq) { if (seq->scene_sound == NULL) { if (seq->sound != NULL) { - if (seq->scene_sound == NULL) { - seq->scene_sound = BKE_sound_add_scene_sound_defaults(scene, seq); - } + seq->scene_sound = BKE_sound_add_scene_sound_defaults(scene, seq); } else if (seq->type == SEQ_TYPE_SCENE) { if (seq->scene != NULL) { diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 1766ac5b85f..d0d63192ebf 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -227,7 +227,12 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area case SPACE_SPREADSHEET: { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; - BKE_LIB_FOREACHID_PROCESS_ID(data, sspreadsheet->pinned_id, IDWALK_CB_NOP); + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + if (context->type == SPREADSHEET_CONTEXT_OBJECT) { + BKE_LIB_FOREACHID_PROCESS( + data, ((SpreadsheetContextObject *)context)->object, IDWALK_CB_NOP); + } + } break; } default: @@ -1350,6 +1355,34 @@ static void write_area(BlendWriter *writer, ScrArea *area) } else if (sl->spacetype == SPACE_SPREADSHEET) { BLO_write_struct(writer, SpaceSpreadsheet, sl); + + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) { + BLO_write_struct(writer, SpreadsheetColumn, column); + BLO_write_struct(writer, SpreadsheetColumnID, column->id); + BLO_write_string(writer, column->id->name); + } + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + switch (context->type) { + case SPREADSHEET_CONTEXT_OBJECT: { + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context; + BLO_write_struct(writer, SpreadsheetContextObject, object_context); + break; + } + case SPREADSHEET_CONTEXT_MODIFIER: { + SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context; + BLO_write_struct(writer, SpreadsheetContextModifier, modifier_context); + BLO_write_string(writer, modifier_context->modifier_name); + break; + } + case SPREADSHEET_CONTEXT_NODE: { + SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)context; + BLO_write_struct(writer, SpreadsheetContextNode, node_context); + BLO_write_string(writer, node_context->node_name); + break; + } + } + } } } } @@ -1527,9 +1560,6 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area) if (sl->spacetype == SPACE_VIEW3D) { View3D *v3d = (View3D *)sl; - - v3d->flag |= V3D_INVALID_BACKBUF; - if (v3d->gpd) { BLO_read_data_address(reader, &v3d->gpd); BKE_gpencil_blend_read_data(reader, v3d->gpd); @@ -1705,6 +1735,31 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area) SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; sspreadsheet->runtime = NULL; + + BLO_read_list(reader, &sspreadsheet->columns); + LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) { + BLO_read_data_address(reader, &column->id); + BLO_read_data_address(reader, &column->id->name); + } + + BLO_read_list(reader, &sspreadsheet->context_path); + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + switch (context->type) { + case SPREADSHEET_CONTEXT_NODE: { + SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)context; + BLO_read_data_address(reader, &node_context->node_name); + break; + } + case SPREADSHEET_CONTEXT_MODIFIER: { + SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context; + BLO_read_data_address(reader, &modifier_context->modifier_name); + break; + } + case SPREADSHEET_CONTEXT_OBJECT: { + break; + } + } + } } } @@ -1921,7 +1976,12 @@ void BKE_screen_area_blend_read_lib(BlendLibReader *reader, ID *parent_id, ScrAr } case SPACE_SPREADSHEET: { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; - BLO_read_id_address(reader, parent_id->lib, &sspreadsheet->pinned_id); + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + if (context->type == SPREADSHEET_CONTEXT_OBJECT) { + BLO_read_id_address( + reader, parent_id->lib, &((SpreadsheetContextObject *)context)->object); + } + } break; } default: diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index de88e8a941c..fcc1afbc59b 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -499,7 +499,6 @@ static void ccd_mesh_free(ccd_Mesh *ccdm) } MEM_freeN(ccdm->mima); MEM_freeN(ccdm); - ccdm = NULL; } } @@ -830,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); @@ -2680,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; @@ -2698,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; @@ -2910,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; @@ -2930,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; @@ -2973,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; @@ -2994,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 */ @@ -3118,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; @@ -3141,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; @@ -3181,9 +3174,11 @@ void sbFree(Object *ob) return; } + const bool is_orig = (ob->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0; + free_softbody_intern(sb); - if ((ob->id.tag & LIB_TAG_NO_MAIN) == 0) { + if (is_orig) { /* Only free shared data on non-CoW copies */ BKE_ptcache_free_list(&sb->shared->ptcaches); sb->shared->pointcache = NULL; @@ -3576,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 new file mode 100644 index 00000000000..2781ff1e49a --- /dev/null +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -0,0 +1,380 @@ +/* + * 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. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_task.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; +using blender::Span; +using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray; +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_Typed; +using blender::fn::GVArrayPtr; + +Spline::Type Spline::type() const +{ + return type_; +} + +void Spline::translate(const blender::float3 &translation) +{ + for (float3 &position : this->positions()) { + position += translation; + } + this->mark_cache_invalid(); +} + +void Spline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + this->mark_cache_invalid(); +} + +int Spline::evaluated_edges_size() const +{ + const int eval_size = this->evaluated_points_size(); + if (eval_size == 1) { + return 0; + } + + return this->is_cyclic_ ? eval_size : eval_size - 1; +} + +float Spline::length() const +{ + Span<float> lengths = this->evaluated_lengths(); + return (lengths.size() == 0) ? 0 : this->evaluated_lengths().last(); +} + +int Spline::segments_size() const +{ + const int points_len = this->size(); + + return is_cyclic_ ? points_len : points_len - 1; +} + +bool Spline::is_cyclic() const +{ + return is_cyclic_; +} + +void Spline::set_cyclic(const bool value) +{ + is_cyclic_ = value; +} + +static void accumulate_lengths(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float> lengths) +{ + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + length += float3::distance(positions[i], positions[i + 1]); + lengths[i] = length; + } + if (is_cyclic) { + lengths.last() = length + float3::distance(positions.last(), positions.first()); + } +} + +/** + * Return non-owning access to the cache of accumulated lengths along the spline. Each item is the + * length of the subsequent segment, i.e. the first value is the length of the first segment rather + * than 0. This calculation is rather trivial, and only depends on the evaluated positions. + * However, the results are used often, so it makes sense to cache it. + */ +Span<float> Spline::evaluated_lengths() const +{ + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + std::lock_guard lock{length_cache_mutex_}; + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + const int total = evaluated_edges_size(); + evaluated_lengths_cache_.resize(total); + + Span<float3> positions = this->evaluated_positions(); + accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_); + + length_cache_dirty_ = false; + return evaluated_lengths_cache_; +} + +static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next) +{ + const float3 dir_prev = (middle - prev).normalized(); + const float3 dir_next = (next - middle).normalized(); + + return (dir_prev + dir_next).normalized(); +} + +static void calculate_tangents(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float3> tangents) +{ + if (positions.size() == 1) { + return; + } + + for (const int i : IndexRange(1, positions.size() - 2)) { + tangents[i] = direction_bisect(positions[i - 1], positions[i], positions[i + 1]); + } + + if (is_cyclic) { + const float3 &second_to_last = positions[positions.size() - 2]; + const float3 &last = positions.last(); + const float3 &first = positions.first(); + const float3 &second = positions[1]; + tangents.first() = direction_bisect(last, first, second); + tangents.last() = direction_bisect(second_to_last, last, first); + } + else { + tangents.first() = (positions[1] - positions[0]).normalized(); + tangents.last() = (positions.last() - positions[positions.size() - 2]).normalized(); + } +} + +/** + * Return non-owning access to the direction of the curve at each evaluated point. + */ +Span<float3> Spline::evaluated_tangents() const +{ + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + std::lock_guard lock{tangent_cache_mutex_}; + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_tangents_cache_.resize(eval_size); + + Span<float3> positions = this->evaluated_positions(); + + if (eval_size == 1) { + evaluated_tangents_cache_.first() = float3(1.0f, 0.0f, 0.0f); + } + else { + calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_); + this->correct_end_tangents(); + } + + tangent_cache_dirty_ = false; + return evaluated_tangents_cache_; +} + +static float3 rotate_direction_around_axis(const float3 &direction, + const float3 &axis, + const float angle) +{ + BLI_ASSERT_UNIT_V3(direction); + BLI_ASSERT_UNIT_V3(axis); + + const float3 axis_scaled = axis * float3::dot(direction, axis); + const float3 diff = direction - axis_scaled; + const float3 cross = float3::cross(axis, diff); + + return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); +} + +static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> normals) +{ + for (const int i : normals.index_range()) { + normals[i] = float3::cross(tangents[i], float3(0.0f, 0.0f, 1.0f)).normalized(); + } +} + +/** + * Return non-owning access to the direction vectors perpendicular to the tangents at every + * evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode. + */ +Span<float3> Spline::evaluated_normals() const +{ + if (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + std::lock_guard lock{normal_cache_mutex_}; + if (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_normals_cache_.resize(eval_size); + + Span<float3> tangents = evaluated_tangents(); + MutableSpan<float3> normals = evaluated_normals_cache_; + + /* Only Z up normals are supported at the moment. */ + calculate_normals_z_up(tangents, normals); + + /* Rotate the generated normals with the interpolated tilt data. */ + GVArray_Typed<float> tilts = this->interpolate_to_evaluated_points(this->tilts()); + for (const int i : normals.index_range()) { + normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]); + } + + normal_cache_dirty_ = false; + return evaluated_normals_cache_; +} + +Spline::LookupResult Spline::lookup_evaluated_factor(const float factor) const +{ + return this->lookup_evaluated_length(this->length() * factor); +} + +/** + * \note This does not support extrapolation currently. + */ +Spline::LookupResult Spline::lookup_evaluated_length(const float length) const +{ + BLI_assert(length >= 0.0f && length <= this->length()); + + Span<float> lengths = this->evaluated_lengths(); + + const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length); + const int index = offset - lengths.begin(); + const int next_index = (index == this->size() - 1) ? 0 : index + 1; + + const float previous_length = (index == 0) ? 0.0f : lengths[index - 1]; + const float factor = (length - previous_length) / (lengths[index] - previous_length); + + 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(); + for (const float3 &position : positions) { + minmax_v3v3_v3(min, max, position); + } +} + +GVArrayPtr Spline::interpolate_to_evaluated_points(GSpan data) const +{ + return this->interpolate_to_evaluated_points(GVArray_For_GSpan(data)); +} + +/** + * Sample any input data with a value for each evaluated point (already interpolated to evaluated + * points) to arbitrary parameters in between the evaluated points. The interpolation is quite + * simple, but this handles the cyclic and end point special cases. + */ +void Spline::sample_based_on_index_factors(const GVArray &src, + Span<float> index_factors, + GMutableSpan dst) const +{ + BLI_assert(src.size() == this->evaluated_points_size()); + + blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + const GVArray_Typed<T> src_typed = src.typed<T>(); + MutableSpan<T> dst_typed = dst.typed<T>(); + blender::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const LookupResult interp = this->lookup_data_from_index_factor(index_factors[i]); + dst_typed[i] = blender::attribute_math::mix2(interp.factor, + src_typed[interp.evaluated_index], + src_typed[interp.next_evaluated_index]); + } + }); + }); +} diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc new file mode 100644 index 00000000000..3e421dcfc13 --- /dev/null +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -0,0 +1,592 @@ +/* + * 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. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +SplinePtr BezierSpline::copy() const +{ + return std::make_unique<BezierSpline>(*this); +} + +SplinePtr BezierSpline::copy_settings() const +{ + std::unique_ptr<BezierSpline> copy = std::make_unique<BezierSpline>(); + copy_base_settings(*this, *copy); + copy->resolution_ = resolution_; + return copy; +} + +int BezierSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == handle_types_left_.size()); + BLI_assert(size == handle_positions_left_.size()); + BLI_assert(size == handle_types_right_.size()); + BLI_assert(size == handle_positions_right_.size()); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + return size; +} + +int BezierSpline::resolution() const +{ + return resolution_; +} + +void BezierSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = 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_left, + const float3 handle_position_left, + const HandleType handle_type_right, + const float3 handle_position_right, + const float radius, + const float tilt) +{ + handle_types_left_.append(handle_type_left); + handle_positions_left_.append(handle_position_left); + positions_.append(position); + handle_types_right_.append(handle_type_right); + handle_positions_right_.append(handle_position_right); + radii_.append(radius); + tilts_.append(tilt); + 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_; +} +Span<float3> BezierSpline::positions() const +{ + return positions_; +} +MutableSpan<float> BezierSpline::radii() +{ + return radii_; +} +Span<float> BezierSpline::radii() const +{ + return radii_; +} +MutableSpan<float> BezierSpline::tilts() +{ + return tilts_; +} +Span<float> BezierSpline::tilts() const +{ + return tilts_; +} +Span<BezierSpline::HandleType> BezierSpline::handle_types_left() const +{ + return handle_types_left_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left() +{ + return 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 +{ + return handle_types_right_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right() +{ + return 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()) { + position += translation; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position += translation; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position += translation; + } + this->mark_cache_invalid(); +} + +void BezierSpline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position = matrix * handle_position; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position = matrix * handle_position; + } + this->mark_cache_invalid(); +} + +bool BezierSpline::point_is_sharp(const int index) const +{ + return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) || + ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free); +} + +bool BezierSpline::segment_is_vector(const int index) const +{ + if (index == this->size() - 1) { + 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; +} + +void BezierSpline::mark_cache_invalid() +{ + offset_cache_dirty_ = true; + position_cache_dirty_ = true; + mapping_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; + auto_handles_dirty_ = true; +} + +int BezierSpline::evaluated_points_size() const +{ + BLI_assert(this->size() > 0); + return this->control_point_offsets().last(); +} + +/** + * If the spline is not cyclic, the direction for the first and last points is just the + * direction formed by the corresponding handles and control points. In the unlikely situation + * that the handles define a zero direction, fallback to using the direction defined by the + * first and last evaluated segments already calculated in #Spline::evaluated_tangents(). + */ +void BezierSpline::correct_end_tangents() const +{ + if (is_cyclic_) { + return; + } + + MutableSpan<float3> tangents(evaluated_tangents_cache_); + + if (handle_positions_right_.first() != positions_.first()) { + tangents.first() = (handle_positions_right_.first() - positions_.first()).normalized(); + } + if (handle_positions_left_.last() != positions_.last()) { + tangents.last() = (positions_.last() - handle_positions_left_.last()).normalized(); + } +} + +static void bezier_forward_difference_3d(const float3 &point_0, + const float3 &point_1, + const float3 &point_2, + const float3 &point_3, + MutableSpan<float3> result) +{ + BLI_assert(result.size() > 0); + const float inv_len = 1.0f / static_cast<float>(result.size()); + const float inv_len_squared = inv_len * inv_len; + const float inv_len_cubed = inv_len_squared * inv_len; + + const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len; + const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; + const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; + + float3 q0 = point_0; + float3 q1 = rt1 + rt2 + rt3; + float3 q2 = 2.0f * rt2 + 6.0f * rt3; + float3 q3 = 6.0f * rt3; + for (const int i : result.index_range()) { + result[i] = q0; + q0 += q1; + q1 += q2; + q2 += q3; + } +} + +void BezierSpline::evaluate_bezier_segment(const int index, + const int next_index, + MutableSpan<float3> positions) const +{ + if (this->segment_is_vector(index)) { + BLI_assert(positions.size() == 1); + positions.first() = positions_[index]; + } + else { + bezier_forward_difference_3d(positions_[index], + handle_positions_right_[index], + handle_positions_left_[next_index], + positions_[next_index], + positions); + } +} + +/** + * Returns access to a cache of offsets into the evaluated point array for each control point. + * 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 +{ + if (!offset_cache_dirty_) { + return offset_cache_; + } + + std::lock_guard lock{offset_cache_mutex_}; + if (!offset_cache_dirty_) { + return offset_cache_; + } + + const int points_len = this->size(); + offset_cache_.resize(points_len + 1); + + MutableSpan<int> offsets = offset_cache_; + + int offset = 0; + for (const int i : IndexRange(points_len)) { + offsets[i] = offset; + offset += this->segment_is_vector(i) ? 1 : resolution_; + } + offsets.last() = offset; + + offset_cache_dirty_ = false; + return offsets; +} + +static void calculate_mappings_linear_resolution(Span<int> offsets, + const int size, + const int resolution, + const bool is_cyclic, + MutableSpan<float> r_mappings) +{ + const float first_segment_len_inv = 1.0f / offsets[1]; + for (const int i : IndexRange(0, offsets[1])) { + r_mappings[i] = i * first_segment_len_inv; + } + + const int grain_size = std::max(2048 / resolution, 1); + 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; + for (const int i : IndexRange(segment_len)) { + r_mappings[offsets[i_control_point] + i] = i_control_point + i * segment_len_inv; + } + } + }); + + if (is_cyclic) { + 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; + } + } + else { + r_mappings.last() = size - 1; + } +} + +/** + * Returns non-owning access to an array of values containing the information necessary to + * interpolate values from the original control points to evaluated points. The control point + * index is the integer part of each value, and the factor used for interpolating to the next + * control point is the remaining factional part. + */ +Span<float> BezierSpline::evaluated_mappings() const +{ + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + std::lock_guard lock{mapping_cache_mutex_}; + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + const int size = this->size(); + const int eval_size = this->evaluated_points_size(); + evaluated_mapping_cache_.resize(eval_size); + MutableSpan<float> mappings = evaluated_mapping_cache_; + + if (eval_size == 1) { + mappings.first() = 0.0f; + mapping_cache_dirty_ = false; + return mappings; + } + + Span<int> offsets = this->control_point_offsets(); + + calculate_mappings_linear_resolution(offsets, size, resolution_, is_cyclic_, mappings); + + mapping_cache_dirty_ = false; + return mappings; +} + +Span<float3> BezierSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + 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(); + + const int grain_size = std::max(512 / resolution_, 1); + 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])); + } + }); + if (is_cyclic_) { + 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, + * it must be added manually in the non-cyclic case. */ + positions.last() = positions_.last(); + } + + position_cache_dirty_ = false; + return positions; +} + +/** + * 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 control point vectors. + */ +BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor( + const float index_factor) const +{ + const int points_len = this->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 InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{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 InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{points_len - 2, points_len - 1, 1.0f}; +} + +/* Use a spline argument to avoid adding this to the header. */ +template<typename T> +static void interpolate_to_evaluated_points_impl(const BezierSpline &spline, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + Span<float> mappings = spline.evaluated_mappings(); + + for (const int i : result_data.index_range()) { + BezierSpline::InterpolationData interp = spline.interpolation_data_from_index_factor( + mappings[i]); + + const T &value = source_data[interp.control_point_index]; + const T &next_value = source_data[interp.next_control_point_index]; + + result_data[i] = blender::attribute_math::mix2(interp.factor, value, next_value); + } +} + +blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + 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(); + } + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(eval_size); + interpolate_to_evaluated_points_impl<T>(*this, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc new file mode 100644 index 00000000000..bfb0d652b1a --- /dev/null +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -0,0 +1,444 @@ +/* + * 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. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; +using blender::fn::GVArray_Typed; + +SplinePtr NURBSpline::copy() const +{ + return std::make_unique<NURBSpline>(*this); +} + +SplinePtr NURBSpline::copy_settings() const +{ + std::unique_ptr<NURBSpline> copy = std::make_unique<NURBSpline>(); + copy_base_settings(*this, *copy); + copy->knots_mode = knots_mode; + copy->resolution_ = resolution_; + copy->order_ = order_; + return copy; +} + +int NURBSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + BLI_assert(size == weights_.size()); + return size; +} + +int NURBSpline::resolution() const +{ + return resolution_; +} + +void NURBSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = value; + this->mark_cache_invalid(); +} + +uint8_t NURBSpline::order() const +{ + return order_; +} + +void NURBSpline::set_order(const uint8_t value) +{ + BLI_assert(value >= 2 && value <= 6); + order_ = 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, + const float weight) +{ + positions_.append(position); + radii_.append(radius); + tilts_.append(tilt); + weights_.append(weight); + knots_dirty_ = true; + 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_; +} +Span<float3> NURBSpline::positions() const +{ + return positions_; +} +MutableSpan<float> NURBSpline::radii() +{ + return radii_; +} +Span<float> NURBSpline::radii() const +{ + return radii_; +} +MutableSpan<float> NURBSpline::tilts() +{ + return tilts_; +} +Span<float> NURBSpline::tilts() const +{ + return tilts_; +} +MutableSpan<float> NURBSpline::weights() +{ + return weights_; +} +Span<float> NURBSpline::weights() const +{ + return weights_; +} + +void NURBSpline::mark_cache_invalid() +{ + basis_cache_dirty_ = true; + position_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int NURBSpline::evaluated_points_size() const +{ + if (!this->check_valid_size_and_order()) { + return 0; + } + return resolution_ * this->segments_size(); +} + +void NURBSpline::correct_end_tangents() const +{ +} + +bool NURBSpline::check_valid_size_and_order() const +{ + if (this->size() < order_) { + return false; + } + + if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) { + if (order_ == 4) { + if (this->size() < 5) { + return false; + } + } + else if (order_ != 3) { + return false; + } + } + + return true; +} + +int NURBSpline::knots_size() const +{ + const int size = this->size() + order_; + return is_cyclic_ ? size + order_ - 1 : size; +} + +void NURBSpline::calculate_knots() const +{ + const KnotsMode mode = this->knots_mode; + const int length = this->size(); + const int order = order_; + + knots_.resize(this->knots_size()); + + MutableSpan<float> knots = knots_; + + if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) { + for (const int i : knots.index_range()) { + knots[i] = static_cast<float>(i); + } + } + else if (mode == NURBSpline::KnotsMode::EndPoint) { + float k = 0.0f; + for (const int i : IndexRange(1, knots.size())) { + knots[i - 1] = k; + if (i >= order && i <= length) { + k += 1.0f; + } + } + } + else if (mode == NURBSpline::KnotsMode::Bezier) { + BLI_assert(ELEM(order, 3, 4)); + if (order == 3) { + float k = 0.6f; + for (const int i : knots.index_range()) { + if (i >= order && i <= length) { + k += 0.5f; + } + knots[i] = std::floor(k); + } + } + else { + float k = 0.34f; + for (const int i : knots.index_range()) { + knots[i] = std::floor(k); + k += 1.0f / 3.0f; + } + } + } + + if (is_cyclic_) { + const int b = length + order - 1; + if (order > 2) { + for (const int i : IndexRange(1, order - 2)) { + if (knots[b] != knots[b - i]) { + if (i == order - 1) { + knots[length + order - 2] += 1.0f; + break; + } + } + } + } + + int c = order; + for (int i = b; i < this->knots_size(); i++) { + knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]); + c--; + } + } +} + +Span<float> NURBSpline::knots() const +{ + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + std::lock_guard lock{knots_mutex_}; + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + this->calculate_knots(); + + knots_dirty_ = false; + + return knots_; +} + +static void calculate_basis_for_point(const float parameter, + const int points_len, + const int order, + Span<float> knots, + MutableSpan<float> basis_buffer, + NURBSpline::BasisCache &basis_cache) +{ + /* Clamp parameter due to floating point inaccuracy. */ + const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]); + + int start = 0; + int end = 0; + for (const int i : IndexRange(points_len + order - 1)) { + const bool knots_equal = knots[i] == knots[i + 1]; + if (knots_equal || t < knots[i] || t > knots[i + 1]) { + basis_buffer[i] = 0.0f; + continue; + } + + basis_buffer[i] = 1.0f; + start = std::max(i - order - 1, 0); + end = i; + basis_buffer.slice(i + 1, points_len + order - 1 - i).fill(0.0f); + break; + } + basis_buffer[points_len + order - 1] = 0.0f; + + for (const int i_order : IndexRange(2, order - 1)) { + if (end + i_order >= points_len + order) { + end = points_len + order - 1 - i_order; + } + for (const int i : IndexRange(start, end - start + 1)) { + float new_basis = 0.0f; + if (basis_buffer[i] != 0.0f) { + new_basis += ((t - knots[i]) * basis_buffer[i]) / (knots[i + i_order - 1] - knots[i]); + } + + if (basis_buffer[i + 1] != 0.0f) { + new_basis += ((knots[i + i_order] - t) * basis_buffer[i + 1]) / + (knots[i + i_order] - knots[i + 1]); + } + + basis_buffer[i] = new_basis; + } + } + + /* Shrink the range of calculated values to avoid storing unnecessary zeros. */ + while (basis_buffer[start] == 0.0f && start < end) { + start++; + } + while (basis_buffer[end] == 0.0f && end > start) { + end--; + } + + basis_cache.weights.clear(); + basis_cache.weights.extend(basis_buffer.slice(start, end - start + 1)); + basis_cache.start_index = start; +} + +void NURBSpline::calculate_basis_cache() const +{ + if (!basis_cache_dirty_) { + return; + } + + std::lock_guard lock{basis_cache_mutex_}; + if (!basis_cache_dirty_) { + return; + } + + const int points_len = this->size(); + const int eval_size = this->evaluated_points_size(); + BLI_assert(this->evaluated_edges_size() > 0); + basis_cache_.resize(eval_size); + + const int order = this->order(); + Span<float> control_weights = this->weights(); + Span<float> knots = this->knots(); + + MutableSpan<BasisCache> basis_cache(basis_cache_); + + /* This buffer is reused by each basis calculation to store temporary values. + * Theoretically it could be optimized away in the future. */ + Array<float> basis_buffer(this->knots_size()); + + const float start = knots[order - 1]; + const float end = is_cyclic_ ? knots[points_len + order - 1] : knots[points_len]; + const float step = (end - start) / this->evaluated_edges_size(); + float parameter = start; + for (const int i : IndexRange(eval_size)) { + BasisCache &basis = basis_cache[i]; + calculate_basis_for_point( + parameter, points_len + (is_cyclic_ ? order - 1 : 0), order, knots, basis_buffer, basis); + BLI_assert(basis.weights.size() <= order); + + for (const int j : basis.weights.index_range()) { + const int point_index = (basis.start_index + j) % points_len; + basis.weights[j] *= control_weights[point_index]; + } + + parameter += step; + } + + basis_cache_dirty_ = false; +} + +template<typename T> +void interpolate_to_evaluated_points_impl(Span<NURBSpline::BasisCache> weights, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + const int points_len = source_data.size(); + BLI_assert(result_data.size() == weights.size()); + blender::attribute_math::DefaultMixer<T> mixer(result_data); + + for (const int i : result_data.index_range()) { + Span<float> point_weights = weights[i].weights; + const int start_index = weights[i].start_index; + + for (const int j : point_weights.index_range()) { + const int point_index = (start_index + j) % points_len; + mixer.mix_in(i, source_data[point_index], point_weights[j]); + } + } + + mixer.finalize(); +} + +blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + 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_); + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(this->evaluated_points_size()); + interpolate_to_evaluated_points_impl<T>(weights, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} + +Span<float3> NURBSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_position_cache_.resize(eval_size); + + /* TODO: Avoid copying the evaluated data from the temporary array. */ + GVArray_Typed<float3> evaluated = Spline::interpolate_to_evaluated_points(positions_.as_span()); + evaluated->materialize(evaluated_position_cache_); + + position_cache_dirty_ = false; + return evaluated_position_cache_; +} diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc new file mode 100644 index 00000000000..5f8e81d5ad0 --- /dev/null +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -0,0 +1,124 @@ +/* + * 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. + */ + +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_spline.hh" + +using blender::float3; +using blender::MutableSpan; +using blender::Span; + +SplinePtr PolySpline::copy() const +{ + return std::make_unique<PolySpline>(*this); +} + +SplinePtr PolySpline::copy_settings() const +{ + std::unique_ptr<PolySpline> copy = std::make_unique<PolySpline>(); + copy_base_settings(*this, *copy); + return copy; +} + +int PolySpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + 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); + radii_.append(radius); + tilts_.append(tilt); + 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_; +} +Span<float3> PolySpline::positions() const +{ + return positions_; +} +MutableSpan<float> PolySpline::radii() +{ + return radii_; +} +Span<float> PolySpline::radii() const +{ + return radii_; +} +MutableSpan<float> PolySpline::tilts() +{ + return tilts_; +} +Span<float> PolySpline::tilts() const +{ + return tilts_; +} + +void PolySpline::mark_cache_invalid() +{ + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int PolySpline::evaluated_points_size() const +{ + return this->size(); +} + +void PolySpline::correct_end_tangents() const +{ +} + +Span<float3> PolySpline::evaluated_positions() const +{ + return this->positions(); +} + +/** + * Poly spline interpolation from control points to evaluated points is a special case, since + * the result data is the same as the input data. This function returns a GVArray that points to + * the original data. Therefore the lifetime of the returned virtual array must not be longer than + * the source data. + */ +blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + return source_data.shallow_copy(); +} diff --git a/source/blender/blenkernel/intern/subdiv_ccg.c b/source/blender/blenkernel/intern/subdiv_ccg.c index a59f9e0c633..5f732ba91ab 100644 --- a/source/blender/blenkernel/intern/subdiv_ccg.c +++ b/source/blender/blenkernel/intern/subdiv_ccg.c @@ -28,12 +28,14 @@ #include "MEM_guardedalloc.h" +#include "BLI_ghash.h" #include "BLI_math_bits.h" #include "BLI_math_vector.h" #include "BLI_task.h" #include "BKE_DerivedMesh.h" #include "BKE_ccg.h" +#include "BKE_global.h" #include "BKE_mesh.h" #include "BKE_subdiv.h" #include "BKE_subdiv_eval.h" @@ -50,6 +52,11 @@ static void subdiv_ccg_average_inner_face_grids(SubdivCCG *subdiv_ccg, CCGKey *key, SubdivCCGFace *face); +void subdiv_ccg_average_faces_boundaries_and_corners(SubdivCCG *subdiv_ccg, + CCGKey *key, + struct CCGFace **effected_faces, + int num_effected_faces); + /** \} */ /* -------------------------------------------------------------------- */ @@ -889,11 +896,12 @@ void BKE_subdiv_ccg_update_normals(SubdivCCG *subdiv_ccg, return; } subdiv_ccg_recalc_modified_inner_grid_normals(subdiv_ccg, effected_faces, num_effected_faces); - /* TODO(sergey): Only average elements which are adjacent to modified - * faces. */ + CCGKey key; BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg); - subdiv_ccg_average_all_boundaries_and_corners(subdiv_ccg, &key); + + subdiv_ccg_average_faces_boundaries_and_corners( + subdiv_ccg, &key, effected_faces, num_effected_faces); } /** \} */ @@ -1032,6 +1040,9 @@ static void subdiv_ccg_average_inner_grids_task(void *__restrict userdata_v, typedef struct AverageGridsBoundariesData { SubdivCCG *subdiv_ccg; CCGKey *key; + + /* Optional lookup table. Maps task index to index in `subdiv_ccg->adjacent_vertices`. */ + int *adjacent_edge_index_map; } AverageGridsBoundariesData; typedef struct AverageGridsBoundariesTLSData { @@ -1079,10 +1090,14 @@ static void subdiv_ccg_average_grids_boundary(SubdivCCG *subdiv_ccg, } static void subdiv_ccg_average_grids_boundaries_task(void *__restrict userdata_v, - const int adjacent_edge_index, + const int n, const TaskParallelTLS *__restrict tls_v) { AverageGridsBoundariesData *data = userdata_v; + const int adjacent_edge_index = data->adjacent_edge_index_map ? + data->adjacent_edge_index_map[n] : + n; + AverageGridsBoundariesTLSData *tls = tls_v->userdata_chunk; SubdivCCG *subdiv_ccg = data->subdiv_ccg; CCGKey *key = data->key; @@ -1100,6 +1115,9 @@ static void subdiv_ccg_average_grids_boundaries_free(const void *__restrict UNUS typedef struct AverageGridsCornerData { SubdivCCG *subdiv_ccg; CCGKey *key; + + /* Optional lookup table. Maps task range index to index in subdiv_ccg->adjacent_vertices*/ + int *adjacent_vert_index_map; } AverageGridsCornerData; static void subdiv_ccg_average_grids_corners(SubdivCCG *subdiv_ccg, @@ -1128,49 +1146,63 @@ static void subdiv_ccg_average_grids_corners(SubdivCCG *subdiv_ccg, } static void subdiv_ccg_average_grids_corners_task(void *__restrict userdata_v, - const int adjacent_vertex_index, + const int n, const TaskParallelTLS *__restrict UNUSED(tls_v)) { AverageGridsCornerData *data = userdata_v; + const int adjacent_vertex_index = data->adjacent_vert_index_map ? + data->adjacent_vert_index_map[n] : + n; SubdivCCG *subdiv_ccg = data->subdiv_ccg; CCGKey *key = data->key; SubdivCCGAdjacentVertex *adjacent_vertex = &subdiv_ccg->adjacent_vertices[adjacent_vertex_index]; subdiv_ccg_average_grids_corners(subdiv_ccg, key, adjacent_vertex); } -static void subdiv_ccg_average_all_boundaries(SubdivCCG *subdiv_ccg, CCGKey *key) +static void subdiv_ccg_average_boundaries(SubdivCCG *subdiv_ccg, + CCGKey *key, + int *adjacent_edge_index_map, + int num_adjacent_edges) { TaskParallelSettings parallel_range_settings; BLI_parallel_range_settings_defaults(¶llel_range_settings); AverageGridsBoundariesData boundaries_data = { - .subdiv_ccg = subdiv_ccg, - .key = key, - }; + .subdiv_ccg = subdiv_ccg, .key = key, .adjacent_edge_index_map = adjacent_edge_index_map}; AverageGridsBoundariesTLSData tls_data = {NULL}; parallel_range_settings.userdata_chunk = &tls_data; parallel_range_settings.userdata_chunk_size = sizeof(tls_data); parallel_range_settings.func_free = subdiv_ccg_average_grids_boundaries_free; BLI_task_parallel_range(0, - subdiv_ccg->num_adjacent_edges, + num_adjacent_edges, &boundaries_data, subdiv_ccg_average_grids_boundaries_task, ¶llel_range_settings); } -static void subdiv_ccg_average_all_corners(SubdivCCG *subdiv_ccg, CCGKey *key) +static void subdiv_ccg_average_all_boundaries(SubdivCCG *subdiv_ccg, CCGKey *key) +{ + subdiv_ccg_average_boundaries(subdiv_ccg, key, NULL, subdiv_ccg->num_adjacent_edges); +} + +static void subdiv_ccg_average_corners(SubdivCCG *subdiv_ccg, + CCGKey *key, + int *adjacent_vert_index_map, + int num_adjacent_vertices) { TaskParallelSettings parallel_range_settings; BLI_parallel_range_settings_defaults(¶llel_range_settings); AverageGridsCornerData corner_data = { - .subdiv_ccg = subdiv_ccg, - .key = key, - }; + .subdiv_ccg = subdiv_ccg, .key = key, .adjacent_vert_index_map = adjacent_vert_index_map}; BLI_task_parallel_range(0, - subdiv_ccg->num_adjacent_vertices, + num_adjacent_vertices, &corner_data, subdiv_ccg_average_grids_corners_task, ¶llel_range_settings); } +static void subdiv_ccg_average_all_corners(SubdivCCG *subdiv_ccg, CCGKey *key) +{ + subdiv_ccg_average_corners(subdiv_ccg, key, NULL, subdiv_ccg->num_adjacent_vertices); +} static void subdiv_ccg_average_all_boundaries_and_corners(SubdivCCG *subdiv_ccg, CCGKey *key) { @@ -1198,6 +1230,98 @@ void BKE_subdiv_ccg_average_grids(SubdivCCG *subdiv_ccg) subdiv_ccg_average_all_boundaries_and_corners(subdiv_ccg, &key); } +static void subdiv_ccg_affected_face_adjacency(SubdivCCG *subdiv_ccg, + struct CCGFace **effected_faces, + int num_effected_faces, + GSet *r_adjacent_vertices, + GSet *r_adjacent_edges) +{ + Subdiv *subdiv = subdiv_ccg->subdiv; + OpenSubdiv_TopologyRefiner *topology_refiner = subdiv->topology_refiner; + + StaticOrHeapIntStorage face_vertices_storage; + StaticOrHeapIntStorage face_edges_storage; + + static_or_heap_storage_init(&face_vertices_storage); + static_or_heap_storage_init(&face_edges_storage); + + for (int i = 0; i < num_effected_faces; i++) { + SubdivCCGFace *face = (SubdivCCGFace *)effected_faces[i]; + int face_index = face - subdiv_ccg->faces; + const int num_face_grids = face->num_grids; + const int num_face_edges = num_face_grids; + int *face_vertices = static_or_heap_storage_get(&face_vertices_storage, num_face_edges); + topology_refiner->getFaceVertices(topology_refiner, face_index, face_vertices); + + /* Note that order of edges is same as order of MLoops, which also + * means it's the same as order of grids. */ + int *face_edges = static_or_heap_storage_get(&face_edges_storage, num_face_edges); + topology_refiner->getFaceEdges(topology_refiner, face_index, face_edges); + for (int corner = 0; corner < num_face_edges; corner++) { + const int vertex_index = face_vertices[corner]; + const int edge_index = face_edges[corner]; + + int edge_vertices[2]; + topology_refiner->getEdgeVertices(topology_refiner, edge_index, edge_vertices); + + SubdivCCGAdjacentEdge *adjacent_edge = &subdiv_ccg->adjacent_edges[edge_index]; + BLI_gset_add(r_adjacent_edges, adjacent_edge); + + SubdivCCGAdjacentVertex *adjacent_vertex = &subdiv_ccg->adjacent_vertices[vertex_index]; + BLI_gset_add(r_adjacent_vertices, adjacent_vertex); + } + } + + static_or_heap_storage_free(&face_vertices_storage); + static_or_heap_storage_free(&face_edges_storage); +} + +void subdiv_ccg_average_faces_boundaries_and_corners(SubdivCCG *subdiv_ccg, + CCGKey *key, + struct CCGFace **effected_faces, + int num_effected_faces) +{ + GSet *adjacent_vertices = BLI_gset_ptr_new(__func__); + GSet *adjacent_edges = BLI_gset_ptr_new(__func__); + GSetIterator gi; + + subdiv_ccg_affected_face_adjacency( + subdiv_ccg, effected_faces, num_effected_faces, adjacent_vertices, adjacent_edges); + + int *adjacent_vertex_index_map; + int *adjacent_edge_index_map; + + StaticOrHeapIntStorage index_heap; + static_or_heap_storage_init(&index_heap); + + int i = 0; + + /* Average boundaries. */ + + adjacent_edge_index_map = static_or_heap_storage_get(&index_heap, BLI_gset_len(adjacent_edges)); + GSET_ITER_INDEX (gi, adjacent_edges, i) { + SubdivCCGAdjacentEdge *adjacent_edge = BLI_gsetIterator_getKey(&gi); + adjacent_edge_index_map[i] = adjacent_edge - subdiv_ccg->adjacent_edges; + } + subdiv_ccg_average_boundaries( + subdiv_ccg, key, adjacent_edge_index_map, BLI_gset_len(adjacent_edges)); + + /* Average corners. */ + + adjacent_vertex_index_map = static_or_heap_storage_get(&index_heap, + BLI_gset_len(adjacent_vertices)); + GSET_ITER_INDEX (gi, adjacent_vertices, i) { + SubdivCCGAdjacentVertex *adjacent_vertex = BLI_gsetIterator_getKey(&gi); + adjacent_vertex_index_map[i] = adjacent_vertex - subdiv_ccg->adjacent_vertices; + } + subdiv_ccg_average_corners( + subdiv_ccg, key, adjacent_vertex_index_map, BLI_gset_len(adjacent_vertices)); + + BLI_gset_free(adjacent_vertices, NULL); + BLI_gset_free(adjacent_edges, NULL); + static_or_heap_storage_free(&index_heap); +} + typedef struct StitchFacesInnerGridsData { SubdivCCG *subdiv_ccg; CCGKey *key; diff --git a/source/blender/blenkernel/intern/subsurf_ccg.c b/source/blender/blenkernel/intern/subsurf_ccg.c index a28136f8527..23eccbfba9b 100644 --- a/source/blender/blenkernel/intern/subsurf_ccg.c +++ b/source/blender/blenkernel/intern/subsurf_ccg.c @@ -1879,12 +1879,11 @@ static const MeshElemMap *ccgDM_getPolyMap(Object *ob, DerivedMesh *dm) /* WARNING! *MUST* be called in an 'loops_cache_rwlock' protected thread context! */ static void ccgDM_recalcLoopTri(DerivedMesh *dm) { - MLoopTri *mlooptri = dm->looptris.array; const int tottri = dm->numPolyData * 2; int i, poly_index; DM_ensure_looptri_data(dm); - mlooptri = dm->looptris.array_wip; + MLoopTri *mlooptri = dm->looptris.array_wip; BLI_assert(tottri == 0 || mlooptri != NULL); BLI_assert(poly_to_tri_count(dm->numPolyData, dm->numLoopData) == dm->looptris.num); diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index 43d587861a5..27f5593c2ca 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -171,6 +171,9 @@ static void text_free_data(ID *id) static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address) { + if (id->us < 1 && !BLO_write_is_undo(writer)) { + return; + } Text *text = (Text *)id; /* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */ @@ -231,8 +234,6 @@ static void text_blend_read_data(BlendDataReader *reader, ID *id) } text->flags = (text->flags) & ~TXT_ISEXT; - - id_us_ensure_real(&text->id); } IDTypeInfo IDType_ID_TXT = { @@ -293,8 +294,10 @@ Text *BKE_text_add(Main *bmain, const char *name) Text *ta; ta = BKE_id_new(bmain, ID_TXT, name); - /* Texts always have 'real' user (see also read code). */ - id_us_ensure_real(&ta->id); + /* Texts have no users by default... Set the fake user flag to ensure that this text block + * doesn't get deleted by default when cleaning up data blocks. */ + id_us_min(&ta->id); + id_fake_user_set(&ta->id); return ta; } @@ -468,7 +471,7 @@ bool BKE_text_reload(Text *text) * \param is_internal: If \a true, this text data-block only exists in memory, * not as a file on disk. * - * \note text data-blocks have no user by default, only the 'real user' flag. + * \note text data-blocks have no real user but have 'fake user' enabled by default */ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal) { @@ -489,9 +492,8 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const } ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0); - /* Texts have no user by default... Only the 'real' user flag. */ - id_us_ensure_real(&ta->id); id_us_min(&ta->id); + id_fake_user_set(&ta->id); BLI_listbase_clear(&ta->lines); ta->curl = ta->sell = NULL; @@ -523,7 +525,8 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const return ta; } -/** Load a text file. +/** + * Load a text file. * * \note Text data-blocks have no user by default, only the 'real user' flag. */ diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c index d124922acd1..f3d6bc4a6e3 100644 --- a/source/blender/blenkernel/intern/tracking.c +++ b/source/blender/blenkernel/intern/tracking.c @@ -3251,6 +3251,11 @@ static void tracking_dopesheet_calc_coverage(MovieTracking *tracking) end_frame = max_ii(end_frame, track->markers[track->markersnr - 1].framenr); } + if (start_frame > end_frame) { + /* There are no markers at all, nothing to calculate coverage from. */ + return; + } + frames = end_frame - start_frame + 1; /* this is a per-frame counter of markers (how many markers belongs to the same frame) */ diff --git a/source/blender/blenkernel/intern/unit.c b/source/blender/blenkernel/intern/unit.c index 9ae1c754846..5cf76bb6452 100644 --- a/source/blender/blenkernel/intern/unit.c +++ b/source/blender/blenkernel/intern/unit.c @@ -358,6 +358,7 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = { NULL, &buNaturalRotCollection, &buNaturalTimeCollection, + &buNaturalTimeCollection, NULL, NULL, NULL, @@ -371,6 +372,7 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = { &buMetricMassCollection, &buNaturalRotCollection, &buNaturalTimeCollection, + &buNaturalTimeCollection, &buMetricVelCollection, &buMetricAclCollection, &buCameraLenCollection, @@ -384,12 +386,13 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = { &buImperialMassCollection, &buNaturalRotCollection, &buNaturalTimeCollection, + &buNaturalTimeCollection, &buImperialVelCollection, &buImperialAclCollection, &buCameraLenCollection, &buPowerCollection, &buImperialTempCollection}, - {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, }; static const bUnitCollection *unit_get_system(int system, int type) @@ -943,7 +946,7 @@ static int unit_scale_str(char *str, /* Add the addition sign, the bias, and the close parenthesis after the value. */ int value_end_ofs = find_end_of_value_chars(str, len_max, prev_op_ofs + 2); - int len_bias_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias); + int len_bias_num = BLI_snprintf_rlen(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias); if (value_end_ofs + len_bias_num < len_max) { memmove(str + value_end_ofs + len_bias_num, str + value_end_ofs, len - value_end_ofs + 1); memcpy(str + value_end_ofs, str_tmp, len_bias_num); @@ -957,7 +960,8 @@ static int unit_scale_str(char *str, int len_move = (len - (found_ofs + len_name)) + 1; /* 1+ to copy the string terminator. */ /* "#" Removed later */ - int len_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref); + int len_num = BLI_snprintf_rlen( + str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref); if (len_num > len_max) { len_num = len_max; diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 9b5231f7d6f..c0ce57818d1 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -28,7 +28,10 @@ #include "BLI_compiler_compat.h" #include "BLI_fileops.h" +#include "BLI_float3.hh" +#include "BLI_float4x4.hh" #include "BLI_ghash.h" +#include "BLI_index_range.hh" #include "BLI_map.hh" #include "BLI_math.h" #include "BLI_path_util.h" @@ -36,6 +39,7 @@ #include "BLI_utildefines.h" #include "BKE_anim_data.h" +#include "BKE_geometry_set.hh" #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" @@ -63,6 +67,10 @@ static CLG_LogRef LOG = {"bke.volume"}; #define VOLUME_FRAME_NONE INT_MAX +using blender::float3; +using blender::float4x4; +using blender::IndexRange; + #ifdef WITH_OPENVDB # include <atomic> # include <list> @@ -144,14 +152,14 @@ static struct VolumeFileCache { blender::Map<int, openvdb::GridBase::Ptr> simplified_grids; /* Has the grid tree been loaded? */ - bool is_loaded; + mutable bool is_loaded; /* Error message if an error occurred while loading. */ std::string error_msg; /* User counting. */ int num_metadata_users; int num_tree_users; /* Mutex for on-demand reading of tree. */ - std::mutex mutex; + mutable std::mutex mutex; }; struct EntryHasher { @@ -171,10 +179,6 @@ static struct VolumeFileCache { }; /* Cache */ - VolumeFileCache() - { - } - ~VolumeFileCache() { BLI_assert(cache.empty()); @@ -293,7 +297,7 @@ struct VolumeGrid { } } - void load(const char *volume_name, const char *filepath) + void load(const char *volume_name, const char *filepath) const { /* If already loaded or not file-backed, nothing to do. */ if (is_loaded || entry == nullptr) { @@ -335,7 +339,7 @@ struct VolumeGrid { is_loaded = true; } - void unload(const char *volume_name) + void unload(const char *volume_name) const { /* Not loaded or not file-backed, nothing to do. */ if (!is_loaded || entry == nullptr) { @@ -436,10 +440,14 @@ struct VolumeGrid { int simplify_level = 0; /* OpenVDB grid if it's not shared through the file cache. */ openvdb::GridBase::Ptr local_grid; - /* Indicates if the tree has been loaded for this grid. Note that vdb.tree() + /** + * Indicates if the tree has been loaded for this grid. Note that vdb.tree() * may actually be loaded by another user while this is false. But only after - * calling load() and is_loaded changes to true is it safe to access. */ - bool is_loaded; + * calling load() and is_loaded changes to true is it safe to access. + * + * Const write access to this must be protected by `entry->mutex`. + */ + mutable bool is_loaded; }; /* Volume Grid Vector @@ -472,14 +480,15 @@ struct VolumeGridVector : public std::list<VolumeGrid> { metadata.reset(); } + /* Mutex for file loading of grids list. Const write access to the fields after this must be + * protected by locking with this mutex. */ + mutable std::mutex mutex; /* Absolute file path that grids have been loaded from. */ char filepath[FILE_MAX]; /* File loading error message. */ std::string error_msg; /* File Metadata. */ openvdb::MetaMap::Ptr metadata; - /* Mutex for file loading of grids list. */ - std::mutex mutex; }; #endif @@ -766,10 +775,10 @@ bool BKE_volume_is_loaded(const Volume *volume) #endif } -bool BKE_volume_load(Volume *volume, Main *bmain) +bool BKE_volume_load(const Volume *volume, const Main *bmain) { #ifdef WITH_OPENVDB - VolumeGridVector &grids = *volume->runtime.grids; + const VolumeGridVector &const_grids = *volume->runtime.grids; if (volume->runtime.frame == VOLUME_FRAME_NONE) { /* Skip loading this frame, outside of sequence range. */ @@ -777,15 +786,19 @@ bool BKE_volume_load(Volume *volume, Main *bmain) } if (BKE_volume_is_loaded(volume)) { - return grids.error_msg.empty(); + return const_grids.error_msg.empty(); } /* Double-checked lock. */ - std::lock_guard<std::mutex> lock(grids.mutex); + std::lock_guard<std::mutex> lock(const_grids.mutex); if (BKE_volume_is_loaded(volume)) { - return grids.error_msg.empty(); + return const_grids.error_msg.empty(); } + /* Guarded by the lock, we can continue to access the grid vector, + * adding error messages or a new grid, etc. */ + VolumeGridVector &grids = const_cast<VolumeGridVector &>(const_grids); + /* Get absolute file path at current frame. */ const char *volume_name = volume->id.name + 2; char filepath[FILE_MAX]; @@ -850,7 +863,10 @@ void BKE_volume_unload(Volume *volume) /* File Save */ -bool BKE_volume_save(Volume *volume, Main *bmain, ReportList *reports, const char *filepath) +bool BKE_volume_save(const Volume *volume, + const Main *bmain, + ReportList *reports, + const char *filepath) { #ifdef WITH_OPENVDB if (!BKE_volume_load(volume, bmain)) { @@ -882,6 +898,32 @@ bool BKE_volume_save(Volume *volume, Main *bmain, ReportList *reports, const cha #endif } +bool BKE_volume_min_max(const Volume *volume, float3 &r_min, float3 &r_max) +{ + bool have_minmax = false; +#ifdef WITH_OPENVDB + /* TODO: if we know the volume is going to be displayed, it may be good to + * load it as part of dependency graph evaluation for better threading. We + * could also share the bounding box computation in the global volume cache. */ + if (BKE_volume_load(const_cast<Volume *>(volume), G.main)) { + for (const int i : IndexRange(BKE_volume_num_grids(volume))) { + const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); + openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + float3 grid_min; + float3 grid_max; + if (BKE_volume_grid_bounds(grid, grid_min, grid_max)) { + DO_MIN(grid_min, r_min); + DO_MAX(grid_max, r_max); + have_minmax = true; + } + } + } +#else + UNUSED_VARS(volume, r_min, r_max); +#endif + return have_minmax; +} + BoundBox *BKE_volume_boundbox_get(Object *ob) { BLI_assert(ob->type == OB_VOLUME); @@ -891,41 +933,20 @@ BoundBox *BKE_volume_boundbox_get(Object *ob) } if (ob->runtime.bb == nullptr) { - Volume *volume = (Volume *)ob->data; - - ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "volume boundbox"); - - float min[3], max[3]; - bool have_minmax = false; - INIT_MINMAX(min, max); - - /* TODO: if we know the volume is going to be displayed, it may be good to - * load it as part of dependency graph evaluation for better threading. We - * could also share the bounding box computation in the global volume cache. */ - if (BKE_volume_load(volume, G.main)) { - const int num_grids = BKE_volume_num_grids(volume); - - for (int i = 0; i < num_grids; i++) { - VolumeGrid *grid = BKE_volume_grid_get(volume, i); - float grid_min[3], grid_max[3]; - - BKE_volume_grid_load(volume, grid); - if (BKE_volume_grid_bounds(grid, grid_min, grid_max)) { - DO_MIN(grid_min, min); - DO_MAX(grid_max, max); - have_minmax = true; - } - } - } + ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); + } - if (!have_minmax) { - min[0] = min[1] = min[2] = -1.0f; - max[0] = max[1] = max[2] = 1.0f; - } + const Volume *volume = (Volume *)ob->data; - BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + float3 min, max; + INIT_MINMAX(min, max); + if (!BKE_volume_min_max(volume, min, max)) { + min = float3(-1); + max = float3(1); } + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + return ob->runtime.bb; } @@ -957,7 +978,7 @@ bool BKE_volume_is_points_only(const Volume *volume) } for (int i = 0; i < num_grids; i++) { - VolumeGrid *grid = BKE_volume_grid_get(volume, i); + const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); if (BKE_volume_grid_type(grid) != VOLUME_GRID_POINTS) { return false; } @@ -983,13 +1004,11 @@ static void volume_update_simplify_level(Volume *volume, const Depsgraph *depsgr #endif } -static Volume *volume_evaluate_modifiers(struct Depsgraph *depsgraph, - struct Scene *scene, - Object *object, - Volume *volume_input) +static void volume_evaluate_modifiers(struct Depsgraph *depsgraph, + struct Scene *scene, + Object *object, + GeometrySet &geometry_set) { - Volume *volume = volume_input; - /* Modifier evaluation modes. */ const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); const int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; @@ -1010,25 +1029,10 @@ static Volume *volume_evaluate_modifiers(struct Depsgraph *depsgraph, continue; } - if (mti->modifyVolume) { - /* Ensure we are not modifying the input. */ - if (volume == volume_input) { - volume = BKE_volume_copy_for_eval(volume, true); - } - - Volume *volume_next = mti->modifyVolume(md, &mectx, volume); - - if (volume_next && volume_next != volume) { - /* If the modifier returned a new volume, release the old one. */ - if (volume != volume_input) { - BKE_id_free(nullptr, volume); - } - volume = volume_next; - } + if (mti->modifyGeometrySet) { + mti->modifyGeometrySet(md, &mectx, &geometry_set); } } - - return volume; } void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume) @@ -1052,6 +1056,24 @@ void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume) } } +static Volume *take_volume_ownership_from_geometry_set(GeometrySet &geometry_set) +{ + if (!geometry_set.has<VolumeComponent>()) { + return nullptr; + } + VolumeComponent &volume_component = geometry_set.get_component_for_write<VolumeComponent>(); + Volume *volume = volume_component.release(); + if (volume != nullptr) { + /* Add back, but only as read-only non-owning component. */ + volume_component.replace(volume, GeometryOwnershipType::ReadOnly); + } + else { + /* The component was empty, we can remove it. */ + geometry_set.remove<VolumeComponent>(); + } + return volume; +} + void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Object *object) { /* Free any evaluated data and restore original data. */ @@ -1059,11 +1081,21 @@ void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Ob /* Evaluate modifiers. */ Volume *volume = (Volume *)object->data; - Volume *volume_eval = volume_evaluate_modifiers(depsgraph, scene, object, volume); + GeometrySet geometry_set; + geometry_set.replace_volume(volume, GeometryOwnershipType::ReadOnly); + volume_evaluate_modifiers(depsgraph, scene, object, geometry_set); + + Volume *volume_eval = take_volume_ownership_from_geometry_set(geometry_set); + + /* If the geometry set did not contain a volume, we still create an empty one. */ + if (volume_eval == nullptr) { + volume_eval = BKE_volume_new_for_eval(volume); + } /* Assign evaluated object. */ - const bool is_owned = (volume != volume_eval); - BKE_object_eval_assign_data(object, &volume_eval->id, is_owned); + const bool eval_is_owned = (volume != volume_eval); + BKE_object_eval_assign_data(object, &volume_eval->id, eval_is_owned); + object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set)); } void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath) @@ -1144,7 +1176,23 @@ const char *BKE_volume_grids_frame_filepath(const Volume *volume) #endif } -VolumeGrid *BKE_volume_grid_get(const Volume *volume, int grid_index) +const VolumeGrid *BKE_volume_grid_get_for_read(const Volume *volume, int grid_index) +{ +#ifdef WITH_OPENVDB + const VolumeGridVector &grids = *volume->runtime.grids; + for (const VolumeGrid &grid : grids) { + if (grid_index-- == 0) { + return &grid; + } + } + return nullptr; +#else + UNUSED_VARS(volume, grid_index); + return nullptr; +#endif +} + +VolumeGrid *BKE_volume_grid_get_for_write(Volume *volume, int grid_index) { #ifdef WITH_OPENVDB VolumeGridVector &grids = *volume->runtime.grids; @@ -1160,7 +1208,7 @@ VolumeGrid *BKE_volume_grid_get(const Volume *volume, int grid_index) #endif } -VolumeGrid *BKE_volume_grid_active_get(const Volume *volume) +const VolumeGrid *BKE_volume_grid_active_get_for_read(const Volume *volume) { const int num_grids = BKE_volume_num_grids(volume); if (num_grids == 0) { @@ -1168,15 +1216,15 @@ VolumeGrid *BKE_volume_grid_active_get(const Volume *volume) } const int index = clamp_i(volume->active_grid, 0, num_grids - 1); - return BKE_volume_grid_get(volume, index); + return BKE_volume_grid_get_for_read(volume, index); } /* Tries to find a grid with the given name. Make sure that that the volume has been loaded. */ -VolumeGrid *BKE_volume_grid_find(const Volume *volume, const char *name) +const VolumeGrid *BKE_volume_grid_find_for_read(const Volume *volume, const char *name) { int num_grids = BKE_volume_num_grids(volume); for (int i = 0; i < num_grids; i++) { - VolumeGrid *grid = BKE_volume_grid_get(volume, i); + const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); if (STREQ(BKE_volume_grid_name(grid), name)) { return grid; } @@ -1187,7 +1235,7 @@ VolumeGrid *BKE_volume_grid_find(const Volume *volume, const char *name) /* Grid Loading */ -bool BKE_volume_grid_load(const Volume *volume, VolumeGrid *grid) +bool BKE_volume_grid_load(const Volume *volume, const VolumeGrid *grid) { #ifdef WITH_OPENVDB VolumeGridVector &grids = *volume->runtime.grids; @@ -1205,7 +1253,7 @@ bool BKE_volume_grid_load(const Volume *volume, VolumeGrid *grid) #endif } -void BKE_volume_grid_unload(const Volume *volume, VolumeGrid *grid) +void BKE_volume_grid_unload(const Volume *volume, const VolumeGrid *grid) { #ifdef WITH_OPENVDB const char *volume_name = volume->id.name + 2; @@ -1335,34 +1383,6 @@ void BKE_volume_grid_transform_matrix(const VolumeGrid *volume_grid, float mat[4 /* Grid Tree and Voxels */ -bool BKE_volume_grid_bounds(const VolumeGrid *volume_grid, float min[3], float max[3]) -{ -#ifdef WITH_OPENVDB - /* TODO: we can get this from grid metadata in some cases? */ - const openvdb::GridBase::Ptr grid = volume_grid->grid(); - BLI_assert(BKE_volume_grid_is_loaded(volume_grid)); - - openvdb::CoordBBox coordbbox; - if (!grid->baseTree().evalLeafBoundingBox(coordbbox)) { - INIT_MINMAX(min, max); - return false; - } - - openvdb::BBoxd bbox = grid->transform().indexToWorld(coordbbox); - min[0] = (float)bbox.min().x(); - min[1] = (float)bbox.min().y(); - min[2] = (float)bbox.min().z(); - max[0] = (float)bbox.max().x(); - max[1] = (float)bbox.max().y(); - max[2] = (float)bbox.max().z(); - return true; -#else - UNUSED_VARS(volume_grid); - INIT_MINMAX(min, max); - return false; -#endif -} - /* Volume Editing */ Volume *BKE_volume_new_for_eval(const Volume *volume_src) @@ -1410,7 +1430,7 @@ VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType { #ifdef WITH_OPENVDB VolumeGridVector &grids = *volume->runtime.grids; - BLI_assert(BKE_volume_grid_find(volume, name) == nullptr); + BLI_assert(BKE_volume_grid_find_for_read(volume, name) == nullptr); BLI_assert(type != VOLUME_GRID_UNKNOWN); openvdb::GridBase::Ptr vdb_grid = BKE_volume_grid_type_operation(type, CreateGridOp{}); @@ -1472,13 +1492,45 @@ float BKE_volume_simplify_factor(const Depsgraph *depsgraph) /* OpenVDB Grid Access */ #ifdef WITH_OPENVDB + +bool BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid, float3 &r_min, float3 &r_max) +{ + /* TODO: we can get this from grid metadata in some cases? */ + openvdb::CoordBBox coordbbox; + if (!grid->baseTree().evalLeafBoundingBox(coordbbox)) { + return false; + } + + openvdb::BBoxd bbox = grid->transform().indexToWorld(coordbbox); + + r_min = float3((float)bbox.min().x(), (float)bbox.min().y(), (float)bbox.min().z()); + r_max = float3((float)bbox.max().x(), (float)bbox.max().y(), (float)bbox.max().z()); + + return true; +} + +/** + * Return a new grid pointer with only the metadata and transform changed. + * This is useful for instances, where there is a separate transform on top of the original + * grid transform that must be applied for some operations that only take a grid argument. + */ +openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid, + const blender::float4x4 &transform) +{ + openvdb::math::Transform::Ptr grid_transform = grid->transform().copy(); + grid_transform->postMult(openvdb::Mat4d(((float *)transform.values))); + + /* Create a transformed grid. The underlying tree is shared. */ + return grid->copyGridReplacingTransform(grid_transform); +} + openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const VolumeGrid *grid) { return grid->grid(); } openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const Volume *volume, - VolumeGrid *grid) + const VolumeGrid *grid) { BKE_volume_grid_load(volume, grid); return grid->grid(); diff --git a/source/blender/blenkernel/intern/volume_render.cc b/source/blender/blenkernel/intern/volume_render.cc index 5c71f1d7eca..6dc497bb616 100644 --- a/source/blender/blenkernel/intern/volume_render.cc +++ b/source/blender/blenkernel/intern/volume_render.cc @@ -104,7 +104,7 @@ static void create_texture_to_object_matrix(const openvdb::Mat4d &grid_transform #endif bool BKE_volume_grid_dense_floats(const Volume *volume, - VolumeGrid *volume_grid, + const VolumeGrid *volume_grid, DenseFloatVolumeGrid *r_dense_grid) { #ifdef WITH_OPENVDB @@ -334,7 +334,7 @@ static void boxes_to_cube_mesh(blender::Span<openvdb::CoordBBox> boxes, #endif void BKE_volume_grid_wireframe(const Volume *volume, - VolumeGrid *volume_grid, + const VolumeGrid *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata) { @@ -411,7 +411,7 @@ static void grow_triangles(blender::MutableSpan<blender::float3> verts, #endif /* WITH_OPENVDB */ void BKE_volume_grid_selection_surface(const Volume *volume, - VolumeGrid *volume_grid, + const VolumeGrid *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata) { diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 0991d804882..560ae30967f 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -36,6 +36,7 @@ # include <AUD_Special.h> # endif +# include "BLI_endian_defines.h" # include "BLI_math_base.h" # include "BLI_threads.h" # include "BLI_utildefines.h" @@ -56,6 +57,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 +82,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 +95,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 +141,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 +184,48 @@ 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); - } - if (pkt.dts != AV_NOPTS_VALUE) { - pkt.dts = av_rescale_q(pkt.dts, c->time_base, context->audio_stream->time_base); + ret = avcodec_receive_packet(c, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; } - if (pkt.duration > 0) { - pkt.duration = av_rescale_q(pkt.duration, c->time_base, context->audio_stream->time_base); + if (ret < 0) { + fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + success = -1; } - pkt.stream_index = context->audio_stream->index; + pkt->stream_index = context->audio_stream->index; + av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base); +# ifdef FFMPEG_USE_DURATION_WORKAROUND + my_guess_pkt_duration(context->outfile, context->audio_stream, pkt); +# endif - 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,61 @@ 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); +# ifdef FFMPEG_USE_DURATION_WORKAROUND + my_guess_pkt_duration(context->outfile, context->video_stream, packet); +# endif + + 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 +418,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 +426,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,11 +514,53 @@ 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); } } } +static AVRational calc_time_base(uint den, double num, int codec_id) +{ + /* Convert the input 'num' to an integer. Simply shift the decimal places until we get an integer + * (within a floating point error range). + * For example if we have `den = 3` and `num = 0.1` then the fps is: `den/num = 30` fps. + * When converting this to a FFMPEG time base, we want num to be an integer. + * So we simply move the decimal places of both numbers. i.e. `den = 30`, `num = 1`. */ + float eps = FLT_EPSILON; + const uint DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1; + + /* Calculate the precision of the initial floating point number. */ + if (num > 1.0) { + const uint num_integer_bits = log2_floor_u((unsigned int)num); + + /* Formula for calculating the epsilon value: (power of two range) / (pow mantissa bits) + * For example, a float has 23 mantissa bits and the float value 3.5f as a pow2 range of + * (4-2=2): + * (2) / pow2(23) = floating point precision for 3.5f + */ + eps = (float)(1 << num_integer_bits) * FLT_EPSILON; + } + + /* Calculate how many decimal shifts we can do until we run out of precision. */ + const int max_num_shift = fabsf(log10f(eps)); + /* Calculate how many times we can shift the denominator. */ + const int max_den_shift = log10f(DENUM_MAX) - log10f(den); + const int max_iter = min_ii(max_num_shift, max_den_shift); + + for (int i = 0; i < max_iter && fabs(num - round(num)) > eps; i++) { + /* Increase the number and denominator until both are integers. */ + num *= 10; + den *= 10; + eps *= 10; + } + + AVRational time_base; + time_base.den = den; + time_base.num = (int)num; + + return time_base; +} + /* prepare a video stream for the output file */ static AVStream *alloc_video_stream(FFMpegContext *context, @@ -552,7 +573,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -566,20 +586,29 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Set up the codec context */ - c = st->codec; - c->thread_count = BLI_system_thread_count(); - c->thread_type = FF_THREAD_SLICE; - + context->video_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->video_codec; c->codec_id = codec_id; c->codec_type = AVMEDIA_TYPE_VIDEO; + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { + fprintf(stderr, "Couldn't find valid video codec\n"); + avcodec_free_context(&c); + context->video_codec = NULL; + return NULL; + } + + /* Load codec defaults into 'c'. */ + avcodec_get_context_defaults3(c, codec); + /* Get some values from the current render settings */ c->width = rectx; c->height = recty; - /* FIXME: Really bad hack (tm) for NTSC support */ if (context->ffmpeg_type == FFMPEG_DV && rd->frs_sec != 25) { + /* FIXME: Really bad hack (tm) for NTSC support */ c->time_base.den = 2997; c->time_base.num = 100; } @@ -587,21 +616,23 @@ static AVStream *alloc_video_stream(FFMpegContext *context, c->time_base.den = rd->frs_sec; c->time_base.num = (int)rd->frs_sec_base; } - else if (compare_ff(rd->frs_sec_base, 1.001f, 0.000001f)) { - /* This converts xx/1.001 (which is used in presets) to xx000/1001 (which is used in the rest - * of the world, including FFmpeg). */ - c->time_base.den = (int)(rd->frs_sec * 1000); - c->time_base.num = (int)(rd->frs_sec_base * 1000); - } else { - /* This calculates a fraction (DENUM_MAX / num) which approximates the scene frame rate - * (frs_sec / frs_sec_base). It uses the maximum denominator allowed by FFmpeg. - */ - const double DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1; - const double num = (DENUM_MAX / (double)rd->frs_sec) * rd->frs_sec_base; + c->time_base = calc_time_base(rd->frs_sec, rd->frs_sec_base, codec_id); + } - c->time_base.den = (int)DENUM_MAX; - c->time_base.num = (int)num; + /* As per the time-base documentation here: + * https://www.ffmpeg.org/ffmpeg-codecs.html#Codec-Options + * We want to set the time base to (1 / fps) for fixed frame rate video. + * If it is not possible, we want to set the time-base numbers to something as + * small as possible. + */ + if (c->time_base.num != 1) { + AVRational new_time_base; + if (av_reduce( + &new_time_base.num, &new_time_base.den, c->time_base.num, c->time_base.den, INT_MAX)) { + /* Exact reduction was possible. Use the new value. */ + c->time_base = new_time_base; + } } st->time_base = c->time_base; @@ -613,6 +644,11 @@ static AVStream *alloc_video_stream(FFMpegContext *context, ffmpeg_dict_set_int(&opts, "lossless", 1); } else if (context->ffmpeg_crf >= 0) { + /* As per https://trac.ffmpeg.org/wiki/Encode/VP9 we must set the bit rate to zero when + * encoding with vp9 in crf mode. + * Set this to always be zero for other codecs as well. + * We don't care about bit rate in crf mode. */ + c->bit_rate = 0; ffmpeg_dict_set_int(&opts, "crf", context->ffmpeg_crf); } else { @@ -652,14 +688,6 @@ 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) { - return NULL; - } - /* Be sure to use the correct pixel format(e.g. RGB, YUV) */ if (codec->pix_fmts) { @@ -676,12 +704,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, c->codec_tag = (('D' << 24) + ('I' << 16) + ('V' << 8) + 'X'); } - if (codec_id == AV_CODEC_ID_H264) { - /* correct wrong default ffmpeg param which crash x264 */ - c->qmin = 10; - c->qmax = 51; - } - /* Keep lossless encodes in the RGB domain. */ if (codec_id == AV_CODEC_ID_HUFFYUV) { if (rd->im_format.planes == R_IMF_PLANES_RGBA) { @@ -708,6 +730,11 @@ static AVStream *alloc_video_stream(FFMpegContext *context, } } + /* Use 4:4:4 instead of 4:2:0 pixel format for lossless rendering. */ + if ((codec_id == AV_CODEC_ID_H264 || codec_id == AV_CODEC_ID_VP9) && context->ffmpeg_crf == 0) { + c->pix_fmt = AV_PIX_FMT_YUV444P; + } + if (codec_id == AV_CODEC_ID_PNG) { if (rd->im_format.planes == R_IMF_PLANES_RGBA) { c->pix_fmt = AV_PIX_FMT_RGBA; @@ -716,7 +743,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... */ @@ -727,9 +754,28 @@ static AVStream *alloc_video_stream(FFMpegContext *context, set_ffmpeg_properties(rd, c, "video", &opts); - if (avcodec_open2(c, codec, &opts) < 0) { + if (codec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { + c->thread_count = 0; + } + else { + c->thread_count = BLI_system_thread_count(); + } + + if (codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) { + c->thread_type = FF_THREAD_FRAME; + } + else if (codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) { + c->thread_type = FF_THREAD_SLICE; + } + + int ret = avcodec_open2(c, codec, &opts); + + if (ret < 0) { + fprintf(stderr, "Couldn't initialize video codec: %s\n", av_err2str(ret)); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); + context->video_codec = NULL; return NULL; } av_dict_free(&opts); @@ -757,6 +803,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, NULL); } + avcodec_parameters_from_context(st->codecpar, c); + return st; } @@ -768,7 +816,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -780,19 +827,30 @@ 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; c->codec_id = codec_id; c->codec_type = AVMEDIA_TYPE_AUDIO; + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { + fprintf(stderr, "Couldn't find valid audio codec\n"); + avcodec_free_context(&c); + context->audio_codec = NULL; + return NULL; + } + + /* Load codec defaults into 'c'. */ + avcodec_get_context_defaults3(c, codec); + c->sample_rate = rd->ffcodecdata.audio_mixrate; c->bit_rate = context->ffmpeg_audio_bitrate * 1000; 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; @@ -810,7 +868,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 */ @@ -818,12 +875,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->sample_fmt = AV_SAMPLE_FMT_FLT; } - codec = avcodec_find_encoder(c->codec_id); - if (!codec) { - // XXX error("Couldn't find a valid audio codec"); - return NULL; - } - if (codec->sample_fmts) { /* Check if the preferred sample format for this codec is supported. * this is because, depending on the version of libav, @@ -832,13 +883,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]; } } @@ -847,52 +898,48 @@ 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); - if (avcodec_open2(c, codec, &opts) < 0) { - // XXX error("Couldn't initialize audio codec"); + int ret = avcodec_open2(c, codec, &opts); + + if (ret < 0) { + fprintf(stderr, "Couldn't initialize audio codec: %s\n", av_err2str(ret)); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); + context->audio_codec = NULL; 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); @@ -901,10 +948,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); @@ -912,6 +955,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 */ @@ -937,7 +982,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); @@ -1028,7 +1073,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: @@ -1093,9 +1138,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; } @@ -1107,9 +1154,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; } @@ -1117,6 +1166,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; } } @@ -1126,10 +1176,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; } @@ -1144,13 +1196,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; } @@ -1178,46 +1228,39 @@ 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); +# ifdef FFMPEG_USE_DURATION_WORKAROUND + my_guess_pkt_duration(context->outfile, context->video_stream, packet); +# endif + + 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); } /* ********************************************************************** @@ -1315,7 +1358,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; @@ -1342,10 +1386,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; @@ -1386,8 +1426,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) { @@ -1416,9 +1456,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); } @@ -1429,14 +1471,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; } @@ -1455,6 +1495,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; @@ -1463,12 +1513,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); @@ -1548,12 +1592,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: @@ -1657,56 +1701,7 @@ static void ffmpeg_set_expert_options(RenderData *rd) IDP_FreePropertyContent(rd->ffcodecdata.properties); } - if (codec_id == AV_CODEC_ID_H264) { - /* - * All options here are for x264, but must be set via ffmpeg. - * The names are therefore different - Search for "x264 to FFmpeg option mapping" - * to get a list. - */ - - /* - * Use CABAC coder. Using "coder:1", which should be equivalent, - * crashes Blender for some reason. Either way - this is no big deal. - */ - BKE_ffmpeg_property_add_string(rd, "video", "coder:vlc"); - - /* - * The other options were taken from the libx264-default.preset - * included in the ffmpeg distribution. - */ - - /* This breaks compatibility for QT. */ - // BKE_ffmpeg_property_add_string(rd, "video", "flags:loop"); - BKE_ffmpeg_property_add_string(rd, "video", "cmp:chroma"); - BKE_ffmpeg_property_add_string(rd, "video", "partitions:parti4x4"); /* Deprecated. */ - BKE_ffmpeg_property_add_string(rd, "video", "partitions:partp8x8"); /* Deprecated. */ - BKE_ffmpeg_property_add_string(rd, "video", "partitions:partb8x8"); /* Deprecated. */ - BKE_ffmpeg_property_add_string(rd, "video", "me:hex"); - BKE_ffmpeg_property_add_string(rd, "video", "subq:6"); - BKE_ffmpeg_property_add_string(rd, "video", "me_range:16"); - BKE_ffmpeg_property_add_string(rd, "video", "qdiff:4"); - BKE_ffmpeg_property_add_string(rd, "video", "keyint_min:25"); - BKE_ffmpeg_property_add_string(rd, "video", "sc_threshold:40"); - BKE_ffmpeg_property_add_string(rd, "video", "i_qfactor:0.71"); - BKE_ffmpeg_property_add_string(rd, "video", "b_strategy:1"); - BKE_ffmpeg_property_add_string(rd, "video", "bf:3"); - BKE_ffmpeg_property_add_string(rd, "video", "refs:2"); - BKE_ffmpeg_property_add_string(rd, "video", "qcomp:0.6"); - - 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 (codec_id == AV_CODEC_ID_DNXHD) { if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { BKE_ffmpeg_property_add_string(rd, "video", "mbd:rd"); } @@ -1859,14 +1854,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) |