initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.2.70",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
1
]
}
],
"nodes":[
{
"mesh":0,
"name":"mesh_instance_3d"
},
{
"children":[
0
],
"name":"_Node3D_6"
}
],
"materials":[
{
"name":"material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.9999998807907104,
0.9999998807907104,
0.9999998807907104,
1
],
"baseColorTexture":{
"index":0
},
"metallicFactor":0
}
}
],
"meshes":[
{
"name":"Mesh_0",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"material_albedo000",
"uri":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABZJREFUCJljZGBg+P+fgYGBBUIxMAAAKCAEAplLvcoAAAAASUVORK5CYII="
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":4,
"max":[
1,
0,
1
],
"min":[
-1,
0,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":4,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":4,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":6,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":48,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":48,
"byteOffset":48,
"target":34962
},
{
"buffer":0,
"byteLength":32,
"byteOffset":96,
"target":34962
},
{
"buffer":0,
"byteLength":12,
"byteOffset":128,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":140,
"uri":"data:application/octet-stream;base64,AACAPwAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAACAAEAAAACAAMAAQA="
}
]
}

View File

@@ -0,0 +1,147 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.2.70",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
1
]
}
],
"nodes":[
{
"mesh":0,
"name":"mesh_instance_3d"
},
{
"children":[
0
],
"name":"_Node3D_6"
}
],
"materials":[
{
"name":"material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.9999998807907104,
0.9999998807907104,
0.9999998807907104,
1
],
"baseColorTexture":{
"index":0
},
"metallicFactor":0
}
}
],
"meshes":[
{
"name":"Mesh_0",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"material_albedo000",
"uri":"texture.png",
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":4,
"max":[
1,
0,
1
],
"min":[
-1,
0,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":4,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":4,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":6,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":48,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":48,
"byteOffset":48,
"target":34962
},
{
"buffer":0,
"byteLength":32,
"byteOffset":96,
"target":34962
},
{
"buffer":0,
"byteLength":12,
"byteOffset":128,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":140,
"uri":"data:application/octet-stream;base64,AACAPwAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAACAAEAAAACAAMAAQA="
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

View File

@@ -0,0 +1,147 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.2.70",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
1
]
}
],
"nodes":[
{
"mesh":0,
"name":"mesh_instance_3d"
},
{
"children":[
0
],
"name":"_Node3D_6"
}
],
"materials":[
{
"name":"material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.9999998807907104,
0.9999998807907104,
0.9999998807907104,
1
],
"baseColorTexture":{
"index":0
},
"metallicFactor":0
}
}
],
"meshes":[
{
"name":"Mesh_0",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"material_albedo000",
"uri":"../texture.png",
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":4,
"max":[
1,
0,
1
],
"min":[
-1,
0,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":4,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":4,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":6,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":48,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":48,
"byteOffset":48,
"target":34962
},
{
"buffer":0,
"byteLength":32,
"byteOffset":96,
"target":34962
},
{
"buffer":0,
"byteLength":12,
"byteOffset":128,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":140,
"uri":"data:application/octet-stream;base64,AACAPwAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAACAAEAAAACAAMAAQA="
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

View File

@@ -0,0 +1,169 @@
/**************************************************************************/
/* test_gltf.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "tests/test_macros.h"
#ifdef TOOLS_ENABLED
#include "core/os/os.h"
#include "drivers/png/image_loader_png.h"
#include "editor/import/3d/resource_importer_scene.h"
#include "editor/import/resource_importer_texture.h"
#include "editor/inspector/editor_resource_preview.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/main/window.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/compressed_texture.h"
#include "scene/resources/material.h"
#include "scene/resources/packed_scene.h"
#include "tests/core/config/test_project_settings.h"
#include "modules/gltf/editor/editor_scene_importer_gltf.h"
#include "modules/gltf/gltf_document.h"
#include "modules/gltf/gltf_state.h"
namespace TestGltf {
static Node *gltf_import(const String &p_file) {
// Setting up importers.
Ref<ResourceImporterScene> import_scene;
import_scene.instantiate("PackedScene", true);
ResourceFormatImporter::get_singleton()->add_importer(import_scene);
Ref<EditorSceneFormatImporterGLTF> import_gltf;
import_gltf.instantiate();
ResourceImporterScene::add_scene_importer(import_gltf);
// Support processing png files in editor import.
Ref<ResourceImporterTexture> import_texture;
import_texture.instantiate(true);
ResourceFormatImporter::get_singleton()->add_importer(import_texture);
// Once editor import convert pngs to ctex, we will need to load it as ctex resource.
Ref<ResourceFormatLoaderCompressedTexture2D> resource_loader_stream_texture;
if (GD_IS_CLASS_ENABLED(CompressedTexture2D)) {
resource_loader_stream_texture.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_stream_texture);
}
HashMap<StringName, Variant> options(21);
options["nodes/root_type"] = "";
options["nodes/root_name"] = "";
options["nodes/root_script"] = Variant();
options["nodes/apply_root_scale"] = true;
options["nodes/root_scale"] = 1.0;
options["meshes/ensure_tangents"] = true;
options["meshes/generate_lods"] = false;
options["meshes/create_shadow_meshes"] = true;
options["meshes/light_baking"] = 1;
options["meshes/lightmap_texel_size"] = 0.2;
options["meshes/force_disable_compression"] = false;
options["skins/use_named_skins"] = true;
options["animation/import"] = true;
options["animation/fps"] = 30;
options["animation/trimming"] = false;
options["animation/remove_immutable_tracks"] = true;
options["import_script/path"] = "";
options["extract_path"] = "res://";
options["_subresources"] = Dictionary();
options["gltf/naming_version"] = 1;
// Process gltf file, note that this generates `.scn` resource from the 2nd argument.
String scene_file = "res://" + p_file.get_file().get_basename();
Error err = import_scene->import(0, p_file, scene_file, options, nullptr, nullptr, nullptr);
CHECK_MESSAGE(err == OK, "GLTF import failed.");
Ref<PackedScene> packed_scene = ResourceLoader::load(scene_file + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
CHECK_MESSAGE(err == OK, "Loading scene failed.");
Node *p_scene = packed_scene->instantiate();
ResourceImporterScene::remove_scene_importer(import_gltf);
ResourceFormatImporter::get_singleton()->remove_importer(import_texture);
if (GD_IS_CLASS_ENABLED(CompressedTexture2D)) {
ResourceLoader::remove_resource_format_loader(resource_loader_stream_texture);
resource_loader_stream_texture.unref();
}
return p_scene;
}
static Node *gltf_export_then_import(Node *p_root, const String &p_test_name) {
String tempfile = TestUtils::get_temp_path(p_test_name);
Ref<GLTFDocument> doc;
doc.instantiate();
Ref<GLTFState> state;
state.instantiate();
Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS);
CHECK_MESSAGE(err == OK, "GLTF state generation failed.");
err = doc->write_to_filesystem(state, tempfile + ".gltf");
CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed.");
return gltf_import(tempfile + ".gltf");
}
void init(const String &p_test, const String &p_copy_target = String()) {
Error err;
// Setup project settings since it's needed for the import process.
String project_folder = TestUtils::get_temp_path(p_test.get_file().get_basename());
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
da->make_dir_recursive(project_folder.path_join(".godot").path_join("imported"));
// Initialize res:// to `project_folder`.
TestProjectSettingsInternalsAccessor::resource_path() = project_folder;
err = ProjectSettings::get_singleton()->setup(project_folder, String(), true);
if (p_copy_target.is_empty()) {
return;
}
// Copy all the necessary test data files to the res:// directory.
da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String test_data = String("modules/gltf/tests/data/").path_join(p_test);
da = DirAccess::open(test_data);
CHECK_MESSAGE(da.is_valid(), "Unable to open folder.");
da->list_dir_begin();
for (String item = da->get_next(); !item.is_empty(); item = da->get_next()) {
if (!FileAccess::exists(test_data.path_join(item))) {
continue;
}
Ref<FileAccess> output = FileAccess::open(p_copy_target.path_join(item), FileAccess::WRITE, &err);
CHECK_MESSAGE(err == OK, "Unable to open output file.");
output->store_buffer(FileAccess::get_file_as_bytes(test_data.path_join(item)));
output->close();
}
da->list_dir_end();
}
} //namespace TestGltf
#endif // TOOLS_ENABLED

View File

@@ -0,0 +1,175 @@
/**************************************************************************/
/* test_gltf_extras.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "test_gltf.h"
#include "tests/test_macros.h"
#ifdef TOOLS_ENABLED
#include "core/os/os.h"
#include "editor/import/3d/resource_importer_scene.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/main/window.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/material.h"
#include "scene/resources/packed_scene.h"
#include "modules/gltf/editor/editor_scene_importer_gltf.h"
#include "modules/gltf/gltf_document.h"
#include "modules/gltf/gltf_state.h"
namespace TestGltf {
TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") {
init("gltf_mesh_material_extras");
// Setup scene.
Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D);
original_material->set_albedo(Color(1.0, .0, .0));
original_material->set_name("material");
Dictionary material_dict;
material_dict["node_type"] = "material";
original_material->set_meta("extras", material_dict);
Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh);
original_meshdata->set_name("planemesh");
Dictionary meshdata_dict;
meshdata_dict["node_type"] = "planemesh";
original_meshdata->set_meta("extras", meshdata_dict);
original_meshdata->surface_set_material(0, original_material);
MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D);
original_mesh_instance->set_mesh(original_meshdata);
original_mesh_instance->set_name("mesh_instance_3d");
Dictionary mesh_instance_dict;
mesh_instance_dict["node_type"] = "mesh_instance_3d";
original_mesh_instance->set_meta("extras", mesh_instance_dict);
Node3D *original = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(original);
original->add_child(original_mesh_instance);
original->set_name("node3d");
Dictionary node_dict;
node_dict["node_type"] = "node3d";
original->set_meta("extras", node_dict);
original->set_meta("meta_not_nested_under_extras", "should not propagate");
original->set_owner(SceneTree::get_singleton()->get_root());
original_mesh_instance->set_owner(SceneTree::get_singleton()->get_root());
// Convert to GLFT and back.
Node *loaded = gltf_export_then_import(original, "gltf_extras");
// Compare the results.
CHECK(loaded->get_name() == "node3d");
CHECK(Dictionary(loaded->get_meta("extras")).size() == 1);
CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d");
CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras"));
CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras"));
MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true));
CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d");
CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d");
Ref<Mesh> mesh = mesh_instance_3d->get_mesh();
CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh");
Ref<Material> material = mesh->surface_get_material(0);
CHECK(material->get_name() == "material");
CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material");
memdelete(original_mesh_instance);
memdelete(original);
memdelete(loaded);
}
TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") {
init("gltf_skeleton_extras");
// Setup scene.
Skeleton3D *skeleton = memnew(Skeleton3D);
skeleton->set_name("skeleton");
Dictionary skeleton_extras;
skeleton_extras["node_type"] = "skeleton";
skeleton->set_meta("extras", skeleton_extras);
skeleton->add_bone("parent");
skeleton->set_bone_rest(0, Transform3D());
Dictionary parent_bone_extras;
parent_bone_extras["bone"] = "i_am_parent_bone";
skeleton->set_bone_meta(0, "extras", parent_bone_extras);
skeleton->add_bone("child");
skeleton->set_bone_rest(1, Transform3D());
skeleton->set_bone_parent(1, 0);
Dictionary child_bone_extras;
child_bone_extras["bone"] = "i_am_child_bone";
skeleton->set_bone_meta(1, "extras", child_bone_extras);
// We have to have a mesh to link with skeleton or it will not get imported.
Ref<PlaneMesh> meshdata = memnew(PlaneMesh);
meshdata->set_name("planemesh");
MeshInstance3D *mesh = memnew(MeshInstance3D);
mesh->set_mesh(meshdata);
mesh->set_name("mesh_instance_3d");
Node3D *original = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(original);
original->add_child(skeleton);
original->add_child(mesh);
original->set_name("node3d");
// Now that both skeleton and mesh are part of scene, link them.
mesh->set_skeleton_path(mesh->get_path_to(skeleton));
mesh->set_owner(SceneTree::get_singleton()->get_root());
original->set_owner(SceneTree::get_singleton()->get_root());
// Convert to GLFT and back.
Node *loaded = gltf_export_then_import(original, "gltf_bone_extras");
// Compare the results.
CHECK(loaded->get_name() == "node3d");
Skeleton3D *result = Object::cast_to<Skeleton3D>(loaded->find_child("Skeleton3D", false, true));
CHECK(result->get_bone_name(0) == "parent");
CHECK(Dictionary(result->get_bone_meta(0, "extras"))["bone"] == "i_am_parent_bone");
CHECK(result->get_bone_name(1) == "child");
CHECK(Dictionary(result->get_bone_meta(1, "extras"))["bone"] == "i_am_child_bone");
memdelete(skeleton);
memdelete(mesh);
memdelete(original);
memdelete(loaded);
}
} //namespace TestGltf
#endif // TOOLS_ENABLED

View File

@@ -0,0 +1,172 @@
/**************************************************************************/
/* test_gltf_images.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "test_gltf.h"
#ifdef TOOLS_ENABLED
#include "editor/file_system/editor_file_system.h"
#include "editor/file_system/editor_paths.h"
#include "scene/resources/image_texture.h"
namespace TestGltf {
Ref<Texture2D> _check_texture(Node *p_node) {
MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(p_node->find_child("mesh_instance_3d", true, true));
Ref<StandardMaterial3D> material = mesh_instance_3d->get_active_material(0);
Ref<Texture2D> texture = material->get_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO);
CHECK_MESSAGE(texture->get_size().x == 2, "Texture width not correct.");
CHECK_MESSAGE(texture->get_size().y == 2, "Texture height not correct.");
// Check if the loaded texture pixels are exactly as we expect.
for (int x = 0; x < 2; ++x) {
for (int y = 0; y < 2; ++y) {
Color c = texture->get_image()->get_pixel(x, y);
CHECK_MESSAGE(c == Color(x, y, y), "Texture content is incorrect.");
}
}
return texture;
}
TEST_CASE("[SceneTree][Node] Export GLTF with external texture and import") {
init("gltf_images_external_export_import");
// Setup scene.
Ref<ImageTexture> original_texture;
original_texture.instantiate();
Ref<Image> image;
image.instantiate();
image->initialize_data(2, 2, false, Image::FORMAT_RGBA8);
for (int x = 0; x < 2; ++x) {
for (int y = 0; y < 2; ++y) {
image->set_pixel(x, y, Color(x, y, y));
}
}
original_texture->set_image(image);
Ref<StandardMaterial3D> original_material;
original_material.instantiate();
original_material->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, original_texture);
original_material->set_name("material");
Ref<PlaneMesh> original_meshdata;
original_meshdata.instantiate();
original_meshdata->set_name("planemesh");
original_meshdata->surface_set_material(0, original_material);
MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D);
original_mesh_instance->set_mesh(original_meshdata);
original_mesh_instance->set_name("mesh_instance_3d");
Node3D *original = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(original);
original->add_child(original_mesh_instance);
original->set_owner(SceneTree::get_singleton()->get_root());
original_mesh_instance->set_owner(SceneTree::get_singleton()->get_root());
// Convert to GLFT and back.
Node *loaded = gltf_export_then_import(original, "gltf_images");
_check_texture(loaded);
memdelete(original_mesh_instance);
memdelete(original);
memdelete(loaded);
}
TEST_CASE("[SceneTree][Node][Editor] Import GLTF from .godot/imported folder with external texture") {
init("gltf_placed_in_dot_godot_imported", "res://.godot/imported");
EditorFileSystem *efs = memnew(EditorFileSystem);
EditorResourcePreview *erp = memnew(EditorResourcePreview);
ERR_PRINT_OFF
Node *loaded = gltf_import("res://.godot/imported/gltf_placed_in_dot_godot_imported.gltf");
Ref<Texture2D> texture = _check_texture(loaded);
ERR_PRINT_ON
// In-editor imports of gltf and texture from .godot/imported folder should end up in res:// if extract_path is defined.
CHECK_MESSAGE(texture->get_path() == "res://gltf_placed_in_dot_godot_imported_material_albedo000.png", "Texture not parsed as resource.");
memdelete(loaded);
memdelete(erp);
memdelete(efs);
}
TEST_CASE("[SceneTree][Node][Editor] Import GLTF with texture outside of res:// directory") {
init("gltf_pointing_to_texture_outside_of_res_folder", "res://");
EditorFileSystem *efs = memnew(EditorFileSystem);
EditorResourcePreview *erp = memnew(EditorResourcePreview);
// Copy texture to the parent folder of res:// - i.e. to res://.. where we can't import from.
String oneup = TestUtils::get_temp_path("texture.png");
Error err;
Ref<FileAccess> output = FileAccess::open(oneup, FileAccess::WRITE, &err);
CHECK_MESSAGE(err == OK, "Unable to open texture file.");
output->store_buffer(FileAccess::get_file_as_bytes("res://texture_source.png"));
output->close();
ERR_PRINT_OFF
Node *loaded = gltf_import("res://gltf_pointing_to_texture_outside_of_res_folder.gltf");
Ref<Texture2D> texture = _check_texture(loaded);
ERR_PRINT_ON
// Imports of gltf with texture from outside of res:// folder should end up being copied to res://
CHECK_MESSAGE(texture->get_path() == "res://gltf_pointing_to_texture_outside_of_res_folder_material_albedo000.png", "Texture not parsed as resource.");
memdelete(loaded);
memdelete(erp);
memdelete(efs);
}
TEST_CASE("[SceneTree][Node][Editor] Import GLTF with embedded texture, check how it got extracted") {
init("gltf_embedded_texture", "res://");
EditorFileSystem *efs = memnew(EditorFileSystem);
EditorResourcePreview *erp = memnew(EditorResourcePreview);
ERR_PRINT_OFF
Node *loaded = gltf_import("res://embedded_texture.gltf");
Ref<Texture2D> texture = _check_texture(loaded);
ERR_PRINT_ON
// In-editor imports of texture embedded in file should end up with a resource.
CHECK_MESSAGE(texture->get_path() == "res://embedded_texture_material_albedo000.png", "Texture not parsed as resource.");
memdelete(loaded);
memdelete(erp);
memdelete(efs);
}
} //namespace TestGltf
#endif // TOOLS_ENABLED