Add ufbx for FBX importing

This update introduces a new import method for FBX files using ufbx. If the fbx2gltf import fails, it will use the most recently cached scene from the ufbx import. The process is sped up by introducing threads to load the ufbx portion.

Key changes include:

- Support for importing geometry helper nodes in FBX files.
- Addition of cameras and lights with updated names.
- Removal of the fbx importer manager.
- Introduction of ModelDocument3D and updates to its methods.
- Changes to FBX import options and visibility.
- Updating the documentation and handling some errors.
- Store the original non-unique node, mesh and animation names in FBX and glTF.

Co-Authored-By: bqqbarbhg <bqqbarbhg@gmail.com>
This commit is contained in:
K. S. Ernest (iFire) Lee 2024-02-16 05:25:15 -08:00 committed by Rémi Verschelde
parent 2fe8f07b6c
commit 04d43947bf
No known key found for this signature in database
GPG Key ID: C3336907360768E1
63 changed files with 41642 additions and 912 deletions

View File

@ -492,6 +492,11 @@ Copyright: 2014-2021, Syoyo Fujita
2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
License: BSD-3-clause
Files: ./thirdparty/ufbx/
Comment: ufbx
Copyright: 2020, Samuli Raivio
License: Expat
Files: ./thirdparty/vhacd/
Comment: V-HACD
Copyright: 2011, Khaled Mamou

View File

@ -571,9 +571,9 @@
The maximum idle uptime (in seconds) of the Blender process.
This prevents Godot from having to create a new process for each import within the given seconds.
</member>
<member name="filesystem/import/fbx/fbx2gltf_path" type="String" setter="" getter="">
<member name="filesystem/import/fbx2gltf/fbx2gltf_path" type="String" setter="" getter="">
The path to the FBX2glTF executable used for converting Autodesk FBX 3D scene files [code].fbx[/code] to glTF 2.0 format during import.
To enable this feature for your specific project, use [member ProjectSettings.filesystem/import/fbx/enabled].
To enable this feature for your specific project, use [member ProjectSettings.filesystem/import/fbx2gltf/enabled].
</member>
<member name="filesystem/on_save/compress_binary_resources" type="bool" setter="" getter="">
If [code]true[/code], uses lossless compression for binary resources.

View File

@ -984,15 +984,15 @@
<member name="filesystem/import/blender/enabled.web" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/blender/enabled] on the Web where Blender can't easily be accessed from Godot.
</member>
<member name="filesystem/import/fbx/enabled" type="bool" setter="" getter="" default="true">
<member name="filesystem/import/fbx2gltf/enabled" type="bool" setter="" getter="" default="true">
If [code]true[/code], Autodesk FBX 3D scene files with the [code].fbx[/code] extension will be imported by converting them to glTF 2.0.
This requires configuring a path to a FBX2glTF executable in the editor settings at [code]filesystem/import/fbx/fbx2gltf_path[/code].
This requires configuring a path to a FBX2glTF executable in the editor settings at [member EditorSettings.filesystem/import/fbx2gltf/fbx2gltf_path].
</member>
<member name="filesystem/import/fbx/enabled.android" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/fbx/enabled] on Android where FBX2glTF can't easily be accessed from Godot.
<member name="filesystem/import/fbx2gltf/enabled.android" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/fbx2gltf/enabled] on Android where FBX2glTF can't easily be accessed from Godot.
</member>
<member name="filesystem/import/fbx/enabled.web" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/fbx/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
<member name="filesystem/import/fbx2gltf/enabled.web" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
</member>
<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.

View File

@ -556,7 +556,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx2gltf/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
// Tools (denoise)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/tools/oidn/oidn_denoise_path", "", "", PROPERTY_USAGE_DEFAULT)

View File

@ -46,7 +46,7 @@ void FBXImporterManager::_notification(int p_what) {
}
void FBXImporterManager::show_dialog(bool p_exclusive) {
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx2gltf/fbx2gltf_path");
fbx_path->set_text(fbx2gltf_path);
_validate_path(fbx2gltf_path);
@ -56,8 +56,8 @@ void FBXImporterManager::show_dialog(bool p_exclusive) {
set_close_on_escape(!p_exclusive);
if (is_importing) {
get_cancel_button()->set_text(TTR("Disable FBX & Restart"));
get_cancel_button()->set_tooltip_text(TTR("Canceling this dialog will disable the FBX importer.\nYou can re-enable it in the Project Settings under Filesystem > Import > FBX > Enabled.\n\nThe editor will restart as importers are registered when the editor starts."));
get_cancel_button()->set_text(TTR("Disable FBX2glTF & Restart"));
get_cancel_button()->set_tooltip_text(TTR("Canceling this dialog will disable the FBX2glTF importer and use the ufbx importer.\nYou can re-enable FBX2glTF in the Project Settings under Filesystem > Import > FBX > Enabled.\n\nThe editor will restart as importers are registered when the editor starts."));
} else {
get_cancel_button()->set_text(TTR("Cancel"));
get_cancel_button()->set_tooltip_text("");
@ -105,7 +105,7 @@ void FBXImporterManager::_select_file(const String &p_path) {
void FBXImporterManager::_path_confirmed() {
String path = fbx_path->get_text();
EditorSettings::get_singleton()->set("filesystem/import/fbx/fbx2gltf_path", path);
EditorSettings::get_singleton()->set("filesystem/import/fbx2gltf/fbx2gltf_path", path);
EditorSettings::get_singleton()->save();
}
@ -114,7 +114,7 @@ void FBXImporterManager::_cancel_setup() {
return; // No worry.
}
// No escape.
ProjectSettings::get_singleton()->set("filesystem/import/fbx/enabled", false);
ProjectSettings::get_singleton()->set("filesystem/import/fbx2gltf/enabled", false);
ProjectSettings::get_singleton()->save();
EditorNode::get_singleton()->save_all_scenes();
EditorNode::get_singleton()->restart_editor();
@ -136,7 +136,7 @@ FBXImporterManager::FBXImporterManager() {
set_title(TTR("Configure FBX Importer"));
VBoxContainer *vb = memnew(VBoxContainer);
vb->add_child(memnew(Label(TTR("FBX2glTF is required for importing FBX files.\nPlease download it and provide a valid path to the binary:"))));
vb->add_child(memnew(Label(TTR("FBX2glTF is required for importing FBX files if using FBX2glTF.\nAlternatively, you can use ufbx by disabling FBX2glTF.\nPlease download the necessary tool and provide a valid path to the binary:"))));
LinkButton *lb = memnew(LinkButton);
lb->set_text(TTR("Click this link to download FBX2glTF"));
lb->set_uri("https://godotengine.org/fbx-import");

View File

@ -52,14 +52,14 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
return;
}
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
// Rename bones in Skeleton3D.
{
if (skeleton) {
// Rename bones in Skeleton3D.
int len = skeleton->get_bone_count();
for (int i = 0; i < len; i++) {
StringName bn = p_rename_map[skeleton->get_bone_name(i)];
if (bn) {
skeleton->set_bone_name(i, bn);
String current_bone_name = skeleton->get_bone_name(i);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
skeleton->set_bone_name(i, new_bone_name->value);
}
}
}
@ -76,10 +76,13 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
if (mesh_skeleton && node == skeleton) {
int len = skin->get_bind_count();
for (int i = 0; i < len; i++) {
StringName bn = p_rename_map[skin->get_bind_name(i)];
if (bn) {
skin->set_bind_name(i, bn);
String current_bone_name = skin->get_bind_name(i);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
skin->set_bind_name(i, new_bone_name->value);
}
}
}
@ -107,9 +110,11 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
if (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == skeleton) {
StringName bn = p_rename_map[anim->track_get_path(i).get_subname(0)];
if (bn) {
anim->track_set_path(i, track_path + ":" + bn);
String current_bone_name = anim->track_get_path(i).get_subname(0);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
String new_track_path = track_path + ":" + new_bone_name->value;
anim->track_set_path(i, new_track_path);
}
}
}

View File

@ -488,8 +488,9 @@ void _apply_scale_to_scalable_node_collection(ScalableNodeCollection &p_collecti
if (skeleton_3d) {
for (int i = 0; i < skeleton_3d->get_bone_count(); i++) {
Transform3D rest = skeleton_3d->get_bone_rest(i);
Vector3 position = skeleton_3d->get_bone_pose_position(i);
skeleton_3d->set_bone_rest(i, Transform3D(rest.basis, p_scale * rest.origin));
skeleton_3d->set_bone_pose_position(i, p_scale * rest.origin);
skeleton_3d->set_bone_pose_position(i, p_scale * position);
}
}
}

View File

@ -1168,7 +1168,7 @@ bool ProjectConverter3To4::test_array_names() {
// Callable is special class, to which normal classes may be renamed.
if (!ClassDB::class_exists(StringName(new_class)) && new_class != "Callable") {
ERR_PRINT(vformat("Class \"%s\" does not exist in Godot 4, so it cannot be used in the conversion.", old_class));
ERR_PRINT(vformat("Class \"%s\" does not exist in Godot 4, so it cannot be used in the conversion.", new_class));
valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
}
}

View File

@ -1514,7 +1514,7 @@ const char *RenamesMap3To4::class_renames[][2] = {
{ "DynamicFontData", "FontFile" },
{ "EditorNavigationMeshGenerator", "NavigationMeshGenerator" },
{ "EditorSceneImporter", "EditorSceneFormatImporter" },
{ "EditorSceneImporterFBX", "EditorSceneFormatImporterFBX" },
{ "EditorSceneImporterFBX", "EditorSceneFormatImporterFBX2GLTF" },
{ "EditorSceneImporterGLTF", "EditorSceneFormatImporterGLTF" },
{ "EditorSpatialGizmo", "EditorNode3DGizmo" },
{ "EditorSpatialGizmoPlugin", "EditorNode3DGizmoPlugin" },

View File

@ -204,3 +204,14 @@ Validate extension JSON: Error: Field 'classes/Window/methods/has_theme_stylebox
Fix the default parameter value for StringName and Variant.
The changes to StringName parameters should be equivalent to the previous default values.
The change to the Variant parameter in 'add_code_completion_option' breaks behavior compatibility.
GH-81746
--------
Validate extension JSON: API was removed: classes/EditorSceneFormatImporterFBX
Renamed to EditorSceneFormatImporterFBX2GLTF.
The compat breakage was deemed necessary as this is a class most users wouldn't
use directly, and the name needs to be disambiguated with the new
EditorSceneFormatImporterUFBX.

48
modules/fbx/SCsub Normal file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_fbx = env_modules.Clone()
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/ufbx/"
thirdparty_sources = [thirdparty_dir + "ufbx.c"]
env_fbx.Prepend(CPPPATH=[thirdparty_dir])
env_thirdparty = env_fbx.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.Append(
CPPDEFINES=[
"UFBX_NO_SUBDIVISION",
"UFBX_NO_TESSELLATION",
"UFBX_NO_GEOMETRY_CACHE",
"UFBX_NO_SCENE_EVALUATION",
"UFBX_NO_INDEX_GENERATION",
"UFBX_NO_SKINNING_EVALUATION",
"UFBX_NO_FORMAT_OBJ",
]
)
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_fbx.add_source_files(module_obj, "*.cpp")
env_fbx.add_source_files(module_obj, "structures/*.cpp")
if env.editor_build:
env_fbx.add_source_files(module_obj, "editor/*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

20
modules/fbx/config.py Normal file
View File

@ -0,0 +1,20 @@
def can_build(env, platform):
env.module_add_dependencies("fbx", ["gltf"])
return not env["disable_3d"]
def configure(env):
pass
def get_doc_classes():
return [
"EditorSceneFormatImporterFBX2GLTF",
"EditorSceneFormatImporterUFBX",
"FBXDocument",
"FBXState",
]
def get_doc_path():
return "doc_classes"

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorSceneFormatImporterFBX2GLTF" inherits="EditorSceneFormatImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Importer for the [code].fbx[/code] scene file format.
</brief_description>
<description>
Imports Autodesk FBX 3D scenes by way of converting them to glTF 2.0 using the FBX2glTF command line tool.
The location of the FBX2glTF binary is set via the [member EditorSettings.filesystem/import/fbx2gltf/fbx2gltf_path] editor setting.
This importer is only used if [member ProjectSettings.filesystem/import/fbx2gltf/enabled] is set to [code]true[/code].
</description>
<tutorials>
</tutorials>
</class>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorSceneFormatImporterUFBX" inherits="EditorSceneFormatImporter" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Import FBX files using the ufbx library.
</brief_description>
<description>
EditorSceneFormatImporterUFBX is designed to load FBX files and supports both binary and ASCII FBX files from version 3000 onward. This class supports various 3D object types like meshes, skins, blend shapes, materials, and rigging information. The class aims for feature parity with the official FBX SDK and supports FBX 7.4 specifications.
</description>
<tutorials>
</tutorials>
</class>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="FBXDocument" inherits="GLTFDocument" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Handles FBX documents.
</brief_description>
<description>
The FBXDocument handles FBX documents. It provides methods to append data from buffers or files, generate scenes, and register/unregister document extensions.
When exporting FBX from Blender, use the "FBX Units Scale" option. The "FBX Units Scale" option sets the correct scale factor and avoids manual adjustments when re-importing into Blender, such as through glTF export.
</description>
<tutorials>
</tutorials>
</class>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="FBXState" inherits="GLTFState" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
</brief_description>
<description>
The FBXState handles the state data imported from FBX files.
</description>
<tutorials>
</tutorials>
<members>
<member name="allow_geometry_helper_nodes" type="bool" setter="set_allow_geometry_helper_nodes" getter="get_allow_geometry_helper_nodes" default="false">
If [code]true[/code], the import process used auxiliary nodes called geometry helper nodes. These nodes help preserve the pivots and transformations of the original 3D model during import.
</member>
</members>
</class>

View File

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_scene_importer_fbx.cpp */
/* editor_scene_importer_fbx2gltf.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,27 +28,39 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_scene_importer_fbx.h"
#include "editor_scene_importer_fbx2gltf.h"
#ifdef TOOLS_ENABLED
#include "../gltf_document.h"
#include "editor_scene_importer_ufbx.h"
#include "modules/gltf/gltf_document.h"
#include "core/config/project_settings.h"
#include "editor/editor_settings.h"
#include "main/main.h"
uint32_t EditorSceneFormatImporterFBX::get_import_flags() const {
uint32_t EditorSceneFormatImporterFBX2GLTF::get_import_flags() const {
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
}
void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) const {
void EditorSceneFormatImporterFBX2GLTF::get_extensions(List<String> *r_extensions) const {
r_extensions->push_back("fbx");
}
Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags,
Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
// FIXME: Hack to work around GH-86309.
if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX) {
Ref<EditorSceneFormatImporterUFBX> fbx2gltf_importer;
fbx2gltf_importer.instantiate();
Node *scene = fbx2gltf_importer->import_scene(p_path, p_flags, p_options, r_missing_deps, r_err);
if (r_err && *r_err == OK) {
return scene;
} else {
return nullptr;
}
}
// Get global paths for source and sink.
// Don't use `c_escape()` as it can generate broken paths. These paths will be
@ -61,7 +73,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t
// Run fbx2gltf.
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx2gltf/fbx2gltf_path");
List<String> args;
args.push_back("--pbr-metallic-roughness");
@ -106,43 +118,32 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t
bool remove_immutable = p_options.has("animation/remove_immutable_tracks") ? (bool)p_options["animation/remove_immutable_tracks"] : true;
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable);
#else
return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
return gltf->create_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
#endif
}
Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation,
Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
if (p_option == "fbx/embedded_image_handling") {
return false;
}
if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF && p_option == "fbx/embedded_image_handling") {
return false;
}
return true;
}
void EditorSceneFormatImporterFBX::get_import_options(const String &p_path,
#define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE));
void EditorSceneFormatImporterFBX2GLTF::get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) {
}
bool EditorFileSystemImportFormatSupportQueryFBX::is_active() const {
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
return !FileAccess::exists(fbx2gltf_path);
}
Vector<String> EditorFileSystemImportFormatSupportQueryFBX::get_file_extensions() const {
Vector<String> ret;
ret.push_back("fbx");
return ret;
}
bool EditorFileSystemImportFormatSupportQueryFBX::query() {
FBXImporterManager::get_singleton()->show_dialog(true);
while (true) {
OS::get_singleton()->delay_usec(1);
DisplayServer::get_singleton()->process_events();
Main::iteration();
if (!FBXImporterManager::get_singleton()->is_visible()) {
break;
}
void EditorSceneFormatImporterFBX2GLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
if (!p_import_params.has("fbx/importer")) {
p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX;
}
return false;
}
#endif // TOOLS_ENABLED

View File

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_scene_importer_fbx.h */
/* editor_scene_importer_fbx2gltf.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,20 +28,18 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef EDITOR_SCENE_IMPORTER_FBX_H
#define EDITOR_SCENE_IMPORTER_FBX_H
#ifndef EDITOR_SCENE_IMPORTER_FBX2GLTF_H
#define EDITOR_SCENE_IMPORTER_FBX2GLTF_H
#ifdef TOOLS_ENABLED
#include "editor/editor_file_system.h"
#include "editor/fbx_importer_manager.h"
#include "editor/import/3d/resource_importer_scene.h"
class Animation;
class Node;
class EditorSceneFormatImporterFBX : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterFBX, EditorSceneFormatImporter);
class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter);
public:
virtual uint32_t get_import_flags() const override;
@ -53,17 +51,9 @@ public:
List<ResourceImporter::ImportOption> *r_options) override;
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
};
class EditorFileSystemImportFormatSupportQueryFBX : public EditorFileSystemImportFormatSupportQuery {
GDCLASS(EditorFileSystemImportFormatSupportQueryFBX, EditorFileSystemImportFormatSupportQuery);
public:
virtual bool is_active() const override;
virtual Vector<String> get_file_extensions() const override;
virtual bool query() override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
#endif // TOOLS_ENABLED
#endif // EDITOR_SCENE_IMPORTER_FBX_H
#endif // EDITOR_SCENE_IMPORTER_FBX2GLTF_H

View File

@ -0,0 +1,112 @@
/**************************************************************************/
/* editor_scene_importer_ufbx.cpp */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#include "editor_scene_importer_ufbx.h"
#ifdef TOOLS_ENABLED
#include "../fbx_document.h"
#include "editor_scene_importer_fbx2gltf.h"
#include "core/config/project_settings.h"
uint32_t EditorSceneFormatImporterUFBX::get_import_flags() const {
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
}
void EditorSceneFormatImporterUFBX::get_extensions(List<String> *r_extensions) const {
r_extensions->push_back("fbx");
}
Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
// FIXME: Hack to work around GH-86309.
if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == FBX_IMPORTER_FBX2GLTF && GLOBAL_GET("filesystem/import/fbx2gltf/enabled")) {
Ref<EditorSceneFormatImporterFBX2GLTF> fbx2gltf_importer;
fbx2gltf_importer.instantiate();
Node *scene = fbx2gltf_importer->import_scene(p_path, p_flags, p_options, r_missing_deps, r_err);
if (r_err && *r_err == OK) {
return scene;
} else {
return nullptr;
}
}
Ref<FBXDocument> fbx;
fbx.instantiate();
Ref<FBXState> state;
state.instantiate();
print_verbose(vformat("FBX path: %s", p_path));
String path = ProjectSettings::get_singleton()->globalize_path(p_path);
bool allow_geometry_helper_nodes = p_options.has("fbx/allow_geometry_helper_nodes") ? (bool)p_options["fbx/allow_geometry_helper_nodes"] : false;
if (allow_geometry_helper_nodes) {
state->set_allow_geometry_helper_nodes(allow_geometry_helper_nodes);
}
if (p_options.has("fbx/embedded_image_handling")) {
int32_t enum_option = p_options["fbx/embedded_image_handling"];
state->set_handle_binary_image(enum_option);
}
p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS;
Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir());
if (err != OK) {
if (r_err) {
*r_err = FAILED;
}
return nullptr;
}
return fbx->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
}
Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
String file_extension = p_path.get_extension().to_lower();
if (file_extension != "fbx" && p_option.begins_with("fbx/")) {
return false;
}
if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) {
return false;
}
return true;
}
void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) {
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES));
}
void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
if (!p_import_params.has("fbx/importer")) {
p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX;
}
}
#endif // TOOLS_ENABLED

View File

@ -0,0 +1,62 @@
/**************************************************************************/
/* editor_scene_importer_ufbx.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. */
/**************************************************************************/
#ifndef EDITOR_SCENE_IMPORTER_UFBX_H
#define EDITOR_SCENE_IMPORTER_UFBX_H
#ifdef TOOLS_ENABLED
#include "editor/import/3d/resource_importer_scene.h"
class Animation;
class Node;
class EditorSceneFormatImporterUFBX : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterUFBX, EditorSceneFormatImporter);
public:
enum FBX_IMPORTER_TYPE {
FBX_IMPORTER_UFBX,
FBX_IMPORTER_FBX2GLTF,
};
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
#endif // TOOLS_ENABLED
#endif // EDITOR_SCENE_IMPORTER_UFBX_H

2373
modules/fbx/fbx_document.cpp Normal file

File diff suppressed because it is too large Load Diff

107
modules/fbx/fbx_document.h Normal file
View File

@ -0,0 +1,107 @@
/**************************************************************************/
/* fbx_document.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. */
/**************************************************************************/
#ifndef FBX_DOCUMENT_H
#define FBX_DOCUMENT_H
#include "fbx_state.h"
#include "modules/gltf/gltf_defines.h"
#include "modules/gltf/gltf_document.h"
#include <ufbx.h>
class FBXDocument : public GLTFDocument {
GDCLASS(FBXDocument, GLTFDocument);
public:
enum {
TEXTURE_TYPE_GENERIC = 0,
TEXTURE_TYPE_NORMAL = 1,
};
static Transform3D _as_xform(const ufbx_matrix &p_mat);
static String _as_string(const ufbx_string &p_string);
static Vector3 _as_vec3(const ufbx_vec3 &p_vector);
static String _gen_unique_name(HashSet<String> &unique_names, const String &p_name);
public:
Error append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags = 0, String p_base_path = String());
Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0);
Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0);
public:
Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true);
PackedByteArray generate_buffer(Ref<GLTFState> p_state);
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path);
protected:
static void _bind_methods();
private:
String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const;
void _process_uv_set(PackedVector2Array &uv_array);
void _zero_unused_elements(Vector<float> &cur_custom, int start, int end, int num_channels);
Error _parse_scenes(Ref<FBXState> p_state);
Error _parse_nodes(Ref<FBXState> p_state);
String _sanitize_animation_name(const String &p_name);
String _gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name);
Ref<Texture2D> _get_texture(Ref<FBXState> p_state,
const GLTFTextureIndex p_texture, int p_texture_type);
Error _parse_meshes(Ref<FBXState> p_state);
Ref<Image> _parse_image_bytes_into_image(Ref<FBXState> p_state, const Vector<uint8_t> &p_bytes, const String &p_filename, int p_index);
GLTFImageIndex _parse_image_save_image(Ref<FBXState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image);
Error _parse_images(Ref<FBXState> p_state, const String &p_base_path);
Error _parse_materials(Ref<FBXState> p_state);
Error _parse_skins(Ref<FBXState> p_state);
Error _parse_animations(Ref<FBXState> p_state);
BoneAttachment3D *_generate_bone_attachment(Ref<FBXState> p_state,
Skeleton3D *p_skeleton,
const GLTFNodeIndex p_node_index,
const GLTFNodeIndex p_bone_index);
ImporterMeshInstance3D *_generate_mesh_instance(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
Camera3D *_generate_camera(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
Light3D *_generate_light(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
Node3D *_generate_spatial(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
void _assign_node_names(Ref<FBXState> p_state);
Error _parse_cameras(Ref<FBXState> p_state);
Error _parse_lights(Ref<FBXState> p_state);
public:
Error _parse_fbx_state(Ref<FBXState> p_state, const String &p_search_path);
void _process_mesh_instances(Ref<FBXState> p_state, Node *p_scene_root);
void _generate_scene_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
void _generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
void _import_animation(Ref<FBXState> p_state, AnimationPlayer *p_animation_player,
const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks);
Error _parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> p_file);
};
#endif // FBX_DOCUMENT_H

46
modules/fbx/fbx_state.cpp Normal file
View File

@ -0,0 +1,46 @@
/**************************************************************************/
/* fbx_state.cpp */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#include "fbx_state.h"
void FBXState::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_allow_geometry_helper_nodes"), &FBXState::get_allow_geometry_helper_nodes);
ClassDB::bind_method(D_METHOD("set_allow_geometry_helper_nodes", "allow"), &FBXState::set_allow_geometry_helper_nodes);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_geometry_helper_nodes"), "set_allow_geometry_helper_nodes", "get_allow_geometry_helper_nodes");
}
bool FBXState::get_allow_geometry_helper_nodes() {
return allow_geometry_helper_nodes;
}
void FBXState::set_allow_geometry_helper_nodes(bool p_allow_geometry_helper_nodes) {
allow_geometry_helper_nodes = p_allow_geometry_helper_nodes;
}

68
modules/fbx/fbx_state.h Normal file
View File

@ -0,0 +1,68 @@
/**************************************************************************/
/* fbx_state.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. */
/**************************************************************************/
#ifndef FBX_STATE_H
#define FBX_STATE_H
#include "modules/gltf/gltf_defines.h"
#include "modules/gltf/gltf_state.h"
#include "modules/gltf/structures/gltf_skeleton.h"
#include "modules/gltf/structures/gltf_skin.h"
#include "modules/gltf/structures/gltf_texture.h"
#include <ufbx.h>
class FBXState : public GLTFState {
GDCLASS(FBXState, GLTFState);
friend class FBXDocument;
friend class SkinTool;
friend class GLTFSkin;
// Smart pointer that holds the loaded scene.
ufbx_unique_ptr<ufbx_scene> scene;
bool allow_geometry_helper_nodes = false;
HashMap<uint64_t, Image::AlphaMode> alpha_mode_cache;
HashMap<Pair<uint64_t, uint64_t>, GLTFTextureIndex, PairHash<uint64_t, uint64_t>> albedo_transparency_textures;
Vector<GLTFSkinIndex> skin_indices;
HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_fbx_skeleton;
HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_fbx_skin;
HashSet<String> unique_mesh_names; // Not in GLTFState because GLTFState prefixes mesh names with the scene name (or _)
protected:
static void _bind_methods();
public:
bool get_allow_geometry_helper_nodes();
void set_allow_geometry_helper_nodes(bool p_allow_geometry_helper_nodes);
};
#endif // FBX_STATE_H

View File

@ -0,0 +1,88 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#include "register_types.h"
#include "fbx_document.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_scene_importer_fbx2gltf.h"
#include "editor/editor_scene_importer_ufbx.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
static void _editor_init() {
Ref<EditorSceneFormatImporterUFBX> import_fbx;
import_fbx.instantiate();
ResourceImporterScene::add_scene_importer(import_fbx);
bool fbx2gltf_enabled = GLOBAL_GET("filesystem/import/fbx2gltf/enabled");
if (fbx2gltf_enabled) {
Ref<EditorSceneFormatImporterFBX2GLTF> importer;
importer.instantiate();
ResourceImporterScene::add_scene_importer(importer);
}
}
#endif // TOOLS_ENABLED
void initialize_fbx_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
GDREGISTER_CLASS(FBXDocument);
GDREGISTER_CLASS(FBXState);
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
// Editor-specific API.
ClassDB::APIType prev_api = ClassDB::get_current_api();
ClassDB::set_current_api(ClassDB::API_EDITOR);
GDREGISTER_CLASS(EditorSceneFormatImporterUFBX);
GLOBAL_DEF_RST_BASIC("filesystem/import/fbx2gltf/enabled", true);
GDREGISTER_CLASS(EditorSceneFormatImporterFBX2GLTF);
GLOBAL_DEF_RST("filesystem/import/fbx2gltf/enabled.android", false);
GLOBAL_DEF_RST("filesystem/import/fbx2gltf/enabled.web", false);
ClassDB::set_current_api(prev_api);
EditorNode::add_init_callback(_editor_init);
}
#endif // TOOLS_ENABLED
}
void uninitialize_fbx_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
// TODO: 20240118 // fire
// FBXDocument::unregister_all_gltf_document_extensions();
}

View File

@ -0,0 +1,39 @@
/**************************************************************************/
/* register_types.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. */
/**************************************************************************/
#ifndef FBX_REGISTER_TYPES_H
#define FBX_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_fbx_module(ModuleInitializationLevel p_level);
void uninitialize_fbx_module(ModuleInitializationLevel p_level);
#endif // FBX_REGISTER_TYPES_H

View File

@ -6,8 +6,11 @@ Import("env_modules")
env_gltf = env_modules.Clone()
# Godot source files
env_gltf.add_source_files(env.modules_sources, "*.cpp")
env_gltf.add_source_files(env.modules_sources, "structures/*.cpp")
SConscript("extensions/SCsub")
if env.editor_build:
env_gltf.add_source_files(env.modules_sources, "editor/*.cpp")

View File

@ -9,7 +9,6 @@ def configure(env):
def get_doc_classes():
return [
"EditorSceneFormatImporterBlend",
"EditorSceneFormatImporterFBX",
"EditorSceneFormatImporterGLTF",
"GLTFAccessor",
"GLTFAnimation",

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorSceneFormatImporterFBX" inherits="EditorSceneFormatImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Importer for the [code].fbx[/code] scene file format.
</brief_description>
<description>
Imports Autodesk FBX 3D scenes by way of converting them to glTF 2.0 using the FBX2glTF command line tool.
The location of the FBX2glTF binary is set via the [code]filesystem/import/fbx/fbx2gltf_path[/code] editor setting.
This importer is only used if [member ProjectSettings.filesystem/import/fbx/enabled] is enabled, otherwise [code].fbx[/code] files present in the project folder are not imported.
</description>
<tutorials>
</tutorials>
</class>

View File

@ -7,8 +7,30 @@
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="get_additional_data">
<return type="Variant" />
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null.
</description>
</method>
<method name="set_additional_data">
<return type="void" />
<param index="0" name="extension_name" type="StringName" />
<param index="1" name="additional_data" type="Variant" />
<description>
Sets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
</description>
</method>
</methods>
<members>
<member name="loop" type="bool" setter="set_loop" getter="get_loop" default="false">
</member>
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="&quot;&quot;">
The original name of the animation.
</member>
</members>
</class>

View File

@ -25,6 +25,19 @@
Create a new GLTFLight instance from the given Godot [Light3D] node.
</description>
</method>
<method name="get_additional_data">
<return type="Variant" />
<param index="0" name="extension_name" type="StringName" />
<description>
</description>
</method>
<method name="set_additional_data">
<return type="void" />
<param index="0" name="extension_name" type="StringName" />
<param index="1" name="additional_data" type="Variant" />
<description>
</description>
</method>
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>

View File

@ -1,18 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFMesh" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
GLTFMesh represents an GLTF mesh.
</brief_description>
<description>
GLTFMesh handles 3D mesh data imported from GLTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<methods>
<method name="get_additional_data">
<return type="Variant" />
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null.
</description>
</method>
<method name="set_additional_data">
<return type="void" />
<param index="0" name="extension_name" type="StringName" />
<param index="1" name="additional_data" type="Variant" />
<description>
Sets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
</description>
</method>
</methods>
<members>
<member name="blend_weights" type="PackedFloat32Array" setter="set_blend_weights" getter="get_blend_weights" default="PackedFloat32Array()">
An array of floats representing the blend weights of the mesh.
</member>
<member name="instance_materials" type="Material[]" setter="set_instance_materials" getter="get_instance_materials" default="[]">
An array of Material objects representing the materials used in the mesh.
</member>
<member name="mesh" type="ImporterMesh" setter="set_mesh" getter="get_mesh">
The [ImporterMesh] object representing the mesh itself.
</member>
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="&quot;&quot;">
The original name of the mesh.
</member>
</members>
</class>

View File

@ -46,6 +46,9 @@
<member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1">
If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh.
</member>
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="&quot;&quot;">
The original name of the node.
</member>
<member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1">
The index of the parent node in the [GLTFState]. If -1, this node is a root node.
</member>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFTexture" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
GLTFTexture represents a texture in an GLTF file.
</brief_description>
<description>
</description>

View File

@ -75,7 +75,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
bool remove_immutable = p_options.has("animation/remove_immutable_tracks") ? (bool)p_options["animation/remove_immutable_tracks"] : true;
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable);
#else
return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
return gltf->create_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
#endif
}
@ -93,4 +93,13 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN
}
}
Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
String file_extension = p_path.get_extension().to_lower();
if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) {
return false;
}
return true;
}
#endif // TOOLS_ENABLED

View File

@ -50,6 +50,8 @@ public:
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation,
const String &p_option, const HashMap<StringName, Variant> &p_options) override;
};
#endif // TOOLS_ENABLED

View File

@ -6,5 +6,6 @@ Import("env_modules")
env_gltf = env_modules.Clone()
# Godot source files
env_gltf.add_source_files(env.modules_sources, "*.cpp")
env_gltf.add_source_files(env.modules_sources, "physics/*.cpp")

View File

@ -51,6 +51,8 @@ void GLTFLight::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_inner_cone_angle", "inner_cone_angle"), &GLTFLight::set_inner_cone_angle);
ClassDB::bind_method(D_METHOD("get_outer_cone_angle"), &GLTFLight::get_outer_cone_angle);
ClassDB::bind_method(D_METHOD("set_outer_cone_angle", "outer_cone_angle"), &GLTFLight::set_outer_cone_angle);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFLight::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFLight::set_additional_data);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); // Color
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity"), "set_intensity", "get_intensity"); // float
@ -220,3 +222,11 @@ Dictionary GLTFLight::to_dictionary() const {
d["range"] = range;
return d;
}
Variant GLTFLight::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
void GLTFLight::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}

View File

@ -51,6 +51,7 @@ private:
float range = INFINITY;
float inner_cone_angle = 0.0f;
float outer_cone_angle = Math_TAU / 8.0f;
Dictionary additional_data;
public:
Color get_color();
@ -76,6 +77,9 @@ public:
static Ref<GLTFLight> from_dictionary(const Dictionary p_dictionary);
Dictionary to_dictionary() const;
Variant get_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
};
#endif // GLTF_LIGHT_H

View File

@ -33,17 +33,6 @@
// This file should only be included by other headers.
// Godot classes used by GLTF headers.
class BoneAttachment3D;
class CSGShape3D;
class GridMap;
class ImporterMeshInstance3D;
class Light3D;
class MeshInstance3D;
class MultiMeshInstance3D;
class Skeleton3D;
class Skin;
// GLTF classes.
struct GLTFAccessor;
class GLTFAnimation;

File diff suppressed because it is too large Load Diff

View File

@ -32,13 +32,23 @@
#define GLTF_DOCUMENT_H
#include "extensions/gltf_document_extension.h"
#include "extensions/gltf_spec_gloss.h"
#include "gltf_defines.h"
#include "gltf_state.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/multimesh_instance_3d.h"
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
#ifdef MODULE_CSG_ENABLED
#include "modules/csg/csg_shape.h"
#endif // MODULE_CSG_ENABLED
#ifdef MODULE_GRIDMAP_ENABLED
#include "modules/gridmap/grid_map.h"
#endif // MODULE_GRIDMAP_ENABLED
class GLTFDocument : public Resource {
GDCLASS(GLTFDocument, Resource);
static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
Vector<Ref<GLTFDocumentExtension>> document_extensions;
public:
const int32_t JOINT_GROUP_SIZE = 4;
@ -81,6 +91,9 @@ private:
protected:
static void _bind_methods();
String _gen_unique_name(Ref<GLTFState> p_state, const String &p_name);
static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
Vector<Ref<GLTFDocumentExtension>> document_extensions;
public:
static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
@ -96,6 +109,7 @@ public:
float get_lossy_quality() const;
void set_root_node_mode(RootNodeMode p_root_node_mode);
RootNodeMode get_root_node_mode() const;
static String _gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name);
private:
void _build_parent_hierachy(Ref<GLTFState> p_state);
@ -106,7 +120,6 @@ private:
Error _parse_nodes(Ref<GLTFState> p_state);
String _get_type_name(const GLTFType p_component);
String _get_accessor_type_name(const GLTFType p_type);
String _gen_unique_name(Ref<GLTFState> p_state, const String &p_name);
String _sanitize_animation_name(const String &p_name);
String _gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name);
String _sanitize_bone_name(const String &p_name);
@ -184,24 +197,7 @@ private:
const Color &p_diffuse,
Color &r_base_color,
float &r_metallic);
GLTFNodeIndex _find_highest_node(Ref<GLTFState> p_state,
const Vector<GLTFNodeIndex> &p_subset);
void _recurse_children(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index,
RBSet<GLTFNodeIndex> &p_all_skin_nodes, HashSet<GLTFNodeIndex> &p_child_visited_set);
bool _capture_nodes_in_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin,
const GLTFNodeIndex p_node_index);
void _capture_nodes_for_multirooted_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin);
Error _expand_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin);
Error _verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin);
Error _parse_skins(Ref<GLTFState> p_state);
Error _determine_skeletons(Ref<GLTFState> p_state);
Error _reparent_non_joint_skeleton_subtrees(
Ref<GLTFState> p_state, Ref<GLTFSkeleton> p_skeleton,
const Vector<GLTFNodeIndex> &p_non_joints);
Error _determine_skeleton_roots(Ref<GLTFState> p_state,
const GLTFSkeletonIndex p_skel_i);
Error _create_skeletons(Ref<GLTFState> p_state);
Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> p_state);
Error _serialize_skins(Ref<GLTFState> p_state);
Error _create_skins(Ref<GLTFState> p_state);
bool _skins_are_same(const Ref<Skin> p_skin_a, const Ref<Skin> p_skin_b);
@ -317,7 +313,6 @@ public:
Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0);
Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0);
public:
Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true);
PackedByteArray generate_buffer(Ref<GLTFState> p_state);
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path);

View File

@ -43,10 +43,13 @@
#include "structures/gltf_texture.h"
#include "structures/gltf_texture_sampler.h"
#include "scene/3d/importer_mesh_instance_3d.h"
class GLTFState : public Resource {
GDCLASS(GLTFState, Resource);
friend class GLTFDocument;
protected:
String base_path;
String filename;
Dictionary json;
@ -69,7 +72,7 @@ class GLTFState : public Resource {
Vector<Ref<GLTFBufferView>> buffer_views;
Vector<Ref<GLTFAccessor>> accessors;
Vector<Ref<GLTFMesh>> meshes; // meshes are loaded directly, no reason not to.
Vector<Ref<GLTFMesh>> meshes; // Meshes are loaded directly, no reason not to.
Vector<AnimationPlayer *> animation_players;
HashMap<Ref<Material>, GLTFMaterialIndex> material_cache;
@ -111,7 +114,7 @@ public:
HANDLE_BINARY_DISCARD_TEXTURES = 0,
HANDLE_BINARY_EXTRACT_TEXTURES,
HANDLE_BINARY_EMBED_AS_BASISU,
HANDLE_BINARY_EMBED_AS_UNCOMPRESSED, // if this value changes from 3, ResourceImporterScene::pre_import must be changed as well.
HANDLE_BINARY_EMBED_AS_UNCOMPRESSED, // If this value changes from 3, ResourceImporterScene::pre_import must be changed as well.
};
int32_t get_handle_binary_image() {
return handle_binary_image;

View File

@ -34,6 +34,7 @@
#include "core/templates/hash_set.h"
#include "core/variant/array.h"
#include "core/variant/dictionary.h"
#include "core/variant/typed_array.h"
namespace GLTFTemplateConvert {
template <class T>
@ -73,7 +74,7 @@ static void set_from_array(HashSet<T> &r_out, const TypedArray<T> &p_inp) {
}
template <class K, class V>
static Dictionary to_dict(const HashMap<K, V> &p_inp) {
static Dictionary to_dictionary(const HashMap<K, V> &p_inp) {
Dictionary ret;
for (const KeyValue<K, V> &E : p_inp) {
ret[E.key] = E.value;
@ -82,7 +83,7 @@ static Dictionary to_dict(const HashMap<K, V> &p_inp) {
}
template <class K, class V>
static void set_from_dict(HashMap<K, V> &r_out, const Dictionary &p_inp) {
static void set_from_dictionary(HashMap<K, V> &r_out, const Dictionary &p_inp) {
r_out.clear();
Array keys = p_inp.keys();
for (int i = 0; i < keys.size(); i++) {

View File

@ -36,12 +36,12 @@
#include "extensions/gltf_spec_gloss.h"
#include "extensions/physics/gltf_document_extension_physics.h"
#include "gltf_document.h"
#include "gltf_state.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_import_blend_runner.h"
#include "editor/editor_scene_exporter_gltf_plugin.h"
#include "editor/editor_scene_importer_blend.h"
#include "editor/editor_scene_importer_fbx.h"
#include "editor/editor_scene_importer_gltf.h"
#include "core/config/project_settings.h"
@ -91,19 +91,6 @@ static void _editor_init() {
}
memnew(EditorImportBlendRunner);
EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton());
// FBX to glTF importer.
bool fbx_enabled = GLOBAL_GET("filesystem/import/fbx/enabled");
if (fbx_enabled) {
Ref<EditorSceneFormatImporterFBX> importer;
importer.instantiate();
ResourceImporterScene::add_scene_importer(importer);
Ref<EditorFileSystemImportFormatSupportQueryFBX> fbx_import_query;
fbx_import_query.instantiate();
EditorFileSystem::get_singleton()->add_import_format_support_query(fbx_import_query);
}
}
#endif // TOOLS_ENABLED
@ -138,7 +125,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureWebP);
bool is_editor = ::Engine::get_singleton()->is_editor_hint();
bool is_editor = Engine::get_singleton()->is_editor_hint();
if (!is_editor) {
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh);
}
@ -155,14 +142,10 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
// Project settings defined here so doctool finds them.
GLOBAL_DEF_RST_BASIC("filesystem/import/blender/enabled", true);
GLOBAL_DEF_RST_BASIC("filesystem/import/fbx/enabled", true);
GDREGISTER_CLASS(EditorSceneFormatImporterBlend);
GDREGISTER_CLASS(EditorSceneFormatImporterFBX);
// Can't (a priori) run external app on these platforms.
GLOBAL_DEF_RST("filesystem/import/blender/enabled.android", false);
GLOBAL_DEF_RST("filesystem/import/blender/enabled.web", false);
GLOBAL_DEF_RST("filesystem/import/fbx/enabled.android", false);
GLOBAL_DEF_RST("filesystem/import/fbx/enabled.web", false);
ClassDB::set_current_api(prev_api);
EditorNode::add_init_callback(_editor_init);

800
modules/gltf/skin_tool.cpp Normal file
View File

@ -0,0 +1,800 @@
/**************************************************************************/
/* skin_tool.cpp */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#include "skin_tool.h"
SkinNodeIndex SkinTool::_find_highest_node(Vector<Ref<GLTFNode>> &r_nodes, const Vector<GLTFNodeIndex> &p_subset) {
int highest = -1;
SkinNodeIndex best_node = -1;
for (int i = 0; i < p_subset.size(); ++i) {
const SkinNodeIndex node_i = p_subset[i];
const Ref<GLTFNode> node = r_nodes[node_i];
if (highest == -1 || node->height < highest) {
highest = node->height;
best_node = node_i;
}
}
return best_node;
}
bool SkinTool::_capture_nodes_in_skin(const Vector<Ref<GLTFNode>> &nodes, Ref<GLTFSkin> p_skin, const SkinNodeIndex p_node_index) {
bool found_joint = false;
Ref<GLTFNode> current_node = nodes[p_node_index];
for (int i = 0; i < current_node->children.size(); ++i) {
found_joint |= _capture_nodes_in_skin(nodes, p_skin, current_node->children[i]);
}
if (found_joint) {
// Mark it if we happen to find another skins joint...
if (current_node->joint && p_skin->joints.find(p_node_index) < 0) {
p_skin->joints.push_back(p_node_index);
} else if (p_skin->non_joints.find(p_node_index) < 0) {
p_skin->non_joints.push_back(p_node_index);
}
}
if (p_skin->joints.find(p_node_index) > 0) {
return true;
}
return false;
}
void SkinTool::_capture_nodes_for_multirooted_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin) {
DisjointSet<SkinNodeIndex> disjoint_set;
for (int i = 0; i < p_skin->joints.size(); ++i) {
const SkinNodeIndex node_index = p_skin->joints[i];
const SkinNodeIndex parent = r_nodes[node_index]->parent;
disjoint_set.insert(node_index);
if (p_skin->joints.find(parent) >= 0) {
disjoint_set.create_union(parent, node_index);
}
}
Vector<SkinNodeIndex> roots;
disjoint_set.get_representatives(roots);
if (roots.size() <= 1) {
return;
}
int maxHeight = -1;
// Determine the max height rooted tree
for (int i = 0; i < roots.size(); ++i) {
const SkinNodeIndex root = roots[i];
if (maxHeight == -1 || r_nodes[root]->height < maxHeight) {
maxHeight = r_nodes[root]->height;
}
}
// Go up the tree till all of the multiple roots of the skin are at the same hierarchy level.
// This sucks, but 99% of all game engines (not just Godot) would have this same issue.
for (int i = 0; i < roots.size(); ++i) {
SkinNodeIndex current_node = roots[i];
while (r_nodes[current_node]->height > maxHeight) {
SkinNodeIndex parent = r_nodes[current_node]->parent;
if (r_nodes[parent]->joint && p_skin->joints.find(parent) < 0) {
p_skin->joints.push_back(parent);
} else if (p_skin->non_joints.find(parent) < 0) {
p_skin->non_joints.push_back(parent);
}
current_node = parent;
}
// replace the roots
roots.write[i] = current_node;
}
// Climb up the tree until they all have the same parent
bool all_same;
do {
all_same = true;
const SkinNodeIndex first_parent = r_nodes[roots[0]]->parent;
for (int i = 1; i < roots.size(); ++i) {
all_same &= (first_parent == r_nodes[roots[i]]->parent);
}
if (!all_same) {
for (int i = 0; i < roots.size(); ++i) {
const SkinNodeIndex current_node = roots[i];
const SkinNodeIndex parent = r_nodes[current_node]->parent;
if (r_nodes[parent]->joint && p_skin->joints.find(parent) < 0) {
p_skin->joints.push_back(parent);
} else if (p_skin->non_joints.find(parent) < 0) {
p_skin->non_joints.push_back(parent);
}
roots.write[i] = parent;
}
}
} while (!all_same);
}
Error SkinTool::_expand_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin) {
_capture_nodes_for_multirooted_skin(r_nodes, p_skin);
// Grab all nodes that lay in between skin joints/nodes
DisjointSet<GLTFNodeIndex> disjoint_set;
Vector<SkinNodeIndex> all_skin_nodes;
all_skin_nodes.append_array(p_skin->joints);
all_skin_nodes.append_array(p_skin->non_joints);
for (int i = 0; i < all_skin_nodes.size(); ++i) {
const SkinNodeIndex node_index = all_skin_nodes[i];
const SkinNodeIndex parent = r_nodes[node_index]->parent;
disjoint_set.insert(node_index);
if (all_skin_nodes.find(parent) >= 0) {
disjoint_set.create_union(parent, node_index);
}
}
Vector<SkinNodeIndex> out_owners;
disjoint_set.get_representatives(out_owners);
Vector<SkinNodeIndex> out_roots;
for (int i = 0; i < out_owners.size(); ++i) {
Vector<SkinNodeIndex> set;
disjoint_set.get_members(set, out_owners[i]);
const SkinNodeIndex root = _find_highest_node(r_nodes, set);
ERR_FAIL_COND_V(root < 0, FAILED);
out_roots.push_back(root);
}
out_roots.sort();
for (int i = 0; i < out_roots.size(); ++i) {
_capture_nodes_in_skin(r_nodes, p_skin, out_roots[i]);
}
p_skin->roots = out_roots;
return OK;
}
Error SkinTool::_verify_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin) {
// This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is)
// In case additional interpolating logic is added to the skins, this will help ensure that you
// do not cause it to self implode into a fiery blaze
// We are going to re-calculate the root nodes and compare them to the ones saved in the skin,
// then ensure the multiple trees (if they exist) are on the same sublevel
// Grab all nodes that lay in between skin joints/nodes
DisjointSet<GLTFNodeIndex> disjoint_set;
Vector<SkinNodeIndex> all_skin_nodes;
all_skin_nodes.append_array(p_skin->joints);
all_skin_nodes.append_array(p_skin->non_joints);
for (int i = 0; i < all_skin_nodes.size(); ++i) {
const SkinNodeIndex node_index = all_skin_nodes[i];
const SkinNodeIndex parent = r_nodes[node_index]->parent;
disjoint_set.insert(node_index);
if (all_skin_nodes.find(parent) >= 0) {
disjoint_set.create_union(parent, node_index);
}
}
Vector<SkinNodeIndex> out_owners;
disjoint_set.get_representatives(out_owners);
Vector<SkinNodeIndex> out_roots;
for (int i = 0; i < out_owners.size(); ++i) {
Vector<SkinNodeIndex> set;
disjoint_set.get_members(set, out_owners[i]);
const SkinNodeIndex root = _find_highest_node(r_nodes, set);
ERR_FAIL_COND_V(root < 0, FAILED);
out_roots.push_back(root);
}
out_roots.sort();
ERR_FAIL_COND_V(out_roots.is_empty(), FAILED);
// Make sure the roots are the exact same (they better be)
ERR_FAIL_COND_V(out_roots.size() != p_skin->roots.size(), FAILED);
for (int i = 0; i < out_roots.size(); ++i) {
ERR_FAIL_COND_V(out_roots[i] != p_skin->roots[i], FAILED);
}
// Single rooted skin? Perfectly ok!
if (out_roots.size() == 1) {
return OK;
}
// Make sure all parents of a multi-rooted skin are the SAME
const SkinNodeIndex parent = r_nodes[out_roots[0]]->parent;
for (int i = 1; i < out_roots.size(); ++i) {
if (r_nodes[out_roots[i]]->parent != parent) {
return FAILED;
}
}
return OK;
}
void SkinTool::_recurse_children(
Vector<Ref<GLTFNode>> &nodes,
const SkinNodeIndex p_node_index,
RBSet<GLTFNodeIndex> &p_all_skin_nodes,
HashSet<GLTFNodeIndex> &p_child_visited_set) {
if (p_child_visited_set.has(p_node_index)) {
return;
}
p_child_visited_set.insert(p_node_index);
Ref<GLTFNode> current_node = nodes[p_node_index];
for (int i = 0; i < current_node->children.size(); ++i) {
_recurse_children(nodes, current_node->children[i], p_all_skin_nodes, p_child_visited_set);
}
// Continue to use 'current_node' for clarity and direct access.
if (current_node->skin < 0 || current_node->mesh < 0 || !current_node->children.is_empty()) {
p_all_skin_nodes.insert(p_node_index);
}
}
Error SkinTool::_determine_skeletons(
Vector<Ref<GLTFSkin>> &skins,
Vector<Ref<GLTFNode>> &nodes,
Vector<Ref<GLTFSkeleton>> &skeletons) {
// Using a disjoint set, we are going to potentially combine all skins that are actually branches
// of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton.
// This is another unclear issue caused by the current glTF specification.
DisjointSet<GLTFNodeIndex> skeleton_sets;
for (GLTFSkinIndex skin_i = 0; skin_i < skins.size(); ++skin_i) {
const Ref<GLTFSkin> skin = skins[skin_i];
ERR_CONTINUE(skin.is_null());
HashSet<GLTFNodeIndex> child_visited_set;
RBSet<GLTFNodeIndex> all_skin_nodes;
for (int i = 0; i < skin->joints.size(); ++i) {
all_skin_nodes.insert(skin->joints[i]);
SkinTool::_recurse_children(nodes, skin->joints[i], all_skin_nodes, child_visited_set);
}
for (int i = 0; i < skin->non_joints.size(); ++i) {
all_skin_nodes.insert(skin->non_joints[i]);
SkinTool::_recurse_children(nodes, skin->non_joints[i], all_skin_nodes, child_visited_set);
}
for (GLTFNodeIndex node_index : all_skin_nodes) {
const GLTFNodeIndex parent = nodes[node_index]->parent;
skeleton_sets.insert(node_index);
if (all_skin_nodes.has(parent)) {
skeleton_sets.create_union(parent, node_index);
}
}
// We are going to connect the separate skin subtrees in each skin together
// so that the final roots are entire sets of valid skin trees
for (int i = 1; i < skin->roots.size(); ++i) {
skeleton_sets.create_union(skin->roots[0], skin->roots[i]);
}
}
{ // attempt to joint all touching subsets (siblings/parent are part of another skin)
Vector<SkinNodeIndex> groups_representatives;
skeleton_sets.get_representatives(groups_representatives);
Vector<SkinNodeIndex> highest_group_members;
Vector<Vector<SkinNodeIndex>> groups;
for (int i = 0; i < groups_representatives.size(); ++i) {
Vector<SkinNodeIndex> group;
skeleton_sets.get_members(group, groups_representatives[i]);
highest_group_members.push_back(SkinTool::_find_highest_node(nodes, group));
groups.push_back(group);
}
for (int i = 0; i < highest_group_members.size(); ++i) {
const SkinNodeIndex node_i = highest_group_members[i];
// Attach any siblings together (this needs to be done n^2/2 times)
for (int j = i + 1; j < highest_group_members.size(); ++j) {
const SkinNodeIndex node_j = highest_group_members[j];
// Even if they are siblings under the root! :)
if (nodes[node_i]->parent == nodes[node_j]->parent) {
skeleton_sets.create_union(node_i, node_j);
}
}
// Attach any parenting going on together (we need to do this n^2 times)
const SkinNodeIndex node_i_parent = nodes[node_i]->parent;
if (node_i_parent >= 0) {
for (int j = 0; j < groups.size() && i != j; ++j) {
const Vector<SkinNodeIndex> &group = groups[j];
if (group.find(node_i_parent) >= 0) {
const SkinNodeIndex node_j = highest_group_members[j];
skeleton_sets.create_union(node_i, node_j);
}
}
}
}
}
// At this point, the skeleton groups should be finalized
Vector<SkinNodeIndex> skeleton_owners;
skeleton_sets.get_representatives(skeleton_owners);
// Mark all the skins actual skeletons, after we have merged them
for (SkinSkeletonIndex skel_i = 0; skel_i < skeleton_owners.size(); ++skel_i) {
const SkinNodeIndex skeleton_owner = skeleton_owners[skel_i];
Ref<GLTFSkeleton> skeleton;
skeleton.instantiate();
Vector<SkinNodeIndex> skeleton_nodes;
skeleton_sets.get_members(skeleton_nodes, skeleton_owner);
for (GLTFSkinIndex skin_i = 0; skin_i < skins.size(); ++skin_i) {
Ref<GLTFSkin> skin = skins.write[skin_i];
// If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton
for (int i = 0; i < skeleton_nodes.size(); ++i) {
SkinNodeIndex skel_node_i = skeleton_nodes[i];
if (skin->joints.find(skel_node_i) >= 0 || skin->non_joints.find(skel_node_i) >= 0) {
skin->skeleton = skel_i;
continue;
}
}
}
Vector<SkinNodeIndex> non_joints;
for (int i = 0; i < skeleton_nodes.size(); ++i) {
const SkinNodeIndex node_i = skeleton_nodes[i];
if (nodes[node_i]->joint) {
skeleton->joints.push_back(node_i);
} else {
non_joints.push_back(node_i);
}
}
skeletons.push_back(skeleton);
SkinTool::_reparent_non_joint_skeleton_subtrees(nodes, skeletons.write[skel_i], non_joints);
}
for (SkinSkeletonIndex skel_i = 0; skel_i < skeletons.size(); ++skel_i) {
Ref<GLTFSkeleton> skeleton = skeletons.write[skel_i];
for (int i = 0; i < skeleton->joints.size(); ++i) {
const SkinNodeIndex node_i = skeleton->joints[i];
Ref<GLTFNode> node = nodes[node_i];
ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR);
ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR);
node->skeleton = skel_i;
}
ERR_FAIL_COND_V(SkinTool::_determine_skeleton_roots(nodes, skeletons, skel_i), ERR_PARSE_ERROR);
}
return OK;
}
Error SkinTool::_reparent_non_joint_skeleton_subtrees(
Vector<Ref<GLTFNode>> &nodes,
Ref<GLTFSkeleton> p_skeleton,
const Vector<SkinNodeIndex> &p_non_joints) {
DisjointSet<GLTFNodeIndex> subtree_set;
// Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector)
// This way we can find any joints that lie in between joints, as the current glTF specification
// mentions nothing about non-joints being in between joints of the same skin. Hopefully one day we
// can remove this code.
// skinD depicted here explains this issue:
// https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin
for (int i = 0; i < p_non_joints.size(); ++i) {
const SkinNodeIndex node_i = p_non_joints[i];
subtree_set.insert(node_i);
const SkinNodeIndex parent_i = nodes[node_i]->parent;
if (parent_i >= 0 && p_non_joints.find(parent_i) >= 0 && !nodes[parent_i]->joint) {
subtree_set.create_union(parent_i, node_i);
}
}
// Find all the non joint subtrees and re-parent them to a new "fake" joint
Vector<SkinNodeIndex> non_joint_subtree_roots;
subtree_set.get_representatives(non_joint_subtree_roots);
for (int root_i = 0; root_i < non_joint_subtree_roots.size(); ++root_i) {
const SkinNodeIndex subtree_root = non_joint_subtree_roots[root_i];
Vector<SkinNodeIndex> subtree_nodes;
subtree_set.get_members(subtree_nodes, subtree_root);
for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) {
Ref<GLTFNode> node = nodes[subtree_nodes[subtree_i]];
node->joint = true;
// Add the joint to the skeletons joints
p_skeleton->joints.push_back(subtree_nodes[subtree_i]);
}
}
return OK;
}
Error SkinTool::_determine_skeleton_roots(
Vector<Ref<GLTFNode>> &nodes,
Vector<Ref<GLTFSkeleton>> &skeletons,
const SkinSkeletonIndex p_skel_i) {
DisjointSet<GLTFNodeIndex> disjoint_set;
for (SkinNodeIndex i = 0; i < nodes.size(); ++i) {
const Ref<GLTFNode> node = nodes[i];
if (node->skeleton != p_skel_i) {
continue;
}
disjoint_set.insert(i);
if (node->parent >= 0 && nodes[node->parent]->skeleton == p_skel_i) {
disjoint_set.create_union(node->parent, i);
}
}
Ref<GLTFSkeleton> skeleton = skeletons.write[p_skel_i];
Vector<SkinNodeIndex> representatives;
disjoint_set.get_representatives(representatives);
Vector<SkinNodeIndex> roots;
for (int i = 0; i < representatives.size(); ++i) {
Vector<SkinNodeIndex> set;
disjoint_set.get_members(set, representatives[i]);
const SkinNodeIndex root = _find_highest_node(nodes, set);
ERR_FAIL_COND_V(root < 0, FAILED);
roots.push_back(root);
}
roots.sort();
skeleton->roots = roots;
if (roots.size() == 0) {
return FAILED;
} else if (roots.size() == 1) {
return OK;
}
// Check that the subtrees have the same parent root
const SkinNodeIndex parent = nodes[roots[0]]->parent;
for (int i = 1; i < roots.size(); ++i) {
if (nodes[roots[i]]->parent != parent) {
return FAILED;
}
}
return OK;
}
Error SkinTool::_create_skeletons(
HashSet<String> &unique_names,
Vector<Ref<GLTFSkin>> &skins,
Vector<Ref<GLTFNode>> &nodes,
HashMap<ObjectID, GLTFSkeletonIndex> &skeleton3d_to_gltf_skeleton,
Vector<Ref<GLTFSkeleton>> &skeletons,
HashMap<GLTFNodeIndex, Node *> &scene_nodes) {
for (SkinSkeletonIndex skel_i = 0; skel_i < skeletons.size(); ++skel_i) {
Ref<GLTFSkeleton> gltf_skeleton = skeletons.write[skel_i];
Skeleton3D *skeleton = memnew(Skeleton3D);
gltf_skeleton->godot_skeleton = skeleton;
skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skel_i;
// Make a unique name, no gltf node represents this skeleton
skeleton->set_name("Skeleton3D");
List<GLTFNodeIndex> bones;
for (int i = 0; i < gltf_skeleton->roots.size(); ++i) {
bones.push_back(gltf_skeleton->roots[i]);
}
// Make the skeleton creation deterministic by going through the roots in
// a sorted order, and DEPTH FIRST
bones.sort();
while (!bones.is_empty()) {
const SkinNodeIndex node_i = bones.front()->get();
bones.pop_front();
Ref<GLTFNode> node = nodes[node_i];
ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED);
{ // Add all child nodes to the stack (deterministically)
Vector<SkinNodeIndex> child_nodes;
for (int i = 0; i < node->children.size(); ++i) {
const SkinNodeIndex child_i = node->children[i];
if (nodes[child_i]->skeleton == skel_i) {
child_nodes.push_back(child_i);
}
}
// Depth first insertion
child_nodes.sort();
for (int i = child_nodes.size() - 1; i >= 0; --i) {
bones.push_front(child_nodes[i]);
}
}
const int bone_index = skeleton->get_bone_count();
if (node->get_name().is_empty()) {
node->set_name("bone");
}
node->set_name(_gen_unique_bone_name(unique_names, node->get_name()));
skeleton->add_bone(node->get_name());
Transform3D rest_transform = node->get_additional_data("GODOT_rest_transform");
skeleton->set_bone_rest(bone_index, rest_transform);
skeleton->set_bone_pose_position(bone_index, node->transform.origin);
skeleton->set_bone_pose_rotation(bone_index, node->transform.basis.get_rotation_quaternion());
skeleton->set_bone_pose_scale(bone_index, node->transform.basis.get_scale());
if (node->parent >= 0 && nodes[node->parent]->skeleton == skel_i) {
const int bone_parent = skeleton->find_bone(nodes[node->parent]->get_name());
ERR_FAIL_COND_V(bone_parent < 0, FAILED);
skeleton->set_bone_parent(bone_index, skeleton->find_bone(nodes[node->parent]->get_name()));
}
scene_nodes.insert(node_i, skeleton);
}
}
ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(skins, skeletons, nodes), ERR_PARSE_ERROR);
return OK;
}
Error SkinTool::_map_skin_joints_indices_to_skeleton_bone_indices(
Vector<Ref<GLTFSkin>> &skins,
Vector<Ref<GLTFSkeleton>> &skeletons,
Vector<Ref<GLTFNode>> &nodes) {
for (GLTFSkinIndex skin_i = 0; skin_i < skins.size(); ++skin_i) {
Ref<GLTFSkin> skin = skins.write[skin_i];
ERR_CONTINUE(skin.is_null());
Ref<GLTFSkeleton> skeleton = skeletons[skin->skeleton];
for (int joint_index = 0; joint_index < skin->joints_original.size(); ++joint_index) {
const SkinNodeIndex node_i = skin->joints_original[joint_index];
const Ref<GLTFNode> node = nodes[node_i];
const int bone_index = skeleton->godot_skeleton->find_bone(node->get_name());
ERR_FAIL_COND_V(bone_index < 0, FAILED);
skin->joint_i_to_bone_i.insert(joint_index, bone_index);
}
}
return OK;
}
Error SkinTool::_create_skins(Vector<Ref<GLTFSkin>> &skins, Vector<Ref<GLTFNode>> &nodes, bool use_named_skin_binds, HashSet<String> &unique_names) {
for (GLTFSkinIndex skin_i = 0; skin_i < skins.size(); ++skin_i) {
Ref<GLTFSkin> gltf_skin = skins.write[skin_i];
ERR_CONTINUE(gltf_skin.is_null());
Ref<Skin> skin;
skin.instantiate();
// Some skins don't have IBM's! What absolute monsters!
const bool has_ibms = !gltf_skin->inverse_binds.is_empty();
for (int joint_i = 0; joint_i < gltf_skin->joints_original.size(); ++joint_i) {
SkinNodeIndex node = gltf_skin->joints_original[joint_i];
String bone_name = nodes[node]->get_name();
Transform3D xform;
if (has_ibms) {
xform = gltf_skin->inverse_binds[joint_i];
}
if (use_named_skin_binds) {
skin->add_named_bind(bone_name, xform);
} else {
int32_t bone_i = gltf_skin->joint_i_to_bone_i[joint_i];
skin->add_bind(bone_i, xform);
}
}
gltf_skin->godot_skin = skin;
}
// Purge the duplicates!
_remove_duplicate_skins(skins);
// Create unique names now, after removing duplicates
for (GLTFSkinIndex skin_i = 0; skin_i < skins.size(); ++skin_i) {
ERR_CONTINUE(skins.get(skin_i).is_null());
Ref<Skin> skin = skins.write[skin_i]->godot_skin;
ERR_CONTINUE(skin.is_null());
if (skin->get_name().is_empty()) {
// Make a unique name, no node represents this skin
skin->set_name(_gen_unique_name(unique_names, "Skin"));
}
}
return OK;
}
// FIXME: Duplicated from FBXDocument, very similar code in GLTFDocument too,
// and even below in this class for bone names.
String SkinTool::_gen_unique_name(HashSet<String> &unique_names, const String &p_name) {
const String s_name = p_name.validate_node_name();
String u_name;
int index = 1;
while (true) {
u_name = s_name;
if (index > 1) {
u_name += itos(index);
}
if (!unique_names.has(u_name)) {
break;
}
index++;
}
unique_names.insert(u_name);
return u_name;
}
bool SkinTool::_skins_are_same(const Ref<Skin> p_skin_a, const Ref<Skin> p_skin_b) {
if (p_skin_a->get_bind_count() != p_skin_b->get_bind_count()) {
return false;
}
for (int i = 0; i < p_skin_a->get_bind_count(); ++i) {
if (p_skin_a->get_bind_bone(i) != p_skin_b->get_bind_bone(i)) {
return false;
}
if (p_skin_a->get_bind_name(i) != p_skin_b->get_bind_name(i)) {
return false;
}
Transform3D a_xform = p_skin_a->get_bind_pose(i);
Transform3D b_xform = p_skin_b->get_bind_pose(i);
if (a_xform != b_xform) {
return false;
}
}
return true;
}
void SkinTool::_remove_duplicate_skins(Vector<Ref<GLTFSkin>> &r_skins) {
for (int i = 0; i < r_skins.size(); ++i) {
for (int j = i + 1; j < r_skins.size(); ++j) {
const Ref<Skin> skin_i = r_skins[i]->godot_skin;
const Ref<Skin> skin_j = r_skins[j]->godot_skin;
if (_skins_are_same(skin_i, skin_j)) {
// replace it and delete the old
r_skins.write[j]->godot_skin = skin_i;
}
}
}
}
String SkinTool::_gen_unique_bone_name(HashSet<String> &r_unique_names, const String &p_name) {
String s_name = _sanitize_bone_name(p_name);
if (s_name.is_empty()) {
s_name = "bone";
}
String u_name;
int index = 1;
while (true) {
u_name = s_name;
if (index > 1) {
u_name += "_" + itos(index);
}
if (!r_unique_names.has(u_name)) {
break;
}
index++;
}
r_unique_names.insert(u_name);
return u_name;
}
Error SkinTool::_asset_parse_skins(
const Vector<SkinNodeIndex> &input_skin_indices,
const Vector<Ref<GLTFSkin>> &input_skins,
const Vector<Ref<GLTFNode>> &input_nodes,
Vector<SkinNodeIndex> &output_skin_indices,
Vector<Ref<GLTFSkin>> &output_skins,
HashMap<GLTFNodeIndex, bool> &joint_mapping) {
output_skin_indices.clear();
output_skins.clear();
joint_mapping.clear();
for (int i = 0; i < input_skin_indices.size(); ++i) {
SkinNodeIndex skin_index = input_skin_indices[i];
if (skin_index >= 0 && skin_index < input_skins.size()) {
output_skin_indices.push_back(skin_index);
output_skins.push_back(input_skins[skin_index]);
Ref<GLTFSkin> skin = input_skins[skin_index];
Vector<SkinNodeIndex> skin_joints = skin->get_joints();
for (int j = 0; j < skin_joints.size(); ++j) {
SkinNodeIndex joint_index = skin_joints[j];
joint_mapping[joint_index] = true;
}
}
}
return OK;
}
String SkinTool::_sanitize_bone_name(const String &p_name) {
String bone_name = p_name;
bone_name = bone_name.replace(":", "_");
bone_name = bone_name.replace("/", "_");
return bone_name;
}

98
modules/gltf/skin_tool.h Normal file
View File

@ -0,0 +1,98 @@
/**************************************************************************/
/* skin_tool.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. */
/**************************************************************************/
#ifndef SKIN_TOOL_H
#define SKIN_TOOL_H
#include "gltf_defines.h"
#include "structures/gltf_node.h"
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "core/math/disjoint_set.h"
#include "core/templates/rb_set.h"
using SkinNodeIndex = int;
using SkinSkeletonIndex = int;
class SkinTool {
public:
static String _sanitize_bone_name(const String &p_name);
static String _gen_unique_bone_name(HashSet<String> &r_unique_names, const String &p_name);
static SkinNodeIndex _find_highest_node(Vector<Ref<GLTFNode>> &r_nodes, const Vector<SkinNodeIndex> &p_subset);
static bool _capture_nodes_in_skin(const Vector<Ref<GLTFNode>> &p_nodes, Ref<GLTFSkin> p_skin, const SkinNodeIndex p_node_index);
static void _capture_nodes_for_multirooted_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin);
static void _recurse_children(
Vector<Ref<GLTFNode>> &r_nodes,
const SkinNodeIndex p_node_index,
RBSet<SkinNodeIndex> &r_all_skin_nodes,
HashSet<SkinNodeIndex> &r_child_visited_set);
static Error _reparent_non_joint_skeleton_subtrees(
Vector<Ref<GLTFNode>> &r_nodes,
Ref<GLTFSkeleton> p_skeleton,
const Vector<SkinNodeIndex> &p_non_joints);
static Error _determine_skeleton_roots(
Vector<Ref<GLTFNode>> &r_nodes,
Vector<Ref<GLTFSkeleton>> &r_skeletons,
const SkinSkeletonIndex p_skel_i);
static Error _map_skin_joints_indices_to_skeleton_bone_indices(
Vector<Ref<GLTFSkin>> &r_skins,
Vector<Ref<GLTFSkeleton>> &r_skeletons,
Vector<Ref<GLTFNode>> &r_nodes);
static String _gen_unique_name(HashSet<String> &unique_names, const String &p_name);
static bool _skins_are_same(const Ref<Skin> p_skin_a, const Ref<Skin> p_skin_b);
static void _remove_duplicate_skins(Vector<Ref<GLTFSkin>> &r_skins);
public:
static Error _expand_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin);
static Error _verify_skin(Vector<Ref<GLTFNode>> &r_nodes, Ref<GLTFSkin> p_skin);
static Error _asset_parse_skins(
const Vector<SkinNodeIndex> &p_input_skin_indices,
const Vector<Ref<GLTFSkin>> &p_input_skins,
const Vector<Ref<GLTFNode>> &p_input_nodes,
Vector<SkinNodeIndex> &r_output_skin_indices,
Vector<Ref<GLTFSkin>> &r_output_skins,
HashMap<GLTFNodeIndex, bool> &r_joint_mapping);
static Error _determine_skeletons(
Vector<Ref<GLTFSkin>> &r_skins,
Vector<Ref<GLTFNode>> &r_nodes,
Vector<Ref<GLTFSkeleton>> &r_skeletons);
static Error _create_skeletons(
HashSet<String> &r_unique_names,
Vector<Ref<GLTFSkin>> &r_skins,
Vector<Ref<GLTFNode>> &r_nodes,
HashMap<ObjectID, GLTFSkeletonIndex> &r_skeleton3d_to_fbx_skeleton,
Vector<Ref<GLTFSkeleton>> &r_skeletons,
HashMap<GLTFNodeIndex, Node *> &r_scene_nodes);
static Error _create_skins(Vector<Ref<GLTFSkin>> &skins, Vector<Ref<GLTFNode>> &nodes, bool use_named_skin_binds, HashSet<String> &unique_names);
};
#endif // SKIN_TOOL_H

View File

@ -31,12 +31,25 @@
#include "gltf_animation.h"
void GLTFAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFAnimation::get_original_name);
ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFAnimation::set_original_name);
ClassDB::bind_method(D_METHOD("get_loop"), &GLTFAnimation::get_loop);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &GLTFAnimation::set_loop);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFAnimation::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFAnimation::set_additional_data);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool
}
String GLTFAnimation::get_original_name() {
return original_name;
}
void GLTFAnimation::set_original_name(String p_name) {
original_name = p_name;
}
bool GLTFAnimation::get_loop() const {
return loop;
}
@ -51,3 +64,11 @@ HashMap<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() {
GLTFAnimation::GLTFAnimation() {
}
Variant GLTFAnimation::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
void GLTFAnimation::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}

View File

@ -49,7 +49,7 @@ public:
template <class T>
struct Channel {
Interpolation interpolation;
Interpolation interpolation = INTERP_LINEAR;
Vector<real_t> times;
Vector<T> values;
};
@ -62,14 +62,21 @@ public:
};
public:
String get_original_name();
void set_original_name(String p_name);
bool get_loop() const;
void set_loop(bool p_val);
HashMap<int, GLTFAnimation::Track> &get_tracks();
Variant get_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
GLTFAnimation();
private:
String original_name;
bool loop = false;
HashMap<int, Track> tracks;
Dictionary additional_data;
};
#endif // GLTF_ANIMATION_H

View File

@ -69,7 +69,7 @@ public:
Camera3D *to_node() const;
static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary);
Dictionary to_dictionary() const;
virtual Dictionary to_dictionary() const;
};
#endif // GLTF_CAMERA_H

View File

@ -33,18 +33,31 @@
#include "scene/resources/importer_mesh.h"
void GLTFMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFMesh::get_original_name);
ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFMesh::set_original_name);
ClassDB::bind_method(D_METHOD("get_mesh"), &GLTFMesh::get_mesh);
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFMesh::set_mesh);
ClassDB::bind_method(D_METHOD("get_blend_weights"), &GLTFMesh::get_blend_weights);
ClassDB::bind_method(D_METHOD("set_blend_weights", "blend_weights"), &GLTFMesh::set_blend_weights);
ClassDB::bind_method(D_METHOD("get_instance_materials"), &GLTFMesh::get_instance_materials);
ClassDB::bind_method(D_METHOD("set_instance_materials", "instance_materials"), &GLTFMesh::set_instance_materials);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFMesh::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFMesh::set_additional_data);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh"), "set_mesh", "get_mesh");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "blend_weights"), "set_blend_weights", "get_blend_weights"); // Vector<float>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "instance_materials"), "set_instance_materials", "get_instance_materials");
}
String GLTFMesh::get_original_name() {
return original_name;
}
void GLTFMesh::set_original_name(String p_name) {
original_name = p_name;
}
Ref<ImporterMesh> GLTFMesh::get_mesh() {
return mesh;
}
@ -68,3 +81,11 @@ Vector<float> GLTFMesh::get_blend_weights() {
void GLTFMesh::set_blend_weights(Vector<float> p_blend_weights) {
blend_weights = p_blend_weights;
}
Variant GLTFMesh::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
void GLTFMesh::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}

View File

@ -39,20 +39,26 @@ class GLTFMesh : public Resource {
GDCLASS(GLTFMesh, Resource);
private:
String original_name;
Ref<ImporterMesh> mesh;
Vector<float> blend_weights;
TypedArray<Material> instance_materials;
Dictionary additional_data;
protected:
static void _bind_methods();
public:
String get_original_name();
void set_original_name(String p_name);
Ref<ImporterMesh> get_mesh();
void set_mesh(Ref<ImporterMesh> p_mesh);
Vector<float> get_blend_weights();
void set_blend_weights(Vector<float> p_blend_weights);
TypedArray<Material> get_instance_materials();
void set_instance_materials(TypedArray<Material> p_instance_materials);
Variant get_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
};
#endif // GLTF_MESH_H

View File

@ -31,6 +31,8 @@
#include "gltf_node.h"
void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFNode::get_original_name);
ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFNode::set_original_name);
ClassDB::bind_method(D_METHOD("get_parent"), &GLTFNode::get_parent);
ClassDB::bind_method(D_METHOD("set_parent", "parent"), &GLTFNode::set_parent);
ClassDB::bind_method(D_METHOD("get_height"), &GLTFNode::get_height);
@ -58,6 +60,7 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "xform"), "set_xform", "get_xform"); // Transform3D
@ -72,6 +75,13 @@ void GLTFNode::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex
}
String GLTFNode::get_original_name() {
return original_name;
}
void GLTFNode::set_original_name(String p_name) {
original_name = p_name;
}
GLTFNodeIndex GLTFNode::get_parent() {
return parent;
}

View File

@ -38,8 +38,11 @@
class GLTFNode : public Resource {
GDCLASS(GLTFNode, Resource);
friend class GLTFDocument;
friend class SkinTool;
friend class FBXDocument;
private:
String original_name;
GLTFNodeIndex parent = -1;
int height = -1;
Transform3D transform;
@ -56,6 +59,9 @@ protected:
static void _bind_methods();
public:
String get_original_name();
void set_original_name(String p_name);
GLTFNodeIndex get_parent();
void set_parent(GLTFNodeIndex p_parent);
@ -65,6 +71,9 @@ public:
Transform3D get_xform();
void set_xform(Transform3D p_xform);
Transform3D get_rest_xform();
void set_rest_xform(Transform3D p_rest_xform);
GLTFMeshIndex get_mesh();
void set_mesh(GLTFMeshIndex p_mesh);

View File

@ -82,11 +82,11 @@ void GLTFSkeleton::set_unique_names(TypedArray<String> p_unique_names) {
}
Dictionary GLTFSkeleton::get_godot_bone_node() {
return GLTFTemplateConvert::to_dict(godot_bone_node);
return GLTFTemplateConvert::to_dictionary(godot_bone_node);
}
void GLTFSkeleton::set_godot_bone_node(Dictionary p_indict) {
GLTFTemplateConvert::set_from_dict(godot_bone_node, p_indict);
GLTFTemplateConvert::set_from_dictionary(godot_bone_node, p_indict);
}
BoneAttachment3D *GLTFSkeleton::get_bone_attachment(int idx) {

View File

@ -34,10 +34,14 @@
#include "../gltf_defines.h"
#include "core/io/resource.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/skeleton_3d.h"
class GLTFSkeleton : public Resource {
GDCLASS(GLTFSkeleton, Resource);
friend class GLTFDocument;
friend class SkinTool;
friend class FBXDocument;
private:
// The *synthesized* skeletons joints

View File

@ -126,11 +126,11 @@ void GLTFSkin::set_skeleton(int p_skeleton) {
}
Dictionary GLTFSkin::get_joint_i_to_bone_i() {
return GLTFTemplateConvert::to_dict(joint_i_to_bone_i);
return GLTFTemplateConvert::to_dictionary(joint_i_to_bone_i);
}
void GLTFSkin::set_joint_i_to_bone_i(Dictionary p_joint_i_to_bone_i) {
GLTFTemplateConvert::set_from_dict(joint_i_to_bone_i, p_joint_i_to_bone_i);
GLTFTemplateConvert::set_from_dictionary(joint_i_to_bone_i, p_joint_i_to_bone_i);
}
Dictionary GLTFSkin::get_joint_i_to_name() {
@ -158,3 +158,121 @@ Ref<Skin> GLTFSkin::get_godot_skin() {
void GLTFSkin::set_godot_skin(Ref<Skin> p_godot_skin) {
godot_skin = p_godot_skin;
}
Error GLTFSkin::from_dictionary(const Dictionary &dict) {
ERR_FAIL_COND_V(!dict.has("skin_root"), ERR_INVALID_DATA);
skin_root = dict["skin_root"];
ERR_FAIL_COND_V(!dict.has("joints_original"), ERR_INVALID_DATA);
Array joints_original_array = dict["joints_original"];
joints_original.clear();
for (int i = 0; i < joints_original_array.size(); ++i) {
joints_original.push_back(joints_original_array[i]);
}
ERR_FAIL_COND_V(!dict.has("inverse_binds"), ERR_INVALID_DATA);
Array inverse_binds_array = dict["inverse_binds"];
inverse_binds.clear();
for (int i = 0; i < inverse_binds_array.size(); ++i) {
ERR_FAIL_COND_V(inverse_binds_array[i].get_type() != Variant::TRANSFORM3D, ERR_INVALID_DATA);
inverse_binds.push_back(inverse_binds_array[i]);
}
ERR_FAIL_COND_V(!dict.has("joints"), ERR_INVALID_DATA);
Array joints_array = dict["joints"];
joints.clear();
for (int i = 0; i < joints_array.size(); ++i) {
joints.push_back(joints_array[i]);
}
ERR_FAIL_COND_V(!dict.has("non_joints"), ERR_INVALID_DATA);
Array non_joints_array = dict["non_joints"];
non_joints.clear();
for (int i = 0; i < non_joints_array.size(); ++i) {
non_joints.push_back(non_joints_array[i]);
}
ERR_FAIL_COND_V(!dict.has("roots"), ERR_INVALID_DATA);
Array roots_array = dict["roots"];
roots.clear();
for (int i = 0; i < roots_array.size(); ++i) {
roots.push_back(roots_array[i]);
}
ERR_FAIL_COND_V(!dict.has("skeleton"), ERR_INVALID_DATA);
skeleton = dict["skeleton"];
ERR_FAIL_COND_V(!dict.has("joint_i_to_bone_i"), ERR_INVALID_DATA);
Dictionary joint_i_to_bone_i_dict = dict["joint_i_to_bone_i"];
joint_i_to_bone_i.clear();
for (int i = 0; i < joint_i_to_bone_i_dict.keys().size(); ++i) {
int key = joint_i_to_bone_i_dict.keys()[i];
int value = joint_i_to_bone_i_dict[key];
joint_i_to_bone_i[key] = value;
}
ERR_FAIL_COND_V(!dict.has("joint_i_to_name"), ERR_INVALID_DATA);
Dictionary joint_i_to_name_dict = dict["joint_i_to_name"];
joint_i_to_name.clear();
for (int i = 0; i < joint_i_to_name_dict.keys().size(); ++i) {
int key = joint_i_to_name_dict.keys()[i];
StringName value = joint_i_to_name_dict[key];
joint_i_to_name[key] = value;
}
if (dict.has("godot_skin")) {
godot_skin = dict["godot_skin"];
}
return OK;
}
Dictionary GLTFSkin::to_dictionary() {
Dictionary dict;
dict["skin_root"] = skin_root;
Array joints_original_array;
for (int i = 0; i < joints_original.size(); ++i) {
joints_original_array.push_back(joints_original[i]);
}
dict["joints_original"] = joints_original_array;
Array inverse_binds_array;
for (int i = 0; i < inverse_binds.size(); ++i) {
inverse_binds_array.push_back(inverse_binds[i]);
}
dict["inverse_binds"] = inverse_binds_array;
Array joints_array;
for (int i = 0; i < joints.size(); ++i) {
joints_array.push_back(joints[i]);
}
dict["joints"] = joints_array;
Array non_joints_array;
for (int i = 0; i < non_joints.size(); ++i) {
non_joints_array.push_back(non_joints[i]);
}
dict["non_joints"] = non_joints_array;
Array roots_array;
for (int i = 0; i < roots.size(); ++i) {
roots_array.push_back(roots[i]);
}
dict["roots"] = roots_array;
dict["skeleton"] = skeleton;
Dictionary joint_i_to_bone_i_dict;
for (HashMap<int, int>::Iterator E = joint_i_to_bone_i.begin(); E; ++E) {
joint_i_to_bone_i_dict[E->key] = E->value;
}
dict["joint_i_to_bone_i"] = joint_i_to_bone_i_dict;
Dictionary joint_i_to_name_dict;
for (HashMap<int, StringName>::Iterator E = joint_i_to_name.begin(); E; ++E) {
joint_i_to_name_dict[E->key] = E->value;
}
dict["joint_i_to_name"] = joint_i_to_name_dict;
dict["godot_skin"] = godot_skin;
return dict;
}

View File

@ -42,6 +42,8 @@ class TypedArray;
class GLTFSkin : public Resource {
GDCLASS(GLTFSkin, Resource);
friend class GLTFDocument;
friend class SkinTool;
friend class FBXDocument;
private:
// The "skeleton" property defined in the gltf spec. -1 = Scene Root
@ -110,6 +112,9 @@ public:
Ref<Skin> get_godot_skin();
void set_godot_skin(Ref<Skin> p_godot_skin);
Dictionary to_dictionary();
Error from_dictionary(const Dictionary &dict);
};
#endif // GLTF_SKIN_H

View File

@ -31,7 +31,6 @@
#include "register_scene_types.h"
#include "core/config/project_settings.h"
#include "core/extension/gdextension_manager.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "scene/2d/animated_sprite_2d.h"

12
thirdparty/README.md vendored
View File

@ -867,6 +867,18 @@ See `thorvg/update-thorvg.sh` for extraction instructions. Set the version
number and run the script and apply patches from the `patches` folder.
## ufbx
- Upstream: https://github.com/ufbx/ufbx
- Version: git (v0.11.1, 2024)
- License: MIT
Files extracted from upstream source:
- `ufbx.{c,h}`
- `LICENSE`
## vhacd
- Upstream: https://github.com/kmammou/v-hacd

39
thirdparty/ufbx/LICENSE vendored Normal file
View File

@ -0,0 +1,39 @@
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2020 Samuli Raivio
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.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
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 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.
----------------------------------------

31525
thirdparty/ufbx/ufbx.c vendored Normal file

File diff suppressed because it is too large Load Diff

5529
thirdparty/ufbx/ufbx.h vendored Normal file

File diff suppressed because it is too large Load Diff