feat: modules moved and engine moved to submodule

This commit is contained in:
Jan van der Weide 2025-04-12 18:40:44 +02:00
parent dfb5e645cd
commit c33d2130cc
5136 changed files with 225275 additions and 64485 deletions

View file

@ -62,6 +62,15 @@
#include "editor/editor_file_system.h"
#endif
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
#ifdef MODULE_CSG_ENABLED
#include "modules/csg/csg_shape.h"
#endif
#ifdef MODULE_GRIDMAP_ENABLED
#include "modules/gridmap/grid_map.h"
#endif
// FIXME: Hardcoded to avoid editor dependency.
#define GLTF_IMPORT_GENERATE_TANGENT_ARRAYS 8
#define GLTF_IMPORT_USE_NAMED_SKIN_BINDS 16
@ -70,7 +79,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <cstdint>
constexpr int COMPONENT_COUNT_FOR_ACCESSOR_TYPE[7] = {
1, 2, 3, 4, 4, 9, 16
@ -291,8 +299,7 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) {
Vector<uint8_t> array;
array.resize(file->get_length());
file->get_buffer(array.ptrw(), array.size());
String text;
text.parse_utf8((const char *)array.ptr(), array.size());
String text = String::utf8((const char *)array.ptr(), array.size());
JSON json;
err = json.parse(text);
@ -322,8 +329,7 @@ Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) {
uint32_t len = p_file->get_buffer(json_data.ptrw(), chunk_length);
ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT);
String text;
text.parse_utf8((const char *)json_data.ptr(), json_data.size());
String text = String::utf8((const char *)json_data.ptr(), json_data.size());
JSON json;
Error err = json.parse(text);
@ -525,8 +531,7 @@ String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const St
String GLTFDocument::_sanitize_bone_name(const String &p_name) {
String bone_name = p_name;
bone_name = bone_name.replace(":", "_");
bone_name = bone_name.replace("/", "_");
bone_name = bone_name.replace_chars(":/", '_');
return bone_name;
}
@ -735,7 +740,7 @@ Error GLTFDocument::_encode_buffer_glb(Ref<GLTFState> p_state, const String &p_p
if (file.is_null()) {
return err;
}
if (buffer_data.size() == 0) {
if (buffer_data.is_empty()) {
return OK;
}
file->create(FileAccess::ACCESS_RESOURCES);
@ -767,7 +772,7 @@ Error GLTFDocument::_encode_buffer_bins(Ref<GLTFState> p_state, const String &p_
if (file.is_null()) {
return err;
}
if (buffer_data.size() == 0) {
if (buffer_data.is_empty()) {
return OK;
}
file->create(FileAccess::ACCESS_RESOURCES);
@ -806,8 +811,8 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_
buffer_data = _parse_base64_uri(uri);
} else { // Relative path to an external image file.
ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER);
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
uri = uri.uri_file_decode();
uri = p_base_path.path_join(uri).replace_char('\\', '/'); // Fix for Windows.
ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri);
buffer_data = FileAccess::get_file_as_bytes(uri);
ERR_FAIL_COND_V_MSG(buffer_data.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
@ -882,6 +887,9 @@ Error GLTFDocument::_parse_buffer_views(Ref<GLTFState> p_state) {
if (d.has("byteStride")) {
buffer_view->byte_stride = d["byteStride"];
if (buffer_view->byte_stride < 4 || buffer_view->byte_stride > 252 || buffer_view->byte_stride % 4 != 0) {
ERR_PRINT("glTF import: Invalid byte stride " + itos(buffer_view->byte_stride) + " for buffer view at index " + itos(i) + " while importing file '" + p_state->filename + "'. If defined, byte stride must be a multiple of 4 and between 4 and 252.");
}
}
if (d.has("target")) {
@ -1023,6 +1031,9 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
accessor->component_type = (GLTFAccessor::GLTFComponentType)(int32_t)d["componentType"];
ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR);
accessor->count = d["count"];
if (accessor->count <= 0) {
ERR_PRINT("glTF import: Invalid accessor count " + itos(accessor->count) + " for accessor at index " + itos(i) + " while importing file '" + p_state->filename + "'. Accessor count must be greater than 0.");
}
ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR);
accessor->accessor_type = _get_accessor_type_from_str(d["type"]);
@ -1426,7 +1437,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view];
int stride = p_element_size;
if (bv->byte_stride != -1) {
if (bv->byte_stride > 0) {
stride = bv->byte_stride;
}
if (p_for_vertex && stride % 4) {
@ -1641,7 +1652,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state, const Vector<int32_t> p_attribs, const bool p_for_vertex, const bool p_for_vertex_indices) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 1;
@ -1706,7 +1717,7 @@ Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> p_state, const
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<int> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -1731,7 +1742,7 @@ Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> p_state, c
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<float> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -1762,7 +1773,7 @@ void GLTFDocument::_round_min_max_components(Vector<double> &r_type_min, Vector<
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state, const Vector<Vector2> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 2;
@ -1812,7 +1823,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state,
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
@ -1878,7 +1889,7 @@ void GLTFDocument::_calc_accessor_min_max(int p_i, const int p_element_count, Ve
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
@ -1932,7 +1943,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
@ -1983,7 +1994,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p_state, const Vector<Quaternion> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 4;
@ -2039,7 +2050,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Vector2> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2062,7 +2073,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<double> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 1;
@ -2111,7 +2122,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 3;
@ -2161,7 +2172,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state,
}
GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const Vector<Vector3> p_reference_attribs, const float p_reference_multiplier, const bool p_for_vertex, const GLTFAccessorIndex p_reference_accessor) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
@ -2270,7 +2281,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
}
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state, const Vector<Transform3D> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
if (p_attribs.is_empty()) {
return -1;
}
const int element_count = 16;
@ -2345,7 +2356,7 @@ Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> p_state, c
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Vector3> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2371,7 +2382,7 @@ Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, co
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Color> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2403,7 +2414,7 @@ Vector<Quaternion> GLTFDocument::_decode_accessor_as_quaternion(Ref<GLTFState> p
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Quaternion> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2422,7 +2433,7 @@ Vector<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> p_s
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Transform2D> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2439,7 +2450,7 @@ Vector<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> p_state, co
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Basis> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -2457,7 +2468,7 @@ Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> p_sta
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Transform3D> ret;
if (attribs.size() == 0) {
if (attribs.is_empty()) {
return ret;
}
@ -3185,9 +3196,11 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
primitives.push_back(primitive);
}
Dictionary e;
e["targetNames"] = target_names;
gltf_mesh["extras"] = e;
if (!target_names.is_empty()) {
Dictionary e;
e["targetNames"] = target_names;
gltf_mesh["extras"] = e;
}
_attach_meta_to_extras(import_mesh, gltf_mesh);
weights.resize(target_names.size());
@ -3573,11 +3586,16 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
// Compression is enabled, so let's validate that the normals and tangents are correct.
Vector<Vector3> normals = array[Mesh::ARRAY_NORMAL];
Vector<float> tangents = array[Mesh::ARRAY_TANGENT];
for (int vert = 0; vert < normals.size(); vert++) {
Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]);
if (abs(tan.dot(normals[vert])) > 0.0001) {
// Tangent is not perpendicular to the normal, so we can't use compression.
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
if (unlikely(tangents.size() < normals.size() * 4)) {
ERR_PRINT("glTF import: Mesh " + itos(i) + " has invalid tangents.");
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
} else {
for (int vert = 0; vert < normals.size(); vert++) {
Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]);
if (abs(tan.dot(normals[vert])) > 0.0001) {
// Tangent is not perpendicular to the normal, so we can't use compression.
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}
}
}
}
@ -3824,10 +3842,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
if (p_state->filename.to_lower().ends_with("gltf")) {
String img_name = p_state->images[i]->get_name();
if (img_name.is_empty()) {
img_name = itos(i);
img_name = itos(i).pad_zeros(3);
}
img_name = _gen_unique_name(p_state, img_name);
img_name = img_name.pad_zeros(3);
String relative_texture_dir = "textures";
String full_texture_dir = p_state->base_path.path_join(relative_texture_dir);
Ref<DirAccess> da = DirAccess::open(p_state->base_path);
@ -4116,8 +4133,8 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
}
} else { // Relative path to an external image file.
ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER);
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
uri = uri.uri_file_decode();
uri = p_base_path.path_join(uri).replace_char('\\', '/'); // Fix for Windows.
resource_uri = uri.simplify_path();
// ResourceLoader will rely on the file extension to use the relevant loader.
// The spec says that if mimeType is defined, it should take precedence (e.g.
@ -4141,7 +4158,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
// Fallback to loading as byte array. This enables us to support the
// spec's requirement that we honor mimetype regardless of file URI.
data = FileAccess::get_file_as_bytes(resource_uri);
if (data.size() == 0) {
if (data.is_empty()) {
WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s because there was no data to load. Skipping it.", i, mime_type, resource_uri));
p_state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count.
p_state->source_images.push_back(Ref<Image>());
@ -4396,12 +4413,8 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
Dictionary mr;
{
Array arr;
const Color c = base_material->get_albedo().srgb_to_linear();
arr.push_back(c.r);
arr.push_back(c.g);
arr.push_back(c.b);
arr.push_back(c.a);
Array arr = { c.r, c.g, c.b, c.a };
mr["baseColorFactor"] = arr;
}
if (_image_format != "None") {
@ -4607,10 +4620,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
const Color c = base_material->get_emission().linear_to_srgb();
Array arr;
arr.push_back(c.r);
arr.push_back(c.g);
arr.push_back(c.b);
Array arr = { c.r, c.g, c.b };
d["emissiveFactor"] = arr;
}
@ -5912,10 +5922,12 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
}
}
#ifdef MODULE_CSG_ENABLED
void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) {
#ifndef MODULE_CSG_ENABLED
ERR_FAIL_MSG("csg module is disabled.");
#else
CSGShape3D *csg = p_current;
csg->call("_update_shape");
csg->update_shape();
Array meshes = csg->get_meshes();
if (meshes.size() != 2) {
return;
@ -5964,8 +5976,8 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
p_gltf_node->transform = csg->get_transform();
p_gltf_node->set_original_name(csg->get_name());
p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name()));
}
#endif // MODULE_CSG_ENABLED
}
void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
r_retflag = true;
@ -5996,8 +6008,10 @@ void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state
}
}
#ifdef MODULE_GRIDMAP_ENABLED
void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) {
#ifndef MODULE_GRIDMAP_ENABLED
ERR_FAIL_MSG("gridmap module is disabled.");
#else
Array cells = p_grid_map->get_used_cells();
for (int32_t k = 0; k < cells.size(); k++) {
GLTFNode *new_gltf_node = memnew(GLTFNode);
@ -6025,8 +6039,8 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex
new_gltf_node->set_original_name(p_grid_map->get_mesh_library()->get_item_name(cell));
new_gltf_node->set_name(_gen_unique_name(p_state, p_grid_map->get_mesh_library()->get_item_name(cell)));
}
}
#endif // MODULE_GRIDMAP_ENABLED
}
void GLTFDocument::_convert_multi_mesh_instance_to_gltf(
MultiMeshInstance3D *p_multi_mesh_instance,
@ -6959,7 +6973,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
animation->set_loop_mode(Animation::LOOP_LINEAR);
}
double anim_start = p_trimming ? INFINITY : 0.0;
double anim_start = p_trimming ? Math::INF : 0.0;
double anim_end = 0.0;
for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
@ -7646,7 +7660,7 @@ bool GLTFDocument::_convert_animation_node_track(Ref<GLTFState> p_state, GLTFAni
} else {
Vector3 rotation_euler = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
if (node_prop == "rotation_degrees") {
rotation_euler *= Math_TAU / 360.0;
rotation_euler *= Math::TAU / 360.0;
}
rotation_quaternion = Quaternion::from_euler(rotation_euler);
}
@ -8067,15 +8081,15 @@ Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_
Error GLTFDocument::_serialize_asset_header(Ref<GLTFState> p_state) {
const String version = "2.0";
p_state->major_version = version.get_slice(".", 0).to_int();
p_state->minor_version = version.get_slice(".", 1).to_int();
p_state->major_version = version.get_slicec('.', 0).to_int();
p_state->minor_version = version.get_slicec('.', 1).to_int();
Dictionary asset;
asset["version"] = version;
if (!p_state->copyright.is_empty()) {
asset["copyright"] = p_state->copyright;
}
String hash = String(VERSION_HASH);
asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);
String hash = String(GODOT_VERSION_HASH);
asset["generator"] = String(GODOT_VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);
p_state->json["asset"] = asset;
ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED);
ERR_FAIL_COND_V(!p_state->json.has("asset"), Error::FAILED);
@ -8349,8 +8363,8 @@ Error GLTFDocument::_parse_asset_header(Ref<GLTFState> p_state) {
return ERR_PARSE_ERROR;
}
String version = asset["version"];
p_state->major_version = version.get_slice(".", 0).to_int();
p_state->minor_version = version.get_slice(".", 1).to_int();
p_state->major_version = version.get_slicec('.', 0).to_int();
p_state->minor_version = version.get_slicec('.', 1).to_int();
if (asset.has("copyright")) {
p_state->copyright = asset["copyright"];
}
@ -8540,18 +8554,16 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
// Add the root node(s) and their descendants to the state.
if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_MULTI_ROOT) {
const int child_count = p_node->get_child_count();
if (child_count > 0) {
for (int i = 0; i < child_count; i++) {
_convert_scene_node(state, p_node->get_child(i), -1, -1);
}
state->scene_name = p_node->get_name();
return OK;
for (int i = 0; i < child_count; i++) {
_convert_scene_node(state, p_node->get_child(i), -1, -1);
}
state->scene_name = p_node->get_name();
} else {
if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
state->extensions_used.append("GODOT_single_root");
}
_convert_scene_node(state, p_node, -1, -1);
}
if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
state->extensions_used.append("GODOT_single_root");
}
_convert_scene_node(state, p_node, -1, -1);
// Run post-convert for each extension, in case an extension needs to do something after converting the scene.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());