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:
parent
2fe8f07b6c
commit
04d43947bf
|
@ -492,6 +492,11 @@ Copyright: 2014-2021, Syoyo Fujita
|
||||||
2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
|
2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
|
||||||
License: BSD-3-clause
|
License: BSD-3-clause
|
||||||
|
|
||||||
|
Files: ./thirdparty/ufbx/
|
||||||
|
Comment: ufbx
|
||||||
|
Copyright: 2020, Samuli Raivio
|
||||||
|
License: Expat
|
||||||
|
|
||||||
Files: ./thirdparty/vhacd/
|
Files: ./thirdparty/vhacd/
|
||||||
Comment: V-HACD
|
Comment: V-HACD
|
||||||
Copyright: 2011, Khaled Mamou
|
Copyright: 2011, Khaled Mamou
|
||||||
|
|
|
@ -571,9 +571,9 @@
|
||||||
The maximum idle uptime (in seconds) of the Blender process.
|
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.
|
This prevents Godot from having to create a new process for each import within the given seconds.
|
||||||
</member>
|
</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.
|
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>
|
||||||
<member name="filesystem/on_save/compress_binary_resources" type="bool" setter="" getter="">
|
<member name="filesystem/on_save/compress_binary_resources" type="bool" setter="" getter="">
|
||||||
If [code]true[/code], uses lossless compression for binary resources.
|
If [code]true[/code], uses lossless compression for binary resources.
|
||||||
|
|
|
@ -984,15 +984,15 @@
|
||||||
<member name="filesystem/import/blender/enabled.web" type="bool" setter="" getter="" default="false">
|
<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.
|
Override for [member filesystem/import/blender/enabled] on the Web where Blender can't easily be accessed from Godot.
|
||||||
</member>
|
</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.
|
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>
|
||||||
<member name="filesystem/import/fbx/enabled.android" type="bool" setter="" getter="" default="false">
|
<member name="filesystem/import/fbx2gltf/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.
|
Override for [member filesystem/import/fbx2gltf/enabled] on Android where FBX2glTF can't easily be accessed from Godot.
|
||||||
</member>
|
</member>
|
||||||
<member name="filesystem/import/fbx/enabled.web" type="bool" setter="" getter="" default="false">
|
<member name="filesystem/import/fbx2gltf/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.
|
Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
|
||||||
</member>
|
</member>
|
||||||
<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
|
<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.
|
Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
|
||||||
|
|
|
@ -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::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::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::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)
|
// Tools (denoise)
|
||||||
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/tools/oidn/oidn_denoise_path", "", "", PROPERTY_USAGE_DEFAULT)
|
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/tools/oidn/oidn_denoise_path", "", "", PROPERTY_USAGE_DEFAULT)
|
||||||
|
|
|
@ -46,7 +46,7 @@ void FBXImporterManager::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FBXImporterManager::show_dialog(bool p_exclusive) {
|
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);
|
fbx_path->set_text(fbx2gltf_path);
|
||||||
_validate_path(fbx2gltf_path);
|
_validate_path(fbx2gltf_path);
|
||||||
|
|
||||||
|
@ -56,8 +56,8 @@ void FBXImporterManager::show_dialog(bool p_exclusive) {
|
||||||
set_close_on_escape(!p_exclusive);
|
set_close_on_escape(!p_exclusive);
|
||||||
|
|
||||||
if (is_importing) {
|
if (is_importing) {
|
||||||
get_cancel_button()->set_text(TTR("Disable FBX & Restart"));
|
get_cancel_button()->set_text(TTR("Disable FBX2glTF & 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_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 {
|
} else {
|
||||||
get_cancel_button()->set_text(TTR("Cancel"));
|
get_cancel_button()->set_text(TTR("Cancel"));
|
||||||
get_cancel_button()->set_tooltip_text("");
|
get_cancel_button()->set_tooltip_text("");
|
||||||
|
@ -105,7 +105,7 @@ void FBXImporterManager::_select_file(const String &p_path) {
|
||||||
|
|
||||||
void FBXImporterManager::_path_confirmed() {
|
void FBXImporterManager::_path_confirmed() {
|
||||||
String path = fbx_path->get_text();
|
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();
|
EditorSettings::get_singleton()->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ void FBXImporterManager::_cancel_setup() {
|
||||||
return; // No worry.
|
return; // No worry.
|
||||||
}
|
}
|
||||||
// No escape.
|
// No escape.
|
||||||
ProjectSettings::get_singleton()->set("filesystem/import/fbx/enabled", false);
|
ProjectSettings::get_singleton()->set("filesystem/import/fbx2gltf/enabled", false);
|
||||||
ProjectSettings::get_singleton()->save();
|
ProjectSettings::get_singleton()->save();
|
||||||
EditorNode::get_singleton()->save_all_scenes();
|
EditorNode::get_singleton()->save_all_scenes();
|
||||||
EditorNode::get_singleton()->restart_editor();
|
EditorNode::get_singleton()->restart_editor();
|
||||||
|
@ -136,7 +136,7 @@ FBXImporterManager::FBXImporterManager() {
|
||||||
set_title(TTR("Configure FBX Importer"));
|
set_title(TTR("Configure FBX Importer"));
|
||||||
|
|
||||||
VBoxContainer *vb = memnew(VBoxContainer);
|
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);
|
LinkButton *lb = memnew(LinkButton);
|
||||||
lb->set_text(TTR("Click this link to download FBX2glTF"));
|
lb->set_text(TTR("Click this link to download FBX2glTF"));
|
||||||
lb->set_uri("https://godotengine.org/fbx-import");
|
lb->set_uri("https://godotengine.org/fbx-import");
|
||||||
|
|
|
@ -52,14 +52,14 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
|
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
|
||||||
|
if (skeleton) {
|
||||||
// Rename bones in Skeleton3D.
|
// Rename bones in Skeleton3D.
|
||||||
{
|
|
||||||
int len = skeleton->get_bone_count();
|
int len = skeleton->get_bone_count();
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
StringName bn = p_rename_map[skeleton->get_bone_name(i)];
|
String current_bone_name = skeleton->get_bone_name(i);
|
||||||
if (bn) {
|
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||||
skeleton->set_bone_name(i, bn);
|
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);
|
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||||
if (mesh_skeleton && node == skeleton) {
|
if (mesh_skeleton && node == skeleton) {
|
||||||
int len = skin->get_bind_count();
|
int len = skin->get_bind_count();
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
StringName bn = p_rename_map[skin->get_bind_name(i)];
|
String current_bone_name = skin->get_bind_name(i);
|
||||||
if (bn) {
|
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||||
skin->set_bind_name(i, bn);
|
|
||||||
|
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) {
|
if (node) {
|
||||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||||
if (track_skeleton && track_skeleton == skeleton) {
|
if (track_skeleton && track_skeleton == skeleton) {
|
||||||
StringName bn = p_rename_map[anim->track_get_path(i).get_subname(0)];
|
String current_bone_name = anim->track_get_path(i).get_subname(0);
|
||||||
if (bn) {
|
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||||
anim->track_set_path(i, track_path + ":" + bn);
|
if (new_bone_name) {
|
||||||
|
String new_track_path = track_path + ":" + new_bone_name->value;
|
||||||
|
anim->track_set_path(i, new_track_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,8 +488,9 @@ void _apply_scale_to_scalable_node_collection(ScalableNodeCollection &p_collecti
|
||||||
if (skeleton_3d) {
|
if (skeleton_3d) {
|
||||||
for (int i = 0; i < skeleton_3d->get_bone_count(); i++) {
|
for (int i = 0; i < skeleton_3d->get_bone_count(); i++) {
|
||||||
Transform3D rest = skeleton_3d->get_bone_rest(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_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1168,7 +1168,7 @@ bool ProjectConverter3To4::test_array_names() {
|
||||||
|
|
||||||
// Callable is special class, to which normal classes may be renamed.
|
// Callable is special class, to which normal classes may be renamed.
|
||||||
if (!ClassDB::class_exists(StringName(new_class)) && new_class != "Callable") {
|
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.
|
valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1514,7 +1514,7 @@ const char *RenamesMap3To4::class_renames[][2] = {
|
||||||
{ "DynamicFontData", "FontFile" },
|
{ "DynamicFontData", "FontFile" },
|
||||||
{ "EditorNavigationMeshGenerator", "NavigationMeshGenerator" },
|
{ "EditorNavigationMeshGenerator", "NavigationMeshGenerator" },
|
||||||
{ "EditorSceneImporter", "EditorSceneFormatImporter" },
|
{ "EditorSceneImporter", "EditorSceneFormatImporter" },
|
||||||
{ "EditorSceneImporterFBX", "EditorSceneFormatImporterFBX" },
|
{ "EditorSceneImporterFBX", "EditorSceneFormatImporterFBX2GLTF" },
|
||||||
{ "EditorSceneImporterGLTF", "EditorSceneFormatImporterGLTF" },
|
{ "EditorSceneImporterGLTF", "EditorSceneFormatImporterGLTF" },
|
||||||
{ "EditorSpatialGizmo", "EditorNode3DGizmo" },
|
{ "EditorSpatialGizmo", "EditorNode3DGizmo" },
|
||||||
{ "EditorSpatialGizmoPlugin", "EditorNode3DGizmoPlugin" },
|
{ "EditorSpatialGizmoPlugin", "EditorNode3DGizmoPlugin" },
|
||||||
|
|
|
@ -204,3 +204,14 @@ Validate extension JSON: Error: Field 'classes/Window/methods/has_theme_stylebox
|
||||||
Fix the default parameter value for StringName and Variant.
|
Fix the default parameter value for StringName and Variant.
|
||||||
The changes to StringName parameters should be equivalent to the previous default values.
|
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.
|
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.
|
||||||
|
|
|
@ -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)
|
|
@ -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"
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,5 +1,5 @@
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* editor_scene_importer_fbx.cpp */
|
/* editor_scene_importer_fbx2gltf.cpp */
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* GODOT ENGINE */
|
/* GODOT ENGINE */
|
||||||
|
@ -28,27 +28,39 @@
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
/* 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
|
#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 "core/config/project_settings.h"
|
||||||
#include "editor/editor_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;
|
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");
|
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,
|
const HashMap<StringName, Variant> &p_options,
|
||||||
List<String> *r_missing_deps, Error *r_err) {
|
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.
|
// Get global paths for source and sink.
|
||||||
|
|
||||||
// Don't use `c_escape()` as it can generate broken paths. These paths will be
|
// 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.
|
// 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;
|
List<String> args;
|
||||||
args.push_back("--pbr-metallic-roughness");
|
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;
|
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);
|
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable);
|
||||||
#else
|
#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
|
#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) {
|
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;
|
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) {
|
List<ResourceImporter::ImportOption> *r_options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EditorFileSystemImportFormatSupportQueryFBX::is_active() const {
|
void EditorSceneFormatImporterFBX2GLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
|
||||||
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
|
if (!p_import_params.has("fbx/importer")) {
|
||||||
return !FileAccess::exists(fbx2gltf_path);
|
p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
|
@ -1,5 +1,5 @@
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* editor_scene_importer_fbx.h */
|
/* editor_scene_importer_fbx2gltf.h */
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/* This file is part of: */
|
/* This file is part of: */
|
||||||
/* GODOT ENGINE */
|
/* GODOT ENGINE */
|
||||||
|
@ -28,20 +28,18 @@
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
|
|
||||||
#ifndef EDITOR_SCENE_IMPORTER_FBX_H
|
#ifndef EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
||||||
#define EDITOR_SCENE_IMPORTER_FBX_H
|
#define EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
|
|
||||||
#include "editor/editor_file_system.h"
|
|
||||||
#include "editor/fbx_importer_manager.h"
|
|
||||||
#include "editor/import/3d/resource_importer_scene.h"
|
#include "editor/import/3d/resource_importer_scene.h"
|
||||||
|
|
||||||
class Animation;
|
class Animation;
|
||||||
class Node;
|
class Node;
|
||||||
|
|
||||||
class EditorSceneFormatImporterFBX : public EditorSceneFormatImporter {
|
class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter {
|
||||||
GDCLASS(EditorSceneFormatImporterFBX, EditorSceneFormatImporter);
|
GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual uint32_t get_import_flags() const override;
|
virtual uint32_t get_import_flags() const override;
|
||||||
|
@ -53,17 +51,9 @@ public:
|
||||||
List<ResourceImporter::ImportOption> *r_options) override;
|
List<ResourceImporter::ImportOption> *r_options) override;
|
||||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
||||||
const HashMap<StringName, Variant> &p_options) override;
|
const HashMap<StringName, Variant> &p_options) override;
|
||||||
};
|
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
#endif // EDITOR_SCENE_IMPORTER_FBX_H
|
#endif // EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -6,8 +6,11 @@ Import("env_modules")
|
||||||
env_gltf = env_modules.Clone()
|
env_gltf = env_modules.Clone()
|
||||||
|
|
||||||
# Godot source files
|
# Godot source files
|
||||||
|
|
||||||
env_gltf.add_source_files(env.modules_sources, "*.cpp")
|
env_gltf.add_source_files(env.modules_sources, "*.cpp")
|
||||||
env_gltf.add_source_files(env.modules_sources, "structures/*.cpp")
|
env_gltf.add_source_files(env.modules_sources, "structures/*.cpp")
|
||||||
|
|
||||||
SConscript("extensions/SCsub")
|
SConscript("extensions/SCsub")
|
||||||
|
|
||||||
if env.editor_build:
|
if env.editor_build:
|
||||||
env_gltf.add_source_files(env.modules_sources, "editor/*.cpp")
|
env_gltf.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||||
|
|
|
@ -9,7 +9,6 @@ def configure(env):
|
||||||
def get_doc_classes():
|
def get_doc_classes():
|
||||||
return [
|
return [
|
||||||
"EditorSceneFormatImporterBlend",
|
"EditorSceneFormatImporterBlend",
|
||||||
"EditorSceneFormatImporterFBX",
|
|
||||||
"EditorSceneFormatImporterGLTF",
|
"EditorSceneFormatImporterGLTF",
|
||||||
"GLTFAccessor",
|
"GLTFAccessor",
|
||||||
"GLTFAnimation",
|
"GLTFAnimation",
|
||||||
|
|
|
@ -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>
|
|
|
@ -7,8 +7,30 @@
|
||||||
<tutorials>
|
<tutorials>
|
||||||
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
|
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
|
||||||
</tutorials>
|
</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>
|
<members>
|
||||||
<member name="loop" type="bool" setter="set_loop" getter="get_loop" default="false">
|
<member name="loop" type="bool" setter="set_loop" getter="get_loop" default="false">
|
||||||
</member>
|
</member>
|
||||||
|
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="""">
|
||||||
|
The original name of the animation.
|
||||||
|
</member>
|
||||||
</members>
|
</members>
|
||||||
</class>
|
</class>
|
||||||
|
|
|
@ -25,6 +25,19 @@
|
||||||
Create a new GLTFLight instance from the given Godot [Light3D] node.
|
Create a new GLTFLight instance from the given Godot [Light3D] node.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="to_dictionary" qualifiers="const">
|
||||||
<return type="Dictionary" />
|
<return type="Dictionary" />
|
||||||
<description>
|
<description>
|
||||||
|
|
|
@ -1,18 +1,45 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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">
|
<class name="GLTFMesh" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
|
GLTFMesh represents an GLTF mesh.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<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>
|
</description>
|
||||||
<tutorials>
|
<tutorials>
|
||||||
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
|
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
|
||||||
</tutorials>
|
</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>
|
<members>
|
||||||
<member name="blend_weights" type="PackedFloat32Array" setter="set_blend_weights" getter="get_blend_weights" default="PackedFloat32Array()">
|
<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>
|
||||||
<member name="instance_materials" type="Material[]" setter="set_instance_materials" getter="get_instance_materials" default="[]">
|
<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>
|
||||||
<member name="mesh" type="ImporterMesh" setter="set_mesh" getter="get_mesh">
|
<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="""">
|
||||||
|
The original name of the mesh.
|
||||||
</member>
|
</member>
|
||||||
</members>
|
</members>
|
||||||
</class>
|
</class>
|
||||||
|
|
|
@ -46,6 +46,9 @@
|
||||||
<member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1">
|
<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.
|
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>
|
||||||
|
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="""">
|
||||||
|
The original name of the node.
|
||||||
|
</member>
|
||||||
<member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1">
|
<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.
|
The index of the parent node in the [GLTFState]. If -1, this node is a root node.
|
||||||
</member>
|
</member>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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">
|
<class name="GLTFTexture" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
|
GLTFTexture represents a texture in an GLTF file.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
|
|
|
@ -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;
|
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);
|
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable);
|
||||||
#else
|
#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
|
#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
|
#endif // TOOLS_ENABLED
|
||||||
|
|
|
@ -50,6 +50,8 @@ public:
|
||||||
virtual void get_import_options(const String &p_path,
|
virtual void get_import_options(const String &p_path,
|
||||||
List<ResourceImporter::ImportOption> *r_options) override;
|
List<ResourceImporter::ImportOption> *r_options) override;
|
||||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const 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
|
#endif // TOOLS_ENABLED
|
||||||
|
|
|
@ -6,5 +6,6 @@ Import("env_modules")
|
||||||
env_gltf = env_modules.Clone()
|
env_gltf = env_modules.Clone()
|
||||||
|
|
||||||
# Godot source files
|
# Godot source files
|
||||||
|
|
||||||
env_gltf.add_source_files(env.modules_sources, "*.cpp")
|
env_gltf.add_source_files(env.modules_sources, "*.cpp")
|
||||||
env_gltf.add_source_files(env.modules_sources, "physics/*.cpp")
|
env_gltf.add_source_files(env.modules_sources, "physics/*.cpp")
|
||||||
|
|
|
@ -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("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("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("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::COLOR, "color"), "set_color", "get_color"); // Color
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity"), "set_intensity", "get_intensity"); // float
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity"), "set_intensity", "get_intensity"); // float
|
||||||
|
@ -220,3 +222,11 @@ Dictionary GLTFLight::to_dictionary() const {
|
||||||
d["range"] = range;
|
d["range"] = range;
|
||||||
return d;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ private:
|
||||||
float range = INFINITY;
|
float range = INFINITY;
|
||||||
float inner_cone_angle = 0.0f;
|
float inner_cone_angle = 0.0f;
|
||||||
float outer_cone_angle = Math_TAU / 8.0f;
|
float outer_cone_angle = Math_TAU / 8.0f;
|
||||||
|
Dictionary additional_data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Color get_color();
|
Color get_color();
|
||||||
|
@ -76,6 +77,9 @@ public:
|
||||||
|
|
||||||
static Ref<GLTFLight> from_dictionary(const Dictionary p_dictionary);
|
static Ref<GLTFLight> from_dictionary(const Dictionary p_dictionary);
|
||||||
Dictionary to_dictionary() const;
|
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
|
#endif // GLTF_LIGHT_H
|
||||||
|
|
|
@ -33,17 +33,6 @@
|
||||||
|
|
||||||
// This file should only be included by other headers.
|
// 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.
|
// GLTF classes.
|
||||||
struct GLTFAccessor;
|
struct GLTFAccessor;
|
||||||
class GLTFAnimation;
|
class GLTFAnimation;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,13 +32,23 @@
|
||||||
#define GLTF_DOCUMENT_H
|
#define GLTF_DOCUMENT_H
|
||||||
|
|
||||||
#include "extensions/gltf_document_extension.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.
|
#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 {
|
class GLTFDocument : public Resource {
|
||||||
GDCLASS(GLTFDocument, Resource);
|
GDCLASS(GLTFDocument, Resource);
|
||||||
static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
|
|
||||||
Vector<Ref<GLTFDocumentExtension>> document_extensions;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const int32_t JOINT_GROUP_SIZE = 4;
|
const int32_t JOINT_GROUP_SIZE = 4;
|
||||||
|
@ -81,6 +91,9 @@ private:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
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:
|
public:
|
||||||
static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
|
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;
|
float get_lossy_quality() const;
|
||||||
void set_root_node_mode(RootNodeMode p_root_node_mode);
|
void set_root_node_mode(RootNodeMode p_root_node_mode);
|
||||||
RootNodeMode get_root_node_mode() const;
|
RootNodeMode get_root_node_mode() const;
|
||||||
|
static String _gen_unique_name_static(HashSet<String> &r_unique_names, const String &p_name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void _build_parent_hierachy(Ref<GLTFState> p_state);
|
void _build_parent_hierachy(Ref<GLTFState> p_state);
|
||||||
|
@ -106,7 +120,6 @@ private:
|
||||||
Error _parse_nodes(Ref<GLTFState> p_state);
|
Error _parse_nodes(Ref<GLTFState> p_state);
|
||||||
String _get_type_name(const GLTFType p_component);
|
String _get_type_name(const GLTFType p_component);
|
||||||
String _get_accessor_type_name(const GLTFType p_type);
|
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 _sanitize_animation_name(const String &p_name);
|
||||||
String _gen_unique_animation_name(Ref<GLTFState> p_state, 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);
|
String _sanitize_bone_name(const String &p_name);
|
||||||
|
@ -184,24 +197,7 @@ private:
|
||||||
const Color &p_diffuse,
|
const Color &p_diffuse,
|
||||||
Color &r_base_color,
|
Color &r_base_color,
|
||||||
float &r_metallic);
|
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 _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 _serialize_skins(Ref<GLTFState> p_state);
|
||||||
Error _create_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);
|
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_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);
|
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);
|
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);
|
PackedByteArray generate_buffer(Ref<GLTFState> p_state);
|
||||||
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path);
|
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path);
|
||||||
|
|
|
@ -43,10 +43,13 @@
|
||||||
#include "structures/gltf_texture.h"
|
#include "structures/gltf_texture.h"
|
||||||
#include "structures/gltf_texture_sampler.h"
|
#include "structures/gltf_texture_sampler.h"
|
||||||
|
|
||||||
|
#include "scene/3d/importer_mesh_instance_3d.h"
|
||||||
|
|
||||||
class GLTFState : public Resource {
|
class GLTFState : public Resource {
|
||||||
GDCLASS(GLTFState, Resource);
|
GDCLASS(GLTFState, Resource);
|
||||||
friend class GLTFDocument;
|
friend class GLTFDocument;
|
||||||
|
|
||||||
|
protected:
|
||||||
String base_path;
|
String base_path;
|
||||||
String filename;
|
String filename;
|
||||||
Dictionary json;
|
Dictionary json;
|
||||||
|
@ -69,7 +72,7 @@ class GLTFState : public Resource {
|
||||||
Vector<Ref<GLTFBufferView>> buffer_views;
|
Vector<Ref<GLTFBufferView>> buffer_views;
|
||||||
Vector<Ref<GLTFAccessor>> accessors;
|
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;
|
Vector<AnimationPlayer *> animation_players;
|
||||||
HashMap<Ref<Material>, GLTFMaterialIndex> material_cache;
|
HashMap<Ref<Material>, GLTFMaterialIndex> material_cache;
|
||||||
|
@ -111,7 +114,7 @@ public:
|
||||||
HANDLE_BINARY_DISCARD_TEXTURES = 0,
|
HANDLE_BINARY_DISCARD_TEXTURES = 0,
|
||||||
HANDLE_BINARY_EXTRACT_TEXTURES,
|
HANDLE_BINARY_EXTRACT_TEXTURES,
|
||||||
HANDLE_BINARY_EMBED_AS_BASISU,
|
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() {
|
int32_t get_handle_binary_image() {
|
||||||
return handle_binary_image;
|
return handle_binary_image;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "core/templates/hash_set.h"
|
#include "core/templates/hash_set.h"
|
||||||
#include "core/variant/array.h"
|
#include "core/variant/array.h"
|
||||||
#include "core/variant/dictionary.h"
|
#include "core/variant/dictionary.h"
|
||||||
|
#include "core/variant/typed_array.h"
|
||||||
|
|
||||||
namespace GLTFTemplateConvert {
|
namespace GLTFTemplateConvert {
|
||||||
template <class T>
|
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>
|
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;
|
Dictionary ret;
|
||||||
for (const KeyValue<K, V> &E : p_inp) {
|
for (const KeyValue<K, V> &E : p_inp) {
|
||||||
ret[E.key] = E.value;
|
ret[E.key] = E.value;
|
||||||
|
@ -82,7 +83,7 @@ static Dictionary to_dict(const HashMap<K, V> &p_inp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class K, class V>
|
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();
|
r_out.clear();
|
||||||
Array keys = p_inp.keys();
|
Array keys = p_inp.keys();
|
||||||
for (int i = 0; i < keys.size(); i++) {
|
for (int i = 0; i < keys.size(); i++) {
|
||||||
|
|
|
@ -36,12 +36,12 @@
|
||||||
#include "extensions/gltf_spec_gloss.h"
|
#include "extensions/gltf_spec_gloss.h"
|
||||||
#include "extensions/physics/gltf_document_extension_physics.h"
|
#include "extensions/physics/gltf_document_extension_physics.h"
|
||||||
#include "gltf_document.h"
|
#include "gltf_document.h"
|
||||||
|
#include "gltf_state.h"
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
#include "editor/editor_import_blend_runner.h"
|
#include "editor/editor_import_blend_runner.h"
|
||||||
#include "editor/editor_scene_exporter_gltf_plugin.h"
|
#include "editor/editor_scene_exporter_gltf_plugin.h"
|
||||||
#include "editor/editor_scene_importer_blend.h"
|
#include "editor/editor_scene_importer_blend.h"
|
||||||
#include "editor/editor_scene_importer_fbx.h"
|
|
||||||
#include "editor/editor_scene_importer_gltf.h"
|
#include "editor/editor_scene_importer_gltf.h"
|
||||||
|
|
||||||
#include "core/config/project_settings.h"
|
#include "core/config/project_settings.h"
|
||||||
|
@ -91,19 +91,6 @@ static void _editor_init() {
|
||||||
}
|
}
|
||||||
memnew(EditorImportBlendRunner);
|
memnew(EditorImportBlendRunner);
|
||||||
EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton());
|
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
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
|
@ -138,7 +125,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
|
||||||
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
|
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
|
||||||
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
|
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
|
||||||
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureWebP);
|
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) {
|
if (!is_editor) {
|
||||||
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh);
|
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh);
|
||||||
}
|
}
|
||||||
|
@ -155,14 +142,10 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
|
||||||
|
|
||||||
// Project settings defined here so doctool finds them.
|
// Project settings defined here so doctool finds them.
|
||||||
GLOBAL_DEF_RST_BASIC("filesystem/import/blender/enabled", true);
|
GLOBAL_DEF_RST_BASIC("filesystem/import/blender/enabled", true);
|
||||||
GLOBAL_DEF_RST_BASIC("filesystem/import/fbx/enabled", true);
|
|
||||||
GDREGISTER_CLASS(EditorSceneFormatImporterBlend);
|
GDREGISTER_CLASS(EditorSceneFormatImporterBlend);
|
||||||
GDREGISTER_CLASS(EditorSceneFormatImporterFBX);
|
|
||||||
// Can't (a priori) run external app on these platforms.
|
// 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.android", false);
|
||||||
GLOBAL_DEF_RST("filesystem/import/blender/enabled.web", 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);
|
ClassDB::set_current_api(prev_api);
|
||||||
EditorNode::add_init_callback(_editor_init);
|
EditorNode::add_init_callback(_editor_init);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -31,12 +31,25 @@
|
||||||
#include "gltf_animation.h"
|
#include "gltf_animation.h"
|
||||||
|
|
||||||
void GLTFAnimation::_bind_methods() {
|
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("get_loop"), &GLTFAnimation::get_loop);
|
||||||
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &GLTFAnimation::set_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
|
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 {
|
bool GLTFAnimation::get_loop() const {
|
||||||
return loop;
|
return loop;
|
||||||
}
|
}
|
||||||
|
@ -51,3 +64,11 @@ HashMap<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() {
|
||||||
|
|
||||||
GLTFAnimation::GLTFAnimation() {
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ public:
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct Channel {
|
struct Channel {
|
||||||
Interpolation interpolation;
|
Interpolation interpolation = INTERP_LINEAR;
|
||||||
Vector<real_t> times;
|
Vector<real_t> times;
|
||||||
Vector<T> values;
|
Vector<T> values;
|
||||||
};
|
};
|
||||||
|
@ -62,14 +62,21 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
String get_original_name();
|
||||||
|
void set_original_name(String p_name);
|
||||||
|
|
||||||
bool get_loop() const;
|
bool get_loop() const;
|
||||||
void set_loop(bool p_val);
|
void set_loop(bool p_val);
|
||||||
HashMap<int, GLTFAnimation::Track> &get_tracks();
|
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();
|
GLTFAnimation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
String original_name;
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
HashMap<int, Track> tracks;
|
HashMap<int, Track> tracks;
|
||||||
|
Dictionary additional_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GLTF_ANIMATION_H
|
#endif // GLTF_ANIMATION_H
|
||||||
|
|
|
@ -69,7 +69,7 @@ public:
|
||||||
Camera3D *to_node() const;
|
Camera3D *to_node() const;
|
||||||
|
|
||||||
static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary);
|
static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary);
|
||||||
Dictionary to_dictionary() const;
|
virtual Dictionary to_dictionary() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GLTF_CAMERA_H
|
#endif // GLTF_CAMERA_H
|
||||||
|
|
|
@ -33,18 +33,31 @@
|
||||||
#include "scene/resources/importer_mesh.h"
|
#include "scene/resources/importer_mesh.h"
|
||||||
|
|
||||||
void GLTFMesh::_bind_methods() {
|
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("get_mesh"), &GLTFMesh::get_mesh);
|
||||||
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &GLTFMesh::set_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("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("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("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("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::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::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");
|
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() {
|
Ref<ImporterMesh> GLTFMesh::get_mesh() {
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
@ -68,3 +81,11 @@ Vector<float> GLTFMesh::get_blend_weights() {
|
||||||
void GLTFMesh::set_blend_weights(Vector<float> p_blend_weights) {
|
void GLTFMesh::set_blend_weights(Vector<float> p_blend_weights) {
|
||||||
blend_weights = 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;
|
||||||
|
}
|
||||||
|
|
|
@ -39,20 +39,26 @@ class GLTFMesh : public Resource {
|
||||||
GDCLASS(GLTFMesh, Resource);
|
GDCLASS(GLTFMesh, Resource);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
String original_name;
|
||||||
Ref<ImporterMesh> mesh;
|
Ref<ImporterMesh> mesh;
|
||||||
Vector<float> blend_weights;
|
Vector<float> blend_weights;
|
||||||
TypedArray<Material> instance_materials;
|
TypedArray<Material> instance_materials;
|
||||||
|
Dictionary additional_data;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
String get_original_name();
|
||||||
|
void set_original_name(String p_name);
|
||||||
Ref<ImporterMesh> get_mesh();
|
Ref<ImporterMesh> get_mesh();
|
||||||
void set_mesh(Ref<ImporterMesh> p_mesh);
|
void set_mesh(Ref<ImporterMesh> p_mesh);
|
||||||
Vector<float> get_blend_weights();
|
Vector<float> get_blend_weights();
|
||||||
void set_blend_weights(Vector<float> p_blend_weights);
|
void set_blend_weights(Vector<float> p_blend_weights);
|
||||||
TypedArray<Material> get_instance_materials();
|
TypedArray<Material> get_instance_materials();
|
||||||
void set_instance_materials(TypedArray<Material> p_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
|
#endif // GLTF_MESH_H
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include "gltf_node.h"
|
#include "gltf_node.h"
|
||||||
|
|
||||||
void GLTFNode::_bind_methods() {
|
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("get_parent"), &GLTFNode::get_parent);
|
||||||
ClassDB::bind_method(D_METHOD("set_parent", "parent"), &GLTFNode::set_parent);
|
ClassDB::bind_method(D_METHOD("set_parent", "parent"), &GLTFNode::set_parent);
|
||||||
ClassDB::bind_method(D_METHOD("get_height"), &GLTFNode::get_height);
|
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("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);
|
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, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height"); // int
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "xform"), "set_xform", "get_xform"); // Transform3D
|
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
|
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() {
|
GLTFNodeIndex GLTFNode::get_parent() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,11 @@
|
||||||
class GLTFNode : public Resource {
|
class GLTFNode : public Resource {
|
||||||
GDCLASS(GLTFNode, Resource);
|
GDCLASS(GLTFNode, Resource);
|
||||||
friend class GLTFDocument;
|
friend class GLTFDocument;
|
||||||
|
friend class SkinTool;
|
||||||
|
friend class FBXDocument;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
String original_name;
|
||||||
GLTFNodeIndex parent = -1;
|
GLTFNodeIndex parent = -1;
|
||||||
int height = -1;
|
int height = -1;
|
||||||
Transform3D transform;
|
Transform3D transform;
|
||||||
|
@ -56,6 +59,9 @@ protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
String get_original_name();
|
||||||
|
void set_original_name(String p_name);
|
||||||
|
|
||||||
GLTFNodeIndex get_parent();
|
GLTFNodeIndex get_parent();
|
||||||
void set_parent(GLTFNodeIndex p_parent);
|
void set_parent(GLTFNodeIndex p_parent);
|
||||||
|
|
||||||
|
@ -65,6 +71,9 @@ public:
|
||||||
Transform3D get_xform();
|
Transform3D get_xform();
|
||||||
void set_xform(Transform3D p_xform);
|
void set_xform(Transform3D p_xform);
|
||||||
|
|
||||||
|
Transform3D get_rest_xform();
|
||||||
|
void set_rest_xform(Transform3D p_rest_xform);
|
||||||
|
|
||||||
GLTFMeshIndex get_mesh();
|
GLTFMeshIndex get_mesh();
|
||||||
void set_mesh(GLTFMeshIndex p_mesh);
|
void set_mesh(GLTFMeshIndex p_mesh);
|
||||||
|
|
||||||
|
|
|
@ -82,11 +82,11 @@ void GLTFSkeleton::set_unique_names(TypedArray<String> p_unique_names) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary GLTFSkeleton::get_godot_bone_node() {
|
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) {
|
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) {
|
BoneAttachment3D *GLTFSkeleton::get_bone_attachment(int idx) {
|
||||||
|
|
|
@ -34,10 +34,14 @@
|
||||||
#include "../gltf_defines.h"
|
#include "../gltf_defines.h"
|
||||||
|
|
||||||
#include "core/io/resource.h"
|
#include "core/io/resource.h"
|
||||||
|
#include "scene/3d/bone_attachment_3d.h"
|
||||||
|
#include "scene/3d/skeleton_3d.h"
|
||||||
|
|
||||||
class GLTFSkeleton : public Resource {
|
class GLTFSkeleton : public Resource {
|
||||||
GDCLASS(GLTFSkeleton, Resource);
|
GDCLASS(GLTFSkeleton, Resource);
|
||||||
friend class GLTFDocument;
|
friend class GLTFDocument;
|
||||||
|
friend class SkinTool;
|
||||||
|
friend class FBXDocument;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The *synthesized* skeletons joints
|
// The *synthesized* skeletons joints
|
||||||
|
|
|
@ -126,11 +126,11 @@ void GLTFSkin::set_skeleton(int p_skeleton) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary GLTFSkin::get_joint_i_to_bone_i() {
|
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) {
|
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() {
|
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) {
|
void GLTFSkin::set_godot_skin(Ref<Skin> p_godot_skin) {
|
||||||
godot_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;
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ class TypedArray;
|
||||||
class GLTFSkin : public Resource {
|
class GLTFSkin : public Resource {
|
||||||
GDCLASS(GLTFSkin, Resource);
|
GDCLASS(GLTFSkin, Resource);
|
||||||
friend class GLTFDocument;
|
friend class GLTFDocument;
|
||||||
|
friend class SkinTool;
|
||||||
|
friend class FBXDocument;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The "skeleton" property defined in the gltf spec. -1 = Scene Root
|
// The "skeleton" property defined in the gltf spec. -1 = Scene Root
|
||||||
|
@ -110,6 +112,9 @@ public:
|
||||||
|
|
||||||
Ref<Skin> get_godot_skin();
|
Ref<Skin> get_godot_skin();
|
||||||
void set_godot_skin(Ref<Skin> p_godot_skin);
|
void set_godot_skin(Ref<Skin> p_godot_skin);
|
||||||
|
|
||||||
|
Dictionary to_dictionary();
|
||||||
|
Error from_dictionary(const Dictionary &dict);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GLTF_SKIN_H
|
#endif // GLTF_SKIN_H
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
#include "register_scene_types.h"
|
#include "register_scene_types.h"
|
||||||
|
|
||||||
#include "core/config/project_settings.h"
|
#include "core/config/project_settings.h"
|
||||||
#include "core/extension/gdextension_manager.h"
|
|
||||||
#include "core/object/class_db.h"
|
#include "core/object/class_db.h"
|
||||||
#include "core/os/os.h"
|
#include "core/os/os.h"
|
||||||
#include "scene/2d/animated_sprite_2d.h"
|
#include "scene/2d/animated_sprite_2d.h"
|
||||||
|
|
|
@ -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.
|
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
|
## vhacd
|
||||||
|
|
||||||
- Upstream: https://github.com/kmammou/v-hacd
|
- Upstream: https://github.com/kmammou/v-hacd
|
||||||
|
|
|
@ -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.
|
||||||
|
----------------------------------------
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue