diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index cb348f713c1..46b9ee6c2c9 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -3270,11 +3270,43 @@ void EditorSceneFormatImporterESCN::get_extensions(List *r_extensions) c r_extensions->push_back("escn"); } +int get_text_format_version(String p_path) { + Error error; + Ref f = FileAccess::open(p_path, FileAccess::READ, &error); + ERR_FAIL_COND_V_MSG(error != OK || f.is_null(), -1, "Cannot open file '" + p_path + "'."); + String line = f->get_line().strip_edges(); + // skip empty lines and comments + while (line.is_empty() || line.begins_with(";")) { + line = f->get_line().strip_edges(); + if (f->eof_reached()) { + break; + } + } + int format_index = line.find("format"); + ERR_FAIL_COND_V_MSG(format_index == -1, -1, "No format specifier in file '" + p_path + "'."); + String format_str = line.substr(format_index).get_slicec('=', 1).strip_edges(); + ERR_FAIL_COND_V_MSG(!format_str.substr(0, 1).is_numeric(), -1, "Invalid format in file '" + p_path + "'."); + int format = format_str.to_int(); + return format; +} + Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err) { Error error; + int format = get_text_format_version(p_path); + ERR_FAIL_COND_V(format == -1, nullptr); Ref ps = ResourceFormatLoaderText::singleton->load(p_path, p_path, &error); ERR_FAIL_COND_V_MSG(!ps.is_valid(), nullptr, "Cannot load scene as text resource from path '" + p_path + "'."); Node *scene = ps->instantiate(); + ERR_FAIL_COND_V(!scene, nullptr); + if (format == 2) { + TypedArray skel_nodes = scene->find_children("*", "AnimationPlayer"); + for (int32_t node_i = 0; node_i < skel_nodes.size(); node_i++) { + // Force re-compute animation tracks. + AnimationPlayer *player = cast_to(skel_nodes[node_i]); + ERR_CONTINUE(!player); + player->advance(0); + } + } TypedArray nodes = scene->find_children("*", "MeshInstance3D"); for (int32_t node_i = 0; node_i < nodes.size(); node_i++) { MeshInstance3D *mesh_3d = cast_to(nodes[node_i]); @@ -3283,9 +3315,22 @@ Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t // Ignore the aabb, it will be recomputed. ImporterMeshInstance3D *importer_mesh_3d = memnew(ImporterMeshInstance3D); importer_mesh_3d->set_name(mesh_3d->get_name()); - importer_mesh_3d->set_transform(mesh_3d->get_relative_transform(mesh_3d->get_parent())); - importer_mesh_3d->set_skin(mesh_3d->get_skin()); + Node *parent = mesh_3d->get_parent(); + Transform3D rel_transform = mesh_3d->get_relative_transform(parent); + if (rel_transform == Transform3D() && parent && parent != mesh_3d) { + // If we're here, we probably got a "data.parent is null" error + // Node3D.data.parent hasn't been set yet but Node.data.parent has, so we need to get the transform manually + Node3D *parent_3d = mesh_3d->get_parent_node_3d(); + if (parent == parent_3d) { + rel_transform = mesh_3d->get_transform(); + } else if (parent_3d) { + rel_transform = parent_3d->get_relative_transform(parent) * mesh_3d->get_transform(); + } // Otherwise, parent isn't a Node3D. + } + importer_mesh_3d->set_transform(rel_transform); + Ref skin = mesh_3d->get_skin(); importer_mesh_3d->set_skeleton_path(mesh_3d->get_skeleton_path()); + importer_mesh_3d->set_skin(skin); Ref array_mesh_3d_mesh = mesh_3d->get_mesh(); if (array_mesh_3d_mesh.is_valid()) { // For the MeshInstance3D nodes, we need to convert the ArrayMesh to an ImporterMesh specially. @@ -3326,7 +3371,5 @@ Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t } } - ERR_FAIL_NULL_V(scene, nullptr); - return scene; } diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 664302d45ea..bc9bee8d4eb 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -242,6 +242,97 @@ TypedArray AnimationMixer::_get_animation_library_list() const { return ret; } +#if !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED) +bool AnimationMixer::_recalc_animation(Ref &anim) { + HashMap> new_track_values_map; + Node *parent = get_node_or_null(root_node); + if (!parent) { + return false; + } + + for (int i = 0; i < anim->get_track_count(); i++) { + int track_type = anim->track_get_type(i); + if (track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_ROTATION_3D || track_type == Animation::TYPE_SCALE_3D) { + NodePath path = anim->track_get_path(i); + Node *node = parent->get_node(path); + ERR_FAIL_COND_V(!node, false); + Skeleton3D *skel = Object::cast_to(node); + ERR_FAIL_COND_V(!skel, false); + + StringName bone = path.get_subname(0); + int bone_idx = skel->find_bone(bone); + if (bone_idx == -1) { + continue; + } + Transform3D rest = skel->get_bone_rest(bone_idx); + new_track_values_map[i] = Vector(); + const int32_t POSITION_TRACK_SIZE = 5; + const int32_t ROTATION_TRACK_SIZE = 6; + const int32_t SCALE_TRACK_SIZE = 5; + int32_t track_size = POSITION_TRACK_SIZE; + if (track_type == Animation::TYPE_ROTATION_3D) { + track_size = ROTATION_TRACK_SIZE; + } + new_track_values_map[i].resize(track_size * anim->track_get_key_count(i)); + real_t *r = new_track_values_map[i].ptrw(); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + real_t time = anim->track_get_key_time(i, j); + real_t transition = anim->track_get_key_transition(i, j); + if (track_type == Animation::TYPE_POSITION_3D) { + Vector3 a_pos = anim->track_get_key_value(i, j); + Transform3D t = Transform3D(); + t.set_origin(a_pos); + Vector3 new_a_pos = (rest * t).origin; + + real_t *ofs = &r[j * POSITION_TRACK_SIZE]; + ofs[0] = time; + ofs[1] = transition; + ofs[2] = new_a_pos.x; + ofs[3] = new_a_pos.y; + ofs[4] = new_a_pos.z; + } else if (track_type == Animation::TYPE_ROTATION_3D) { + Quaternion q = anim->track_get_key_value(i, j); + Transform3D t = Transform3D(); + t.basis.rotate(q); + Quaternion new_q = (rest * t).basis.get_rotation_quaternion(); + real_t *ofs = &r[j * ROTATION_TRACK_SIZE]; + ofs[0] = time; + ofs[1] = transition; + ofs[2] = new_q.x; + ofs[3] = new_q.y; + ofs[4] = new_q.z; + ofs[5] = new_q.w; + } else if (track_type == Animation::TYPE_SCALE_3D) { + Vector3 v = anim->track_get_key_value(i, j); + Transform3D t = Transform3D(); + t.scale(v); + Vector3 new_v = (rest * t).basis.get_scale(); + + real_t *ofs = &r[j * SCALE_TRACK_SIZE]; + ofs[0] = time; + ofs[1] = transition; + ofs[2] = new_v.x; + ofs[3] = new_v.y; + ofs[4] = new_v.z; + } + } + } + } + if (new_track_values_map.is_empty()) { + return false; + } + for (int i = 0; i < anim->get_track_count(); i++) { + if (!new_track_values_map.has(i)) { + continue; + } + anim->set("tracks/" + itos(i) + "/keys", new_track_values_map[i]); + anim->set("tracks/" + itos(i) + "/relative_to_rest", false); + } + anim->emit_changed(); + return true; +} +#endif // !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED) + void AnimationMixer::get_animation_library_list(List *p_libraries) const { for (const AnimationLibraryData &lib : animation_libraries) { p_libraries->push_back(lib.name); @@ -986,6 +1077,18 @@ void AnimationMixer::_blend_init() { root_motion_scale_accumulator = Vector3(1, 1, 1); if (!cache_valid) { +#if !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED) + List sname; + get_animation_list(&sname); + + for (const StringName &E : sname) { + Ref anim = get_animation(E); + + if (anim->has_tracks_relative_to_rest()) { + _recalc_animation(anim); + } + } +#endif if (!_update_caches()) { return; } diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 5482197fbdf..fde280ba7d8 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -317,6 +317,10 @@ protected: void _init_root_motion_cache(); bool _update_caches(); +#if !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED) + bool _recalc_animation(Ref &p_anim); +#endif + /* ---- Audio ---- */ AudioServer::PlaybackType playback_type; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 1dac4b97ad9..0bd987ccf4a 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -34,6 +34,10 @@ #include "core/io/marshalls.h" #include "core/math/geometry_3d.h" +#define _LOAD_META_PROPERTY "_load" +#define _TRANSFORM_TRACK_LIST_META_PROPERTY "_transform_track_list" +#define _TRANSFORM_TRACK_DATA_META_PROPERTY(m_track_idx) "_transform_track_data__" + String::num(m_track_idx) + bool Animation::_set(const StringName &p_name, const Variant &p_value) { String prop_name = p_name; @@ -89,6 +93,23 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } else if (type == "animation") { add_track(TYPE_ANIMATION); } else { +#ifndef DISABLE_DEPRECATED + // for compatibility with 3.x animations + if (get_meta(_LOAD_META_PROPERTY, false)) { // Only do compatibility conversions if we are loading a resource. + if (type == "transform") { + WARN_DEPRECATED_MSG("Animation uses old 'transform' track types, which is deprecated (and loads slower). Consider re-importing or re-saving the resource."); + PackedInt32Array track_list = get_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, PackedInt32Array()); + track_list.push_back(track); + set_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, track_list); + Dictionary track_data; + track_data["type"] = "transform"; + set_meta(_TRANSFORM_TRACK_DATA_META_PROPERTY(track), track_data); + // Add an empty track that will get replaced after we are finished loading, so we don't throw off the track indices. + add_track(TYPE_ANIMATION, track); + return true; + } + } +#endif // DISABLE_DEPRECATED return false; } @@ -97,6 +118,42 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_INDEX_V(track, tracks.size(), false); +#ifndef DISABLE_DEPRECATED + if (what == "relative_to_rest") { + Track *t = tracks[track]; + switch (t->type) { + case TYPE_POSITION_3D: { + PositionTrack *tt = static_cast(t); + tt->relative_to_rest = p_value; + } break; + case TYPE_ROTATION_3D: { + RotationTrack *rt = static_cast(t); + rt->relative_to_rest = p_value; + } break; + case TYPE_SCALE_3D: { + ScaleTrack *st = static_cast(t); + st->relative_to_rest = p_value; + } break; + default: { + return false; + } + } + return true; + } + // If we have a transform track, we need to store the data in the metadata to be able to convert it to the new format after the load is finished. + if (get_meta(_LOAD_META_PROPERTY, false)) { // Only do this on resource loads, not on editor changes + // check the metadata to see if this track is a transform track that we are holding on to + PackedInt32Array transform_tracks = get_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, PackedInt32Array()); + if (transform_tracks.has(track)) { + // get the track data + Dictionary track_data = get_meta(_TRANSFORM_TRACK_DATA_META_PROPERTY(track), Dictionary()); + track_data[what] = p_value; + set_meta(_TRANSFORM_TRACK_DATA_META_PROPERTY(track), track_data); + return true; + } + } +#endif // DISABLE_DEPRECATED + if (what == "path") { track_set_path(track, p_value); } else if (what == "compressed_track") { @@ -850,6 +907,11 @@ void Animation::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/interp", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); +#ifndef DISABLE_DEPRECATED + if (track_is_relative_to_rest(i)) { + p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/relative_to_rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +#endif // DISABLE_DEPRECATED } if (track_get_type(i) == TYPE_AUDIO) { p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/use_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); @@ -1093,6 +1155,63 @@ void Animation::_clear(T &p_keys) { } //// +#ifndef DISABLE_DEPRECATED +bool Animation::has_tracks_relative_to_rest() const { + for (int i = 0; i < tracks.size(); i++) { + if (track_is_relative_to_rest(i)) { + return true; + } + } + return false; +} + +bool Animation::track_is_relative_to_rest(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), false); + Track *t = tracks[p_track]; + + switch (t->type) { + case TYPE_POSITION_3D: { + PositionTrack *tt = static_cast(t); + return tt->relative_to_rest; + } break; + case TYPE_ROTATION_3D: { + RotationTrack *rt = static_cast(t); + return rt->relative_to_rest; + } break; + case TYPE_SCALE_3D: { + ScaleTrack *st = static_cast(t); + return st->relative_to_rest; + } break; + default: { + return false; // Animation does not really use transitions. + } break; + } +} + +void Animation::track_set_relative_to_rest(int p_track, bool p_relative_to_rest) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + + switch (t->type) { + case TYPE_POSITION_3D: { + PositionTrack *tt = static_cast(t); + tt->relative_to_rest = p_relative_to_rest; + } break; + case TYPE_ROTATION_3D: { + RotationTrack *rt = static_cast(t); + rt->relative_to_rest = p_relative_to_rest; + } break; + case TYPE_SCALE_3D: { + ScaleTrack *st = static_cast(t); + st->relative_to_rest = p_relative_to_rest; + } break; + default: { + return; // Animation does not really use transitions. + } break; + } + emit_changed(); +} +#endif // DISABLE_DEPRECATED int Animation::position_track_insert_key(int p_track, double p_time, const Vector3 &p_position) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); @@ -5070,6 +5189,116 @@ void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tol #endif } +void Animation::_start_load(const StringName &p_res_format_type, int p_res_format_version) { +#ifndef DISABLE_DEPRECATED + set_meta(_LOAD_META_PROPERTY, true); +#endif +} + +void Animation::_finish_load(const StringName &p_res_format_type, int p_res_format_version) { +#ifndef DISABLE_DEPRECATED // 3.x compatibility, convert transform tracks to separate tracks + if (!has_meta(_LOAD_META_PROPERTY)) { + return; + } + set_meta(_LOAD_META_PROPERTY, Variant()); + if (!has_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY)) { + return; + } + PackedInt32Array transform_track_list = get_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, PackedInt32Array()); + set_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, Variant()); + if (transform_track_list.is_empty()) { + return; + } + int offset = 0; + for (int track_idx : transform_track_list) { + // now that we have all the tracks, we need to split the transform tracks into separate tracks + // this is because the current animation player doesn't support transform tracks + // so we need to split them into separate position, rotation, and scale tracks + // No need to worry about compression here; this was added in 4.x and wasn't backported to 3.x. + Dictionary track_data = get_meta(_TRANSFORM_TRACK_DATA_META_PROPERTY(track_idx), Dictionary()); + if (track_data.is_empty()) { + continue; + } + // split the transform track into separate tracks + + // Old scene format only used 32-bit floats, did not have configurable real_t. + Vector keys = track_data["keys"]; + int vcount = keys.size(); + int tcount = vcount / 12; + ERR_CONTINUE_MSG((vcount % 12) != 0, "Failed to convert transform track: invalid number of key frames."); + + Vector position_keys; + Vector rotation_keys; + Vector scale_keys; + position_keys.resize(tcount * 5); // time + transition + xyz + rotation_keys.resize(tcount * 6); // time + transition + xyzw + scale_keys.resize(tcount * 5); // time + transition + xyz + // split the keys into separate tracks + for (int j = 0; j < tcount; j++) { + // it's position (Vector3, xyz), then rotation (Quaternion, xyzw), then scale (Vector3, xyz) + // each track has time and transition values, so get those + const float *ofs = &(keys.ptr()[j * 12]); + float time = ofs[0]; + float transition = ofs[1]; + + position_keys.write[j * 5 + 0] = time; + position_keys.write[j * 5 + 1] = transition; + position_keys.write[j * 5 + 2] = ofs[2]; // x + position_keys.write[j * 5 + 3] = ofs[3]; // y + position_keys.write[j * 5 + 4] = ofs[4]; // z + + rotation_keys.write[j * 6 + 0] = time; + rotation_keys.write[j * 6 + 1] = transition; + rotation_keys.write[j * 6 + 2] = ofs[5]; // x + rotation_keys.write[j * 6 + 3] = ofs[6]; // y + rotation_keys.write[j * 6 + 4] = ofs[7]; // z + rotation_keys.write[j * 6 + 5] = ofs[8]; // w + + scale_keys.write[j * 5 + 0] = time; + scale_keys.write[j * 5 + 1] = transition; + scale_keys.write[j * 5 + 2] = ofs[9]; // x + scale_keys.write[j * 5 + 3] = ofs[10]; // y + scale_keys.write[j * 5 + 4] = ofs[11]; // z + } + Array c_track_keys = track_data.keys(); + c_track_keys.erase("type"); + c_track_keys.erase("keys"); + remove_track(track_idx + offset); // remove dummy track + + add_track(TYPE_POSITION_3D, track_idx + offset); + for (int j = 0; j < c_track_keys.size(); j++) { + String key = c_track_keys[j]; + _set("tracks/" + itos(track_idx + offset) + "/" + key, track_data[key]); + } + _set("tracks/" + itos(track_idx + offset) + "/keys", position_keys); + _set("tracks/" + itos(track_idx + offset) + "/relative_to_rest", true); + offset++; + + add_track(TYPE_ROTATION_3D, track_idx + offset); + for (int j = 0; j < c_track_keys.size(); j++) { + String key = c_track_keys[j]; + _set("tracks/" + itos(track_idx + offset) + "/" + key, track_data[key]); + } + _set("tracks/" + itos(track_idx + offset) + "/keys", rotation_keys); + _set("tracks/" + itos(track_idx + offset) + "/relative_to_rest", true); + offset++; + add_track(TYPE_SCALE_3D, track_idx + offset); + for (int j = 0; j < c_track_keys.size(); j++) { + String key = c_track_keys[j]; + _set("tracks/" + itos(track_idx + offset) + "/" + key, track_data[key]); + } + _set("tracks/" + itos(track_idx + offset) + "/keys", scale_keys); + _set("tracks/" + itos(track_idx + offset) + "/relative_to_rest", true); + offset++; + offset--; // subtract 1 because we removed the dummy track + // erase the track data + set_meta(_TRANSFORM_TRACK_DATA_META_PROPERTY(track_idx), Variant()); + } + // erase the track list + set_meta(_TRANSFORM_TRACK_LIST_META_PROPERTY, Variant()); +#endif // DISABLE_DEPRECATED +} + bool Animation::_rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const { Vector3i current; Vector3i next; diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 0c29790ea40..3ace599d7f1 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -138,6 +138,9 @@ private: struct PositionTrack : public Track { Vector> positions; int32_t compressed_track = -1; +#ifndef DISABLE_DEPRECATED + bool relative_to_rest = false; +#endif PositionTrack() { type = TYPE_POSITION_3D; } }; @@ -146,6 +149,9 @@ private: struct RotationTrack : public Track { Vector> rotations; int32_t compressed_track = -1; +#ifndef DISABLE_DEPRECATED + bool relative_to_rest = false; +#endif RotationTrack() { type = TYPE_ROTATION_3D; } }; @@ -154,6 +160,9 @@ private: struct ScaleTrack : public Track { Vector> scales; int32_t compressed_track = -1; +#ifndef DISABLE_DEPRECATED + bool relative_to_rest = false; +#endif ScaleTrack() { type = TYPE_SCALE_3D; } }; @@ -434,7 +443,11 @@ public: double track_get_key_time(int p_track, int p_key_idx) const; real_t track_get_key_transition(int p_track, int p_key_idx) const; bool track_is_compressed(int p_track) const; - +#ifndef DISABLE_DEPRECATED + bool has_tracks_relative_to_rest() const; + bool track_is_relative_to_rest(int p_track) const; + void track_set_relative_to_rest(int p_track, bool p_relative_to_rest); +#endif int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position); Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const; Error try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false) const; @@ -515,6 +528,9 @@ public: void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3); void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests. + virtual void _start_load(const StringName &p_res_format_type, int p_res_format_version) override; + virtual void _finish_load(const StringName &p_res_format_type, int p_res_format_version) override; + // Helper functions for Variant. static bool is_variant_interpolatable(const Variant p_value);