Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/io/resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,10 @@ String Resource::get_id_for_path(const String &p_referrer_path) const {
#endif
}

void Resource::_start_load(const StringName &p_res_format_type, int p_res_format_version) {}

void Resource::_finish_load(const StringName &p_res_format_type, int p_res_format_version) {}

void Resource::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path);
ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path);
Expand Down
3 changes: 3 additions & 0 deletions core/io/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ class Resource : public RefCounted {
void set_id_for_path(const String &p_referrer_path, const String &p_id) { set_resource_id_for_path(p_referrer_path, get_path(), p_id); }
String get_id_for_path(const String &p_referrer_path) const;

virtual void _start_load(const StringName &p_res_format_type, int p_res_format_version);
virtual void _finish_load(const StringName &p_res_format_type, int p_res_format_version);

Resource();
~Resource();
};
Expand Down
4 changes: 4 additions & 0 deletions core/io/resource_format_binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,8 @@ Error ResourceLoaderBinary::load() {
internal_index_cache[path] = res;
}

res->_start_load(SNAME("binary"), ver_format);

int pc = f->get_32();

//set properties
Expand Down Expand Up @@ -825,6 +827,8 @@ Error ResourceLoaderBinary::load() {
res->set_edited(false);
#endif

res->_finish_load(SNAME("binary"), ver_format);

if (progress) {
*progress = (i + 1) / float(internal_resources.size());
}
Expand Down
51 changes: 47 additions & 4 deletions editor/import/3d/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3528,11 +3528,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_null(), 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);
Comment on lines +3562 to +3565

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can do the track conversion here using an explicit function call instead of player->advance(0) and remove all the code about caches, then I'd feel a lot less bad about the runtime cost issue.

But it doesn't change that this is bleeding importer complexity into the animation system.

}
}
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]);
Expand All @@ -3541,9 +3573,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.
Expand Down Expand Up @@ -3584,7 +3629,5 @@ Node *EditorSceneFormatImporterESCN::import_scene(const String &p_path, uint32_t
}
}

ERR_FAIL_NULL_V(scene, nullptr);

return scene;
}
81 changes: 81 additions & 0 deletions editor/shader/text_shader_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
#include "servers/rendering/shader_types.h"

#include "modules/regex/regex.h"
#ifndef DISABLE_DEPRECATED
#include "servers/rendering/shader_converter.h"
#endif

/*** SHADER SYNTAX HIGHLIGHTER ****/

Expand Down Expand Up @@ -1353,6 +1356,28 @@ void TextShaderEditor::_menu_option(int p_option) {
case EDIT_COMPLETE: {
tx->request_code_completion();
} break;
#ifndef DISABLE_DEPRECATED
case EDIT_CONVERT: {
if (shader.is_null()) {
return;
}
String code = code_editor->get_text_editor()->get_text();
if (code.is_empty()) {
return;
}
ShaderDeprecatedConverter converter;
if (!converter.is_code_deprecated(code)) {
if (converter.get_error_text() != String()) {
shader_convert_error_dialog->set_text(vformat(RTR("Line %d: %s"), converter.get_error_line(), converter.get_error_text()));
shader_convert_error_dialog->popup_centered();
ERR_PRINT("Shader conversion failed: " + converter.get_error_text());
}
confirm_convert_shader->popup_centered();
return;
}
_convert_shader();
} break;
#endif
case SEARCH_FIND: {
code_editor->get_find_replace_bar()->popup_search();
} break;
Expand Down Expand Up @@ -1507,6 +1532,43 @@ void TextShaderEditor::_notification(int p_what) {
} break;
}
}
#ifndef DISABLE_DEPRECATED
void TextShaderEditor::_convert_shader() {
if (shader.is_null()) {
return;
}
String code = code_editor->get_text_editor()->get_text();
if (code.is_empty()) {
return;
}
ShaderDeprecatedConverter converter;
if (!converter.convert_code(code)) {
String err_text = converter.get_error_text();
if (err_text.is_empty()) {
err_text = TTR("Unknown error occurred while converting the shader.");
} else if (converter.get_error_line() > 0) {
err_text = vformat("%s (line %d)", err_text, converter.get_error_line());
}

shader_convert_error_dialog->set_text(err_text);
shader_convert_error_dialog->popup_centered();
ERR_PRINT("Shader conversion failed: " + err_text);
return;
}
String new_code = converter.emit_code();

#ifdef DEBUG_ENABLED
print_line(converter.get_report());
#endif
if (new_code == code) {
return;
}
// Ensure undoable.
code_editor->get_text_editor()->set_text(new_code);
code_editor->get_text_editor()->tag_saved_version();
code_editor->_validate_script();
}
#endif

void TextShaderEditor::_apply_editor_settings() {
code_editor->update_editor_settings();
Expand Down Expand Up @@ -2022,6 +2084,11 @@ TextShaderEditor::TextShaderEditor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
edit_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextShaderEditor::_menu_option));

#ifndef DISABLE_DEPRECATED
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_item(TTR("Convert 3.x Shader"), EDIT_CONVERT);
#endif

search_menu = memnew(MenuButton);
search_menu->set_flat(false);
search_menu->set_theme_type_variation("FlatMenuButton");
Expand Down Expand Up @@ -2155,6 +2222,20 @@ TextShaderEditor::TextShaderEditor() {
preview_timer->set_wait_time(0.001);
preview_timer->connect("timeout", callable_mp(code_editor, &ShaderTextEditor::redraw_preview_lines));

#ifndef DISABLE_DEPRECATED
shader_convert_error_dialog = memnew(AcceptDialog);
shader_convert_error_dialog->set_title(TTR("Error converting shader"));
shader_convert_error_dialog->set_hide_on_ok(true);
add_child(shader_convert_error_dialog);

confirm_convert_shader = memnew(ConfirmationDialog);
confirm_convert_shader->set_title(TTR("Confirm Convert 3.x Shader"));
confirm_convert_shader->set_text(TTR("This shader does not appear to be a 3.x shader.\nAre you sure you want to convert it?"));
confirm_convert_shader->get_ok_button()->set_text(TTR("Convert"));
confirm_convert_shader->get_cancel_button()->set_text(TTR("Cancel"));
confirm_convert_shader->connect(SceneStringName(confirmed), callable_mp(this, &TextShaderEditor::_convert_shader));
add_child(confirm_convert_shader);
#endif
_apply_editor_settings();
code_editor->show_toggle_files_button(); // TODO: Disabled for now, because it doesn't work properly.
}
9 changes: 9 additions & 0 deletions editor/shader/text_shader_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class TextShaderEditor : public ShaderEditor {
EDIT_TOGGLE_WORD_WRAP,
EDIT_TOGGLE_COMMENT,
EDIT_COMPLETE,
EDIT_CONVERT,
SEARCH_FIND,
SEARCH_FIND_NEXT,
SEARCH_FIND_PREV,
Expand Down Expand Up @@ -241,13 +242,21 @@ class TextShaderEditor : public ShaderEditor {
ConfirmationDialog *disk_changed = nullptr;

ShaderTextEditor *code_editor = nullptr;
#ifndef DISABLE_DEPRECATED
AcceptDialog *shader_convert_error_dialog = nullptr;
ConfirmationDialog *confirm_convert_shader = nullptr;
#endif

bool compilation_success = true;

void _menu_option(int p_option);
void _prepare_edit_menu();
mutable Ref<Shader> shader;
mutable Ref<ShaderInclude> shader_inc;

#ifndef DISABLE_DEPRECATED
void _convert_shader();
#endif
void _apply_editor_settings();
void _project_settings_changed();

Expand Down
15 changes: 13 additions & 2 deletions scene/3d/mesh_instance_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,19 @@ bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) {
return true;
}

if (p_name.operator String().begins_with("surface_material_override/")) {
int idx = p_name.operator String().get_slicec('/', 1).to_int();
String name = p_name;

#ifndef DISABLE_DEPRECATED
if (name.begins_with("material/")) {
WARN_DEPRECATED_MSG("This mesh uses an old deprecated parameter name. Consider re-saving this scene in order for it to continue working in future versions." +
(is_inside_tree() ? vformat(" Path: \"%s\"", get_path()) : (get_name().is_empty() ? String() : vformat(" Name: \"%s\"", get_name()))));
}
if (name.begins_with("surface_material_override/") || name.begins_with("material/"))
#else
if (name.begins_with("surface_material_override/"))
#endif
{
int idx = name.get_slicec('/', 1).to_int();

if (idx >= surface_override_materials.size() || idx < 0) {
return false;
Expand Down
100 changes: 100 additions & 0 deletions scene/animation/animation_mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,101 @@ TypedArray<StringName> AnimationMixer::_get_animation_library_list() const {
return ret;
}

#if !defined(_3D_DISABLED) && !defined(DISABLE_DEPRECATED)
bool AnimationMixer::_recalc_animation(const Ref<Animation> &p_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 < p_anim->get_track_count(); i++) {
int track_type = p_anim->track_get_type(i);
if (!p_anim->track_is_relative_to_rest(i)) {
continue;
}
if (track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_ROTATION_3D || track_type == Animation::TYPE_SCALE_3D) {
NodePath path = p_anim->track_get_path(i);
Node *node = parent->get_node(path);
ERR_FAIL_NULL_V(node, false);
Skeleton3D *skel = Object::cast_to<Skeleton3D>(node);
if (!skel) { // transforming non-skeleton node, not relative to rest
continue;
}
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 * p_anim->track_get_key_count(i));
real_t *r = new_track_values_map[i].ptrw();
for (int j = 0; j < p_anim->track_get_key_count(i); j++) {
real_t time = p_anim->track_get_key_time(i, j);
real_t transition = p_anim->track_get_key_transition(i, j);
if (track_type == Animation::TYPE_POSITION_3D) {
Vector3 a_pos = p_anim->track_get_key_value(i, j);
Transform3D t;
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 = p_anim->track_get_key_value(i, j);
Transform3D t;
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 = p_anim->track_get_key_value(i, j);
Transform3D t;
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 < p_anim->get_track_count(); i++) {
if (!new_track_values_map.has(i)) {
continue;
}
p_anim->set("tracks/" + itos(i) + "/keys", new_track_values_map[i]);
p_anim->set("tracks/" + itos(i) + "/relative_to_rest", false);
}
p_anim->emit_changed();
return true;
}
#endif // !defined(_3D_DISABLED) || !defined(DISABLE_DEPRECATED)

void AnimationMixer::get_animation_library_list(LocalVector<StringName> *p_libraries) const {
for (const AnimationLibraryData &lib : animation_libraries) {
p_libraries->push_back(lib.name);
Expand Down Expand Up @@ -691,6 +786,11 @@ bool AnimationMixer::_update_caches() {
}
for (const StringName &E : sname_list) {
const Ref<Animation> &anim = get_animation(E);
#if !defined(_3D_DISABLED) && !defined(DISABLE_DEPRECATED)
if (anim->has_tracks_relative_to_rest()) {
_recalc_animation(anim);
}
Comment on lines +790 to +792

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is adding code in runtime which I am not happy with.

It's also doing conversions in such a way that we now have an additional relative_to_rest property internally on Animations which we will have to continue to support across the entirety of the Animation system and tooling indefinitely.

Clearly @TokageItLab would need to give the final call on this, but given the complexity the animation system already has, I'm uncertain if this is a good idea. I really don't like that this introduces a strange interaction between the track cache and the Animation keyframes but only if a legacy flag is in use, and one that can never be removed.

When Godot 5.0 comes out, no doubt there will still be Animation resources floating around with this flag that will have the same problem. It also means that the animation track editor might be broken for these animations, since the actual tracks in the resource are rest-relative. How would we even know which Animation's are running this track conversion during every playback, and which aren't?

I understand the problem though... this conversion cannot be done during import because the skeleton rest is not known until runtime, and in fact the Animation resource could be loaded independently of the tscn with the Skeleton3D and only attached later on, so Godot would have no way to adapt the Animation resource for the associated Skeleton3D.

That said, if we have a way of knowing which Skeleton3D corresponds to the given Animation, then I think I would prefer if the escn importer should do the work of converting the legacy animation tracks to not be rest-relative.

@nikitalita nikitalita Nov 28, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, if we have a way of knowing which Skeleton3D corresponds to the given Animation, then I think I would prefer if the escn importer should do the work of converting the legacy animation tracks to not be rest-relative.

This is originally how I had this setup, but I think I misconstrued your original comment a while back.

While this method has the added benefit of supporting older TSCNs as well, I understand the concerns about runtime complexity. So, would it be enough to simply remove the ADD_PROPERTY macro (and the property declaration in _get_property_list), and move the conversion code from the mixer to animation.cpp and only call it from the escn importer?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe adding this as compatibility code is not acceptable. Relative animation was removed in 4.0.

In the past, after discussing with reduz, the alternative provided at that time was _post_process_key_value(), and it is possible to make relative animation work in a custom module using this.

Supporting this would be akin to shipping that custom module in core, but considering the circumstances under which it was provided as a custom module, this would likely be rejected.

Please note that what can be provided as compatibility code is resource conversion, such as converting animation keys from relative to absolute animation. Runtime-based solutions are almost never permitted.

#endif
for (int i = 0; i < anim->get_track_count(); i++) {
if (!anim->track_is_enabled(i)) {
continue;
Expand Down
Loading
Loading