summaryrefslogtreecommitdiffstats
path: root/modules/gltf/gltf_document.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gltf/gltf_document.cpp')
-rw-r--r--modules/gltf/gltf_document.cpp501
1 files changed, 321 insertions, 180 deletions
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index d828363e03..bac988630d 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -42,7 +42,6 @@
#include "core/io/stream_peer.h"
#include "core/math/disjoint_set.h"
#include "core/version.h"
-#include "drivers/png/png_driver_common.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
@@ -110,11 +109,17 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
return importer_mesh;
}
-Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
+Error GLTFDocument::_serialize(Ref<GLTFState> p_state) {
if (!p_state->buffers.size()) {
p_state->buffers.push_back(Vector<uint8_t>());
}
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Error err = ext->export_preserialize(p_state);
+ ERR_CONTINUE(err != OK);
+ }
+
/* STEP CONVERT MESH INSTANCES */
_convert_mesh_instances(p_state);
@@ -161,7 +166,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE IMAGES */
- err = _serialize_images(p_state, p_path);
+ err = _serialize_images(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -207,7 +212,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE VERSION */
- err = _serialize_version(p_state);
+ err = _serialize_asset_header(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -243,23 +248,18 @@ Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
}
Error GLTFDocument::_serialize_scenes(Ref<GLTFState> p_state) {
+ ERR_FAIL_COND_V_MSG(p_state->root_nodes.size() == 0, ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
+ // Godot only supports one scene per glTF file.
Array scenes;
- const int loaded_scene = 0;
- p_state->json["scene"] = loaded_scene;
-
- if (p_state->nodes.size()) {
- Dictionary s;
- if (!p_state->scene_name.is_empty()) {
- s["name"] = p_state->scene_name;
- }
-
- Array nodes;
- nodes.push_back(0);
- s["nodes"] = nodes;
- scenes.push_back(s);
- }
+ Dictionary scene_dict;
+ scenes.append(scene_dict);
p_state->json["scenes"] = scenes;
-
+ p_state->json["scene"] = 0;
+ // Add nodes to the scene dict.
+ scene_dict["nodes"] = p_state->root_nodes;
+ if (!p_state->scene_name.is_empty()) {
+ scene_dict["name"] = p_state->scene_name;
+ }
return OK;
}
@@ -562,17 +562,17 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) {
if (scenes.size()) {
ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT);
- const Dictionary &s = scenes[loaded_scene];
- ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE);
- const Array &nodes = s["nodes"];
+ const Dictionary &scene_dict = scenes[loaded_scene];
+ ERR_FAIL_COND_V(!scene_dict.has("nodes"), ERR_UNAVAILABLE);
+ const Array &nodes = scene_dict["nodes"];
for (int j = 0; j < nodes.size(); j++) {
p_state->root_nodes.push_back(nodes[j]);
}
-
- if (s.has("name") && !String(s["name"]).is_empty() && !((String)s["name"]).begins_with("Scene")) {
- p_state->scene_name = _gen_unique_name(p_state, s["name"]);
+ // Determine what to use for the scene name.
+ if (scene_dict.has("name") && !String(scene_dict["name"]).is_empty() && !((String)scene_dict["name"]).begins_with("Scene")) {
+ p_state->scene_name = scene_dict["name"];
} else {
- p_state->scene_name = _gen_unique_name(p_state, p_state->filename);
+ p_state->scene_name = p_state->filename;
}
}
@@ -3000,17 +3000,77 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
return OK;
}
-Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_path) {
+void GLTFDocument::set_image_format(const String &p_image_format) {
+ _image_format = p_image_format;
+}
+
+String GLTFDocument::get_image_format() const {
+ return _image_format;
+}
+
+void GLTFDocument::set_lossy_quality(float p_lossy_quality) {
+ _lossy_quality = p_lossy_quality;
+}
+
+float GLTFDocument::get_lossy_quality() const {
+ return _lossy_quality;
+}
+
+Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
Array images;
+ // Check if any extension wants to be the image saver.
+ _image_save_extension = Ref<GLTFDocumentExtension>();
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Vector<String> image_formats = ext->get_saveable_image_formats();
+ if (image_formats.has(_image_format)) {
+ _image_save_extension = ext;
+ break;
+ }
+ }
+ // Serialize every image in the state's images array.
for (int i = 0; i < p_state->images.size(); i++) {
- Dictionary d;
+ Dictionary image_dict;
ERR_CONTINUE(p_state->images[i].is_null());
Ref<Image> image = p_state->images[i]->get_image();
ERR_CONTINUE(image.is_null());
+ if (image->is_compressed()) {
+ image->decompress();
+ ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed.");
+ }
- if (p_path.to_lower().ends_with("glb") || p_path.is_empty()) {
+ 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 = _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);
+ ERR_FAIL_COND_V(da.is_null(), FAILED);
+
+ if (!da->dir_exists(full_texture_dir)) {
+ da->make_dir(full_texture_dir);
+ }
+ if (_image_save_extension.is_valid()) {
+ img_name = img_name + _image_save_extension->get_image_file_extension();
+ Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file.");
+ } else if (_image_format == "PNG") {
+ img_name = img_name + ".png";
+ image->save_png(full_texture_dir.path_join(img_name));
+ } else if (_image_format == "JPEG") {
+ img_name = img_name + ".jpg";
+ image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality);
+ } else {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ }
+ image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode();
+ } else {
GLTFBufferViewIndex bvi;
Ref<GLTFBufferView> bv;
@@ -3026,8 +3086,20 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
if (img_tex.is_valid()) {
image = img_tex->get_image();
}
- Error err = PNGDriverCommon::image_to_png(image, buffer);
- ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG.");
+ // Save in various image formats. Note that if the format is "None",
+ // the state's images will be empty, so this code will not be reached.
+ if (_image_save_extension.is_valid()) {
+ buffer = _image_save_extension->serialize_image_to_bytes(p_state, image, image_dict, _image_format, _lossy_quality);
+ } else if (_image_format == "PNG") {
+ buffer = image->save_png_to_buffer();
+ image_dict["mimeType"] = "image/png";
+ } else if (_image_format == "JPEG") {
+ buffer = image->save_jpg_to_buffer(_lossy_quality);
+ image_dict["mimeType"] = "image/jpeg";
+ } else {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ }
+ ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format.");
bv->byte_length = buffer.size();
p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length);
@@ -3036,27 +3108,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
p_state->buffer_views.push_back(bv);
bvi = p_state->buffer_views.size() - 1;
- d["bufferView"] = bvi;
- d["mimeType"] = "image/png";
- } else {
- ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
- String img_name = p_state->images[i]->get_name();
- if (img_name.is_empty()) {
- img_name = itos(i);
- }
- img_name = _gen_unique_name(p_state, img_name);
- img_name = img_name.pad_zeros(3) + ".png";
- String texture_dir = "textures";
- String path = p_path.get_base_dir();
- String new_texture_dir = path + "/" + texture_dir;
- Ref<DirAccess> da = DirAccess::open(path);
- if (!da->dir_exists(new_texture_dir)) {
- da->make_dir(new_texture_dir);
- }
- image->save_png(new_texture_dir.path_join(img_name));
- d["uri"] = texture_dir.path_join(img_name).uri_encode();
+ image_dict["bufferView"] = bvi;
}
- images.push_back(d);
+ images.push_back(image_dict);
}
print_verbose("Total images: " + itos(p_state->images.size()));
@@ -3069,7 +3123,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
return OK;
}
-Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index) {
+Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) {
Ref<Image> r_image;
r_image.instantiate();
// Check if any GLTFDocumentExtensions want to import this data as an image.
@@ -3078,6 +3132,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image);
ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing.");
if (!r_image->is_empty()) {
+ r_file_extension = ext->get_image_file_extension();
return r_image;
}
}
@@ -3085,8 +3140,10 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
// First we honor the mime types if they were defined.
if (p_mime_type == "image/png") { // Load buffer as PNG.
r_image->load_png_from_buffer(p_bytes);
+ r_file_extension = ".png";
} else if (p_mime_type == "image/jpeg") { // Loader buffer as JPEG.
r_image->load_jpg_from_buffer(p_bytes);
+ r_file_extension = ".jpg";
}
// If we didn't pass the above tests, we attempt loading as PNG and then JPEG directly.
// This covers URIs with base64-encoded data with application/* type but
@@ -3107,7 +3164,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
return r_image;
}
-void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image) {
+void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image) {
GLTFState::GLTFHandleBinary handling = GLTFState::GLTFHandleBinary(p_state->handle_binary_image);
if (p_image->is_empty() || handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) {
p_state->images.push_back(Ref<Texture2D>());
@@ -3124,11 +3181,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
} else {
- Error err = OK;
bool must_import = true;
Vector<uint8_t> img_data = p_image->get_data();
Dictionary generator_parameters;
- String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name() + ".png";
+ String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name();
+ file_path += p_file_extension.is_empty() ? ".png" : p_file_extension;
if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
config.instantiate();
@@ -3149,8 +3206,18 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
}
}
if (must_import) {
- err = p_image->save_png(file_path);
- ERR_FAIL_COND(err != OK);
+ Error err = OK;
+ if (p_file_extension.is_empty()) {
+ // If a file extension was not specified, save the image data to a PNG file.
+ err = p_image->save_png(file_path);
+ ERR_FAIL_COND(err != OK);
+ } else {
+ // If a file extension was specified, save the original bytes to a file with that extension.
+ Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ ERR_FAIL_COND(err != OK);
+ file->store_buffer(p_bytes);
+ file->close();
+ }
// ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
HashMap<StringName, Variant> custom_options;
custom_options[SNAME("mipmaps/generate")] = true;
@@ -3300,9 +3367,10 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
continue;
}
// Parse the image data from bytes into an Image resource and save if needed.
- Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i);
+ String file_extension;
+ Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i, file_extension);
img->set_name(image_name);
- _parse_image_save_image(p_state, mime_type, i, img);
+ _parse_image_save_image(p_state, data, file_extension, i, img);
}
print_verbose("glTF: Total images: " + itos(p_state->images.size()));
@@ -3317,16 +3385,20 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
Array textures;
for (int32_t i = 0; i < p_state->textures.size(); i++) {
- Dictionary d;
- Ref<GLTFTexture> t = p_state->textures[i];
- ERR_CONTINUE(t->get_src_image() == -1);
- d["source"] = t->get_src_image();
-
- GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+ Dictionary texture_dict;
+ Ref<GLTFTexture> gltf_texture = p_state->textures[i];
+ if (_image_save_extension.is_valid()) {
+ Error err = _image_save_extension->serialize_texture_json(p_state, texture_dict, gltf_texture, _image_format);
+ ERR_FAIL_COND_V(err != OK, err);
+ } else {
+ ERR_CONTINUE(gltf_texture->get_src_image() == -1);
+ texture_dict["source"] = gltf_texture->get_src_image();
+ }
+ GLTFTextureSamplerIndex sampler_index = gltf_texture->get_sampler();
if (sampler_index != -1) {
- d["sampler"] = sampler_index;
+ texture_dict["sampler"] = sampler_index;
}
- textures.push_back(d);
+ textures.push_back(texture_dict);
}
p_state->json["textures"] = textures;
@@ -3340,28 +3412,28 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) {
const Array &textures = p_state->json["textures"];
for (GLTFTextureIndex i = 0; i < textures.size(); i++) {
- const Dictionary &dict = textures[i];
- Ref<GLTFTexture> texture;
- texture.instantiate();
+ const Dictionary &texture_dict = textures[i];
+ Ref<GLTFTexture> gltf_texture;
+ gltf_texture.instantiate();
// Check if any GLTFDocumentExtensions want to handle this texture JSON.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- Error err = ext->parse_texture_json(p_state, dict, texture);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(dict)) + " in file " + p_state->filename + ". Continuing.");
- if (texture->get_src_image() != -1) {
+ Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture);
+ ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing.");
+ if (gltf_texture->get_src_image() != -1) {
break;
}
}
- if (texture->get_src_image() == -1) {
+ if (gltf_texture->get_src_image() == -1) {
// No extensions handled it, so use the base GLTF source.
// This may be the fallback, or the only option anyway.
- ERR_FAIL_COND_V(!dict.has("source"), ERR_PARSE_ERROR);
- texture->set_src_image(dict["source"]);
+ ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR);
+ gltf_texture->set_src_image(texture_dict["source"]);
}
- if (texture->get_sampler() == -1 && dict.has("sampler")) {
- texture->set_sampler(dict["sampler"]);
+ if (gltf_texture->get_sampler() == -1 && texture_dict.has("sampler")) {
+ gltf_texture->set_sampler(texture_dict["sampler"]);
}
- p_state->textures.push_back(texture);
+ p_state->textures.push_back(gltf_texture);
}
return OK;
@@ -3387,10 +3459,11 @@ Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> p_state, const GLTFText
const GLTFImageIndex image = p_state->textures[p_texture]->get_src_image();
ERR_FAIL_INDEX_V(image, p_state->images.size(), Ref<Texture2D>());
if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) {
+ ERR_FAIL_INDEX_V(image, p_state->source_images.size(), Ref<Texture2D>());
Ref<PortableCompressedTexture2D> portable_texture;
portable_texture.instantiate();
portable_texture->set_keep_compressed_buffer(true);
- Ref<Image> new_img = p_state->source_images[p_texture]->duplicate();
+ Ref<Image> new_img = p_state->source_images[image]->duplicate();
ERR_FAIL_COND_V(new_img.is_null(), Ref<Texture2D>());
new_img->generate_mipmaps();
if (p_texture_types) {
@@ -3529,7 +3602,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
arr.push_back(c.a);
mr["baseColorFactor"] = arr;
}
- {
+ if (_image_format != "None") {
Dictionary bct;
Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
GLTFTextureIndex gltf_texture_index = -1;
@@ -3806,7 +3879,6 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
} else {
material->set_name(vformat("material_%s", itos(i)));
}
- material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
Dictionary material_extensions;
if (material_dict.has("extensions")) {
material_extensions = material_dict["extensions"];
@@ -5254,26 +5326,24 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
return OK;
}
-void GLTFDocument::_assign_scene_names(Ref<GLTFState> p_state) {
+void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) {
for (int i = 0; i < p_state->nodes.size(); i++) {
- Ref<GLTFNode> n = p_state->nodes[i];
-
+ Ref<GLTFNode> gltf_node = p_state->nodes[i];
// Any joints get unique names generated when the skeleton is made, unique to the skeleton
- if (n->skeleton >= 0) {
+ if (gltf_node->skeleton >= 0) {
continue;
}
-
- if (n->get_name().is_empty()) {
- if (n->mesh >= 0) {
- n->set_name(_gen_unique_name(p_state, "Mesh"));
- } else if (n->camera >= 0) {
- n->set_name(_gen_unique_name(p_state, "Camera3D"));
+ String gltf_node_name = gltf_node->get_name();
+ if (gltf_node_name.is_empty()) {
+ if (gltf_node->mesh >= 0) {
+ gltf_node_name = "Mesh";
+ } else if (gltf_node->camera >= 0) {
+ gltf_node_name = "Camera";
} else {
- n->set_name(_gen_unique_name(p_state, "Node"));
+ gltf_node_name = "Node";
}
}
-
- n->set_name(_gen_unique_name(p_state, n->get_name()));
+ gltf_node->set_name(_gen_unique_name(p_state, gltf_node_name));
}
}
@@ -5462,9 +5532,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
GLTFNodeIndex gltf_root = p_gltf_root;
if (gltf_root == -1) {
gltf_root = current_node_i;
- Array scenes;
- scenes.push_back(gltf_root);
- p_state->json["scene"] = scenes;
+ p_state->root_nodes.push_back(gltf_root);
}
_create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
@@ -5526,7 +5594,7 @@ void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_paren
}
void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- ERR_FAIL_COND(!p_animation_player);
+ ERR_FAIL_NULL(p_animation_player);
p_state->animation_players.push_back(p_animation_player);
print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name());
}
@@ -5545,7 +5613,7 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
}
void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
- ERR_FAIL_COND(!camera);
+ ERR_FAIL_NULL(camera);
GLTFCameraIndex camera_index = _convert_camera(p_state, camera);
if (camera_index != -1) {
p_gltf_node->camera = camera_index;
@@ -5553,7 +5621,7 @@ void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_st
}
void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) {
- ERR_FAIL_COND(!light);
+ ERR_FAIL_NULL(light);
GLTFLightIndex light_index = _convert_light(p_state, light);
if (light_index != -1) {
p_gltf_node->light = light_index;
@@ -5595,7 +5663,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf(
GLTFNodeIndex p_parent_node_index,
GLTFNodeIndex p_root_node_index,
Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) {
- ERR_FAIL_COND(!p_multi_mesh_instance);
+ ERR_FAIL_NULL(p_multi_mesh_instance);
Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh();
if (multi_mesh.is_null()) {
return;
@@ -5751,40 +5819,40 @@ void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent
}
}
-void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) {
- Ref<GLTFNode> gltf_node = p_state->nodes[node_index];
+void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
+ Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
if (gltf_node->skeleton >= 0) {
- _generate_skeleton_bone_node(p_state, scene_parent, scene_root, node_index);
+ _generate_skeleton_bone_node(p_state, p_node_index, p_scene_parent, p_scene_root);
return;
}
Node3D *current_node = nullptr;
// Is our parent a skeleton
- Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent);
+ Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(p_scene_parent);
const bool non_bone_parented_to_skeleton = active_skeleton;
// skinned meshes must not be placed in a bone attachment.
if (non_bone_parented_to_skeleton && gltf_node->skin < 0) {
// Bone Attachment - Parent Case
- BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, gltf_node->parent);
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent);
- scene_parent->add_child(bone_attachment, true);
- bone_attachment->set_owner(scene_root);
+ p_scene_parent->add_child(bone_attachment, true);
+ bone_attachment->set_owner(p_scene_root);
// There is no gltf_node that represent this, so just directly create a unique name
bone_attachment->set_name(gltf_node->get_name());
// We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node
// and attach it to the bone_attachment
- scene_parent = bone_attachment;
+ p_scene_parent = bone_attachment;
}
// Check if any GLTFDocumentExtension classes want to generate a node for us.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- current_node = ext->generate_scene_node(p_state, gltf_node, scene_parent);
+ current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent);
if (current_node) {
break;
}
@@ -5792,38 +5860,49 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_pare
// If none of our GLTFDocumentExtension classes generated us a node, we generate one.
if (!current_node) {
if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) {
- current_node = _generate_spatial(p_state, node_index);
- Node3D *mesh_inst = _generate_mesh_instance(p_state, node_index);
+ // GLTF specifies that skinned meshes should ignore their node transforms,
+ // only being controlled by the skeleton, so Godot will reparent a skinned
+ // mesh to its skeleton. However, we still need to ensure any child nodes
+ // keep their place in the tree, so if there are any child nodes, the skinned
+ // mesh must not be the base node, so generate an empty spatial base.
+ current_node = _generate_spatial(p_state, p_node_index);
+ Node3D *mesh_inst = _generate_mesh_instance(p_state, p_node_index);
mesh_inst->set_name(gltf_node->get_name());
-
current_node->add_child(mesh_inst, true);
} else if (gltf_node->mesh >= 0) {
- current_node = _generate_mesh_instance(p_state, node_index);
+ current_node = _generate_mesh_instance(p_state, p_node_index);
} else if (gltf_node->camera >= 0) {
- current_node = _generate_camera(p_state, node_index);
+ current_node = _generate_camera(p_state, p_node_index);
} else if (gltf_node->light >= 0) {
- current_node = _generate_light(p_state, node_index);
+ current_node = _generate_light(p_state, p_node_index);
} else {
- current_node = _generate_spatial(p_state, node_index);
+ current_node = _generate_spatial(p_state, p_node_index);
}
}
- // Add the node we generated and set the owner to the scene root.
- scene_parent->add_child(current_node, true);
- if (current_node != scene_root) {
+ String gltf_node_name = gltf_node->get_name();
+ if (!gltf_node_name.is_empty()) {
+ current_node->set_name(gltf_node_name);
+ }
+ // Note: p_scene_parent and p_scene_root must either both be null or both be valid.
+ if (p_scene_root == nullptr) {
+ // If the root node argument is null, this is the root node.
+ p_scene_root = current_node;
+ } else {
+ // Add the node we generated and set the owner to the scene root.
+ p_scene_parent->add_child(current_node, true);
Array args;
- args.append(scene_root);
+ args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
+ current_node->set_transform(gltf_node->xform);
}
- current_node->set_transform(gltf_node->xform);
- current_node->set_name(gltf_node->get_name());
- p_state->scene_nodes.insert(node_index, current_node);
+ p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, current_node, scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root);
}
}
-void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index) {
+void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
Node3D *current_node = nullptr;
@@ -5907,7 +5986,7 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, active_skeleton, p_scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], active_skeleton, p_scene_root);
}
}
@@ -6066,7 +6145,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key];
Node *root = p_animation_player->get_parent();
- ERR_FAIL_COND(root == nullptr);
+ ERR_FAIL_NULL(root);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
node_path = root->get_path_to(node_element->value);
@@ -6079,7 +6158,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (gltf_node->skeleton >= 0) {
const Skeleton3D *sk = p_state->skeletons[gltf_node->skeleton]->godot_skeleton;
- ERR_FAIL_COND(sk == nullptr);
+ ERR_FAIL_NULL(sk);
const String path = p_animation_player->get_parent()->get_path_to(sk);
const String bone = gltf_node->get_name();
@@ -6429,7 +6508,7 @@ float GLTFDocument::get_max_component(const Color &p_color) {
return MAX(MAX(r, g), b);
}
-void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) {
+void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) {
for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) {
Ref<GLTFNode> node = p_state->nodes[node_i];
@@ -6999,20 +7078,8 @@ Error GLTFDocument::_parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess
p_state->json = json.get_data();
}
- if (!p_state->json.has("asset")) {
- return ERR_PARSE_ERROR;
- }
-
- Dictionary asset = p_state->json["asset"];
-
- if (!asset.has("version")) {
- 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();
+ err = _parse_asset_header(p_state);
+ ERR_FAIL_COND_V(err != OK, err);
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
@@ -7069,13 +7136,15 @@ Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
-Error GLTFDocument::_serialize_version(Ref<GLTFState> p_state) {
+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();
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);
p_state->json["asset"] = asset;
@@ -7154,6 +7223,21 @@ void GLTFDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
&GLTFDocument::write_to_filesystem);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_SINGLE_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT);
+ BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT);
+
+ ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format);
+ ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format);
+ ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality);
+ ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality);
+ ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode);
+ ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
+
ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
@@ -7238,7 +7322,10 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Erro
PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, PackedByteArray());
- Error err = _serialize(p_state, "");
+ // For buffers, set the state filename to an empty string, but
+ // don't touch the base path, in case the user set it manually.
+ p_state->filename = "";
+ Error err = _serialize(p_state);
ERR_FAIL_COND_V(err != OK, PackedByteArray());
PackedByteArray bytes = _serialize_glb_buffer(p_state, &err);
return bytes;
@@ -7246,7 +7333,9 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- Error err = _serialize(p_state, p_path);
+ p_state->base_path = p_path.get_base_dir();
+ p_state->filename = p_path.get_file();
+ Error err = _serialize(p_state);
if (err != OK) {
return err;
}
@@ -7257,15 +7346,40 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
return OK;
}
+Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) {
+ // Generate the skeletons and skins (if any).
+ Error err = _create_skeletons(p_state);
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons.");
+ err = _create_skins(p_state);
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins.");
+ // Generate the node tree.
+ Node *single_root;
+ if (p_state->extensions_used.has("GODOT_single_root")) {
+ _generate_scene_node(p_state, 0, nullptr, nullptr);
+ single_root = p_state->scene_nodes[0];
+ } else {
+ single_root = memnew(Node3D);
+ for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
+ _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root);
+ }
+ }
+ // Assign the scene name and single root name to each other
+ // if one is missing, or do nothing if both are already set.
+ if (unlikely(p_state->scene_name.is_empty())) {
+ p_state->scene_name = single_root->get_name();
+ } else if (single_root->get_name() == StringName()) {
+ single_root->set_name(_gen_unique_name(p_state, p_state->scene_name));
+ }
+ return single_root;
+}
+
Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
ERR_FAIL_NULL_V(p_state, nullptr);
ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr);
Error err = OK;
- GLTFNodeIndex gltf_root = p_state->root_nodes.write[0];
- Node *gltf_root_node = p_state->get_scene_node(gltf_root);
- Node *root = gltf_root_node->get_parent();
+ Node *root = _generate_scene_node_tree(p_state);
ERR_FAIL_NULL_V(root, nullptr);
- _process_mesh_instances(p_state, root);
+ _process_mesh_instances(p_state);
if (p_state->get_create_animations() && p_state->animations.size()) {
AnimationPlayer *ap = memnew(AnimationPlayer);
root->add_child(ap, true);
@@ -7301,7 +7415,11 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
ERR_FAIL_COND_V(p_state.is_null(), FAILED);
p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
-
+ if (!p_state->buffers.size()) {
+ p_state->buffers.push_back(Vector<uint8_t>());
+ }
+ // Perform export preflight for document extensions. Only extensions that
+ // return OK will be used for the rest of the export steps.
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -7310,10 +7428,21 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
document_extensions.push_back(ext);
}
}
- _convert_scene_node(p_state, p_node, -1, -1);
- if (!p_state->buffers.size()) {
- p_state->buffers.push_back(Vector<uint8_t>());
+ // 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(p_state, p_node->get_child(i), -1, -1);
+ }
+ p_state->scene_name = p_node->get_name();
+ return OK;
+ }
}
+ if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) {
+ p_state->extensions_used.append("GODOT_single_root");
+ }
+ _convert_scene_node(p_state, p_node, -1, -1);
return OK;
}
@@ -7338,6 +7467,23 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
return OK;
}
+Error GLTFDocument::_parse_asset_header(Ref<GLTFState> p_state) {
+ if (!p_state->json.has("asset")) {
+ return ERR_PARSE_ERROR;
+ }
+ Dictionary asset = p_state->json["asset"];
+ if (!asset.has("version")) {
+ 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();
+ if (asset.has("copyright")) {
+ p_state->copyright = asset["copyright"];
+ }
+ return OK;
+}
+
Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path) {
Error err;
@@ -7399,14 +7545,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = _determine_skeletons(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
- /* CREATE SKELETONS */
- err = _create_skeletons(p_state);
- ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
-
- /* CREATE SKINS */
- err = _create_skins(p_state);
- ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
-
/* PARSE MESHES (we have enough info now) */
err = _parse_meshes(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
@@ -7424,24 +7562,19 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
/* ASSIGN SCENE NAMES */
- _assign_scene_names(p_state);
-
- Node3D *root = memnew(Node3D);
- for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
- _generate_scene_node(p_state, root, root, p_state->root_nodes[root_i]);
- }
+ _assign_node_names(p_state);
return OK;
}
-Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) {
+Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags, String p_base_path) {
// TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire
- if (r_state == Ref<GLTFState>()) {
- r_state.instantiate();
+ if (p_state == Ref<GLTFState>()) {
+ p_state.instantiate();
}
- r_state->filename = p_path.get_file().get_basename();
- r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
- r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
+ p_state->filename = p_path.get_file().get_basename();
+ p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
+ p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
@@ -7450,12 +7583,12 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
}
- r_state->base_path = base_path;
- err = _parse(r_state, base_path, file);
+ p_state->base_path = base_path;
+ err = _parse(p_state, base_path, file);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- err = ext->import_post_parse(r_state);
+ err = ext->import_post_parse(p_state);
ERR_FAIL_COND_V(err != OK, err);
}
return OK;
@@ -7493,3 +7626,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
}
return ret;
}
+
+void GLTFDocument::set_root_node_mode(GLTFDocument::RootNodeMode p_root_node_mode) {
+ _root_node_mode = p_root_node_mode;
+}
+
+GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const {
+ return _root_node_mode;
+}