Convert 3.x `transform` animation tracks

This commit is contained in:
nikitalita 2024-09-30 03:12:56 -07:00
parent a22e2c2f57
commit 324ad7571a
5 changed files with 400 additions and 5 deletions

View File

@ -3270,11 +3270,43 @@ void EditorSceneFormatImporterESCN::get_extensions(List<String> *r_extensions) c
r_extensions->push_back("escn");
}
int get_text_format_version(String p_path) {
Error error;
Ref<FileAccess> 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<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) {
Error error;
int format = get_text_format_version(p_path);
ERR_FAIL_COND_V(format == -1, nullptr);
Ref<PackedScene> 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<Node> 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<AnimationPlayer>(skel_nodes[node_i]);
ERR_CONTINUE(!player);
player->advance(0);
}
}
TypedArray<Node> nodes = scene->find_children("*", "MeshInstance3D");
for (int32_t node_i = 0; node_i < nodes.size(); node_i++) {
MeshInstance3D *mesh_3d = cast_to<MeshInstance3D>(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> skin = mesh_3d->get_skin();
importer_mesh_3d->set_skeleton_path(mesh_3d->get_skeleton_path());
importer_mesh_3d->set_skin(skin);
Ref<ArrayMesh> 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;
}

View File

@ -242,6 +242,97 @@ TypedArray<StringName> AnimationMixer::_get_animation_library_list() const {
return ret;
}
#if !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED)
bool AnimationMixer::_recalc_animation(Ref<Animation> &anim) {
HashMap<int, Vector<real_t>> 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<Skeleton3D>(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<real_t>();
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<StringName> *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<StringName> sname;
get_animation_list(&sname);
for (const StringName &E : sname) {
Ref<Animation> anim = get_animation(E);
if (anim->has_tracks_relative_to_rest()) {
_recalc_animation(anim);
}
}
#endif
if (!_update_caches()) {
return;
}

View File

@ -317,6 +317,10 @@ protected:
void _init_root_motion_cache();
bool _update_caches();
#if !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED)
bool _recalc_animation(Ref<Animation> &p_anim);
#endif
/* ---- Audio ---- */
AudioServer::PlaybackType playback_type;

View File

@ -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<PositionTrack *>(t);
tt->relative_to_rest = p_value;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
rt->relative_to_rest = p_value;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(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<PropertyInfo> *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<PositionTrack *>(t);
return tt->relative_to_rest;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
return rt->relative_to_rest;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(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<PositionTrack *>(t);
tt->relative_to_rest = p_relative_to_rest;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
rt->relative_to_rest = p_relative_to_rest;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(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<float> 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<real_t> position_keys;
Vector<real_t> rotation_keys;
Vector<real_t> 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;

View File

@ -138,6 +138,9 @@ private:
struct PositionTrack : public Track {
Vector<TKey<Vector3>> 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<TKey<Quaternion>> 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<TKey<Vector3>> 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);