diff options
134 files changed, 3977 insertions, 14725 deletions
diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml index e2dd0f7626..7af3516f71 100644 --- a/.github/actions/godot-build/action.yml +++ b/.github/actions/godot-build/action.yml @@ -20,8 +20,9 @@ inputs: default: "${{ github.workspace }}/.scons-cache/" scons-cache-limit: description: The scons cache size limit. - # actions/cache has 10 GiB limit. Allow 10 GiB minus 256 MiB. - default: 9984 + # actions/cache has 10 GiB limit, and GitHub runners have a 14 GiB disk. + # Limit to 7 GiB to avoid having the extracted cache fill the disk. + default: 7168 runs: using: "composite" steps: diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index c0b291b252..4919f34cf0 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -27,6 +27,8 @@ jobs: target: release_debug tools: true tests: true + # Skip debug symbols, they're way too big with MSVC. + sconsflags: debug_symbols=no bin: "./bin/godot.windows.opt.tools.64.exe" - name: Template (target=release, tools=no) @@ -57,7 +59,6 @@ jobs: target: ${{ matrix.target }} tools: ${{ matrix.tools }} tests: ${{ matrix.tests }} - scons-cache-limit: 8192 # Execute unit tests for the editor - name: Unit tests diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index b1323e0441..59c4dc4020 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -63,13 +63,6 @@ Copyright: 2011, Ole Kniemeyer, MAXON, www.maxon.net 2014-2022, Godot Engine contributors. License: Expat and Zlib -Files: ./modules/fbx/fbx_parser/ -Comment: Open Asset Import Library (FBX parser) -Copyright: 2006-2020, assimp team - 2007-2022, Juan Linietsky, Ariel Manzur. - 2014-2022, Godot Engine contributors. -License: BSD-3-clause - Files: ./platform/android/java/lib/aidl/com/android/* ./platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml ./platform/android/java/lib/src/com/google/android/* diff --git a/core/io/image.cpp b/core/io/image.cpp index 67e6724e71..fad9942017 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2726,6 +2726,7 @@ Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr; Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr; Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr; Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr; +Ref<Image> (*Image::basis_universal_unpacker_ptr)(const uint8_t *, int) = nullptr; void Image::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("width")); @@ -3008,7 +3009,7 @@ void Image::adjust_bcs(float p_brightness, float p_contrast, float p_saturation) } } -Image::UsedChannels Image::detect_used_channels(CompressSource p_source) { +Image::UsedChannels Image::detect_used_channels(CompressSource p_source) const { ERR_FAIL_COND_V(data.size() == 0, USED_CHANNELS_RGBA); ERR_FAIL_COND_V(is_compressed(), USED_CHANNELS_RGBA); bool r = false, g = false, b = false, a = false, c = false; @@ -3613,6 +3614,10 @@ Image::Image(const uint8_t *p_mem_png_jpg, int p_len) { if (is_empty() && _jpg_mem_loader_func) { copy_internals_from(_jpg_mem_loader_func(p_mem_png_jpg, p_len)); } + + if (is_empty() && _webp_mem_loader_func) { + copy_internals_from(_webp_mem_loader_func(p_mem_png_jpg, p_len)); + } } Ref<Resource> Image::duplicate(bool p_subresources) const { diff --git a/core/io/image.h b/core/io/image.h index 39c700565b..7e1e853244 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -147,6 +147,7 @@ public: static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer); static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels); static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer); + static Ref<Image> (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size); _FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const; _FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color); @@ -379,7 +380,7 @@ public: virtual Ref<Resource> duplicate(bool p_subresources = false) const override; - UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC); + UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC) const; void optimize_channels(); Color get_pixelv(const Point2i &p_point) const; diff --git a/core/object/method_bind.h b/core/object/method_bind.h index 1518c8d793..27098571e3 100644 --- a/core/object/method_bind.h +++ b/core/object/method_bind.h @@ -143,21 +143,20 @@ public: virtual ~MethodBind(); }; -template <class T> -class MethodBindVarArg : public MethodBind { -public: - typedef Variant (T::*NativeCall)(const Variant **, int, Callable::CallError &); - +// MethodBindVarArg base CRTP +template <class Derived, class T, class R, bool should_returns> +class MethodBindVarArgBase : public MethodBind { protected: - NativeCall call_method = nullptr; - MethodInfo arguments; + R(T::*method) + (const Variant **, int, Callable::CallError &); + MethodInfo method_info; public: virtual PropertyInfo _gen_argument_type_info(int p_arg) const { if (p_arg < 0) { - return arguments.return_val; - } else if (p_arg < arguments.arguments.size()) { - return arguments.arguments[p_arg]; + return _gen_return_type_info(); + } else if (p_arg < method_info.arguments.size()) { + return method_info.arguments[p_arg]; } else { return PropertyInfo(Variant::NIL, "arg_" + itos(p_arg), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT); } @@ -173,24 +172,31 @@ public: } #endif - virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { - T *instance = static_cast<T *>(p_object); - return (instance->*call_method)(p_args, p_arg_count, r_error); + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { + ERR_FAIL(); // Can't call. } - void set_method_info(const MethodInfo &p_info, bool p_return_nil_is_variant) { - set_argument_count(p_info.arguments.size()); - Variant::Type *at = memnew_arr(Variant::Type, p_info.arguments.size() + 1); - at[0] = p_info.return_val.type; - if (p_info.arguments.size()) { + virtual bool is_const() const { return false; } + + virtual bool is_vararg() const { return true; } + + MethodBindVarArgBase( + R (T::*p_method)(const Variant **, int, Callable::CallError &), + const MethodInfo &p_method_info, + bool p_return_nil_is_variant) : + method(p_method), method_info(p_method_info) { + set_argument_count(method_info.arguments.size()); + Variant::Type *at = memnew_arr(Variant::Type, method_info.arguments.size() + 1); + at[0] = _gen_return_type_info().type; + if (method_info.arguments.size()) { #ifdef DEBUG_METHODS_ENABLED Vector<StringName> names; - names.resize(p_info.arguments.size()); + names.resize(method_info.arguments.size()); #endif - for (int i = 0; i < p_info.arguments.size(); i++) { - at[i + 1] = p_info.arguments[i].type; + for (int i = 0; i < method_info.arguments.size(); i++) { + at[i + 1] = method_info.arguments[i].type; #ifdef DEBUG_METHODS_ENABLED - names.write[i] = p_info.arguments[i].name; + names.write[i] = method_info.arguments[i].name; #endif } @@ -199,31 +205,76 @@ public: #endif } argument_types = at; - arguments = p_info; if (p_return_nil_is_variant) { - arguments.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + method_info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; } + + _set_returns(should_returns); } - virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { - ERR_FAIL(); // Can't call. +private: + PropertyInfo _gen_return_type_info() const { + return reinterpret_cast<const Derived *>(this)->_gen_return_type_info_impl(); } +}; - void set_method(NativeCall p_method) { call_method = p_method; } - virtual bool is_const() const { return false; } +// variadic, no return +template <class T> +class MethodBindVarArgT : public MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false> { + friend class MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>; - virtual bool is_vararg() const { return true; } +public: + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + (static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>::method)(p_args, p_arg_count, r_error); + return {}; + } - MethodBindVarArg() { - _set_returns(true); + MethodBindVarArgT( + void (T::*p_method)(const Variant **, int, Callable::CallError &), + const MethodInfo &p_method_info, + bool p_return_nil_is_variant) : + MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>(p_method, p_method_info, p_return_nil_is_variant) { + } + +private: + PropertyInfo _gen_return_type_info_impl() const { + return {}; } }; template <class T> -MethodBind *create_vararg_method_bind(Variant (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) { - MethodBindVarArg<T> *a = memnew((MethodBindVarArg<T>)); - a->set_method(p_method); - a->set_method_info(p_info, p_return_nil_is_variant); +MethodBind *create_vararg_method_bind(void (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) { + MethodBind *a = memnew((MethodBindVarArgT<T>)(p_method, p_info, p_return_nil_is_variant)); + a->set_instance_class(T::get_class_static()); + return a; +} + +// variadic, return +template <class T, class R> +class MethodBindVarArgTR : public MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true> { + friend class MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>; + +public: + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + return (static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>::method)(p_args, p_arg_count, r_error); + } + + MethodBindVarArgTR( + R (T::*p_method)(const Variant **, int, Callable::CallError &), + const MethodInfo &p_info, + bool p_return_nil_is_variant) : + MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>(p_method, p_info, p_return_nil_is_variant) { + } + +private: + PropertyInfo _gen_return_type_info_impl() const { + return GetTypeInfo<R>::get_class_info(); + } +}; + +template <class T, class R> +MethodBind *create_vararg_method_bind(R (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) { + MethodBind *a = memnew((MethodBindVarArgTR<T, R>)(p_method, p_info, p_return_nil_is_variant)); a->set_instance_class(T::get_class_static()); return a; } diff --git a/core/object/object.cpp b/core/object/object.cpp index 0a7879c774..c2cd42ff91 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1028,15 +1028,15 @@ struct _ObjectSignalDisconnectData { Callable callable; }; -Variant Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - ERR_FAIL_COND_V(p_argcount < 1, Variant()); + ERR_FAIL_COND_V(p_argcount < 1, Error::ERR_INVALID_PARAMETER); if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; - ERR_FAIL_COND_V(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING, Variant()); + ERR_FAIL_COND_V(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING, Error::ERR_INVALID_PARAMETER); } r_error.error = Callable::CallError::CALL_OK; @@ -1050,9 +1050,7 @@ Variant Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::C args = &p_args[1]; } - emit_signalp(signal, args, argc); - - return Variant(); + return emit_signalp(signal, args, argc); } Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int p_argcount) { diff --git a/core/object/object.h b/core/object/object.h index f58a79b49c..b640c4e78a 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -522,7 +522,7 @@ private: void _add_user_signal(const String &p_name, const Array &p_args = Array()); bool _has_user_signal(const StringName &p_name) const; - Variant _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Array _get_signal_list() const; Array _get_signal_connection_list(const String &p_signal) const; Array _get_incoming_connections() const; diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index ee8eb97a93..d3c48853f1 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -444,25 +444,25 @@ UndoRedo::~UndoRedo() { clear_history(); } -Variant UndoRedo::_add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void UndoRedo::_add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 2) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 0; - return Variant(); + return; } if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; - return Variant(); + return; } if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; - return Variant(); + return; } r_error.error = Callable::CallError::CALL_OK; @@ -471,28 +471,27 @@ Variant UndoRedo::_add_do_method(const Variant **p_args, int p_argcount, Callabl StringName method = *p_args[1]; add_do_methodp(object, method, p_args + 2, p_argcount - 2); - return Variant(); } -Variant UndoRedo::_add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void UndoRedo::_add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 2) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 0; - return Variant(); + return; } if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; - return Variant(); + return; } if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; - return Variant(); + return; } r_error.error = Callable::CallError::CALL_OK; @@ -501,7 +500,6 @@ Variant UndoRedo::_add_undo_method(const Variant **p_args, int p_argcount, Calla StringName method = *p_args[1]; add_undo_methodp(object, method, p_args + 2, p_argcount - 2); - return Variant(); } void UndoRedo::_bind_methods() { diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index ecd7a21167..63cf3e5cbe 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -46,8 +46,8 @@ public: }; typedef void (*CommitNotifyCallback)(void *p_ud, const String &p_name); - Variant _add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - Variant _add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); typedef void (*MethodNotifyCallback)(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount); typedef void (*PropertyNotifyCallback)(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value); diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 68cb615209..27331d3f96 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -513,48 +513,56 @@ <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Compares the left operand [Array] against the [code]right[/code] [Array]. Returns [code]true[/code] if the sizes or contents of the arrays are [i]not[/i] equal, [code]false[/code] otherwise. </description> </operator> <operator name="operator +"> <return type="Array" /> <argument index="0" name="right" type="Array" /> <description> + Concatenates two [Array]s together, with the [code]right[/code] [Array] being added to the end of the [Array] specified in the left operand. For example, [code][1, 2] + [3, 4][/code] results in [code][1, 2, 3, 4][/code]. </description> </operator> <operator name="operator <"> <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Performs a comparison for each index between the left operand [Array] and the [code]right[/code] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has less elements, otherwise it returns [code]true[/code]. </description> </operator> <operator name="operator <="> <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Performs a comparison for each index between the left operand [Array] and the [code]right[/code] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has less or the same number of elements, otherwise it returns [code]false[/code]. </description> </operator> <operator name="operator =="> <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Compares the left operand [Array] against the [code]right[/code] [Array]. Returns [code]true[/code] if the sizes and contents of the arrays are equal, [code]false[/code] otherwise. </description> </operator> <operator name="operator >"> <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Performs a comparison for each index between the left operand [Array] and the [code]right[/code] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [code]right[/code] [Array] has more elements, otherwise it returns [code]false[/code]. </description> </operator> <operator name="operator >="> <return type="bool" /> <argument index="0" name="right" type="Array" /> <description> + Performs a comparison for each index between the left operand [Array] and the [code]right[/code] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [code]right[/code] [Array] has more or the same number of elements, otherwise it returns [code]false[/code]. </description> </operator> <operator name="operator []"> <return type="void" /> <argument index="0" name="index" type="int" /> <description> + Returns a reference to the element of type [Variant] at the specified location. Arrays start at index 0. [code]index[/code] can be a zero or positive value to start from the beginning, or a negative value to start from the end. Out-of-bounds array access causes a run-time error, which will result in an error being printed and the project execution pausing if run from the editor. </description> </operator> </operators> diff --git a/doc/classes/EditorSceneFormatImporterFBX.xml b/doc/classes/EditorSceneFormatImporterFBX.xml deleted file mode 100644 index 21aebd4507..0000000000 --- a/doc/classes/EditorSceneFormatImporterFBX.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="EditorSceneFormatImporterFBX" inherits="EditorSceneFormatImporter" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> - <brief_description> - FBX 3D asset importer. - </brief_description> - <description> - This is an FBX 3D asset importer with full support for most FBX features. - If exporting a FBX scene from Autodesk Maya, use these FBX export settings: - [codeblock] - - Smoothing Groups - - Smooth Mesh - - Triangluate (for meshes with blend shapes) - - Bake Animation - - Resample All - - Deformed Models - - Skins - - Blend Shapes - - Curve Filters - - Constant Key Reducer - - Auto Tangents Only - - *Do not check* Constraints (as it will break the file) - - Can check Embed Media (embeds textures into the exported FBX file) - - Note that when importing embedded media, the texture and mesh will be a single immutable file. - - You will have to re-export then re-import the FBX if the texture has changed. - - Units: Centimeters - - Up Axis: Y - - Binary format in FBX 2017 - [/codeblock] - </description> - <tutorials> - </tutorials> -</class> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 35868563de..140015babf 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -153,7 +153,7 @@ Returns [constant ALPHA_BLEND] if the image has data for alpha values. Returns [constant ALPHA_BIT] if all the alpha values are stored in a single bit. Returns [constant ALPHA_NONE] if no data for alpha values is found. </description> </method> - <method name="detect_used_channels"> + <method name="detect_used_channels" qualifiers="const"> <return type="int" enum="Image.UsedChannels" /> <argument index="0" name="source" type="int" enum="Image.CompressSource" default="0" /> <description> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 28b104e276..5d0a49c23b 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -586,7 +586,7 @@ </description> </method> <method name="rpc" qualifiers="vararg"> - <return type="Variant" /> + <return type="void" /> <argument index="0" name="method" type="StringName" /> <description> Sends a remote procedure call request for the given [code]method[/code] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behaviour depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns an empty [Variant]. @@ -605,7 +605,7 @@ </description> </method> <method name="rpc_id" qualifiers="vararg"> - <return type="Variant" /> + <return type="void" /> <argument index="0" name="peer_id" type="int" /> <argument index="1" name="method" type="StringName" /> <description> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 77927b9a8a..f7a3be48cf 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -107,7 +107,7 @@ </description> </method> <method name="call_deferred" qualifiers="vararg"> - <return type="void" /> + <return type="Variant" /> <argument index="0" name="method" type="StringName" /> <description> Calls the [code]method[/code] on the object during idle time. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: @@ -301,7 +301,7 @@ </description> </method> <method name="emit_signal" qualifiers="vararg"> - <return type="void" /> + <return type="int" enum="Error" /> <argument index="0" name="signal" type="StringName" /> <description> Emits the given [code]signal[/code]. The signal must exist, so it should be a built-in signal of this class or one of its parent classes, or a user-defined signal. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: diff --git a/doc/classes/PortableCompressedTexture2D.xml b/doc/classes/PortableCompressedTexture2D.xml new file mode 100644 index 0000000000..7b46a0bf07 --- /dev/null +++ b/doc/classes/PortableCompressedTexture2D.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="PortableCompressedTexture2D" inherits="Texture2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Provides a compressed texture for disk and/or VRAM in a way that is portable. + </brief_description> + <description> + This class allows storing compressed textures as self contained (not imported) resources. + For 2D usage (compressed on disk, uncompressed on VRAM), the lossy and lossless modes are recommended. For 3D usage (compressed on VRAM) it depends on the target platform. + If you intend to only use desktop, S3TC or BPTC are recommended. For only mobile, ETC2 is recommended. + For portable, self contained 3D textures that work on both desktop and mobile, Basis Universal is recommended (although it has a small quality cost and longer compression time as a tradeoff). + This resource is intended to be created from code. + </description> + <tutorials> + </tutorials> + <methods> + <method name="create_from_image"> + <return type="void" /> + <argument index="0" name="image" type="Image" /> + <argument index="1" name="compression_mode" type="int" enum="PortableCompressedTexture2D.CompressionMode" /> + <argument index="2" name="normal_map" type="bool" default="false" /> + <argument index="3" name="lossy_quality" type="float" default="0.8" /> + <description> + Initializes the compressed texture from a base image. The compression mode must be provided. + If this image will be used as a normal map, the "normal map" flag is recommended, to ensure optimum quality. + If lossy compression is requested, the quality setting can optionally be provided. This maps to Lossy WEBP compression quality. + </description> + </method> + <method name="get_compression_mode" qualifiers="const"> + <return type="int" enum="PortableCompressedTexture2D.CompressionMode" /> + <description> + Return the compression mode used (valid after initialized). + </description> + </method> + <method name="get_format" qualifiers="const"> + <return type="int" enum="Image.Format" /> + <description> + Return the image format used (valid after initialized). + </description> + </method> + <method name="is_keeping_all_compressed_buffers" qualifiers="static"> + <return type="bool" /> + <description> + Return whether the flag is overriden for all textures of this type. + </description> + </method> + <method name="set_keep_all_compressed_buffers" qualifiers="static"> + <return type="void" /> + <argument index="0" name="keep" type="bool" /> + <description> + Overrides the flag globally for all textures of this type. This is used primarly by the editor. + </description> + </method> + </methods> + <members> + <member name="_data" type="PackedByteArray" setter="_set_data" getter="_get_data" default="PackedByteArray()"> + </member> + <member name="keep_compressed_buffer" type="bool" setter="set_keep_compressed_buffer" getter="is_keeping_compressed_buffer" default="false"> + When running on the editor, this class will keep the source compressed data in memory. Otherwise, the source compressed data is lost after loading and the resource can't be re saved. + This flag allows to keep the compressed data in memory if you intend it to persist after loading. + </member> + <member name="size_override" type="Vector2" setter="set_size_override" getter="get_size_override" default="Vector2(0, 0)"> + Allow overriding the texture size (for 2D only). + </member> + </members> + <constants> + <constant name="COMPRESSION_MODE_LOSSLESS" value="0" enum="CompressionMode"> + </constant> + <constant name="COMPRESSION_MODE_LOSSY" value="1" enum="CompressionMode"> + </constant> + <constant name="COMPRESSION_MODE_BASIS_UNIVERSAL" value="2" enum="CompressionMode"> + </constant> + <constant name="COMPRESSION_MODE_S3TC" value="3" enum="CompressionMode"> + </constant> + <constant name="COMPRESSION_MODE_ETC2" value="4" enum="CompressionMode"> + </constant> + <constant name="COMPRESSION_MODE_BPTC" value="5" enum="CompressionMode"> + </constant> + </constants> +</class> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index a8b4129061..ab36c9473c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -553,6 +553,14 @@ <member name="editor/script/templates_search_path" type="String" setter="" getter="" default=""res://script_templates""> Search path for project-specific script templates. Godot will search for script templates both in the editor-specific path and in this project-specific path. </member> + <member name="filesystem/import/blend/enabled" type="bool" setter="" getter="" default="true"> + If [code]true[/code], Blender 3D scene files with the [code].blend[/code] extension will be imported by converting them to glTF 2.0. + This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blend/blender_path[/code]. Blender 3.0 or later is required. + </member> + <member name="filesystem/import/fbx/enabled" type="bool" setter="" getter="" default="true"> + If [code]true[/code], Autodesk FBX 3D scene files with the [code].fbx[/code] extension will be imported by converting them to glTF 2.0. + This requires configuring a path to a FBX2glTF executable in the editor settings at [code]filesystem/import/fbx/fbx2gltf_path[/code]. + </member> <member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0"> Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden. </member> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index f3dfc727b0..77023d2126 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -14,7 +14,7 @@ </tutorials> <methods> <method name="call_group" qualifiers="vararg"> - <return type="Variant" /> + <return type="void" /> <argument index="0" name="group" type="StringName" /> <argument index="1" name="method" type="StringName" /> <description> @@ -24,7 +24,7 @@ </description> </method> <method name="call_group_flags" qualifiers="vararg"> - <return type="Variant" /> + <return type="void" /> <argument index="0" name="flags" type="int" /> <argument index="1" name="group" type="StringName" /> <argument index="2" name="method" type="StringName" /> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index c909a35ab5..396be2d9f8 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -22,7 +22,7 @@ </description> </method> <method name="call_recursive" qualifiers="vararg"> - <return type="Variant" /> + <return type="void" /> <argument index="0" name="method" type="StringName" /> <description> Calls the [code]method[/code] on the actual TreeItem and its children recursively. Pass parameters as a comma separated list. diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index a075ff0c55..58c584b8b2 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -93,8 +93,11 @@ public: if (unbinds > 0) { return Callable(target, method).unbind(unbinds); } else if (!binds.is_empty()) { - const Variant *args = binds.ptr(); - return Callable(target, method).bind(&args, binds.size()); + const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * binds.size()); + for (int i = 0; i < binds.size(); i++) { + argptrs[i] = &binds[i]; + } + return Callable(target, method).bind(argptrs, binds.size()); } else { return Callable(target, method); } diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index 56d1e7cee9..c821561ca6 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -30,6 +30,7 @@ #include "editor_performance_profiler.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "main/performance.h" @@ -386,8 +387,8 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() { monitor_draw->add_child(info_message); for (int i = 0; i < Performance::MONITOR_MAX; i++) { - String base = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)).get_slicec('/', 0).capitalize(); - String name = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)).get_slicec('/', 1).capitalize(); + String base = EditorPropertyNameProcessor::get_singleton()->process_name(Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)).get_slicec('/', 0), EditorPropertyNameProcessor::STYLE_CAPITALIZED); + String name = EditorPropertyNameProcessor::get_singleton()->process_name(Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)).get_slicec('/', 1), EditorPropertyNameProcessor::STYLE_CAPITALIZED); monitors.insert(Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)), Monitor(name, base, i, Performance::get_singleton()->get_monitor_type(Performance::Monitor(i)), nullptr)); } diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 92345f024c..1e8753acc0 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -44,6 +44,7 @@ #include "editor/editor_file_dialog.h" #include "editor/editor_log.h" #include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" @@ -647,7 +648,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da const ServersDebugger::ServerInfo &srv = frame.servers[i]; EditorProfiler::Metric::Category c; const String name = srv.name; - c.name = name.capitalize(); + c.name = EditorPropertyNameProcessor::get_singleton()->process_name(name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); c.items.resize(srv.functions.size()); c.total_time = 0; c.signature = "categ::" + name; @@ -659,7 +660,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da item.self = srv.functions[j].time; item.total = item.self; item.signature = "categ::" + name + "::" + item.name; - item.name = item.name.capitalize(); + item.name = EditorPropertyNameProcessor::get_singleton()->process_name(item.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); c.total_time += item.total; c.items.write[j] = item; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d386642ca3..25e3bc8d6a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5839,6 +5839,7 @@ EditorNode::EditorNode() { EditorPropertyNameProcessor *epnp = memnew(EditorPropertyNameProcessor); add_child(epnp); + PortableCompressedTexture2D::set_keep_all_compressed_buffers(true); Input::get_singleton()->set_use_accumulated_input(true); Resource::_get_local_scene_func = _resource_get_edited_scene; diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index af4ae3e576..5e6c35806c 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -123,6 +123,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["dof"] = "DoF"; capitalize_string_remaps["dpi"] = "DPI"; capitalize_string_remaps["dtls"] = "DTLS"; + capitalize_string_remaps["erp"] = "ERP"; capitalize_string_remaps["etc"] = "ETC"; capitalize_string_remaps["etc2"] = "ETC2"; capitalize_string_remaps["fbx"] = "FBX"; @@ -140,10 +141,12 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["gles3"] = "GLES3"; capitalize_string_remaps["gpu"] = "GPU"; capitalize_string_remaps["gui"] = "GUI"; + capitalize_string_remaps["guid"] = "GUID"; capitalize_string_remaps["hdr"] = "HDR"; capitalize_string_remaps["hidpi"] = "hiDPI"; capitalize_string_remaps["hipass"] = "High-pass"; capitalize_string_remaps["hsv"] = "HSV"; + capitalize_string_remaps["html"] = "HTML"; capitalize_string_remaps["http"] = "HTTP"; capitalize_string_remaps["id"] = "ID"; capitalize_string_remaps["igd"] = "IGD"; @@ -157,6 +160,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["iphone"] = "iPhone"; capitalize_string_remaps["ipv6"] = "IPv6"; capitalize_string_remaps["ir"] = "IR"; + capitalize_string_remaps["itunes"] = "iTunes"; capitalize_string_remaps["jit"] = "JIT"; capitalize_string_remaps["k1"] = "K1"; capitalize_string_remaps["k2"] = "K2"; @@ -205,6 +209,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["url"] = "URL"; capitalize_string_remaps["urls"] = "URLs"; capitalize_string_remaps["us"] = String::utf8("(µs)"); // Unit. + capitalize_string_remaps["usb"] = "USB"; capitalize_string_remaps["usec"] = String::utf8("(µsec)"); // Unit. capitalize_string_remaps["uuid"] = "UUID"; capitalize_string_remaps["uv"] = "UV"; diff --git a/editor/project_manager.h b/editor/project_manager.h index 2965dc7d2e..4f053793fd 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -55,46 +55,46 @@ class ProjectManager : public Control { static ProjectManager *singleton; - TabContainer *tabs; + TabContainer *tabs = nullptr; - ProjectList *_project_list; + ProjectList *_project_list = nullptr; - LineEdit *search_box; - Label *loading_label; - OptionButton *filter_option; + LineEdit *search_box = nullptr; + Label *loading_label = nullptr; + OptionButton *filter_option = nullptr; - Button *run_btn; - Button *open_btn; - Button *rename_btn; - Button *erase_btn; - Button *erase_missing_btn; - Button *about_btn; + Button *run_btn = nullptr; + Button *open_btn = nullptr; + Button *rename_btn = nullptr; + Button *erase_btn = nullptr; + Button *erase_missing_btn = nullptr; + Button *about_btn = nullptr; - EditorAssetLibrary *asset_library; + EditorAssetLibrary *asset_library = nullptr; - EditorFileDialog *scan_dir; - ConfirmationDialog *language_restart_ask; + EditorFileDialog *scan_dir = nullptr; + ConfirmationDialog *language_restart_ask = nullptr; - ConfirmationDialog *erase_ask; - Label *erase_ask_label; - CheckBox *delete_project_contents; + ConfirmationDialog *erase_ask = nullptr; + Label *erase_ask_label = nullptr; + CheckBox *delete_project_contents = nullptr; - ConfirmationDialog *erase_missing_ask; - ConfirmationDialog *multi_open_ask; - ConfirmationDialog *multi_run_ask; - ConfirmationDialog *multi_scan_ask; - ConfirmationDialog *ask_update_settings; - ConfirmationDialog *open_templates; - EditorAbout *about; + ConfirmationDialog *erase_missing_ask = nullptr; + ConfirmationDialog *multi_open_ask = nullptr; + ConfirmationDialog *multi_run_ask = nullptr; + ConfirmationDialog *multi_scan_ask = nullptr; + ConfirmationDialog *ask_update_settings = nullptr; + ConfirmationDialog *open_templates = nullptr; + EditorAbout *about = nullptr; - HBoxContainer *settings_hb; + HBoxContainer *settings_hb = nullptr; - AcceptDialog *run_error_diag; - AcceptDialog *dialog_error; - ProjectDialog *npdialog; + AcceptDialog *run_error_diag = nullptr; + AcceptDialog *dialog_error = nullptr; + ProjectDialog *npdialog = nullptr; - OptionButton *language_btn; - LinkButton *version_btn; + OptionButton *language_btn = nullptr; + LinkButton *version_btn = nullptr; void _open_asset_library(); void _scan_projects(); diff --git a/editor/translations/extract.py b/editor/translations/extract.py index 4a7e87e271..cb918c0092 100755 --- a/editor/translations/extract.py +++ b/editor/translations/extract.py @@ -113,15 +113,20 @@ message_patterns = { r'RTRN\("(?P<message>([^"\\]|\\.)*)", "(?P<plural_message>([^"\\]|\\.)*)",[^,)]+?(, "(?P<context>([^"\\]|\\.)*)")?\)' ): ExtractType.TEXT, re.compile(r'_initial_set\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH, - re.compile(r'GLOBAL_DEF(_RST)?(_NOVAL)?\("(?P<message>[^".]+?)",'): ExtractType.PROPERTY_PATH, + re.compile(r'GLOBAL_DEF(_RST)?(_NOVAL)?(_BASIC)?\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH, + re.compile(r'GLOBAL_DEF_BASIC\(vformat\("(?P<message>layer_names/\w+)/layer_%d"'): ExtractType.PROPERTY_PATH, re.compile(r'EDITOR_DEF(_RST)?\("(?P<message>[^"]+?)",'): ExtractType.PROPERTY_PATH, re.compile( + r'EDITOR_SETTING(_USAGE)?\(Variant::[_A-Z0-9]+, [_A-Z0-9]+, "(?P<message>[^"]+?)",' + ): ExtractType.PROPERTY_PATH, + re.compile( r'(ADD_PROPERTYI?|ImportOption|ExportOption)\(PropertyInfo\(Variant::[_A-Z0-9]+, "(?P<message>[^"]+?)"[,)]' ): ExtractType.PROPERTY_PATH, re.compile( r"(?!#define )LIMPL_PROPERTY(_RANGE)?\(Variant::[_A-Z0-9]+, (?P<message>[^,]+?)," ): ExtractType.PROPERTY_PATH, re.compile(r'ADD_GROUP\("(?P<message>[^"]+?)", "(?P<prefix>[^"]*?)"\)'): ExtractType.GROUP, + re.compile(r'#define WRTC_\w+ "(?P<message>[^"]+?)"'): ExtractType.PROPERTY_PATH, } @@ -231,6 +236,8 @@ def process_file(f, fname): msg = msg[len(current_group) :] else: current_group = "" + if "." in msg: # Strip feature tag. + msg = msg.split(".", 1)[0] for part in msg.split("/"): _add_message(_process_editor_string(part), msg_plural, msgctx, location, translator_comment) elif extract_type == ExtractType.GROUP: diff --git a/main/main.cpp b/main/main.cpp index c7a644d7b3..89b56a814e 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1488,6 +1488,28 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF("display/window/ios/hide_home_indicator", true); GLOBAL_DEF("input_devices/pointing/ios/touch_delay", 0.150); + // XR project settings. + GLOBAL_DEF_BASIC("xr/openxr/enabled", false); + GLOBAL_DEF_BASIC("xr/openxr/default_action_map", "res://default_action_map.tres"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/default_action_map", PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres")); + + GLOBAL_DEF_BASIC("xr/openxr/form_factor", "0"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/form_factor", PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head mounted,Handheld")); + + GLOBAL_DEF_BASIC("xr/openxr/view_configuration", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/view_configuration", PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo")); // "Mono,Stereo,Quad,Observer" + + GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); + +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + + // editor settings (it seems we're too early in the process when setting up rendering, to access editor settings...) + // EDITOR_DEF_RST("xr/openxr/in_editor", false); + // GLOBAL_DEF("xr/openxr/in_editor", false); +#endif + Engine::get_singleton()->set_frame_delay(frame_delay); message_queue = memnew(MessageQueue); diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh index 13b5ab79b6..5ab9c1bbb9 100755 --- a/misc/scripts/clang_format.sh +++ b/misc/scripts/clang_format.sh @@ -19,6 +19,10 @@ while read -r f; do continue elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/input/InputManager"* ]]; then continue + elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView"* ]]; then + continue + elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper"* ]]; then + continue fi python misc/scripts/copyright_headers.py "$f" diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index 8e328a519d..4c2ebe603f 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -143,12 +143,11 @@ static Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image:: } #endif // TOOLS_ENABLED -static Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) { +static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { Ref<Image> image; - const uint8_t *r = p_buffer.ptr(); - const uint8_t *ptr = r; - int size = p_buffer.size(); + const uint8_t *ptr = p_data; + int size = p_size; basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats; Image::Format imgfmt = Image::FORMAT_MAX; @@ -259,6 +258,14 @@ static Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) { return image; } +static Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) { + Ref<Image> image; + + const uint8_t *r = p_buffer.ptr(); + int size = p_buffer.size(); + return basis_universal_unpacker_ptr(r, size); +} + void register_basis_universal_types() { #ifdef TOOLS_ENABLED using namespace basisu; @@ -267,6 +274,7 @@ void register_basis_universal_types() { Image::basis_universal_packer = basis_universal_packer; #endif Image::basis_universal_unpacker = basis_universal_unpacker; + Image::basis_universal_unpacker_ptr = basis_universal_unpacker_ptr; } void unregister_basis_universal_types() { @@ -274,4 +282,5 @@ void unregister_basis_universal_types() { Image::basis_universal_packer = nullptr; #endif Image::basis_universal_unpacker = nullptr; + Image::basis_universal_unpacker_ptr = nullptr; } diff --git a/modules/fbx/README.md b/modules/fbx/README.md deleted file mode 100644 index 8eca4bd3c9..0000000000 --- a/modules/fbx/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# Open Source FBX Specification for the Importer - -The goal of this document is to make everything in FBX clearly stated, any errors will be corrected over time this -is a first draft. - -## fbx parser - originally from assimp - -- Folder: /modules/fbx/fbx_parser -- Upstream: assimp -- Original Version: git (308db73d0b3c2d1870cd3e465eaa283692a4cf23, 2019) -- License: BSD-3-Clause - -This can never be updated from upstream, we have heavily modified the parser to provide memory safety and add some -functionality. If anything we should give this parser back to assimp at some point as it has a lot of new features. - -# Updating assimp fbx parser - -Don't. It's not possible the code is rewritten in many areas to remove thirdparty deps and various bugs are fixed. - -Many days were put into rewriting the parser to use safe code and safe memory accessors. - -# File Headers - -FBX Binaries start with the header "Kaydara FBX Binary" - -FBX ASCII documents contain a larger header, sometimes with copyright information for a file. - -Detecting these is pretty simple. - -# What is an OP link? -It's an object to property link. It lists the properties for that object in some cases. Source and destination based by -ID. - -# What is a OO link? -Its an object to object link, it contains the ID source and destination ID. - -# FBX Node connections - -Nodes in FBX are connected using OO links, This means Object to Object. - -FBX has a single other kind of link which is Object Property, this is used for Object to Property Links, this can be - extra attributes, defaults, or even some simple settings. - -# Bones / Joints / Locators - -Bones in FBX are nodes, they initially have the Model:: Type, then have links to SubDeformer the sub deformer -is part of the skin there is also an explicit Skin link, which then links to the geometry using OO links in the -document. - -# Rotation Order in FBX compared to Godot - -**Godot uses the rotation order:** YXZ - -**FBX has dynamic rotation order to prevent gimbal lock with complex animations** - -```cpp -enum RotOrder { - RotOrder_EulerXYZ = 0 - RotOrder_EulerXZY, - RotOrder_EulerYZX, - RotOrder_EulerYXZ, - RotOrder_EulerZXY, - RotOrder_EulerZYX, - RotOrder_SphericXYZ // nobody uses this - as far as we can tell -}; -``` - - -# Pivot transforms - -### Pivot description: -- Maya and 3DS max consider everything to be in node space (bones joints, skins, lights, cameras, etc) -- Everything is a node, this means essentially nodes are auto or variants -- They are local to the node in the tree. -- They are used to calculate where a node is in space -```c++ -// For a better reference you can check editor_scene_importer_fbx.h -// references: GenFBXTransform / read the data in -// references: ComputePivotTransform / run the calculation -// This is the local pivot transform for the node, not the global transforms -Transform ComputePivotTransform( - Transform3D chain[TransformationComp_MAXIMUM], - Transform3D &geometric_transform) { - // Maya pivots - Transform3D T = chain[TransformationComp_Translation]; - Transform3D Roff = chain[TransformationComp_RotationOffset]; - Transform3D Rp = chain[TransformationComp_RotationPivot]; - Transform3D Rpre = chain[TransformationComp_PreRotation]; - Transform3D R = chain[TransformationComp_Rotation]; - Transform3D Rpost = chain[TransformationComp_PostRotation]; - Transform3D Soff = chain[TransformationComp_ScalingOffset]; - Transform3D Sp = chain[TransformationComp_ScalingPivot]; - Transform3D S = chain[TransformationComp_Scaling]; - - // 3DS Max Pivots - Transform3D OT = chain[TransformationComp_GeometricTranslation]; - Transform3D OR = chain[TransformationComp_GeometricRotation]; - Transform3D OS = chain[TransformationComp_GeometricScaling]; - - // Calculate 3DS max pivot transform - use geometric space (e.g doesn't effect children nodes only the current node) - geometric_transform = OT * OR * OS; - // Calculate standard maya pivots - return T * Roff * Rp * Rpre * R * Rpost.inverse() * Rp.inverse() * Soff * Sp * S * Sp.inverse(); -} -``` - -# Transform inheritance for FBX Nodes - -The goal of below is to explain why they implement this in the first place. - -The use case is to make nodes have an option to override their local scaling or to make scaling influenced by orientation, which i would imagine would be useful for when you need to rotate a node and the child to scale based on the orientation rather than setting on the rotation matrix planes. -```cpp -// not modified the formatting here since this code must remain clear -enum TransformInheritance { - Transform_RrSs = 0, - // Parent Rotation * Local Rotation * Parent Scale * Local Scale -- Parent Rotation Offset * Parent ScalingOffset (Local scaling is offset by rotation of parent node) - Transform_RSrs = 1, // Parent Rotation * Parent Scale * Local Rotation * Local Scale -- Parent * Local (normal mode) - Transform_Rrs = 2, // Parent Rotation * Local Rotation * Local Scale -- Node transform scale is the only relevant component - TransformInheritance_MAX // end-of-enum sentinel -}; - -enum TransformInheritance { - Transform_RrSs = 0, - // Local scaling is offset by rotation of parent node - Transform_RSrs = 1, - // Parent * Local (normal mode) - Transform_Rrs = 2, - // Node transform scale is the only relevant component - TransformInheritance_MAX // end-of-enum sentinel -}; -``` - -# Axis in FBX - -Godot has one format for the declared axis - -AXIS X, AXIS Y, -AXIS Z - -FBX supports any format you can think of. As it has to support Maya and 3DS Max. - -#### FBX File Header -```json -GlobalSettings: { - Version: 1000 - Properties70: { - P: "UpAxis", "int", "Integer", "",1 - P: "UpAxisSign", "int", "Integer", "",1 - P: "FrontAxis", "int", "Integer", "",2 - P: "FrontAxisSign", "int", "Integer", "",1 - P: "CoordAxis", "int", "Integer", "",0 - P: "CoordAxisSign", "int", "Integer", "",1 - P: "OriginalUpAxis", "int", "Integer", "",1 - P: "OriginalUpAxisSign", "int", "Integer", "",1 - P: "UnitScaleFactor", "double", "Number", "",1 - P: "OriginalUnitScaleFactor", "double", "Number", "",1 - P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 - P: "DefaultCamera", "KString", "", "", "Producer Perspective" - P: "TimeMode", "enum", "", "",6 - P: "TimeProtocol", "enum", "", "",2 - P: "SnapOnFrameMode", "enum", "", "",0 - P: "TimeSpanStart", "KTime", "Time", "",0 - P: "TimeSpanStop", "KTime", "Time", "",92372316000 - P: "CustomFrameRate", "double", "Number", "",-1 - P: "TimeMarker", "Compound", "", "" - P: "CurrentTimeMarker", "int", "Integer", "",-1 - } -} -``` - -#### FBX FILE declares axis dynamically using FBX header -Coord is X -Up is Y -Front is Z - -#### GODOT - constant reference point -Coord is X positive, -Y is up positive, -Front is -Z negative - -### Explaining MeshGeometry indexing - -Reference type declared: -- Direct (directly related to the mapping information type) -- IndexToDirect (Map with key value, meaning depends on the MappingInformationType) - -ControlPoint is a vertex -* None The mapping is undetermined. -* ByVertex There will be one mapping coordinate for each surface control point/vertex. - * If you have direct reference type vertices [x] - * If you have IndexToDirect reference type the UV -* ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex) -* ByPolygon There can be only one mapping coordinate for the whole polygon. - * One mapping per polygon polygon x has this normal x - * For each vertex of the polygon then set the normal to x -* ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id) -* AllSame There can be only one mapping coordinate for the whole surface. diff --git a/modules/fbx/SCsub b/modules/fbx/SCsub deleted file mode 100644 index 0311fddfee..0000000000 --- a/modules/fbx/SCsub +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_modules") - -env_fbx = env_modules.Clone() - -# Make includes relative to the folder path specified here so our includes are clean -env_fbx.Prepend(CPPPATH=["#modules/fbx/"]) - -if env["builtin_zlib"]: - env_fbx.Prepend(CPPPATH=["#thirdparty/zlib/"]) - -# Godot's own source files -env_fbx.add_source_files(env.modules_sources, "tools/*.cpp") -env_fbx.add_source_files(env.modules_sources, "data/*.cpp") -env_fbx.add_source_files(env.modules_sources, "fbx_parser/*.cpp") -env_fbx.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/fbx/config.py b/modules/fbx/config.py deleted file mode 100644 index 78929800b3..0000000000 --- a/modules/fbx/config.py +++ /dev/null @@ -1,16 +0,0 @@ -def can_build(env, platform): - return env["tools"] - - -def configure(env): - pass - - -def get_doc_classes(): - return [ - "EditorSceneImporterFBX", - ] - - -def get_doc_path(): - return "doc_classes" diff --git a/modules/fbx/data/fbx_anim_container.h b/modules/fbx/data/fbx_anim_container.h deleted file mode 100644 index 6559add858..0000000000 --- a/modules/fbx/data/fbx_anim_container.h +++ /dev/null @@ -1,46 +0,0 @@ -/*************************************************************************/ -/* fbx_anim_container.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_ANIM_CONTAINER_H -#define FBX_ANIM_CONTAINER_H - -#include "core/math/vector3.h" - -// Generic keyframes 99.99 percent of files will be vector3, except if quat interp is used, or visibility tracks -// FBXTrack is used in a map in the implementation in fbx/editor_scene_importer_fbx.cpp -// to avoid having to rewrite the entire logic I refactored this into the code instead. -// once it works I can rewrite so we can add the fun misc features / small features -struct FBXTrack { - bool has_default = false; - Vector3 default_value; - std::map<int64_t, Vector3> keyframes; -}; - -#endif //MODEL_ABSTRACTION_ANIM_CONTAINER_H diff --git a/modules/fbx/data/fbx_bone.cpp b/modules/fbx/data/fbx_bone.cpp deleted file mode 100644 index 72aba20fd4..0000000000 --- a/modules/fbx/data/fbx_bone.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/*************************************************************************/ -/* fbx_bone.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_bone.h" - -#include "fbx_node.h" -#include "import_state.h" - -Ref<FBXNode> FBXSkinDeformer::get_link(const ImportState &state) const { - print_verbose("bone name: " + bone->bone_name); - - // safe for when deformers must be polyfilled when skin has different count of binds to bones in the scene ;) - if (!cluster) { - return nullptr; - } - - ERR_FAIL_COND_V_MSG(cluster->TargetNode() == nullptr, nullptr, "bone has invalid target node"); - - Ref<FBXNode> link_node; - uint64_t id = cluster->TargetNode()->ID(); - if (state.fbx_target_map.has(id)) { - link_node = state.fbx_target_map[id]; - } else { - print_error("link node not found for " + itos(id)); - } - - // the node in space this is for, like if it's FOR a target. - return link_node; -} diff --git a/modules/fbx/data/fbx_bone.h b/modules/fbx/data/fbx_bone.h deleted file mode 100644 index 6c8f7f7cae..0000000000 --- a/modules/fbx/data/fbx_bone.h +++ /dev/null @@ -1,90 +0,0 @@ -/*************************************************************************/ -/* fbx_bone.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_BONE_H -#define FBX_BONE_H - -#include "fbx_node.h" -#include "import_state.h" - -#include "fbx_parser/FBXDocument.h" - -struct PivotTransform; - -struct FBXBone : public RefCounted { - uint64_t parent_bone_id = 0; - uint64_t bone_id = 0; - - bool valid_parent = false; // if the parent bone id is set up. - String bone_name = String(); // bone name - - bool is_root_bone() const { - return !valid_parent; - } - - // Godot specific data - int godot_bone_id = -2; // godot internal bone id assigned after import - - // if a bone / armature is the root then FBX skeleton will contain the bone not any other skeleton. - // this is to support joints by themselves in scenes - bool valid_armature_id = false; - uint64_t armature_id = 0; - - /* link node is the parent bone */ - mutable const FBXDocParser::Geometry *geometry = nullptr; - mutable const FBXDocParser::ModelLimbNode *limb_node = nullptr; - - void set_node(Ref<FBXNode> p_node) { - node = p_node; - } - - // Stores the pivot xform for this bone - - Ref<FBXNode> node = nullptr; - Ref<FBXBone> parent_bone = nullptr; - Ref<FBXSkeleton> fbx_skeleton = nullptr; -}; - -struct FBXSkinDeformer { - FBXSkinDeformer(Ref<FBXBone> p_bone, const FBXDocParser::Cluster *p_cluster) : - cluster(p_cluster), bone(p_bone) {} - ~FBXSkinDeformer() {} - const FBXDocParser::Cluster *cluster; - Ref<FBXBone> bone; - - /* get associate model - the model can be invalid sometimes */ - Ref<FBXBone> get_associate_model() const { - return bone->parent_bone; - } - - Ref<FBXNode> get_link(const ImportState &state) const; -}; - -#endif // FBX_BONE_H diff --git a/modules/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp deleted file mode 100644 index 36e20df3a9..0000000000 --- a/modules/fbx/data/fbx_material.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/*************************************************************************/ -/* fbx_material.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_material.h" - -// FIXME: Shouldn't depend on core_bind.h! Use DirAccessRef like the rest of -// the engine instead of core_bind::Directory. -#include "core/core_bind.h" -#include "scene/resources/material.h" -#include "scene/resources/texture.h" -#include "tools/validation_tools.h" - -String FBXMaterial::get_material_name() const { - return material_name; -} - -void FBXMaterial::set_imported_material(FBXDocParser::Material *p_material) { - material = p_material; -} - -void FBXMaterial::add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths) { - if (search_directory.is_empty()) { - texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(p_filename)); - } else { - texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(search_directory + "/" + p_filename)); - texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file("../" + search_directory + "/" + p_filename)); - } -} - -String find_file(const String &p_base, const String &p_file_to_find) { - core_bind::Directory dir; - dir.open(p_base); - - dir.list_dir_begin(); - String n = dir.get_next(); - while (!n.is_empty()) { - if (n == "." || n == "..") { - n = dir.get_next(); - continue; - } - if (dir.current_is_dir()) { - // Don't use `path_to` or the returned path will be wrong. - const String f = find_file(p_base + "/" + n, p_file_to_find); - if (!f.is_empty()) { - return f; - } - } else if (n == p_file_to_find) { - return p_base + "/" + n; - } - n = dir.get_next(); - } - dir.list_dir_end(); - - return String(); -} - -// fbx will not give us good path information and let's not regex them to fix them -// no relative paths are in fbx generally they have a rel field but it's populated incorrectly by the SDK. -String FBXMaterial::find_texture_path_by_filename(const String p_filename, const String p_current_directory) { - core_bind::Directory dir; - Vector<String> paths; - add_search_string(p_filename, p_current_directory, "", paths); - add_search_string(p_filename, p_current_directory, "texture", paths); - add_search_string(p_filename, p_current_directory, "textures", paths); - add_search_string(p_filename, p_current_directory, "Textures", paths); - add_search_string(p_filename, p_current_directory, "materials", paths); - add_search_string(p_filename, p_current_directory, "mats", paths); - add_search_string(p_filename, p_current_directory, "pictures", paths); - add_search_string(p_filename, p_current_directory, "images", paths); - - for (int i = 0; i < paths.size(); i++) { - if (dir.file_exists(paths[i])) { - return paths[i]; - } - } - - // We were not able to find the texture in the common locations, - // try to find it into the project globally. - // The common textures can be stored into one of those folders: - // res://asset - // res://texture - // res://material - // res://mat - // res://image - // res://picture - // - // Note the folders can also be called with custom names, like: - // res://my_assets - // since the keyword `asset` is into the directory name the textures will be - // searched there too. - - dir.open("res://"); - dir.list_dir_begin(); - String n = dir.get_next(); - while (!n.is_empty()) { - if (n == "." || n == "..") { - n = dir.get_next(); - continue; - } - if (dir.current_is_dir()) { - const String lower_n = n.to_lower(); - if ( - // Don't need to use plural. - lower_n.find("asset") >= 0 || - lower_n.find("texture") >= 0 || - lower_n.find("material") >= 0 || - lower_n.find("mat") >= 0 || - lower_n.find("image") >= 0 || - lower_n.find("picture") >= 0) { - // Don't use `path_to` or the returned path will be wrong. - const String f = find_file(String("res://") + n, p_filename); - if (!f.is_empty()) { - return f; - } - } - } - n = dir.get_next(); - } - dir.list_dir_end(); - - return ""; -} - -template <class T> -T extract_from_prop(FBXDocParser::PropertyPtr prop, const T &p_default, const std::string &p_name, const String &p_type) { - ERR_FAIL_COND_V_MSG(prop == nullptr, p_default, "invalid property passed to extractor"); - const FBXDocParser::TypedProperty<T> *val = dynamic_cast<const FBXDocParser::TypedProperty<T> *>(prop); - - ERR_FAIL_COND_V_MSG(val == nullptr, p_default, "The FBX is corrupted, the property `" + String(p_name.c_str()) + "` is a `" + String(typeid(*prop).name()) + "` but should be a " + p_type); - // Make sure to not lost any eventual opacity. - return val->Value(); -} - -Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) { - ERR_FAIL_COND_V(material == nullptr, nullptr); - - const String p_fbx_current_directory = state.path; - - Ref<StandardMaterial3D> spatial_material; - spatial_material.instantiate(); - - // read the material file - // is material two sided - // read material name - print_verbose("[material] material name: " + ImportUtils::FBXNodeToName(material->Name())); - - material_name = ImportUtils::FBXNodeToName(material->Name()); - - for (const std::pair<std::string, const FBXDocParser::Texture *> iter : material->Textures()) { - const uint64_t texture_id = iter.second->ID(); - const std::string &fbx_mapping_name = iter.first; - const FBXDocParser::Texture *fbx_texture_data = iter.second; - const String absolute_texture_path = iter.second->FileName().c_str(); - const String texture_name = absolute_texture_path.get_file(); - const String file_extension = absolute_texture_path.get_extension().to_upper(); - - const String debug_string = "texture id: " + itos(texture_id) + " texture name: " + String(iter.second->Name().c_str()) + " mapping name: " + String(fbx_mapping_name.c_str()); - // remember errors STILL need this string at the end for when you aren't in verbose debug mode :) they need context for when you're not verbose-ing. - print_verbose(debug_string); - - const String file_extension_uppercase = file_extension.to_upper(); - - if (fbx_transparency_flags.count(fbx_mapping_name) > 0) { - // just enable it later let's make this fine-tuned. - spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); - } - - ERR_CONTINUE_MSG(file_extension.is_empty(), "your texture has no file extension so we had to ignore it, let us know if you think this is wrong file an issue on github! " + debug_string); - ERR_CONTINUE_MSG(fbx_texture_map.count(fbx_mapping_name) <= 0, "This material has a texture with mapping name: " + String(fbx_mapping_name.c_str()) + " which is not yet supported by this importer. Consider opening an issue so we can support it."); - ERR_CONTINUE_MSG( - file_extension_uppercase != "PNG" && - file_extension_uppercase != "JPEG" && - file_extension_uppercase != "JPG" && - file_extension_uppercase != "TGA" && - file_extension_uppercase != "WEBP" && - file_extension_uppercase != "DDS", - "The FBX file contains a texture with an unrecognized extension: " + file_extension_uppercase); - - print_verbose("Getting FBX mapping mode for " + String(fbx_mapping_name.c_str())); - // get the texture map type - const StandardMaterial3D::TextureParam mapping_mode = fbx_texture_map.at(fbx_mapping_name); - print_verbose("Set FBX mapping mode to " + get_texture_param_name(mapping_mode)); - - Ref<Texture> texture; - print_verbose("texture mapping name: " + texture_name); - - if (state.cached_image_searches.has(texture_name)) { - texture = state.cached_image_searches[texture_name]; - } else { - String path = find_texture_path_by_filename(texture_name, p_fbx_current_directory); - if (!path.is_empty()) { - Ref<Texture2D> image_texture = ResourceLoader::load(path); - - ERR_CONTINUE(image_texture.is_null()); - - texture = image_texture; - state.cached_image_searches.insert(texture_name, texture); - print_verbose("Created texture from loaded image file."); - - } else if (fbx_texture_data != nullptr && fbx_texture_data->Media() != nullptr && fbx_texture_data->Media()->IsEmbedded()) { - // This is an embedded texture. Extract it. - Ref<Image> image; - //image.instantiate(); // oooo double instance bug? why make Image::_png_blah call - - const String extension = texture_name.get_extension().to_upper(); - if (extension == "PNG") { - // The stored file is a PNG. - image = Image::_png_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); - ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded PNG image load fail."); - - } else if ( - extension == "JPEG" || - extension == "JPG") { - // The stored file is a JPEG. - image = Image::_jpg_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); - ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded JPEG image load fail."); - - } else if (extension == "TGA") { - // The stored file is a TGA. - image = Image::_tga_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); - ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded TGA image load fail."); - - } else if (extension == "WEBP") { - // The stored file is a WEBP. - image = Image::_webp_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength()); - ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded WEBP image load fail."); - - // } else if (extension == "DDS") { - // // In this moment is not possible to extract a DDS from a buffer, TODO consider add it to godot. See `textureloader_dds.cpp::load(). - // // The stored file is a DDS. - } else { - ERR_CONTINUE_MSG(true, "The embedded image with extension: " + extension + " is not yet supported. Open an issue please."); - } - - Ref<ImageTexture> image_texture; - image_texture.instantiate(); - image_texture->create_from_image(image); - - texture = image_texture; - - // TODO: this is potentially making something with the same name have a match incorrectly USE FBX ID as Hash. #fuck it later. - state.cached_image_searches[texture_name] = texture; - print_verbose("Created texture from embedded image."); - } else { - ERR_CONTINUE_MSG(true, "The FBX texture, with name: `" + texture_name + "`, is not found into the project nor is stored as embedded file. Make sure to insert the texture as embedded file or into the project, then reimport."); - } - } - - spatial_material->set_texture(mapping_mode, texture); - } - - if (spatial_material.is_valid()) { - spatial_material->set_name(material_name); - } - - /// ALL below is related to properties - for (FBXDocParser::LazyPropertyMap::value_type iter : material->GetLazyProperties()) { - const std::string name = iter.first; - - if (name.empty()) { - continue; - } - - PropertyDesc desc = PROPERTY_DESC_NOT_FOUND; - if (fbx_properties_desc.count(name) > 0) { - desc = fbx_properties_desc.at(name); - } - - // check if we can ignore this it will be done at the next phase - if (desc == PROPERTY_DESC_NOT_FOUND || desc == PROPERTY_DESC_IGNORE) { - // count the texture mapping references. Skip this one if it's found and we can't look up a property value. - if (fbx_texture_map.count(name) > 0) { - continue; // safe to ignore it's a texture mapping. - } - } - - if (desc == PROPERTY_DESC_IGNORE) { - //WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored."); - continue; - } else { - print_verbose("FBX Material parameter: " + String(name.c_str())); - - // Check for Diffuse material system / lambert materials / legacy basically - if (name == "Diffuse" && !warning_non_pbr_material) { - ValidationTracker::get_singleton()->add_validation_error(state.path, "Invalid material settings change to Ai Standard Surface shader, mat name: " + material_name.c_escape()); - warning_non_pbr_material = true; - } - } - - // DISABLE when adding support for all weird and wonderful material formats - if (desc == PROPERTY_DESC_NOT_FOUND) { - continue; - } - - ERR_CONTINUE_MSG(desc == PROPERTY_DESC_NOT_FOUND, "The FBX material parameter: `" + String(name.c_str()) + "` was not recognized. Please open an issue so we can add the support to it."); - - const FBXDocParser::PropertyTable *tbl = material; - FBXDocParser::PropertyPtr prop = tbl->Get(name); - - ERR_CONTINUE_MSG(prop == nullptr, "This file may be corrupted because is not possible to extract the material parameter: " + String(name.c_str())); - - if (spatial_material.is_null()) { - // Done here so if no data no material is created. - spatial_material.instantiate(); - } - - const FBXDocParser::TypedProperty<real_t> *real_value = dynamic_cast<const FBXDocParser::TypedProperty<real_t> *>(prop); - const FBXDocParser::TypedProperty<Vector3> *vector_value = dynamic_cast<const FBXDocParser::TypedProperty<Vector3> *>(prop); - - if (!real_value && !vector_value) { - //WARN_PRINT("unsupported datatype in property: " + String(name.c_str())); - continue; - } - - if (vector_value && !real_value) { - if (vector_value->Value() == Vector3(0, 0, 0) && !real_value) { - continue; - } - } - - switch (desc) { - case PROPERTY_DESC_ALBEDO_COLOR: { - if (vector_value) { - const Vector3 &color = vector_value->Value(); - // Make sure to not lost any eventual opacity. - if (color != Vector3(0, 0, 0)) { - Color c = Color(); - c[0] = color[0]; - c[1] = color[1]; - c[2] = color[2]; - spatial_material->set_albedo(c); - } - - } else if (real_value) { - print_error("albedo is unsupported format?"); - } - } break; - case PROPERTY_DESC_TRANSPARENT: { - if (real_value) { - const real_t opacity = real_value->Value(); - if (opacity < (1.0 - CMP_EPSILON)) { - Color c = spatial_material->get_albedo(); - c.a = opacity; - spatial_material->set_albedo(c); - - spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); - spatial_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_OPAQUE_ONLY); - } - } else if (vector_value) { - print_error("unsupported transparent desc type vector!"); - } - } break; - case PROPERTY_DESC_SPECULAR: { - if (real_value) { - print_verbose("specular real value: " + rtos(real_value->Value())); - spatial_material->set_specular(MIN(1.0, real_value->Value())); - } - - if (vector_value) { - print_error("unsupported specular vector value: " + vector_value->Value()); - } - } break; - - case PROPERTY_DESC_SPECULAR_COLOR: { - if (vector_value) { - print_error("unsupported specular color: " + vector_value->Value()); - } - } break; - case PROPERTY_DESC_SHINYNESS: { - if (real_value) { - print_error("unsupported shinyness:" + rtos(real_value->Value())); - } - } break; - case PROPERTY_DESC_METALLIC: { - if (real_value) { - print_verbose("metallic real value: " + rtos(real_value->Value())); - spatial_material->set_metallic(MIN(1.0f, real_value->Value())); - } else { - print_error("unsupported value type for metallic"); - } - } break; - case PROPERTY_DESC_ROUGHNESS: { - if (real_value) { - print_verbose("roughness real value: " + rtos(real_value->Value())); - spatial_material->set_roughness(MIN(1.0f, real_value->Value())); - } else { - print_error("unsupported value type for roughness"); - } - } break; - case PROPERTY_DESC_COAT: { - if (real_value) { - print_verbose("clearcoat real value: " + rtos(real_value->Value())); - spatial_material->set_clearcoat(MIN(1.0f, real_value->Value())); - } else { - print_error("unsupported value type for clearcoat"); - } - } break; - case PROPERTY_DESC_COAT_ROUGHNESS: { - // meaning is that approx equal to zero is disabled not actually zero. ;) - if (real_value && Math::is_zero_approx(real_value->Value())) { - print_verbose("clearcoat real value: " + rtos(real_value->Value())); - spatial_material->set_clearcoat_roughness(real_value->Value()); - } else { - print_error("unsupported value type for clearcoat gloss"); - } - } break; - case PROPERTY_DESC_EMISSIVE: { - if (real_value && Math::is_zero_approx(real_value->Value())) { - print_verbose("Emissive real value: " + rtos(real_value->Value())); - spatial_material->set_emission_energy(real_value->Value()); - } else if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { - const Vector3 &color = vector_value->Value(); - Color c; - c[0] = color[0]; - c[1] = color[1]; - c[2] = color[2]; - spatial_material->set_emission(c); - } - } break; - case PROPERTY_DESC_EMISSIVE_COLOR: { - if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { - const Vector3 &color = vector_value->Value(); - Color c; - c[0] = color[0]; - c[1] = color[1]; - c[2] = color[2]; - spatial_material->set_emission(c); - } else { - print_error("unsupported value type for emissive color"); - } - } break; - case PROPERTY_DESC_NOT_FOUND: - case PROPERTY_DESC_IGNORE: - break; - default: - break; - } - } - - return spatial_material; -} diff --git a/modules/fbx/data/fbx_material.h b/modules/fbx/data/fbx_material.h deleted file mode 100644 index e20b43561b..0000000000 --- a/modules/fbx/data/fbx_material.h +++ /dev/null @@ -1,285 +0,0 @@ -/*************************************************************************/ -/* fbx_material.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_MATERIAL_H -#define FBX_MATERIAL_H - -#include "tools/import_utils.h" - -#include "core/object/ref_counted.h" -#include "core/string/ustring.h" - -struct FBXMaterial : public RefCounted { - String material_name = String(); - bool warning_non_pbr_material = false; - FBXDocParser::Material *material = nullptr; - - /* Godot materials - *** Texture Maps: - * Albedo - color, texture - * Metallic - specular, metallic, texture - * Roughness - roughness, texture - * Emission - color, texture - * Normal Map - scale, texture - * Ambient Occlusion - texture - * Refraction - scale, texture - *** Has Settings for: - * UV1 - SCALE, OFFSET - * UV2 - SCALE, OFFSET - *** Flags for - * Transparent - * Cull Mode - */ - - enum class MapMode { - AlbedoM = 0, - MetallicM, - SpecularM, - EmissionM, - RoughnessM, - NormalM, - AmbientOcclusionM, - RefractionM, - ReflectionM, - }; - - /* Returns the string representation of the TextureParam enum */ - static String get_texture_param_name(StandardMaterial3D::TextureParam param) { - switch (param) { - case StandardMaterial3D::TEXTURE_ALBEDO: - return "TEXTURE_ALBEDO"; - case StandardMaterial3D::TEXTURE_METALLIC: - return "TEXTURE_METALLIC"; - case StandardMaterial3D::TEXTURE_ROUGHNESS: - return "TEXTURE_ROUGHNESS"; - case StandardMaterial3D::TEXTURE_EMISSION: - return "TEXTURE_EMISSION"; - case StandardMaterial3D::TEXTURE_NORMAL: - return "TEXTURE_NORMAL"; - case StandardMaterial3D::TEXTURE_RIM: - return "TEXTURE_RIM"; - case StandardMaterial3D::TEXTURE_CLEARCOAT: - return "TEXTURE_CLEARCOAT"; - case StandardMaterial3D::TEXTURE_FLOWMAP: - return "TEXTURE_FLOWMAP"; - case StandardMaterial3D::TEXTURE_AMBIENT_OCCLUSION: - return "TEXTURE_AMBIENT_OCCLUSION"; - // case StandardMaterial3D::TEXTURE_DEPTH: // TODO: work out how to make this function again! - // return "TEXTURE_DEPTH"; - case StandardMaterial3D::TEXTURE_SUBSURFACE_SCATTERING: - return "TEXTURE_SUBSURFACE_SCATTERING"; - // case StandardMaterial3D::TEXTURE_TRANSMISSION: // TODO: work out how to make this function again! - // return "TEXTURE_TRANSMISSION"; - case StandardMaterial3D::TEXTURE_REFRACTION: - return "TEXTURE_REFRACTION"; - case StandardMaterial3D::TEXTURE_DETAIL_MASK: - return "TEXTURE_DETAIL_MASK"; - case StandardMaterial3D::TEXTURE_DETAIL_ALBEDO: - return "TEXTURE_DETAIL_ALBEDO"; - case StandardMaterial3D::TEXTURE_DETAIL_NORMAL: - return "TEXTURE_DETAIL_NORMAL"; - case StandardMaterial3D::TEXTURE_MAX: - return "TEXTURE_MAX"; - default: - return "broken horribly"; - } - }; - - // TODO make this static? - const std::map<std::string, bool> fbx_transparency_flags = { - /* Transparent */ - { "TransparentColor", true }, - { "Maya|opacity", true } - }; - - // TODO make this static? - const std::map<std::string, StandardMaterial3D::TextureParam> fbx_texture_map = { - /* Diffuse */ - { "Maya|base", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "DiffuseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "Maya|DiffuseTexture", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "Maya|baseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "Maya|baseColor|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "3dsMax|Parameters|base_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "Maya|TEX_color_map|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - { "Maya|TEX_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO }, - /* Emission */ - { "EmissiveColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "EmissiveFactor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "Maya|emissionColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "Maya|emissionColor|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "3dsMax|Parameters|emission_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "Maya|TEX_emissive_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - { "Maya|TEX_emissive_map|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION }, - /* Metallic */ - { "Maya|metalness", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - { "Maya|metalness|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - { "3dsMax|Parameters|metalness_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - { "Maya|TEX_metallic_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - { "Maya|TEX_metallic_map|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - - /* Roughness */ - // Arnold Roughness Map - { "Maya|specularRoughness", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, - - { "3dsMax|Parameters|roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, - { "Maya|TEX_roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, - { "Maya|TEX_roughness_map|file", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS }, - - /* Normal */ - { "NormalMap", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, - //{ "Bump", Material::TextureParam::TEXTURE_NORMAL }, - //{ "3dsMax|Parameters|bump_map", Material::TextureParam::TEXTURE_NORMAL }, - { "Maya|NormalTexture", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, - //{ "Maya|normalCamera", Material::TextureParam::TEXTURE_NORMAL }, - //{ "Maya|normalCamera|file", Material::TextureParam::TEXTURE_NORMAL }, - { "Maya|TEX_normal_map", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, - { "Maya|TEX_normal_map|file", StandardMaterial3D::TextureParam::TEXTURE_NORMAL }, - /* AO */ - { "Maya|TEX_ao_map", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION }, - { "Maya|TEX_ao_map|file", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION }, - - // TODO: specular workflow conversion - // { "SpecularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - // { "Maya|specularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - // { "Maya|SpecularTexture", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - // { "Maya|SpecularTexture|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC }, - // { "ShininessExponent", SpatialMaterial::TextureParam::UNSUPPORTED }, - // { "ReflectionFactor", SpatialMaterial::TextureParam::UNSUPPORTED }, - - //{ "TransparentColor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA }, - //{ "TransparencyFactor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA } - - // TODO: diffuse roughness - //{ "Maya|diffuseRoughness", SpatialMaterial::TextureParam::UNSUPPORTED }, - //{ "Maya|diffuseRoughness|file", SpatialMaterial::TextureParam::UNSUPPORTED }, - - }; - - // TODO make this static? - enum PropertyDesc { - PROPERTY_DESC_NOT_FOUND, - PROPERTY_DESC_ALBEDO_COLOR, - PROPERTY_DESC_TRANSPARENT, - PROPERTY_DESC_METALLIC, - PROPERTY_DESC_ROUGHNESS, - PROPERTY_DESC_SPECULAR, - PROPERTY_DESC_SPECULAR_COLOR, - PROPERTY_DESC_SHINYNESS, - PROPERTY_DESC_COAT, - PROPERTY_DESC_COAT_ROUGHNESS, - PROPERTY_DESC_EMISSIVE, - PROPERTY_DESC_EMISSIVE_COLOR, - PROPERTY_DESC_IGNORE - }; - - const std::map<std::string, PropertyDesc> fbx_properties_desc = { - /* Albedo */ - { "DiffuseColor", PROPERTY_DESC_ALBEDO_COLOR }, - { "Maya|baseColor", PROPERTY_DESC_ALBEDO_COLOR }, - - /* Specular */ - { "Maya|specular", PROPERTY_DESC_SPECULAR }, - { "Maya|specularColor", PROPERTY_DESC_SPECULAR_COLOR }, - - /* Specular roughness - arnold roughness map */ - { "Maya|specularRoughness", PROPERTY_DESC_ROUGHNESS }, - - /* Transparent */ - { "Opacity", PROPERTY_DESC_TRANSPARENT }, - { "TransparencyFactor", PROPERTY_DESC_TRANSPARENT }, - { "Maya|opacity", PROPERTY_DESC_TRANSPARENT }, - - { "Maya|metalness", PROPERTY_DESC_METALLIC }, - { "Maya|metallic", PROPERTY_DESC_METALLIC }, - - /* Roughness */ - { "Maya|roughness", PROPERTY_DESC_ROUGHNESS }, - - /* Coat */ - //{ "Maya|coat", PROPERTY_DESC_COAT }, - - /* Coat roughness */ - //{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS }, - - /* Emissive */ - { "Maya|emission", PROPERTY_DESC_EMISSIVE }, - { "Maya|emissive", PROPERTY_DESC_EMISSIVE }, - - /* Emissive color */ - { "EmissiveColor", PROPERTY_DESC_EMISSIVE_COLOR }, - { "Maya|emissionColor", PROPERTY_DESC_EMISSIVE_COLOR }, - - /* Ignore */ - { "Shininess", PROPERTY_DESC_IGNORE }, - { "Reflectivity", PROPERTY_DESC_IGNORE }, - { "Maya|diffuseRoughness", PROPERTY_DESC_IGNORE }, - { "Maya", PROPERTY_DESC_IGNORE }, - { "Diffuse", PROPERTY_DESC_ALBEDO_COLOR }, - { "Maya|TypeId", PROPERTY_DESC_IGNORE }, - { "Ambient", PROPERTY_DESC_IGNORE }, - { "AmbientColor", PROPERTY_DESC_IGNORE }, - { "ShininessExponent", PROPERTY_DESC_IGNORE }, - { "Specular", PROPERTY_DESC_IGNORE }, - { "SpecularColor", PROPERTY_DESC_IGNORE }, - { "SpecularFactor", PROPERTY_DESC_IGNORE }, - //{ "BumpFactor", PROPERTY_DESC_IGNORE }, - { "Maya|exitToBackground", PROPERTY_DESC_IGNORE }, - { "Maya|indirectDiffuse", PROPERTY_DESC_IGNORE }, - { "Maya|indirectSpecular", PROPERTY_DESC_IGNORE }, - { "Maya|internalReflections", PROPERTY_DESC_IGNORE }, - { "DiffuseFactor", PROPERTY_DESC_IGNORE }, - { "AmbientFactor", PROPERTY_DESC_IGNORE }, - { "ReflectionColor", PROPERTY_DESC_IGNORE }, - { "Emissive", PROPERTY_DESC_IGNORE }, - { "Maya|coatColor", PROPERTY_DESC_IGNORE }, - { "Maya|coatNormal", PROPERTY_DESC_IGNORE }, - { "Maya|coatIOR", PROPERTY_DESC_IGNORE }, - }; - - /* storing the texture properties like color */ - template <class T> - struct TexturePropertyMapping : RefCounted { - StandardMaterial3D::TextureParam map_mode = StandardMaterial3D::TextureParam::TEXTURE_ALBEDO; - const T property = T(); - }; - - static void add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths); - - static String find_texture_path_by_filename(const String p_filename, const String p_current_directory); - - String get_material_name() const; - - void set_imported_material(FBXDocParser::Material *p_material); - - Ref<StandardMaterial3D> import_material(ImportState &state); -}; - -#endif // FBX_MATERIAL_H diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp deleted file mode 100644 index 1d59851778..0000000000 --- a/modules/fbx/data/fbx_mesh_data.cpp +++ /dev/null @@ -1,1446 +0,0 @@ -/*************************************************************************/ -/* fbx_mesh_data.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_mesh_data.h" - -#include "core/templates/local_vector.h" -#include "scene/resources/importer_mesh.h" -#include "scene/resources/mesh.h" -#include "scene/resources/surface_tool.h" - -#include "thirdparty/misc/polypartition.h" - -template <class T> -T collect_first(const Vector<VertexData<T>> *p_data, T p_fall_back) { - if (p_data->is_empty()) { - return p_fall_back; - } - - return (*p_data)[0].data; -} - -template <class T> -HashMap<int, T> collect_all(const Vector<VertexData<T>> *p_data, HashMap<int, T> p_fall_back) { - if (p_data->is_empty()) { - return p_fall_back; - } - - HashMap<int, T> collection; - for (int i = 0; i < p_data->size(); i += 1) { - const VertexData<T> &vd = (*p_data)[i]; - collection[vd.polygon_index] = vd.data; - } - return collection; -} - -template <class T> -T collect_average(const Vector<VertexData<T>> *p_data, T p_fall_back) { - if (p_data->is_empty()) { - return p_fall_back; - } - - T combined = (*p_data)[0].data; // Make sure the data is always correctly initialized. - print_verbose("size of data: " + itos(p_data->size())); - for (int i = 1; i < p_data->size(); i += 1) { - combined += (*p_data)[i].data; - } - combined = combined / real_t(p_data->size()); - - return combined.normalized(); -} - -HashMap<int, Vector3> collect_normal(const Vector<VertexData<Vector3>> *p_data, HashMap<int, Vector3> p_fall_back) { - if (p_data->is_empty()) { - return p_fall_back; - } - - HashMap<int, Vector3> collection; - for (int i = 0; i < p_data->size(); i += 1) { - const VertexData<Vector3> &vd = (*p_data)[i]; - collection[vd.polygon_index] = vd.data; - } - return collection; -} - -HashMap<int, Vector2> collect_uv(const Vector<VertexData<Vector2>> *p_data, HashMap<int, Vector2> p_fall_back) { - if (p_data->is_empty()) { - return p_fall_back; - } - - HashMap<int, Vector2> collection; - for (int i = 0; i < p_data->size(); i += 1) { - const VertexData<Vector2> &vd = (*p_data)[i]; - collection[vd.polygon_index] = vd.data; - } - return collection; -} - -ImporterMeshInstance3D *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression) { - mesh_geometry = p_mesh_geometry; - // todo: make this just use a uint64_t FBX ID this is a copy of our original materials unfortunately. - const std::vector<const FBXDocParser::Material *> &material_lookup = model->GetMaterials(); - - // TODO: perf hotspot on large files - // this can be a very large copy - std::vector<int> polygon_indices = mesh_geometry->get_polygon_indices(); - std::vector<Vector3> vertices = mesh_geometry->get_vertices(); - - // Phase 1. Parse all FBX data. - HashMap<int, Vector3> normals; - HashMap<int, HashMap<int, Vector3>> normals_raw = extract_per_vertex_data( - vertices.size(), - mesh_geometry->get_edge_map(), - polygon_indices, - mesh_geometry->get_normals(), - &collect_all, - HashMap<int, Vector3>()); - - HashMap<int, Vector2> uvs_0; - HashMap<int, HashMap<int, Vector2>> uvs_0_raw = extract_per_vertex_data( - vertices.size(), - mesh_geometry->get_edge_map(), - polygon_indices, - mesh_geometry->get_uv_0(), - &collect_all, - HashMap<int, Vector2>()); - - HashMap<int, Vector2> uvs_1; - HashMap<int, HashMap<int, Vector2>> uvs_1_raw = extract_per_vertex_data( - vertices.size(), - mesh_geometry->get_edge_map(), - polygon_indices, - mesh_geometry->get_uv_1(), - &collect_all, - HashMap<int, Vector2>()); - - HashMap<int, Color> colors; - HashMap<int, HashMap<int, Color>> colors_raw = extract_per_vertex_data( - vertices.size(), - mesh_geometry->get_edge_map(), - polygon_indices, - mesh_geometry->get_colors(), - &collect_all, - HashMap<int, Color>()); - - // TODO what about tangents? - // TODO what about bi-nomials? - // TODO there is other? - - HashMap<int, SurfaceId> polygon_surfaces = extract_per_polygon( - vertices.size(), - polygon_indices, - mesh_geometry->get_material_allocation_id(), - -1); - - HashMap<String, MorphVertexData> morphs; - extract_morphs(mesh_geometry, morphs); - - // TODO please add skinning. - //mesh_id = mesh_geometry->ID(); - - sanitize_vertex_weights(state); - - // Reorganize polygon vertices to correctly take into account strange - // UVs. - reorganize_vertices( - polygon_indices, - vertices, - normals, - uvs_0, - uvs_1, - colors, - morphs, - normals_raw, - colors_raw, - uvs_0_raw, - uvs_1_raw); - - const int color_count = colors.size(); - print_verbose("Vertex color count: " + itos(color_count)); - - // Make sure that from this moment on the mesh_geometry is no used anymore. - // This is a safety step, because the mesh_geometry data are no more valid - // at this point. - - const int vertex_count = vertices.size(); - - print_verbose("Vertex count: " + itos(vertex_count)); - - // The map key is the material allocator id that is also used as surface id. - HashMap<SurfaceId, SurfaceData> surfaces; - - // Phase 2. For each material create a surface tool (So a different mesh). - { - if (polygon_surfaces.is_empty()) { - // No material, just use the default one with index -1. - // Set -1 to all polygons. - const int polygon_count = count_polygons(polygon_indices); - for (int p = 0; p < polygon_count; p += 1) { - polygon_surfaces[p] = -1; - } - } - - // Create the surface now. - for (const int *polygon_id = polygon_surfaces.next(nullptr); polygon_id != nullptr; polygon_id = polygon_surfaces.next(polygon_id)) { - const int surface_id = polygon_surfaces[*polygon_id]; - if (surfaces.has(surface_id) == false) { - SurfaceData sd; - sd.surface_tool.instantiate(); - sd.surface_tool->begin(Mesh::PRIMITIVE_TRIANGLES); - - if (surface_id < 0) { - // nothing to do - } else if (surface_id < (int)material_lookup.size()) { - const FBXDocParser::Material *mat_mapping = material_lookup.at(surface_id); - const uint64_t mapping_id = mat_mapping->ID(); - if (state.cached_materials.has(mapping_id)) { - sd.material = state.cached_materials[mapping_id]; - } - } else { - WARN_PRINT("out of bounds surface detected, FBX file has corrupt material data"); - } - - surfaces.set(surface_id, sd); - } - } - } - - // Phase 3. Map the vertices relative to each surface, in this way we can - // just insert the vertices that we need per each surface. - { - PolygonId polygon_index = -1; - SurfaceId surface_id = -1; - SurfaceData *surface_data = nullptr; - - for (size_t polygon_vertex = 0; polygon_vertex < polygon_indices.size(); polygon_vertex += 1) { - if (is_start_of_polygon(polygon_indices, polygon_vertex)) { - polygon_index += 1; - ERR_FAIL_COND_V_MSG(polygon_surfaces.has(polygon_index) == false, nullptr, "The FBX file is corrupted, This surface_index is not expected."); - surface_id = polygon_surfaces[polygon_index]; - surface_data = surfaces.getptr(surface_id); - CRASH_COND(surface_data == nullptr); // Can't be null. - } - - const int vertex = get_vertex_from_polygon_vertex(polygon_indices, polygon_vertex); - - // The vertex position in the surface - // Uses a lookup table for speed with large scenes - bool has_polygon_vertex_index = surface_data->lookup_table.has(vertex); - int surface_polygon_vertex_index = -1; - - if (has_polygon_vertex_index) { - surface_polygon_vertex_index = surface_data->lookup_table[vertex]; - } else { - surface_polygon_vertex_index = surface_data->vertices_map.size(); - surface_data->lookup_table[vertex] = surface_polygon_vertex_index; - surface_data->vertices_map.push_back(vertex); - } - - surface_data->surface_polygon_vertex[polygon_index].push_back(surface_polygon_vertex_index); - } - } - - //print_verbose("[debug UV 1] UV1: " + itos(uvs_0.size())); - //print_verbose("[debug UV 2] UV2: " + itos(uvs_1.size())); - - // Phase 4. Per each surface just insert the vertices and add the indices. - for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { - SurfaceData *surface = surfaces.getptr(*surface_id); - - // Just add the vertices data. - for (unsigned int i = 0; i < surface->vertices_map.size(); i += 1) { - const Vertex vertex = surface->vertices_map[i]; - - // This must be done before add_vertex because the surface tool is - // expecting this before the st->add_vertex() call - add_vertex(state, - surface->surface_tool, - state.scale, - vertex, - vertices, - normals, - uvs_0, - uvs_1, - colors); - } - - // Triangulate the various polygons and add the indices. - for (const PolygonId *polygon_id = surface->surface_polygon_vertex.next(nullptr); polygon_id != nullptr; polygon_id = surface->surface_polygon_vertex.next(polygon_id)) { - const Vector<DataIndex> *indices = surface->surface_polygon_vertex.getptr(*polygon_id); - triangulate_polygon( - surface, - *indices, - vertices); - } - } - - // Phase 5. Compose the morphs if any. - for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { - SurfaceData *surface = surfaces.getptr(*surface_id); - - for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) { - MorphVertexData *morph_data = morphs.getptr(*morph_name); - - // As said by the docs, this is not supposed to be different than - // vertex_count. - CRASH_COND(morph_data->vertices.size() != vertex_count); - CRASH_COND(morph_data->normals.size() != vertex_count); - - Vector3 *vertices_ptr = morph_data->vertices.ptrw(); - Vector3 *normals_ptr = morph_data->normals.ptrw(); - - Ref<SurfaceTool> morph_st; - morph_st.instantiate(); - morph_st->begin(Mesh::PRIMITIVE_TRIANGLES); - - for (unsigned int vi = 0; vi < surface->vertices_map.size(); vi += 1) { - const Vertex &vertex = surface->vertices_map[vi]; - add_vertex( - state, - morph_st, - state.scale, - vertex, - vertices, - normals, - uvs_0, - uvs_1, - colors, - vertices_ptr[vertex], - normals_ptr[vertex]); - } - - if (state.is_blender_fbx) { - morph_st->generate_normals(); - } - morph_st->generate_tangents(); - surface->morphs.push_back(morph_st->commit_to_arrays()); - } - } - - // Phase 6. Compose the mesh and return it. - Ref<ImporterMesh> mesh; - mesh.instantiate(); - - // Add blend shape info. - for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) { - mesh->add_blend_shape(*morph_name); - } - - // TODO always normalized, Why? - mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); - - // Add surfaces. - for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) { - SurfaceData *surface = surfaces.getptr(*surface_id); - - if (state.is_blender_fbx) { - surface->surface_tool->generate_normals(); - } - // you can't generate them without a valid uv map. - if (uvs_0_raw.size() > 0) { - surface->surface_tool->generate_tangents(); - } - - Array mesh_array = surface->surface_tool->commit_to_arrays(); - Array blend_shapes = surface->morphs; - - // Enforce blend shape mask array format - for (int i = 0; i < blend_shapes.size(); i++) { - Array bsdata = blend_shapes[i]; - - for (int j = 0; j < Mesh::ARRAY_MAX; j++) { - if (!(Mesh::ARRAY_FORMAT_BLEND_SHAPE_MASK & (1 << j))) { - bsdata[j] = Variant(); - } - } - } - - if (surface->material.is_valid()) { - mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes, Dictionary(), surface->material, surface->material->get_name()); - } else { - mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes); - } - } - - ImporterMeshInstance3D *godot_mesh = memnew(ImporterMeshInstance3D); - godot_mesh->set_mesh(mesh); - const String name = ImportUtils::FBXNodeToName(model->Name()); - godot_mesh->set_name(name); // hurry up compiling >.< - mesh->set_name("mesh3d-" + name); - return godot_mesh; -} - -void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { - const int max_vertex_influence_count = RS::ARRAY_WEIGHTS_SIZE; - Map<int, int> skeleton_to_skin_bind_id; - // TODO: error's need added - const FBXDocParser::Skin *fbx_skin = mesh_geometry->DeformerSkin(); - - if (fbx_skin == nullptr || fbx_skin->Clusters().size() == 0) { - return; // do nothing - } - - // - // Precalculate the skin cluster mapping - // - - int bind_id = 0; - for (const FBXDocParser::Cluster *cluster : fbx_skin->Clusters()) { - ERR_CONTINUE_MSG(!state.fbx_bone_map.has(cluster->TargetNode()->ID()), "Missing bone map for cluster target node with id " + uitos(cluster->TargetNode()->ID()) + "."); - Ref<FBXBone> bone = state.fbx_bone_map[cluster->TargetNode()->ID()]; - skeleton_to_skin_bind_id.insert(bone->godot_bone_id, bind_id); - bind_id++; - } - - for (const Vertex *v = vertex_weights.next(nullptr); v != nullptr; v = vertex_weights.next(v)) { - VertexWeightMapping *vm = vertex_weights.getptr(*v); - ERR_CONTINUE(vm->bones.size() != vm->weights.size()); // No message, already checked. - ERR_CONTINUE(vm->bones_ref.size() != vm->weights.size()); // No message, already checked. - - const int initial_size = vm->weights.size(); - { - // Init bone id - int *bones_ptr = vm->bones.ptrw(); - Ref<FBXBone> *bones_ref_ptr = vm->bones_ref.ptrw(); - - for (int i = 0; i < vm->weights.size(); i += 1) { - // At this point this is not possible because the skeleton is already initialized. - CRASH_COND(bones_ref_ptr[i]->godot_bone_id == -2); - bones_ptr[i] = skeleton_to_skin_bind_id[bones_ref_ptr[i]->godot_bone_id]; - } - - // From this point on the data is no more valid. - vm->bones_ref.clear(); - } - - { - // Sort - float *weights_ptr = vm->weights.ptrw(); - int *bones_ptr = vm->bones.ptrw(); - for (int i = 0; i < vm->weights.size(); i += 1) { - for (int x = i + 1; x < vm->weights.size(); x += 1) { - if (weights_ptr[i] < weights_ptr[x]) { - SWAP(weights_ptr[i], weights_ptr[x]); - SWAP(bones_ptr[i], bones_ptr[x]); - } - } - } - } - - { - // Resize - vm->weights.resize(max_vertex_influence_count); - vm->bones.resize(max_vertex_influence_count); - float *weights_ptr = vm->weights.ptrw(); - int *bones_ptr = vm->bones.ptrw(); - for (int i = initial_size; i < max_vertex_influence_count; i += 1) { - weights_ptr[i] = 0.0; - bones_ptr[i] = 0; - } - - // Normalize - real_t sum = 0.0; - for (int i = 0; i < max_vertex_influence_count; i += 1) { - sum += weights_ptr[i]; - } - if (sum > 0.0) { - for (int i = 0; i < vm->weights.size(); i += 1) { - weights_ptr[i] = weights_ptr[i] / sum; - } - } - } - } -} - -void FBXMeshData::reorganize_vertices( - // TODO: perf hotspot on insane files - std::vector<int> &r_polygon_indices, - std::vector<Vector3> &r_vertices, - HashMap<int, Vector3> &r_normals, - HashMap<int, Vector2> &r_uv_1, - HashMap<int, Vector2> &r_uv_2, - HashMap<int, Color> &r_color, - HashMap<String, MorphVertexData> &r_morphs, - HashMap<int, HashMap<int, Vector3>> &r_normals_raw, - HashMap<int, HashMap<int, Color>> &r_colors_raw, - HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw, - HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw) { - // Key: OldVertex; Value: [New vertices]; - HashMap<int, Vector<int>> duplicated_vertices; - - PolygonId polygon_index = -1; - for (int pv = 0; pv < (int)r_polygon_indices.size(); pv += 1) { - if (is_start_of_polygon(r_polygon_indices, pv)) { - polygon_index += 1; - } - const Vertex index = get_vertex_from_polygon_vertex(r_polygon_indices, pv); - - bool need_duplication = false; - Vector2 this_vert_poly_uv1 = Vector2(); - Vector2 this_vert_poly_uv2 = Vector2(); - Vector3 this_vert_poly_normal = Vector3(); - Color this_vert_poly_color = Color(); - - // Take the normal and see if we need to duplicate this polygon. - if (r_normals_raw.has(index)) { - const HashMap<PolygonId, Vector3> *nrml_arr = r_normals_raw.getptr(index); - - if (nrml_arr->has(polygon_index)) { - this_vert_poly_normal = nrml_arr->get(polygon_index); - } else if (nrml_arr->has(-1)) { - this_vert_poly_normal = nrml_arr->get(-1); - } else { - print_error("invalid normal detected: " + itos(index) + " polygon index: " + itos(polygon_index)); - for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { - print_verbose("debug contents key: " + itos(*pid)); - - if (nrml_arr->has(*pid)) { - print_verbose("contents valid: " + nrml_arr->get(*pid)); - } - } - } - - // Now, check if we need to duplicate it. - for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { - if (*pid == polygon_index) { - continue; - } - - const Vector3 vert_poly_normal = *nrml_arr->getptr(*pid); - if (!vert_poly_normal.is_equal_approx(this_vert_poly_normal)) { - // Yes this polygon need duplication. - need_duplication = true; - break; - } - } - } - - // TODO: make me vertex color - // Take the normal and see if we need to duplicate this polygon. - if (r_colors_raw.has(index)) { - const HashMap<PolygonId, Color> *color_arr = r_colors_raw.getptr(index); - - if (color_arr->has(polygon_index)) { - this_vert_poly_color = color_arr->get(polygon_index); - } else if (color_arr->has(-1)) { - this_vert_poly_color = color_arr->get(-1); - } else { - print_error("invalid color detected: " + itos(index) + " polygon index: " + itos(polygon_index)); - for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) { - print_verbose("debug contents key: " + itos(*pid)); - - if (color_arr->has(*pid)) { - print_verbose("contents valid: " + color_arr->get(*pid)); - } - } - } - - // Now, check if we need to duplicate it. - for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) { - if (*pid == polygon_index) { - continue; - } - - const Color vert_poly_color = *color_arr->getptr(*pid); - if (!this_vert_poly_color.is_equal_approx(vert_poly_color)) { - // Yes this polygon need duplication. - need_duplication = true; - break; - } - } - } - - // Take the UV1 and UV2 and see if we need to duplicate this polygon. - { - HashMap<int, HashMap<int, Vector2>> *uv_raw = &r_uv_1_raw; - Vector2 *this_vert_poly_uv = &this_vert_poly_uv1; - for (int kk = 0; kk < 2; kk++) { - if (uv_raw->has(index)) { - const HashMap<PolygonId, Vector2> *uvs = uv_raw->getptr(index); - - if (uvs->has(polygon_index)) { - // This Polygon has its own uv. - (*this_vert_poly_uv) = *uvs->getptr(polygon_index); - - // Check if we need to duplicate it. - for (const PolygonId *pid = uvs->next(nullptr); pid != nullptr; pid = uvs->next(pid)) { - if (*pid == polygon_index) { - continue; - } - const Vector2 vert_poly_uv = *uvs->getptr(*pid); - if (!vert_poly_uv.is_equal_approx(*this_vert_poly_uv)) { - // Yes this polygon need duplication. - need_duplication = true; - break; - } - } - } else if (uvs->has(-1)) { - // It has the default UV. - (*this_vert_poly_uv) = *uvs->getptr(-1); - } else if (uvs->size() > 0) { - // No uv, this is strange, just take the first and duplicate. - (*this_vert_poly_uv) = *uvs->getptr(*uvs->next(nullptr)); - WARN_PRINT("No UVs for this polygon, while there is no default and some other polygons have it. This FBX file may be corrupted."); - } - } - uv_raw = &r_uv_2_raw; - this_vert_poly_uv = &this_vert_poly_uv2; - } - } - - // If we want to duplicate it, Let's see if we already duplicated this - // vertex. - if (need_duplication) { - if (duplicated_vertices.has(index)) { - Vertex similar_vertex = -1; - // Let's see if one of the new vertices has the same data of this. - const Vector<int> *new_vertices = duplicated_vertices.getptr(index); - for (int j = 0; j < new_vertices->size(); j += 1) { - const Vertex new_vertex = (*new_vertices)[j]; - bool same_uv1 = false; - bool same_uv2 = false; - bool same_normal = false; - bool same_color = false; - - if (r_uv_1.has(new_vertex)) { - if ((this_vert_poly_uv1 - (*r_uv_1.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { - same_uv1 = true; - } - } - - if (r_uv_2.has(new_vertex)) { - if ((this_vert_poly_uv2 - (*r_uv_2.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { - same_uv2 = true; - } - } - - if (r_color.has(new_vertex)) { - if (this_vert_poly_color.is_equal_approx((*r_color.getptr(new_vertex)))) { - same_color = true; - } - } - - if (r_normals.has(new_vertex)) { - if ((this_vert_poly_normal - (*r_normals.getptr(new_vertex))).length_squared() <= CMP_EPSILON) { - same_uv2 = true; - } - } - - if (same_uv1 && same_uv2 && same_normal && same_color) { - similar_vertex = new_vertex; - break; - } - } - - if (similar_vertex != -1) { - // Update polygon. - if (is_end_of_polygon(r_polygon_indices, pv)) { - r_polygon_indices[pv] = ~similar_vertex; - } else { - r_polygon_indices[pv] = similar_vertex; - } - need_duplication = false; - } - } - } - - if (need_duplication) { - const Vertex old_index = index; - const Vertex new_index = r_vertices.size(); - - // Polygon index. - if (is_end_of_polygon(r_polygon_indices, pv)) { - r_polygon_indices[pv] = ~new_index; - } else { - r_polygon_indices[pv] = new_index; - } - - // Vertex position. - r_vertices.push_back(r_vertices[old_index]); - - // Normals - if (r_normals_raw.has(old_index)) { - r_normals.set(new_index, this_vert_poly_normal); - r_normals_raw.getptr(old_index)->erase(polygon_index); - r_normals_raw[new_index][polygon_index] = this_vert_poly_normal; - } - - // Vertex Color - if (r_colors_raw.has(old_index)) { - r_color.set(new_index, this_vert_poly_color); - r_colors_raw.getptr(old_index)->erase(polygon_index); - r_colors_raw[new_index][polygon_index] = this_vert_poly_color; - } - - // UV 0 - if (r_uv_1_raw.has(old_index)) { - r_uv_1.set(new_index, this_vert_poly_uv1); - r_uv_1_raw.getptr(old_index)->erase(polygon_index); - r_uv_1_raw[new_index][polygon_index] = this_vert_poly_uv1; - } - - // UV 1 - if (r_uv_2_raw.has(old_index)) { - r_uv_2.set(new_index, this_vert_poly_uv2); - r_uv_2_raw.getptr(old_index)->erase(polygon_index); - r_uv_2_raw[new_index][polygon_index] = this_vert_poly_uv2; - } - - // Morphs - for (const String *mname = r_morphs.next(nullptr); mname != nullptr; mname = r_morphs.next(mname)) { - MorphVertexData *d = r_morphs.getptr(*mname); - // This can't never happen. - CRASH_COND(d == nullptr); - if (d->vertices.size() > old_index) { - d->vertices.push_back(d->vertices[old_index]); - } - if (d->normals.size() > old_index) { - d->normals.push_back(d->normals[old_index]); - } - } - - if (vertex_weights.has(old_index)) { - vertex_weights.set(new_index, vertex_weights[old_index]); - } - - duplicated_vertices[old_index].push_back(new_index); - } else { - if (r_normals_raw.has(index) && - r_normals.has(index) == false) { - r_normals.set(index, this_vert_poly_normal); - } - - if (r_colors_raw.has(index) && r_color.has(index) == false) { - r_color.set(index, this_vert_poly_color); - } - - if (r_uv_1_raw.has(index) && - r_uv_1.has(index) == false) { - r_uv_1.set(index, this_vert_poly_uv1); - } - - if (r_uv_2_raw.has(index) && - r_uv_2.has(index) == false) { - r_uv_2.set(index, this_vert_poly_uv2); - } - } - } -} - -void FBXMeshData::add_vertex( - const ImportState &state, - Ref<SurfaceTool> p_surface_tool, - real_t p_scale, - Vertex p_vertex, - const std::vector<Vector3> &p_vertices_position, - const HashMap<int, Vector3> &p_normals, - const HashMap<int, Vector2> &p_uvs_0, - const HashMap<int, Vector2> &p_uvs_1, - const HashMap<int, Color> &p_colors, - const Vector3 &p_morph_value, - const Vector3 &p_morph_normal) { - ERR_FAIL_INDEX_MSG(p_vertex, (Vertex)p_vertices_position.size(), "FBX file is corrupted, the position of the vertex can't be retrieved."); - - if (p_normals.has(p_vertex) && !state.is_blender_fbx) { - p_surface_tool->set_normal(p_normals[p_vertex] + p_morph_normal); - } - - if (p_uvs_0.has(p_vertex)) { - //print_verbose("uv1: [" + itos(p_vertex) + "] " + p_uvs_0[p_vertex]); - // Inverts Y UV. - p_surface_tool->set_uv(Vector2(p_uvs_0[p_vertex].x, 1 - p_uvs_0[p_vertex].y)); - } - - if (p_uvs_1.has(p_vertex)) { - //print_verbose("uv2: [" + itos(p_vertex) + "] " + p_uvs_1[p_vertex]); - // Inverts Y UV. - p_surface_tool->set_uv2(Vector2(p_uvs_1[p_vertex].x, 1 - p_uvs_1[p_vertex].y)); - } - - if (p_colors.has(p_vertex)) { - p_surface_tool->set_color(p_colors[p_vertex]); - } - - // TODO what about binormals? - // TODO there is other? - - if (vertex_weights.has(p_vertex)) { - // Let's extract the weight info. - const VertexWeightMapping *vm = vertex_weights.getptr(p_vertex); - const Vector<int> &bones = vm->bones; - - // the bug is that the bone idx is wrong because it is not ref'ing the skin. - - if (bones.size() > RS::ARRAY_WEIGHTS_SIZE) { - print_error("[weight overflow detected]"); - } - - p_surface_tool->set_weights(vm->weights); - // 0 1 2 3 4 5 6 7 < local skeleton / skin for mesh - // 0 1 2 3 4 5 6 7 8 9 10 < actual skeleton with all joints - p_surface_tool->set_bones(bones); - } - - // The surface tool want the vertex position as last thing. - p_surface_tool->add_vertex((p_vertices_position[p_vertex] + p_morph_value) * p_scale); -} - -void FBXMeshData::triangulate_polygon(SurfaceData *surface, const Vector<int> &p_polygon_vertex, const std::vector<Vector3> &p_vertices) const { - Ref<SurfaceTool> st(surface->surface_tool); - const int polygon_vertex_count = p_polygon_vertex.size(); - //const Vector<Vertex>& p_surface_vertex_map - if (polygon_vertex_count == 1) { - // point to triangle - st->add_index(p_polygon_vertex[0]); - st->add_index(p_polygon_vertex[0]); - st->add_index(p_polygon_vertex[0]); - return; - } else if (polygon_vertex_count == 2) { - // line to triangle - st->add_index(p_polygon_vertex[1]); - st->add_index(p_polygon_vertex[1]); - st->add_index(p_polygon_vertex[0]); - return; - } else if (polygon_vertex_count == 3) { - // triangle to triangle - st->add_index(p_polygon_vertex[0]); - st->add_index(p_polygon_vertex[2]); - st->add_index(p_polygon_vertex[1]); - return; - } else if (polygon_vertex_count == 4) { - // quad to triangle - this code is awesome for import times - // it prevents triangles being generated slowly - st->add_index(p_polygon_vertex[0]); - st->add_index(p_polygon_vertex[2]); - st->add_index(p_polygon_vertex[1]); - st->add_index(p_polygon_vertex[2]); - st->add_index(p_polygon_vertex[0]); - st->add_index(p_polygon_vertex[3]); - return; - } else { - // non triangulated - we must run the triangulation algorithm - bool is_simple_convex = false; - // this code is 'slow' but required it triangulates all the unsupported geometry. - // Doesn't allow for bigger polygons because those are unlikely be convex - if (polygon_vertex_count <= 6) { - // Start from true, check if it's false. - is_simple_convex = true; - Vector3 first_vec; - for (int i = 0; i < polygon_vertex_count; i += 1) { - const Vector3 p1 = p_vertices[surface->vertices_map[p_polygon_vertex[i]]]; - const Vector3 p2 = p_vertices[surface->vertices_map[p_polygon_vertex[(i + 1) % polygon_vertex_count]]]; - const Vector3 p3 = p_vertices[surface->vertices_map[p_polygon_vertex[(i + 2) % polygon_vertex_count]]]; - - const Vector3 edge1 = p1 - p2; - const Vector3 edge2 = p3 - p2; - - const Vector3 res = edge1.normalized().cross(edge2.normalized()).normalized(); - if (i == 0) { - first_vec = res; - } else { - if (first_vec.dot(res) < 0.0) { - // Ok we found an angle that is not the same dir of the - // others. - is_simple_convex = false; - break; - } - } - } - } - - if (is_simple_convex) { - // This is a convex polygon, so just triangulate it. - for (int i = 0; i < (polygon_vertex_count - 2); i += 1) { - st->add_index(p_polygon_vertex[2 + i]); - st->add_index(p_polygon_vertex[1 + i]); - st->add_index(p_polygon_vertex[0]); - } - return; - } - } - - { - // This is a concave polygon. - - std::vector<Vector3> poly_vertices(polygon_vertex_count); - for (int i = 0; i < polygon_vertex_count; i += 1) { - poly_vertices[i] = p_vertices[surface->vertices_map[p_polygon_vertex[i]]]; - } - - const Vector3 poly_norm = get_poly_normal(poly_vertices); - if (poly_norm.length_squared() <= CMP_EPSILON) { - ERR_FAIL_COND_MSG(poly_norm.length_squared() <= CMP_EPSILON, "The normal of this poly was not computed. Is this FBX file corrupted."); - } - - // Select the plan coordinate. - int axis_1_coord = 0; - int axis_2_coord = 1; - { - real_t inv = poly_norm.z; - - const real_t axis_x = ABS(poly_norm.x); - const real_t axis_y = ABS(poly_norm.y); - const real_t axis_z = ABS(poly_norm.z); - - if (axis_x > axis_y) { - if (axis_x > axis_z) { - // For the most part the normal point toward X. - axis_1_coord = 1; - axis_2_coord = 2; - inv = poly_norm.x; - } - } else if (axis_y > axis_z) { - // For the most part the normal point toward Y. - axis_1_coord = 2; - axis_2_coord = 0; - inv = poly_norm.y; - } - - // Swap projection axes to take the negated projection vector into account - if (inv < 0.0f) { - SWAP(axis_1_coord, axis_2_coord); - } - } - - TPPLPoly tppl_poly; - tppl_poly.Init(polygon_vertex_count); - std::vector<Vector2> projected_vertices(polygon_vertex_count); - for (int i = 0; i < polygon_vertex_count; i += 1) { - const Vector2 pv(poly_vertices[i][axis_1_coord], poly_vertices[i][axis_2_coord]); - projected_vertices[i] = pv; - tppl_poly.GetPoint(i) = pv; - } - tppl_poly.SetOrientation(TPPL_ORIENTATION_CCW); - - List<TPPLPoly> out_poly; - - TPPLPartition tppl_partition; - if (tppl_partition.Triangulate_OPT(&tppl_poly, &out_poly) == 0) { // Good result. - if (tppl_partition.Triangulate_EC(&tppl_poly, &out_poly) == 0) { // Medium result. - if (tppl_partition.Triangulate_MONO(&tppl_poly, &out_poly) == 0) { // Really poor result. - ERR_FAIL_MSG("The triangulation of this polygon failed, please try to triangulate your mesh or check if it has broken polygons."); - } - } - } - - std::vector<Vector2> tris(out_poly.size()); - for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) { - TPPLPoly &tp = I->get(); - - ERR_FAIL_COND_MSG(tp.GetNumPoints() != 3, "The triangulator returned more points, how this is possible?"); - // Find Index - for (int i = 2; i >= 0; i -= 1) { - const Vector2 vertex = tp.GetPoint(i); - bool done = false; - // Find Index - for (int y = 0; y < polygon_vertex_count; y += 1) { - if ((projected_vertices[y] - vertex).length_squared() <= CMP_EPSILON) { - // This seems the right vertex - st->add_index(p_polygon_vertex[y]); - done = true; - break; - } - } - ERR_FAIL_COND(done == false); - } - } - } -} - -void FBXMeshData::gen_weight_info(Ref<SurfaceTool> st, Vertex vertex_id) const { - if (vertex_weights.is_empty()) { - return; - } - - if (vertex_weights.has(vertex_id)) { - // Let's extract the weight info. - const VertexWeightMapping *vm = vertex_weights.getptr(vertex_id); - st->set_weights(vm->weights); - st->set_bones(vm->bones); - } -} - -int FBXMeshData::get_vertex_from_polygon_vertex(const std::vector<int> &p_polygon_indices, int p_index) const { - if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { - return -1; - } - - const int vertex = p_polygon_indices[p_index]; - if (vertex >= 0) { - return vertex; - } else { - // Negative numbers are the end of the face, reversing the bits is - // possible to obtain the positive correct vertex number. - return ~vertex; - } -} - -bool FBXMeshData::is_end_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const { - if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { - return false; - } - - const int vertex = p_polygon_indices[p_index]; - - // If the index is negative this is the end of the Polygon. - return vertex < 0; -} - -bool FBXMeshData::is_start_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const { - if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) { - return false; - } - - if (p_index == 0) { - return true; - } - - // If the previous indices is negative this is the begin of a new Polygon. - return p_polygon_indices[p_index - 1] < 0; -} - -int FBXMeshData::count_polygons(const std::vector<int> &p_polygon_indices) const { - // The negative numbers define the end of the polygon. Counting the amount of - // negatives the numbers of polygons are obtained. - int count = 0; - for (size_t i = 0; i < p_polygon_indices.size(); i += 1) { - if (p_polygon_indices[i] < 0) { - count += 1; - } - } - return count; -} - -template <class R, class T> -HashMap<int, R> FBXMeshData::extract_per_vertex_data( - int p_vertex_count, - const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edge_map, - const std::vector<int> &p_mesh_indices, - const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data, - R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back), - R p_fall_back) const { - /* When index_to_direct is set - * index size is 184 ( contains index for the data array [values 0, 96] ) - * data size is 96 (contains uv coordinates) - * this means index is simple data reduction basically - */ - //// - if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0) { - print_verbose("debug count: index size: " + itos(p_mapping_data.index.size()) + ", data size: " + itos(p_mapping_data.data.size())); - print_verbose("vertex indices count: " + itos(p_mesh_indices.size())); - print_verbose("Edge map size: " + itos(p_edge_map.size())); - } - - ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "FBX importer needs to map correctly to this field, please specify the override index name to fix this problem!"); - ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "The FBX seems corrupted"); - - // Aggregate vertex data. - HashMap<Vertex, Vector<VertexData<T>>> aggregate_vertex_data; - - switch (p_mapping_data.map_type) { - case FBXDocParser::MeshGeometry::MapType::none: { - // No data nothing to do. - return (HashMap<int, R>()); - } - case FBXDocParser::MeshGeometry::MapType::vertex: { - ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct, (HashMap<int, R>()), "We will support in future"); - - if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { - // The data is mapped per vertex directly. - ERR_FAIL_COND_V_MSG((int)p_mapping_data.data.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR01"); - for (size_t vertex_index = 0; vertex_index < p_mapping_data.data.size(); vertex_index += 1) { - aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[vertex_index] }); - } - } else { - // The data is mapped per vertex using a reference. - // The indices array, contains a *reference_id for each vertex. - // * Note that the reference_id is the id of data into the data array. - // - // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html - ERR_FAIL_COND_V_MSG((int)p_mapping_data.index.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR02"); - for (size_t vertex_index = 0; vertex_index < p_mapping_data.index.size(); vertex_index += 1) { - ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[vertex_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR03."); - aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[p_mapping_data.index[vertex_index]] }); - } - } - } break; - case FBXDocParser::MeshGeometry::MapType::polygon_vertex: { - if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) { - // The data is mapped using each index from the indexes array then direct to the data (data reduction algorithm) - ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04"); - int polygon_id = -1; - for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) { - if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { - polygon_id += 1; - } - const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); - ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05"); - ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06"); - const int index_to_direct = get_vertex_from_polygon_vertex(p_mapping_data.index, polygon_vertex_index); - T value = p_mapping_data.data[index_to_direct]; - aggregate_vertex_data[vertex_index].push_back({ polygon_id, value }); - } - } else if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { - // The data are mapped per polygon vertex directly. - ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04"); - int polygon_id = -1; - for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.data.size(); polygon_vertex_index += 1) { - if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { - polygon_id += 1; - } - const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); - ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05"); - ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06"); - - aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[polygon_vertex_index] }); - } - } else { - // The data is mapped per polygon_vertex using a reference. - // The indices array, contains a *reference_id for each polygon_vertex. - // * Note that the reference_id is the id of data into the data array. - // - // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html - ERR_FAIL_COND_V_MSG(p_mesh_indices.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR7"); - int polygon_id = -1; - for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) { - if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { - polygon_id += 1; - } - const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); - ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR8"); - ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR9."); - ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] < 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR10."); - ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] >= (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR11."); - aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[p_mapping_data.index[polygon_vertex_index]] }); - } - } - } break; - case FBXDocParser::MeshGeometry::MapType::polygon: { - if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { - // The data are mapped per polygon directly. - const int polygon_count = count_polygons(p_mesh_indices); - ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR12"); - - // Advance each polygon vertex, each new polygon advance the polygon index. - int polygon_index = -1; - for (size_t polygon_vertex_index = 0; - polygon_vertex_index < p_mesh_indices.size(); - polygon_vertex_index += 1) { - if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { - polygon_index += 1; - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR13"); - } - - const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); - ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR14"); - - aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[polygon_index] }); - } - ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR16. Not all Polygons are present in the file."); - } else { - // The data is mapped per polygon using a reference. - // The indices array, contains a *reference_id for each polygon. - // * Note that the reference_id is the id of data into the data array. - // - // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html - const int polygon_count = count_polygons(p_mesh_indices); - ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR17"); - - // Advance each polygon vertex, each new polygon advance the polygon index. - int polygon_index = -1; - for (size_t polygon_vertex_index = 0; - polygon_vertex_index < p_mesh_indices.size(); - polygon_vertex_index += 1) { - if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) { - polygon_index += 1; - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR18"); - ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[polygon_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR19"); - } - - const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index); - ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR20"); - - aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[p_mapping_data.index[polygon_index]] }); - } - ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR22. Not all Polygons are present in the file."); - } - } break; - case FBXDocParser::MeshGeometry::MapType::edge: { - if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { - // The data are mapped per edge directly. - ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR23"); - for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) { - const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index); - ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR24"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR25"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR26"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR27"); - aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[edge_index] }); - aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[edge_index] }); - } - } else { - // The data is mapped per edge using a reference. - // The indices array, contains a *reference_id for each polygon. - // * Note that the reference_id is the id of data into the data array. - // - // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html - ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR28"); - for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) { - const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index); - ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR29"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR30"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR31"); - ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR32"); - ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_0], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR33"); - ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_1], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR34"); - aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] }); - aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] }); - } - } - } break; - case FBXDocParser::MeshGeometry::MapType::all_the_same: { - // No matter the mode, no matter the data size; The first always win - // and is set to all the vertices. - ERR_FAIL_COND_V_MSG(p_mapping_data.data.size() <= 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR35"); - if (p_mapping_data.data.size() > 0) { - for (int vertex_index = 0; vertex_index < p_vertex_count; vertex_index += 1) { - aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[0] }); - } - } - } break; - } - - if (aggregate_vertex_data.size() == 0) { - return (HashMap<int, R>()); - } - - // A map is used because turns out that the some FBX file are not well organized - // with vertices well compacted. Using a map allows avoid those issues. - HashMap<Vertex, R> result; - - // Aggregate the collected data. - for (const Vertex *index = aggregate_vertex_data.next(nullptr); index != nullptr; index = aggregate_vertex_data.next(index)) { - Vector<VertexData<T>> *aggregated_vertex = aggregate_vertex_data.getptr(*index); - // This can't be null because we are just iterating. - CRASH_COND(aggregated_vertex == nullptr); - - ERR_FAIL_INDEX_V_MSG(0, aggregated_vertex->size(), (HashMap<int, R>()), "The FBX file is corrupted, No valid data for this vertex index."); - result[*index] = collector_function(aggregated_vertex, p_fall_back); - } - - // Sanitize the data now, if the file is broken we can try import it anyway. - bool problem_found = false; - for (size_t i = 0; i < p_mesh_indices.size(); i += 1) { - const Vertex vertex = get_vertex_from_polygon_vertex(p_mesh_indices, i); - if (result.has(vertex) == false) { - result[vertex] = p_fall_back; - problem_found = true; - } - } - if (problem_found) { - WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN0."); - } - - return result; -} - -template <class T> -HashMap<int, T> FBXMeshData::extract_per_polygon( - int p_vertex_count, - const std::vector<int> &p_polygon_indices, - const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data, - T p_fallback_value) const { - ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_fbx_data.data.size() == 0, (HashMap<int, T>()), "invalid index to direct array"); - ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_fbx_data.index.size() == 0, (HashMap<int, T>()), "The FBX seems corrupted"); - - const int polygon_count = count_polygons(p_polygon_indices); - - // Aggregate vertex data. - HashMap<int, Vector<T>> aggregate_polygon_data; - - switch (p_fbx_data.map_type) { - case FBXDocParser::MeshGeometry::MapType::none: { - // No data nothing to do. - return (HashMap<int, T>()); - } - case FBXDocParser::MeshGeometry::MapType::vertex: { - ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per vertex. This should not happen."); - } break; - case FBXDocParser::MeshGeometry::MapType::polygon_vertex: { - ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per polygon vertex. This should not happen."); - } break; - case FBXDocParser::MeshGeometry::MapType::polygon: { - if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) { - // The data is stored efficiently index_to_direct allows less data in the FBX file. - for (int polygon_index = 0; - polygon_index < polygon_count; - polygon_index += 1) { - if (p_fbx_data.index.size() == 0) { - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62"); - aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]); - } else { - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62"); - - const int index_to_direct = get_vertex_from_polygon_vertex(p_fbx_data.index, polygon_index); - T value = p_fbx_data.data[index_to_direct]; - aggregate_polygon_data[polygon_index].push_back(value); - } - } - } else if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) { - // The data are mapped per polygon directly. - ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR51"); - - // Advance each polygon vertex, each new polygon advance the polygon index. - for (int polygon_index = 0; - polygon_index < polygon_count; - polygon_index += 1) { - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR52"); - aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]); - } - } else { - // The data is mapped per polygon using a reference. - // The indices array, contains a *reference_id for each polygon. - // * Note that the reference_id is the id of data into the data array. - // - // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html - ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file seems corrupted: #ERR52"); - - // Advance each polygon vertex, each new polygon advance the polygon index. - for (int polygon_index = 0; - polygon_index < polygon_count; - polygon_index += 1) { - ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR53"); - ERR_FAIL_INDEX_V_MSG(p_fbx_data.index[polygon_index], (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR54"); - aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[p_fbx_data.index[polygon_index]]); - } - } - } break; - case FBXDocParser::MeshGeometry::MapType::edge: { - ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per edge. This should not happen."); - } break; - case FBXDocParser::MeshGeometry::MapType::all_the_same: { - // No matter the mode, no matter the data size; The first always win - // and is set to all the vertices. - ERR_FAIL_COND_V_MSG(p_fbx_data.data.size() <= 0, (HashMap<int, T>()), "FBX file seems corrupted: #ERR55"); - if (p_fbx_data.data.size() > 0) { - for (int polygon_index = 0; polygon_index < polygon_count; polygon_index += 1) { - aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[0]); - } - } - } break; - } - - if (aggregate_polygon_data.size() == 0) { - return (HashMap<int, T>()); - } - - // A map is used because turns out that the some FBX file are not well organized - // with vertices well compacted. Using a map allows avoid those issues. - HashMap<int, T> polygons; - - // Take the first value for each vertex. - for (const Vertex *index = aggregate_polygon_data.next(nullptr); index != nullptr; index = aggregate_polygon_data.next(index)) { - Vector<T> *aggregated_polygon = aggregate_polygon_data.getptr(*index); - // This can't be null because we are just iterating. - CRASH_COND(aggregated_polygon == nullptr); - - ERR_FAIL_INDEX_V_MSG(0, (int)aggregated_polygon->size(), (HashMap<int, T>()), "The FBX file is corrupted, No valid data for this polygon index."); - - // Validate the final value. - polygons[*index] = (*aggregated_polygon)[0]; - } - - // Sanitize the data now, if the file is broken we can try import it anyway. - bool problem_found = false; - for (int polygon_i = 0; polygon_i < polygon_count; polygon_i += 1) { - if (polygons.has(polygon_i) == false) { - polygons[polygon_i] = p_fallback_value; - problem_found = true; - } - } - if (problem_found) { - WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN1."); - } - - return polygons; -} - -void FBXMeshData::extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data) { - r_data.clear(); - - const int vertex_count = mesh_geometry->get_vertices().size(); - - for (const FBXDocParser::BlendShape *blend_shape : mesh_geometry->get_blend_shapes()) { - for (const FBXDocParser::BlendShapeChannel *blend_shape_channel : blend_shape->BlendShapeChannels()) { - const std::vector<const FBXDocParser::ShapeGeometry *> &shape_geometries = blend_shape_channel->GetShapeGeometries(); - for (const FBXDocParser::ShapeGeometry *shape_geometry : shape_geometries) { - String morph_name = ImportUtils::FBXAnimMeshName(shape_geometry->Name()).c_str(); - if (morph_name.is_empty()) { - morph_name = "morph"; - } - - // TODO we have only these?? - const std::vector<unsigned int> &morphs_vertex_indices = shape_geometry->GetIndices(); - const std::vector<Vector3> &morphs_vertices = shape_geometry->GetVertices(); - const std::vector<Vector3> &morphs_normals = shape_geometry->GetNormals(); - - ERR_FAIL_COND_MSG((int)morphs_vertex_indices.size() > vertex_count, "The FBX file is corrupted: #ERR103"); - ERR_FAIL_COND_MSG(morphs_vertex_indices.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR104"); - ERR_FAIL_COND_MSG((int)morphs_vertices.size() > vertex_count, "The FBX file is corrupted: #ERR105"); - ERR_FAIL_COND_MSG(morphs_normals.size() != 0 && morphs_normals.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR106"); - - if (r_data.has(morph_name) == false) { - // This morph doesn't exist yet. - // Create it. - MorphVertexData md; - md.vertices.resize(vertex_count); - md.normals.resize(vertex_count); - r_data.set(morph_name, md); - } - - MorphVertexData *data = r_data.getptr(morph_name); - Vector3 *data_vertices_ptr = data->vertices.ptrw(); - Vector3 *data_normals_ptr = data->normals.ptrw(); - - for (int i = 0; i < (int)morphs_vertex_indices.size(); i += 1) { - const Vertex vertex = morphs_vertex_indices[i]; - - ERR_FAIL_INDEX_MSG(vertex, vertex_count, "The blend shapes of this FBX file are corrupted. It has a not valid vertex."); - - data_vertices_ptr[vertex] = morphs_vertices[i]; - - if (morphs_normals.size() != 0) { - data_normals_ptr[vertex] = morphs_normals[i]; - } - } - } - } - } -} diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h deleted file mode 100644 index 358d0c2cb6..0000000000 --- a/modules/fbx/data/fbx_mesh_data.h +++ /dev/null @@ -1,200 +0,0 @@ -/*************************************************************************/ -/* fbx_mesh_data.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_MESH_DATA_H -#define FBX_MESH_DATA_H - -#include "core/templates/hash_map.h" -#include "core/templates/local_vector.h" -#include "core/templates/ordered_hash_map.h" -#include "editor/import/resource_importer_scene.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/surface_tool.h" - -#include "fbx_bone.h" -#include "fbx_parser/FBXMeshGeometry.h" -#include "import_state.h" -#include "tools/import_utils.h" - -struct FBXNode; -struct FBXMeshData; -struct FBXBone; -struct ImportState; - -typedef int Vertex; -typedef int SurfaceId; -typedef int PolygonId; -typedef int DataIndex; - -struct SurfaceData { - Ref<SurfaceTool> surface_tool; - OrderedHashMap<Vertex, int> lookup_table; // proposed fix is to replace lookup_table[vertex_id] to give the position of the vertices_map[int] index. - LocalVector<Vertex> vertices_map; // this must be ordered the same as insertion <-- slow to do find() operation. - Ref<Material> material; - HashMap<PolygonId, Vector<DataIndex>> surface_polygon_vertex; - Array morphs; -}; - -struct VertexWeightMapping { - Vector<float> weights; - Vector<int> bones; - // This extra vector is used because the bone id is computed in a second step. - // TODO Get rid of this extra step is a good idea. - Vector<Ref<FBXBone>> bones_ref; -}; - -template <class T> -struct VertexData { - int polygon_index; - T data; -}; - -// Caches mesh information and instantiates meshes for you using helper functions. -struct FBXMeshData : RefCounted { - struct MorphVertexData { - // TODO we have only these?? - /// Each element is a vertex. Not supposed to be void. - Vector<Vector3> vertices; - /// Each element is a vertex. Not supposed to be void. - Vector<Vector3> normals; - }; - - // FIXME: remove this is a hack for testing only - mutable const FBXDocParser::MeshGeometry *mesh_geometry = nullptr; - - Ref<FBXNode> mesh_node = nullptr; - /// vertex id, Weight Info - /// later: perf we can use array here - HashMap<int, VertexWeightMapping> vertex_weights; - - // translate fbx mesh data from document context to FBX Mesh Geometry Context - bool valid_weight_indexes = false; - - ImporterMeshInstance3D *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression); - - void gen_weight_info(Ref<SurfaceTool> st, int vertex_id) const; - - /* mesh maximum weight count */ - bool valid_weight_count = false; - int max_weight_count = 0; - uint64_t armature_id = 0; - bool valid_armature_id = false; - ImporterMeshInstance3D *godot_mesh_instance = nullptr; - -private: - void sanitize_vertex_weights(const ImportState &state); - - /// Make sure to reorganize the vertices so that the correct UV is taken. - /// This step is needed because differently from the normal, that can be - /// combined, the UV may need its own triangle because sometimes they have - /// really different UV for the same vertex but different polygon. - /// This function make sure to add another vertex for those UVS. - void reorganize_vertices( - std::vector<int> &r_polygon_indices, - std::vector<Vector3> &r_vertices, - HashMap<int, Vector3> &r_normals, - HashMap<int, Vector2> &r_uv_1, - HashMap<int, Vector2> &r_uv_2, - HashMap<int, Color> &r_color, - HashMap<String, MorphVertexData> &r_morphs, - HashMap<int, HashMap<int, Vector3>> &r_normals_raw, - HashMap<int, HashMap<int, Color>> &r_colors_raw, - HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw, - HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw); - - void add_vertex( - const ImportState &state, - Ref<SurfaceTool> p_surface_tool, - real_t p_scale, - int p_vertex, - const std::vector<Vector3> &p_vertices_position, - const HashMap<int, Vector3> &p_normals, - const HashMap<int, Vector2> &p_uvs_0, - const HashMap<int, Vector2> &p_uvs_1, - const HashMap<int, Color> &p_colors, - const Vector3 &p_morph_value = Vector3(), - const Vector3 &p_morph_normal = Vector3()); - - void triangulate_polygon(SurfaceData *surface, const Vector<int> &p_polygon_vertex, const std::vector<Vector3> &p_vertices) const; - - /// This function is responsible to convert the FBX polygon vertex to - /// vertex index. - /// The polygon vertices are stored in an array with some negative - /// values. The negative values define the last face index. - /// For example the following `face_array` contains two faces, the former - /// with 3 vertices and the latter with a line: - /// [0,2,-2,3,-5] - /// Parsed as: - /// [0, 2, 1, 3, 4] - /// The negative values are computed using this formula: `(-value) - 1` - /// - /// Returns the vertex index from the polygon vertex. - /// Returns -1 if `p_index` is invalid. - int get_vertex_from_polygon_vertex(const std::vector<int> &p_face_indices, int p_index) const; - - /// Returns true if this polygon_vertex_index is the end of a new polygon. - bool is_end_of_polygon(const std::vector<int> &p_face_indices, int p_index) const; - - /// Returns true if this polygon_vertex_index is the begin of a new polygon. - bool is_start_of_polygon(const std::vector<int> &p_face_indices, int p_index) const; - - /// Returns the number of polygons. - int count_polygons(const std::vector<int> &p_face_indices) const; - - /// Used to extract data from the `MappingData` aligned with vertex. - /// Useful to extract normal/uvs/colors/tangents/etc... - /// If the function fails somehow, it returns an hollow vector and print an error. - template <class R, class T> - HashMap<int, R> extract_per_vertex_data( - int p_vertex_count, - const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edges, - const std::vector<int> &p_mesh_indices, - const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data, - R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back), - R p_fall_back) const; - - /// Used to extract data from the `MappingData` organized per polygon. - /// Useful to extract the material - /// If the function fails somehow, it returns an hollow vector and print an error. - template <class T> - HashMap<int, T> extract_per_polygon( - int p_vertex_count, - const std::vector<int> &p_face_indices, - const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data, - T p_fallback_value) const; - - /// Extracts the morph data and organizes it per vertices. - /// The returned `MorphVertexData` arrays are never something different - /// then the `vertex_count`. - void extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data); -}; - -#endif // FBX_MESH_DATA_H diff --git a/modules/fbx/data/fbx_node.h b/modules/fbx/data/fbx_node.h deleted file mode 100644 index 7a4139dcdf..0000000000 --- a/modules/fbx/data/fbx_node.h +++ /dev/null @@ -1,63 +0,0 @@ -/*************************************************************************/ -/* fbx_node.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_NODE_H -#define FBX_NODE_H - -#include "fbx_skeleton.h" -#include "model_abstraction.h" -#include "pivot_transform.h" - -#include "fbx_parser/FBXDocument.h" - -class Node3D; -struct PivotTransform; - -struct FBXNode : RefCounted, ModelAbstraction { - uint64_t current_node_id = 0; - String node_name = String(); - Node3D *godot_node = nullptr; - - // used to parent the skeleton once the tree is built. - Ref<FBXSkeleton> skeleton_node = Ref<FBXSkeleton>(); - - void set_parent(Ref<FBXNode> p_parent) { - fbx_parent = p_parent; - } - - void set_pivot_transform(Ref<PivotTransform> p_pivot_transform) { - pivot_transform = p_pivot_transform; - } - - Ref<PivotTransform> pivot_transform = Ref<PivotTransform>(); // local and global xform data - Ref<FBXNode> fbx_parent = Ref<FBXNode>(); // parent node -}; - -#endif // FBX_NODE_H diff --git a/modules/fbx/data/fbx_skeleton.cpp b/modules/fbx/data/fbx_skeleton.cpp deleted file mode 100644 index 0225df16af..0000000000 --- a/modules/fbx/data/fbx_skeleton.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/*************************************************************************/ -/* fbx_skeleton.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_skeleton.h" - -#include "import_state.h" - -#include "tools/import_utils.h" - -void FBXSkeleton::init_skeleton(const ImportState &state) { - int skeleton_bone_count = skeleton_bones.size(); - - if (skeleton == nullptr && skeleton_bone_count > 0) { - skeleton = memnew(Skeleton3D); - - if (fbx_node.is_valid()) { - // cache skeleton attachment for later during node creation - // can't be done until after node hierarchy is built - if (fbx_node->godot_node != state.root) { - fbx_node->skeleton_node = Ref<FBXSkeleton>(this); - print_verbose("cached armature skeleton attachment for node " + fbx_node->node_name); - } else { - // root node must never be a skeleton to prevent cyclic skeletons from being allowed (skeleton in a skeleton) - fbx_node->godot_node->add_child(skeleton); - skeleton->set_owner(state.root_owner); - skeleton->set_name("Skeleton3D"); - print_verbose("created armature skeleton for root"); - } - } else { - memfree(skeleton); - skeleton = nullptr; - print_error("[doc] skeleton has no valid node to parent nodes to - erasing"); - skeleton_bones.clear(); - return; - } - } - - // Make the bone name uniques. - for (int x = 0; x < skeleton_bone_count; x++) { - Ref<FBXBone> bone = skeleton_bones[x]; - if (bone.is_valid()) { - // Make sure the bone name is unique. - const String bone_name = bone->bone_name; - int same_name_count = 0; - for (int y = x + 1; y < skeleton_bone_count; y++) { - Ref<FBXBone> other_bone = skeleton_bones[y]; - if (other_bone.is_valid()) { - if (other_bone->bone_name == bone_name) { - same_name_count += 1; - other_bone->bone_name += "_" + itos(same_name_count); - } - } - } - } - } - - Map<int, Ref<FBXBone>> bone_map; - // implement fbx cluster skin logic here this is where it goes - int bone_count = 0; - for (int x = 0; x < skeleton_bone_count; x++) { - Ref<FBXBone> bone = skeleton_bones[x]; - if (bone.is_valid()) { - skeleton->add_bone(bone->bone_name); - bone->godot_bone_id = bone_count; - bone->fbx_skeleton = Ref<FBXSkeleton>(this); - bone_map.insert(bone_count, bone); - print_verbose("added bone " + itos(bone->bone_id) + " " + bone->bone_name); - bone_count++; - } - } - - ERR_FAIL_COND_MSG(skeleton->get_bone_count() != bone_count, "Not all bones got added, is the file corrupted?"); - - for (const KeyValue<int, Ref<FBXBone>> &bone_element : bone_map) { - const Ref<FBXBone> bone = bone_element.value; - int bone_index = bone_element.key; - print_verbose("working on bone: " + itos(bone_index) + " bone name:" + bone->bone_name); - - skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->node->pivot_transform->LocalTransform, state.scale)); - { - Transform3D base_xform = bone->node->pivot_transform->LocalTransform; - - skeleton->set_bone_pose_position(bone_index, base_xform.origin); - skeleton->set_bone_pose_rotation(bone_index, base_xform.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(bone_index, base_xform.basis.get_scale()); - } - - // lookup parent ID - if (bone->valid_parent && state.fbx_bone_map.has(bone->parent_bone_id)) { - Ref<FBXBone> parent_bone = state.fbx_bone_map[bone->parent_bone_id]; - int bone_id = skeleton->find_bone(parent_bone->bone_name); - if (bone_id != -1) { - skeleton->set_bone_parent(bone_index, bone_id); - } else { - print_error("invalid bone parent: " + parent_bone->bone_name); - } - } else { - if (bone->godot_bone_id != -1) { - skeleton->set_bone_parent(bone_index, -1); // no parent for this bone - } - } - } -} diff --git a/modules/fbx/data/fbx_skeleton.h b/modules/fbx/data/fbx_skeleton.h deleted file mode 100644 index e615d0f5e3..0000000000 --- a/modules/fbx/data/fbx_skeleton.h +++ /dev/null @@ -1,53 +0,0 @@ -/*************************************************************************/ -/* fbx_skeleton.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_SKELETON_H -#define FBX_SKELETON_H - -#include "fbx_bone.h" -#include "fbx_node.h" -#include "model_abstraction.h" - -#include "core/object/ref_counted.h" -#include "scene/3d/skeleton_3d.h" - -struct FBXNode; -struct ImportState; -struct FBXBone; - -struct FBXSkeleton : RefCounted { - Ref<FBXNode> fbx_node = Ref<FBXNode>(); - Vector<Ref<FBXBone>> skeleton_bones = Vector<Ref<FBXBone>>(); - Skeleton3D *skeleton = nullptr; - - void init_skeleton(const ImportState &state); -}; - -#endif // FBX_SKELETON_H diff --git a/modules/fbx/data/import_state.h b/modules/fbx/data/import_state.h deleted file mode 100644 index eff11e3bb8..0000000000 --- a/modules/fbx/data/import_state.h +++ /dev/null @@ -1,112 +0,0 @@ -/*************************************************************************/ -/* import_state.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 IMPORT_STATE_H -#define IMPORT_STATE_H - -#include "fbx_mesh_data.h" -#include "tools/import_utils.h" -#include "tools/validation_tools.h" - -#include "pivot_transform.h" - -#include "core/io/resource_importer.h" -#include "core/templates/vector.h" -#include "editor/import/resource_importer_scene.h" -#include "editor/project_settings_editor.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" -#include "scene/animation/animation_player.h" -#include "scene/resources/animation.h" -#include "scene/resources/surface_tool.h" - -#include "modules/fbx/fbx_parser/FBXDocument.h" -#include "modules/fbx/fbx_parser/FBXImportSettings.h" -#include "modules/fbx/fbx_parser/FBXMeshGeometry.h" -#include "modules/fbx/fbx_parser/FBXParser.h" -#include "modules/fbx/fbx_parser/FBXTokenizer.h" -#include "modules/fbx/fbx_parser/FBXUtil.h" - -struct FBXBone; -struct FBXMeshData; -struct FBXNode; -struct FBXSkeleton; - -struct ImportState { - bool enable_material_import = true; - bool enable_animation_import = true; - bool is_blender_fbx = false; - - Map<StringName, Ref<Texture>> cached_image_searches; - Map<uint64_t, Ref<Material>> cached_materials; - - String path = String(); - Node3D *root_owner = nullptr; - Node3D *root = nullptr; - real_t scale = 0.01; - Ref<FBXNode> fbx_root_node = Ref<FBXNode>(); - // skeleton map - merged automatically when they are on the same x node in the tree so we can merge them automatically. - Map<uint64_t, Ref<FBXSkeleton>> skeleton_map = Map<uint64_t, Ref<FBXSkeleton>>(); - - // nodes on the same level get merged automatically. - //Map<uint64_t, Skeleton3D *> armature_map; - AnimationPlayer *animation_player = nullptr; - - // Generation 4 - Raw document accessing for bone/skin/joint/kLocators - // joints are not necessarily bones but must be merged into the skeleton - // (bone id), bone - Map<uint64_t, Ref<FBXBone>> fbx_bone_map = Map<uint64_t, Ref<FBXBone>>(); // this is the bone name and setup information required for joints - // this will never contain joints only bones attached to a mesh. - - // Generation 4 - Raw document for creating the nodes transforms in the scene - // this is a list of the nodes in the scene - // (id, node) - List<Ref<FBXNode>> fbx_node_list = List<Ref<FBXNode>>(); - - // All nodes which have been created in the scene - // this will not contain the root node of the scene - Map<uint64_t, Ref<FBXNode>> fbx_target_map = Map<uint64_t, Ref<FBXNode>>(); - - // mesh nodes which are created in node / mesh step - used for populating skin poses in MeshSkins - Map<uint64_t, Ref<FBXNode>> MeshNodes = Map<uint64_t, Ref<FBXNode>>(); - // mesh skin map - Map<uint64_t, Ref<Skin>> MeshSkins = Map<uint64_t, Ref<Skin>>(); - - // this is the container for the mesh weight information and eventually - // any mesh data - // but not the skin, just stuff important for rendering - // skin is applied to mesh instance so not really required to be in here yet. - // maybe later - // fbx mesh id, FBXMeshData - Map<uint64_t, Ref<FBXMeshData>> renderer_mesh_data = Map<uint64_t, Ref<FBXMeshData>>(); -}; - -#endif // IMPORT_STATE_H diff --git a/modules/fbx/data/model_abstraction.h b/modules/fbx/data/model_abstraction.h deleted file mode 100644 index 4c3f81b163..0000000000 --- a/modules/fbx/data/model_abstraction.h +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* model_abstraction.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 MODEL_ABSTRACTION_H -#define MODEL_ABSTRACTION_H - -#include "modules/fbx/fbx_parser/FBXDocument.h" - -struct ModelAbstraction { - mutable const FBXDocParser::Model *fbx_model = nullptr; - - void set_model(const FBXDocParser::Model *p_model) { - fbx_model = p_model; - } - - bool has_model() const { - return fbx_model != nullptr; - } - - const FBXDocParser::Model *get_model() const { - return fbx_model; - } -}; - -#endif // MODEL_ABSTRACTION_H diff --git a/modules/fbx/data/pivot_transform.cpp b/modules/fbx/data/pivot_transform.cpp deleted file mode 100644 index 9457b19a7f..0000000000 --- a/modules/fbx/data/pivot_transform.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/*************************************************************************/ -/* pivot_transform.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "pivot_transform.h" - -#include "tools/import_utils.h" - -void PivotTransform::ReadTransformChain() { - const FBXDocParser::PropertyTable *props = fbx_model; - const FBXDocParser::Model::RotOrder &rot = fbx_model->RotationOrder(); - const FBXDocParser::TransformInheritance &inheritType = fbx_model->InheritType(); - inherit_type = inheritType; // copy the inherit type we need it in the second step. - print_verbose("Model: " + String(fbx_model->Name().c_str()) + " Has inherit type: " + itos(fbx_model->InheritType())); - bool ok = false; - raw_pre_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", ok)); - if (ok) { - pre_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_pre_rotation)); - print_verbose("valid pre_rotation: " + raw_pre_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI))); - } - raw_post_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", ok)); - if (ok) { - post_rotation = ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder_EulerXYZ, ImportUtils::deg2rad(raw_post_rotation)); - print_verbose("valid post_rotation: " + raw_post_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI))); - } - const Vector3 &RotationPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationPivot", ok)); - if (ok) { - rotation_pivot = ImportUtils::FixAxisConversions(RotationPivot); - } - const Vector3 &RotationOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationOffset", ok)); - if (ok) { - rotation_offset = ImportUtils::FixAxisConversions(RotationOffset); - } - const Vector3 &ScalingOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingOffset", ok)); - if (ok) { - scaling_offset = ImportUtils::FixAxisConversions(ScalingOffset); - } - const Vector3 &ScalingPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingPivot", ok)); - if (ok) { - scaling_pivot = ImportUtils::FixAxisConversions(ScalingPivot); - } - const Vector3 &Translation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Translation", ok)); - if (ok) { - translation = ImportUtils::FixAxisConversions(Translation); - } - raw_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Rotation", ok)); - if (ok) { - rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_rotation)); - } - const Vector3 &Scaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Scaling", ok)); - if (ok) { - scaling = Scaling; - } else { - scaling = Vector3(1, 1, 1); - } - const Vector3 &GeometricScaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricScaling", ok)); - if (ok) { - geometric_scaling = GeometricScaling; - } else { - geometric_scaling = Vector3(1, 1, 1); - } - - const Vector3 &GeometricRotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricRotation", ok)); - if (ok) { - geometric_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(GeometricRotation)); - } else { - geometric_rotation = Quaternion(); - } - - const Vector3 &GeometricTranslation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricTranslation", ok)); - if (ok) { - geometric_translation = ImportUtils::FixAxisConversions(GeometricTranslation); - } else { - geometric_translation = Vector3(0, 0, 0); - } - - if (geometric_rotation != Quaternion()) { - print_error("geometric rotation is unsupported!"); - //CRASH_COND(true); - } - - if (!geometric_scaling.is_equal_approx(Vector3(1, 1, 1))) { - print_error("geometric scaling is unsupported!"); - //CRASH_COND(true); - } - - if (!geometric_translation.is_equal_approx(Vector3(0, 0, 0))) { - print_error("geometric translation is unsupported."); - //CRASH_COND(true); - } -} - -Transform3D PivotTransform::ComputeLocalTransform(Vector3 p_translation, Quaternion p_rotation, Vector3 p_scaling) const { - Transform3D T, Roff, Rp, Soff, Sp, S; - - // Here I assume this is the operation which needs done. - // Its WorldTransform * V - - // Origin pivots - T.set_origin(p_translation); - Roff.set_origin(rotation_offset); - Rp.set_origin(rotation_pivot); - Soff.set_origin(scaling_offset); - Sp.set_origin(scaling_pivot); - - // Scaling node - S.scale(p_scaling); - // Rotation pivots - Transform3D Rpre = Transform3D(pre_rotation); - Transform3D R = Transform3D(p_rotation); - Transform3D Rpost = Transform3D(post_rotation); - - return T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); -} - -Transform3D PivotTransform::ComputeGlobalTransform(Transform3D t) const { - Vector3 pos = t.origin; - Vector3 scale = t.basis.get_scale(); - Quaternion rot = t.basis.get_rotation_quaternion(); - return ComputeGlobalTransform(pos, rot, scale); -} - -Transform3D PivotTransform::ComputeLocalTransform(Transform3D t) const { - Vector3 pos = t.origin; - Vector3 scale = t.basis.get_scale(); - Quaternion rot = t.basis.get_rotation_quaternion(); - return ComputeLocalTransform(pos, rot, scale); -} - -Transform3D PivotTransform::ComputeGlobalTransform(Vector3 p_translation, Quaternion p_rotation, Vector3 p_scaling) const { - Transform3D T, Roff, Rp, Soff, Sp, S; - - // Here I assume this is the operation which needs done. - // Its WorldTransform * V - - // Origin pivots - T.set_origin(p_translation); - Roff.set_origin(rotation_offset); - Rp.set_origin(rotation_pivot); - Soff.set_origin(scaling_offset); - Sp.set_origin(scaling_pivot); - - // Scaling node - S.scale(p_scaling); - - // Rotation pivots - Transform3D Rpre = Transform3D(pre_rotation); - Transform3D R = Transform3D(p_rotation); - Transform3D Rpost = Transform3D(post_rotation); - - Transform3D parent_global_xform; - Transform3D parent_local_scaling_m; - - if (parent_transform.is_valid()) { - parent_global_xform = parent_transform->GlobalTransform; - parent_local_scaling_m = parent_transform->Local_Scaling_Matrix; - } - - Transform3D local_rotation_m, parent_global_rotation_m; - Quaternion parent_global_rotation = parent_global_xform.basis.get_rotation_quaternion(); - parent_global_rotation_m.basis.set_quaternion(parent_global_rotation); - local_rotation_m = Rpre * R * Rpost; - - //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quaternion().normalized()); - - Transform3D local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation; - Vector3 parent_translation = parent_global_xform.get_origin(); - parent_shear_translation.origin = parent_translation; - parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform; - parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation; - local_shear_scaling = S; - - // Inherit type handler - we don't care about T here, just reordering RSrs etc. - Transform3D global_rotation_scale; - if (inherit_type == FBXDocParser::Transform_RrSs) { - global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling; - } else if (inherit_type == FBXDocParser::Transform_RSrs) { - global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling; - } else if (inherit_type == FBXDocParser::Transform_Rrs) { - Transform3D parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.affine_inverse(); - global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling; - } - Transform3D local_transform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); - //Transform3D local_translation_pivoted = Transform3D(Basis(), LocalTransform.origin); - - ERR_FAIL_COND_V_MSG(local_transform.basis.determinant() == 0, Transform3D(), "Det == 0 prevented in scene file"); - - // manual hack to force SSC not to be compensated for - until we can handle it properly with tests - return parent_global_xform * local_transform; -} - -void PivotTransform::ComputePivotTransform() { - Transform3D T, Roff, Rp, Soff, Sp, S; - - // Here I assume this is the operation which needs done. - // Its WorldTransform * V - - // Origin pivots - T.set_origin(translation); - Roff.set_origin(rotation_offset); - Rp.set_origin(rotation_pivot); - Soff.set_origin(scaling_offset); - Sp.set_origin(scaling_pivot); - - // Scaling node - if (!scaling.is_equal_approx(Vector3())) { - S.scale(scaling); - } else { - S.scale(Vector3(1, 1, 1)); - } - Local_Scaling_Matrix = S; // copy for when node / child is looking for the value of this. - - // Rotation pivots - Transform3D Rpre = Transform3D(pre_rotation); - Transform3D R = Transform3D(rotation); - Transform3D Rpost = Transform3D(post_rotation); - - Transform3D parent_global_xform; - Transform3D parent_local_scaling_m; - - if (parent_transform.is_valid()) { - parent_global_xform = parent_transform->GlobalTransform; - parent_local_scaling_m = parent_transform->Local_Scaling_Matrix; - } - - Transform3D local_rotation_m, parent_global_rotation_m; - Quaternion parent_global_rotation = parent_global_xform.basis.get_rotation_quaternion(); - parent_global_rotation_m.basis.set_quaternion(parent_global_rotation); - local_rotation_m = Rpre * R * Rpost; - - //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quaternion().normalized()); - - Transform3D local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation; - Vector3 parent_translation = parent_global_xform.get_origin(); - parent_shear_translation.origin = parent_translation; - parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform; - parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation; - local_shear_scaling = S; - - // Inherit type handler - we don't care about T here, just reordering RSrs etc. - Transform3D global_rotation_scale; - if (inherit_type == FBXDocParser::Transform_RrSs) { - global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling; - } else if (inherit_type == FBXDocParser::Transform_RSrs) { - global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling; - } else if (inherit_type == FBXDocParser::Transform_Rrs) { - Transform3D parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.inverse(); - global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling; - } - LocalTransform = Transform3D(); - LocalTransform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); - - ERR_FAIL_COND_MSG(LocalTransform.basis.determinant() == 0, "invalid scale reset"); - - Transform3D local_translation_pivoted = Transform3D(Basis(), LocalTransform.origin); - GlobalTransform = Transform3D(); - //GlobalTransform = parent_global_xform * LocalTransform; - Transform3D global_origin = Transform3D(Basis(), parent_translation); - GlobalTransform = (global_origin * local_translation_pivoted) * global_rotation_scale; - - ImportUtils::debug_xform("local xform calculation", LocalTransform); - print_verbose("scale of node: " + S.basis.get_scale_local()); - print_verbose("---------------------------------------------------------------"); -} - -void PivotTransform::Execute() { - ReadTransformChain(); - ComputePivotTransform(); - - ImportUtils::debug_xform("global xform: ", GlobalTransform); - - if (LocalTransform.basis.determinant() == 0) { - print_error("Serious det == 0!"); - } - - if (GlobalTransform.basis.determinant() == 0) { - print_error("Serious! node has det == 0!"); - } - - computed_global_xform = true; -} diff --git a/modules/fbx/data/pivot_transform.h b/modules/fbx/data/pivot_transform.h deleted file mode 100644 index df97c8db57..0000000000 --- a/modules/fbx/data/pivot_transform.h +++ /dev/null @@ -1,115 +0,0 @@ -/*************************************************************************/ -/* pivot_transform.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 PIVOT_TRANSFORM_H -#define PIVOT_TRANSFORM_H - -#include "core/math/transform_3d.h" -#include "core/object/ref_counted.h" - -#include "model_abstraction.h" - -#include "fbx_parser/FBXDocument.h" -#include "tools/import_utils.h" - -enum TransformationComp { - TransformationComp_Translation, - TransformationComp_Scaling, - TransformationComp_Rotation, - TransformationComp_RotationOffset, - TransformationComp_RotationPivot, - TransformationComp_PreRotation, - TransformationComp_PostRotation, - TransformationComp_ScalingOffset, - TransformationComp_ScalingPivot, - TransformationComp_GeometricTranslation, - TransformationComp_GeometricRotation, - TransformationComp_GeometricScaling, - TransformationComp_MAXIMUM -}; -// Abstract away pivot data so its simpler to handle -struct PivotTransform : RefCounted, ModelAbstraction { - // at the end we want to keep geometric_ everything, post and pre rotation - // these are used during animation data processing / keyframe ingestion the rest can be simplified down / out. - Quaternion pre_rotation = Quaternion(); - Quaternion post_rotation = Quaternion(); - Quaternion rotation = Quaternion(); - Quaternion geometric_rotation = Quaternion(); - Vector3 rotation_pivot = Vector3(); - Vector3 rotation_offset = Vector3(); - Vector3 scaling_offset = Vector3(1.0, 1.0, 1.0); - Vector3 scaling_pivot = Vector3(1.0, 1.0, 1.0); - Vector3 translation = Vector3(); - Vector3 scaling = Vector3(1.0, 1.0, 1.0); - Vector3 geometric_scaling = Vector3(1.0, 1.0, 1.0); - Vector3 geometric_translation = Vector3(); - - Vector3 raw_rotation = Vector3(); - Vector3 raw_post_rotation = Vector3(); - Vector3 raw_pre_rotation = Vector3(); - - /* Read pivots from the document */ - void ReadTransformChain(); - - void debug_pivot_xform(String p_name) { - print_verbose("debugging node name: " + p_name); - print_verbose("raw rotation: " + raw_rotation * (180 / Math_PI)); - print_verbose("raw pre_rotation " + raw_pre_rotation * (180 / Math_PI)); - print_verbose("raw post_rotation " + raw_post_rotation * (180 / Math_PI)); - } - - Transform3D ComputeGlobalTransform(Transform3D t) const; - Transform3D ComputeLocalTransform(Transform3D t) const; - Transform3D ComputeGlobalTransform(Vector3 p_translation, Quaternion p_rotation, Vector3 p_scaling) const; - Transform3D ComputeLocalTransform(Vector3 p_translation, Quaternion p_rotation, Vector3 p_scaling) const; - - /* Extract into xforms and calculate once */ - void ComputePivotTransform(); - - /* Execute the command for the pivot generation */ - void Execute(); - - void set_parent(Ref<PivotTransform> p_parent) { - parent_transform = p_parent; - } - - bool computed_global_xform = false; - Ref<PivotTransform> parent_transform = Ref<PivotTransform>(); - //Transform chain[TransformationComp_MAXIMUM]; - - // cached for later use - Transform3D GlobalTransform = Transform3D(); - Transform3D LocalTransform = Transform3D(); - Transform3D Local_Scaling_Matrix = Transform3D(); // used for inherit type. - Transform3D GeometricTransform = Transform3D(); // 3DS max only - FBXDocParser::TransformInheritance inherit_type = FBXDocParser::TransformInheritance_MAX; // maya fbx requires this - sorry <3 -}; - -#endif // PIVOT_TRANSFORM_H diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp deleted file mode 100644 index afaeb15708..0000000000 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ /dev/null @@ -1,1463 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_fbx.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_fbx.h" - -#include "data/fbx_anim_container.h" -#include "data/fbx_material.h" -#include "data/fbx_mesh_data.h" -#include "data/fbx_skeleton.h" -#include "tools/import_utils.h" - -#include "core/io/image_loader.h" -#include "editor/editor_log.h" -#include "editor/editor_node.h" -#include "editor/import/resource_importer_scene.h" -#include "scene/3d/bone_attachment_3d.h" -#include "scene/3d/camera_3d.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/light_3d.h" -#include "scene/main/node.h" -#include "scene/resources/material.h" - -#include "fbx_parser/FBXDocument.h" -#include "fbx_parser/FBXImportSettings.h" -#include "fbx_parser/FBXMeshGeometry.h" -#include "fbx_parser/FBXParser.h" -#include "fbx_parser/FBXProperties.h" -#include "fbx_parser/FBXTokenizer.h" - -#include <string> - -void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) const { - // register FBX as the one and only format for FBX importing - if (GLOBAL_GET("filesystem/import/fbx/use_fbx")) { - r_extensions->push_back("fbx"); - } -} - -uint32_t EditorSceneFormatImporterFBX::get_import_flags() const { - return IMPORT_SCENE; -} - -Node3D *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps, - List<String> *r_missing_deps, Error *r_err) { - // done for performance when re-importing lots of files when testing importer in verbose only! - if (OS::get_singleton()->is_stdout_verbose()) { - EditorLog *log = EditorNode::get_log(); - log->clear(); - } - Error err; - FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); - - ERR_FAIL_COND_V(!f, nullptr); - - { - PackedByteArray data; - // broadphase tokenizing pass in which we identify the core - // syntax elements of FBX (brackets, commas, key:value mappings) - FBXDocParser::TokenList tokens; - - bool is_binary = false; - data.resize(f->get_length()); - - ERR_FAIL_COND_V(data.size() < 64, nullptr); - - f->get_buffer(data.ptrw(), data.size()); - PackedByteArray fbx_header; - fbx_header.resize(64); - for (int32_t byte_i = 0; byte_i < 64; byte_i++) { - fbx_header.ptrw()[byte_i] = data.ptr()[byte_i]; - } - - String fbx_header_string; - if (fbx_header.size() >= 0) { - fbx_header_string.parse_utf8((const char *)fbx_header.ptr(), fbx_header.size()); - } - - print_verbose("[doc] opening fbx file: " + p_path); - print_verbose("[doc] fbx header: " + fbx_header_string); - bool corrupt = false; - - // safer to check this way as there can be different formatted headers - if (fbx_header_string.contains("Kaydara FBX Binary")) { - is_binary = true; - print_verbose("[doc] is binary"); - - FBXDocParser::TokenizeBinary(tokens, (const char *)data.ptrw(), (size_t)data.size(), corrupt); - - } else { - print_verbose("[doc] is ascii"); - FBXDocParser::Tokenize(tokens, (const char *)data.ptrw(), (size_t)data.size(), corrupt); - } - - if (corrupt) { - for (FBXDocParser::TokenPtr token : tokens) { - delete token; - } - tokens.clear(); - ERR_PRINT(vformat("Cannot import FBX file: %s the file is corrupt so we safely exited parsing the file.", p_path)); - return memnew(Node3D); - } - - // The import process explained: - // 1. Tokens are made, these are then taken into the 'parser' below - // 2. The parser constructs 'Elements' and all 'real' FBX Types. - // 3. This creates a problem: shared_ptr ownership, should Elements later 'take ownership' - // 4. No, it shouldn't so we should either a.) use weak ref for elements; but this is not correct. - - // use this information to construct a very rudimentary - // parse-tree representing the FBX scope structure - FBXDocParser::Parser parser(tokens, is_binary); - - if (parser.IsCorrupt()) { - for (FBXDocParser::TokenPtr token : tokens) { - delete token; - } - tokens.clear(); - ERR_PRINT(vformat("Cannot import FBX file: %s the file is corrupt so we safely exited parsing the file.", p_path)); - return memnew(Node3D); - } - - FBXDocParser::ImportSettings settings; - settings.strictMode = false; - - // this function leaks a lot - FBXDocParser::Document doc(parser, settings); - - // yeah so closing the file is a good idea (prevents readonly states) - f->close(); - - // safety for version handling - if (doc.IsSafeToImport()) { - bool is_blender_fbx = false; - const FBXDocParser::PropertyTable &import_props = doc.GetMetadataProperties(); - const FBXDocParser::PropertyPtr app_name = import_props.Get("Original|ApplicationName"); - const FBXDocParser::PropertyPtr app_vendor = import_props.Get("Original|ApplicationVendor"); - const FBXDocParser::PropertyPtr app_version = import_props.Get("Original|ApplicationVersion"); - // - if (app_name) { - const FBXDocParser::TypedProperty<std::string> *app_name_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_name); - if (app_name_string) { - print_verbose("FBX App Name: " + String(app_name_string->Value().c_str())); - } - } - - if (app_vendor) { - const FBXDocParser::TypedProperty<std::string> *app_vendor_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_vendor); - if (app_vendor_string) { - print_verbose("FBX App Vendor: " + String(app_vendor_string->Value().c_str())); - is_blender_fbx = app_vendor_string->Value().find("Blender") != std::string::npos; - } - } - - if (app_version) { - const FBXDocParser::TypedProperty<std::string> *app_version_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_version); - if (app_version_string) { - print_verbose("FBX App Version: " + String(app_version_string->Value().c_str())); - } - } - - if (is_blender_fbx) { - WARN_PRINT("We don't officially support Blender FBX animations yet, due to issues with upstream Blender,\n" - "so please wait for us to work around remaining issues. We will continue to import the file but it may be broken.\n" - "For minimal breakage, please export FBX from Blender with -Z forward, and Y up."); - } - - Node3D *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8, is_blender_fbx); - // todo: move to document shutdown (will need to be validated after moving; this code has been validated already) - for (FBXDocParser::TokenPtr token : tokens) { - if (token) { - delete token; - token = nullptr; - } - } - - return spatial; - - } else { - for (FBXDocParser::TokenPtr token : tokens) { - delete token; - } - tokens.clear(); - - ERR_PRINT(vformat("Cannot import FBX file: %s. It uses file format %d which is unsupported by Godot. Please re-export it or convert it to a newer format.", p_path, doc.FBXVersion())); - } - } - - return memnew(Node3D); -} - -template <class T> -struct EditorSceneFormatImporterAssetImportInterpolate { - T lerp(const T &a, const T &b, float c) const { - return a + (b - a) * c; - } - - T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) { - const float t2 = t * t; - const float t3 = t2 * t; - - return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); - } - - T bezier(T start, T control_1, T control_2, T end, float t) { - /* Formula from Wikipedia article on Bezier curves. */ - const real_t omt = (1.0 - t); - const real_t omt2 = omt * omt; - const real_t omt3 = omt2 * omt; - const real_t t2 = t * t; - const real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; - } -}; - -//thank you for existing, partial specialization -template <> -struct EditorSceneFormatImporterAssetImportInterpolate<Quaternion> { - Quaternion lerp(const Quaternion &a, const Quaternion &b, float c) const { - ERR_FAIL_COND_V(!a.is_normalized(), Quaternion()); - ERR_FAIL_COND_V(!b.is_normalized(), Quaternion()); - - return a.slerp(b, c).normalized(); - } - - Quaternion catmull_rom(const Quaternion &p0, const Quaternion &p1, const Quaternion &p2, const Quaternion &p3, float c) { - ERR_FAIL_COND_V(!p1.is_normalized(), Quaternion()); - ERR_FAIL_COND_V(!p2.is_normalized(), Quaternion()); - - return p1.slerp(p2, c).normalized(); - } - - Quaternion bezier(Quaternion start, Quaternion control_1, Quaternion control_2, Quaternion end, float t) { - ERR_FAIL_COND_V(!start.is_normalized(), Quaternion()); - ERR_FAIL_COND_V(!end.is_normalized(), Quaternion()); - - return start.slerp(end, t).normalized(); - } -}; - -template <class T> -T EditorSceneFormatImporterFBX::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, - AssetImportAnimation::Interpolation p_interp) { - //could use binary search, worth it? - int idx = -1; - for (int i = 0; i < p_times.size(); i++) { - if (p_times[i] > p_time) { - break; - } - idx++; - } - - EditorSceneFormatImporterAssetImportInterpolate<T> interp; - - switch (p_interp) { - case AssetImportAnimation::INTERP_LINEAR: { - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.lerp(p_values[idx], p_values[idx + 1], c); - - } break; - case AssetImportAnimation::INTERP_STEP: { - if (idx == -1) { - return p_values[0]; - } else if (idx >= p_times.size() - 1) { - return p_values[p_times.size() - 1]; - } - - return p_values[idx]; - - } break; - case AssetImportAnimation::INTERP_CATMULLROMSPLINE: { - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[1 + p_times.size() - 1]; - } - - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c); - - } break; - case AssetImportAnimation::INTERP_CUBIC_SPLINE: { - if (idx == -1) { - return p_values[1]; - } else if (idx >= p_times.size() - 1) { - return p_values[(p_times.size() - 1) * 3 + 1]; - } - - float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - - T from = p_values[idx * 3 + 1]; - T c1 = from + p_values[idx * 3 + 2]; - T to = p_values[idx * 3 + 4]; - T c2 = to + p_values[idx * 3 + 3]; - - return interp.bezier(from, c1, c2, to, c); - - } break; - } - - ERR_FAIL_V(p_values[0]); -} - -Node3D *EditorSceneFormatImporterFBX::_generate_scene( - const String &p_path, - const FBXDocParser::Document *p_document, - const uint32_t p_flags, - int p_bake_fps, - const int32_t p_max_bone_weights, - bool p_is_blender_fbx) { - ImportState state; - state.is_blender_fbx = p_is_blender_fbx; - state.path = p_path; - state.animation_player = nullptr; - - // create new root node for scene - Node3D *scene_root = memnew(Node3D); - state.root = memnew(Node3D); - state.root_owner = scene_root; // the real scene root... sorry compatibility code is painful... - - state.root->set_name("RootNode"); - scene_root->add_child(state.root); - state.root->set_owner(scene_root); - - state.fbx_root_node.instantiate(); - state.fbx_root_node->godot_node = state.root; - - // Size relative to cm. - const real_t fbx_unit_scale = p_document->GlobalSettingsPtr()->UnitScaleFactor(); - - print_verbose("FBX unit scale import value: " + rtos(fbx_unit_scale)); - // Set FBX file scale is relative to CM must be converted to M - state.scale = fbx_unit_scale / 100.0; - print_verbose("FBX unit scale is: " + rtos(state.scale)); - - // Enabled by default. - state.enable_material_import = true; - // Enabled by default. - state.enable_animation_import = true; - Ref<FBXNode> root_node; - root_node.instantiate(); - - // make sure fake noFBXDocParser::PropertyPtr ptrde always has a transform too ;) - Ref<PivotTransform> pivot_transform; - pivot_transform.instantiate(); - root_node->pivot_transform = pivot_transform; - root_node->node_name = "root node"; - root_node->current_node_id = 0; - root_node->godot_node = state.root; - - // cache this node onto the fbx_target map. - state.fbx_target_map.insert(0, root_node); - - // cache basic node information from FBX document - // grabs all FBX bones - BuildDocumentBones(Ref<FBXBone>(), state, p_document, 0L); - BuildDocumentNodes(Ref<PivotTransform>(), state, p_document, 0L, nullptr); - - // Build document skinning information - - // Algorithm is this: - // Get Deformer: object with "Skin" class. - // Deformer:: has link to Geometry:: (correct mesh for skin) - // Deformer:: has Source which is the SubDeformer:: (e.g. the Cluster) - // Notes at the end it configures the vertex weight mapping. - - for (uint64_t skin_id : p_document->GetSkinIDs()) { - // Validate the parser - FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id); - ERR_CONTINUE_MSG(lazy_skin == nullptr, "invalid lazy object [serious parser bug]"); - - // Validate the parser - const FBXDocParser::Skin *skin = lazy_skin->Get<FBXDocParser::Skin>(); - ERR_CONTINUE_MSG(skin == nullptr, "invalid skin added to skin list [parser bug]"); - - const std::vector<const FBXDocParser::Connection *> source_to_destination = p_document->GetConnectionsBySourceSequenced(skin_id); - FBXDocParser::MeshGeometry *mesh = nullptr; - uint64_t mesh_id = 0; - - // Most likely only contains the mesh link for the skin - // The mesh geometry. - for (const FBXDocParser::Connection *con : source_to_destination) { - // do something - print_verbose("src: " + itos(con->src)); - FBXDocParser::Object *ob = con->DestinationObject(); - mesh = dynamic_cast<FBXDocParser::MeshGeometry *>(ob); - - if (mesh) { - mesh_id = mesh->ID(); - break; - } - } - - // Validate the mesh exists and was retrieved - ERR_CONTINUE_MSG(mesh_id == 0, "mesh id is invalid"); - const std::vector<const FBXDocParser::Cluster *> clusters = skin->Clusters(); - - // NOTE: this will ONLY work on skinned bones (it is by design.) - // A cluster is a skinned bone so SKINS won't contain unskinned bones so we need to pre-add all bones and parent them in a step beforehand. - for (const FBXDocParser::Cluster *cluster : clusters) { - ERR_CONTINUE_MSG(cluster == nullptr, "invalid bone cluster"); - const uint64_t deformer_id = cluster->ID(); - std::vector<const FBXDocParser::Connection *> connections = p_document->GetConnectionsByDestinationSequenced(deformer_id); - - // Weight data always has a node in the scene lets grab the limb's node in the scene :) (reverse set to true since it's the opposite way around) - const FBXDocParser::ModelLimbNode *limb_node = ProcessDOMConnection<FBXDocParser::ModelLimbNode>(p_document, deformer_id, true); - - ERR_CONTINUE_MSG(limb_node == nullptr, "unable to resolve model for skinned bone"); - - const uint64_t model_id = limb_node->ID(); - - // This will never happen, so if it does you know you fucked up. - ERR_CONTINUE_MSG(!state.fbx_bone_map.has(model_id), "missing LimbNode detected"); - - // new bone instance - Ref<FBXBone> bone_element = state.fbx_bone_map[model_id]; - - // - // Bone Weight Information Configuration - // - - // Cache Weight Information into bone for later usage if you want the raw data. - const std::vector<unsigned int> &indexes = cluster->GetIndices(); - const std::vector<float> &weights = cluster->GetWeights(); - Ref<FBXMeshData> mesh_vertex_data; - - // this data will pre-exist if vertex weight information is found - if (state.renderer_mesh_data.has(mesh_id)) { - mesh_vertex_data = state.renderer_mesh_data[mesh_id]; - } else { - mesh_vertex_data.instantiate(); - state.renderer_mesh_data.insert(mesh_id, mesh_vertex_data); - } - - mesh_vertex_data->armature_id = bone_element->armature_id; - mesh_vertex_data->valid_armature_id = true; - - //print_verbose("storing mesh vertex data for mesh to use later"); - ERR_CONTINUE_MSG(indexes.size() != weights.size(), "[doc] error mismatch between weight info"); - - for (size_t idx = 0; idx < indexes.size(); idx++) { - const size_t vertex_index = indexes[idx]; - const real_t influence_weight = weights[idx]; - - VertexWeightMapping &vm = mesh_vertex_data->vertex_weights[vertex_index]; - vm.weights.push_back(influence_weight); - vm.bones.push_back(0); // bone id is pushed on here during sanitization phase - vm.bones_ref.push_back(bone_element); - } - - for (const int *vertex_index = mesh_vertex_data->vertex_weights.next(nullptr); - vertex_index != nullptr; - vertex_index = mesh_vertex_data->vertex_weights.next(vertex_index)) { - VertexWeightMapping *vm = mesh_vertex_data->vertex_weights.getptr(*vertex_index); - const int influence_count = vm->weights.size(); - if (influence_count > mesh_vertex_data->max_weight_count) { - mesh_vertex_data->max_weight_count = influence_count; - mesh_vertex_data->valid_weight_count = true; - } - } - - if (mesh_vertex_data->max_weight_count > 4) { - if (mesh_vertex_data->max_weight_count > 8) { - ERR_PRINT("[doc] Serious: maximum bone influences is 8 in this branch."); - } - // Clamp to 8 bone vertex influences. - mesh_vertex_data->max_weight_count = 8; - print_verbose("[doc] Using 8 vertex bone influences configuration."); - } else { - mesh_vertex_data->max_weight_count = 4; - print_verbose("[doc] Using 4 vertex bone influences configuration."); - } - } - } - - // do we globally allow for import of materials - // (prevents overwrite of materials; so you can handle them explicitly) - if (state.enable_material_import) { - const std::vector<uint64_t> &materials = p_document->GetMaterialIDs(); - - for (uint64_t material_id : materials) { - FBXDocParser::LazyObject *lazy_material = p_document->GetObject(material_id); - FBXDocParser::Material *mat = (FBXDocParser::Material *)lazy_material->Get<FBXDocParser::Material>(); - ERR_CONTINUE_MSG(!mat, "Could not convert fbx material by id: " + itos(material_id)); - - Ref<FBXMaterial> material; - material.instantiate(); - material->set_imported_material(mat); - - Ref<StandardMaterial3D> godot_material = material->import_material(state); - - state.cached_materials.insert(material_id, godot_material); - } - } - - // build skin and skeleton information - print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size())); - - // Importing bones using document based method from FBX directly - // We do not use the assimp bone format to determine this information anymore. - if (state.fbx_bone_map.size() > 0) { - // We are using a single skeleton only method here - // this is because we really have no concept of skeletons in FBX - // their are bones in a scene but they have no specific armature - // we can detect armatures but the issue lies in the complexity - // we opted to merge the entire scene onto one skeleton for now - // if we need to change this we have an archive of the old code. - - // bind pose normally only has 1 per mesh but can have more than one - // this is the point of skins - // in FBX first bind pose is the master for the first skin - - // In order to handle the FBX skeleton we must also inverse any parent transforms on the bones - // just to rule out any parent node transforms in the bone data - // this is trivial to do and allows us to use the single skeleton method and merge them - // this means that the nodes from maya kLocators will be preserved as bones - // in the same rig without having to match this across skeletons and merge by detection - // we can just merge and undo any parent transforms - for (KeyValue<uint64_t, Ref<FBXBone>> &bone_element : state.fbx_bone_map) { - Ref<FBXBone> bone = bone_element.value; - Ref<FBXSkeleton> fbx_skeleton_inst; - - uint64_t armature_id = bone->armature_id; - if (state.skeleton_map.has(armature_id)) { - fbx_skeleton_inst = state.skeleton_map[armature_id]; - } else { - fbx_skeleton_inst.instantiate(); - state.skeleton_map.insert(armature_id, fbx_skeleton_inst); - } - - print_verbose("populating skeleton with bone: " + bone->bone_name); - - //// populate bone skeleton - since fbx has no DOM for the skeleton just a node. - //bone->bone_skeleton = fbx_skeleton_inst; - - // now populate bone on the armature node list - fbx_skeleton_inst->skeleton_bones.push_back(bone); - - CRASH_COND_MSG(!state.fbx_target_map.has(armature_id), "invalid armature [serious]"); - - Ref<FBXNode> node = state.fbx_target_map[armature_id]; - - CRASH_COND_MSG(node.is_null(), "invalid node [serious]"); - CRASH_COND_MSG(node->pivot_transform.is_null(), "invalid pivot transform [serious]"); - fbx_skeleton_inst->fbx_node = node; - - ERR_CONTINUE_MSG(fbx_skeleton_inst->fbx_node.is_null(), "invalid skeleton node [serious]"); - - // we need to have a valid armature id and the model configured for the bone to be assigned fully. - // happens once per skeleton - - if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { - print_verbose("allocated fbx skeleton primary / armature node for the level: " + fbx_skeleton_inst->fbx_node->node_name); - } else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { - print_error("bones are not mapped to an armature node for armature id: " + itos(armature_id) + " bone: " + bone->bone_name); - // this means bone will be removed and not used, which is safe actually and no skeleton will be created. - } - } - - // setup skeleton instances if required :) - for (KeyValue<uint64_t, Ref<FBXSkeleton>> &skeleton_node : state.skeleton_map) { - Ref<FBXSkeleton> &skeleton = skeleton_node.value; - skeleton->init_skeleton(state); - - ERR_CONTINUE_MSG(skeleton->fbx_node.is_null(), "invalid fbx target map, missing skeleton"); - } - - // This list is not populated - for (Map<uint64_t, Ref<FBXNode>>::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) { - } - } - - // build godot node tree - if (state.fbx_node_list.size() > 0) { - for (List<Ref<FBXNode>>::Element *node_element = state.fbx_node_list.front(); - node_element; - node_element = node_element->next()) { - Ref<FBXNode> fbx_node = node_element->get(); - ImporterMeshInstance3D *mesh_node = nullptr; - Ref<FBXMeshData> mesh_data_precached; - - // check for valid geometry - if (fbx_node->fbx_model == nullptr) { - print_error("[doc] fundamental flaw, submit bug immediately with full import log with verbose logging on"); - } else { - const std::vector<const FBXDocParser::Geometry *> &geometry = fbx_node->fbx_model->GetGeometry(); - for (const FBXDocParser::Geometry *mesh : geometry) { - print_verbose("[doc] [" + itos(mesh->ID()) + "] mesh: " + fbx_node->node_name); - - if (mesh == nullptr) { - continue; - } - - const FBXDocParser::MeshGeometry *mesh_geometry = dynamic_cast<const FBXDocParser::MeshGeometry *>(mesh); - if (mesh_geometry) { - uint64_t mesh_id = mesh_geometry->ID(); - - // this data will pre-exist if vertex weight information is found - if (state.renderer_mesh_data.has(mesh_id)) { - mesh_data_precached = state.renderer_mesh_data[mesh_id]; - } else { - mesh_data_precached.instantiate(); - state.renderer_mesh_data.insert(mesh_id, mesh_data_precached); - } - - mesh_data_precached->mesh_node = fbx_node; - - // mesh node, mesh id - mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, false); - if (!state.MeshNodes.has(mesh_id)) { - state.MeshNodes.insert(mesh_id, fbx_node); - } - } - - const FBXDocParser::ShapeGeometry *shape_geometry = dynamic_cast<const FBXDocParser::ShapeGeometry *>(mesh); - if (shape_geometry != nullptr) { - print_verbose("[doc] valid shape geometry converted"); - } - } - } - - Ref<FBXSkeleton> node_skeleton = fbx_node->skeleton_node; - - if (node_skeleton.is_valid()) { - Skeleton3D *skel = node_skeleton->skeleton; - fbx_node->godot_node = skel; - } else if (mesh_node == nullptr) { - fbx_node->godot_node = memnew(Node3D); - } else { - fbx_node->godot_node = mesh_node; - } - - fbx_node->godot_node->set_name(fbx_node->node_name); - - // assign parent if valid - if (fbx_node->fbx_parent.is_valid()) { - fbx_node->fbx_parent->godot_node->add_child(fbx_node->godot_node); - fbx_node->godot_node->set_owner(state.root_owner); - } - - // Node Transform debug, set local xform data. - fbx_node->godot_node->set_transform(get_unscaled_transform(fbx_node->pivot_transform->LocalTransform, state.scale)); - - // populate our mesh node reference - if (mesh_node != nullptr && mesh_data_precached.is_valid()) { - mesh_data_precached->godot_mesh_instance = mesh_node; - } - } - } - - for (KeyValue<uint64_t, Ref<FBXMeshData>> &mesh_data : state.renderer_mesh_data) { - const uint64_t mesh_id = mesh_data.key; - Ref<FBXMeshData> mesh = mesh_data.value; - - const FBXDocParser::MeshGeometry *mesh_geometry = p_document->GetObject(mesh_id)->Get<FBXDocParser::MeshGeometry>(); - - ERR_CONTINUE_MSG(mesh->mesh_node.is_null(), "invalid mesh allocation"); - - const FBXDocParser::Skin *mesh_skin = mesh_geometry->DeformerSkin(); - - if (!mesh_skin) { - continue; // safe to continue - } - - // - // Skin bone configuration - // - - // - // Get Mesh Node Xform only - // - //ERR_CONTINUE_MSG(!state.fbx_target_map.has(mesh_id), "invalid xform for the skin pose: " + itos(mesh_id)); - //Ref<FBXNode> mesh_node_xform_data = state.fbx_target_map[mesh_id]; - - if (!mesh_skin) { - continue; // not a deformer. - } - - if (mesh_skin->Clusters().size() == 0) { - continue; // possibly buggy mesh - } - - // Lookup skin or create it if it's not found. - Ref<Skin> skin; - if (!state.MeshSkins.has(mesh_id)) { - print_verbose("Created new skin"); - skin.instantiate(); - state.MeshSkins.insert(mesh_id, skin); - } else { - print_verbose("Grabbed skin"); - skin = state.MeshSkins[mesh_id]; - } - - for (const FBXDocParser::Cluster *cluster : mesh_skin->Clusters()) { - // node or bone this cluster targets (in theory will only be a bone target) - uint64_t skin_target_id = cluster->TargetNode()->ID(); - - print_verbose("adding cluster [" + itos(cluster->ID()) + "] " + String(cluster->Name().c_str()) + " for target: [" + itos(skin_target_id) + "] " + String(cluster->TargetNode()->Name().c_str())); - ERR_CONTINUE_MSG(!state.fbx_bone_map.has(skin_target_id), "no bone found by that ID? locator"); - - const Ref<FBXBone> bone = state.fbx_bone_map[skin_target_id]; - const Ref<FBXSkeleton> skeleton = bone->fbx_skeleton; - const Ref<FBXNode> skeleton_node = skeleton->fbx_node; - - skin->add_named_bind( - bone->bone_name, - get_unscaled_transform( - skeleton_node->pivot_transform->GlobalTransform.affine_inverse() * cluster->TransformLink().affine_inverse(), state.scale)); - } - - print_verbose("cluster name / id: " + String(mesh_skin->Name().c_str()) + " [" + itos(mesh_skin->ID()) + "]"); - print_verbose("skeleton has " + itos(state.fbx_bone_map.size()) + " binds"); - print_verbose("fbx skin has " + itos(mesh_skin->Clusters().size()) + " binds"); - } - - // mesh data iteration for populating skeleton mapping - for (KeyValue<uint64_t, Ref<FBXMeshData>> &mesh_data : state.renderer_mesh_data) { - Ref<FBXMeshData> mesh = mesh_data.value; - const uint64_t mesh_id = mesh_data.key; - ImporterMeshInstance3D *mesh_instance = mesh->godot_mesh_instance; - const int mesh_weights = mesh->max_weight_count; - Ref<FBXSkeleton> skeleton; - const bool valid_armature = mesh->valid_armature_id; - const uint64_t armature = mesh->armature_id; - - if (mesh_weights > 0) { - // this is a bug, it means the weights were found but the skeleton wasn't - ERR_CONTINUE_MSG(!valid_armature, "[doc] fbx armature is missing"); - } else { - continue; // safe to continue not a bug just a normal mesh - } - - if (state.skeleton_map.has(armature)) { - skeleton = state.skeleton_map[armature]; - print_verbose("[doc] armature mesh to skeleton mapping has been allocated"); - } else { - print_error("[doc] unable to find armature mapping"); - } - - ERR_CONTINUE_MSG(!mesh_instance, "[doc] invalid mesh mapping for skeleton assignment"); - ERR_CONTINUE_MSG(skeleton.is_null(), "[doc] unable to resolve the correct skeleton but we have weights!"); - - mesh_instance->set_skeleton_path(mesh_instance->get_path_to(skeleton->skeleton)); - print_verbose("[doc] allocated skeleton to mesh " + mesh_instance->get_name()); - - // do we have a mesh skin for this mesh - ERR_CONTINUE_MSG(!state.MeshSkins.has(mesh_id), "no skin found for mesh"); - - Ref<Skin> mesh_skin = state.MeshSkins[mesh_id]; - - ERR_CONTINUE_MSG(mesh_skin.is_null(), "invalid skin stored in map"); - print_verbose("[doc] allocated skin to mesh " + mesh_instance->get_name()); - mesh_instance->set_skin(mesh_skin); - } - - // build skin and skeleton information - print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size())); - const FBXDocParser::FileGlobalSettings *FBXSettings = p_document->GlobalSettingsPtr(); - - // Configure constraints - // NOTE: constraints won't be added quite yet, we don't have a real need for them *yet*. (they can be supported later on) - // const std::vector<uint64_t> fbx_constraints = p_document->GetConstraintStackIDs(); - - // get the animation FPS - float fps_setting = ImportUtils::get_fbx_fps(FBXSettings); - - // enable animation import, only if local animation is enabled - if (state.enable_animation_import && (p_flags & IMPORT_ANIMATION)) { - // document animation stack list - get by ID so we can unload any non used animation stack - const std::vector<uint64_t> animation_stack = p_document->GetAnimationStackIDs(); - - for (uint64_t anim_id : animation_stack) { - FBXDocParser::LazyObject *lazyObject = p_document->GetObject(anim_id); - const FBXDocParser::AnimationStack *stack = lazyObject->Get<FBXDocParser::AnimationStack>(); - - if (stack != nullptr) { - String animation_name = ImportUtils::FBXNodeToName(stack->Name()); - print_verbose("Valid animation stack has been found: " + animation_name); - // ReferenceTime is the same for some animations? - // LocalStop time is the start and end time - float r_start = CONVERT_FBX_TIME(stack->ReferenceStart()); - float r_stop = CONVERT_FBX_TIME(stack->ReferenceStop()); - float start_time = CONVERT_FBX_TIME(stack->LocalStart()); - float end_time = CONVERT_FBX_TIME(stack->LocalStop()); - float duration = end_time - start_time; - - print_verbose("r_start " + rtos(r_start) + ", r_stop " + rtos(r_stop)); - print_verbose("start_time" + rtos(start_time) + " end_time " + rtos(end_time)); - print_verbose("anim duration : " + rtos(duration)); - - // we can safely create the animation player - if (state.animation_player == nullptr) { - print_verbose("Creating animation player"); - state.animation_player = memnew(AnimationPlayer); - state.root->add_child(state.animation_player, true); - state.animation_player->set_owner(state.root_owner); - } - - Ref<Animation> animation; - animation.instantiate(); - animation->set_name(animation_name); - animation->set_length(duration); - - print_verbose("Animation length: " + rtos(animation->get_length()) + " seconds"); - - // i think assimp was duplicating things, this lets me know to just reference or ignore this to prevent duplicate information in tracks - // this would mean that we would be doing three times as much work per track if my theory is correct. - // this was not the case but this is a good sanity check for the animation handler from the document. - // it also lets us know if the FBX specification massively changes the animation system, in theory such a change would make this show - // an fbx specification error, so best keep it in - // the overhead is tiny. - Map<uint64_t, const FBXDocParser::AnimationCurve *> CheckForDuplication; - - const std::vector<const FBXDocParser::AnimationLayer *> &layers = stack->Layers(); - print_verbose("FBX Animation layers: " + itos(layers.size())); - for (const FBXDocParser::AnimationLayer *layer : layers) { - std::vector<const FBXDocParser::AnimationCurveNode *> node_list = layer->Nodes(); - print_verbose("Layer: " + ImportUtils::FBXNodeToName(layer->Name()) + ", " + " AnimCurveNode count " + itos(node_list.size())); - - // first thing to do here is that i need to first get the animcurvenode to a Vector3 - // we now need to put this into the track information for godot. - // to do this we need to know which track is what? - - // target id, [ track name, [time index, vector] ] - // new map needs to be [ track name, keyframe_data ] - Map<uint64_t, Map<StringName, FBXTrack>> AnimCurveNodes; - - // struct AnimTrack { - // // Animation track can be - // // visible, T, R, S - // Map<StringName, Map<uint64_t, Vector3> > animation_track; - // }; - - // Map<uint64_t, AnimTrack> AnimCurveNodes; - - // so really, what does this mean to make an animtion track. - // we need to know what object the curves are for. - // we need the target ID and the target name for the track reduction. - - FBXDocParser::Model::RotOrder quaternion_rotation_order = FBXDocParser::Model::RotOrder_EulerXYZ; - - // T:: R:: S:: Visible:: Custom:: - for (const FBXDocParser::AnimationCurveNode *curve_node : node_list) { - // when Curves() is called the curves are actually read, we could replace this with our own ProcessDomConnection code here if required. - // We may need to do this but ideally we use Curves - // note: when you call this there might be a delay in opening it - // uses mutable type to 'cache' the response until the AnimationCurveNode is cleaned up. - std::map<std::string, const FBXDocParser::AnimationCurve *> curves = curve_node->Curves(); - const FBXDocParser::Object *object = curve_node->Target(); - const FBXDocParser::Model *target = curve_node->TargetAsModel(); - if (target == nullptr) { - if (object != nullptr) { - print_error("[doc] warning failed to find a target Model for curve: " + String(object->Name().c_str())); - } else { - //print_error("[doc] failed to resolve object"); - continue; - } - - continue; - } else { - //print_verbose("[doc] applied rotation order: " + itos(target->RotationOrder())); - quaternion_rotation_order = target->RotationOrder(); - } - - uint64_t target_id = target->ID(); - String target_name = ImportUtils::FBXNodeToName(target->Name()); - - const FBXDocParser::PropertyTable *properties = curve_node; - bool got_x = false, got_y = false, got_z = false; - float offset_x = FBXDocParser::PropertyGet<float>(properties, "d|X", got_x); - float offset_y = FBXDocParser::PropertyGet<float>(properties, "d|Y", got_y); - float offset_z = FBXDocParser::PropertyGet<float>(properties, "d|Z", got_z); - - String curve_node_name = ImportUtils::FBXNodeToName(curve_node->Name()); - - // Reduce all curves for this node into a single container - // T, R, S is what we expect, although other tracks are possible - // like for example visibility tracks. - - // We are not ordered here, we don't care about ordering, this happens automagically by godot when we insert with the - // key time :), so order is unimportant because the insertion will happen at a time index - // good to know: we do not need a list of these in another format :) - //Map<String, Vector<const Assimp::FBX::AnimationCurve *> > unordered_track; - - // T - // R - // S - // Map[String, List<VECTOR>] - - // So this is a reduction of the animation curve nodes - // We build this as a lookup, this is essentially our 'animation track' - //AnimCurveNodes.insert(curve_node_name, Map<uint64_t, Vector3>()); - - // create the animation curve information with the target id - // so the point of this makes a track with the name "T" for example - // the target ID is also set here, this means we don't need to do anything extra when we are in the 'create all animation tracks' step - FBXTrack &keyframe_map = AnimCurveNodes[target_id][StringName(curve_node_name)]; - - if (got_x && got_y && got_z) { - Vector3 default_value = Vector3(offset_x, offset_y, offset_z); - keyframe_map.default_value = default_value; - keyframe_map.has_default = true; - //print_verbose("track name: " + curve_node_name); - //print_verbose("xyz default: " + default_value); - } - // target id, [ track name, [time index, vector] ] - // Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes; - - // we probably need the target id here. - // so map[uint64_t map]... - // Map<uint64_t, Vector3D> translation_keys, rotation_keys, scale_keys; - - // extra const required by C++11 colon/Range operator - // note: do not use C++17 syntax here for dicts. - // this is banned in Godot. - for (const std::pair<const std::string, const FBXDocParser::AnimationCurve *> &kvp : curves) { - const String curve_element = ImportUtils::FBXNodeToName(kvp.first); - const FBXDocParser::AnimationCurve *curve = kvp.second; - String curve_name = ImportUtils::FBXNodeToName(curve->Name()); - uint64_t curve_id = curve->ID(); - - if (CheckForDuplication.has(curve_id)) { - print_error("(FBX spec changed?) We found a duplicate curve being used for an alternative node - report to godot issue tracker"); - } else { - CheckForDuplication.insert(curve_id, curve); - } - - // FBX has no name for AnimCurveNode::, most of the time, not seen any with valid name here. - const std::map<int64_t, float> &track_time = curve->GetValueTimeTrack(); - - if (track_time.size() > 0) { - for (const std::pair<const int64_t, float> &keyframe : track_time) { - if (curve_element == "d|X") { - keyframe_map.keyframes[keyframe.first].x = keyframe.second; - } else if (curve_element == "d|Y") { - keyframe_map.keyframes[keyframe.first].y = keyframe.second; - } else if (curve_element == "d|Z") { - keyframe_map.keyframes[keyframe.first].z = keyframe.second; - } else { - //print_error("FBX Unsupported element: " + curve_element); - } - - //print_verbose("[" + itos(target_id) + "] Keyframe added: " + itos(keyframe_map.size())); - - //print_verbose("Keyframe t:" + rtos(animation_track_time) + " v: " + rtos(keyframe.second)); - } - } - } - } - - // Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes; - // add this animation track here - - // target id, [ track name, [time index, vector] ] - //std::map<uint64_t, std::map<StringName, FBXTrack > > AnimCurveNodes; - for (KeyValue<uint64_t, Map<StringName, FBXTrack>> &track : AnimCurveNodes) { - // 5 tracks - // current track index - // track count is 5 - // track count is 5. - // next track id is 5. - const uint64_t target_id = track.key; - - Ref<FBXBone> bone; - - // note we must not run the below code if the entry doesn't exist, it will create dummy entries which is very bad. - // remember that state.fbx_bone_map[target_id] will create a new entry EVEN if you only read. - // this would break node animation targets, so if you change this be warned. :) - if (state.fbx_bone_map.has(target_id)) { - bone = state.fbx_bone_map[target_id]; - } - - Transform3D target_transform; - - if (state.fbx_target_map.has(target_id)) { - Ref<FBXNode> node_ref = state.fbx_target_map[target_id]; - target_transform = node_ref->pivot_transform->GlobalTransform; - //print_verbose("[doc] allocated animation node transform"); - } - - //int size_targets = state.fbx_target_map.size(); - //print_verbose("Target ID map: " + itos(size_targets)); - //print_verbose("[doc] debug bone map size: " + itos(state.fbx_bone_map.size())); - - // if this is a skeleton mapped track we can just set the path for the track. - // todo: implement node paths here at some - NodePath track_path; - if (state.fbx_bone_map.size() > 0 && state.fbx_bone_map.has(target_id)) { - if (bone->fbx_skeleton.is_valid() && bone.is_valid()) { - Ref<FBXSkeleton> fbx_skeleton = bone->fbx_skeleton; - String bone_path = state.root->get_path_to(fbx_skeleton->skeleton); - bone_path += ":" + fbx_skeleton->skeleton->get_bone_name(bone->godot_bone_id); - print_verbose("[doc] track bone path: " + bone_path); - track_path = bone_path; - } - } else if (state.fbx_target_map.has(target_id)) { - //print_verbose("[doc] we have a valid target for a node animation"); - Ref<FBXNode> target_node = state.fbx_target_map[target_id]; - if (target_node.is_valid() && target_node->godot_node != nullptr) { - String node_path = state.root->get_path_to(target_node->godot_node); - track_path = node_path; - //print_verbose("[doc] node animation path: " + node_path); - } - } else { - // note: this could actually be unsafe this means we should be careful about continuing here, if we see bizarre effects later we should disable this. - // I am not sure if this is unsafe or not, testing will tell us this. - print_error("[doc] invalid fbx target detected for this track"); - continue; - } - - // everything in FBX and Maya is a node therefore if this happens something is seriously broken. - if (!state.fbx_target_map.has(target_id)) { - print_error("unable to resolve this to an FBX object."); - continue; - } - - Ref<FBXNode> target_node = state.fbx_target_map[target_id]; - const FBXDocParser::Model *model = target_node->fbx_model; - const FBXDocParser::PropertyTable *props = dynamic_cast<const FBXDocParser::PropertyTable *>(model); - - Map<StringName, FBXTrack> &track_data = track.value; - FBXTrack &translation_keys = track_data[StringName("T")]; - FBXTrack &rotation_keys = track_data[StringName("R")]; - FBXTrack &scale_keys = track_data[StringName("S")]; - - double increment = 1.0f / fps_setting; - double time = 0.0f; - - bool last = false; - - Vector<Vector3> pos_values; - Vector<float> pos_times; - Vector<Vector3> scale_values; - Vector<float> scale_times; - Vector<Quaternion> rot_values; - Vector<float> rot_times; - - double max_duration = 0; - double anim_length = animation->get_length(); - - for (const std::pair<const int64_t, Vector3> &position_key : translation_keys.keyframes) { - pos_values.push_back(position_key.second * state.scale); - double animation_track_time = CONVERT_FBX_TIME(position_key.first); - - if (animation_track_time > max_duration) { - max_duration = animation_track_time; - } - - //print_verbose("pos keyframe: t:" + rtos(animation_track_time) + " value " + position_key.second); - pos_times.push_back(animation_track_time); - } - - for (const std::pair<const int64_t, Vector3> &scale_key : scale_keys.keyframes) { - scale_values.push_back(scale_key.second); - double animation_track_time = CONVERT_FBX_TIME(scale_key.first); - - if (animation_track_time > max_duration) { - max_duration = animation_track_time; - } - //print_verbose("scale keyframe t:" + rtos(animation_track_time)); - scale_times.push_back(animation_track_time); - } - - // - // Pre and Post keyframe rotation handler - // -- Required because Maya and Autodesk <3 the pain when it comes to implementing animation code! enjoy <3 - - bool got_pre = false; - bool got_post = false; - - Quaternion post_rotation; - Quaternion pre_rotation; - - // Rotation matrix - const Vector3 &PreRotation = FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", got_pre); - const Vector3 &PostRotation = FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", got_post); - - FBXDocParser::Model::RotOrder rot_order = model->RotationOrder(); - if (got_pre) { - pre_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PreRotation)); - } - if (got_post) { - post_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PostRotation)); - } - - Quaternion lastQuaternion = Quaternion(); - - for (const std::pair<const int64_t, Vector3> &rotation_key : rotation_keys.keyframes) { - double animation_track_time = CONVERT_FBX_TIME(rotation_key.first); - - //print_verbose("euler rotation key: " + rotation_key.second); - Quaternion rot_key_value = ImportUtils::EulerToQuaternion(quaternion_rotation_order, ImportUtils::deg2rad(rotation_key.second)); - - if (lastQuaternion != Quaternion() && rot_key_value.dot(lastQuaternion) < 0) { - rot_key_value.x = -rot_key_value.x; - rot_key_value.y = -rot_key_value.y; - rot_key_value.z = -rot_key_value.z; - rot_key_value.w = -rot_key_value.w; - } - // pre_post rotation possibly could fix orientation - Quaternion final_rotation = pre_rotation * rot_key_value * post_rotation; - - lastQuaternion = final_rotation; - - if (animation_track_time > max_duration) { - max_duration = animation_track_time; - } - - rot_values.push_back(final_rotation.normalized()); - rot_times.push_back(animation_track_time); - } - - bool valid_rest = false; - Transform3D bone_rest; - int skeleton_bone = -1; - if (state.fbx_bone_map.has(target_id)) { - if (bone.is_valid() && bone->fbx_skeleton.is_valid()) { - skeleton_bone = bone->godot_bone_id; - if (skeleton_bone >= 0) { - bone_rest = bone->fbx_skeleton->skeleton->get_bone_rest(skeleton_bone); - valid_rest = true; - } - } - - if (!valid_rest) { - print_verbose("invalid rest!"); - } - } - - const Vector3 def_pos = translation_keys.has_default ? (translation_keys.default_value * state.scale) : bone_rest.origin; - const Quaternion def_rot = rotation_keys.has_default ? ImportUtils::EulerToQuaternion(quaternion_rotation_order, ImportUtils::deg2rad(rotation_keys.default_value)) : bone_rest.basis.get_rotation_quaternion(); - const Vector3 def_scale = scale_keys.has_default ? scale_keys.default_value : bone_rest.basis.get_scale(); - print_verbose("track defaults: p(" + def_pos + ") s(" + def_scale + ") r(" + def_rot + ")"); - - int position_idx = -1; - if (pos_values.size()) { - position_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_POSITION_3D); - animation->track_set_path(position_idx, track_path); - animation->track_set_imported(position_idx, true); - } - - int rotation_idx = -1; - if (pos_values.size()) { - rotation_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_ROTATION_3D); - animation->track_set_path(rotation_idx, track_path); - animation->track_set_imported(rotation_idx, true); - } - - int scale_idx = -1; - if (pos_values.size()) { - scale_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_SCALE_3D); - animation->track_set_path(scale_idx, track_path); - animation->track_set_imported(scale_idx, true); - } - - while (true) { - Vector3 pos = def_pos; - Quaternion rot = def_rot; - Vector3 scale = def_scale; - - if (pos_values.size()) { - pos = _interpolate_track<Vector3>(pos_times, pos_values, time, - AssetImportAnimation::INTERP_LINEAR); - } - - if (rot_values.size()) { - rot = _interpolate_track<Quaternion>(rot_times, rot_values, time, - AssetImportAnimation::INTERP_LINEAR); - } - - if (scale_values.size()) { - scale = _interpolate_track<Vector3>(scale_times, scale_values, time, - AssetImportAnimation::INTERP_LINEAR); - } - - if (position_idx >= 0) { - animation->position_track_insert_key(position_idx, time, pos); - } - if (rotation_idx >= 0) { - animation->rotation_track_insert_key(rotation_idx, time, rot); - } - if (scale_idx >= 0) { - animation->scale_track_insert_key(scale_idx, time, scale); - } - - if (last) { - break; - } - - time += increment; - if (time > anim_length) { - last = true; - time = anim_length; - break; - } - } - } - } - state.animation_player->add_animation(animation_name, animation); - } - } - - // AnimStack elements contain start stop time and name of animation - // AnimLayer is the current active layer of the animation (multiple layers can be active we only support 1) - // AnimCurveNode has a OP link back to the model which is the real node. - // AnimCurveNode has a direct link to AnimationCurve (of which it may have more than one) - - // Store animation stack in list - // iterate over all AnimStacks like the cache node algorithm recursively - // this can then be used with ProcessDomConnection<> to link from - // AnimStack:: <-- (OO) --> AnimLayer:: <-- (OO) --> AnimCurveNode:: (which can OP resolve) to Model:: - } - - // - // Cleanup operations - explicit to prevent errors on shutdown - found that ref to ref does behave badly sometimes. - // - - state.renderer_mesh_data.clear(); - state.MeshSkins.clear(); - state.fbx_target_map.clear(); - state.fbx_node_list.clear(); - - for (KeyValue<uint64_t, Ref<FBXBone>> &element : state.fbx_bone_map) { - Ref<FBXBone> bone = element.value; - bone->parent_bone.unref(); - bone->node.unref(); - bone->fbx_skeleton.unref(); - } - - for (KeyValue<uint64_t, Ref<FBXSkeleton>> &element : state.skeleton_map) { - Ref<FBXSkeleton> skel = element.value; - skel->fbx_node.unref(); - skel->skeleton_bones.clear(); - } - - state.fbx_bone_map.clear(); - state.skeleton_map.clear(); - state.fbx_root_node.unref(); - - return scene_root; -} - -void EditorSceneFormatImporterFBX::BuildDocumentBones(Ref<FBXBone> p_parent_bone, - ImportState &state, const FBXDocParser::Document *p_doc, - uint64_t p_id) { - const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(p_id, "Model"); - // FBX can do an join like this - // Model -> SubDeformer (bone) -> Deformer (skin pose) - // This is important because we need to somehow link skin back to bone id in skeleton :) - // The rules are: - // A subdeformer will exist if 'limbnode' class tag present - // The subdeformer will not necessarily have a deformer as joints do not have one - for (const FBXDocParser::Connection *con : conns) { - // goto: bone creation - //print_verbose("con: " + String(con->PropertyName().c_str())); - - // ignore object-property links we want the object to object links nothing else - if (con->PropertyName().length()) { - continue; - } - - // convert connection source object into Object base class - const FBXDocParser::Object *const object = con->SourceObject(); - - if (nullptr == object) { - print_verbose("failed to convert source object for Model link"); - continue; - } - - // FBX Model::Cube, Model::Bone001, etc elements - // This detects if we can cast the object into this model structure. - const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(object); - - // declare our bone element reference (invalid, unless we create a bone in this step) - // this lets us pass valid armature information into children objects and this is why we moved this up here - // previously this was created .instantiated() on the same line. - Ref<FBXBone> bone_element; - - if (model != nullptr) { - // model marked with limb node / casted. - const FBXDocParser::ModelLimbNode *const limb_node = dynamic_cast<const FBXDocParser::ModelLimbNode *>(model); - if (limb_node != nullptr) { - // Write bone into bone list for FBX - - ERR_FAIL_COND_MSG(state.fbx_bone_map.has(limb_node->ID()), "[serious] duplicate LimbNode detected"); - - bool parent_is_bone = state.fbx_bone_map.find(p_id); - bone_element.instantiate(); - - // used to build the bone hierarchy in the skeleton - bone_element->parent_bone_id = parent_is_bone ? p_id : 0; - bone_element->valid_parent = parent_is_bone; - bone_element->limb_node = limb_node; - - // parent is a node and this is the first bone - if (!parent_is_bone) { - uint64_t armature_id = p_id; - bone_element->valid_armature_id = true; - bone_element->armature_id = armature_id; - print_verbose("[doc] valid armature has been configured for first child: " + itos(armature_id)); - } else if (p_parent_bone.is_valid()) { - if (p_parent_bone->valid_armature_id) { - bone_element->valid_armature_id = true; - bone_element->armature_id = p_parent_bone->armature_id; - print_verbose("[doc] bone has valid armature id:" + itos(bone_element->armature_id)); - } else { - print_error("[doc] unassigned armature id: " + String(limb_node->Name().c_str())); - } - } else { - print_error("[doc] error is this a bone? " + String(limb_node->Name().c_str())); - } - - if (!parent_is_bone) { - print_verbose("[doc] Root bone: " + bone_element->bone_name); - } - - uint64_t limb_id = limb_node->ID(); - bone_element->bone_id = limb_id; - bone_element->bone_name = ImportUtils::FBXNodeToName(model->Name()); - bone_element->parent_bone = p_parent_bone; - - // insert limb by ID into list. - state.fbx_bone_map.insert(limb_node->ID(), bone_element); - } - - // recursion call - child nodes - BuildDocumentBones(bone_element, state, p_doc, model->ID()); - } - } -} - -void EditorSceneFormatImporterFBX::BuildDocumentNodes( - Ref<PivotTransform> parent_transform, - ImportState &state, - const FBXDocParser::Document *p_doc, - uint64_t id, - Ref<FBXNode> parent_node) { - // tree - // here we get the node 0 on the root by default - const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(id, "Model"); - - // branch - for (const FBXDocParser::Connection *con : conns) { - // ignore object-property links - if (con->PropertyName().length()) { - // really important we document why this is ignored. - print_verbose("ignoring property link - no docs on why this is ignored"); - continue; - } - - // convert connection source object into Object base class - // Source objects can exist with 'null connections' this means that we only for sure know the source exists. - const FBXDocParser::Object *const source_object = con->SourceObject(); - - if (nullptr == source_object) { - print_verbose("failed to convert source object for Model link"); - continue; - } - - // FBX Model::Cube, Model::Bone001, etc elements - // This detects if we can cast the object into this model structure. - const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(source_object); - // model is the current node - if (nullptr != model) { - uint64_t current_node_id = model->ID(); - - Ref<FBXNode> new_node; - new_node.instantiate(); - new_node->current_node_id = current_node_id; - new_node->node_name = ImportUtils::FBXNodeToName(model->Name()); - - Ref<PivotTransform> fbx_transform; - fbx_transform.instantiate(); - fbx_transform->set_parent(parent_transform); - fbx_transform->set_model(model); - fbx_transform->debug_pivot_xform("name: " + new_node->node_name); - fbx_transform->Execute(); - - new_node->set_pivot_transform(fbx_transform); - - // check if this node is a bone - if (state.fbx_bone_map.has(current_node_id)) { - Ref<FBXBone> bone = state.fbx_bone_map[current_node_id]; - if (bone.is_valid()) { - bone->set_node(new_node); - print_verbose("allocated bone data: " + bone->bone_name); - } - } - - // set the model, we can't just assign this safely - new_node->set_model(model); - - if (parent_node.is_valid()) { - new_node->set_parent(parent_node); - } else { - new_node->set_parent(state.fbx_root_node); - } - - CRASH_COND_MSG(new_node->pivot_transform.is_null(), "invalid fbx target map pivot transform [serious]"); - - // populate lookup tables with references - // [fbx_node_id, fbx_node] - - state.fbx_node_list.push_back(new_node); - if (!state.fbx_target_map.has(new_node->current_node_id)) { - state.fbx_target_map[new_node->current_node_id] = new_node; - } - - // print node name - print_verbose("[doc] new node " + new_node->node_name); - - // sub branches - BuildDocumentNodes(new_node->pivot_transform, state, p_doc, current_node_id, new_node); - } - } -} -Ref<Animation> EditorSceneFormatImporterFBX::import_animation(const String &p_path, - uint32_t p_flags, const Map<StringName, Variant> &p_options, - int p_bake_fps) { - return Ref<Animation>(); -} - -EditorSceneFormatImporterFBX::EditorSceneFormatImporterFBX() { - _GLOBAL_DEF("filesystem/import/fbx/use_fbx", true, true); -} diff --git a/modules/fbx/editor_scene_importer_fbx.h b/modules/fbx/editor_scene_importer_fbx.h deleted file mode 100644 index fb72f93feb..0000000000 --- a/modules/fbx/editor_scene_importer_fbx.h +++ /dev/null @@ -1,136 +0,0 @@ -/*************************************************************************/ -/* editor_scene_importer_fbx.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_FBX_H -#define EDITOR_SCENE_IMPORTER_FBX_H - -#ifdef TOOLS_ENABLED - -#include "data/import_state.h" -#include "tools/import_utils.h" - -#include "core/io/resource_importer.h" -#include "core/string/ustring.h" -#include "core/templates/local_vector.h" -#include "core/templates/vector.h" -#include "core/variant/dictionary.h" -#include "editor/import/resource_importer_scene.h" -#include "editor/project_settings_editor.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" -#include "scene/animation/animation_player.h" -#include "scene/resources/animation.h" -#include "scene/resources/surface_tool.h" - -#include "fbx_parser/FBXDocument.h" -#include "fbx_parser/FBXImportSettings.h" -#include "fbx_parser/FBXMeshGeometry.h" -#include "fbx_parser/FBXUtil.h" - -#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL - -class EditorSceneFormatImporterFBX : public EditorSceneFormatImporter { -private: - GDCLASS(EditorSceneFormatImporterFBX, EditorSceneFormatImporter); - - struct AssetImportAnimation { - enum Interpolation { - INTERP_LINEAR, - INTERP_STEP, - INTERP_CATMULLROMSPLINE, - INTERP_CUBIC_SPLINE - }; - }; - - // ------------------------------------------------------------------------------------------------ - template <typename T> - const T *ProcessDOMConnection( - const FBXDocParser::Document *doc, - uint64_t current_element, - bool reverse_lookup = false) { - const std::vector<const FBXDocParser::Connection *> &conns = reverse_lookup ? doc->GetConnectionsByDestinationSequenced(current_element) : doc->GetConnectionsBySourceSequenced(current_element); - //print_verbose("[doc] looking for " + String(element_to_find)); - // using the temp pattern here so we can debug before it returns - // in some cases we return too early, with 'deformer object base class' in wrong place - // in assimp this means we can accidentally return too early... - const T *return_obj = nullptr; - - for (const FBXDocParser::Connection *con : conns) { - const FBXDocParser::Object *source_object = con->SourceObject(); - const FBXDocParser::Object *dest_object = con->DestinationObject(); - if (source_object && dest_object != nullptr) { - //print_verbose("[doc] connection name: " + String(source_object->Name().c_str()) + ", dest: " + String(dest_object->Name().c_str())); - const T *temp = dynamic_cast<const T *>(reverse_lookup ? source_object : dest_object); - if (temp) { - return_obj = temp; - } - } - } - - if (return_obj != nullptr) { - //print_verbose("[doc] returned valid element"); - //print_verbose("Found object for bone"); - return return_obj; - } - - // safe to return nothing, need to use nullptr here as nullptr is used internally for FBX document. - return nullptr; - } - - void BuildDocumentBones(Ref<FBXBone> p_parent_bone, - ImportState &state, const FBXDocParser::Document *p_doc, - uint64_t p_id); - - void BuildDocumentNodes(Ref<PivotTransform> parent_transform, ImportState &state, const FBXDocParser::Document *doc, uint64_t id, Ref<FBXNode> fbx_parent); - - Node3D *_generate_scene(const String &p_path, const FBXDocParser::Document *p_document, - const uint32_t p_flags, - int p_bake_fps, - const int32_t p_max_bone_weights, - bool p_is_blender_fbx); - - template <class T> - T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp); - -public: - EditorSceneFormatImporterFBX(); - ~EditorSceneFormatImporterFBX() {} - - virtual void get_extensions(List<String> *r_extensions) const override; - virtual uint32_t get_import_flags() const override; - virtual Node3D *import_scene(const String &p_path, uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr) override; - virtual Ref<Animation> import_animation(const String &p_path, - uint32_t p_flags, const Map<StringName, Variant> &p_options, - int p_bake_fps) override; -}; - -#endif // TOOLS_ENABLED -#endif // EDITOR_SCENE_IMPORTER_FBX_H diff --git a/modules/fbx/fbx_parser/ByteSwapper.h b/modules/fbx/fbx_parser/ByteSwapper.h deleted file mode 100644 index 21c5557001..0000000000 --- a/modules/fbx/fbx_parser/ByteSwapper.h +++ /dev/null @@ -1,283 +0,0 @@ -/*************************************************************************/ -/* ByteSwapper.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file Helper class tp perform various byte order swappings - (e.g. little to big endian) */ -#ifndef BYTE_SWAPPER_H -#define BYTE_SWAPPER_H - -#include <stdint.h> -#include <algorithm> -#include <locale> - -namespace FBXDocParser { -// -------------------------------------------------------------------------------------- -/** Defines some useful byte order swap routines. - * - * This is required to read big-endian model formats on little-endian machines, - * and vice versa. Direct use of this class is DEPRECATED. Use #StreamReader instead. */ -// -------------------------------------------------------------------------------------- -class ByteSwap { - ByteSwap() {} - -public: - // ---------------------------------------------------------------------- - /** Swap two bytes of data - * @param[inout] _szOut A void* to save the reintcasts for the caller. */ - static inline void Swap2(void *_szOut) { - uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); - std::swap(szOut[0], szOut[1]); - } - - // ---------------------------------------------------------------------- - /** Swap four bytes of data - * @param[inout] _szOut A void* to save the reintcasts for the caller. */ - static inline void Swap4(void *_szOut) { - uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); - std::swap(szOut[0], szOut[3]); - std::swap(szOut[1], szOut[2]); - } - - // ---------------------------------------------------------------------- - /** Swap eight bytes of data - * @param[inout] _szOut A void* to save the reintcasts for the caller. */ - static inline void Swap8(void *_szOut) { - uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut); - std::swap(szOut[0], szOut[7]); - std::swap(szOut[1], szOut[6]); - std::swap(szOut[2], szOut[5]); - std::swap(szOut[3], szOut[4]); - } - - // ---------------------------------------------------------------------- - /** ByteSwap a float. Not a joke. - * @param[inout] fOut ehm. .. */ - static inline void Swap(float *fOut) { - Swap4(fOut); - } - - // ---------------------------------------------------------------------- - /** ByteSwap a double. Not a joke. - * @param[inout] fOut ehm. .. */ - static inline void Swap(double *fOut) { - Swap8(fOut); - } - - // ---------------------------------------------------------------------- - /** ByteSwap an int16t. Not a joke. - * @param[inout] fOut ehm. .. */ - static inline void Swap(int16_t *fOut) { - Swap2(fOut); - } - - static inline void Swap(uint16_t *fOut) { - Swap2(fOut); - } - - // ---------------------------------------------------------------------- - /** ByteSwap an int32t. Not a joke. - * @param[inout] fOut ehm. .. */ - static inline void Swap(int32_t *fOut) { - Swap4(fOut); - } - - static inline void Swap(uint32_t *fOut) { - Swap4(fOut); - } - - // ---------------------------------------------------------------------- - /** ByteSwap an int64t. Not a joke. - * @param[inout] fOut ehm. .. */ - static inline void Swap(int64_t *fOut) { - Swap8(fOut); - } - - static inline void Swap(uint64_t *fOut) { - Swap8(fOut); - } - - // ---------------------------------------------------------------------- - //! Templatized ByteSwap - //! \returns param tOut as swapped - template <typename Type> - static inline Type Swapped(Type tOut) { - return _swapper<Type, sizeof(Type)>()(tOut); - } - -private: - template <typename T, size_t size> - struct _swapper; -}; - -template <typename T> -struct ByteSwap::_swapper<T, 2> { - T operator()(T tOut) { - Swap2(&tOut); - return tOut; - } -}; - -template <typename T> -struct ByteSwap::_swapper<T, 4> { - T operator()(T tOut) { - Swap4(&tOut); - return tOut; - } -}; - -template <typename T> -struct ByteSwap::_swapper<T, 8> { - T operator()(T tOut) { - Swap8(&tOut); - return tOut; - } -}; - -// -------------------------------------------------------------------------------------- -// ByteSwap macros for BigEndian/LittleEndian support -// -------------------------------------------------------------------------------------- -#if (defined AI_BUILD_BIG_ENDIAN) -#define AI_LE(t) (t) -#define AI_BE(t) ByteSwap::Swapped(t) -#define AI_LSWAP2(p) -#define AI_LSWAP4(p) -#define AI_LSWAP8(p) -#define AI_LSWAP2P(p) -#define AI_LSWAP4P(p) -#define AI_LSWAP8P(p) -#define LE_NCONST const -#define AI_SWAP2(p) ByteSwap::Swap2(&(p)) -#define AI_SWAP4(p) ByteSwap::Swap4(&(p)) -#define AI_SWAP8(p) ByteSwap::Swap8(&(p)) -#define AI_SWAP2P(p) ByteSwap::Swap2((p)) -#define AI_SWAP4P(p) ByteSwap::Swap4((p)) -#define AI_SWAP8P(p) ByteSwap::Swap8((p)) -#define BE_NCONST -#else -#define AI_BE(t) (t) -#define AI_LE(t) ByteSwap::Swapped(t) -#define AI_SWAP2(p) -#define AI_SWAP4(p) -#define AI_SWAP8(p) -#define AI_SWAP2P(p) -#define AI_SWAP4P(p) -#define AI_SWAP8P(p) -#define BE_NCONST const -#define AI_LSWAP2(p) ByteSwap::Swap2(&(p)) -#define AI_LSWAP4(p) ByteSwap::Swap4(&(p)) -#define AI_LSWAP8(p) ByteSwap::Swap8(&(p)) -#define AI_LSWAP2P(p) ByteSwap::Swap2((p)) -#define AI_LSWAP4P(p) ByteSwap::Swap4((p)) -#define AI_LSWAP8P(p) ByteSwap::Swap8((p)) -#define LE_NCONST -#endif - -namespace Intern { - -// -------------------------------------------------------------------------------------------- -template <typename T, bool doit> -struct ByteSwapper { - void operator()(T *inout) { - ByteSwap::Swap(inout); - } -}; - -template <typename T> -struct ByteSwapper<T, false> { - void operator()(T *) { - } -}; - -// -------------------------------------------------------------------------------------------- -template <bool SwapEndianess, typename T, bool RuntimeSwitch> -struct Getter { - void operator()(T *inout, bool le) { - le = !le; - if (le) { - ByteSwapper<T, (sizeof(T) > 1 ? true : false)>()(inout); - } else { - ByteSwapper<T, false>()(inout); - } - } -}; - -template <bool SwapEndianess, typename T> -struct Getter<SwapEndianess, T, false> { - void operator()(T *inout, bool /*le*/) { - // static branch - ByteSwapper<T, (SwapEndianess && sizeof(T) > 1)>()(inout); - } -}; -} // namespace Intern -} // namespace FBXDocParser - -#endif // BYTE_SWAPPER_H diff --git a/modules/fbx/fbx_parser/CREDITS b/modules/fbx/fbx_parser/CREDITS deleted file mode 100644 index 62b449614e..0000000000 --- a/modules/fbx/fbx_parser/CREDITS +++ /dev/null @@ -1,183 +0,0 @@ -=============================================================== -Open Asset Import Library (Assimp) -Developers and Contributors -=============================================================== - -The following is a non-exhaustive list of all constributors over the years. -If you think your name should be listed here, drop us a line and we'll add you. - -- Alexander Gessler, -3DS-, BLEND-, ASE-, DXF-, HMP-, MDL-, MD2-, MD3-, MD5-, MDC-, NFF-, PLY-, STL-, RAW-, OFF-, MS3D-, Q3D- and LWO-Loader, Assimp-Viewer, assimp-cmd, -noboost, Website (Design). - -- Thomas Schulze, -X-, Collada-, BVH-Loader, Postprocessing framework. Data structure & Interface design, documentation. - -- Kim Kulling, -Obj-, Q3BSD-, OpenGEX-Loader, Logging system, CMake-build-environment, Linux-build, Website ( Admin ), Coverity ( Admin ), Glitter ( Admin ). - -- R.Schmidt, -Linux build, eclipse support. - -- Matthias Gubisch, -Assimp.net -Visual Studio 9 support, bugfixes. - -- Mark Sibly -B3D-Loader, Assimp testing - -- Jonathan Klein -Ogre Loader, VC2010 fixes and CMake fixes. - -- Sebastian Hempel, -PyAssimp (first version) -Compile-Bugfixes for mingw, add environment for static library support in make. - -- Jonathan Pokrass -Supplied a bugfix concerning the scaling in the md3 loader. - -- Andrew Galante, -Submitted patches to make Assimp compile with GCC-4, a makefile and the xcode3 workspace. - -- Andreas Nagel -First Assimp testing & verification under Windows Vista 64 Bit. - -- Marius Schr�der -Allowed us to use many of his models for screenshots and testing. - -- Christian Schubert -Supplied various XFiles for testing purposes. - -- Tizian Wieland -Searched the web for hundreds of test models for internal use - -- John Connors -Supplied patches for linux and SCons. - -- T. R. -The GUY who performed some of the CSM mocaps. - -- Andy Maloney -Contributed fixes for the documentation and the doxygen markup - -- Zhao Lei -Contributed several bugfixes fixing memory leaks and improving float parsing - -- sueastside -Updated PyAssimp to the latest Assimp data structures and provided a script to keep the Python binding up-to-date. - -- Tobias Rittig -Collada testing with Cinema 4D - -- Brad Grantham -Improvements in OpenGL-Sample. - -- Robert Ramirez -Add group loading feature to Obj-Loader. - -- Chris Maiwald -Many bugreports, improving Assimp's portability, regular testing & feedback. - -- Stepan Hrbek -Bugreport and fix for a obj-materialloader crash. - -- David Nadlinger -D bindings, CMake install support. - -- Dario Accornero -Contributed several patches regarding Mac OS/XCode targets, bug reports. - -- Martin Walser (Samhayne) -Contributed the 'SimpleTexturedOpenGl' sample. - -- Matthias Fauconneau -Contributed a fix for the Q3-BSP loader. - -- Jørgen P. Tjernø -Contributed updated and improved xcode workspaces - -- drparallax -Contributed the /samples/SimpleAssimpViewX sample - -- Carsten Fuchs -Contributed a fix for the Normalize method in aiQuaternion. - -- dbburgess -Contributes a Android-specific build issue: log the hardware architecture for ARM. - -- alfiereinre7 -Contributes a obj-fileparser fix: missing tokens in the obj-token list. - -- Roman Kharitonov -Contributes a fix for the configure script environment. - -- Ed Diana -Contributed AssimpDelphi (/port/AssimpDelphi). - -- rdb -Contributes a bundle of fixes and improvements for the bsp-importer. - -- Mick P -For contributing the De-bone postprocessing step and filing various bug reports. - -- Rosen Diankov -Contributed patches to build assimp debian packages using cmake. - -- Mark Page -Contributed a patch to fix the VertexTriangleAdjacency postprocessing step. - -- IOhannes -Contributed the Debian build fixes ( architecture macro ). - -- gellule -Several LWO and LWS fixes (pivoting). - -- Marcel Metz -GCC/Linux fixes for the SimpleOpenGL sample. - -- Brian Miller -Bugfix for a compiler fix for iOS on arm. - -- Séverin Lemaignan -Rewrite of PyAssimp, distutils and Python3 support - -- albert-wang -Bugfixes for the collada parser - -- Ya ping Jin -Bugfixes for uv-tanget calculation. - -- Jonne Nauha -Ogre Binary format support - -- Filip Wasil, Tieto Poland Sp. z o.o. -Android JNI asset extraction support - -- Richard Steffen -Contributed ExportProperties interface -Contributed X File exporter -Contributed Step (stp) exporter - -- Thomas Iorns (mesilliac) -Initial FBX Export support - -For a more detailed list just check: https://github.com/assimp/assimp/network/members - - -======== -Patreons -======== - -Huge thanks to our Patreons! - -- migenius -- Marcus -- Cort -- elect -- Steffen - - -=================== -Commercial Sponsors -=================== - -- MyDidimo (mydidimo.com): Sponsored development of FBX Export support diff --git a/modules/fbx/fbx_parser/FBXAnimation.cpp b/modules/fbx/fbx_parser/FBXAnimation.cpp deleted file mode 100644 index 8627c95012..0000000000 --- a/modules/fbx/fbx_parser/FBXAnimation.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/*************************************************************************/ -/* FBXAnimation.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXAnimation.cpp - * @brief Assimp::FBX::AnimationCurve, Assimp::FBX::AnimationCurveNode, - * Assimp::FBX::AnimationLayer, Assimp::FBX::AnimationStack - */ - -#include "FBXCommon.h" -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXParser.h" - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -AnimationCurve::AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document & /*doc*/) : - Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - const ElementPtr KeyTime = GetRequiredElement(sc, "KeyTime"); - const ElementPtr KeyValueFloat = GetRequiredElement(sc, "KeyValueFloat"); - - // note preserved keys and values for legacy FBXConverter.cpp - // we can remove this once the animation system is written - // and clean up this code so we do not have copies everywhere. - ParseVectorDataArray(keys, KeyTime); - ParseVectorDataArray(values, KeyValueFloat); - - if (keys.size() != values.size()) { - DOMError("the number of key times does not match the number of keyframe values", KeyTime); - } - - // put the two lists into the map, underlying container is really just a dictionary - // these will always match, if not an error will throw and the file will not import - // this is useful because we then can report something and fix this later if it becomes an issue - // at this point we do not need a different count of these elements so this makes the - // most sense to do. - for (size_t x = 0; x < keys.size(); x++) { - keyvalues[keys[x]] = values[x]; - } - - const ElementPtr KeyAttrDataFloat = sc->GetElement("KeyAttrDataFloat"); - if (KeyAttrDataFloat) { - ParseVectorDataArray(attributes, KeyAttrDataFloat); - } - - const ElementPtr KeyAttrFlags = sc->GetElement("KeyAttrFlags"); - if (KeyAttrFlags) { - ParseVectorDataArray(flags, KeyAttrFlags); - } -} - -// ------------------------------------------------------------------------------------------------ -AnimationCurve::~AnimationCurve() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, - const Document &doc, const char *const *target_prop_whitelist /*= nullptr*/, - size_t whitelist_size /*= 0*/) : - Object(id, element, name), doc(doc) { - // find target node - const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; - const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); - - for (const Connection *con : conns) { - // link should go for a property - if (!con->PropertyName().length()) { - continue; - } - - Object *object = con->DestinationObject(); - - if (!object) { - DOMWarning("failed to read destination object for AnimationCurveNode->Model link, ignoring", element); - continue; - } - - target = object; - prop = con->PropertyName(); - break; - } -} - -// ------------------------------------------------------------------------------------------------ -AnimationCurveNode::~AnimationCurveNode() { - curves.clear(); -} - -// ------------------------------------------------------------------------------------------------ -const AnimationMap &AnimationCurveNode::Curves() const { - /* Lazy loaded animation curves, will only load if required */ - if (curves.empty()) { - // resolve attached animation curves - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve"); - - for (const Connection *con : conns) { - // So the advantage of having this STL boilerplate is that it's dead simple once you get it. - // The other advantage is casting is guaranteed to be safe and nullptr will be returned in the last step if it fails. - Object *ob = con->SourceObject(); - AnimationCurve *anim_curve = dynamic_cast<AnimationCurve *>(ob); - ERR_CONTINUE_MSG(!anim_curve, "Failed to convert animation curve from object"); - - curves.insert(std::make_pair(con->PropertyName(), anim_curve)); - } - } - - return curves; -} - -// ------------------------------------------------------------------------------------------------ -AnimationLayer::AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Object(id, element, name), doc(doc) { -} - -// ------------------------------------------------------------------------------------------------ -AnimationLayer::~AnimationLayer() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -const AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_prop_whitelist, - size_t whitelist_size /*= 0*/) const { - AnimationCurveNodeList nodes; - - // resolve attached animation nodes - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurveNode"); - nodes.reserve(conns.size()); - - for (const Connection *con : conns) { - // link should not go to a property - if (con->PropertyName().length()) { - continue; - } - - Object *ob = con->SourceObject(); - - if (!ob) { - DOMWarning("failed to read source object for AnimationCurveNode->AnimationLayer link, ignoring", element); - continue; - } - - const AnimationCurveNode *anim = dynamic_cast<AnimationCurveNode *>(ob); - if (!anim) { - DOMWarning("source object for ->AnimationLayer link is not an AnimationCurveNode", element); - continue; - } - - if (target_prop_whitelist) { - const char *s = anim->TargetProperty().c_str(); - bool ok = false; - for (size_t i = 0; i < whitelist_size; ++i) { - if (!strcmp(s, target_prop_whitelist[i])) { - ok = true; - break; - } - } - if (!ok) { - continue; - } - } - nodes.push_back(anim); - } - - return nodes; -} - -// ------------------------------------------------------------------------------------------------ -AnimationStack::AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Object(id, element, name) { - // resolve attached animation layers - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationLayer"); - layers.reserve(conns.size()); - - for (const Connection *con : conns) { - // link should not go to a property - if (con->PropertyName().length()) { - continue; - } - - Object *ob = con->SourceObject(); - if (!ob) { - DOMWarning("failed to read source object for AnimationLayer->AnimationStack link, ignoring", element); - continue; - } - - const AnimationLayer *anim = dynamic_cast<const AnimationLayer *>(ob); - - if (!anim) { - DOMWarning("source object for ->AnimationStack link is not an AnimationLayer", element); - continue; - } - - layers.push_back(anim); - } -} - -// ------------------------------------------------------------------------------------------------ -AnimationStack::~AnimationStack() { -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp b/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp deleted file mode 100644 index f982e0ca4d..0000000000 --- a/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp +++ /dev/null @@ -1,442 +0,0 @@ -/*************************************************************************/ -/* FBXBinaryTokenizer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ -/** @file FBXBinaryTokenizer.cpp - * @brief Implementation of a fake lexer for binary fbx files - - * we emit tokens so the parser needs almost no special handling - * for binary files. - */ - -#include "ByteSwapper.h" -#include "FBXTokenizer.h" -#include "core/string/print_string.h" - -#include <stdint.h> - -namespace FBXDocParser { -// ------------------------------------------------------------------------------------------------ -Token::Token(const char *sbegin, const char *send, TokenType type, size_t offset) : - sbegin(sbegin), - send(send), - type(type), - line(offset), - column(BINARY_MARKER) { -#ifdef DEBUG_ENABLED - // contents is bad.. :/ - contents = std::string(sbegin, static_cast<size_t>(send - sbegin)); -#endif - // calc length - // measure from sBegin to sEnd and validate? -} - -namespace { - -// ------------------------------------------------------------------------------------------------ -// signal tokenization error -void TokenizeError(const std::string &message, size_t offset) { - print_error("[FBX-Tokenize] " + String(message.c_str()) + ", offset " + itos(offset)); -} - -// ------------------------------------------------------------------------------------------------ -size_t Offset(const char *begin, const char *cursor) { - //ai_assert(begin <= cursor); - - return cursor - begin; -} - -// ------------------------------------------------------------------------------------------------ -void TokenizeError(const std::string &message, const char *begin, const char *cursor) { - TokenizeError(message, Offset(begin, cursor)); -} - -// ------------------------------------------------------------------------------------------------ -uint32_t ReadWord(const char *input, const char *&cursor, const char *end) { - const size_t k_to_read = sizeof(uint32_t); - if (Offset(cursor, end) < k_to_read) { - TokenizeError("cannot ReadWord, out of bounds", input, cursor); - } - - uint32_t word; - ::memcpy(&word, cursor, 4); - AI_SWAP4(word); - - cursor += k_to_read; - - return word; -} - -// ------------------------------------------------------------------------------------------------ -uint64_t ReadDoubleWord(const char *input, const char *&cursor, const char *end) { - const size_t k_to_read = sizeof(uint64_t); - if (Offset(cursor, end) < k_to_read) { - TokenizeError("cannot ReadDoubleWord, out of bounds", input, cursor); - } - - uint64_t dword /*= *reinterpret_cast<const uint64_t*>(cursor)*/; - ::memcpy(&dword, cursor, sizeof(uint64_t)); - AI_SWAP8(dword); - - cursor += k_to_read; - - return dword; -} - -// ------------------------------------------------------------------------------------------------ -uint8_t ReadByte(const char *input, const char *&cursor, const char *end) { - if (Offset(cursor, end) < sizeof(uint8_t)) { - TokenizeError("cannot ReadByte, out of bounds", input, cursor); - } - - uint8_t word; /* = *reinterpret_cast< const uint8_t* >( cursor )*/ - ::memcpy(&word, cursor, sizeof(uint8_t)); - ++cursor; - - return word; -} - -// ------------------------------------------------------------------------------------------------ -unsigned int ReadString(const char *&sbegin_out, const char *&send_out, const char *input, - const char *&cursor, const char *end, bool long_length = false, bool allow_null = false) { - const uint32_t len_len = long_length ? 4 : 1; - if (Offset(cursor, end) < len_len) { - TokenizeError("cannot ReadString, out of bounds reading length", input, cursor); - } - - const uint32_t length = long_length ? ReadWord(input, cursor, end) : ReadByte(input, cursor, end); - - if (Offset(cursor, end) < length) { - TokenizeError("cannot ReadString, length is out of bounds", input, cursor); - } - - sbegin_out = cursor; - cursor += length; - - send_out = cursor; - - if (!allow_null) { - for (unsigned int i = 0; i < length; ++i) { - if (sbegin_out[i] == '\0') { - TokenizeError("failed ReadString, unexpected NUL character in string", input, cursor); - } - } - } - - return length; -} - -// ------------------------------------------------------------------------------------------------ -void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, const char *&cursor, const char *end, bool &corrupt) { - if (Offset(cursor, end) < 1) { - TokenizeError("cannot ReadData, out of bounds reading length", input, cursor); - corrupt = true; - return; - } - - const char type = *cursor; - sbegin_out = cursor++; - - switch (type) { - // 16 bit int - case 'Y': - cursor += 2; - break; - - // 1 bit bool flag (yes/no) - case 'C': - cursor += 1; - break; - - // 32 bit int - case 'I': - // <- fall through - - // float - case 'F': - cursor += 4; - break; - - // double - case 'D': - cursor += 8; - break; - - // 64 bit int - case 'L': - cursor += 8; - break; - - // note: do not write cursor += ReadWord(...cursor) as this would be UB - - // raw binary data - case 'R': { - const uint32_t length = ReadWord(input, cursor, end); - cursor += length; - break; - } - - case 'b': - // TODO: what is the 'b' type code? Right now we just skip over it / - // take the full range we could get - cursor = end; - break; - - // array of * - case 'f': - case 'd': - case 'l': - case 'i': - case 'c': { - const uint32_t length = ReadWord(input, cursor, end); - const uint32_t encoding = ReadWord(input, cursor, end); - - const uint32_t comp_len = ReadWord(input, cursor, end); - - // compute length based on type and check against the stored value - if (encoding == 0) { - uint32_t stride = 0; - switch (type) { - case 'f': - case 'i': - stride = 4; - break; - - case 'd': - case 'l': - stride = 8; - break; - - case 'c': - stride = 1; - break; - - default: - break; - }; - //ai_assert(stride > 0); - if (length * stride != comp_len) { - TokenizeError("cannot ReadData, calculated data stride differs from what the file claims", input, cursor); - } - } - // zip/deflate algorithm (encoding==1)? take given length. anything else? die - else if (encoding != 1) { - TokenizeError("cannot ReadData, unknown encoding", input, cursor); - } - cursor += comp_len; - break; - } // string - case 'S': { - const char *sb, *se; - // 0 characters can legally happen in such strings - ReadString(sb, se, input, cursor, end, true, true); - break; - } - default: - corrupt = true; // must exit - TokenizeError("cannot ReadData, unexpected type code: " + std::string(&type, 1), input, cursor); - return; - } - - if (cursor > end) { - corrupt = true; // must exit - TokenizeError("cannot ReadData, the remaining size is too small for the data type: " + std::string(&type, 1), input, cursor); - return; - } - - // the type code is contained in the returned range - send_out = cursor; -} - -// ------------------------------------------------------------------------------------------------ -bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const char *end, bool const is64bits, bool &corrupt) { - // the first word contains the offset at which this block ends - const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); - - // we may get 0 if reading reached the end of the file - - // fbx files have a mysterious extra footer which I don't know - // how to extract any information from, but at least it always - // starts with a 0. - if (!end_offset) { - return false; - } - - if (end_offset > Offset(input, end)) { - TokenizeError("block offset is out of range", input, cursor); - corrupt = true; - return false; - } else if (end_offset < Offset(input, cursor)) { - TokenizeError("block offset is negative out of range", input, cursor); - corrupt = true; - return false; - } - - // the second data word contains the number of properties in the scope - const uint64_t prop_count = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); - - // the third data word contains the length of the property list - const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); - - // now comes the name of the scope/key - const char *sbeg = nullptr, *send = nullptr; - ReadString(sbeg, send, input, cursor, end); - - output_tokens.push_back(new_Token(sbeg, send, TokenType_KEY, Offset(input, cursor))); - - // now come the individual properties - const char *begin_cursor = cursor; - for (unsigned int i = 0; i < prop_count; ++i) { - ReadData(sbeg, send, input, cursor, begin_cursor + prop_length, corrupt); - if (corrupt) { - return false; - } - - output_tokens.push_back(new_Token(sbeg, send, TokenType_DATA, Offset(input, cursor))); - - if (i != prop_count - 1) { - output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_COMMA, Offset(input, cursor))); - } - } - - if (Offset(begin_cursor, cursor) != prop_length) { - TokenizeError("property length not reached, something is wrong", input, cursor); - corrupt = true; - return false; - } - - // at the end of each nested block, there is a NUL record to indicate - // that the sub-scope exists (i.e. to distinguish between P: and P : {}) - // this NUL record is 13 bytes long on 32 bit version and 25 bytes long on 64 bit. - const size_t sentinel_block_length = is64bits ? (sizeof(uint64_t) * 3 + 1) : (sizeof(uint32_t) * 3 + 1); - - if (Offset(input, cursor) < end_offset) { - if (end_offset - Offset(input, cursor) < sentinel_block_length) { - TokenizeError("insufficient padding bytes at block end", input, cursor); - } - - output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_OPEN_BRACKET, Offset(input, cursor))); - - // XXX this is vulnerable to stack overflowing .. - while (Offset(input, cursor) < end_offset - sentinel_block_length) { - ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits, corrupt); - if (corrupt) { - return false; - } - } - output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor))); - - for (unsigned int i = 0; i < sentinel_block_length; ++i) { - if (cursor[i] != '\0') { - TokenizeError("failed to read nested block sentinel, expected all bytes to be 0", input, cursor); - corrupt = true; - return false; - } - } - cursor += sentinel_block_length; - } - - if (Offset(input, cursor) != end_offset) { - TokenizeError("scope length not reached, something is wrong", input, cursor); - corrupt = true; - return false; - } - - return true; -} -} // anonymous namespace - -// ------------------------------------------------------------------------------------------------ -// TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent -void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, bool &corrupt) { - if (length < 0x1b) { - //TokenizeError("file is too short",0); - } - - if (strncmp(input, "Kaydara FBX Binary", 18)) { - TokenizeError("magic bytes not found", 0); - } - - const char *cursor = input + 18; - /*Result ignored*/ ReadByte(input, cursor, input + length); - /*Result ignored*/ ReadByte(input, cursor, input + length); - /*Result ignored*/ ReadByte(input, cursor, input + length); - /*Result ignored*/ ReadByte(input, cursor, input + length); - /*Result ignored*/ ReadByte(input, cursor, input + length); - const uint32_t version = ReadWord(input, cursor, input + length); - print_verbose("FBX Version: " + itos(version)); - //ASSIMP_LOG_DEBUG_F("FBX version: ", version); - const bool is64bits = version >= 7500; - const char *end = input + length; - while (cursor < end) { - if (!ReadScope(output_tokens, input, cursor, input + length, is64bits, corrupt)) { - break; - } - } -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXCommon.h b/modules/fbx/fbx_parser/FBXCommon.h deleted file mode 100644 index d0d4790ba8..0000000000 --- a/modules/fbx/fbx_parser/FBXCommon.h +++ /dev/null @@ -1,110 +0,0 @@ -/*************************************************************************/ -/* FBXCommon.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXCommon.h - * Some useful constants and enums for dealing with FBX files. - */ -#ifndef FBX_COMMON_H -#define FBX_COMMON_H - -#include <string> - -namespace FBXDocParser { -const std::string NULL_RECORD = { // 13 null bytes - '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' -}; // who knows why -const std::string SEPARATOR = { '\x00', '\x01' }; // for use inside strings -const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import -const int64_t SECOND = 46186158000; // FBX's kTime unit - -// rotation order. We'll probably use EulerXYZ for everything -enum RotOrder { - RotOrder_EulerXYZ = 0, - RotOrder_EulerXZY, - RotOrder_EulerYZX, - RotOrder_EulerYXZ, - RotOrder_EulerZXY, - RotOrder_EulerZYX, - - RotOrder_SphericXYZ, - - RotOrder_MAX // end-of-enum sentinel -}; - -enum TransformInheritance { - Transform_RrSs = 0, - Transform_RSrs = 1, - Transform_Rrs = 2, - TransformInheritance_MAX // end-of-enum sentinel -}; -} // namespace FBXDocParser - -#endif // FBX_COMMON_H diff --git a/modules/fbx/fbx_parser/FBXDeformer.cpp b/modules/fbx/fbx_parser/FBXDeformer.cpp deleted file mode 100644 index a2b216ab09..0000000000 --- a/modules/fbx/fbx_parser/FBXDeformer.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/*************************************************************************/ -/* FBXDeformer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXNoteAttribute.cpp - * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation - */ - -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXMeshGeometry.h" -#include "FBXParser.h" -#include "core/math/math_funcs.h" -#include "core/math/transform_3d.h" - -#include <iostream> - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -Deformer::Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { -} - -// ------------------------------------------------------------------------------------------------ -Deformer::~Deformer() { -} - -Constraint::Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { -} - -Constraint::~Constraint() { -} - -// ------------------------------------------------------------------------------------------------ -Cluster::Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name) { - const ScopePtr sc = GetRequiredScope(element); - // for( auto element : sc.Elements()) - // { - // std::cout << "cluster element: " << element.first << std::endl; - // } - // - // element: Indexes - // element: Transform - // element: TransformAssociateModel - // element: TransformLink - // element: UserData - // element: Version - // element: Weights - - const ElementPtr Indexes = sc->GetElement("Indexes"); - const ElementPtr Weights = sc->GetElement("Weights"); - - const ElementPtr TransformAssociateModel = sc->GetElement("TransformAssociateModel"); - if (TransformAssociateModel != nullptr) { - //Transform t = ReadMatrix(*TransformAssociateModel); - link_mode = SkinLinkMode_Additive; - valid_transformAssociateModel = true; - } else { - link_mode = SkinLinkMode_Normalized; - valid_transformAssociateModel = false; - } - - const ElementPtr Transform = GetRequiredElement(sc, "Transform", element); - const ElementPtr TransformLink = GetRequiredElement(sc, "TransformLink", element); - - // todo: check if we need this - //const Element& TransformAssociateModel = GetRequiredElement(sc, "TransformAssociateModel", &element); - - transform = ReadMatrix(Transform); - transformLink = ReadMatrix(TransformLink); - - // it is actually possible that there be Deformer's with no weights - if (!!Indexes != !!Weights) { - DOMError("either Indexes or Weights are missing from Cluster", element); - } - - if (Indexes) { - ParseVectorDataArray(indices, Indexes); - ParseVectorDataArray(weights, Weights); - } - - if (indices.size() != weights.size()) { - DOMError("sizes of index and weight array don't match up", element); - } - - // read assigned node - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Model"); - for (const Connection *con : conns) { - const Model *mod = ProcessSimpleConnection<Model>(*con, false, "Model -> Cluster", element); - if (mod) { - node = mod; - break; - } - } - - if (!node) { - DOMError("failed to read target Node for Cluster", element); - node = nullptr; - } -} - -// ------------------------------------------------------------------------------------------------ -Cluster::~Cluster() { -} - -// ------------------------------------------------------------------------------------------------ -Skin::Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name) { - const ScopePtr sc = GetRequiredScope(element); - - // keep this it is used for debugging and any FBX format changes - // for (auto element : sc.Elements()) { - // std::cout << "skin element: " << element.first << std::endl; - // } - - const ElementPtr Link_DeformAcuracy = sc->GetElement("Link_DeformAcuracy"); - if (Link_DeformAcuracy) { - accuracy = ParseTokenAsFloat(GetRequiredToken(Link_DeformAcuracy, 0)); - } - - const ElementPtr SkinType = sc->GetElement("SkinningType"); - - if (SkinType) { - std::string skin_type = ParseTokenAsString(GetRequiredToken(SkinType, 0)); - - if (skin_type == "Linear") { - skinType = Skin_Linear; - } else if (skin_type == "Rigid") { - skinType = Skin_Rigid; - } else if (skin_type == "DualQuaternion") { - skinType = Skin_DualQuaternion; - } else if (skin_type == "Blend") { - skinType = Skin_Blend; - } else { - print_error("[doc:skin] could not find valid skin type: " + String(skin_type.c_str())); - } - } - - // resolve assigned clusters - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); - - // - - clusters.reserve(conns.size()); - for (const Connection *con : conns) { - const Cluster *cluster = ProcessSimpleConnection<Cluster>(*con, false, "Cluster -> Skin", element); - if (cluster) { - clusters.push_back(cluster); - continue; - } - } -} - -// ------------------------------------------------------------------------------------------------ -Skin::~Skin() { -} -// ------------------------------------------------------------------------------------------------ -BlendShape::BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name) { - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); - blendShapeChannels.reserve(conns.size()); - for (const Connection *con : conns) { - const BlendShapeChannel *bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element); - if (bspc) { - blendShapeChannels.push_back(bspc); - continue; - } - } -} -// ------------------------------------------------------------------------------------------------ -BlendShape::~BlendShape() { -} -// ------------------------------------------------------------------------------------------------ -BlendShapeChannel::BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name) { - const ScopePtr sc = GetRequiredScope(element); - const ElementPtr DeformPercent = sc->GetElement("DeformPercent"); - if (DeformPercent) { - percent = ParseTokenAsFloat(GetRequiredToken(DeformPercent, 0)); - } - const ElementPtr FullWeights = sc->GetElement("FullWeights"); - if (FullWeights) { - ParseVectorDataArray(fullWeights, FullWeights); - } - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry"); - shapeGeometries.reserve(conns.size()); - for (const Connection *con : conns) { - const ShapeGeometry *const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element); - if (sg) { - shapeGeometries.push_back(sg); - continue; - } - } -} -// ------------------------------------------------------------------------------------------------ -BlendShapeChannel::~BlendShapeChannel() { -} -// ------------------------------------------------------------------------------------------------ -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocument.cpp b/modules/fbx/fbx_parser/FBXDocument.cpp deleted file mode 100644 index 929272c972..0000000000 --- a/modules/fbx/fbx_parser/FBXDocument.cpp +++ /dev/null @@ -1,636 +0,0 @@ -/*************************************************************************/ -/* FBXDocument.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the* - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXDocument.cpp - * @brief Implementation of the FBX DOM classes - */ - -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXImportSettings.h" -#include "FBXMeshGeometry.h" -#include "FBXParser.h" -#include "FBXProperties.h" -#include "FBXUtil.h" - -#include <algorithm> -#include <functional> -#include <iostream> -#include <map> -#include <memory> - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -LazyObject::LazyObject(uint64_t id, const ElementPtr element, const Document &doc) : - doc(doc), element(element), id(id) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -LazyObject::~LazyObject() { - object.reset(); -} - -ObjectPtr LazyObject::LoadObject() { - if (IsBeingConstructed() || FailedToConstruct()) { - return nullptr; - } - - if (object) { - return object.get(); - } - - TokenPtr key = element->KeyToken(); - ERR_FAIL_COND_V(!key, nullptr); - const TokenList &tokens = element->Tokens(); - - if (tokens.size() < 3) { - //DOMError("expected at least 3 tokens: id, name and class tag",&element); - return nullptr; - } - - const char *err = nullptr; - std::string name = ParseTokenAsString(tokens[1], err); - if (err) { - DOMError(err, element); - } - - // small fix for binary reading: binary fbx files don't use - // prefixes such as Model:: in front of their names. The - // loading code expects this at many places, though! - // so convert the binary representation (a 0x0001) to the - // double colon notation. - if (tokens[1]->IsBinary()) { - for (size_t i = 0; i < name.length(); ++i) { - if (name[i] == 0x0 && name[i + 1] == 0x1) { - name = name.substr(i + 2) + "::" + name.substr(0, i); - } - } - } - - const std::string classtag = ParseTokenAsString(tokens[2], err); - if (err) { - DOMError(err, element); - } - - // prevent recursive calls - flags |= BEING_CONSTRUCTED; - - // this needs to be relatively fast since it happens a lot, - // so avoid constructing strings all the time. - const char *obtype = key->begin(); - const size_t length = static_cast<size_t>(key->end() - key->begin()); - - if (!strncmp(obtype, "Pose", length)) { - object.reset(new FbxPose(id, element, doc, name)); - } else if (!strncmp(obtype, "Geometry", length)) { - if (!strcmp(classtag.c_str(), "Mesh")) { - object.reset(new MeshGeometry(id, element, name, doc)); - } - if (!strcmp(classtag.c_str(), "Shape")) { - object.reset(new ShapeGeometry(id, element, name, doc)); - } - if (!strcmp(classtag.c_str(), "Line")) { - object.reset(new LineGeometry(id, element, name, doc)); - } - } else if (!strncmp(obtype, "NodeAttribute", length)) { - if (!strcmp(classtag.c_str(), "Camera")) { - object.reset(new Camera(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "CameraSwitcher")) { - object.reset(new CameraSwitcher(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "Light")) { - object.reset(new Light(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "Null")) { - object.reset(new Null(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "LimbNode")) { - // This is an older format for bones - // this is what blender uses I believe - object.reset(new LimbNode(id, element, doc, name)); - } - } else if (!strncmp(obtype, "Constraint", length)) { - object.reset(new Constraint(id, element, doc, name)); - } else if (!strncmp(obtype, "Deformer", length)) { - if (!strcmp(classtag.c_str(), "Cluster")) { - object.reset(new Cluster(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "Skin")) { - object.reset(new Skin(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "BlendShape")) { - object.reset(new BlendShape(id, element, doc, name)); - } else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) { - object.reset(new BlendShapeChannel(id, element, doc, name)); - } - } else if (!strncmp(obtype, "Model", length)) { - // Model is normal node - - // LimbNode model is a 'bone' node. - if (!strcmp(classtag.c_str(), "LimbNode")) { - object.reset(new ModelLimbNode(id, element, doc, name)); - - } else if (strcmp(classtag.c_str(), "IKEffector") && strcmp(classtag.c_str(), "FKEffector")) { - // FK and IK effectors are not supported. - object.reset(new Model(id, element, doc, name)); - } - } else if (!strncmp(obtype, "Material", length)) { - object.reset(new Material(id, element, doc, name)); - } else if (!strncmp(obtype, "Texture", length)) { - object.reset(new Texture(id, element, doc, name)); - } else if (!strncmp(obtype, "LayeredTexture", length)) { - object.reset(new LayeredTexture(id, element, doc, name)); - } else if (!strncmp(obtype, "Video", length)) { - object.reset(new Video(id, element, doc, name)); - } else if (!strncmp(obtype, "AnimationStack", length)) { - object.reset(new AnimationStack(id, element, name, doc)); - } else if (!strncmp(obtype, "AnimationLayer", length)) { - object.reset(new AnimationLayer(id, element, name, doc)); - } else if (!strncmp(obtype, "AnimationCurve", length)) { - object.reset(new AnimationCurve(id, element, name, doc)); - } else if (!strncmp(obtype, "AnimationCurveNode", length)) { - object.reset(new AnimationCurveNode(id, element, name, doc)); - } else { - ERR_FAIL_V_MSG(nullptr, "FBX contains unsupported object: " + String(obtype)); - } - - flags &= ~BEING_CONSTRUCTED; - - return object.get(); -} - -// ------------------------------------------------------------------------------------------------ -Object::Object(uint64_t id, const ElementPtr element, const std::string &name) : - PropertyTable(element), element(element), name(name), id(id) { -} - -// ------------------------------------------------------------------------------------------------ -Object::~Object() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -FileGlobalSettings::FileGlobalSettings(const Document &doc) : - PropertyTable(), doc(doc) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -FileGlobalSettings::~FileGlobalSettings() { -} - -// ------------------------------------------------------------------------------------------------ -Document::Document(const Parser &parser, const ImportSettings &settings) : - settings(settings), parser(parser) { - // Cannot use array default initialization syntax because vc8 fails on it - for (unsigned int &timeStamp : creationTimeStamp) { - timeStamp = 0; - } - - // we must check if we can read the header version safely, if its outdated then drop it. - if (ReadHeader()) { - SafeToImport = true; - ReadPropertyTemplates(); - - ReadGlobalSettings(); - - // This order is important, connections need parsed objects to check - // whether connections are ok or not. Objects may not be evaluated yet, - // though, since this may require valid connections. - ReadObjects(); - ReadConnections(); - } -} - -// ------------------------------------------------------------------------------------------------ -Document::~Document() { - for (PropertyTemplateMap::value_type v : templates) { - delete v.second; - } - - for (ObjectMap::value_type &v : objects) { - delete v.second; - } - - for (ConnectionMap::value_type &v : src_connections) { - delete v.second; - } - - // clear globals import pointer - globals.reset(); -} - -// ------------------------------------------------------------------------------------------------ -static const unsigned int LowerSupportedVersion = 7100; -static const unsigned int UpperSupportedVersion = 7700; - -bool Document::ReadHeader() { - // Read ID objects from "Objects" section - ScopePtr sc = parser.GetRootScope(); - ElementPtr ehead = sc->GetElement("FBXHeaderExtension"); - if (!ehead || !ehead->Compound()) { - DOMError("no FBXHeaderExtension dictionary found"); - } - - if (parser.IsCorrupt()) { - DOMError("File is corrupt"); - return false; - } - - const ScopePtr shead = ehead->Compound(); - fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead, "FBXVersion", ehead), 0)); - - // While we may have some success with newer files, we don't support - // the older 6.n fbx format - if (fbxVersion < LowerSupportedVersion) { - DOMWarning("unsupported, old format version, FBX 2015-2020, you must re-export in a more modern version of your original modelling application"); - return false; - } - if (fbxVersion > UpperSupportedVersion) { - DOMWarning("unsupported, newer format version, supported are only FBX 2015, up to FBX 2020" - " trying to read it nevertheless"); - } - - const ElementPtr ecreator = shead->GetElement("Creator"); - if (ecreator) { - creator = ParseTokenAsString(GetRequiredToken(ecreator, 0)); - } - - // Scene Info - const ElementPtr scene_info = shead->GetElement("SceneInfo"); - - if (scene_info) { - metadata_properties.Setup(scene_info); - } - - const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp"); - if (etimestamp && etimestamp->Compound()) { - const ScopePtr stimestamp = etimestamp->Compound(); - creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Year"), 0)); - creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Month"), 0)); - creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Day"), 0)); - creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Hour"), 0)); - creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Minute"), 0)); - creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Second"), 0)); - creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Millisecond"), 0)); - } - - return true; -} - -// ------------------------------------------------------------------------------------------------ -void Document::ReadGlobalSettings() { - ERR_FAIL_COND_MSG(globals != nullptr, "Global settings is already setup this is a serious error and should be reported"); - - globals = std::make_shared<FileGlobalSettings>(*this); -} - -// ------------------------------------------------------------------------------------------------ -void Document::ReadObjects() { - // read ID objects from "Objects" section - const ScopePtr sc = parser.GetRootScope(); - const ElementPtr eobjects = sc->GetElement("Objects"); - if (!eobjects || !eobjects->Compound()) { - DOMError("no Objects dictionary found"); - } - - // add a dummy entry to represent the Model::RootNode object (id 0), - // which is only indirectly defined in the input file - objects[0] = new LazyObject(0L, eobjects, *this); - - const ScopePtr sobjects = eobjects->Compound(); - for (const ElementMap::value_type &iter : sobjects->Elements()) { - // extract ID - const TokenList &tok = iter.second->Tokens(); - - if (tok.empty()) { - DOMError("expected ID after object key", iter.second); - } - - const char *err; - const uint64_t id = ParseTokenAsID(tok[0], err); - if (err) { - DOMError(err, iter.second); - } - - // id=0 is normally implicit - if (id == 0L) { - DOMError("encountered object with implicitly defined id 0", iter.second); - } - - if (objects.find(id) != objects.end()) { - DOMWarning("encountered duplicate object id, ignoring first occurrence", iter.second); - } - - objects[id] = new LazyObject(id, iter.second, *this); - - // grab all animation stacks upfront since there is no listing of them - if (!strcmp(iter.first.c_str(), "AnimationStack")) { - animationStacks.push_back(id); - } else if (!strcmp(iter.first.c_str(), "Constraint")) { - constraints.push_back(id); - } else if (!strcmp(iter.first.c_str(), "Pose")) { - bind_poses.push_back(id); - } else if (!strcmp(iter.first.c_str(), "Material")) { - materials.push_back(id); - } else if (!strcmp(iter.first.c_str(), "Deformer")) { - TokenPtr key = iter.second->KeyToken(); - ERR_CONTINUE_MSG(!key, "[parser bug] invalid token key for deformer"); - const TokenList &tokens = iter.second->Tokens(); - const std::string class_tag = ParseTokenAsString(tokens[2], err); - - if (err) { - DOMError(err, iter.second); - } - - if (class_tag == "Skin") { - //print_verbose("registered skin:" + itos(id)); - skins.push_back(id); - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -void Document::ReadPropertyTemplates() { -} - -// ------------------------------------------------------------------------------------------------ -void Document::ReadConnections() { - const ScopePtr sc = parser.GetRootScope(); - - // read property templates from "Definitions" section - const ElementPtr econns = sc->GetElement("Connections"); - if (!econns || !econns->Compound()) { - DOMError("no Connections dictionary found"); - } - - uint64_t insertionOrder = 0l; - const ScopePtr sconns = econns->Compound(); - const ElementCollection conns = sconns->GetCollection("C"); - for (ElementMap::const_iterator it = conns.first; it != conns.second; ++it) { - const ElementPtr el = (*it).second; - const std::string &type = ParseTokenAsString(GetRequiredToken(el, 0)); - - // PP = property-property connection, ignored for now - // (tokens: "PP", ID1, "Property1", ID2, "Property2") - if (type == "PP") { - continue; - } - - const uint64_t src = ParseTokenAsID(GetRequiredToken(el, 1)); - const uint64_t dest = ParseTokenAsID(GetRequiredToken(el, 2)); - - // OO = object-object connection - // OP = object-property connection, in which case the destination property follows the object ID - const std::string &prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el, 3)) : ""); - - if (objects.find(src) == objects.end()) { - DOMWarning("source object for connection does not exist", el); - continue; - } - - // dest may be 0 (root node) but we added a dummy object before - if (objects.find(dest) == objects.end()) { - DOMWarning("destination object for connection does not exist", el); - continue; - } - - // add new connection - const Connection *const c = new Connection(insertionOrder++, src, dest, prop, *this); - src_connections.insert(ConnectionMap::value_type(src, c)); - dest_connections.insert(ConnectionMap::value_type(dest, c)); - } -} - -// ------------------------------------------------------------------------------------------------ -const std::vector<const AnimationStack *> &Document::AnimationStacks() const { - if (!animationStacksResolved.empty() || animationStacks.empty()) { - return animationStacksResolved; - } - - animationStacksResolved.reserve(animationStacks.size()); - for (uint64_t id : animationStacks) { - LazyObject *lazy = GetObject(id); - - // Two things happen here: - // We cast internally an Object PTR to an Animation Stack PTR - // We return invalid weak_ptrs for objects which are invalid - - const AnimationStack *stack = lazy->Get<AnimationStack>(); - ERR_CONTINUE_MSG(!stack, "invalid ptr to AnimationStack - conversion failure"); - - // We push back the weak reference :) to keep things simple, as ownership is on the parser side so it won't be cleaned up. - animationStacksResolved.push_back(stack); - } - - return animationStacksResolved; -} - -// ------------------------------------------------------------------------------------------------ -LazyObject *Document::GetObject(uint64_t id) const { - ObjectMap::const_iterator it = objects.find(id); - return it == objects.end() ? nullptr : (*it).second; -} - -#define MAX_CLASSNAMES 6 - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap &conns) const { - std::vector<const Connection *> temp; - - const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range = - conns.equal_range(id); - - temp.reserve(std::distance(range.first, range.second)); - for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { - temp.push_back((*it).second); - } - - std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); - - return temp; // NRVO should handle this -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, bool is_src, - const ConnectionMap &conns, - const char *const *classnames, - size_t count) const - -{ - size_t lengths[MAX_CLASSNAMES]; - - const size_t c = count; - for (size_t i = 0; i < c; ++i) { - lengths[i] = strlen(classnames[i]); - } - - std::vector<const Connection *> temp; - const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range = - conns.equal_range(id); - - temp.reserve(std::distance(range.first, range.second)); - for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { - TokenPtr key = (is_src ? (*it).second->LazyDestinationObject() : (*it).second->LazySourceObject())->GetElement()->KeyToken(); - - const char *obtype = key->begin(); - - for (size_t i = 0; i < c; ++i) { - //ai_assert(classnames[i]); - if (static_cast<size_t>(std::distance(key->begin(), key->end())) == lengths[i] && !strncmp(classnames[i], obtype, lengths[i])) { - obtype = nullptr; - break; - } - } - - if (obtype) { - continue; - } - - temp.push_back((*it).second); - } - - std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); - return temp; // NRVO should handle this -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source) const { - return GetConnectionsSequenced(source, ConnectionsBySource()); -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t src, const char *classname) const { - const char *arr[] = { classname }; - return GetConnectionsBySourceSequenced(src, arr, 1); -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source, - const char *const *classnames, size_t count) const { - return GetConnectionsSequenced(source, true, ConnectionsBySource(), classnames, count); -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest, - const char *classname) const { - const char *arr[] = { classname }; - return GetConnectionsByDestinationSequenced(dest, arr, 1); -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const { - return GetConnectionsSequenced(dest, ConnectionsByDestination()); -} - -// ------------------------------------------------------------------------------------------------ -std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest, - const char *const *classnames, size_t count) const { - return GetConnectionsSequenced(dest, false, ConnectionsByDestination(), classnames, count); -} - -// ------------------------------------------------------------------------------------------------ -Connection::Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop, - const Document &doc) : - insertionOrder(insertionOrder), prop(prop), src(src), dest(dest), doc(doc) { -} - -// ------------------------------------------------------------------------------------------------ -Connection::~Connection() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -LazyObject *Connection::LazySourceObject() const { - LazyObject *const lazy = doc.GetObject(src); - return lazy; -} - -// ------------------------------------------------------------------------------------------------ -LazyObject *Connection::LazyDestinationObject() const { - LazyObject *const lazy = doc.GetObject(dest); - return lazy; -} - -// ------------------------------------------------------------------------------------------------ -Object *Connection::SourceObject() const { - LazyObject *lazy = doc.GetObject(src); - //ai_assert(lazy); - return lazy->LoadObject(); -} - -// ------------------------------------------------------------------------------------------------ -Object *Connection::DestinationObject() const { - LazyObject *lazy = doc.GetObject(dest); - //ai_assert(lazy); - return lazy->LoadObject(); -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocument.h b/modules/fbx/fbx_parser/FBXDocument.h deleted file mode 100644 index 5bf280a1dc..0000000000 --- a/modules/fbx/fbx_parser/FBXDocument.h +++ /dev/null @@ -1,1252 +0,0 @@ -/*************************************************************************/ -/* FBXDocument.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/** @file FBXDocument.h - * @brief FBX DOM - */ -#ifndef FBX_DOCUMENT_H -#define FBX_DOCUMENT_H - -#include "FBXCommon.h" -#include "FBXParser.h" -#include "FBXProperties.h" -#include "core/math/transform_3d.h" -#include "core/math/vector2.h" -#include "core/math/vector3.h" -#include "core/string/print_string.h" -#include <stdint.h> -#include <numeric> - -#define _AI_CONCAT(a, b) a##b -#define AI_CONCAT(a, b) _AI_CONCAT(a, b) - -namespace FBXDocParser { - -class Parser; -class Object; -struct ImportSettings; -class Connection; - -class PropertyTable; -class Document; -class Material; -class ShapeGeometry; -class LineGeometry; -class Geometry; - -class Video; - -class AnimationCurve; -class AnimationCurveNode; -class AnimationLayer; -class AnimationStack; - -class BlendShapeChannel; -class BlendShape; -class Skin; -class Cluster; - -typedef Object *ObjectPtr; -#define new_Object new Object - -/** Represents a delay-parsed FBX objects. Many objects in the scene - * are not needed by assimp, so it makes no sense to parse them - * upfront. */ -class LazyObject { -public: - LazyObject(uint64_t id, const ElementPtr element, const Document &doc); - ~LazyObject(); - - ObjectPtr LoadObject(); - - /* Casting weak pointers to their templated type safely and preserving ref counting and safety - * with lock() keyword to prevent leaking memory - */ - template <typename T> - const T *Get() { - ObjectPtr ob = LoadObject(); - return dynamic_cast<const T *>(ob); - } - - uint64_t ID() const { - return id; - } - - bool IsBeingConstructed() const { - return (flags & BEING_CONSTRUCTED) != 0; - } - - bool FailedToConstruct() const { - return (flags & FAILED_TO_CONSTRUCT) != 0; - } - - ElementPtr GetElement() const { - return element; - } - - const Document &GetDocument() const { - return doc; - } - -private: - const Document &doc; - ElementPtr element = nullptr; - std::shared_ptr<Object> object = nullptr; - const uint64_t id = 0; - - enum Flags { - BEING_CONSTRUCTED = 0x1, - FAILED_TO_CONSTRUCT = 0x2 - }; - - unsigned int flags = 0; -}; - -/** Base class for in-memory (DOM) representations of FBX objects */ -class Object : public PropertyTable { -public: - Object(uint64_t id, const ElementPtr element, const std::string &name); - - virtual ~Object(); - - ElementPtr SourceElement() const { - return element; - } - - const std::string &Name() const { - return name; - } - - uint64_t ID() const { - return id; - } - -protected: - const ElementPtr element = nullptr; - const std::string name; - const uint64_t id; -}; - -/** DOM class for generic FBX NoteAttribute blocks. NoteAttribute's just hold a property table, - * fixed members are added by deriving classes. */ -class NodeAttribute : public Object { -public: - NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~NodeAttribute(); -}; - -/** DOM base class for FBX camera settings attached to a node */ -class CameraSwitcher : public NodeAttribute { -public: - CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~CameraSwitcher(); - - int CameraID() const { - return cameraId; - } - - const std::string &CameraName() const { - return cameraName; - } - - const std::string &CameraIndexName() const { - return cameraIndexName; - } - -private: - int cameraId = 0; - std::string cameraName; - std::string cameraIndexName; -}; - -#define fbx_stringize(a) #a - -#define fbx_simple_property(name, type, default_value) \ - type name() const { \ - return PropertyGet<type>(this, fbx_stringize(name), (default_value)); \ - } - -// XXX improve logging -#define fbx_simple_enum_property(name, type, default_value) \ - type name() const { \ - const int ival = PropertyGet<int>(this, fbx_stringize(name), static_cast<int>(default_value)); \ - if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) { \ - return static_cast<type>(default_value); \ - } \ - return static_cast<type>(ival); \ - } - -class FbxPoseNode; -class FbxPose : public Object { -public: - FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - const std::vector<FbxPoseNode *> &GetBindPoses() const { - return pose_nodes; - } - - virtual ~FbxPose(); - -private: - std::vector<FbxPoseNode *> pose_nodes; -}; - -class FbxPoseNode { -public: - FbxPoseNode(const ElementPtr element, const Document &doc, const std::string &name) { - const ScopePtr sc = GetRequiredScope(element); - - // get pose node transform - const ElementPtr Transform = GetRequiredElement(sc, "Matrix", element); - transform = ReadMatrix(Transform); - - // get node id this pose node is for - const ElementPtr NodeId = sc->GetElement("Node3D"); - if (NodeId) { - target_id = ParseTokenAsInt64(GetRequiredToken(NodeId, 0)); - } - - print_verbose("added posenode " + itos(target_id) + " transform: " + transform); - } - virtual ~FbxPoseNode() { - } - - uint64_t GetNodeID() const { - return target_id; - } - - Transform3D GetBindPose() const { - return transform; - } - -private: - uint64_t target_id = 0; - Transform3D transform; -}; - -/** DOM base class for FBX cameras attached to a node */ -class Camera : public NodeAttribute { -public: - Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Camera(); - - fbx_simple_property(Position, Vector3, Vector3(0, 0, 0)); - fbx_simple_property(UpVector, Vector3, Vector3(0, 1, 0)); - fbx_simple_property(InterestPosition, Vector3, Vector3(0, 0, 0)); - - fbx_simple_property(AspectWidth, float, 1.0f); - fbx_simple_property(AspectHeight, float, 1.0f); - fbx_simple_property(FilmWidth, float, 1.0f); - fbx_simple_property(FilmHeight, float, 1.0f); - - fbx_simple_property(NearPlane, float, 0.1f); - fbx_simple_property(FarPlane, float, 100.0f); - - fbx_simple_property(FilmAspectRatio, float, 1.0f); - fbx_simple_property(ApertureMode, int, 0); - - fbx_simple_property(FieldOfView, float, 1.0f); - fbx_simple_property(FocalLength, float, 1.0f); -}; - -/** DOM base class for FBX null markers attached to a node */ -class Null : public NodeAttribute { -public: - Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Null(); -}; - -/** DOM base class for FBX limb node markers attached to a node */ -class LimbNode : public NodeAttribute { -public: - LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~LimbNode(); -}; - -/** DOM base class for FBX lights attached to a node */ -class Light : public NodeAttribute { -public: - Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Light(); - - enum Type { - Type_Point, - Type_Directional, - Type_Spot, - Type_Area, - Type_Volume, - - Type_MAX // end-of-enum sentinel - }; - - enum Decay { - Decay_None, - Decay_Linear, - Decay_Quadratic, - Decay_Cubic, - - Decay_MAX // end-of-enum sentinel - }; - - fbx_simple_property(Color, Vector3, Vector3(1, 1, 1)); - fbx_simple_enum_property(LightType, Type, 0); - fbx_simple_property(CastLightOnObject, bool, false); - fbx_simple_property(DrawVolumetricLight, bool, true); - fbx_simple_property(DrawGroundProjection, bool, true); - fbx_simple_property(DrawFrontFacingVolumetricLight, bool, false); - fbx_simple_property(Intensity, float, 100.0f); - fbx_simple_property(InnerAngle, float, 0.0f); - fbx_simple_property(OuterAngle, float, 45.0f); - fbx_simple_property(Fog, int, 50); - fbx_simple_enum_property(DecayType, Decay, 2); - fbx_simple_property(DecayStart, float, 1.0f); - fbx_simple_property(FileName, std::string, ""); - - fbx_simple_property(EnableNearAttenuation, bool, false); - fbx_simple_property(NearAttenuationStart, float, 0.0f); - fbx_simple_property(NearAttenuationEnd, float, 0.0f); - fbx_simple_property(EnableFarAttenuation, bool, false); - fbx_simple_property(FarAttenuationStart, float, 0.0f); - fbx_simple_property(FarAttenuationEnd, float, 0.0f); - - fbx_simple_property(CastShadows, bool, true); - fbx_simple_property(ShadowColor, Vector3, Vector3(0, 0, 0)); - - fbx_simple_property(AreaLightShape, int, 0); - - fbx_simple_property(LeftBarnDoor, float, 20.0f); - fbx_simple_property(RightBarnDoor, float, 20.0f); - fbx_simple_property(TopBarnDoor, float, 20.0f); - fbx_simple_property(BottomBarnDoor, float, 20.0f); - fbx_simple_property(EnableBarnDoor, bool, true); -}; - -class Model; - -typedef Model *ModelPtr; -#define new_Model new Model - -/** DOM base class for FBX models (even though its semantics are more "node" than "model" */ -class Model : public Object { -public: - enum RotOrder { - RotOrder_EulerXYZ = 0, - RotOrder_EulerXZY, - RotOrder_EulerYZX, - RotOrder_EulerYXZ, - RotOrder_EulerZXY, - RotOrder_EulerZYX, - - RotOrder_SphericXYZ, - - RotOrder_MAX // end-of-enum sentinel - }; - - Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Model(); - - fbx_simple_property(QuaternionInterpolate, int, 0); - - fbx_simple_property(RotationOffset, Vector3, Vector3()); - fbx_simple_property(RotationPivot, Vector3, Vector3()); - fbx_simple_property(ScalingOffset, Vector3, Vector3()); - fbx_simple_property(ScalingPivot, Vector3, Vector3()); - fbx_simple_property(TranslationActive, bool, false); - fbx_simple_property(TranslationMin, Vector3, Vector3()); - fbx_simple_property(TranslationMax, Vector3, Vector3()); - - fbx_simple_property(TranslationMinX, bool, false); - fbx_simple_property(TranslationMaxX, bool, false); - fbx_simple_property(TranslationMinY, bool, false); - fbx_simple_property(TranslationMaxY, bool, false); - fbx_simple_property(TranslationMinZ, bool, false); - fbx_simple_property(TranslationMaxZ, bool, false); - - fbx_simple_enum_property(RotationOrder, RotOrder, 0); - fbx_simple_property(RotationSpaceForLimitOnly, bool, false); - fbx_simple_property(RotationStiffnessX, float, 0.0f); - fbx_simple_property(RotationStiffnessY, float, 0.0f); - fbx_simple_property(RotationStiffnessZ, float, 0.0f); - fbx_simple_property(AxisLen, float, 0.0f); - - fbx_simple_property(PreRotation, Vector3, Vector3()); - fbx_simple_property(PostRotation, Vector3, Vector3()); - fbx_simple_property(RotationActive, bool, false); - - fbx_simple_property(RotationMin, Vector3, Vector3()); - fbx_simple_property(RotationMax, Vector3, Vector3()); - - fbx_simple_property(RotationMinX, bool, false); - fbx_simple_property(RotationMaxX, bool, false); - fbx_simple_property(RotationMinY, bool, false); - fbx_simple_property(RotationMaxY, bool, false); - fbx_simple_property(RotationMinZ, bool, false); - fbx_simple_property(RotationMaxZ, bool, false); - fbx_simple_enum_property(InheritType, TransformInheritance, 0); - - fbx_simple_property(ScalingActive, bool, false); - fbx_simple_property(ScalingMin, Vector3, Vector3()); - fbx_simple_property(ScalingMax, Vector3, Vector3(1, 1, 1)); - fbx_simple_property(ScalingMinX, bool, false); - fbx_simple_property(ScalingMaxX, bool, false); - fbx_simple_property(ScalingMinY, bool, false); - fbx_simple_property(ScalingMaxY, bool, false); - fbx_simple_property(ScalingMinZ, bool, false); - fbx_simple_property(ScalingMaxZ, bool, false); - - fbx_simple_property(GeometricTranslation, Vector3, Vector3()); - fbx_simple_property(GeometricRotation, Vector3, Vector3()); - fbx_simple_property(GeometricScaling, Vector3, Vector3(1, 1, 1)); - - fbx_simple_property(MinDampRangeX, float, 0.0f); - fbx_simple_property(MinDampRangeY, float, 0.0f); - fbx_simple_property(MinDampRangeZ, float, 0.0f); - fbx_simple_property(MaxDampRangeX, float, 0.0f); - fbx_simple_property(MaxDampRangeY, float, 0.0f); - fbx_simple_property(MaxDampRangeZ, float, 0.0f); - - fbx_simple_property(MinDampStrengthX, float, 0.0f); - fbx_simple_property(MinDampStrengthY, float, 0.0f); - fbx_simple_property(MinDampStrengthZ, float, 0.0f); - fbx_simple_property(MaxDampStrengthX, float, 0.0f); - fbx_simple_property(MaxDampStrengthY, float, 0.0f); - fbx_simple_property(MaxDampStrengthZ, float, 0.0f); - - fbx_simple_property(PreferredAngleX, float, 0.0f); - fbx_simple_property(PreferredAngleY, float, 0.0f); - fbx_simple_property(PreferredAngleZ, float, 0.0f); - - fbx_simple_property(Show, bool, true); - fbx_simple_property(LODBox, bool, false); - fbx_simple_property(Freeze, bool, false); - - const std::string &Shading() const { - return shading; - } - - const std::string &Culling() const { - return culling; - } - - /** Get material links */ - const std::vector<const Material *> &GetMaterials() const { - return materials; - } - - /** Get geometry links */ - const std::vector<const Geometry *> &GetGeometry() const { - return geometry; - } - - /** Get node attachments */ - const std::vector<const NodeAttribute *> &GetAttributes() const { - return attributes; - } - - /** convenience method to check if the node has a Null node marker */ - bool IsNull() const; - -private: - void ResolveLinks(const ElementPtr element, const Document &doc); - -private: - std::vector<const Material *> materials; - std::vector<const Geometry *> geometry; - std::vector<const NodeAttribute *> attributes; - - std::string shading; - std::string culling; -}; - -class ModelLimbNode : public Model { -public: - ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~ModelLimbNode(); -}; - -/** DOM class for generic FBX textures */ -class Texture : public Object { -public: - Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Texture(); - - const std::string &Type() const { - return type; - } - - const std::string &FileName() const { - return fileName; - } - - const std::string &RelativeFilename() const { - return relativeFileName; - } - - const std::string &AlphaSource() const { - return alphaSource; - } - - const Vector2 &UVTranslation() const { - return uvTrans; - } - - const Vector2 &UVScaling() const { - return uvScaling; - } - - // return a 4-tuple - const unsigned int *Crop() const { - return crop; - } - - const Video *Media() const { - return media; - } - -private: - Vector2 uvTrans; - Vector2 uvScaling; - - std::string type; - std::string relativeFileName; - std::string fileName; - std::string alphaSource; - - unsigned int crop[4] = { 0 }; - const Video *media = nullptr; -}; - -/** DOM class for layered FBX textures */ -class LayeredTexture : public Object { -public: - LayeredTexture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~LayeredTexture(); - - // Can only be called after construction of the layered texture object due to construction flag. - void fillTexture(const Document &doc); - - enum BlendMode { - BlendMode_Translucent, - BlendMode_Additive, - BlendMode_Modulate, - BlendMode_Modulate2, - BlendMode_Over, - BlendMode_Normal, - BlendMode_Dissolve, - BlendMode_Darken, - BlendMode_ColorBurn, - BlendMode_LinearBurn, - BlendMode_DarkerColor, - BlendMode_Lighten, - BlendMode_Screen, - BlendMode_ColorDodge, - BlendMode_LinearDodge, - BlendMode_LighterColor, - BlendMode_SoftLight, - BlendMode_HardLight, - BlendMode_VividLight, - BlendMode_LinearLight, - BlendMode_PinLight, - BlendMode_HardMix, - BlendMode_Difference, - BlendMode_Exclusion, - BlendMode_Subtract, - BlendMode_Divide, - BlendMode_Hue, - BlendMode_Saturation, - BlendMode_Color, - BlendMode_Luminosity, - BlendMode_Overlay, - BlendMode_BlendModeCount - }; - - const Texture *getTexture(int index = 0) const { - return textures[index]; - } - int textureCount() const { - return static_cast<int>(textures.size()); - } - BlendMode GetBlendMode() const { - return blendMode; - } - float Alpha() { - return alpha; - } - -private: - std::vector<const Texture *> textures; - BlendMode blendMode = BlendMode::BlendMode_Additive; - float alpha = 0; -}; - -typedef std::map<std::string, const Texture *> TextureMap; -typedef std::map<std::string, const LayeredTexture *> LayeredTextureMap; - -/** DOM class for generic FBX videos */ -class Video : public Object { -public: - Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~Video(); - - const std::string &Type() const { - return type; - } - - bool IsEmbedded() const { - return contentLength > 0; - } - - const std::string &FileName() const { - return fileName; - } - - const std::string &RelativeFilename() const { - return relativeFileName; - } - - const uint8_t *Content() const { - return content; - } - - uint64_t ContentLength() const { - return contentLength; - } - - uint8_t *RelinquishContent() { - uint8_t *ptr = content; - content = nullptr; - return ptr; - } - - bool operator==(const Video &other) const { - return ( - type == other.type && relativeFileName == other.relativeFileName && fileName == other.fileName); - } - - bool operator<(const Video &other) const { - return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName); - } - -private: - std::string type; - std::string relativeFileName; - std::string fileName; - - uint64_t contentLength = 0; - uint8_t *content = nullptr; -}; - -/** DOM class for generic FBX materials */ -class Material : public Object { -public: - Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~Material(); - - const std::string &GetShadingModel() const { - return shading; - } - - bool IsMultilayer() const { - return multilayer; - } - - const TextureMap &Textures() const { - return textures; - } - - const LayeredTextureMap &LayeredTextures() const { - return layeredTextures; - } - -private: - std::string shading; - bool multilayer = false; - - TextureMap textures; - LayeredTextureMap layeredTextures; -}; - -// signed int keys (this can happen!) -typedef std::vector<int64_t> KeyTimeList; -typedef std::vector<float> KeyValueList; - -/** Represents a FBX animation curve (i.e. a 1-dimensional set of keyframes and values therefore) */ -class AnimationCurve : public Object { -public: - AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - virtual ~AnimationCurve(); - - /** get list of keyframe positions (time). - * Invariant: |GetKeys()| > 0 */ - const KeyTimeList &GetKeys() const { - return keys; - } - - /** get list of keyframe values. - * Invariant: |GetKeys()| == |GetValues()| && |GetKeys()| > 0*/ - const KeyValueList &GetValues() const { - return values; - } - - const std::map<int64_t, float> &GetValueTimeTrack() const { - return keyvalues; - } - - const std::vector<float> &GetAttributes() const { - return attributes; - } - - const std::vector<unsigned int> &GetFlags() const { - return flags; - } - -private: - KeyTimeList keys; - KeyValueList values; - std::vector<float> attributes; - std::map<int64_t, float> keyvalues; - std::vector<unsigned int> flags; -}; - -/* Typedef for pointers for the animation handler */ -typedef std::shared_ptr<AnimationCurve> AnimationCurvePtr; -typedef std::weak_ptr<AnimationCurve> AnimationCurveWeakPtr; -typedef std::map<std::string, const AnimationCurve *> AnimationMap; - -/* Animation Curve node ptr */ -typedef std::shared_ptr<AnimationCurveNode> AnimationCurveNodePtr; -typedef std::weak_ptr<AnimationCurveNode> AnimationCurveNodeWeakPtr; - -/** Represents a FBX animation curve (i.e. a mapping from single animation curves to nodes) */ -class AnimationCurveNode : public Object { -public: - /* the optional white list specifies a list of property names for which the caller - wants animations for. If the curve node does not match one of these, std::range_error - will be thrown. */ - AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc, - const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0); - - virtual ~AnimationCurveNode(); - - const AnimationMap &Curves() const; - - /** Object the curve is assigned to, this can be nullptr if the - * target object has no DOM representation or could not - * be read for other reasons.*/ - Object *Target() const { - return target; - } - - Model *TargetAsModel() const { - return dynamic_cast<Model *>(target); - } - - NodeAttribute *TargetAsNodeAttribute() const { - return dynamic_cast<NodeAttribute *>(target); - } - - /** Property of Target() that is being animated*/ - const std::string &TargetProperty() const { - return prop; - } - -private: - Object *target = nullptr; - mutable AnimationMap curves; - std::string prop; - const Document &doc; -}; - -typedef std::vector<const AnimationCurveNode *> AnimationCurveNodeList; - -typedef std::shared_ptr<AnimationLayer> AnimationLayerPtr; -typedef std::weak_ptr<AnimationLayer> AnimationLayerWeakPtr; -typedef std::vector<const AnimationLayer *> AnimationLayerList; - -/** Represents a FBX animation layer (i.e. a list of node animations) */ -class AnimationLayer : public Object { -public: - AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - virtual ~AnimationLayer(); - - /* the optional white list specifies a list of property names for which the caller - wants animations for. Curves not matching this list will not be added to the - animation layer. */ - const AnimationCurveNodeList Nodes(const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0) const; - -private: - const Document &doc; -}; - -/** Represents a FBX animation stack (i.e. a list of animation layers) */ -class AnimationStack : public Object { -public: - AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - virtual ~AnimationStack(); - - fbx_simple_property(LocalStart, int64_t, 0L); - fbx_simple_property(LocalStop, int64_t, 0L); - fbx_simple_property(ReferenceStart, int64_t, 0L); - fbx_simple_property(ReferenceStop, int64_t, 0L); - - const AnimationLayerList &Layers() const { - return layers; - } - -private: - AnimationLayerList layers; -}; - -/** DOM class for deformers */ -class Deformer : public Object { -public: - Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Deformer(); -}; - -/** Constraints are from Maya they can help us with BoneAttachments :) **/ -class Constraint : public Object { -public: - Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Constraint(); -}; - -typedef std::vector<float> WeightArray; -typedef std::vector<unsigned int> WeightIndexArray; - -/** DOM class for BlendShapeChannel deformers */ -class BlendShapeChannel : public Deformer { -public: - BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~BlendShapeChannel(); - - float DeformPercent() const { - return percent; - } - - const WeightArray &GetFullWeights() const { - return fullWeights; - } - - const std::vector<const ShapeGeometry *> &GetShapeGeometries() const { - return shapeGeometries; - } - -private: - float percent = 0; - WeightArray fullWeights; - std::vector<const ShapeGeometry *> shapeGeometries; -}; - -/** DOM class for BlendShape deformers */ -class BlendShape : public Deformer { -public: - BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~BlendShape(); - - const std::vector<const BlendShapeChannel *> &BlendShapeChannels() const { - return blendShapeChannels; - } - -private: - std::vector<const BlendShapeChannel *> blendShapeChannels; -}; - -/** DOM class for skin deformer clusters (aka sub-deformers) */ -class Cluster : public Deformer { -public: - Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~Cluster(); - - /** get the list of deformer weights associated with this cluster. - * Use #GetIndices() to get the associated vertices. Both arrays - * have the same size (and may also be empty). */ - const std::vector<float> &GetWeights() const { - return weights; - } - - /** get indices into the vertex data of the geometry associated - * with this cluster. Use #GetWeights() to get the associated weights. - * Both arrays have the same size (and may also be empty). */ - const std::vector<unsigned int> &GetIndices() const { - return indices; - } - - /** */ - const Transform3D &GetTransform() const { - return transform; - } - - const Transform3D &TransformLink() const { - return transformLink; - } - - const Model *TargetNode() const { - return node; - } - - const Transform3D &TransformAssociateModel() const { - return transformAssociateModel; - } - - bool TransformAssociateModelValid() const { - return valid_transformAssociateModel; - } - - // property is not in the fbx file - // if the cluster has an associate model - // we then have an additive type - enum SkinLinkMode { - SkinLinkMode_Normalized = 0, - SkinLinkMode_Additive = 1 - }; - - SkinLinkMode GetLinkMode() { - return link_mode; - } - -private: - std::vector<float> weights; - std::vector<unsigned int> indices; - - Transform3D transform; - Transform3D transformLink; - Transform3D transformAssociateModel; - SkinLinkMode link_mode; - bool valid_transformAssociateModel = false; - const Model *node = nullptr; -}; - -/** DOM class for skin deformers */ -class Skin : public Deformer { -public: - Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - - virtual ~Skin(); - - float DeformAccuracy() const { - return accuracy; - } - - const std::vector<const Cluster *> &Clusters() const { - return clusters; - } - - enum SkinType { - Skin_Rigid = 0, - Skin_Linear, - Skin_DualQuaternion, - Skin_Blend - }; - - const SkinType &GetSkinType() const { - return skinType; - } - -private: - float accuracy = 0; - SkinType skinType = SkinType::Skin_Linear; - std::vector<const Cluster *> clusters; -}; - -/** Represents a link between two FBX objects. */ -class Connection { -public: - Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop, const Document &doc); - ~Connection(); - - // note: a connection ensures that the source and dest objects exist, but - // not that they have DOM representations, so the return value of one of - // these functions can still be nullptr. - Object *SourceObject() const; - Object *DestinationObject() const; - - // these, however, are always guaranteed to be valid - LazyObject *LazySourceObject() const; - LazyObject *LazyDestinationObject() const; - - /** return the name of the property the connection is attached to. - * this is an empty string for object to object (OO) connections. */ - const std::string &PropertyName() const { - return prop; - } - - uint64_t InsertionOrder() const { - return insertionOrder; - } - - int CompareTo(const Connection *c) const { - //ai_assert(nullptr != c); - - // note: can't subtract because this would overflow uint64_t - if (InsertionOrder() > c->InsertionOrder()) { - return 1; - } else if (InsertionOrder() < c->InsertionOrder()) { - return -1; - } - return 0; - } - - bool Compare(const Connection *c) const { - //ai_assert(nullptr != c); - - return InsertionOrder() < c->InsertionOrder(); - } - -public: - uint64_t insertionOrder = 0; - const std::string prop; - - uint64_t src = 0, dest = 0; - const Document &doc; -}; - -// XXX again, unique_ptr would be useful. shared_ptr is too -// bloated since the objects have a well-defined single owner -// during their entire lifetime (Document). FBX files have -// up to many thousands of objects (most of which we never use), -// so the memory overhead for them should be kept at a minimum. -typedef std::map<uint64_t, LazyObject *> ObjectMap; -typedef std::map<std::string, const PropertyTable *> PropertyTemplateMap; -typedef std::multimap<uint64_t, const Connection *> ConnectionMap; - -/** DOM class for global document settings, a single instance per document can - * be accessed via Document.Globals(). */ -class FileGlobalSettings : public PropertyTable { -public: - FileGlobalSettings(const Document &doc); - virtual ~FileGlobalSettings(); - - const Document &GetDocument() const { - return doc; - } - - fbx_simple_property(UpAxis, int, 1); - fbx_simple_property(UpAxisSign, int, 1); - fbx_simple_property(FrontAxis, int, 2); - fbx_simple_property(FrontAxisSign, int, 1); - fbx_simple_property(CoordAxis, int, 0); - fbx_simple_property(CoordAxisSign, int, 1); - fbx_simple_property(OriginalUpAxis, int, 0); - fbx_simple_property(OriginalUpAxisSign, int, 1); - fbx_simple_property(UnitScaleFactor, float, 1); - fbx_simple_property(OriginalUnitScaleFactor, float, 1); - fbx_simple_property(AmbientColor, Vector3, Vector3(0, 0, 0)); - fbx_simple_property(DefaultCamera, std::string, ""); - - enum FrameRate { - FrameRate_DEFAULT = 0, - FrameRate_120 = 1, - FrameRate_100 = 2, - FrameRate_60 = 3, - FrameRate_50 = 4, - FrameRate_48 = 5, - FrameRate_30 = 6, - FrameRate_30_DROP = 7, - FrameRate_NTSC_DROP_FRAME = 8, - FrameRate_NTSC_FULL_FRAME = 9, - FrameRate_PAL = 10, - FrameRate_CINEMA = 11, - FrameRate_1000 = 12, - FrameRate_CINEMA_ND = 13, - FrameRate_CUSTOM = 14, - - FrameRate_MAX // end-of-enum sentinel - }; - - fbx_simple_enum_property(TimeMode, FrameRate, FrameRate_DEFAULT); - fbx_simple_property(TimeSpanStart, uint64_t, 0L); - fbx_simple_property(TimeSpanStop, uint64_t, 0L); - fbx_simple_property(CustomFrameRate, float, -1.0f); - -private: - const Document &doc; -}; - -/** DOM root for a FBX file */ -class Document { -public: - Document(const Parser &parser, const ImportSettings &settings); - - ~Document(); - - LazyObject *GetObject(uint64_t id) const; - - bool IsSafeToImport() const { - return SafeToImport; - } - - bool IsBinary() const { - return parser.IsBinary(); - } - - unsigned int FBXVersion() const { - return fbxVersion; - } - - const std::string &Creator() const { - return creator; - } - - // elements (in this order): Year, Month, Day, Hour, Second, Millisecond - const unsigned int *CreationTimeStamp() const { - return creationTimeStamp; - } - - const FileGlobalSettings *GlobalSettingsPtr() const { - return globals.get(); - } - - const PropertyTable &GetMetadataProperties() const { - return metadata_properties; - } - - const PropertyTemplateMap &Templates() const { - return templates; - } - - const ObjectMap &Objects() const { - return objects; - } - - const ImportSettings &Settings() const { - return settings; - } - - const ConnectionMap &ConnectionsBySource() const { - return src_connections; - } - - const ConnectionMap &ConnectionsByDestination() const { - return dest_connections; - } - - // note: the implicit rule in all DOM classes is to always resolve - // from destination to source (since the FBX object hierarchy is, - // with very few exceptions, a DAG, this avoids cycles). In all - // cases that may involve back-facing edges in the object graph, - // use LazyObject::IsBeingConstructed() to check. - - std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source) const; - std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest) const; - - std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source, const char *classname) const; - std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest, const char *classname) const; - - std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source, - const char *const *classnames, size_t count) const; - std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest, - const char *const *classnames, - size_t count) const; - - const std::vector<const AnimationStack *> &AnimationStacks() const; - const std::vector<uint64_t> &GetAnimationStackIDs() const { - return animationStacks; - } - - const std::vector<uint64_t> &GetConstraintStackIDs() const { - return constraints; - } - - const std::vector<uint64_t> &GetBindPoseIDs() const { - return bind_poses; - }; - - const std::vector<uint64_t> &GetMaterialIDs() const { - return materials; - }; - - const std::vector<uint64_t> &GetSkinIDs() const { - return skins; - } - -private: - std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, const ConnectionMap &) const; - std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, bool is_src, - const ConnectionMap &, - const char *const *classnames, - size_t count) const; - bool ReadHeader(); - void ReadObjects(); - void ReadPropertyTemplates(); - void ReadConnections(); - void ReadGlobalSettings(); - -private: - const ImportSettings &settings; - - ObjectMap objects; - const Parser &parser; - bool SafeToImport = false; - - PropertyTemplateMap templates; - ConnectionMap src_connections; - ConnectionMap dest_connections; - - unsigned int fbxVersion = 0; - std::string creator; - unsigned int creationTimeStamp[7] = { 0 }; - - std::vector<uint64_t> animationStacks; - std::vector<uint64_t> bind_poses; - // constraints aren't in the tree / at least they are not easy to access. - std::vector<uint64_t> constraints; - std::vector<uint64_t> materials; - std::vector<uint64_t> skins; - mutable std::vector<const AnimationStack *> animationStacksResolved; - PropertyTable metadata_properties; - std::shared_ptr<FileGlobalSettings> globals = nullptr; -}; -} // namespace FBXDocParser - -namespace std { -template <> -struct hash<const FBXDocParser::Video> { - std::size_t operator()(const FBXDocParser::Video &video) const { - using std::hash; - using std::size_t; - using std::string; - - size_t res = 17; - res = res * 31 + hash<string>()(video.Name()); - res = res * 31 + hash<string>()(video.RelativeFilename()); - res = res * 31 + hash<string>()(video.Type()); - - return res; - } -}; -} // namespace std - -#endif // FBX_DOCUMENT_H diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.cpp b/modules/fbx/fbx_parser/FBXDocumentUtil.cpp deleted file mode 100644 index 4ee8a42b33..0000000000 --- a/modules/fbx/fbx_parser/FBXDocumentUtil.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************/ -/* FBXDocumentUtil.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXDocumentUtil.cpp - * @brief Implementation of the FBX DOM utility functions declared in FBXDocumentUtil.h - */ - -#include "FBXDocumentUtil.h" -#include "FBXDocument.h" -#include "FBXParser.h" -#include "FBXProperties.h" -#include "FBXUtil.h" -#include "core/string/print_string.h" - -namespace FBXDocParser { -namespace Util { - -void DOMError(const std::string &message) { - print_error("[FBX-DOM]" + String(message.c_str())); -} - -void DOMError(const std::string &message, const Token *token) { - print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); -} - -void DOMError(const std::string &message, const std::shared_ptr<Token> token) { - print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); -} - -void DOMError(const std::string &message, const Element *element /*= nullptr*/) { - if (element) { - DOMError(message, element->KeyToken()); - } - print_error("[FBX-DOM] " + String(message.c_str())); -} - -void DOMError(const std::string &message, const std::shared_ptr<Element> element /*= nullptr*/) { - if (element) { - DOMError(message, element->KeyToken()); - } - print_error("[FBX-DOM] " + String(message.c_str())); -} - -void DOMWarning(const std::string &message) { - print_verbose("[FBX-DOM] warning:" + String(message.c_str())); -} - -void DOMWarning(const std::string &message, const Token *token) { - print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); -} - -void DOMWarning(const std::string &message, const Element *element /*= nullptr*/) { - if (element) { - DOMWarning(message, element->KeyToken()); - return; - } - print_verbose("[FBX-DOM] warning:" + String(message.c_str())); -} - -void DOMWarning(const std::string &message, const std::shared_ptr<Token> token) { - print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str())); -} - -void DOMWarning(const std::string &message, const std::shared_ptr<Element> element /*= nullptr*/) { - if (element) { - DOMWarning(message, element->KeyToken()); - return; - } - print_verbose("[FBX-DOM] warning:" + String(message.c_str())); -} - -} // namespace Util -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.h b/modules/fbx/fbx_parser/FBXDocumentUtil.h deleted file mode 100644 index 61c92dbc83..0000000000 --- a/modules/fbx/fbx_parser/FBXDocumentUtil.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************/ -/* FBXDocumentUtil.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2012, assimp team -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXDocumentUtil.h - * @brief FBX internal utilities used by the DOM reading code - */ -#ifndef FBX_DOCUMENT_UTIL_H -#define FBX_DOCUMENT_UTIL_H - -#include "FBXDocument.h" -#include <memory> -#include <string> - -struct Token; -struct Element; - -namespace FBXDocParser { -namespace Util { - -// Parser errors -void DOMError(const std::string &message); -void DOMError(const std::string &message, const Token *token); -void DOMError(const std::string &message, const Element *element); -void DOMError(const std::string &message, const std::shared_ptr<Element> element); -void DOMError(const std::string &message, const std::shared_ptr<Token> token); - -// Parser warnings -void DOMWarning(const std::string &message); -void DOMWarning(const std::string &message, const Token *token); -void DOMWarning(const std::string &message, const Element *element); -void DOMWarning(const std::string &message, const std::shared_ptr<Token> token); -void DOMWarning(const std::string &message, const std::shared_ptr<Element> element); - -// ------------------------------------------------------------------------------------------------ -template <typename T> -const T *ProcessSimpleConnection(const Connection &con, - bool is_object_property_conn, - const char *name, - const ElementPtr element, - const char **propNameOut = nullptr) { - if (is_object_property_conn && !con.PropertyName().length()) { - DOMWarning("expected incoming " + std::string(name) + - " link to be an object-object connection, ignoring", - element); - return nullptr; - } else if (!is_object_property_conn && con.PropertyName().length()) { - DOMWarning("expected incoming " + std::string(name) + - " link to be an object-property connection, ignoring", - element); - return nullptr; - } - - if (is_object_property_conn && propNameOut) { - // note: this is ok, the return value of PropertyValue() is guaranteed to - // remain valid and unchanged as long as the document exists. - *propNameOut = con.PropertyName().c_str(); - } - - // Cast Object to AnimationPlayer for example using safe functions, which return nullptr etc - Object *ob = con.SourceObject(); - ERR_FAIL_COND_V_MSG(!ob, nullptr, "Failed to load object from SourceObject ptr"); - return dynamic_cast<const T *>(ob); -} -} // namespace Util -} // namespace FBXDocParser - -#endif // FBX_DOCUMENT_UTIL_H diff --git a/modules/fbx/fbx_parser/FBXImportSettings.h b/modules/fbx/fbx_parser/FBXImportSettings.h deleted file mode 100644 index fa93f275c2..0000000000 --- a/modules/fbx/fbx_parser/FBXImportSettings.h +++ /dev/null @@ -1,162 +0,0 @@ -/*************************************************************************/ -/* FBXImportSettings.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXImportSettings.h - * @brief FBX importer runtime configuration - */ -#ifndef FBX_IMPORT_SETTINGS_H -#define FBX_IMPORT_SETTINGS_H - -namespace FBXDocParser { - -/** FBX import settings, parts of which are publicly accessible via their corresponding AI_CONFIG constants */ -struct ImportSettings { - /** enable strict mode: - * - only accept fbx 2012, 2013 files - * - on the slightest error, give up. - * - * Basically, strict mode means that the fbx file will actually - * be validated.*/ - bool strictMode = true; - - /** specifies whether all geometry layers are read and scanned for - * usable data channels. The FBX spec indicates that many readers - * will only read the first channel and that this is in some way - * the recommended way- in reality, however, it happens a lot that - * vertex data is spread among multiple layers.*/ - bool readAllLayers = true; - - /** specifies whether all materials are read, or only those that - * are referenced by at least one mesh. Reading all materials - * may make FBX reading a lot slower since all objects - * need to be processed. - * This bit is ignored unless readMaterials=true.*/ - bool readAllMaterials = true; - - /** import materials (true) or skip them and assign a default - * material.*/ - bool readMaterials = true; - - /** import embedded textures?*/ - bool readTextures = true; - - /** import cameras?*/ - bool readCameras = true; - - /** import light sources?*/ - bool readLights = true; - - /** import animations (i.e. animation curves, the node - * skeleton is always imported).*/ - bool readAnimations = true; - - /** read bones (vertex weights and deform info).*/ - bool readWeights = true; - - /** preserve transformation pivots and offsets. Since these can - * not directly be represented in assimp, additional dummy - * nodes will be generated. Note that settings this to false - * can make animation import a lot slower. - * - * The naming scheme for the generated nodes is: - * <OriginalName>_$AssimpFbx$_<TransformName> - * - * where <TransformName> is one of - * RotationPivot - * RotationOffset - * PreRotation - * PostRotation - * ScalingPivot - * ScalingOffset - * Translation - * Scaling - * Rotation - **/ - bool preservePivots = true; - - /** do not import animation curves that specify a constant - * values matching the corresponding node transformation.*/ - bool optimizeEmptyAnimationCurves = true; - - /** use legacy naming for embedded textures eg: (*0, *1, *2).*/ - bool useLegacyEmbeddedTextureNaming = false; - - /** Empty bones shall be removed.*/ - bool removeEmptyBones = true; - - /** Set to true to perform a conversion from cm to meter after - * the import.*/ - bool convertToMeters = false; -}; -} // namespace FBXDocParser - -#endif // FBX_IMPORT_SETTINGS_H diff --git a/modules/fbx/fbx_parser/FBXMaterial.cpp b/modules/fbx/fbx_parser/FBXMaterial.cpp deleted file mode 100644 index e4ee77e850..0000000000 --- a/modules/fbx/fbx_parser/FBXMaterial.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/*************************************************************************/ -/* FBXMaterial.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXMaterial.cpp - * @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation - */ - -#include "ByteSwapper.h" -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXImportSettings.h" -#include "FBXParser.h" -#include "FBXProperties.h" - -#include "FBXUtil.h" -#include <algorithm> // std::transform - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -Material::Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - - const ElementPtr ShadingModel = sc->GetElement("ShadingModel"); - const ElementPtr MultiLayer = sc->GetElement("MultiLayer"); - - if (MultiLayer) { - multilayer = !!ParseTokenAsInt(GetRequiredToken(MultiLayer, 0)); - } - - if (ShadingModel) { - shading = ParseTokenAsString(GetRequiredToken(ShadingModel, 0)); - } else { - DOMWarning("shading mode not specified, assuming phong", element); - shading = "phong"; - } - - std::string templateName; - - if (shading == "phong") { - templateName = "Material.Phong"; - } else if (shading == "lambert") { - templateName = "Material.Lambert"; - } else if (shading == "unknown") { - templateName = "Material.StingRay"; - } else { - DOMWarning("shading mode not recognized: " + shading, element); - } - - // resolve texture links - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); - for (const Connection *con : conns) { - // texture link to properties, not objects - if (!con->PropertyName().length()) { - continue; - } - - Object *ob = con->SourceObject(); - if (!ob) { - DOMWarning("failed to read source object for texture link, ignoring", element); - continue; - } - - const Texture *tex = dynamic_cast<const Texture *>(ob); - if (!tex) { - LayeredTexture *layeredTexture = dynamic_cast<LayeredTexture *>(ob); - - if (!layeredTexture) { - DOMWarning("source object for texture link is not a texture or layered texture, ignoring", element); - continue; - } - - const std::string &prop = con->PropertyName(); - if (layeredTextures.find(prop) != layeredTextures.end()) { - DOMWarning("duplicate layered texture link: " + prop, element); - } - - layeredTextures[prop] = layeredTexture; - layeredTexture->fillTexture(doc); - } else { - const std::string &prop = con->PropertyName(); - if (textures.find(prop) != textures.end()) { - DOMWarning("duplicate texture link: " + prop, element); - } - - textures[prop] = tex; - } - } -} - -// ------------------------------------------------------------------------------------------------ -Material::~Material() { -} - -// ------------------------------------------------------------------------------------------------ -Texture::Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name), uvScaling(1.0f, 1.0f) { - const ScopePtr sc = GetRequiredScope(element); - - const ElementPtr Type = sc->GetElement("Type"); - const ElementPtr FileName = sc->GetElement("FileName"); - const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename"); - const ElementPtr ModelUVTranslation = sc->GetElement("ModelUVTranslation"); - const ElementPtr ModelUVScaling = sc->GetElement("ModelUVScaling"); - const ElementPtr Texture_Alpha_Source = sc->GetElement("Texture_Alpha_Source"); - const ElementPtr Cropping = sc->GetElement("Cropping"); - - if (Type) { - type = ParseTokenAsString(GetRequiredToken(Type, 0)); - } - - if (FileName) { - fileName = ParseTokenAsString(GetRequiredToken(FileName, 0)); - } - - if (RelativeFilename) { - relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0)); - } - - if (ModelUVTranslation) { - uvTrans = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 0)), - ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 1))); - } - - if (ModelUVScaling) { - uvScaling = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 0)), - ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 1))); - } - - if (Cropping) { - crop[0] = ParseTokenAsInt(GetRequiredToken(Cropping, 0)); - crop[1] = ParseTokenAsInt(GetRequiredToken(Cropping, 1)); - crop[2] = ParseTokenAsInt(GetRequiredToken(Cropping, 2)); - crop[3] = ParseTokenAsInt(GetRequiredToken(Cropping, 3)); - } else { - // vc8 doesn't support the crop() syntax in initialization lists - // (and vc9 WARNS about the new (i.e. compliant) behaviour). - crop[0] = crop[1] = crop[2] = crop[3] = 0; - } - - if (Texture_Alpha_Source) { - alphaSource = ParseTokenAsString(GetRequiredToken(Texture_Alpha_Source, 0)); - } - - // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available. - bool ok = true; - const Vector3 &scaling = PropertyGet<Vector3>(this, "Scaling", ok); - if (ok) { - uvScaling.x = scaling.x; - uvScaling.y = scaling.y; - } - - const Vector3 &trans = PropertyGet<Vector3>(this, "Translation", ok); - if (ok) { - uvTrans.x = trans.x; - uvTrans.y = trans.y; - } - - // resolve video links - if (doc.Settings().readTextures) { - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); - for (const Connection *con : conns) { - const Object *const ob = con->SourceObject(); - if (!ob) { - DOMWarning("failed to read source object for texture link, ignoring", element); - continue; - } - - const Video *const video = dynamic_cast<const Video *>(ob); - if (video) { - media = video; - } - } - } -} - -Texture::~Texture() { -} - -LayeredTexture::LayeredTexture(uint64_t id, const ElementPtr element, const Document & /*doc*/, const std::string &name) : - Object(id, element, name), blendMode(BlendMode_Modulate), alpha(1) { - const ScopePtr sc = GetRequiredScope(element); - - ElementPtr BlendModes = sc->GetElement("BlendModes"); - ElementPtr Alphas = sc->GetElement("Alphas"); - - if (BlendModes != nullptr) { - blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(BlendModes, 0)); - } - if (Alphas != nullptr) { - alpha = ParseTokenAsFloat(GetRequiredToken(Alphas, 0)); - } -} - -LayeredTexture::~LayeredTexture() { -} - -void LayeredTexture::fillTexture(const Document &doc) { - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); - for (size_t i = 0; i < conns.size(); ++i) { - const Connection *con = conns.at(i); - - const Object *const ob = con->SourceObject(); - if (!ob) { - DOMWarning("failed to read source object for texture link, ignoring", element); - continue; - } - - const Texture *const tex = dynamic_cast<const Texture *>(ob); - - textures.push_back(tex); - } -} - -// ------------------------------------------------------------------------------------------------ -Video::Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - - const ElementPtr Type = sc->GetElement("Type"); - // File Version 7500 Crashes if this is not checked fully. - // As of writing this comment 7700 exists, in August 2020 - ElementPtr FileName = nullptr; - if (HasElement(sc, "Filename")) { - FileName = (ElementPtr)sc->GetElement("Filename"); - } else if (HasElement(sc, "FileName")) { - FileName = (ElementPtr)sc->GetElement("FileName"); - } else { - print_error("file has invalid video material returning..."); - return; - } - const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename"); - const ElementPtr Content = sc->GetElement("Content"); - - if (Type) { - type = ParseTokenAsString(GetRequiredToken(Type, 0)); - } - - if (FileName) { - fileName = ParseTokenAsString(GetRequiredToken(FileName, 0)); - } - - if (RelativeFilename) { - relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0)); - } - - if (Content && !Content->Tokens().empty()) { - //this field is omitted when the embedded texture is already loaded, let's ignore if it's not found - try { - const Token *token = GetRequiredToken(Content, 0); - const char *data = token->begin(); - if (!token->IsBinary()) { - if (*data != '"') { - DOMError("embedded content is not surrounded by quotation marks", element); - } else { - size_t targetLength = 0; - const size_t numTokens = Content->Tokens().size(); - // First time compute size (it could be large like 64Gb and it is good to allocate it once) - for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { - const Token *dataToken = GetRequiredToken(Content, tokenIdx); - size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes - const char *base64data = dataToken->begin() + 1; - const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength); - if (outLength == 0) { - DOMError("Corrupted embedded content found", element); - } - targetLength += outLength; - } - if (targetLength == 0) { - DOMError("Corrupted embedded content found", element); - } else { - content = new uint8_t[targetLength]; - contentLength = static_cast<uint64_t>(targetLength); - size_t dst_offset = 0; - for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) { - const Token *dataToken = GetRequiredToken(Content, tokenIdx); - ERR_FAIL_COND(!dataToken); - size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes - const char *base64data = dataToken->begin() + 1; - dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset); - } - if (targetLength != dst_offset) { - delete[] content; - contentLength = 0; - DOMError("Corrupted embedded content found", element); - } - } - } - } else if (static_cast<size_t>(token->end() - data) < 5) { - DOMError("binary data array is too short, need five (5) bytes for type signature and element count", element); - } else if (*data != 'R') { - DOMWarning("video content is not raw binary data, ignoring", element); - } else { - // read number of elements - uint32_t len = 0; - ::memcpy(&len, data + 1, sizeof(len)); - AI_SWAP4(len); - - contentLength = len; - - content = new uint8_t[len]; - ::memcpy(content, data + 5, len); - } - } catch (...) { - // //we don't need the content data for contents that has already been loaded - // ASSIMP_LOG_VERBOSE_DEBUG_F("Caught exception in FBXMaterial (likely because content was already loaded): ", - // runtimeError.what()); - } - } -} - -Video::~Video() { - if (content) { - delete[] content; - } -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.cpp b/modules/fbx/fbx_parser/FBXMeshGeometry.cpp deleted file mode 100644 index 591f2e5503..0000000000 --- a/modules/fbx/fbx_parser/FBXMeshGeometry.cpp +++ /dev/null @@ -1,459 +0,0 @@ -/*************************************************************************/ -/* FBXMeshGeometry.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXMeshGeometry.cpp - * @brief Assimp::FBX::MeshGeometry implementation - */ - -#include <functional> - -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXImportSettings.h" -#include "FBXMeshGeometry.h" -#include "core/math/vector3.h" - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -Geometry::Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Object(id, element, name) { - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); - for (const Connection *con : conns) { - const Skin *sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element); - if (sk) { - skin = sk; - } - const BlendShape *bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", - element); - if (bsp) { - blendShapes.push_back(bsp); - } - } -} - -// ------------------------------------------------------------------------------------------------ -Geometry::~Geometry() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -const std::vector<const BlendShape *> &Geometry::get_blend_shapes() const { - return blendShapes; -} - -// ------------------------------------------------------------------------------------------------ -const Skin *Geometry::DeformerSkin() const { - return skin; -} - -// ------------------------------------------------------------------------------------------------ -MeshGeometry::MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Geometry(id, element, name, doc) { - print_verbose("mesh name: " + String(name.c_str())); - - ScopePtr sc = element->Compound(); - ERR_FAIL_COND_MSG(sc == nullptr, "failed to read geometry, prevented crash"); - ERR_FAIL_COND_MSG(!HasElement(sc, "Vertices"), "Detected mesh with no vertices, didn't populate the mesh"); - - // must have Mesh elements: - const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element); - const ElementPtr PolygonVertexIndex = GetRequiredElement(sc, "PolygonVertexIndex", element); - - if (HasElement(sc, "Edges")) { - const ElementPtr element_edges = GetRequiredElement(sc, "Edges", element); - ParseVectorDataArray(m_edges, element_edges); - } - - // read mesh data into arrays - ParseVectorDataArray(m_vertices, Vertices); - ParseVectorDataArray(m_face_indices, PolygonVertexIndex); - - ERR_FAIL_COND_MSG(m_vertices.empty(), "mesh with no vertices in FBX file, did you mean to delete it?"); - ERR_FAIL_COND_MSG(m_face_indices.empty(), "mesh has no faces, was this intended?"); - - // Retrieve layer elements, for all of the mesh - const ElementCollection &Layer = sc->GetCollection("Layer"); - - // Store all layers - std::vector<std::tuple<int, std::string>> valid_layers; - - // now read the sub mesh information from the geometry (normals, uvs, etc) - for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) { - const ScopePtr layer = GetRequiredScope(it->second); - const ElementCollection &LayerElement = layer->GetCollection("LayerElement"); - for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) { - std::string layer_name = eit->first; - ElementPtr element_layer = eit->second; - const ScopePtr layer_element = GetRequiredScope(element_layer); - - // Actual usable 'type' LayerElementUV, LayerElementNormal, etc - const ElementPtr Type = GetRequiredElement(layer_element, "Type"); - const ElementPtr TypedIndex = GetRequiredElement(layer_element, "TypedIndex"); - const std::string &type = ParseTokenAsString(GetRequiredToken(Type, 0)); - const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex, 0)); - - // we only need the layer name and the typed index. - valid_layers.push_back(std::tuple<int, std::string>(typedIndex, type)); - } - } - - // get object / mesh directly from the FBX by the element ID. - const ScopePtr top = GetRequiredScope(element); - - // iterate over all layers for the mesh (uvs, normals, smoothing groups, colors, etc) - for (size_t x = 0; x < valid_layers.size(); x++) { - const int layer_id = std::get<0>(valid_layers[x]); - const std::string &layer_type_name = std::get<1>(valid_layers[x]); - - // Get collection of elements from the XLayerMap (example: LayerElementUV) - // this must contain our proper elements. - - // This is stupid, because it means we select them ALL not just the one we want. - // but it's fine we can match by id. - - const ElementCollection &candidates = top->GetCollection(layer_type_name); - - ElementMap::const_iterator iter; - for (iter = candidates.first; iter != candidates.second; ++iter) { - const ScopePtr layer_scope = GetRequiredScope(iter->second); - TokenPtr layer_token = GetRequiredToken(iter->second, 0); - const int index = ParseTokenAsInt(layer_token); - - ERR_FAIL_COND_MSG(layer_scope == nullptr, "prevented crash, layer scope is invalid"); - - if (index == layer_id) { - const std::string &MappingInformationType = ParseTokenAsString(GetRequiredToken( - GetRequiredElement(layer_scope, "MappingInformationType"), 0)); - - const std::string &ReferenceInformationType = ParseTokenAsString(GetRequiredToken( - GetRequiredElement(layer_scope, "ReferenceInformationType"), 0)); - - if (layer_type_name == "LayerElementUV") { - if (index == 0) { - m_uv_0 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV"); - } else if (index == 1) { - m_uv_1 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV"); - } - } else if (layer_type_name == "LayerElementMaterial") { - m_material_allocation_ids = resolve_vertex_data_array<int>(layer_scope, MappingInformationType, ReferenceInformationType, "Materials"); - } else if (layer_type_name == "LayerElementNormal") { - m_normals = resolve_vertex_data_array<Vector3>(layer_scope, MappingInformationType, ReferenceInformationType, "Normals"); - } else if (layer_type_name == "LayerElementColor") { - m_colors = resolve_vertex_data_array<Color>(layer_scope, MappingInformationType, ReferenceInformationType, "Colors", "ColorIndex"); - } - } - } - } - - print_verbose("Mesh statistics \nuv_0: " + m_uv_0.debug_info() + "\nuv_1: " + m_uv_1.debug_info() + "\nvertices: " + itos(m_vertices.size())); - - // Compose the edge of the mesh. - // You can see how the edges are stored into the FBX here: https://gist.github.com/AndreaCatania/da81840f5aa3b2feedf189e26c5a87e6 - for (size_t i = 0; i < m_edges.size(); i += 1) { - ERR_FAIL_INDEX_MSG((size_t)m_edges[i], m_face_indices.size(), "The edge is pointing to a weird location in the face indices. The FBX is corrupted."); - int polygon_vertex_0 = m_face_indices[m_edges[i]]; - int polygon_vertex_1; - if (polygon_vertex_0 < 0) { - // The polygon_vertex_0 points to the end of a polygon, so it's - // connected with the beginning of polygon in the edge list. - - // Fist invert the vertex. - polygon_vertex_0 = ~polygon_vertex_0; - - // Search the start vertex of the polygon. - // Iterate from the polygon_vertex_index backward till the start of - // the polygon is found. - ERR_FAIL_COND_MSG(m_edges[i] - 1 < 0, "The polygon is not yet started and we already need the final vertex. This FBX is corrupted."); - bool found_it = false; - for (int x = m_edges[i] - 1; x >= 0; x -= 1) { - if (x == 0) { - // This for sure is the start. - polygon_vertex_1 = m_face_indices[x]; - found_it = true; - break; - } else if (m_face_indices[x] < 0) { - // This is the end of the previous polygon, so the next is - // the start of the polygon we need. - polygon_vertex_1 = m_face_indices[x + 1]; - found_it = true; - break; - } - } - // As the algorithm above, this check is useless. Because the first - // ever vertex is always considered the beginning of a polygon. - ERR_FAIL_COND_MSG(found_it == false, "Was not possible to find the first vertex of this polygon. FBX file is corrupted."); - - } else { - ERR_FAIL_INDEX_MSG((size_t)(m_edges[i] + 1), m_face_indices.size(), "FBX The other FBX edge seems to point to an invalid vertices. This FBX file is corrupted."); - // Take the next vertex - polygon_vertex_1 = m_face_indices[m_edges[i] + 1]; - } - - if (polygon_vertex_1 < 0) { - // We don't care if the `polygon_vertex_1` is the end of the polygon, - // for `polygon_vertex_1` so we can just invert it. - polygon_vertex_1 = ~polygon_vertex_1; - } - - ERR_FAIL_COND_MSG(polygon_vertex_0 == polygon_vertex_1, "The vertices of this edge can't be the same, Is this a point???. This FBX file is corrupted."); - - // Just create the edge. - edge_map.push_back({ polygon_vertex_0, polygon_vertex_1 }); - } -} - -MeshGeometry::~MeshGeometry() { - // empty -} - -const std::vector<Vector3> &MeshGeometry::get_vertices() const { - return m_vertices; -} - -const std::vector<MeshGeometry::Edge> &MeshGeometry::get_edge_map() const { - return edge_map; -} - -const std::vector<int> &MeshGeometry::get_polygon_indices() const { - return m_face_indices; -} - -const std::vector<int> &MeshGeometry::get_edges() const { - return m_edges; -} - -const MeshGeometry::MappingData<Vector3> &MeshGeometry::get_normals() const { - return m_normals; -} - -const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_0() const { - //print_verbose("get uv_0 " + m_uv_0.debug_info() ); - return m_uv_0; -} - -const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_1() const { - //print_verbose("get uv_1 " + m_uv_1.debug_info() ); - return m_uv_1; -} - -const MeshGeometry::MappingData<Color> &MeshGeometry::get_colors() const { - return m_colors; -} - -const MeshGeometry::MappingData<int> &MeshGeometry::get_material_allocation_id() const { - return m_material_allocation_ids; -} - -int MeshGeometry::get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b) { - for (size_t i = 0; i < p_map.size(); i += 1) { - if ((p_map[i].vertex_0 == p_vertex_a && p_map[i].vertex_1 == p_vertex_b) || (p_map[i].vertex_1 == p_vertex_a && p_map[i].vertex_0 == p_vertex_b)) { - return i; - } - } - return -1; -} - -MeshGeometry::Edge MeshGeometry::get_edge(const std::vector<Edge> &p_map, int p_id) { - ERR_FAIL_INDEX_V_MSG((size_t)p_id, p_map.size(), Edge({ -1, -1 }), "ID not found."); - return p_map[p_id]; -} - -template <class T> -MeshGeometry::MappingData<T> MeshGeometry::resolve_vertex_data_array( - const ScopePtr source, - const std::string &MappingInformationType, - const std::string &ReferenceInformationType, - const std::string &dataElementName, - const std::string &indexOverride) { - ERR_FAIL_COND_V_MSG(source == nullptr, MappingData<T>(), "Invalid scope operator preventing memory corruption"); - - // UVIndex, MaterialIndex, NormalIndex, etc.. - std::string indexDataElementName; - - if (!indexOverride.empty()) { - // Colors should become ColorIndex - indexDataElementName = indexOverride; - } else { - // Some indexes will exist. - indexDataElementName = dataElementName + "Index"; - } - - // goal: expand everything to be per vertex - - ReferenceType l_ref_type = ReferenceType::direct; - - // Read the reference type into the enumeration - if (ReferenceInformationType == "IndexToDirect") { - l_ref_type = ReferenceType::index_to_direct; - } else if (ReferenceInformationType == "Index") { - // set non legacy index to direct mapping - l_ref_type = ReferenceType::index; - } else if (ReferenceInformationType == "Direct") { - l_ref_type = ReferenceType::direct; - } else { - ERR_FAIL_V_MSG(MappingData<T>(), "invalid reference type has the FBX format changed?"); - } - - MapType l_map_type = MapType::none; - - if (MappingInformationType == "None") { - l_map_type = MapType::none; - } else if (MappingInformationType == "ByVertice") { - l_map_type = MapType::vertex; - } else if (MappingInformationType == "ByPolygonVertex") { - l_map_type = MapType::polygon_vertex; - } else if (MappingInformationType == "ByPolygon") { - l_map_type = MapType::polygon; - } else if (MappingInformationType == "ByEdge") { - l_map_type = MapType::edge; - } else if (MappingInformationType == "AllSame") { - l_map_type = MapType::all_the_same; - } else { - print_error("invalid mapping type: " + String(MappingInformationType.c_str())); - } - - // create mapping data - MeshGeometry::MappingData<T> tempData; - tempData.map_type = l_map_type; - tempData.ref_type = l_ref_type; - - // parse data into array - ParseVectorDataArray(tempData.data, GetRequiredElement(source, dataElementName)); - - // index array won't always exist - const ElementPtr element = GetOptionalElement(source, indexDataElementName); - if (element) { - ParseVectorDataArray(tempData.index, element); - } - - return tempData; -} -// ------------------------------------------------------------------------------------------------ -ShapeGeometry::ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Geometry(id, element, name, doc) { - const ScopePtr sc = element->Compound(); - if (nullptr == sc) { - DOMError("failed to read Geometry object (class: Shape), no data scope found"); - } - const ElementPtr Indexes = GetRequiredElement(sc, "Indexes", element); - const ElementPtr Normals = GetRequiredElement(sc, "Normals", element); - const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element); - ParseVectorDataArray(m_indices, Indexes); - ParseVectorDataArray(m_vertices, Vertices); - ParseVectorDataArray(m_normals, Normals); -} - -// ------------------------------------------------------------------------------------------------ -ShapeGeometry::~ShapeGeometry() { - // empty -} -// ------------------------------------------------------------------------------------------------ -const std::vector<Vector3> &ShapeGeometry::GetVertices() const { - return m_vertices; -} -// ------------------------------------------------------------------------------------------------ -const std::vector<Vector3> &ShapeGeometry::GetNormals() const { - return m_normals; -} -// ------------------------------------------------------------------------------------------------ -const std::vector<unsigned int> &ShapeGeometry::GetIndices() const { - return m_indices; -} -// ------------------------------------------------------------------------------------------------ -LineGeometry::LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : - Geometry(id, element, name, doc) { - const ScopePtr sc = element->Compound(); - if (!sc) { - DOMError("failed to read Geometry object (class: Line), no data scope found"); - } - const ElementPtr Points = GetRequiredElement(sc, "Points", element); - const ElementPtr PointsIndex = GetRequiredElement(sc, "PointsIndex", element); - ParseVectorDataArray(m_vertices, Points); - ParseVectorDataArray(m_indices, PointsIndex); -} - -// ------------------------------------------------------------------------------------------------ -LineGeometry::~LineGeometry() { - // empty -} -// ------------------------------------------------------------------------------------------------ -const std::vector<Vector3> &LineGeometry::GetVertices() const { - return m_vertices; -} -// ------------------------------------------------------------------------------------------------ -const std::vector<int> &LineGeometry::GetIndices() const { - return m_indices; -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.h b/modules/fbx/fbx_parser/FBXMeshGeometry.h deleted file mode 100644 index 9f0242d233..0000000000 --- a/modules/fbx/fbx_parser/FBXMeshGeometry.h +++ /dev/null @@ -1,263 +0,0 @@ -/*************************************************************************/ -/* FBXMeshGeometry.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -#ifndef FBX_MESH_GEOMETRY_H -#define FBX_MESH_GEOMETRY_H - -#include "core/math/color.h" -#include "core/math/vector2.h" -#include "core/math/vector3.h" -#include "core/templates/vector.h" - -#include "FBXDocument.h" -#include "FBXParser.h" - -#include <iostream> - -#define AI_MAX_NUMBER_OF_TEXTURECOORDS 4 -#define AI_MAX_NUMBER_OF_COLOR_SETS 8 - -namespace FBXDocParser { - -/* - * DOM base class for all kinds of FBX geometry - */ -class Geometry : public Object { -public: - Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - virtual ~Geometry(); - - /** Get the Skin attached to this geometry or nullptr */ - const Skin *DeformerSkin() const; - - const std::vector<const BlendShape *> &get_blend_shapes() const; - - size_t get_blend_shape_count() const { - return blendShapes.size(); - } - -private: - const Skin *skin = nullptr; - std::vector<const BlendShape *> blendShapes; -}; - -typedef std::vector<int> MatIndexArray; - -/// Map Geometry stores the FBX file information. -/// -/// # FBX doc. -/// ## Reference type declared: -/// - Direct (directly related to the mapping information type) -/// - IndexToDirect (Map with key value, meaning depends on the MappingInformationType) -/// -/// ## Map Type: -/// * None The mapping is undetermined. -/// * ByVertex There will be one mapping coordinate for each surface control point/vertex (ControlPoint is a vertex). -/// * If you have direct reference type vertices[x] -/// * If you have IndexToDirect reference type the UV -/// * ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex) -/// * ByPolygon There can be only one mapping coordinate for the whole polygon. -/// * One mapping per polygon polygon x has this normal x -/// * For each vertex of the polygon then set the normal to x -/// * ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id) -/// * AllSame There can be only one mapping coordinate for the whole surface. -class MeshGeometry : public Geometry { -public: - enum class MapType { - none = 0, // No mapping type. Stored as "None". - vertex, // Maps per vertex. Stored as "ByVertice". - polygon_vertex, // Maps per polygon vertex. Stored as "ByPolygonVertex". - polygon, // Maps per polygon. Stored as "ByPolygon". - edge, // Maps per edge. Stored as "ByEdge". - all_the_same // Uaps to everything. Stored as "AllSame". - }; - - enum class ReferenceType { - direct = 0, - index = 1, - index_to_direct = 2 - }; - - template <class T> - struct MappingData { - MapType map_type = MapType::none; - ReferenceType ref_type = ReferenceType::direct; - std::vector<T> data; - /// The meaning of the indices depends from the `MapType`. - /// If `ref_type` is `direct` this map is hollow. - std::vector<int> index; - - String debug_info() const { - return "indexes: " + itos(index.size()) + " data: " + itos(data.size()); - } - }; - - struct Edge { - int vertex_0 = 0, vertex_1 = 0; - Edge(int v0, int v1) : - vertex_0(v0), vertex_1(v1) {} - Edge() {} - }; - -public: - MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - - virtual ~MeshGeometry(); - - const std::vector<Vector3> &get_vertices() const; - const std::vector<Edge> &get_edge_map() const; - const std::vector<int> &get_polygon_indices() const; - const std::vector<int> &get_edges() const; - const MappingData<Vector3> &get_normals() const; - const MappingData<Vector2> &get_uv_0() const; - const MappingData<Vector2> &get_uv_1() const; - const MappingData<Color> &get_colors() const; - const MappingData<int> &get_material_allocation_id() const; - - /// Returns -1 if the vertices doesn't form an edge. Vertex order, doesn't - // matter. - static int get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b); - // Returns the edge point bu that ID, or the edge with -1 vertices if the - // id is not valid. - static Edge get_edge(const std::vector<Edge> &p_map, int p_id); - -private: - // Read directly from the FBX file. - std::vector<Vector3> m_vertices; - std::vector<Edge> edge_map; - std::vector<int> m_face_indices; - std::vector<int> m_edges; - MappingData<Vector3> m_normals; - MappingData<Vector2> m_uv_0; // first uv coordinates - MappingData<Vector2> m_uv_1; // second uv coordinates - MappingData<Color> m_colors; // colors for the mesh - MappingData<int> m_material_allocation_ids; // slot of material used - - template <class T> - MappingData<T> resolve_vertex_data_array( - const ScopePtr source, - const std::string &MappingInformationType, - const std::string &ReferenceInformationType, - const std::string &dataElementName, - const std::string &indexOverride = ""); -}; - -/* - * DOM class for FBX geometry of type "Shape" - */ -class ShapeGeometry : public Geometry { -public: - /** The class constructor */ - ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - - /** The class destructor */ - virtual ~ShapeGeometry(); - - /** Get a list of all vertex points, non-unique*/ - const std::vector<Vector3> &GetVertices() const; - - /** Get a list of all vertex normals or an empty array if - * no normals are specified. */ - const std::vector<Vector3> &GetNormals() const; - - /** Return list of vertex indices. */ - const std::vector<unsigned int> &GetIndices() const; - -private: - std::vector<Vector3> m_vertices; - std::vector<Vector3> m_normals; - std::vector<unsigned int> m_indices; -}; -/** - * DOM class for FBX geometry of type "Line" - */ -class LineGeometry : public Geometry { -public: - /** The class constructor */ - LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); - - /** The class destructor */ - virtual ~LineGeometry(); - - /** Get a list of all vertex points, non-unique*/ - const std::vector<Vector3> &GetVertices() const; - - /** Return list of vertex indices. */ - const std::vector<int> &GetIndices() const; - -private: - std::vector<Vector3> m_vertices; - std::vector<int> m_indices; -}; -} // namespace FBXDocParser - -#endif // FBX_MESH_GEOMETRY_H diff --git a/modules/fbx/fbx_parser/FBXModel.cpp b/modules/fbx/fbx_parser/FBXModel.cpp deleted file mode 100644 index 625ee6237b..0000000000 --- a/modules/fbx/fbx_parser/FBXModel.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/*************************************************************************/ -/* FBXModel.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXModel.cpp - * @brief Assimp::FBX::Model implementation - */ - -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXMeshGeometry.h" -#include "FBXParser.h" - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -Model::Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name), shading("Y") { - const ScopePtr sc = GetRequiredScope(element); - const ElementPtr Shading = sc->GetElement("Shading"); - const ElementPtr Culling = sc->GetElement("Culling"); - - if (Shading) { - shading = GetRequiredToken(Shading, 0)->StringContents(); - } - - if (Culling) { - culling = ParseTokenAsString(GetRequiredToken(Culling, 0)); - } - - ResolveLinks(element, doc); -} - -// ------------------------------------------------------------------------------------------------ -Model::~Model() { -} - -ModelLimbNode::ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Model(id, element, doc, name){}; - -ModelLimbNode::~ModelLimbNode() { -} - -// ------------------------------------------------------------------------------------------------ -void Model::ResolveLinks(const ElementPtr element, const Document &doc) { - const char *const arr[] = { "Geometry", "Material", "NodeAttribute" }; - - // resolve material - const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), arr, 3); - - materials.reserve(conns.size()); - geometry.reserve(conns.size()); - attributes.reserve(conns.size()); - for (const Connection *con : conns) { - // material and geometry links should be Object-Object connections - if (con->PropertyName().length()) { - continue; - } - - const Object *const ob = con->SourceObject(); - if (!ob) { - //DOMWarning("failed to read source object for incoming Model link, ignoring",&element); - continue; - } - - const Material *const mat = dynamic_cast<const Material *>(ob); - if (mat) { - materials.push_back(mat); - continue; - } - - const Geometry *const geo = dynamic_cast<const Geometry *>(ob); - if (geo) { - geometry.push_back(geo); - continue; - } - - const NodeAttribute *const att = dynamic_cast<const NodeAttribute *>(ob); - if (att) { - attributes.push_back(att); - continue; - } - - DOMWarning("source object for model link is neither Material, NodeAttribute nor Geometry, ignoring", element); - continue; - } -} - -// ------------------------------------------------------------------------------------------------ -bool Model::IsNull() const { - const std::vector<const NodeAttribute *> &attrs = GetAttributes(); - for (const NodeAttribute *att : attrs) { - const Null *null_tag = dynamic_cast<const Null *>(att); - if (null_tag) { - return true; - } - } - - return false; -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXNodeAttribute.cpp b/modules/fbx/fbx_parser/FBXNodeAttribute.cpp deleted file mode 100644 index c8ea03adec..0000000000 --- a/modules/fbx/fbx_parser/FBXNodeAttribute.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/*************************************************************************/ -/* FBXNodeAttribute.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXNoteAttribute.cpp - * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation - */ - -#include "FBXDocument.h" -#include "FBXDocumentUtil.h" -#include "FBXParser.h" -#include <iostream> - -namespace FBXDocParser { -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -NodeAttribute::NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { -} - -// ------------------------------------------------------------------------------------------------ -NodeAttribute::~NodeAttribute() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -CameraSwitcher::CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - NodeAttribute(id, element, doc, name) { - const ScopePtr sc = GetRequiredScope(element); - const ElementPtr CameraId = sc->GetElement("CameraId"); - const ElementPtr CameraName = sc->GetElement("CameraName"); - const ElementPtr CameraIndexName = sc->GetElement("CameraIndexName"); - - if (CameraId) { - cameraId = ParseTokenAsInt(GetRequiredToken(CameraId, 0)); - } - - if (CameraName) { - cameraName = GetRequiredToken(CameraName, 0)->StringContents(); - } - - if (CameraIndexName && CameraIndexName->Tokens().size()) { - cameraIndexName = GetRequiredToken(CameraIndexName, 0)->StringContents(); - } -} - -// ------------------------------------------------------------------------------------------------ -CameraSwitcher::~CameraSwitcher() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -Camera::Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - NodeAttribute(id, element, doc, name) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -Camera::~Camera() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -Light::Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - NodeAttribute(id, element, doc, name) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -Light::~Light() { -} - -// ------------------------------------------------------------------------------------------------ -Null::Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - NodeAttribute(id, element, doc, name) { -} - -// ------------------------------------------------------------------------------------------------ -Null::~Null() { -} - -// ------------------------------------------------------------------------------------------------ -LimbNode::LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - NodeAttribute(id, element, doc, name) { - //std::cout << "limb node: " << name << std::endl; - //const Scope &sc = GetRequiredScope(element); - - //const ElementPtr const TypeFlag = sc["TypeFlags"]; - - // keep this it can dump new properties for you - // for( auto element : sc.Elements()) - // { - // std::cout << "limbnode element: " << element.first << std::endl; - // } - - // if(TypeFlag) - // { - // // std::cout << "type flag: " << GetRequiredToken(*TypeFlag, 0).StringContents() << std::endl; - // } -} - -// ------------------------------------------------------------------------------------------------ -LimbNode::~LimbNode() { -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXParseTools.h b/modules/fbx/fbx_parser/FBXParseTools.h deleted file mode 100644 index 37a7ccad2a..0000000000 --- a/modules/fbx/fbx_parser/FBXParseTools.h +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************/ -/* FBXParseTools.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_PARSE_TOOLS_H -#define FBX_PARSE_TOOLS_H - -#include "core/error/error_macros.h" -#include "core/string/ustring.h" - -#include <stdint.h> -#include <algorithm> -#include <locale> - -template <class char_t> -inline bool IsNewLine(char_t c) { - return c == '\n' || c == '\r'; -} -template <class char_t> -inline bool IsSpace(char_t c) { - return (c == (char_t)' ' || c == (char_t)'\t'); -} - -template <class char_t> -inline bool IsSpaceOrNewLine(char_t c) { - return IsNewLine(c) || IsSpace(c); -} - -template <class char_t> -inline bool IsLineEnd(char_t c) { - return (c == (char_t)'\r' || c == (char_t)'\n' || c == (char_t)'\0' || c == (char_t)'\f'); -} - -// ------------------------------------------------------------------------------------ -// Special version of the function, providing higher accuracy and safety -// It is mainly used by fast_atof to prevent ugly and unwanted integer overflows. -// ------------------------------------------------------------------------------------ -inline uint64_t strtoul10_64(const char *in, bool &errored, const char **out = nullptr, unsigned int *max_inout = nullptr) { - unsigned int cur = 0; - uint64_t value = 0; - - errored = *in < '0' || *in > '9'; - ERR_FAIL_COND_V_MSG(errored, 0, "The string cannot be converted parser error"); - - for (;;) { - if (*in < '0' || *in > '9') { - break; - } - - const uint64_t new_value = (value * (uint64_t)10) + ((uint64_t)(*in - '0')); - - // numeric overflow, we rely on you - if (new_value < value) { - //WARN_PRINT( "Converting the string \" " + in + " \" into a value resulted in overflow." ); - return 0; - } - - value = new_value; - - ++in; - ++cur; - - if (max_inout && *max_inout == cur) { - if (out) { /* skip to end */ - while (*in >= '0' && *in <= '9') { - ++in; - } - *out = in; - } - - return value; - } - } - if (out) { - *out = in; - } - - if (max_inout) { - *max_inout = cur; - } - - return value; -} - -#endif // FBX_PARSE_TOOLS_H diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp deleted file mode 100644 index e345b7fc18..0000000000 --- a/modules/fbx/fbx_parser/FBXParser.cpp +++ /dev/null @@ -1,1322 +0,0 @@ -/*************************************************************************/ -/* FBXParser.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXParser.cpp - * @brief Implementation of the FBX parser and the rudimentary DOM that we use - */ - -#include <stdlib.h> /* strtol */ -#include <zlib.h> - -#include "ByteSwapper.h" -#include "FBXParseTools.h" -#include "FBXParser.h" -#include "FBXTokenizer.h" -#include "core/math/math_defs.h" -#include "core/math/transform_3d.h" -#include "core/math/vector3.h" -#include "core/string/print_string.h" - -using namespace FBXDocParser; -namespace { - -// Initially, we did reinterpret_cast, breaking strict aliasing rules. -// This actually caused trouble on Android, so let's be safe this time. -// https://github.com/assimp/assimp/issues/24 -template <typename T> -T SafeParse(const char *data, const char *end) { - // Actual size validation happens during Tokenization so - // this is valid as an assertion. - (void)(end); - //ai_assert(static_cast<size_t>(end - data) >= sizeof(T)); - T result = static_cast<T>(0); - ::memcpy(&result, data, sizeof(T)); - return result; -} -} // namespace - -namespace FBXDocParser { - -// ------------------------------------------------------------------------------------------------ -Element::Element(const TokenPtr key_token, Parser &parser) : - key_token(key_token) { - TokenPtr n = nullptr; - do { - n = parser.AdvanceToNextToken(); - if (n == nullptr) { - continue; - } - - if (!n) { - print_error("unexpected end of file, expected closing bracket" + String(parser.LastToken()->StringContents().c_str())); - } - - if (n && n->Type() == TokenType_DATA) { - tokens.push_back(n); - TokenPtr prev = n; - n = parser.AdvanceToNextToken(); - - if (n == nullptr) { - break; - } - - if (!n) { - print_error("unexpected end of file, expected bracket, comma or key" + String(parser.LastToken()->StringContents().c_str())); - parser.corrupt = true; - return; - } - - const TokenType ty = n->Type(); - - // some exporters are missing a comma on the next line - if (ty == TokenType_DATA && prev->Type() == TokenType_DATA && (n->Line() == prev->Line() + 1)) { - tokens.push_back(n); - continue; - } - - if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) { - print_error("unexpected token; expected bracket, comma or key" + String(n->StringContents().c_str())); - parser.corrupt = true; - return; - } - } - - if (n && n->Type() == TokenType_OPEN_BRACKET) { - compound = new_Scope(parser); - parser.scopes.push_back(compound); - - if (parser.corrupt) { - return; - } - - // current token should be a TOK_CLOSE_BRACKET - n = parser.CurrentToken(); - - if (n && n->Type() != TokenType_CLOSE_BRACKET) { - print_error("expected closing bracket" + String(n->StringContents().c_str())); - parser.corrupt = true; - return; - } - - parser.AdvanceToNextToken(); - return; - } - } while (n && n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET); -} - -// ------------------------------------------------------------------------------------------------ -Element::~Element() { -} - -// ------------------------------------------------------------------------------------------------ -Scope::Scope(Parser &parser, bool topLevel) { - if (!topLevel) { - TokenPtr t = parser.CurrentToken(); - if (t->Type() != TokenType_OPEN_BRACKET) { - print_error("expected open bracket" + String(t->StringContents().c_str())); - parser.corrupt = true; - return; - } - } - - TokenPtr n = parser.AdvanceToNextToken(); - if (n == nullptr) { - print_error("unexpected end of file"); - parser.corrupt = true; - return; - } - - // note: empty scopes are allowed - while (n && n->Type() != TokenType_CLOSE_BRACKET) { - if (n->Type() != TokenType_KEY) { - print_error("unexpected token, expected TOK_KEY" + String(n->StringContents().c_str())); - parser.corrupt = true; - return; - } - - const std::string str = n->StringContents(); - - if (parser.corrupt) { - return; - } - // std::multimap<std::string, ElementPtr> (key and value) - elements.insert(ElementMap::value_type(str, new_Element(n, parser))); - - // Element() should stop at the next Key token (or right after a Close token) - n = parser.CurrentToken(); - if (n == nullptr) { - if (topLevel) { - return; - } - - //print_error("unexpected end of file" + String(parser.LastToken()->StringContents().c_str())); - } - } -} - -// ------------------------------------------------------------------------------------------------ -Scope::~Scope() { - for (ElementMap::value_type &v : elements) { - delete v.second; - v.second = nullptr; - } - - elements.clear(); -} - -// ------------------------------------------------------------------------------------------------ -Parser::Parser(const TokenList &tokens, bool is_binary) : - tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { - root = new_Scope(*this, true); - scopes.push_back(root); -} - -// ------------------------------------------------------------------------------------------------ -Parser::~Parser() { - for (ScopePtr scope : scopes) { - delete scope; - scope = nullptr; - } -} - -// ------------------------------------------------------------------------------------------------ -TokenPtr Parser::AdvanceToNextToken() { - last = current; - if (cursor == tokens.end()) { - current = nullptr; - } else { - current = *cursor++; - } - return current; -} - -// ------------------------------------------------------------------------------------------------ -TokenPtr Parser::CurrentToken() const { - return current; -} - -// ------------------------------------------------------------------------------------------------ -TokenPtr Parser::LastToken() const { - return last; -} - -// ------------------------------------------------------------------------------------------------ -uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out) { - ERR_FAIL_COND_V_MSG(t == nullptr, 0L, "Invalid token passed to ParseTokenAsID"); - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return 0L; - } - - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'L') { - err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; - return 0L; - } - - uint64_t id = SafeParse<uint64_t>(data + 1, t->end()); - return id; - } - - // XXX: should use size_t here - unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); - //ai_assert(length > 0); - - const char *out = nullptr; - bool errored = false; - - const uint64_t id = strtoul10_64(t->begin(), errored, &out, &length); - if (errored || out > t->end()) { - err_out = "failed to parse ID (text)"; - return 0L; - } - - return id; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsID() with print_error handling -uint64_t ParseTokenAsID(const TokenPtr t) { - const char *err = nullptr; - const uint64_t i = ParseTokenAsID(t, err); - if (err) { - print_error(String(err) + " " + String(t->StringContents().c_str())); - } - return i; -} - -// ------------------------------------------------------------------------------------------------ -size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out) { - // same as ID parsing, except there is a trailing asterisk - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return 0; - } - - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'L') { - err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; - return 0; - } - - uint64_t id = SafeParse<uint64_t>(data + 1, t->end()); - AI_SWAP8(id); - return static_cast<size_t>(id); - } - - if (*t->begin() != '*') { - err_out = "expected asterisk before array dimension"; - return 0; - } - - // XXX: should use size_t here - unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); - if (length == 0) { - err_out = "expected valid integer number after asterisk"; - return 0; - } - - const char *out = nullptr; - bool errored = false; - const size_t id = static_cast<size_t>(strtoul10_64(t->begin() + 1, errored, &out, &length)); - if (errored || out > t->end()) { - print_error("failed to parse id"); - err_out = "failed to parse ID"; - return 0; - } - - return id; -} - -// ------------------------------------------------------------------------------------------------ -float ParseTokenAsFloat(const TokenPtr t, const char *&err_out) { - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return 0.0f; - } - - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'F' && data[0] != 'D') { - err_out = "failed to parse F(loat) or D(ouble), unexpected data type (binary)"; - return 0.0f; - } - - if (data[0] == 'F') { - return SafeParse<float>(data + 1, t->end()); - } else { - return static_cast<float>(SafeParse<double>(data + 1, t->end())); - } - } - -// need to copy the input string to a temporary buffer -// first - next in the fbx token stream comes ',', -// which fast_atof could interpret as decimal point. -#define MAX_FLOAT_LENGTH 31 - char temp[MAX_FLOAT_LENGTH + 1]; - const size_t length = static_cast<size_t>(t->end() - t->begin()); - std::copy(t->begin(), t->end(), temp); - temp[std::min(static_cast<size_t>(MAX_FLOAT_LENGTH), length)] = '\0'; - - return atof(temp); -} - -// ------------------------------------------------------------------------------------------------ -int ParseTokenAsInt(const TokenPtr t, const char *&err_out) { - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return 0; - } - - // binary files are simple to parse - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'I') { - err_out = "failed to parse I(nt), unexpected data type (binary)"; - return 0; - } - - int32_t ival = SafeParse<int32_t>(data + 1, t->end()); - AI_SWAP4(ival); - return static_cast<int>(ival); - } - - // ASCII files are unsafe. - const size_t length = static_cast<size_t>(t->end() - t->begin()); - if (length == 0) { - err_out = "expected valid integer number after asterisk"; - ERR_FAIL_V_MSG(0, "expected valid integer number after asterisk"); - } - - // must not be null for strtol to work - char *out = (char *)t->end(); - // string begin, end ptr ref, base 10 - const int value = strtol(t->begin(), &out, 10); - if (out == nullptr || out != t->end()) { - err_out = "failed to parse ID"; - ERR_FAIL_V_MSG(0, "failed to parse ID"); - } - - return value; -} - -// ------------------------------------------------------------------------------------------------ -int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out) { - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return 0L; - } - - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'L') { - err_out = "failed to parse Int64, unexpected data type"; - return 0L; - } - - int64_t id = SafeParse<int64_t>(data + 1, t->end()); - AI_SWAP8(id); - return id; - } - - // XXX: should use size_t here - unsigned int length = static_cast<unsigned int>(t->end() - t->begin()); - //ai_assert(length > 0); - - char *out = nullptr; - const int64_t id = strtol(t->begin(), &out, length); - if (out > t->end()) { - err_out = "failed to parse Int64 (text)"; - return 0L; - } - - return id; -} - -// ------------------------------------------------------------------------------------------------ -std::string ParseTokenAsString(const TokenPtr t, const char *&err_out) { - err_out = nullptr; - - if (t->Type() != TokenType_DATA) { - err_out = "expected TOK_DATA token"; - return ""; - } - - if (t->IsBinary()) { - const char *data = t->begin(); - if (data[0] != 'S') { - err_out = "failed to parse String, unexpected data type (binary)"; - return ""; - } - - // read string length - int32_t len = SafeParse<int32_t>(data + 1, t->end()); - AI_SWAP4(len); - - //ai_assert(t.end() - data == 5 + len); - return std::string(data + 5, len); - } - - const size_t length = static_cast<size_t>(t->end() - t->begin()); - if (length < 2) { - err_out = "token is too short to hold a string"; - return ""; - } - - const char *s = t->begin(), *e = t->end() - 1; - if (*s != '\"' || *e != '\"') { - err_out = "expected double quoted string"; - return ""; - } - - return std::string(s + 1, length - 2); -} - -namespace { - -// ------------------------------------------------------------------------------------------------ -// read the type code and element count of a binary data array and stop there -void ReadBinaryDataArrayHead(const char *&data, const char *end, char &type, uint32_t &count, - const ElementPtr el) { - TokenPtr token = el->KeyToken(); - if (static_cast<size_t>(end - data) < 5) { - print_error("binary data array is too short, need five (5) bytes for type signature and element count: " + String(token->StringContents().c_str())); - } - - // data type - type = *data; - - // read number of elements - uint32_t len = SafeParse<uint32_t>(data + 1, end); - AI_SWAP4(len); - - count = len; - data += 5; -} - -// ------------------------------------------------------------------------------------------------ -// read binary data array, assume cursor points to the 'compression mode' field (i.e. behind the header) -void ReadBinaryDataArray(char type, uint32_t count, const char *&data, const char *end, - std::vector<char> &buff, - const ElementPtr /*el*/) { - uint32_t encmode = SafeParse<uint32_t>(data, end); - AI_SWAP4(encmode); - data += 4; - - // next comes the compressed length - uint32_t comp_len = SafeParse<uint32_t>(data, end); - AI_SWAP4(comp_len); - data += 4; - - //ai_assert(data + comp_len == end); - - // determine the length of the uncompressed data by looking at the type signature - uint32_t stride = 0; - switch (type) { - case 'f': - case 'i': - stride = 4; - break; - - case 'd': - case 'l': - stride = 8; - break; - } - - const uint32_t full_length = stride * count; - buff.resize(full_length); - - if (encmode == 0) { - //ai_assert(full_length == comp_len); - - // plain data, no compression - std::copy(data, end, buff.begin()); - } else if (encmode == 1) { - // zlib/deflate, next comes ZIP head (0x78 0x01) - // see https://www.ietf.org/rfc/rfc1950.txt - - z_stream zstream; - zstream.opaque = Z_NULL; - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.data_type = Z_BINARY; - - // http://hewgill.com/journal/entries/349-how-to-decompress-gzip-stream-with-zlib - if (Z_OK != inflateInit(&zstream)) { - print_error("failure initializing zlib"); - } - - zstream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data)); - zstream.avail_in = comp_len; - - zstream.avail_out = static_cast<uInt>(buff.size()); - zstream.next_out = reinterpret_cast<Bytef *>(&*buff.begin()); - const int ret = inflate(&zstream, Z_FINISH); - - if (ret != Z_STREAM_END && ret != Z_OK) { - print_error("failure decompressing compressed data section"); - } - - // terminate zlib - inflateEnd(&zstream); - } -#ifdef ASSIMP_BUILD_DEBUG - else { - // runtime check for this happens at tokenization stage - //ai_assert(false); - } -#endif - - data += comp_len; - //ai_assert(data == end); -} -} // namespace - -// ------------------------------------------------------------------------------------------------ -// read an array of float3 tuples -void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el) { - out.resize(0); - - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - if (tok.empty()) { - print_error("unexpected empty element" + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (count % 3 != 0) { - print_error("number of floats is not a multiple of three (3) (binary)" + String(token->StringContents().c_str())); - } - - if (!count) { - return; - } - - if (type != 'd' && type != 'f') { - print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); - - const uint32_t count3 = count / 3; - out.reserve(count3); - - if (type == 'd') { - const double *d = reinterpret_cast<const double *>(&buff[0]); - for (unsigned int i = 0; i < count3; ++i, d += 3) { - out.push_back(Vector3(static_cast<real_t>(d[0]), - static_cast<real_t>(d[1]), - static_cast<real_t>(d[2]))); - } - } else if (type == 'f') { - const float *f = reinterpret_cast<const float *>(&buff[0]); - for (unsigned int i = 0; i < count3; ++i, f += 3) { - out.push_back(Vector3(f[0], f[1], f[2])); - } - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // may throw bad_alloc if the input is rubbish, but this need - // not to be prevented - importing would fail but we wouldn't - // crash since assimp handles this case properly. - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - if (a->Tokens().size() % 3 != 0) { - print_error("number of floats is not a multiple of three (3)" + String(token->StringContents().c_str())); - } else { - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - Vector3 v; - v.x = ParseTokenAsFloat(*it++); - v.y = ParseTokenAsFloat(*it++); - v.z = ParseTokenAsFloat(*it++); - - out.push_back(v); - } - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of color4 tuples -void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - - TokenPtr token = el->KeyToken(); - - if (tok.empty()) { - print_error("unexpected empty element" + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (count % 4 != 0) { - print_error("number of floats is not a multiple of four (4) (binary)" + String(token->StringContents().c_str())); - } - - if (!count) { - return; - } - - if (type != 'd' && type != 'f') { - print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); - - const uint32_t count4 = count / 4; - out.reserve(count4); - - if (type == 'd') { - const double *d = reinterpret_cast<const double *>(&buff[0]); - for (unsigned int i = 0; i < count4; ++i, d += 4) { - out.push_back(Color(static_cast<float>(d[0]), - static_cast<float>(d[1]), - static_cast<float>(d[2]), - static_cast<float>(d[3]))); - } - } else if (type == 'f') { - const float *f = reinterpret_cast<const float *>(&buff[0]); - for (unsigned int i = 0; i < count4; ++i, f += 4) { - out.push_back(Color(f[0], f[1], f[2], f[3])); - } - } - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() above - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - if (a->Tokens().size() % 4 != 0) { - print_error("number of floats is not a multiple of four (4)" + String(token->StringContents().c_str())); - } - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - Color v; - v.r = ParseTokenAsFloat(*it++); - v.g = ParseTokenAsFloat(*it++); - v.b = ParseTokenAsFloat(*it++); - v.a = ParseTokenAsFloat(*it++); - - out.push_back(v); - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of float2 tuples -void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - if (tok.empty()) { - print_error("unexpected empty element" + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (count % 2 != 0) { - print_error("number of floats is not a multiple of two (2) (binary)" + String(token->StringContents().c_str())); - } - - if (!count) { - return; - } - - if (type != 'd' && type != 'f') { - print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); - - const uint32_t count2 = count / 2; - out.reserve(count2); - - if (type == 'd') { - const double *d = reinterpret_cast<const double *>(&buff[0]); - for (unsigned int i = 0; i < count2; ++i, d += 2) { - out.push_back(Vector2(static_cast<float>(d[0]), - static_cast<float>(d[1]))); - } - } else if (type == 'f') { - const float *f = reinterpret_cast<const float *>(&buff[0]); - for (unsigned int i = 0; i < count2; ++i, f += 2) { - out.push_back(Vector2(f[0], f[1])); - } - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() above - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - if (a->Tokens().size() % 2 != 0) { - print_error("number of floats is not a multiple of two (2)" + String(token->StringContents().c_str())); - } else { - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - Vector2 v; - v.x = ParseTokenAsFloat(*it++); - v.y = ParseTokenAsFloat(*it++); - out.push_back(v); - } - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of ints -void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - if (tok.empty()) { - print_error("unexpected empty element" + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (!count) { - return; - } - - if (type != 'i') { - print_error("expected int array (binary)" + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * 4); - - out.reserve(count); - - const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++ip) { - int32_t val = *ip; - AI_SWAP4(val); - out.push_back(val); - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - const int ival = ParseTokenAsInt(*it++); - out.push_back(ival); - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of floats -void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - if (tok.empty()) { - print_error("unexpected empty element: " + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (!count) { - return; - } - - if (type != 'd' && type != 'f') { - print_error("expected float or double array (binary) " + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); - - if (type == 'd') { - const double *d = reinterpret_cast<const double *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++d) { - out.push_back(static_cast<float>(*d)); - } - } else if (type == 'f') { - const float *f = reinterpret_cast<const float *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++f) { - out.push_back(*f); - } - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - const float ival = ParseTokenAsFloat(*it++); - out.push_back(ival); - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of uints -void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - const TokenPtr token = el->KeyToken(); - - ERR_FAIL_COND_MSG(!token, "invalid ParseVectorDataArrat token invalid"); - - if (tok.empty()) { - print_error("unexpected empty element: " + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (!count) { - return; - } - - if (type != 'i') { - print_error("expected (u)int array (binary)" + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * 4); - - out.reserve(count); - - const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++ip) { - int32_t val = *ip; - if (val < 0) { - print_error("encountered negative integer index (binary)"); - } - - out.push_back(val); - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - const int ival = ParseTokenAsInt(*it++); - if (ival < 0) { - print_error("encountered negative integer index"); - } - out.push_back(static_cast<unsigned int>(ival)); - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of uint64_ts -void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr el) { - out.resize(0); - - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - ERR_FAIL_COND(!token); - - if (tok.empty()) { - print_error("unexpected empty element " + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (!count) { - return; - } - - if (type != 'l') { - print_error("expected long array (binary): " + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * 8); - - out.reserve(count); - - const uint64_t *ip = reinterpret_cast<const uint64_t *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++ip) { - uint64_t val = *ip; - AI_SWAP8(val); - out.push_back(val); - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - const uint64_t ival = ParseTokenAsID(*it++); - - out.push_back(ival); - } -} - -// ------------------------------------------------------------------------------------------------ -// read an array of int64_ts -void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el) { - out.resize(0); - const TokenList &tok = el->Tokens(); - TokenPtr token = el->KeyToken(); - ERR_FAIL_COND(!token); - if (tok.empty()) { - print_error("unexpected empty element: " + String(token->StringContents().c_str())); - } - - if (tok[0]->IsBinary()) { - const char *data = tok[0]->begin(), *end = tok[0]->end(); - - char type; - uint32_t count; - ReadBinaryDataArrayHead(data, end, type, count, el); - - if (!count) { - return; - } - - if (type != 'l') { - print_error("expected long array (binary) " + String(token->StringContents().c_str())); - } - - std::vector<char> buff; - ReadBinaryDataArray(type, count, data, end, buff, el); - - //ai_assert(data == end); - //ai_assert(buff.size() == count * 8); - - out.reserve(count); - - const int64_t *ip = reinterpret_cast<const int64_t *>(&buff[0]); - for (unsigned int i = 0; i < count; ++i, ++ip) { - int64_t val = *ip; - AI_SWAP8(val); - out.push_back(val); - } - - return; - } - - const size_t dim = ParseTokenAsDim(tok[0]); - - // see notes in ParseVectorDataArray() - out.reserve(dim); - - const ScopePtr scope = GetRequiredScope(el); - const ElementPtr a = GetRequiredElement(scope, "a", el); - - for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { - const int64_t val = ParseTokenAsInt64(*it++); - out.push_back(val); - } -} - -// ------------------------------------------------------------------------------------------------ -Transform3D ReadMatrix(const ElementPtr element) { - std::vector<float> values; - ParseVectorDataArray(values, element); - - if (values.size() != 16) { - print_error("expected 16 matrix elements"); - } - - // clean values to prevent any IBM damage on inverse() / affine_inverse() - for (float &value : values) { - if (::Math::is_zero_approx(value)) { - value = 0; - } - } - - Transform3D xform; - Basis basis; - - basis.set( - Vector3(values[0], values[1], values[2]), - Vector3(values[4], values[5], values[6]), - Vector3(values[8], values[9], values[10])); - - xform.basis = basis; - xform.origin = Vector3(values[12], values[13], values[14]); - // determine if we need to think about this with dynamic rotation order? - // for example: - // xform.basis = z_axis * y_axis * x_axis; - //xform.basis.transpose(); - - print_verbose("xform verbose basis: " + (xform.basis.get_euler() * (180 / Math_PI)) + " xform origin:" + xform.origin); - - return xform; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsString() with print_error handling -std::string ParseTokenAsString(const TokenPtr t) { - ERR_FAIL_COND_V(!t, ""); - const char *err; - const std::string &i = ParseTokenAsString(t, err); - if (err) { - print_error(String(err) + ", " + String(t->StringContents().c_str())); - } - return i; -} - -// ------------------------------------------------------------------------------------------------ -// extract a required element from a scope, abort if the element cannot be found -ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= nullptr*/) { - const ElementPtr el = sc->GetElement(index); - TokenPtr token = el->KeyToken(); - ERR_FAIL_COND_V(!token, nullptr); - if (!el) { - print_error("did not find required element \"" + String(index.c_str()) + "\" " + String(token->StringContents().c_str())); - } - return el; -} - -bool HasElement(const ScopePtr sc, const std::string &index) { - const ElementPtr el = sc->GetElement(index); - if (nullptr == el) { - return false; - } - - return true; -} - -// ------------------------------------------------------------------------------------------------ -// extract a required element from a scope, abort if the element cannot be found -ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= nullptr*/) { - const ElementPtr el = sc->GetElement(index); - return el; -} - -// ------------------------------------------------------------------------------------------------ -// extract required compound scope -ScopePtr GetRequiredScope(const ElementPtr el) { - if (el) { - ScopePtr s = el->Compound(); - TokenPtr token = el->KeyToken(); - ERR_FAIL_COND_V(!token, nullptr); - if (s) { - return s; - } - - ERR_FAIL_V_MSG(nullptr, "expected compound scope " + String(token->StringContents().c_str())); - } - - ERR_FAIL_V_MSG(nullptr, "Invalid element supplied to parser"); -} - -// ------------------------------------------------------------------------------------------------ -// extract optional compound scope -ScopePtr GetOptionalScope(const ElementPtr el) { - if (el) { - ScopePtr s = el->Compound(); - TokenPtr token = el->KeyToken(); - - if (token && s) { - return s; - } - } - - return nullptr; -} - -// ------------------------------------------------------------------------------------------------ -// get token at a particular index -TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index) { - if (el) { - const TokenList &x = el->Tokens(); - TokenPtr token = el->KeyToken(); - - ERR_FAIL_COND_V(!token, nullptr); - - if (index >= x.size()) { - ERR_FAIL_V_MSG(nullptr, "missing token at index: " + itos(index) + " " + String(token->StringContents().c_str())); - } - - return x[index]; - } - - return nullptr; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsDim() with print_error handling -size_t ParseTokenAsDim(const TokenPtr t) { - const char *err; - const size_t i = ParseTokenAsDim(t, err); - if (err) { - print_error(String(err) + " " + String(t->StringContents().c_str())); - } - return i; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsFloat() with print_error handling -float ParseTokenAsFloat(const TokenPtr t) { - const char *err; - const float i = ParseTokenAsFloat(t, err); - if (err) { - print_error(String(err) + " " + String(t->StringContents().c_str())); - } - return i; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsInt() with print_error handling -int ParseTokenAsInt(const TokenPtr t) { - const char *err; - const int i = ParseTokenAsInt(t, err); - if (err) { - print_error(String(err) + " " + String(t->StringContents().c_str())); - } - return i; -} - -// ------------------------------------------------------------------------------------------------ -// wrapper around ParseTokenAsInt64() with print_error handling -int64_t ParseTokenAsInt64(const TokenPtr t) { - const char *err; - const int64_t i = ParseTokenAsInt64(t, err); - if (err) { - print_error(String(err) + " " + String(t->StringContents().c_str())); - } - return i; -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXParser.h b/modules/fbx/fbx_parser/FBXParser.h deleted file mode 100644 index c5e3f5bf6b..0000000000 --- a/modules/fbx/fbx_parser/FBXParser.h +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* FBXParser.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXParser.h - * @brief FBX parsing code - */ -#ifndef FBX_PARSER_H -#define FBX_PARSER_H - -#include <stdint.h> -#include <map> -#include <memory> - -#include "core/math/color.h" -#include "core/math/transform_3d.h" -#include "core/math/vector2.h" -#include "core/math/vector3.h" - -#include "FBXTokenizer.h" - -namespace FBXDocParser { - -class Scope; -class Parser; -class Element; - -typedef Element *ElementPtr; -typedef Scope *ScopePtr; - -typedef std::vector<ScopePtr> ScopeList; -typedef std::multimap<std::string, ElementPtr> ElementMap; -typedef std::pair<ElementMap::const_iterator, ElementMap::const_iterator> ElementCollection; - -#define new_Scope new Scope -#define new_Element new Element - -/** FBX data entity that consists of a key:value tuple. - * - * Example: - * @verbatim - * AnimationCurve: 23, "AnimCurve::", "" { - * [..] - * } - * @endverbatim - * - * As can be seen in this sample, elements can contain nested #Scope - * as their trailing member. **/ -class Element { -public: - Element(TokenPtr key_token, Parser &parser); - ~Element(); - - ScopePtr Compound() const { - return compound; - } - - TokenPtr KeyToken() const { - return key_token; - } - - const TokenList &Tokens() const { - return tokens; - } - -private: - TokenList tokens; - ScopePtr compound = nullptr; - std::vector<ScopePtr> compound_scope; - TokenPtr key_token = nullptr; -}; - -/** FBX data entity that consists of a 'scope', a collection - * of not necessarily unique #Element instances. - * - * Example: - * @verbatim - * GlobalSettings: { - * Version: 1000 - * Properties70: - * [...] - * } - * @endverbatim */ -class Scope { -public: - Scope(Parser &parser, bool topLevel = false); - ~Scope(); - - ElementPtr GetElement(const std::string &index) const { - ElementMap::const_iterator it = elements.find(index); - return it == elements.end() ? nullptr : (*it).second; - } - - ElementPtr FindElementCaseInsensitive(const std::string &elementName) const { - for (FBXDocParser::ElementMap::const_iterator element = elements.begin(); element != elements.end(); ++element) { - if (element->first.compare(elementName)) { - return element->second; - } - } - - // nothing to reference / expired. - return nullptr; - } - - ElementCollection GetCollection(const std::string &index) const { - return elements.equal_range(index); - } - - const ElementMap &Elements() const { - return elements; - } - -private: - ElementMap elements; -}; - -/** FBX parsing class, takes a list of input tokens and generates a hierarchy - * of nested #Scope instances, representing the fbx DOM.*/ -class Parser { -public: - /** Parse given a token list. Does not take ownership of the tokens - - * the objects must persist during the entire parser lifetime */ - Parser(const TokenList &tokens, bool is_binary); - ~Parser(); - - ScopePtr GetRootScope() const { - return root; - } - - bool IsBinary() const { - return is_binary; - } - - bool IsCorrupt() const { - return corrupt; - } - -private: - friend class Scope; - friend class Element; - - TokenPtr AdvanceToNextToken(); - TokenPtr LastToken() const; - TokenPtr CurrentToken() const; - -private: - bool corrupt = false; - ScopeList scopes; - const TokenList &tokens; - - TokenPtr last = nullptr, current = nullptr; - TokenList::const_iterator cursor; - ScopePtr root = nullptr; - - const bool is_binary; -}; - -/* token parsing - this happens when building the DOM out of the parse-tree*/ -uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out); -size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out); -float ParseTokenAsFloat(const TokenPtr t, const char *&err_out); -int ParseTokenAsInt(const TokenPtr t, const char *&err_out); -int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out); -std::string ParseTokenAsString(const TokenPtr t, const char *&err_out); - -/* wrapper around ParseTokenAsXXX() with DOMError handling */ -uint64_t ParseTokenAsID(const TokenPtr t); -size_t ParseTokenAsDim(const TokenPtr t); -float ParseTokenAsFloat(const TokenPtr t); -int ParseTokenAsInt(const TokenPtr t); -int64_t ParseTokenAsInt64(const TokenPtr t); -std::string ParseTokenAsString(const TokenPtr t); - -/* read data arrays */ -void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el); -void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr ep); -void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el); -bool HasElement(const ScopePtr sc, const std::string &index); - -// extract a required element from a scope, abort if the element cannot be found -ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); -ScopePtr GetRequiredScope(const ElementPtr el); // New in 2020. (less likely to destroy application) -ScopePtr GetOptionalScope(const ElementPtr el); // New in 2021. (even LESS likely to destroy application now) - -ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); -// extract required compound scope -ScopePtr GetRequiredScope(const ElementPtr el); -// get token at a particular index -TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index); - -// ------------------------------------------------------------------------------------------------ -// read a 4x4 matrix from an array of 16 floats -Transform3D ReadMatrix(const ElementPtr element); -} // namespace FBXDocParser - -#endif // FBX_PARSER_H diff --git a/modules/fbx/fbx_parser/FBXPose.cpp b/modules/fbx/fbx_parser/FBXPose.cpp deleted file mode 100644 index 738b9131b6..0000000000 --- a/modules/fbx/fbx_parser/FBXPose.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/*************************************************************************/ -/* FBXPose.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXNoteAttribute.cpp - * @brief Assimp::FBX::NodeAttribute (and subclasses) implementation - */ - -#include "FBXDocument.h" -#include "FBXParser.h" -#include <iostream> - -namespace FBXDocParser { - -class FbxPoseNode; -// ------------------------------------------------------------------------------------------------ -FbxPose::FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - //const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); - - const ElementCollection &PoseNodes = sc->GetCollection("PoseNode"); - for (ElementMap::const_iterator it = PoseNodes.first; it != PoseNodes.second; ++it) { - std::string entry_name = (*it).first; - ElementPtr some_element = (*it).second; - FbxPoseNode *pose_node = new FbxPoseNode(some_element, doc, entry_name); - pose_nodes.push_back(pose_node); - } -} - -// ------------------------------------------------------------------------------------------------ -FbxPose::~FbxPose() { - pose_nodes.clear(); - // empty -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXProperties.cpp b/modules/fbx/fbx_parser/FBXProperties.cpp deleted file mode 100644 index 7cbb3a2eda..0000000000 --- a/modules/fbx/fbx_parser/FBXProperties.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/*************************************************************************/ -/* FBXProperties.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXProperties.cpp - * @brief Implementation of the FBX dynamic properties system - */ - -#include "FBXProperties.h" -#include "FBXDocumentUtil.h" -#include "FBXParser.h" -#include "FBXTokenizer.h" - -namespace FBXDocParser { - -using namespace Util; - -// ------------------------------------------------------------------------------------------------ -Property::Property() { -} - -// ------------------------------------------------------------------------------------------------ -Property::~Property() { -} - -namespace { - -// ------------------------------------------------------------------------------------------------ -// read a typed property out of a FBX element. The return value is nullptr if the property cannot be read. -PropertyPtr ReadTypedProperty(const ElementPtr element) { - //ai_assert(element.KeyToken().StringContents() == "P"); - - const TokenList &tok = element->Tokens(); - //ai_assert(tok.size() >= 5); - - const std::string &s = ParseTokenAsString(tok[1]); - const char *const cs = s.c_str(); - if (!strcmp(cs, "KString")) { - return new TypedProperty<std::string>(ParseTokenAsString(tok[4])); - } else if (!strcmp(cs, "bool") || !strcmp(cs, "Bool")) { - return new TypedProperty<bool>(ParseTokenAsInt(tok[4]) != 0); - } else if (!strcmp(cs, "int") || !strcmp(cs, "Int") || !strcmp(cs, "enum") || !strcmp(cs, "Enum")) { - return new TypedProperty<int>(ParseTokenAsInt(tok[4])); - } else if (!strcmp(cs, "ULongLong")) { - return new TypedProperty<uint64_t>(ParseTokenAsID(tok[4])); - } else if (!strcmp(cs, "KTime")) { - return new TypedProperty<int64_t>(ParseTokenAsInt64(tok[4])); - } else if (!strcmp(cs, "Vector3D") || - !strcmp(cs, "ColorRGB") || - !strcmp(cs, "Vector") || - !strcmp(cs, "Color") || - !strcmp(cs, "Lcl Translation") || - !strcmp(cs, "Lcl Rotation") || - !strcmp(cs, "Lcl Scaling")) { - return new TypedProperty<Vector3>(Vector3( - ParseTokenAsFloat(tok[4]), - ParseTokenAsFloat(tok[5]), - ParseTokenAsFloat(tok[6]))); - } else if (!strcmp(cs, "double") || !strcmp(cs, "Number") || !strcmp(cs, "Float") || !strcmp(cs, "float") || !strcmp(cs, "FieldOfView") || !strcmp(cs, "UnitScaleFactor")) { - return new TypedProperty<float>(ParseTokenAsFloat(tok[4])); - } - - return nullptr; -} - -// ------------------------------------------------------------------------------------------------ -// peek into an element and check if it contains a FBX property, if so return its name. -std::string PeekPropertyName(const Element &element) { - //ai_assert(element.KeyToken().StringContents() == "P"); - const TokenList &tok = element.Tokens(); - if (tok.size() < 4) { - return ""; - } - - return ParseTokenAsString(tok[0]); -} -} // namespace - -// ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable() { -} - -// Is used when dealing with FBX Objects not metadata. -PropertyTable::PropertyTable(const ElementPtr element) : - element(element) { - Setup(element); -} - -// ------------------------------------------------------------------------------------------------ -PropertyTable::~PropertyTable() { - for (PropertyMap::value_type &v : props) { - delete v.second; - } -} - -void PropertyTable::Setup(ElementPtr ptr) { - const ScopePtr sc = GetRequiredScope(ptr); - const ElementPtr Properties70 = sc->GetElement("Properties70"); - const ScopePtr scope = GetOptionalScope(Properties70); - - // no scope, no care. - if (!scope) { - return; // NOTE: this is not an error this is actually a Object, without properties, here we will nullptr it. - } - - for (const ElementMap::value_type &v : scope->Elements()) { - if (v.first != "P") { - DOMWarning("expected only P elements in property table", v.second); - continue; - } - - const std::string &name = PeekPropertyName(*v.second); - if (!name.length()) { - DOMWarning("could not read property name", v.second); - continue; - } - - LazyPropertyMap::const_iterator it = lazyProps.find(name); - if (it != lazyProps.end()) { - DOMWarning("duplicate property name, will hide previous value: " + name, v.second); - continue; - } - - // since the above checks for duplicates we can be sure to insert the only match here. - lazyProps[name] = v.second; - } -} - -// ------------------------------------------------------------------------------------------------ -PropertyPtr PropertyTable::Get(const std::string &name) const { - PropertyMap::const_iterator it = props.find(name); - if (it == props.end()) { - // hasn't been parsed yet? - LazyPropertyMap::const_iterator lit = lazyProps.find(name); - if (lit != lazyProps.end()) { - props[name] = ReadTypedProperty(lit->second); - it = props.find(name); - - //ai_assert(it != props.end()); - } - - if (it == props.end()) { - // check property template - return nullptr; - } - } - - return (*it).second; -} - -DirectPropertyMap PropertyTable::GetUnparsedProperties() const { - DirectPropertyMap result; - - // Loop through all the lazy properties (which is all the properties) - for (const LazyPropertyMap::value_type &element : lazyProps) { - // Skip parsed properties - if (props.end() != props.find(element.first)) { - continue; - } - - // Read the element's value. - // Wrap the naked pointer (since the call site is required to acquire ownership) - // std::unique_ptr from C++11 would be preferred both as a wrapper and a return value. - Property *prop = ReadTypedProperty(element.second); - - // Element could not be read. Skip it. - if (!prop) { - continue; - } - - // Add to result - result[element.first] = prop; - } - - return result; -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXProperties.h b/modules/fbx/fbx_parser/FBXProperties.h deleted file mode 100644 index 4d74a1db17..0000000000 --- a/modules/fbx/fbx_parser/FBXProperties.h +++ /dev/null @@ -1,212 +0,0 @@ -/*************************************************************************/ -/* FBXProperties.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXProperties.h - * @brief FBX dynamic properties - */ -#ifndef FBX_PROPERTIES_H -#define FBX_PROPERTIES_H - -#include "FBXParser.h" -#include <map> -#include <memory> -#include <string> -#include <vector> - -namespace FBXDocParser { - -// Forward declarations -class Element; - -/** Represents a dynamic property. Type info added by deriving classes, - * see #TypedProperty. - Example: - @verbatim - P: "ShininessExponent", "double", "Number", "",0.5 - @endvebatim -*/ -class Property { -protected: - Property(); - -public: - virtual ~Property(); - -public: - template <typename T> - const T *As() const { - return dynamic_cast<const T *>(this); - } -}; - -template <typename T> -class TypedProperty : public Property { -public: - explicit TypedProperty(const T &value) : - value(value) { - // empty - } - - const T &Value() const { - return value; - } - -private: - T value; -}; - -#define new_Property new Property -typedef Property *PropertyPtr; -typedef std::map<std::string, PropertyPtr> DirectPropertyMap; -typedef std::map<std::string, PropertyPtr> PropertyMap; -typedef std::map<std::string, ElementPtr> LazyPropertyMap; - -/** - * Represents a property table as can be found in the newer FBX files (Properties60, Properties70) - */ -class PropertyTable { -public: - // in-memory property table with no source element - PropertyTable(); - PropertyTable(const ElementPtr element); - virtual ~PropertyTable(); - - PropertyPtr Get(const std::string &name) const; - void Setup(ElementPtr ptr); - - // PropertyTable's need not be coupled with FBX elements so this can be NULL - ElementPtr GetElement() { - return element; - } - - PropertyMap &GetProperties() { - return props; - } - - const LazyPropertyMap &GetLazyProperties() { - return lazyProps; - } - - DirectPropertyMap GetUnparsedProperties() const; - -private: - LazyPropertyMap lazyProps; - mutable PropertyMap props; - ElementPtr element = nullptr; -}; - -// ------------------------------------------------------------------------------------------------ -template <typename T> -inline T PropertyGet(const PropertyTable *in, const std::string &name, const T &defaultValue) { - PropertyPtr prop = in->Get(name); - if (nullptr == prop) { - return defaultValue; - } - - // strong typing, no need to be lenient - const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>(); - if (nullptr == tprop) { - return defaultValue; - } - - return tprop->Value(); -} - -// ------------------------------------------------------------------------------------------------ -template <typename T> -inline T PropertyGet(const PropertyTable *in, const std::string &name, bool &result, bool useTemplate = false) { - PropertyPtr prop = in->Get(name); - if (nullptr == prop) { - if (nullptr == in) { - result = false; - return T(); - } - prop = in->Get(name); - if (nullptr == prop) { - result = false; - return T(); - } - } - - // strong typing, no need to be lenient - const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>(); - if (nullptr == tprop) { - result = false; - return T(); - } - - result = true; - return tprop->Value(); -} -} // namespace FBXDocParser - -#endif // FBX_PROPERTIES_H diff --git a/modules/fbx/fbx_parser/FBXTokenizer.cpp b/modules/fbx/fbx_parser/FBXTokenizer.cpp deleted file mode 100644 index bac3e6d5c4..0000000000 --- a/modules/fbx/fbx_parser/FBXTokenizer.cpp +++ /dev/null @@ -1,253 +0,0 @@ -/*************************************************************************/ -/* FBXTokenizer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXTokenizer.cpp - * @brief Implementation of the FBX broadphase lexer - */ - -// tab width for logging columns -#define ASSIMP_FBX_TAB_WIDTH 4 - -#include "FBXTokenizer.h" -#include "core/string/print_string.h" - -namespace FBXDocParser { - -// ------------------------------------------------------------------------------------------------ -Token::Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column) : - sbegin(p_sbegin), - send(p_send), - type(p_type), - line(p_line), - column(p_column) { -#ifdef DEBUG_ENABLED - contents = std::string(sbegin, static_cast<size_t>(send - sbegin)); -#endif -} - -// ------------------------------------------------------------------------------------------------ -Token::~Token() { -} - -namespace { - -// ------------------------------------------------------------------------------------------------ -void TokenizeError(const std::string &message, unsigned int line, unsigned int column) { - print_error("[FBX-Tokenize]" + String(message.c_str()) + " " + itos(line) + ":" + itos(column)); -} - -// process a potential data token up to 'cur', adding it to 'output_tokens'. -// ------------------------------------------------------------------------------------------------ -void ProcessDataToken(TokenList &output_tokens, const char *&start, const char *&end, - unsigned int line, - unsigned int column, - TokenType type = TokenType_DATA, - bool must_have_token = false) { - if (start && end) { - // sanity check: - // tokens should have no whitespace outside quoted text and [start,end] should - // properly delimit the valid range. - bool in_double_quotes = false; - for (const char *c = start; c != end + 1; ++c) { - if (*c == '\"') { - in_double_quotes = !in_double_quotes; - } - - if (!in_double_quotes && IsSpaceOrNewLine(*c)) { - TokenizeError("unexpected whitespace in token", line, column); - } - } - - if (in_double_quotes) { - TokenizeError("non-terminated double quotes", line, column); - } - - output_tokens.push_back(new_Token(start, end + 1, type, line, column)); - } else if (must_have_token) { - TokenizeError("unexpected character, expected data token", line, column); - } - - start = end = nullptr; -} -} // namespace - -// ------------------------------------------------------------------------------------------------ -void Tokenize(TokenList &output_tokens, const char *input, size_t length, bool &corrupt) { - // line and column numbers numbers are one-based - unsigned int line = 1; - unsigned int column = 1; - - bool comment = false; - bool in_double_quotes = false; - bool pending_data_token = false; - - const char *token_begin = nullptr, *token_end = nullptr; - - // input (starting string), *cur the current string, column += - // modified to fix strlen() and stop buffer overflow - for (size_t x = 0; x < length; x++) { - const char c = input[x]; - const char *cur = &input[x]; - column += (c == '\t' ? ASSIMP_FBX_TAB_WIDTH : 1); - - if (IsLineEnd(c)) { - comment = false; - - column = 0; - ++line; - } - - if (comment) { - continue; - } - - if (in_double_quotes) { - if (c == '\"') { - in_double_quotes = false; - token_end = cur; - - ProcessDataToken(output_tokens, token_begin, token_end, line, column); - pending_data_token = false; - } - continue; - } - - switch (c) { - case '\"': - if (token_begin) { - TokenizeError("unexpected double-quote", line, column); - corrupt = true; - return; - } - token_begin = cur; - in_double_quotes = true; - continue; - - case ';': - ProcessDataToken(output_tokens, token_begin, token_end, line, column); - comment = true; - continue; - - case '{': - ProcessDataToken(output_tokens, token_begin, token_end, line, column); - output_tokens.push_back(new_Token(cur, cur + 1, TokenType_OPEN_BRACKET, line, column)); - continue; - - case '}': - ProcessDataToken(output_tokens, token_begin, token_end, line, column); - output_tokens.push_back(new_Token(cur, cur + 1, TokenType_CLOSE_BRACKET, line, column)); - continue; - - case ',': - if (pending_data_token) { - ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_DATA, true); - } - output_tokens.push_back(new_Token(cur, cur + 1, TokenType_COMMA, line, column)); - continue; - - case ':': - if (pending_data_token) { - ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_KEY, true); - } else { - TokenizeError("unexpected colon", line, column); - } - continue; - } - - if (IsSpaceOrNewLine(c)) { - if (token_begin) { - // peek ahead and check if the next token is a colon in which - // case this counts as KEY token. - TokenType type = TokenType_DATA; - for (const char *peek = cur; *peek && IsSpaceOrNewLine(*peek); ++peek) { - if (*peek == ':') { - type = TokenType_KEY; - cur = peek; - break; - } - } - - ProcessDataToken(output_tokens, token_begin, token_end, line, column, type); - } - - pending_data_token = false; - } else { - token_end = cur; - if (!token_begin) { - token_begin = cur; - } - - pending_data_token = true; - } - } -} -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXTokenizer.h b/modules/fbx/fbx_parser/FBXTokenizer.h deleted file mode 100644 index 789f8b5d9d..0000000000 --- a/modules/fbx/fbx_parser/FBXTokenizer.h +++ /dev/null @@ -1,203 +0,0 @@ -/*************************************************************************/ -/* FBXTokenizer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXTokenizer.h - * @brief FBX lexer - */ -#ifndef FBX_TOKENIZER_H -#define FBX_TOKENIZER_H - -#include "FBXParseTools.h" -#include "core/string/ustring.h" -#include <iostream> -#include <memory> -#include <string> -#include <vector> - -namespace FBXDocParser { -/** Rough classification for text FBX tokens used for constructing the - * basic scope hierarchy. */ -enum TokenType { - // { - TokenType_OPEN_BRACKET = 0, - - // } - TokenType_CLOSE_BRACKET, - - // '"blablubb"', '2', '*14' - very general token class, - // further processing happens at a later stage. - TokenType_DATA, - - // - TokenType_BINARY_DATA, - - // , - TokenType_COMMA, - - // blubb: - TokenType_KEY -}; - -/** Represents a single token in a FBX file. Tokens are - * classified by the #TokenType enumerated types. - * - * Offers iterator protocol. Tokens are immutable. */ -class Token { -private: - static const unsigned int BINARY_MARKER = static_cast<unsigned int>(-1); - -public: - /** construct a textual token */ - Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column); - - /** construct a binary token */ - Token(const char *p_sbegin, const char *p_send, TokenType p_type, size_t p_offset); - ~Token(); - -public: - std::string StringContents() const { - return std::string(begin(), end()); - } - - bool IsBinary() const { - return column == BINARY_MARKER; - } - - const char *begin() const { - return sbegin; - } - - const char *end() const { - return send; - } - - TokenType Type() const { - return type; - } - - size_t Offset() const { - return offset; - } - - unsigned int Line() const { - return static_cast<unsigned int>(line); - } - - unsigned int Column() const { - return column; - } - -private: -#ifdef DEBUG_ENABLED - // full string copy for the sole purpose that it nicely appears - // in msvc's debugger window. - std::string contents; -#endif - - const char *sbegin = nullptr; - const char *send = nullptr; - const TokenType type; - - union { - size_t line; - size_t offset; - }; - const unsigned int column = 0; -}; - -// Fixed leak by using shared_ptr for tokens -typedef Token *TokenPtr; -typedef std::vector<TokenPtr> TokenList; - -#define new_Token new Token - -/** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens. - * - * Skips over comments and generates line and column numbers. - * - * @param output_tokens Receives a list of all tokens in the input data. - * @param input_buffer Textual input buffer to be processed, 0-terminated. - * @print_error if something goes wrong */ -void Tokenize(TokenList &output_tokens, const char *input, size_t length, bool &corrupt); - -/** Tokenizer function for binary FBX files. - * - * Emits a token list suitable for direct parsing. - * - * @param output_tokens Receives a list of all tokens in the input data. - * @param input_buffer Binary input buffer to be processed. - * @param length Length of input buffer, in bytes. There is no 0-terminal. - * @print_error if something goes wrong */ -void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, bool &corrupt); -} // namespace FBXDocParser - -#endif // FBX_TOKENIZER_H diff --git a/modules/fbx/fbx_parser/FBXUtil.cpp b/modules/fbx/fbx_parser/FBXUtil.cpp deleted file mode 100644 index e4958547d4..0000000000 --- a/modules/fbx/fbx_parser/FBXUtil.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/*************************************************************************/ -/* FBXUtil.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXUtil.cpp - * @brief Implementation of internal FBX utility functions - */ - -#include "FBXUtil.h" -#include "FBXTokenizer.h" -#include <cstring> -#include <string> - -namespace FBXDocParser { -namespace Util { - -// ------------------------------------------------------------------------------------------------ -const char *TokenTypeString(TokenType t) { - switch (t) { - case TokenType_OPEN_BRACKET: - return "TOK_OPEN_BRACKET"; - - case TokenType_CLOSE_BRACKET: - return "TOK_CLOSE_BRACKET"; - - case TokenType_DATA: - return "TOK_DATA"; - - case TokenType_COMMA: - return "TOK_COMMA"; - - case TokenType_KEY: - return "TOK_KEY"; - - case TokenType_BINARY_DATA: - return "TOK_BINARY_DATA"; - } - - //ai_assert(false); - return ""; -} - -// Generated by this formula: T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; -static const uint8_t base64DecodeTable[128] = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, - 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, - 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255 -}; - -uint8_t DecodeBase64(char ch) { - const uint8_t idx = static_cast<uint8_t>(ch); - if (idx > 127) { - return 255; - } - return base64DecodeTable[idx]; -} - -size_t ComputeDecodedSizeBase64(const char *in, size_t inLength) { - if (inLength < 2) { - return 0; - } - const size_t equals = size_t(in[inLength - 1] == '=') + size_t(in[inLength - 2] == '='); - const size_t full_length = (inLength * 3) >> 2; // div by 4 - if (full_length < equals) { - return 0; - } - return full_length - equals; -} - -size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength) { - if (maxOutLength == 0 || inLength < 2) { - return 0; - } - const size_t realLength = inLength - size_t(in[inLength - 1] == '=') - size_t(in[inLength - 2] == '='); - size_t dst_offset = 0; - int val = 0, valb = -8; - for (size_t src_offset = 0; src_offset < realLength; ++src_offset) { - const uint8_t table_value = Util::DecodeBase64(in[src_offset]); - if (table_value == 255) { - return 0; - } - val = (val << 6) + table_value; - valb += 6; - if (valb >= 0) { - out[dst_offset++] = static_cast<uint8_t>((val >> valb) & 0xFF); - valb -= 8; - val &= 0xFFF; - } - } - return dst_offset; -} - -static const char to_base64_string[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -char EncodeBase64(char byte) { - return to_base64_string[(size_t)byte]; -} - -/** Encodes a block of 4 bytes to base64 encoding - * @param bytes Bytes to encode. - * @param out_string String to write encoded values to. - * @param string_pos Position in out_string. - */ -void EncodeByteBlock(const char *bytes, std::string &out_string, size_t string_pos) { - char b0 = (bytes[0] & 0xFC) >> 2; - char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4); - char b2 = (bytes[1] & 0x0F) << 2 | ((bytes[2] & 0xC0) >> 6); - char b3 = (bytes[2] & 0x3F); - - out_string[string_pos + 0] = EncodeBase64(b0); - out_string[string_pos + 1] = EncodeBase64(b1); - out_string[string_pos + 2] = EncodeBase64(b2); - out_string[string_pos + 3] = EncodeBase64(b3); -} - -std::string EncodeBase64(const char *data, size_t length) { - // calculate extra bytes needed to get a multiple of 3 - size_t extraBytes = 3 - length % 3; - - // number of base64 bytes - size_t encodedBytes = 4 * (length + extraBytes) / 3; - - std::string encoded_string(encodedBytes, '='); - - // read blocks of 3 bytes - for (size_t ib3 = 0; ib3 < length / 3; ib3++) { - const size_t iByte = ib3 * 3; - const size_t iEncodedByte = ib3 * 4; - const char *currData = &data[iByte]; - - EncodeByteBlock(currData, encoded_string, iEncodedByte); - } - - // if size of data is not a multiple of 3, also encode the final bytes (and add zeros where needed) - if (extraBytes > 0) { - char finalBytes[4] = { 0, 0, 0, 0 }; - memcpy(&finalBytes[0], &data[length - length % 3], length % 3); - - const size_t iEncodedByte = encodedBytes - 4; - EncodeByteBlock(&finalBytes[0], encoded_string, iEncodedByte); - - // add '=' at the end - for (size_t i = 0; i < 4 * extraBytes / 3; i++) { - encoded_string[encodedBytes - i - 1] = '='; - } - } - return encoded_string; -} -} // namespace Util -} // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXUtil.h b/modules/fbx/fbx_parser/FBXUtil.h deleted file mode 100644 index 8022233029..0000000000 --- a/modules/fbx/fbx_parser/FBXUtil.h +++ /dev/null @@ -1,122 +0,0 @@ -/*************************************************************************/ -/* FBXUtil.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file FBXUtil.h - * @brief FBX utility functions for internal use - */ -#ifndef FBX_UTIL_H -#define FBX_UTIL_H - -#include "FBXTokenizer.h" -#include <stdint.h> - -namespace FBXDocParser { - -namespace Util { - -/** Get a string representation for a #TokenType. */ -const char *TokenTypeString(TokenType t); - -/** Decode a single Base64-encoded character. - * - * @param ch Character to decode (from base64 to binary). - * @return decoded byte value*/ -uint8_t DecodeBase64(char ch); - -/** Compute decoded size of a Base64-encoded string - * - * @param in Characters to decode. - * @param inLength Number of characters to decode. - * @return size of the decoded data (number of bytes)*/ -size_t ComputeDecodedSizeBase64(const char *in, size_t inLength); - -/** Decode a Base64-encoded string - * - * @param in Characters to decode. - * @param inLength Number of characters to decode. - * @param out Pointer where we will store the decoded data. - * @param maxOutLength Size of output buffer. - * @return size of the decoded data (number of bytes)*/ -size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength); - -char EncodeBase64(char byte); - -/** Encode bytes in base64-encoding - * - * @param data Binary data to encode. - * @param inLength Number of bytes to encode. - * @return base64-encoded string*/ -std::string EncodeBase64(const char *data, size_t length); -} // namespace Util -} // namespace FBXDocParser - -#endif // FBX_UTIL_H diff --git a/modules/fbx/fbx_parser/LICENSE b/modules/fbx/fbx_parser/LICENSE deleted file mode 100644 index b42fc6efe6..0000000000 --- a/modules/fbx/fbx_parser/LICENSE +++ /dev/null @@ -1,39 +0,0 @@ -The files in this folder were originally from ASSIMP, but have been heavily modified to fix bugs and match coding -conventions of the Godot Engine project. We have kept a copy of the applicable licenses in the folder as required by -the license. - -Open Asset Import Library (assimp) - -Copyright (c) 2006-2020, assimp team -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/modules/fbx/register_types.h b/modules/fbx/register_types.h deleted file mode 100644 index 6e3cc0dc46..0000000000 --- a/modules/fbx/register_types.h +++ /dev/null @@ -1,37 +0,0 @@ -/*************************************************************************/ -/* register_types.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 - -void register_fbx_types(); -void unregister_fbx_types(); - -#endif // FBX_REGISTER_TYPES_H diff --git a/modules/fbx/tools/import_utils.cpp b/modules/fbx/tools/import_utils.cpp deleted file mode 100644 index 5a49f24c00..0000000000 --- a/modules/fbx/tools/import_utils.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/*************************************************************************/ -/* import_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "import_utils.h" - -Vector3 ImportUtils::deg2rad(const Vector3 &p_rotation) { - return p_rotation / 180.0 * Math_PI; -} - -Vector3 ImportUtils::rad2deg(const Vector3 &p_rotation) { - return p_rotation / Math_PI * 180.0; -} - -Basis ImportUtils::EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) { - Basis ret; - - // FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot - // by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf - switch (mode) { - case FBXDocParser::Model::RotOrder_EulerXYZ: - ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ); - break; - - case FBXDocParser::Model::RotOrder_EulerXZY: - ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY); - break; - - case FBXDocParser::Model::RotOrder_EulerYZX: - ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX); - break; - - case FBXDocParser::Model::RotOrder_EulerYXZ: - ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ); - break; - - case FBXDocParser::Model::RotOrder_EulerZXY: - ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY); - break; - - case FBXDocParser::Model::RotOrder_EulerZYX: - ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX); - break; - - case FBXDocParser::Model::RotOrder_SphericXYZ: - // TODO do this. - break; - - default: - // If you land here, Please integrate all enums. - CRASH_NOW_MSG("This is not unreachable."); - } - - return ret; -} - -Quaternion ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) { - return ImportUtils::EulerToBasis(mode, p_rotation); -} - -Vector3 ImportUtils::BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation) { - // FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot - // by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf - switch (mode) { - case FBXDocParser::Model::RotOrder_EulerXYZ: - return p_rotation.get_euler(Basis::EULER_ORDER_XYZ); - - case FBXDocParser::Model::RotOrder_EulerXZY: - return p_rotation.get_euler(Basis::EULER_ORDER_XZY); - - case FBXDocParser::Model::RotOrder_EulerYZX: - return p_rotation.get_euler(Basis::EULER_ORDER_YZX); - - case FBXDocParser::Model::RotOrder_EulerYXZ: - return p_rotation.get_euler(Basis::EULER_ORDER_YXZ); - - case FBXDocParser::Model::RotOrder_EulerZXY: - return p_rotation.get_euler(Basis::EULER_ORDER_ZXY); - - case FBXDocParser::Model::RotOrder_EulerZYX: - return p_rotation.get_euler(Basis::EULER_ORDER_ZYX); - - case FBXDocParser::Model::RotOrder_SphericXYZ: - // TODO - return Vector3(); - - default: - // If you land here, Please integrate all enums. - CRASH_NOW_MSG("This is not unreachable."); - return Vector3(); - } -} - -Vector3 ImportUtils::QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quaternion &p_rotation) { - return BasisToEuler(mode, p_rotation); -} - -Transform3D get_unscaled_transform(const Transform3D &p_initial, real_t p_scale) { - Transform3D unscaled = Transform3D(p_initial.basis, p_initial.origin * p_scale); - ERR_FAIL_COND_V_MSG(unscaled.basis.determinant() == 0, Transform3D(), "det is zero unscaled?"); - return unscaled; -} - -Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices) { - ERR_FAIL_COND_V_MSG(p_vertices.size() < 3, Vector3(0, 0, 0), "At least 3 vertices are necessary"); - // Using long double to make sure that normal is computed for even really tiny objects. - typedef long double ldouble; - ldouble x = 0.0; - ldouble y = 0.0; - ldouble z = 0.0; - for (size_t i = 0; i < p_vertices.size(); i += 1) { - const Vector3 current = p_vertices[i]; - const Vector3 next = p_vertices[(i + 1) % p_vertices.size()]; - x += (ldouble(current.y) - ldouble(next.y)) * (ldouble(current.z) + ldouble(next.z)); - y += (ldouble(current.z) - ldouble(next.z)) * (ldouble(current.x) + ldouble(next.x)); - z += (ldouble(current.x) - ldouble(next.x)) * (ldouble(current.y) + ldouble(next.y)); - } - const ldouble l2 = x * x + y * y + z * z; - if (l2 == 0.0) { - return (p_vertices[0] - p_vertices[1]).normalized().cross((p_vertices[0] - p_vertices[2]).normalized()).normalized(); - } else { - const double l = Math::sqrt(double(l2)); - return Vector3(x / l, y / l, z / l); - } -} diff --git a/modules/fbx/tools/import_utils.h b/modules/fbx/tools/import_utils.h deleted file mode 100644 index b52263c2ac..0000000000 --- a/modules/fbx/tools/import_utils.h +++ /dev/null @@ -1,400 +0,0 @@ -/*************************************************************************/ -/* import_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 IMPORT_UTILS_FBX_IMPORTER_H -#define IMPORT_UTILS_FBX_IMPORTER_H - -#include "core/io/image_loader.h" - -#include "data/import_state.h" -#include "fbx_parser/FBXDocument.h" - -#include <string> - -#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL - -/** - * Import Utils - * Conversion tools / glue code to convert from FBX to Godot - */ -class ImportUtils { -public: - /// Convert a vector from degrees to radians. - static Vector3 deg2rad(const Vector3 &p_rotation); - - /// Convert a vector from radians to degrees. - static Vector3 rad2deg(const Vector3 &p_rotation); - - /// Converts rotation order vector (in rad) to quaternion. - static Basis EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation); - - /// Converts rotation order vector (in rad) to quaternion. - static Quaternion EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation); - - /// Converts basis into rotation order vector (in rad). - static Vector3 BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation); - - /// Converts quaternion into rotation order vector (in rad). - static Vector3 QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quaternion &p_rotation); - - static void debug_xform(String name, const Transform3D &t) { - print_verbose(name + " " + t.origin + " rotation: " + (t.basis.get_euler() * (180 / Math_PI))); - } - - static String FBXNodeToName(const std::string &name) { - // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if - // this causes ambiguities, well possible between empty identifiers, - // such as "Model::" and ""). Make sure the behaviour is consistent - // across multiple calls to FixNodeName(). - - // We must remove this from the name - // Some bones have this - // SubDeformer:: - // Meshes, Joints have this, some other IK elements too. - // Model:: - - String node_name = String(name.c_str()); - - if (node_name.substr(0, 7) == "Model::") { - node_name = node_name.substr(7, node_name.length() - 7); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 13) == "SubDeformer::") { - node_name = node_name.substr(13, node_name.length() - 13); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 11) == "AnimStack::") { - node_name = node_name.substr(11, node_name.length() - 11); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 15) == "AnimCurveNode::") { - node_name = node_name.substr(15, node_name.length() - 15); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 11) == "AnimCurve::") { - node_name = node_name.substr(11, node_name.length() - 11); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 10) == "Geometry::") { - node_name = node_name.substr(10, node_name.length() - 10); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 10) == "Material::") { - node_name = node_name.substr(10, node_name.length() - 10); - return node_name.replace(":", ""); - } - - if (node_name.substr(0, 9) == "Texture::") { - node_name = node_name.substr(9, node_name.length() - 9); - return node_name.replace(":", ""); - } - - return node_name.replace(":", ""); - } - - static std::string FBXAnimMeshName(const std::string &name) { - if (name.length()) { - size_t indexOf = name.find_first_of("::"); - if (indexOf != std::string::npos && indexOf < name.size() - 2) { - return name.substr(indexOf + 2); - } - } - return name.length() ? name : "AnimMesh"; - } - - static Vector3 safe_import_vector3(const Vector3 &p_vec) { - Vector3 vector = p_vec; - if (Math::is_zero_approx(vector.x)) { - vector.x = 0; - } - - if (Math::is_zero_approx(vector.y)) { - vector.y = 0; - } - - if (Math::is_zero_approx(vector.z)) { - vector.z = 0; - } - return vector; - } - - static void debug_xform(String name, const Basis &t) { - //print_verbose(name + " rotation: " + (t.get_euler() * (180 / Math_PI))); - } - - static Vector3 FixAxisConversions(Vector3 input) { - return Vector3(input.x, input.y, input.z); - } - - static void AlignMeshAxes(std::vector<Vector3> &vertex_data) { - for (size_t x = 0; x < vertex_data.size(); x++) { - vertex_data[x] = FixAxisConversions(vertex_data[x]); - } - } - - struct AssetImportFbx { - enum ETimeMode { - TIME_MODE_DEFAULT = 0, - TIME_MODE_120 = 1, - TIME_MODE_100 = 2, - TIME_MODE_60 = 3, - TIME_MODE_50 = 4, - TIME_MODE_48 = 5, - TIME_MODE_30 = 6, - TIME_MODE_30_DROP = 7, - TIME_MODE_NTSC_DROP_FRAME = 8, - TIME_MODE_NTSC_FULL_FRAME = 9, - TIME_MODE_PAL = 10, - TIME_MODE_CINEMA = 11, - TIME_MODE_1000 = 12, - TIME_MODE_CINEMA_ND = 13, - TIME_MODE_CUSTOM = 14, - TIME_MODE_TIME_MODE_COUNT = 15 - }; - enum UpAxis { - UP_VECTOR_AXIS_X = 1, - UP_VECTOR_AXIS_Y = 2, - UP_VECTOR_AXIS_Z = 3 - }; - enum FrontAxis { - FRONT_PARITY_EVEN = 1, - FRONT_PARITY_ODD = 2, - }; - - enum CoordAxis { - COORD_RIGHT = 0, - COORD_LEFT = 1 - }; - }; - - /** Get fbx fps for time mode meta data - */ - static float get_fbx_fps(int32_t time_mode) { - switch (time_mode) { - case AssetImportFbx::TIME_MODE_DEFAULT: - return 24; - case AssetImportFbx::TIME_MODE_120: - return 120; - case AssetImportFbx::TIME_MODE_100: - return 100; - case AssetImportFbx::TIME_MODE_60: - return 60; - case AssetImportFbx::TIME_MODE_50: - return 50; - case AssetImportFbx::TIME_MODE_48: - return 48; - case AssetImportFbx::TIME_MODE_30: - return 30; - case AssetImportFbx::TIME_MODE_30_DROP: - return 30; - case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: - return 29.9700262f; - case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: - return 29.9700262f; - case AssetImportFbx::TIME_MODE_PAL: - return 25; - case AssetImportFbx::TIME_MODE_CINEMA: - return 24; - case AssetImportFbx::TIME_MODE_1000: - return 1000; - case AssetImportFbx::TIME_MODE_CINEMA_ND: - return 23.976f; - case AssetImportFbx::TIME_MODE_CUSTOM: - return -1; - } - return 0; - } - - static float get_fbx_fps(const FBXDocParser::FileGlobalSettings *FBXSettings) { - int time_mode = FBXSettings->TimeMode(); - - // get the animation FPS - float frames_per_second = get_fbx_fps(time_mode); - - // handle animation custom FPS time. - if (time_mode == ImportUtils::AssetImportFbx::TIME_MODE_CUSTOM) { - print_verbose("FBX Animation has custom FPS setting"); - frames_per_second = FBXSettings->CustomFrameRate(); - - // not our problem this is the modeller, we can print as an error so they can fix the source. - if (frames_per_second == 0) { - print_error("Custom animation time in file is set to 0 value, animation won't play, please edit your file to correct the FPS value"); - } - } - return frames_per_second; - } - - /** - * Find hardcoded textures from assimp which could be in many different directories - */ - - /** - * set_texture_mapping_mode - * Helper to check the mapping mode of the texture (repeat, clamp and mirror) - */ - // static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) { - // ERR_FAIL_COND(texture.is_null()); - // ERR_FAIL_COND(map_mode == nullptr); - // aiTextureMapMode tex_mode = map_mode[0]; - - // int32_t flags = Texture::FLAGS_DEFAULT; - // if (tex_mode == aiTextureMapMode_Wrap) { - // //Default - // } else if (tex_mode == aiTextureMapMode_Clamp) { - // flags = flags & ~Texture::FLAG_REPEAT; - // } else if (tex_mode == aiTextureMapMode_Mirror) { - // flags = flags | Texture::FLAG_MIRRORED_REPEAT; - // } - // texture->set_flags(flags); - // } - - /** - * Load or load from cache image :) - * We need to upgrade this in the later version :) should not be hard - */ - //static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path){ - // Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path); - - // // if our cache contains this image then don't bother - // if (match) { - // return match->get(); - // } - - // Vector<String> split_path = p_path.get_basename().split("*"); - // if (split_path.size() == 2) { - // size_t texture_idx = split_path[1].to_int(); - // ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>()); - // aiTexture *tex = p_scene->mTextures[texture_idx]; - // String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename); - // filename = filename.get_file(); - // print_verbose("Open Asset Import: Loading embedded texture " + filename); - // if (tex->mHeight == 0) { - // if (tex->CheckFormat("png")) { - // Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - // state.path_to_image_cache.insert(p_path, img); - // return img; - // } else if (tex->CheckFormat("jpg")) { - // Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - // state.path_to_image_cache.insert(p_path, img); - // return img; - // } else if (tex->CheckFormat("dds")) { - // ERR_FAIL_COND_V_MSG(true, Ref<Image>(), "Open Asset Import: Embedded dds not implemented"); - // } - // } else { - // Ref<Image> img; - // img.instantiate(); - // PoolByteArray arr; - // uint32_t size = tex->mWidth * tex->mHeight; - // arr.resize(size); - // memcpy(arr.write().ptr(), tex->pcData, size); - // ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>()); - // //ARGB8888 to RGBA8888 - // for (int32_t i = 0; i < arr.size() / 4; i++) { - // arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; - // arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; - // arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; - // arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; - // } - // img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); - // ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); - // state.path_to_image_cache.insert(p_path, img); - // return img; - // } - // return Ref<Image>(); - // } else { - // Ref<Texture> texture = ResourceLoader::load(p_path); - // ERR_FAIL_COND_V(texture.is_null(), Ref<Image>()); - // Ref<Image> image = texture->get_image(); - // ERR_FAIL_COND_V(image.is_null(), Ref<Image>()); - // state.path_to_image_cache.insert(p_path, image); - // return image; - // } - - // return Ref<Image>(); - //} - - // /* create texture from assimp data, if found in path */ - // static bool CreateAssimpTexture( - // AssimpImporter::ImportState &state, - // aiString texture_path, - // String &filename, - // String &path, - // AssimpImageData &image_state) { - // filename = get_raw_string_from_assimp(texture_path); - // path = state.path.get_base_dir().plus_file(filename.replace("\\", "/")); - // bool found = false; - // find_texture_path(state.path, path, found); - // if (found) { - // image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path); - // if (image_state.raw_image.is_valid()) { - // image_state.texture.instantiate(); - // image_state.texture->create_from_image(image_state.raw_image); - // image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - // return true; - // } - // } - - // return false; - // } - // /** GetAssimpTexture - // * Designed to retrieve textures for you - // */ - // static bool GetAssimpTexture( - // AssimpImporter::ImportState &state, - // aiMaterial *ai_material, - // aiTextureType texture_type, - // String &filename, - // String &path, - // AssimpImageData &image_state) { - // aiString ai_filename = aiString(); - // if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, nullptr, nullptr, nullptr, nullptr, image_state.map_mode)) { - // return CreateAssimpTexture(state, ai_filename, filename, path, image_state); - // } - - // return false; - // } -}; - -// Apply the transforms so the basis will have scale 1. -Transform3D get_unscaled_transform(const Transform3D &p_initial, real_t p_scale); - -/// Uses the Newell's method to compute any polygon normal. -/// The polygon must be at least size of 3 or bigger. -Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices); - -#endif // IMPORT_UTILS_FBX_IMPORTER_H diff --git a/modules/fbx/tools/validation_tools.h b/modules/fbx/tools/validation_tools.h deleted file mode 100644 index 69a2673197..0000000000 --- a/modules/fbx/tools/validation_tools.h +++ /dev/null @@ -1,92 +0,0 @@ -/*************************************************************************/ -/* validation_tools.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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_VALIDATION_TOOLS_H -#define FBX_VALIDATION_TOOLS_H - -#ifdef TOOLS_ENABLED - -#include "core/io/file_access.h" -#include "core/string/print_string.h" -#include "core/templates/local_vector.h" -#include "core/templates/map.h" - -class ValidationTracker { -protected: - struct Entries { - Map<String, LocalVector<String>> validation_entries = Map<String, LocalVector<String>>(); - - // for printing our CSV to dump validation problems of files - // later we can make some agnostic tooling for this but this is fine for the time being. - void add_validation_error(String asset_path, String message); - void print_to_csv() { - print_verbose("Exporting assset validation log please wait"); - String massive_log_file; - - String csv_header = "file_path, error message, extra data\n"; - massive_log_file += csv_header; - - for (const KeyValue<String, LocalVector<String>> &element : validation_entries) { - for (unsigned int x = 0; x < element.value.size(); x++) { - const String &line_entry = element.key + ", " + element.value[x].c_escape() + "\n"; - massive_log_file += line_entry; - } - } - - String path = "asset_validation_errors.csv"; - Error err; - FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err); - if (!file || err) { - if (file) { - memdelete(file); - } - print_error("ValidationTracker Error - failed to create file - path: %s\n" + path); - return; - } - - file->store_string(massive_log_file); - if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { - print_error("ValidationTracker Error - failed to write to file - path: %s\n" + path); - } - file->close(); - memdelete(file); - } - }; - // asset path, error messages - static Entries *entries_singleton; - -public: - static Entries *get_singleton() { - return entries_singleton; - } -}; - -#endif // TOOLS_ENABLED -#endif // FBX_VALIDATION_TOOLS_H diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 27e1520ab8..6f25b06e87 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2426,6 +2426,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a base_type = parser->current_class->base_type; base_type.is_meta_type = false; is_self = true; + + if (p_call->callee == nullptr && !lambda_stack.is_empty()) { + push_error("Cannot use `super()` inside a lambda.", p_call); + } } else if (callee_type == GDScriptParser::Node::IDENTIFIER) { base_type = parser->current_class->get_datatype(); base_type.is_meta_type = false; @@ -2494,12 +2498,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { - push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call); } else if (!is_self && base_type.is_meta_type && !is_static) { base_type.is_meta_type = false; // For `to_string()`. - push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call->callee); + push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call); } else if (is_self && !is_static && !lambda_stack.is_empty()) { - push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); + push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call); } call_type = return_type; @@ -2539,7 +2543,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (call_type.is_coroutine && !p_is_await && !p_is_root) { - push_error(vformat(R"*(Function "%s()" is a coroutine, so it must be called with "await".)*", p_call->function_name), p_call->callee); + push_error(vformat(R"*(Function "%s()" is a coroutine, so it must be called with "await".)*", p_call->function_name), p_call); } p_call->set_datatype(call_type); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index fd8fcea7b3..5ed4054c57 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2397,7 +2397,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_arghint = _make_arguments_hint(info, p_argidx); return; } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { - // Complete constructor + // Complete constructor. List<MethodInfo> constructors; Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); @@ -2423,6 +2423,32 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); + if (subscript->base != nullptr && subscript->base->type == GDScriptParser::Node::IDENTIFIER) { + const GDScriptParser::IdentifierNode *base_identifier = static_cast<const GDScriptParser::IdentifierNode *>(subscript->base); + + Variant::Type method_type = GDScriptParser::get_builtin_type(base_identifier->name); + if (method_type < Variant::VARIANT_MAX) { + Variant v; + Callable::CallError err; + Variant::construct(method_type, v, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + return; + } + List<MethodInfo> methods; + v.get_method_list(&methods); + + for (MethodInfo &E : methods) { + if (p_argidx >= E.arguments.size()) { + continue; + } + if (E.name == call->function_name) { + r_arghint += _make_arguments_hint(E, p_argidx); + return; + } + } + } + } + if (subscript->is_attribute) { GDScriptCompletionIdentifier ci; if (_guess_expression_type(p_context, subscript->base, ci)) { @@ -2485,17 +2511,36 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options); r_forced = true; } break; - case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - List<StringName> constants; - Variant::get_constants_for_type(completion_context.builtin_type, &constants); - for (const StringName &E : constants) { - ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); - bool valid = false; - Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E, &valid); - if (valid) { - option.default_value = default_value; + case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { + // Constants. + { + List<StringName> constants; + Variant::get_constants_for_type(completion_context.builtin_type, &constants); + for (const StringName &E : constants) { + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); + bool valid = false; + Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E, &valid); + if (valid) { + option.default_value = default_value; + } + options.insert(option.display, option); + } + } + // Methods. + { + List<StringName> methods; + Variant::get_builtin_method_list(completion_context.builtin_type, &methods); + for (const StringName &E : methods) { + if (Variant::is_builtin_method_static(completion_context.builtin_type, E)) { + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); + if (Variant::get_builtin_method_argument_count(completion_context.builtin_type, E) > 0 || Variant::is_builtin_method_vararg(completion_context.builtin_type, E)) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + options.insert(option.display, option); + } } - options.insert(option.display, option); } } break; case GDScriptParser::COMPLETION_INHERIT_TYPE: { @@ -3049,11 +3094,21 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co bool is_function = false; switch (context.type) { - case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(context.builtin_type); - r_result.class_member = p_symbol; - return OK; + case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { + if (!Variant::has_builtin_method(context.builtin_type, StringName(p_symbol))) { + // A constant. + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; + r_result.class_name = Variant::get_type_name(context.builtin_type); + r_result.class_member = p_symbol; + return OK; + } + // A method. + GDScriptParser::DataType base_type; + base_type.kind = GDScriptParser::DataType::BUILTIN; + base_type.builtin_type = context.builtin_type; + if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { + return OK; + } } break; case GDScriptParser::COMPLETION_SUPER_METHOD: case GDScriptParser::COMPLETION_METHOD: { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 10709d3667..67d778f932 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2685,7 +2685,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { - make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true); + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); is_builtin = true; } } @@ -2752,7 +2752,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre pop_multiline(); return nullptr; } - call->function_name = current_function->identifier->name; + if (current_function->identifier) { + call->function_name = current_function->identifier->name; + } else { + call->function_name = SNAME("<anonymous>"); + } } else { consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); make_completion_context(COMPLETION_SUPER_METHOD, call, true); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index c09b07282f..0da1896936 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1146,7 +1146,7 @@ public: COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values). COMPLETION_ATTRIBUTE, // After id.| to look for members. COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods. - COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue). + COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, // Constants inside a built-in type (e.g. Color.BLUE) or static methods (e.g. Color.html). COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions). // TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func). COMPLETION_GET_NODE, // Get node with $ notation. diff --git a/modules/gltf/config.py b/modules/gltf/config.py index a4736321fa..189b5a831a 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -8,12 +8,16 @@ def configure(env): def get_doc_classes(): return [ - "EditorSceneImporterGLTF", + "EditorSceneFormatImporterBlend", + "EditorSceneFormatImporterFBX", + "EditorSceneFormatImporterGLTF", "GLTFAccessor", "GLTFAnimation", "GLTFBufferView", "GLTFCamera", "GLTFDocument", + "GLTFDocumentExtension", + "GLTFDocumentExtensionConvertImporterMesh", "GLTFLight", "GLTFMesh", "GLTFNode", @@ -22,8 +26,6 @@ def get_doc_classes(): "GLTFSpecGloss", "GLTFState", "GLTFTexture", - "GLTFDocumentExtension", - "GLTFDocumentExtensionConvertImporterMesh", ] diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml new file mode 100644 index 0000000000..9c6c8e03c4 --- /dev/null +++ b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorSceneFormatImporterBlend" inherits="EditorSceneFormatImporter" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Importer for Blender's [code].blend[/code] scene file format. + </brief_description> + <description> + Imports Blender scenes in the [code].blend[/code] file format through the glTF 2.0 3D import pipeline. This importer requires Blender to be installed by the user, so that it can be used to export the scene as glTF 2.0. + The location of the Blender binary is set via the [code]filesystem/import/blend/blender_path[/code] editor setting. + This importer is only used if [member ProjectSettings.filesystem/import/blend/enabled] is enabled, otherwise [code].blend[/code] files present in the project folder are not imported. + Blend import requires Blender 3.0. + Internally, the EditorSceneFormatImporterBlend uses the Blender glTF "Use Original" mode to reference external textures. + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterFBX.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterFBX.xml new file mode 100644 index 0000000000..6754d963ff --- /dev/null +++ b/modules/gltf/doc_classes/EditorSceneFormatImporterFBX.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorSceneFormatImporterFBX" inherits="EditorSceneFormatImporter" version="4.0" 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> diff --git a/doc/classes/EditorSceneFormatImporterGLTF.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterGLTF.xml index 6478e853eb..5a6a2f52d9 100644 --- a/doc/classes/EditorSceneFormatImporterGLTF.xml +++ b/modules/gltf/doc_classes/EditorSceneFormatImporterGLTF.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="EditorSceneFormatImporterGLTF" inherits="EditorSceneFormatImporter" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="EditorSceneFormatImporterGLTF" inherits="EditorSceneFormatImporter" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 7adabdc605..cb0e3b6754 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -24,6 +24,7 @@ <argument index="1" name="state" type="GLTFState" /> <argument index="2" name="flags" type="int" default="0" /> <argument index="3" name="bake_fps" type="int" default="30" /> + <argument index="4" name="base_path" type="String" default="""" /> <description> </description> </method> diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp new file mode 100644 index 0000000000..2587c095e1 --- /dev/null +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* editor_scene_importer_blend.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_blend.h" + +#if TOOLS_ENABLED + +#include "../gltf_document.h" +#include "../gltf_state.h" + +#include "core/config/project_settings.h" +#include "editor/editor_settings.h" +#include "scene/main/node.h" +#include "scene/resources/animation.h" + +uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { + return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; +} + +void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) const { + r_extensions->push_back("blend"); +} + +Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err) { + // Get global paths for source and sink. + + const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path); + const String sink = ProjectSettings::get_singleton()->get_imported_files_path().plus_file( + vformat("%s-%s.gltf", p_path.get_file().get_basename(), p_path.md5_text())); + const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); + + // Handle configuration options. + + String parameters_arg; + + if (p_options.has(SNAME("blender/nodes/custom_properties")) && p_options[SNAME("blender/nodes/custom_properties")]) { + parameters_arg += "export_extras=True,"; + } else { + parameters_arg += "export_extras=False,"; + } + if (p_options.has(SNAME("blender/meshes/skins")) && p_options[SNAME("blender/meshes/skins")]) { + int32_t skins = p_options["blender/meshes/skins"]; + if (skins == BLEND_BONE_INFLUENCES_NONE) { + parameters_arg += "export_all_influences=False,"; + } else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) { + parameters_arg += "export_all_influences=False,"; + } else if (skins == BLEND_BONE_INFLUENCES_ALL) { + parameters_arg += "export_all_influences=True,"; + } + parameters_arg += "export_skins=True,"; + } else { + parameters_arg += "export_skins=False,"; + } + if (p_options.has(SNAME("blender/nodes/cameras")) && p_options[SNAME("blender/nodes/cameras")]) { + parameters_arg += "export_cameras=True,"; + } else { + parameters_arg += "export_cameras=False,"; + } + if (p_options.has(SNAME("blender/nodes/lights")) && p_options[SNAME("blender/nodes/lights")]) { + parameters_arg += "export_lights=True,"; + } else { + parameters_arg += "export_lights=False,"; + } + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_arg += "export_colors=True,"; + } else { + parameters_arg += "export_colors=False,"; + } + if (p_options.has(SNAME("blender/nodes/visible")) && p_options[SNAME("blender/nodes/visible")]) { + int32_t visible = p_options["blender/nodes/visible"]; + if (visible == BLEND_VISIBLE_VISIBLE_ONLY) { + parameters_arg += "use_visible=True,"; + } else if (visible == BLEND_VISIBLE_RENDERABLE) { + parameters_arg += "use_renderable=True,"; + } else if (visible == BLEND_VISIBLE_ALL) { + parameters_arg += "use_visible=False,use_renderable=False,"; + } + } else { + parameters_arg += "use_visible=False,use_renderable=False,"; + } + if (p_options.has(SNAME("blender/meshes/uvs")) && p_options[SNAME("blender/meshes/uvs")]) { + parameters_arg += "export_texcoords=True,"; + } else { + parameters_arg += "export_texcoords=False,"; + } + if (p_options.has(SNAME("blender/meshes/normals")) && p_options[SNAME("blender/meshes/normals")]) { + parameters_arg += "export_normals=True,"; + } else { + parameters_arg += "export_normals=False,"; + } + if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) { + parameters_arg += "export_tangents=True,"; + } else { + parameters_arg += "export_tangents=False,"; + } + if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) { + parameters_arg += "export_nla_strips=True,"; + } else { + parameters_arg += "export_nla_strips=False,"; + } + if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) { + parameters_arg += "export_frame_range=True,"; + } else { + parameters_arg += "export_frame_range=False,"; + } + if (p_options.has(SNAME("blender/animation/always_sample")) && p_options[SNAME("blender/animation/always_sample")]) { + parameters_arg += "export_force_sampling=True,"; + } else { + parameters_arg += "export_force_sampling=False,"; + } + if (p_options.has(SNAME("blender/meshes/export_bones_deforming_mesh_only")) && p_options[SNAME("blender/meshes/export_bones_deforming_mesh_only")]) { + parameters_arg += "export_def_bones=True,"; + } else { + parameters_arg += "export_def_bones=False,"; + } + if (p_options.has(SNAME("blender/nodes/modifiers")) && p_options[SNAME("blender/nodes/modifiers")]) { + parameters_arg += "export_apply=True"; + } else { + parameters_arg += "export_apply=False"; + } + + String unpack_all; + if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { + unpack_all = "bpy.ops.file.unpack_all(method='USE_LOCAL');"; + } + + // Prepare Blender export script. + + String common_args = vformat("filepath='%s',", sink_global) + + "export_format='GLTF_SEPARATE'," + "export_yup=True," + + parameters_arg; + String script = + String("import bpy, sys;") + + "print('Blender 3.0 or higher is required.', file=sys.stderr) if bpy.app.version < (3, 0, 0) else None;" + + vformat("bpy.ops.wm.open_mainfile(filepath='%s');", source_global) + + unpack_all + + vformat("bpy.ops.export_scene.gltf(export_keep_originals=True,%s);", common_args); + print_verbose(script); + + // Run script with configured Blender binary. + + String blender_path = EDITOR_GET("filesystem/import/blend/blender_path"); + + List<String> args; + args.push_back("--background"); + args.push_back("--python-expr"); + args.push_back(script); + + String standard_out; + int ret; + OS::get_singleton()->execute(blender_path, args, &standard_out, &ret, true); + print_verbose(blender_path); + print_verbose(standard_out); + + if (ret != 0) { + if (r_err) { + *r_err = ERR_SCRIPT_FAILED; + } + ERR_PRINT(vformat("Blend export to glTF failed with error: %d.", ret)); + return nullptr; + } + + // Import the generated glTF. + + // Use GLTFDocument instead of glTF importer to keep image references. + Ref<GLTFDocument> gltf; + gltf.instantiate(); + Ref<GLTFState> state; + state.instantiate(); + String base_dir; + if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { + base_dir = sink.get_base_dir(); + } + Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, p_bake_fps, base_dir); + if (err != OK) { + if (r_err) { + *r_err = FAILED; + } + return nullptr; + } + return gltf->generate_scene(state, p_bake_fps); +} + +Ref<Animation> EditorSceneFormatImporterBlend::import_animation(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps) { + return Ref<Animation>(); +} + +Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_option, + const Map<StringName, Variant> &p_options) { + if (p_option.begins_with("animation/")) { + if (p_option != "animation/import" && !bool(p_options["animation/import"])) { + return false; + } + } + return true; +} + +void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { +#define ADD_OPTION_BOOL(PATH, VALUE) \ + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, SNAME(PATH)), VALUE)); +#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)); + + ADD_OPTION_ENUM("blender/nodes/visible", "Visible Only,Renderable,All", BLEND_VISIBLE_ALL); + ADD_OPTION_BOOL("blender/nodes/punctual_lights", true); + ADD_OPTION_BOOL("blender/nodes/cameras", true); + ADD_OPTION_BOOL("blender/nodes/custom_properties", true); + ADD_OPTION_ENUM("blender/nodes/modifiers", "No Modifiers,All Modifiers", BLEND_MODIFIERS_ALL); + ADD_OPTION_BOOL("blender/meshes/colors", false); + ADD_OPTION_BOOL("blender/meshes/uvs", true); + ADD_OPTION_BOOL("blender/meshes/normals", true); + ADD_OPTION_BOOL("blender/meshes/tangents", true); + ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL); + ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false); + ADD_OPTION_BOOL("blender/materials/unpack_enabled", true); + ADD_OPTION_BOOL("blender/animation/limit_playback", true); + ADD_OPTION_BOOL("blender/animation/always_sample", true); + ADD_OPTION_BOOL("blender/animation/group_tracks", true); + +#undef ADD_OPTION_BOOL +#undef ADD_OPTION_ENUM +} + +#endif // TOOLS_ENABLED diff --git a/modules/fbx/register_types.cpp b/modules/gltf/editor/editor_scene_importer_blend.h index 73e15e38b4..4bdf4c93a2 100644 --- a/modules/fbx/register_types.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* register_types.cpp */ +/* editor_scene_importer_blend.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,31 +28,48 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "register_types.h" +#ifndef EDITOR_SCENE_IMPORTER_BLEND_H +#define EDITOR_SCENE_IMPORTER_BLEND_H -#include "editor/editor_node.h" -#include "modules/fbx/editor_scene_importer_fbx.h" - -#ifdef TOOLS_ENABLED -static void _editor_init() { - Ref<EditorSceneFormatImporterFBX> import_fbx; - import_fbx.instantiate(); - ResourceImporterScene::get_singleton()->add_importer(import_fbx); -} -#endif - -void register_fbx_types() { #ifdef TOOLS_ENABLED - ClassDB::APIType prev_api = ClassDB::get_current_api(); - ClassDB::set_current_api(ClassDB::API_EDITOR); - GDREGISTER_CLASS(EditorSceneFormatImporterFBX); +#include "editor/import/resource_importer_scene.h" + +class Animation; +class Node; + +class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter); + +public: + enum { + BLEND_VISIBLE_VISIBLE_ONLY, + BLEND_VISIBLE_RENDERABLE, + BLEND_VISIBLE_ALL + }; + enum { + BLEND_BONE_INFLUENCES_NONE, + BLEND_BONE_INFLUENCES_COMPATIBLE, + BLEND_BONE_INFLUENCES_ALL + }; + enum { + BLEND_MODIFIERS_NONE, + BLEND_MODIFIERS_ALL + }; - ClassDB::set_current_api(prev_api); + 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 Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err = nullptr) override; + virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps) 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, const String &p_option, + const Map<StringName, Variant> &p_options) override; +}; - EditorNode::add_init_callback(_editor_init); -#endif -} +#endif // TOOLS_ENABLED -void unregister_fbx_types() { -} +#endif // EDITOR_SCENE_IMPORTER_BLEND_H diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp new file mode 100644 index 0000000000..7cfd85c73a --- /dev/null +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* editor_scene_importer_fbx.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_fbx.h" + +#if TOOLS_ENABLED + +#include "../gltf_document.h" +#include "../gltf_state.h" + +#include "core/config/project_settings.h" +#include "editor/editor_settings.h" +#include "scene/main/node.h" +#include "scene/resources/animation.h" + +uint32_t EditorSceneFormatImporterFBX::get_import_flags() const { + return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; +} + +void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) const { + r_extensions->push_back("fbx"); +} + +Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err) { + // Get global paths for source and sink. + + const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path); + const String sink = ProjectSettings::get_singleton()->get_imported_files_path().plus_file( + vformat("%s-%s.glb", p_path.get_file().get_basename(), p_path.md5_text())); + const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); + + // Run fbx2gltf. + + String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path"); + + List<String> args; + args.push_back("--pbr-metallic-roughness"); + args.push_back("--input"); + args.push_back(vformat("\"%s\"", source_global)); + args.push_back("--output"); + args.push_back(vformat("\"%s\"", sink_global)); + args.push_back("--binary"); + + String standard_out; + int ret; + OS::get_singleton()->execute(fbx2gltf_path, args, &standard_out, &ret, true); + print_verbose(fbx2gltf_path); + print_verbose(standard_out); + + if (ret != 0) { + if (r_err) { + *r_err = ERR_SCRIPT_FAILED; + } + ERR_PRINT(vformat("FBX conversion to glTF failed with error: %d.", ret)); + return nullptr; + } + + // Import the generated glTF. + + // Use GLTFDocument instead of glTF importer to keep image references. + Ref<GLTFDocument> gltf; + gltf.instantiate(); + Ref<GLTFState> state; + state.instantiate(); + print_verbose(vformat("glTF path: %s", sink)); + Error err = gltf->append_from_file(sink, state, p_flags, p_bake_fps); + if (err != OK) { + if (r_err) { + *r_err = FAILED; + } + return nullptr; + } + return gltf->generate_scene(state, p_bake_fps); +} + +Ref<Animation> EditorSceneFormatImporterFBX::import_animation(const String &p_path, + uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps) { + return Ref<Animation>(); +} + +Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, + const String &p_option, const Map<StringName, Variant> &p_options) { + return true; +} + +void EditorSceneFormatImporterFBX::get_import_options(const String &p_path, + List<ResourceImporter::ImportOption> *r_options) { +} + +#endif // TOOLS_ENABLED diff --git a/modules/fbx/tools/validation_tools.cpp b/modules/gltf/editor/editor_scene_importer_fbx.h index f775480e69..bb69cfaa30 100644 --- a/modules/fbx/tools/validation_tools.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* validation_tools.cpp */ +/* editor_scene_importer_fbx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,33 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "validation_tools.h" +#ifndef EDITOR_SCENE_IMPORTER_FBX_H +#define EDITOR_SCENE_IMPORTER_FBX_H #ifdef TOOLS_ENABLED -#include "core/string/print_string.h" -#include "core/string/ustring.h" +#include "editor/import/resource_importer_scene.h" -ValidationTracker::Entries *ValidationTracker::entries_singleton = memnew(ValidationTracker::Entries); +class Animation; +class Node; -// for printing our CSV to dump validation problems of files -// later we can make some agnostic tooling for this but this is fine for the time being. -void ValidationTracker::Entries::add_validation_error(String asset_path, String message) { - print_error(message); - // note: implementation is static - validation_entries[asset_path].push_back(message); -} +class EditorSceneFormatImporterFBX : public EditorSceneFormatImporter { + GDCLASS(EditorSceneFormatImporterFBX, EditorSceneFormatImporter); + +public: + 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 Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err = nullptr) override; + virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps) 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, const String &p_option, + const Map<StringName, Variant> &p_options) override; +}; #endif // TOOLS_ENABLED + +#endif // EDITOR_SCENE_IMPORTER_FBX_H diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index f9193c2a42..1d6a82e58a 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -35,8 +35,7 @@ #include "../gltf_document.h" #include "../gltf_state.h" -#include "scene/3d/node_3d.h" -#include "scene/animation/animation_player.h" +#include "scene/main/node.h" #include "scene/resources/animation.h" uint32_t EditorSceneFormatImporterGLTF::get_import_flags() const { @@ -48,26 +47,25 @@ void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) c r_extensions->push_back("glb"); } -Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, - uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps, - List<String> *r_missing_deps, - Error *r_err) { +Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err) { Ref<GLTFDocument> doc; doc.instantiate(); Ref<GLTFState> state; state.instantiate(); Error err = doc->append_from_file(p_path, state, p_flags, p_bake_fps); if (err != OK) { - *r_err = err; + if (r_err) { + *r_err = err; + } return nullptr; } - Node *root = doc->generate_scene(state, p_bake_fps); - return root; + return doc->generate_scene(state, p_bake_fps); } Ref<Animation> EditorSceneFormatImporterGLTF::import_animation(const String &p_path, - uint32_t p_flags, const Map<StringName, Variant> &p_options, - int p_bake_fps) { + uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps) { return Ref<Animation>(); } diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index 206fe63426..1f62ca9537 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -33,14 +33,10 @@ #ifdef TOOLS_ENABLED -#include "../gltf_document_extension.h" -#include "../gltf_state.h" - #include "editor/import/resource_importer_scene.h" -#include "scene/main/node.h" -#include "scene/resources/packed_scene.h" class Animation; +class Node; class EditorSceneFormatImporterGLTF : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterGLTF, EditorSceneFormatImporter); @@ -48,9 +44,11 @@ class EditorSceneFormatImporterGLTF : public EditorSceneFormatImporter { public: 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 Map<StringName, Variant> &p_options, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr) override; - virtual Ref<Animation> import_animation(const String &p_path, - uint32_t p_flags, const Map<StringName, Variant> &p_options, int p_bake_fps) override; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps, + List<String> *r_missing_deps, Error *r_err = nullptr) override; + virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, + const Map<StringName, Variant> &p_options, int p_bake_fps) override; }; #endif // TOOLS_ENABLED diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 56f31fd812..ee756c6d2e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -6758,8 +6758,8 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { } void GLTFDocument::_bind_methods() { - ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "bake_fps"), - &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(30)); + ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "bake_fps", "base_path"), + &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(30), DEFVAL(String())); ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags", "bake_fps"), &GLTFDocument::append_from_buffer, DEFVAL(0), DEFVAL(30)); ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags", "bake_fps"), @@ -7024,20 +7024,22 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear return OK; } -Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, int32_t p_bake_fps) { +Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, int32_t p_bake_fps, String p_base_path) { // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire if (r_state == Ref<GLTFState>()) { r_state.instantiate(); } r_state->filename = p_path.get_file().get_basename(); - r_state->use_named_skin_binds = - p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; Error err; FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); ERR_FAIL_NULL_V(f, ERR_FILE_CANT_OPEN); - - err = _parse(r_state, p_path.get_base_dir(), f, p_bake_fps); + String base_path = p_base_path; + if (base_path.is_empty()) { + base_path = p_path.get_base_dir(); + } + err = _parse(r_state, base_path, f, p_bake_fps); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); return err; } diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index c0649e0129..0b7664a616 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -381,7 +381,7 @@ private: static float get_max_component(const Color &p_color); public: - Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); + Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30, String p_base_path = String()); Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); diff --git a/modules/gltf/gltf_mesh.cpp b/modules/gltf/gltf_mesh.cpp index 1251622642..3add8304b1 100644 --- a/modules/gltf/gltf_mesh.cpp +++ b/modules/gltf/gltf_mesh.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "gltf_mesh.h" + #include "scene/resources/importer_mesh.h" void GLTFMesh::_bind_methods() { diff --git a/modules/gltf/gltf_mesh.h b/modules/gltf/gltf_mesh.h index 4a0533933a..dc26120b48 100644 --- a/modules/gltf/gltf_mesh.h +++ b/modules/gltf/gltf_mesh.h @@ -55,4 +55,5 @@ public: Array get_instance_materials(); void set_instance_materials(Array p_instance_materials); }; + #endif // GLTF_MESH_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index ef30628dbb..79b918dbc0 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -49,41 +49,96 @@ #include "gltf_texture.h" #ifdef TOOLS_ENABLED +#include "core/config/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_scene_exporter_gltf_plugin.h" +#include "editor/editor_scene_importer_blend.h" +#include "editor/editor_scene_importer_fbx.h" #include "editor/editor_scene_importer_gltf.h" +#include "editor/editor_settings.h" static void _editor_init() { Ref<EditorSceneFormatImporterGLTF> import_gltf; import_gltf.instantiate(); ResourceImporterScene::get_singleton()->add_importer(import_gltf); + + // Blend to glTF importer. + + bool blend_enabled = GLOBAL_GET("filesystem/import/blend/enabled"); + // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet. + String blender_path = EDITOR_DEF_RST("filesystem/import/blend/blender_path", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, + "filesystem/import/blend/blender_path", PROPERTY_HINT_GLOBAL_FILE)); + if (blend_enabled) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (blender_path.is_empty()) { + WARN_PRINT("Blend file import is enabled, but no Blender path is configured. Blend files will not be imported."); + } else if (!da->file_exists(blender_path)) { + WARN_PRINT("Blend file import is enabled, but the Blender path doesn't point to a valid Blender executable. Blend files will not be imported."); + } else { + Ref<EditorSceneFormatImporterBlend> importer; + importer.instantiate(); + ResourceImporterScene::get_singleton()->add_importer(importer); + } + } + + // FBX to glTF importer. + + bool fbx_enabled = GLOBAL_GET("filesystem/import/fbx/enabled"); + // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet. + String fbx2gltf_path = EDITOR_DEF_RST("filesystem/import/fbx/fbx2gltf_path", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, + "filesystem/import/fbx/fbx2gltf_path", PROPERTY_HINT_GLOBAL_FILE)); + if (fbx_enabled) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (fbx2gltf_path.is_empty()) { + WARN_PRINT("FBX file import is enabled, but no FBX2glTF path is configured. FBX files will not be imported."); + } else if (!da->file_exists(fbx2gltf_path)) { + WARN_PRINT("FBX file import is enabled, but the FBX2glTF path doesn't point to a valid FBX2glTF executable. FBX files will not be imported."); + } else { + Ref<EditorSceneFormatImporterFBX> importer; + importer.instantiate(); + ResourceImporterScene::get_singleton()->add_importer(importer); + } + } } -#endif +#endif // TOOLS_ENABLED void register_gltf_types() { + // glTF API available at runtime. + GDREGISTER_CLASS(GLTFAccessor); + GDREGISTER_CLASS(GLTFAnimation); + GDREGISTER_CLASS(GLTFBufferView); + GDREGISTER_CLASS(GLTFCamera); + GDREGISTER_CLASS(GLTFDocument); + GDREGISTER_CLASS(GLTFDocumentExtension); + GDREGISTER_CLASS(GLTFDocumentExtensionConvertImporterMesh); + GDREGISTER_CLASS(GLTFLight); + GDREGISTER_CLASS(GLTFMesh); + GDREGISTER_CLASS(GLTFNode); + GDREGISTER_CLASS(GLTFSkeleton); + GDREGISTER_CLASS(GLTFSkin); + GDREGISTER_CLASS(GLTFSpecGloss); + GDREGISTER_CLASS(GLTFState); + GDREGISTER_CLASS(GLTFTexture); + #ifdef TOOLS_ENABLED + // Editor-specific API. ClassDB::APIType prev_api = ClassDB::get_current_api(); ClassDB::set_current_api(ClassDB::API_EDITOR); + GDREGISTER_CLASS(EditorSceneFormatImporterGLTF); - GDREGISTER_CLASS(GLTFMesh); EditorPlugins::add_by_type<SceneExporterGLTFPlugin>(); + + // Project settings defined here so doctool finds them. + GLOBAL_DEF_RST("filesystem/import/blend/enabled", true); + GLOBAL_DEF_RST("filesystem/import/fbx/enabled", true); + GDREGISTER_CLASS(EditorSceneFormatImporterBlend); + GDREGISTER_CLASS(EditorSceneFormatImporterFBX); + ClassDB::set_current_api(prev_api); EditorNode::add_init_callback(_editor_init); -#endif - GDREGISTER_CLASS(GLTFSpecGloss); - GDREGISTER_CLASS(GLTFNode); - GDREGISTER_CLASS(GLTFAnimation); - GDREGISTER_CLASS(GLTFBufferView); - GDREGISTER_CLASS(GLTFAccessor); - GDREGISTER_CLASS(GLTFTexture); - GDREGISTER_CLASS(GLTFSkeleton); - GDREGISTER_CLASS(GLTFSkin); - GDREGISTER_CLASS(GLTFCamera); - GDREGISTER_CLASS(GLTFLight); - GDREGISTER_CLASS(GLTFState); - GDREGISTER_CLASS(GLTFDocumentExtensionConvertImporterMesh); - GDREGISTER_CLASS(GLTFDocumentExtension); - GDREGISTER_CLASS(GLTFDocument); +#endif // TOOLS_ENABLED } void unregister_gltf_types() { diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index bf668bac27..f7ff6cc89d 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -52,32 +52,6 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr; -void OpenXRAPI::setup_global_defs() { - // As OpenXRAPI is not constructed if OpenXR is not enabled, we register our project and editor settings here - - // Project settings - GLOBAL_DEF_BASIC("xr/openxr/enabled", false); - GLOBAL_DEF_BASIC("xr/openxr/default_action_map", "res://default_action_map.tres"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/default_action_map", PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres")); - - GLOBAL_DEF_BASIC("xr/openxr/form_factor", "0"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/form_factor", PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head mounted,Handheld")); - - GLOBAL_DEF_BASIC("xr/openxr/view_configuration", "1"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/view_configuration", PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo")); // "Mono,Stereo,Quad,Observer" - - GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); - -#ifdef TOOLS_ENABLED - // Disabled for now, using XR inside of the editor we'll be working on during the coming months. - - // editor settings (it seems we're too early in the process when setting up rendering, to access editor settings...) - // EDITOR_DEF_RST("xr/openxr/in_editor", false); - // GLOBAL_DEF("xr/openxr/in_editor", false); -#endif -} - bool OpenXRAPI::openxr_is_enabled() { // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5d1cea1217..1a1508e993 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -222,7 +222,6 @@ protected: void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity); public: - static void setup_global_defs(); static bool openxr_is_enabled(); static OpenXRAPI *get_singleton(); diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index bb550980cf..47ee1316e7 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -44,7 +44,6 @@ Ref<OpenXRInterface> openxr_interface; void preregister_openxr_types() { // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. - OpenXRAPI::setup_global_defs(); if (OpenXRAPI::openxr_is_enabled()) { openxr_api = memnew(OpenXRAPI); ERR_FAIL_NULL(openxr_api); diff --git a/modules/svg/register_types.cpp b/modules/svg/register_types.cpp index a4341c6f1e..b59c815056 100644 --- a/modules/svg/register_types.cpp +++ b/modules/svg/register_types.cpp @@ -38,7 +38,7 @@ static ImageLoaderSVG *image_loader_svg = nullptr; void register_svg_types() { tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; - if (tvg::Initializer::init(tvgEngine, 0) != tvg::Result::Success) { + if (tvg::Initializer::init(tvgEngine, 1) != tvg::Result::Success) { return; } image_loader_svg = memnew(ImageLoaderSVG); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 61093d54de..08da1b1832 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -29,6 +29,8 @@ /*************************************************************************/ package org.godotengine.godot; +import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.gl.GodotRenderer; import org.godotengine.godot.input.GodotGestureHandler; import org.godotengine.godot.input.GodotInputHandler; import org.godotengine.godot.utils.GLUtils; @@ -43,7 +45,6 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; import android.os.Build; import android.view.GestureDetector; import android.view.KeyEvent; diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 29e4b4b29e..253a51b83c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -30,6 +30,8 @@ package org.godotengine.godot; +import org.godotengine.godot.gl.GodotRenderer; + import android.app.Activity; import android.hardware.SensorEvent; import android.view.Surface; @@ -68,7 +70,7 @@ public class GodotLib { * @param p_surface * @param p_width * @param p_height - * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) + * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) */ public static native void resize(Surface p_surface, int p_width, int p_height); @@ -85,9 +87,9 @@ public class GodotLib { /** * Invoked on the GL thread to draw the current frame. - * @see android.opengl.GLSurfaceView.Renderer#onDrawFrame(GL10) + * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onDrawFrame(GL10) */ - public static native void step(); + public static native boolean step(); /** * Forward touch events from the main thread to the GL thread. diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java new file mode 100644 index 0000000000..af16cfce74 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java @@ -0,0 +1,566 @@ +// clang-format off + +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.godotengine.godot.gl; + +import android.opengl.GLDebugHelper; +import android.opengl.GLException; + +import java.io.IOException; +import java.io.Writer; + +import javax.microedition.khronos.egl.EGL; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +class EGLLogWrapper implements EGL11 { + private EGL10 mEgl10; + Writer mLog; + boolean mLogArgumentNames; + boolean mCheckError; + private int mArgCount; + + + public EGLLogWrapper(EGL egl, int configFlags, Writer log) { + mEgl10 = (EGL10) egl; + mLog = log; + mLogArgumentNames = + (GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES & configFlags) != 0; + mCheckError = + (GLDebugHelper.CONFIG_CHECK_GL_ERROR & configFlags) != 0; + } + + public boolean eglChooseConfig(EGLDisplay display, int[] attrib_list, + EGLConfig[] configs, int config_size, int[] num_config) { + begin("eglChooseConfig"); + arg("display", display); + arg("attrib_list", attrib_list); + arg("config_size", config_size); + end(); + + boolean result = mEgl10.eglChooseConfig(display, attrib_list, configs, + config_size, num_config); + arg("configs", configs); + arg("num_config", num_config); + returns(result); + checkError(); + return result; + } + + public boolean eglCopyBuffers(EGLDisplay display, EGLSurface surface, + Object native_pixmap) { + begin("eglCopyBuffers"); + arg("display", display); + arg("surface", surface); + arg("native_pixmap", native_pixmap); + end(); + + boolean result = mEgl10.eglCopyBuffers(display, surface, native_pixmap); + returns(result); + checkError(); + return result; + } + + public EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, + EGLContext share_context, int[] attrib_list) { + begin("eglCreateContext"); + arg("display", display); + arg("config", config); + arg("share_context", share_context); + arg("attrib_list", attrib_list); + end(); + + EGLContext result = mEgl10.eglCreateContext(display, config, + share_context, attrib_list); + returns(result); + checkError(); + return result; + } + + public EGLSurface eglCreatePbufferSurface(EGLDisplay display, + EGLConfig config, int[] attrib_list) { + begin("eglCreatePbufferSurface"); + arg("display", display); + arg("config", config); + arg("attrib_list", attrib_list); + end(); + + EGLSurface result = mEgl10.eglCreatePbufferSurface(display, config, + attrib_list); + returns(result); + checkError(); + return result; + } + + public EGLSurface eglCreatePixmapSurface(EGLDisplay display, + EGLConfig config, Object native_pixmap, int[] attrib_list) { + begin("eglCreatePixmapSurface"); + arg("display", display); + arg("config", config); + arg("native_pixmap", native_pixmap); + arg("attrib_list", attrib_list); + end(); + + EGLSurface result = mEgl10.eglCreatePixmapSurface(display, config, + native_pixmap, attrib_list); + returns(result); + checkError(); + return result; + } + + public EGLSurface eglCreateWindowSurface(EGLDisplay display, + EGLConfig config, Object native_window, int[] attrib_list) { + begin("eglCreateWindowSurface"); + arg("display", display); + arg("config", config); + arg("native_window", native_window); + arg("attrib_list", attrib_list); + end(); + + EGLSurface result = mEgl10.eglCreateWindowSurface(display, config, + native_window, attrib_list); + returns(result); + checkError(); + return result; + } + + public boolean eglDestroyContext(EGLDisplay display, EGLContext context) { + begin("eglDestroyContext"); + arg("display", display); + arg("context", context); + end(); + + boolean result = mEgl10.eglDestroyContext(display, context); + returns(result); + checkError(); + return result; + } + + public boolean eglDestroySurface(EGLDisplay display, EGLSurface surface) { + begin("eglDestroySurface"); + arg("display", display); + arg("surface", surface); + end(); + + boolean result = mEgl10.eglDestroySurface(display, surface); + returns(result); + checkError(); + return result; + } + + public boolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, + int attribute, int[] value) { + begin("eglGetConfigAttrib"); + arg("display", display); + arg("config", config); + arg("attribute", attribute); + end(); + boolean result = mEgl10.eglGetConfigAttrib(display, config, attribute, + value); + arg("value", value); + returns(result); + checkError(); + return false; + } + + public boolean eglGetConfigs(EGLDisplay display, EGLConfig[] configs, + int config_size, int[] num_config) { + begin("eglGetConfigs"); + arg("display", display); + arg("config_size", config_size); + end(); + + boolean result = mEgl10.eglGetConfigs(display, configs, config_size, + num_config); + arg("configs", configs); + arg("num_config", num_config); + returns(result); + checkError(); + return result; + } + + public EGLContext eglGetCurrentContext() { + begin("eglGetCurrentContext"); + end(); + + EGLContext result = mEgl10.eglGetCurrentContext(); + returns(result); + + checkError(); + return result; + } + + public EGLDisplay eglGetCurrentDisplay() { + begin("eglGetCurrentDisplay"); + end(); + + EGLDisplay result = mEgl10.eglGetCurrentDisplay(); + returns(result); + + checkError(); + return result; + } + + public EGLSurface eglGetCurrentSurface(int readdraw) { + begin("eglGetCurrentSurface"); + arg("readdraw", readdraw); + end(); + + EGLSurface result = mEgl10.eglGetCurrentSurface(readdraw); + returns(result); + + checkError(); + return result; + } + + public EGLDisplay eglGetDisplay(Object native_display) { + begin("eglGetDisplay"); + arg("native_display", native_display); + end(); + + EGLDisplay result = mEgl10.eglGetDisplay(native_display); + returns(result); + + checkError(); + return result; + } + + public int eglGetError() { + begin("eglGetError"); + end(); + + int result = mEgl10.eglGetError(); + returns(getErrorString(result)); + + return result; + } + + public boolean eglInitialize(EGLDisplay display, int[] major_minor) { + begin("eglInitialize"); + arg("display", display); + end(); + boolean result = mEgl10.eglInitialize(display, major_minor); + returns(result); + arg("major_minor", major_minor); + checkError(); + return result; + } + + public boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, + EGLSurface read, EGLContext context) { + begin("eglMakeCurrent"); + arg("display", display); + arg("draw", draw); + arg("read", read); + arg("context", context); + end(); + boolean result = mEgl10.eglMakeCurrent(display, draw, read, context); + returns(result); + checkError(); + return result; + } + + public boolean eglQueryContext(EGLDisplay display, EGLContext context, + int attribute, int[] value) { + begin("eglQueryContext"); + arg("display", display); + arg("context", context); + arg("attribute", attribute); + end(); + boolean result = mEgl10.eglQueryContext(display, context, attribute, + value); + returns(value[0]); + returns(result); + checkError(); + return result; + } + + public String eglQueryString(EGLDisplay display, int name) { + begin("eglQueryString"); + arg("display", display); + arg("name", name); + end(); + String result = mEgl10.eglQueryString(display, name); + returns(result); + checkError(); + return result; + } + + public boolean eglQuerySurface(EGLDisplay display, EGLSurface surface, + int attribute, int[] value) { + begin("eglQuerySurface"); + arg("display", display); + arg("surface", surface); + arg("attribute", attribute); + end(); + boolean result = mEgl10.eglQuerySurface(display, surface, attribute, + value); + returns(value[0]); + returns(result); + checkError(); + return result; + } + + public boolean eglSwapBuffers(EGLDisplay display, EGLSurface surface) { + begin("eglSwapBuffers"); + arg("display", display); + arg("surface", surface); + end(); + boolean result = mEgl10.eglSwapBuffers(display, surface); + returns(result); + checkError(); + return result; + } + + public boolean eglTerminate(EGLDisplay display) { + begin("eglTerminate"); + arg("display", display); + end(); + boolean result = mEgl10.eglTerminate(display); + returns(result); + checkError(); + return result; + } + + public boolean eglWaitGL() { + begin("eglWaitGL"); + end(); + boolean result = mEgl10.eglWaitGL(); + returns(result); + checkError(); + return result; + } + + public boolean eglWaitNative(int engine, Object bindTarget) { + begin("eglWaitNative"); + arg("engine", engine); + arg("bindTarget", bindTarget); + end(); + boolean result = mEgl10.eglWaitNative(engine, bindTarget); + returns(result); + checkError(); + return result; + } + + private void checkError() { + int eglError; + if ((eglError = mEgl10.eglGetError()) != EGL_SUCCESS) { + String errorMessage = "eglError: " + getErrorString(eglError); + logLine(errorMessage); + if (mCheckError) { + throw new GLException(eglError, errorMessage); + } + } + } + + private void logLine(String message) { + log(message + '\n'); + } + + private void log(String message) { + try { + mLog.write(message); + } catch (IOException e) { + // Ignore exception, keep on trying + } + } + + private void begin(String name) { + log(name + '('); + mArgCount = 0; + } + + private void arg(String name, String value) { + if (mArgCount++ > 0) { + log(", "); + } + if (mLogArgumentNames) { + log(name + "="); + } + log(value); + } + + private void end() { + log(");\n"); + flush(); + } + + private void flush() { + try { + mLog.flush(); + } catch (IOException e) { + mLog = null; + } + } + + private void arg(String name, int value) { + arg(name, Integer.toString(value)); + } + + private void arg(String name, Object object) { + arg(name, toString(object)); + } + + private void arg(String name, EGLDisplay object) { + if (object == EGL10.EGL_DEFAULT_DISPLAY) { + arg(name, "EGL10.EGL_DEFAULT_DISPLAY"); + } else if (object == EGL_NO_DISPLAY) { + arg(name, "EGL10.EGL_NO_DISPLAY"); + } else { + arg(name, toString(object)); + } + } + + private void arg(String name, EGLContext object) { + if (object == EGL10.EGL_NO_CONTEXT) { + arg(name, "EGL10.EGL_NO_CONTEXT"); + } else { + arg(name, toString(object)); + } + } + + private void arg(String name, EGLSurface object) { + if (object == EGL10.EGL_NO_SURFACE) { + arg(name, "EGL10.EGL_NO_SURFACE"); + } else { + arg(name, toString(object)); + } + } + + private void returns(String result) { + log(" returns " + result + ";\n"); + flush(); + } + + private void returns(int result) { + returns(Integer.toString(result)); + } + + private void returns(boolean result) { + returns(Boolean.toString(result)); + } + + private void returns(Object result) { + returns(toString(result)); + } + + private String toString(Object obj) { + if (obj == null) { + return "null"; + } else { + return obj.toString(); + } + } + + private void arg(String name, int[] arr) { + if (arr == null) { + arg(name, "null"); + } else { + arg(name, toString(arr.length, arr, 0)); + } + } + + private void arg(String name, Object[] arr) { + if (arr == null) { + arg(name, "null"); + } else { + arg(name, toString(arr.length, arr, 0)); + } + } + + private String toString(int n, int[] arr, int offset) { + StringBuilder buf = new StringBuilder(); + buf.append("{\n"); + int arrLen = arr.length; + for (int i = 0; i < n; i++) { + int index = offset + i; + buf.append(" [" + index + "] = "); + if (index < 0 || index >= arrLen) { + buf.append("out of bounds"); + } else { + buf.append(arr[index]); + } + buf.append('\n'); + } + buf.append("}"); + return buf.toString(); + } + + private String toString(int n, Object[] arr, int offset) { + StringBuilder buf = new StringBuilder(); + buf.append("{\n"); + int arrLen = arr.length; + for (int i = 0; i < n; i++) { + int index = offset + i; + buf.append(" [" + index + "] = "); + if (index < 0 || index >= arrLen) { + buf.append("out of bounds"); + } else { + buf.append(arr[index]); + } + buf.append('\n'); + } + buf.append("}"); + return buf.toString(); + } + + private static String getHex(int value) { + return "0x" + Integer.toHexString(value); + } + + public static String getErrorString(int error) { + switch (error) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL11.EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return getHex(error); + } + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java new file mode 100644 index 0000000000..8449c08b88 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java @@ -0,0 +1,1939 @@ +// clang-format off + +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.godotengine.godot.gl; + +import android.content.Context; +import android.opengl.EGL14; +import android.opengl.EGLExt; +import android.opengl.GLDebugHelper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import java.io.Writer; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying OpenGL rendering. + * <p> + * A GLSurfaceView provides the following features: + * <p> + * <ul> + * <li>Manages a surface, which is a special piece of memory that can be + * composited into the Android view system. + * <li>Manages an EGL display, which enables OpenGL to render into a surface. + * <li>Accepts a user-provided Renderer object that does the actual rendering. + * <li>Renders on a dedicated thread to decouple rendering performance from the + * UI thread. + * <li>Supports both on-demand and continuous rendering. + * <li>Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls. + * </ul> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about how to use OpenGL, read the + * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p> + * </div> + * + * <h3>Using GLSurfaceView</h3> + * <p> + * Typically you use GLSurfaceView by subclassing it and overriding one or more of the + * View system input event methods. If your application does not need to override event + * methods then GLSurfaceView can be used as-is. For the most part + * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. + * For example, unlike a regular View, drawing is delegated to a separate Renderer object which + * is registered with the GLSurfaceView + * using the {@link #setRenderer(Renderer)} call. + * <p> + * <h3>Initializing GLSurfaceView</h3> + * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}. + * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or + * more of these methods before calling setRenderer: + * <ul> + * <li>{@link #setDebugFlags(int)} + * <li>{@link #setEGLConfigChooser(boolean)} + * <li>{@link #setEGLConfigChooser(EGLConfigChooser)} + * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)} + * <li>{@link #setGLWrapper(GLWrapper)} + * </ul> + * <p> + * <h4>Specifying the android.view.Surface</h4> + * By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent + * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT). + * The exact format of a TRANSLUCENT surface is device dependent, but it will be + * a 32-bit-per-pixel surface with 8 bits per component. + * <p> + * <h4>Choosing an EGL Configuration</h4> + * A given Android device may support multiple EGLConfig rendering configurations. + * The available configurations may differ in how many channels of data are present, as + * well as how many bits are allocated to each channel. Therefore, the first thing + * GLSurfaceView has to do when starting to render is choose what EGLConfig to use. + * <p> + * By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format, + * with at least a 16-bit depth buffer and no stencil. + * <p> + * If you would prefer a different EGLConfig + * you can override the default behavior by calling one of the + * setEGLConfigChooser methods. + * <p> + * <h4>Debug Behavior</h4> + * You can optionally modify the behavior of GLSurfaceView by calling + * one or more of the debugging methods {@link #setDebugFlags(int)}, + * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but + * typically they are called before setRenderer so that they take effect immediately. + * <p> + * <h4>Setting a Renderer</h4> + * Finally, you must call {@link #setRenderer} to register a {@link Renderer}. + * The renderer is + * responsible for doing the actual OpenGL rendering. + * <p> + * <h3>Rendering Mode</h3> + * Once the renderer is set, you can control whether the renderer draws + * continuously or on-demand by calling + * {@link #setRenderMode}. The default is continuous rendering. + * <p> + * <h3>Activity Life-cycle</h3> + * A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients + * are required to call {@link #onPause()} when the activity stops and + * {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to + * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate + * the OpenGL display. + * <p> + * <h3>Handling events</h3> + * <p> + * To handle an event you will typically subclass GLSurfaceView and override the + * appropriate method, just as you would with any other View. However, when handling + * the event, you may need to communicate with the Renderer object + * that's running in the rendering thread. You can do this using any + * standard Java cross-thread communication mechanism. In addition, + * one relatively easy way to communicate with your renderer is + * to call + * {@link #queueEvent(Runnable)}. For example: + * <pre class="prettyprint"> + * class MyGLSurfaceView extends GLSurfaceView { + * + * private MyRenderer mMyRenderer; + * + * public void start() { + * mMyRenderer = ...; + * setRenderer(mMyRenderer); + * } + * + * public boolean onKeyDown(int keyCode, KeyEvent event) { + * if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + * queueEvent(new Runnable() { + * // This method will be called on the rendering + * // thread: + * public void run() { + * mMyRenderer.handleDpadCenter(); + * }}); + * return true; + * } + * return super.onKeyDown(keyCode, event); + * } + * } + * </pre> + * + */ +public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { + private final static String TAG = "GLSurfaceView"; + private final static boolean LOG_ATTACH_DETACH = false; + private final static boolean LOG_THREADS = false; + private final static boolean LOG_PAUSE_RESUME = false; + private final static boolean LOG_SURFACE = false; + private final static boolean LOG_RENDERER = false; + private final static boolean LOG_RENDERER_DRAW_FRAME = false; + private final static boolean LOG_EGL = false; + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + * @see #requestRender() + */ + public final static int RENDERMODE_WHEN_DIRTY = 0; + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + */ + public final static int RENDERMODE_CONTINUOUSLY = 1; + + /** + * Check glError() after every GL call and throw an exception if glError indicates + * that an error has occurred. This can be used to help track down which OpenGL ES call + * is causing an error. + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_CHECK_GL_ERROR = 1; + + /** + * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_LOG_GL_CALLS = 2; + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView(Context context) { + super(context); + init(); + } + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mGLThread != null) { + // GLThread may still be running if this view was never + // attached to a window. + mGLThread.requestExitAndWait(); + } + } finally { + super.finalize(); + } + } + + private void init() { + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment + // this statement if back-porting to 2.2 or older: + // holder.setFormat(PixelFormat.RGB_565); + // + // setType is not needed for SDK 2.0 or newer. Uncomment this + // statement if back-porting this code to older SDKs. + // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); + } + + /** + * Set the glWrapper. If the glWrapper is not null, its + * {@link GLWrapper#wrap(GL)} method is called + * whenever a surface is created. A GLWrapper can be used to wrap + * the GL object that's passed to the renderer. Wrapping a GL + * object enables examining and modifying the behavior of the + * GL calls made by the renderer. + * <p> + * Wrapping is typically used for debugging purposes. + * <p> + * The default value is null. + * @param glWrapper the new GLWrapper + */ + public void setGLWrapper(GLWrapper glWrapper) { + mGLWrapper = glWrapper; + } + + /** + * Set the debug flags to a new value. The value is + * constructed by OR-together zero or more + * of the DEBUG_CHECK_* constants. The debug flags take effect + * whenever a surface is created. The default value is zero. + * @param debugFlags the new debug flags + * @see #DEBUG_CHECK_GL_ERROR + * @see #DEBUG_LOG_GL_CALLS + */ + public void setDebugFlags(int debugFlags) { + mDebugFlags = debugFlags; + } + + /** + * Get the current value of the debug flags. + * @return the current value of the debug flags. + */ + public int getDebugFlags() { + return mDebugFlags; + } + + /** + * Control whether the EGL context is preserved when the GLSurfaceView is paused and + * resumed. + * <p> + * If set to true, then the EGL context may be preserved when the GLSurfaceView is paused. + * <p> + * Prior to API level 11, whether the EGL context is actually preserved or not + * depends upon whether the Android device can support an arbitrary number of + * EGL contexts or not. Devices that can only support a limited number of EGL + * contexts must release the EGL context in order to allow multiple applications + * to share the GPU. + * <p> + * If set to false, the EGL context will be released when the GLSurfaceView is paused, + * and recreated when the GLSurfaceView is resumed. + * <p> + * + * The default is false. + * + * @param preserveOnPause preserve the EGL context when paused + */ + public void setPreserveEGLContextOnPause(boolean preserveOnPause) { + mPreserveEGLContextOnPause = preserveOnPause; + } + + /** + * @return true if the EGL context will be preserved when paused + */ + public boolean getPreserveEGLContextOnPause() { + return mPreserveEGLContextOnPause; + } + + /** + * Set the renderer associated with this view. Also starts the thread that + * will call the renderer, which in turn causes the rendering to start. + * <p>This method should be called once and only once in the life-cycle of + * a GLSurfaceView. + * <p>The following GLSurfaceView methods can only be called <em>before</em> + * setRenderer is called: + * <ul> + * <li>{@link #setEGLConfigChooser(boolean)} + * <li>{@link #setEGLConfigChooser(EGLConfigChooser)} + * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)} + * </ul> + * <p> + * The following GLSurfaceView methods can only be called <em>after</em> + * setRenderer is called: + * <ul> + * <li>{@link #getRenderMode()} + * <li>{@link #onPause()} + * <li>{@link #onResume()} + * <li>{@link #queueEvent(Runnable)} + * <li>{@link #requestRender()} + * <li>{@link #setRenderMode(int)} + * </ul> + * + * @param renderer the renderer to use to perform OpenGL drawing. + */ + public void setRenderer(Renderer renderer) { + checkRenderThreadState(); + if (mEGLConfigChooser == null) { + mEGLConfigChooser = new SimpleEGLConfigChooser(true); + } + if (mEGLContextFactory == null) { + mEGLContextFactory = new DefaultContextFactory(); + } + if (mEGLWindowSurfaceFactory == null) { + mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); + } + mRenderer = renderer; + mGLThread = new GLThread(mThisWeakRef); + mGLThread.start(); + } + + /** + * Install a custom EGLContextFactory. + * <p>If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p> + * If this method is not called, then by default + * a context will be created with no shared context and + * with a null attribute list. + */ + public void setEGLContextFactory(EGLContextFactory factory) { + checkRenderThreadState(); + mEGLContextFactory = factory; + } + + /** + * Install a custom EGLWindowSurfaceFactory. + * <p>If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p> + * If this method is not called, then by default + * a window surface will be created with a null attribute list. + */ + public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { + checkRenderThreadState(); + mEGLWindowSurfaceFactory = factory; + } + + /** + * Install a custom EGLConfigChooser. + * <p>If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p> + * If no setEGLConfigChooser method is called, then by default the + * view will choose an EGLConfig that is compatible with the current + * android.view.Surface, with a depth buffer depth of + * at least 16 bits. + * @param configChooser + */ + public void setEGLConfigChooser(EGLConfigChooser configChooser) { + checkRenderThreadState(); + mEGLConfigChooser = configChooser; + } + + /** + * Install a config chooser which will choose a config + * as close to 16-bit RGB as possible, with or without an optional depth + * buffer as close to 16-bits as possible. + * <p>If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p> + * If no setEGLConfigChooser method is called, then by default the + * view will choose an RGB_888 surface with a depth buffer depth of + * at least 16 bits. + * + * @param needDepth + */ + public void setEGLConfigChooser(boolean needDepth) { + setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); + } + + /** + * Install a config chooser which will choose a config + * with at least the specified depthSize and stencilSize, + * and exactly the specified redSize, greenSize, blueSize and alphaSize. + * <p>If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p> + * If no setEGLConfigChooser method is called, then by default the + * view will choose an RGB_888 surface with a depth buffer depth of + * at least 16 bits. + * + */ + public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, + blueSize, alphaSize, depthSize, stencilSize)); + } + + /** + * Inform the default EGLContextFactory and default EGLConfigChooser + * which EGLContext client version to pick. + * <p>Use this method to create an OpenGL ES 2.0-compatible context. + * Example: + * <pre class="prettyprint"> + * public MyView(Context context) { + * super(context); + * setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context. + * setRenderer(new MyRenderer()); + * } + * </pre> + * <p>Note: Activities which require OpenGL ES 2.0 should indicate this by + * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's + * AndroidManifest.xml file. + * <p>If this method is called, it must be called before {@link #setRenderer(Renderer)} + * is called. + * <p>This method only affects the behavior of the default EGLContexFactory and the + * default EGLConfigChooser. If + * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied + * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context. + * If + * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied + * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. + * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 + */ + public void setEGLContextClientVersion(int version) { + checkRenderThreadState(); + mEGLContextClientVersion = version; + } + + /** + * Set the rendering mode. When renderMode is + * RENDERMODE_CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + * <p> + * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + * <p> + * This method can only be called after {@link #setRenderer(Renderer)} + * + * @param renderMode one of the RENDERMODE_X constants + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public void setRenderMode(int renderMode) { + mGLThread.setRenderMode(renderMode); + } + + /** + * Get the current rendering mode. May be called + * from any thread. Must not be called before a renderer has been set. + * @return the current rendering mode. + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public int getRenderMode() { + return mGLThread.getRenderMode(); + } + + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + public void requestRender() { + mGLThread.requestRender(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceCreated(SurfaceHolder holder) { + mGLThread.surfaceCreated(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceDestroyed(SurfaceHolder holder) { + // Surface will be destroyed when we return + mGLThread.surfaceDestroyed(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + mGLThread.onWindowResize(w, h); + } + + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + @Override + public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { + if (mGLThread != null) { + mGLThread.requestRenderAndNotify(finishDrawing); + } + } + + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + @Deprecated + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + // Since we are part of the framework we know only surfaceRedrawNeededAsync + // will be called. + } + + + /** + * Pause the rendering thread, optionally tearing down the EGL context + * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}. + * + * This method should be called when it is no longer desirable for the + * GLSurfaceView to continue rendering, such as in response to + * {@link android.app.Activity#onStop Activity.onStop}. + * + * Must not be called before a renderer has been set. + */ + public void onPause() { + mGLThread.onPause(); + } + + /** + * Resumes the rendering thread, re-creating the OpenGL context if necessary. It + * is the counterpart to {@link #onPause()}. + * + * This method should typically be called in + * {@link android.app.Activity#onStart Activity.onStart}. + * + * Must not be called before a renderer has been set. + */ + public void onResume() { + mGLThread.onResume(); + } + + /** + * Queue a runnable to be run on the GL rendering thread. This can be used + * to communicate with the Renderer on the rendering thread. + * Must not be called before a renderer has been set. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + mGLThread.queueEvent(r); + } + + /** + * This method is used as part of the View class and is not normally + * called or subclassed by clients of GLSurfaceView. + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); + } + if (mDetached && (mRenderer != null)) { + int renderMode = RENDERMODE_CONTINUOUSLY; + if (mGLThread != null) { + renderMode = mGLThread.getRenderMode(); + } + mGLThread = new GLThread(mThisWeakRef); + if (renderMode != RENDERMODE_CONTINUOUSLY) { + mGLThread.setRenderMode(renderMode); + } + mGLThread.start(); + } + mDetached = false; + } + + @Override + protected void onDetachedFromWindow() { + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onDetachedFromWindow"); + } + if (mGLThread != null) { + mGLThread.requestExitAndWait(); + } + mDetached = true; + super.onDetachedFromWindow(); + } + + // ---------------------------------------------------------------------- + + /** + * An interface used to wrap a GL interface. + * <p>Typically + * used for implementing debugging and tracing on top of the default + * GL interface. You would typically use this by creating your own class + * that implemented all the GL methods by delegating to another GL instance. + * Then you could add your own behavior before or after calling the + * delegate. All the GLWrapper would do was instantiate and return the + * wrapper GL instance: + * <pre class="prettyprint"> + * class MyGLWrapper implements GLWrapper { + * GL wrap(GL gl) { + * return new MyGLImplementation(gl); + * } + * static class MyGLImplementation implements GL,GL10,GL11,... { + * ... + * } + * } + * </pre> + * @see #setGLWrapper(GLWrapper) + */ + public interface GLWrapper { + /** + * Wraps a gl interface in another gl interface. + * @param gl a GL interface that is to be wrapped. + * @return either the input argument or another GL object that wraps the input argument. + */ + GL wrap(GL gl); + } + + /** + * A generic renderer interface. + * <p> + * The renderer is responsible for making OpenGL calls to render a frame. + * <p> + * GLSurfaceView clients typically create their own classes that implement + * this interface, and then call {@link GLSurfaceView#setRenderer} to + * register the renderer with the GLSurfaceView. + * <p> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about how to use OpenGL, read the + * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p> + * </div> + * + * <h3>Threading</h3> + * The renderer will be called on a separate thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. + * <p> + * <h3>EGL Context Lost</h3> + * There are situations where the EGL rendering context will be lost. This + * typically happens when device wakes up after going to sleep. When + * the EGL context is lost, all OpenGL resources (such as textures) that are + * associated with that context will be automatically deleted. In order to + * keep rendering correctly, a renderer must recreate any lost resources + * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method + * is a convenient place to do this. + * + * + * @see #setRenderer(Renderer) + */ + public interface Renderer { + /** + * Called when the surface is created or recreated. + * <p> + * Called when the rendering thread + * starts and whenever the EGL context is lost. The EGL context will typically + * be lost when the Android device awakes after going to sleep. + * <p> + * Since this method is called at the beginning of rendering, as well as + * every time the EGL context is lost, this method is a convenient place to put + * code to create resources that need to be created when the rendering + * starts, and that need to be recreated when the EGL context is lost. + * Textures are an example of a resource that you might want to create + * here. + * <p> + * Note that when the EGL context is lost, all OpenGL resources associated + * with that context will be automatically deleted. You do not need to call + * the corresponding "glDelete" methods such as glDeleteTextures to + * manually delete these lost resources. + * <p> + * @param gl the GL interface. Use <code>instanceof</code> to + * test if the interface supports GL11 or higher interfaces. + * @param config the EGLConfig of the created surface. Can be used + * to create matching pbuffers. + */ + void onSurfaceCreated(GL10 gl, EGLConfig config); + + /** + * Called when the surface changed size. + * <p> + * Called after the surface is created and whenever + * the OpenGL ES surface size changes. + * <p> + * Typically you will set your viewport here. If your camera + * is fixed then you could also set your projection matrix here: + * <pre class="prettyprint"> + * void onSurfaceChanged(GL10 gl, int width, int height) { + * gl.glViewport(0, 0, width, height); + * // for a fixed camera, set the projection too + * float ratio = (float) width / height; + * gl.glMatrixMode(GL10.GL_PROJECTION); + * gl.glLoadIdentity(); + * gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); + * } + * </pre> + * @param gl the GL interface. Use <code>instanceof</code> to + * test if the interface supports GL11 or higher interfaces. + * @param width + * @param height + */ + void onSurfaceChanged(GL10 gl, int width, int height); + + // -- GODOT start -- + /** + * Called to draw the current frame. + * <p> + * This method is responsible for drawing the current frame. + * <p> + * The implementation of this method typically looks like this: + * <pre class="prettyprint"> + * boolean onDrawFrame(GL10 gl) { + * gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + * //... other gl calls to render the scene ... + * return true; + * } + * </pre> + * @param gl the GL interface. Use <code>instanceof</code> to + * test if the interface supports GL11 or higher interfaces. + * + * @return true if the buffers should be swapped, false otherwise. + */ + boolean onDrawFrame(GL10 gl); + // -- GODOT end -- + } + + /** + * An interface for customizing the eglCreateContext and eglDestroyContext calls. + * <p> + * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)} + */ + public interface EGLContextFactory { + EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); + void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); + } + + private class DefaultContextFactory implements EGLContextFactory { + private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, + EGL10.EGL_NONE }; + + return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, + mEGLContextClientVersion != 0 ? attrib_list : null); + } + + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + if (!egl.eglDestroyContext(display, context)) { + Log.e("DefaultContextFactory", "display:" + display + " context: " + context); + if (LOG_THREADS) { + Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); + } + EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); + } + } + } + + /** + * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. + * <p> + * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} + */ + public interface EGLWindowSurfaceFactory { + /** + * @return null if the surface cannot be constructed. + */ + EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow); + void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); + } + + private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { + + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, + EGLConfig config, Object nativeWindow) { + EGLSurface result = null; + try { + result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); + } catch (IllegalArgumentException e) { + // This exception indicates that the surface flinger surface + // is not valid. This can happen if the surface flinger surface has + // been torn down, but the application has not yet been + // notified via SurfaceHolder.Callback.surfaceDestroyed. + // In theory the application should be notified first, + // but in practice sometimes it is not. See b/4588890 + Log.e(TAG, "eglCreateWindowSurface", e); + } + return result; + } + + public void destroySurface(EGL10 egl, EGLDisplay display, + EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } + } + + /** + * An interface for choosing an EGLConfig configuration from a list of + * potential configurations. + * <p> + * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} + */ + public interface EGLConfigChooser { + /** + * Choose a configuration from the list. Implementors typically + * implement this method by calling + * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. + * @param egl the EGL10 for the current display. + * @param display the current display. + * @return the chosen configuration. + */ + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); + } + + private abstract class BaseConfigChooser + implements EGLConfigChooser { + public BaseConfigChooser(int[] configSpec) { + mConfigSpec = filterConfigSpec(configSpec); + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } + + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + + protected int[] mConfigSpec; + + private int[] filterConfigSpec(int[] configSpec) { + if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) { + return configSpec; + } + /* We know none of the subclasses define EGL_RENDERABLE_TYPE. + * And we know the configSpec is well formed. + */ + int len = configSpec.length; + int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); + newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + if (mEGLContextClientVersion == 2) { + newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT; /* EGL_OPENGL_ES2_BIT */ + } else { + newConfigSpec[len] = EGLExt.EGL_OPENGL_ES3_BIT_KHR; /* EGL_OPENGL_ES3_BIT_KHR */ + } + newConfigSpec[len+1] = EGL10.EGL_NONE; + return newConfigSpec; + } + } + + /** + * Choose a configuration with exactly the specified r,g,b,a sizes, + * and at least the specified depth and stencil sizes. + */ + private class ComponentSizeChooser extends BaseConfigChooser { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(new int[] { + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for (EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if ((d >= mDepthSize) && (s >= mStencilSize)) { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + if ((r == mRedSize) && (g == mGreenSize) + && (b == mBlueSize) && (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + private int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + } + + /** + * This class will choose a RGB_888 surface with + * or without a depth buffer. + * + */ + private class SimpleEGLConfigChooser extends ComponentSizeChooser { + public SimpleEGLConfigChooser(boolean withDepthBuffer) { + super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); + } + } + + /** + * An EGL helper class. + */ + + private static class EglHelper { + public EglHelper(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) { + mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + } + + /** + * Initialize EGL for a given configuration spec. + */ + public void start() { + if (LOG_EGL) { + Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); + } + /* + * Get an EGL instance + */ + mEgl = (EGL10) EGLContext.getEGL(); + + /* + * Get to the default display. + */ + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + + /* + * We can now initialize EGL for that display + */ + int[] version = new int[2]; + if(!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view == null) { + mEglConfig = null; + mEglContext = null; + } else { + mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); + + /* + * Create an EGL context. We want to do this as rarely as we can, because an + * EGL context is a somewhat heavy object. + */ + mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); + } + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + mEglContext = null; + throwEglException("createContext"); + } + if (LOG_EGL) { + Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); + } + + mEglSurface = null; + } + + /** + * Create an egl surface for the current SurfaceHolder surface. If a surface + * already exists, destroy it before creating the new surface. + * + * @return true if the surface was created successfully. + */ + public boolean createSurface() { + if (LOG_EGL) { + Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); + } + /* + * Check preconditions. + */ + if (mEgl == null) { + throw new RuntimeException("egl not initialized"); + } + if (mEglDisplay == null) { + throw new RuntimeException("eglDisplay not initialized"); + } + if (mEglConfig == null) { + throw new RuntimeException("mEglConfig not initialized"); + } + + /* + * The window size has changed, so we need to create a new + * surface. + */ + destroySurfaceImp(); + + /* + * Create an EGL surface we can render into. + */ + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, + mEglDisplay, mEglConfig, view.getHolder()); + } else { + mEglSurface = null; + } + + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + + /* + * Before we can issue GL commands, we need to make sure + * the context is current and bound to a surface. + */ + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + /* + * Could not make the context current, probably because the underlying + * SurfaceView surface has been destroyed. + */ + logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); + return false; + } + + return true; + } + + /** + * Create a GL object for the current EGL context. + * @return + */ + GL createGL() { + + GL gl = mEglContext.getGL(); + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + if (view.mGLWrapper != null) { + gl = view.mGLWrapper.wrap(gl); + } + + if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { + int configFlags = 0; + Writer log = null; + if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { + configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; + } + if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { + log = new LogWriter(); + } + gl = GLDebugHelper.wrap(gl, configFlags, log); + } + } + return gl; + } + + /** + * Display the current render surface. + * @return the EGL error code from eglSwapBuffers. + */ + public int swap() { + if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + return mEgl.eglGetError(); + } + return EGL10.EGL_SUCCESS; + } + + public void destroySurface() { + if (LOG_EGL) { + Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); + } + destroySurfaceImp(); + } + + private void destroySurfaceImp() { + if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); + } + mEglSurface = null; + } + } + + public void finish() { + if (LOG_EGL) { + Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); + } + if (mEglContext != null) { + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); + } + mEglContext = null; + } + if (mEglDisplay != null) { + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + } + } + + private void throwEglException(String function) { + throwEglException(function, mEgl.eglGetError()); + } + + public static void throwEglException(String function, int error) { + String message = formatEglError(function, error); + if (LOG_THREADS) { + Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + + message); + } + throw new RuntimeException(message); + } + + public static void logEglErrorAsWarning(String tag, String function, int error) { + Log.w(tag, formatEglError(function, error)); + } + + public static String formatEglError(String function, int error) { + return function + " failed: " + EGLLogWrapper.getErrorString(error); + } + + private WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef; + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + EGLContext mEglContext; + + } + + /** + * A generic GL Thread. Takes care of initializing EGL and GL. Delegates + * to a Renderer instance to do the actual drawing. Can be configured to + * render continuously or on request. + * + * All potentially blocking synchronization is done through the + * sGLThreadManager object. This avoids multiple-lock ordering issues. + * + */ + static class GLThread extends Thread { + GLThread(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) { + super(); + mWidth = 0; + mHeight = 0; + mRequestRender = true; + mRenderMode = RENDERMODE_CONTINUOUSLY; + mWantRenderNotification = false; + mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + } + + @Override + public void run() { + setName("GLThread " + getId()); + if (LOG_THREADS) { + Log.i("GLThread", "starting tid=" + getId()); + } + + try { + guardedRun(); + } catch (InterruptedException e) { + // fall thru and exit normally + } finally { + sGLThreadManager.threadExiting(this); + } + } + + /* + * This private method should only be called inside a + * synchronized(sGLThreadManager) block. + */ + private void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; + mEglHelper.destroySurface(); + } + } + + /* + * This private method should only be called inside a + * synchronized(sGLThreadManager) block. + */ + private void stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + sGLThreadManager.releaseEglContextLocked(this); + } + } + private void guardedRun() throws InterruptedException { + mEglHelper = new EglHelper(mGLSurfaceViewWeakRef); + mHaveEglContext = false; + mHaveEglSurface = false; + mWantRenderNotification = false; + + try { + GL10 gl = null; + boolean createEglContext = false; + boolean createEglSurface = false; + boolean createGlInterface = false; + boolean lostEglContext = false; + boolean sizeChanged = false; + boolean wantRenderNotification = false; + boolean doRenderNotification = false; + boolean askedToReleaseEglContext = false; + int w = 0; + int h = 0; + Runnable event = null; + Runnable finishDrawingRunnable = null; + + while (true) { + synchronized (sGLThreadManager) { + while (true) { + if (mShouldExit) { + return; + } + + if (! mEventQueue.isEmpty()) { + event = mEventQueue.remove(0); + break; + } + + // Update the pause state. + boolean pausing = false; + if (mPaused != mRequestPaused) { + pausing = mRequestPaused; + mPaused = mRequestPaused; + sGLThreadManager.notifyAll(); + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId()); + } + } + + // Do we need to give up the EGL context? + if (mShouldReleaseEglContext) { + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because asked to tid=" + getId()); + } + stopEglSurfaceLocked(); + stopEglContextLocked(); + mShouldReleaseEglContext = false; + askedToReleaseEglContext = true; + } + + // Have we lost the EGL context? + if (lostEglContext) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + lostEglContext = false; + } + + // When pausing, release the EGL surface: + if (pausing && mHaveEglSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); + } + stopEglSurfaceLocked(); + } + + // When pausing, optionally release the EGL Context: + if (pausing && mHaveEglContext) { + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + boolean preserveEglContextOnPause = view == null ? + false : view.mPreserveEGLContextOnPause; + if (!preserveEglContextOnPause) { + stopEglContextLocked(); + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); + } + } + } + + // Have we lost the SurfaceView surface? + if ((! mHasSurface) && (! mWaitingForSurface)) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); + } + if (mHaveEglSurface) { + stopEglSurfaceLocked(); + } + mWaitingForSurface = true; + mSurfaceIsBad = false; + sGLThreadManager.notifyAll(); + } + + // Have we acquired the surface view surface? + if (mHasSurface && mWaitingForSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId()); + } + mWaitingForSurface = false; + sGLThreadManager.notifyAll(); + } + + if (doRenderNotification) { + if (LOG_SURFACE) { + Log.i("GLThread", "sending render notification tid=" + getId()); + } + mWantRenderNotification = false; + doRenderNotification = false; + mRenderComplete = true; + sGLThreadManager.notifyAll(); + } + + if (mFinishDrawingRunnable != null) { + finishDrawingRunnable = mFinishDrawingRunnable; + mFinishDrawingRunnable = null; + } + + // Ready to draw? + if (readyToDraw()) { + + // If we don't have an EGL context, try to acquire one. + if (! mHaveEglContext) { + if (askedToReleaseEglContext) { + askedToReleaseEglContext = false; + } else { + try { + mEglHelper.start(); + } catch (RuntimeException t) { + sGLThreadManager.releaseEglContextLocked(this); + throw t; + } + mHaveEglContext = true; + createEglContext = true; + + sGLThreadManager.notifyAll(); + } + } + + if (mHaveEglContext && !mHaveEglSurface) { + mHaveEglSurface = true; + createEglSurface = true; + createGlInterface = true; + sizeChanged = true; + } + + if (mHaveEglSurface) { + if (mSizeChanged) { + sizeChanged = true; + w = mWidth; + h = mHeight; + mWantRenderNotification = true; + if (LOG_SURFACE) { + Log.i("GLThread", + "noticing that we want render notification tid=" + + getId()); + } + + // Destroy and recreate the EGL surface. + createEglSurface = true; + + mSizeChanged = false; + } + mRequestRender = false; + sGLThreadManager.notifyAll(); + if (mWantRenderNotification) { + wantRenderNotification = true; + } + break; + } + } else { + if (finishDrawingRunnable != null) { + Log.w(TAG, "Warning, !readyToDraw() but waiting for " + + "draw finished! Early reporting draw finished."); + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } + // By design, this is the only place in a GLThread thread where we wait(). + if (LOG_THREADS) { + Log.i("GLThread", "waiting tid=" + getId() + + " mHaveEglContext: " + mHaveEglContext + + " mHaveEglSurface: " + mHaveEglSurface + + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface + + " mPaused: " + mPaused + + " mHasSurface: " + mHasSurface + + " mSurfaceIsBad: " + mSurfaceIsBad + + " mWaitingForSurface: " + mWaitingForSurface + + " mWidth: " + mWidth + + " mHeight: " + mHeight + + " mRequestRender: " + mRequestRender + + " mRenderMode: " + mRenderMode); + } + sGLThreadManager.wait(); + } + } // end of synchronized(sGLThreadManager) + + if (event != null) { + event.run(); + event = null; + continue; + } + + if (createEglSurface) { + if (LOG_SURFACE) { + Log.w("GLThread", "egl createSurface"); + } + if (mEglHelper.createSurface()) { + synchronized(sGLThreadManager) { + mFinishedCreatingEglSurface = true; + sGLThreadManager.notifyAll(); + } + } else { + synchronized(sGLThreadManager) { + mFinishedCreatingEglSurface = true; + mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + continue; + } + createEglSurface = false; + } + + if (createGlInterface) { + gl = (GL10) mEglHelper.createGL(); + + createGlInterface = false; + } + + // -- GODOT start -- + if (createEglContext) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceCreated"); + } + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + try { + view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + } finally { + } + } + createEglContext = false; + } + + if (sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); + } + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + try { + view.mRenderer.onSurfaceChanged(gl, w, h); + } finally { + } + } + sizeChanged = false; + } + + boolean swapBuffers = false; + if (LOG_RENDERER_DRAW_FRAME) { + Log.w("GLThread", "onDrawFrame tid=" + getId()); + } + { + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + try { + swapBuffers = view.mRenderer.onDrawFrame(gl); + if (finishDrawingRunnable != null) { + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } finally {} + } + } + if (swapBuffers) { + int swapError = mEglHelper.swap(); + switch (swapError) { + case EGL10.EGL_SUCCESS: + break; + case EGL11.EGL_CONTEXT_LOST: + if (LOG_SURFACE) { + Log.i("GLThread", "egl context lost tid=" + getId()); + } + lostEglContext = true; + break; + default: + // Other errors typically mean that the current surface is bad, + // probably because the SurfaceView surface has been destroyed, + // but we haven't been notified yet. + // Log the error to help developers understand why rendering stopped. + EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); + + synchronized (sGLThreadManager) { + mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + break; + } + } + // -- GODOT end -- + + if (wantRenderNotification) { + doRenderNotification = true; + wantRenderNotification = false; + } + } + + } finally { + /* + * clean-up everything... + */ + synchronized (sGLThreadManager) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + } + } + } + + public boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && readyToDraw(); + } + + private boolean readyToDraw() { + return (!mPaused) && mHasSurface && (!mSurfaceIsBad) + && (mWidth > 0) && (mHeight > 0) + && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); + } + + public void setRenderMode(int renderMode) { + if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { + throw new IllegalArgumentException("renderMode"); + } + synchronized(sGLThreadManager) { + mRenderMode = renderMode; + sGLThreadManager.notifyAll(); + } + } + + public int getRenderMode() { + synchronized(sGLThreadManager) { + return mRenderMode; + } + } + + public void requestRender() { + synchronized(sGLThreadManager) { + mRequestRender = true; + sGLThreadManager.notifyAll(); + } + } + + public void requestRenderAndNotify(Runnable finishDrawing) { + synchronized(sGLThreadManager) { + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We will return to the client rendering code, so here we don't need to + // do anything. + if (Thread.currentThread() == this) { + return; + } + + mWantRenderNotification = true; + mRequestRender = true; + mRenderComplete = false; + mFinishDrawingRunnable = finishDrawing; + + sGLThreadManager.notifyAll(); + } + } + + public void surfaceCreated() { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceCreated tid=" + getId()); + } + mHasSurface = true; + mFinishedCreatingEglSurface = false; + sGLThreadManager.notifyAll(); + while (mWaitingForSurface + && !mFinishedCreatingEglSurface + && !mExited) { + try { + sGLThreadManager.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void surfaceDestroyed() { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceDestroyed tid=" + getId()); + } + mHasSurface = false; + sGLThreadManager.notifyAll(); + while((!mWaitingForSurface) && (!mExited)) { + try { + sGLThreadManager.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onPause() { + synchronized (sGLThreadManager) { + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "onPause tid=" + getId()); + } + mRequestPaused = true; + sGLThreadManager.notifyAll(); + while ((! mExited) && (! mPaused)) { + if (LOG_PAUSE_RESUME) { + Log.i("Main thread", "onPause waiting for mPaused."); + } + try { + sGLThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onResume() { + synchronized (sGLThreadManager) { + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "onResume tid=" + getId()); + } + mRequestPaused = false; + mRequestRender = true; + mRenderComplete = false; + sGLThreadManager.notifyAll(); + while ((! mExited) && mPaused && (!mRenderComplete)) { + if (LOG_PAUSE_RESUME) { + Log.i("Main thread", "onResume waiting for !mPaused."); + } + try { + sGLThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onWindowResize(int w, int h) { + synchronized (sGLThreadManager) { + mWidth = w; + mHeight = h; + mSizeChanged = true; + mRequestRender = true; + mRenderComplete = false; + + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We need to process the size change eventually though and update our EGLSurface. + // So we set the parameters and return so they can be processed on our + // next iteration. + if (Thread.currentThread() == this) { + return; + } + + sGLThreadManager.notifyAll(); + + // Wait for thread to react to resize and render a frame + while (! mExited && !mPaused && !mRenderComplete + && ableToDraw()) { + if (LOG_SURFACE) { + Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId()); + } + try { + sGLThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void requestExitAndWait() { + // don't call this from GLThread thread or it is a guaranteed + // deadlock! + synchronized(sGLThreadManager) { + mShouldExit = true; + sGLThreadManager.notifyAll(); + while (! mExited) { + try { + sGLThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void requestReleaseEglContextLocked() { + mShouldReleaseEglContext = true; + sGLThreadManager.notifyAll(); + } + + /** + * Queue an "event" to be run on the GL rendering thread. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("r must not be null"); + } + synchronized(sGLThreadManager) { + mEventQueue.add(r); + sGLThreadManager.notifyAll(); + } + } + + // Once the thread is started, all accesses to the following member + // variables are protected by the sGLThreadManager monitor + private boolean mShouldExit; + private boolean mExited; + private boolean mRequestPaused; + private boolean mPaused; + private boolean mHasSurface; + private boolean mSurfaceIsBad; + private boolean mWaitingForSurface; + private boolean mHaveEglContext; + private boolean mHaveEglSurface; + private boolean mFinishedCreatingEglSurface; + private boolean mShouldReleaseEglContext; + private int mWidth; + private int mHeight; + private int mRenderMode; + private boolean mRequestRender; + private boolean mWantRenderNotification; + private boolean mRenderComplete; + private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>(); + private boolean mSizeChanged = true; + private Runnable mFinishDrawingRunnable = null; + + // End of member variables protected by the sGLThreadManager monitor. + + private EglHelper mEglHelper; + + /** + * Set once at thread construction time, nulled out when the parent view is garbage + * called. This weak reference allows the GLSurfaceView to be garbage collected while + * the GLThread is still alive. + */ + private WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef; + + } + + static class LogWriter extends Writer { + + @Override public void close() { + flushBuilder(); + } + + @Override public void flush() { + flushBuilder(); + } + + @Override public void write(char[] buf, int offset, int count) { + for(int i = 0; i < count; i++) { + char c = buf[offset + i]; + if ( c == '\n') { + flushBuilder(); + } + else { + mBuilder.append(c); + } + } + } + + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.v("GLSurfaceView", mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } + + private StringBuilder mBuilder = new StringBuilder(); + } + + + private void checkRenderThreadState() { + if (mGLThread != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + } + + private static class GLThreadManager { + private static String TAG = "GLThreadManager"; + + public synchronized void threadExiting(GLThread thread) { + if (LOG_THREADS) { + Log.i("GLThread", "exiting tid=" + thread.getId()); + } + thread.mExited = true; + notifyAll(); + } + + /* + * Releases the EGL context. Requires that we are already in the + * sGLThreadManager monitor when this is called. + */ + public void releaseEglContextLocked(GLThread thread) { + notifyAll(); + } + } + + private static final GLThreadManager sGLThreadManager = new GLThreadManager(); + + private final WeakReference<GLSurfaceView> mThisWeakRef = + new WeakReference<GLSurfaceView>(this); + private GLThread mGLThread; + private Renderer mRenderer; + private boolean mDetached; + private EGLConfigChooser mEGLConfigChooser; + private EGLContextFactory mEGLContextFactory; + private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + private GLWrapper mGLWrapper; + private int mDebugFlags; + private int mEGLContextClientVersion; + private boolean mPreserveEGLContextOnPause; +} + diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java index e3956ac459..5c4fd00f6d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java @@ -28,38 +28,38 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot; +package org.godotengine.godot.gl; +import org.godotengine.godot.GodotLib; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.GodotPluginRegistry; -import org.godotengine.godot.utils.GLUtils; - -import android.opengl.GLSurfaceView; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** - * Godot's renderer implementation. + * Godot's GL renderer implementation. */ -class GodotRenderer implements GLSurfaceView.Renderer { +public class GodotRenderer implements GLSurfaceView.Renderer { private final GodotPluginRegistry pluginRegistry; private boolean activityJustResumed = false; - GodotRenderer() { + public GodotRenderer() { this.pluginRegistry = GodotPluginRegistry.getPluginRegistry(); } - public void onDrawFrame(GL10 gl) { + public boolean onDrawFrame(GL10 gl) { if (activityJustResumed) { GodotLib.onRendererResumed(); activityJustResumed = false; } - GodotLib.step(); + boolean swapBuffers = GodotLib.step(); for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onGLDrawFrame(gl); } + + return swapBuffers; } public void onSurfaceChanged(GL10 gl, int width, int height) { @@ -76,13 +76,13 @@ class GodotRenderer implements GLSurfaceView.Renderer { } } - void onActivityResumed() { + public void onActivityResumed() { // We defer invoking GodotLib.onRendererResumed() until the first draw frame call. // This ensures we have a valid GL context and surface when we do so. activityJustResumed = true; } - void onActivityPaused() { + public void onActivityPaused() { GodotLib.onRendererPaused(); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java index 4c1c84affb..e35d4f5828 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -30,8 +30,9 @@ package org.godotengine.godot.xr.ovr; +import org.godotengine.godot.gl.GLSurfaceView; + import android.opengl.EGLExt; -import android.opengl.GLSurfaceView; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java index 2b4369b8a6..deb9c4bb1d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -30,8 +30,9 @@ package org.godotengine.godot.xr.ovr; +import org.godotengine.godot.gl.GLSurfaceView; + import android.opengl.EGL14; -import android.opengl.GLSurfaceView; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java index fbfe0a3a75..f087b7dc74 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.ovr; -import android.opengl.GLSurfaceView; +import org.godotengine.godot.gl.GLSurfaceView; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java index 9fde1961ea..445238b1c2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -30,10 +30,9 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.gl.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; -import android.opengl.GLSurfaceView; - import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index ce1184a75c..5d62723170 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -30,9 +30,9 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.gl.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; -import android.opengl.GLSurfaceView; import android.util.Log; import javax.microedition.khronos.egl.EGL10; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java index 420dda45a0..68329c5c49 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java @@ -30,8 +30,6 @@ package org.godotengine.godot.xr.regular; -import org.godotengine.godot.utils.GLUtils; - import android.util.Log; import javax.microedition.khronos.egl.EGL10; diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 59cfe21c49..711f7cd502 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -17,4 +17,4 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${GODOT_ROOT_DIR}) -add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DTOOLS_ENABLED) +add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED) diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 658f9281ab..ea72bc0e15 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -213,9 +213,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jcl } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { if (step.get() == -1) { - return; + return true; } if (step.get() == 0) { @@ -224,12 +224,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl Main::setup2(Thread::get_caller_id()); input_handler = new AndroidInputHandler(); step.increment(); - return; + return true; } if (step.get() == 1) { if (!Main::start()) { - return; // should exit instead and print the error + return true; // should exit instead and print the error } godot_java->on_godot_setup_completed(env); @@ -243,9 +243,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer); DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope); - if (os_android->main_loop_iterate()) { + bool should_swap_buffers = false; + if (os_android->main_loop_iterate(&should_swap_buffers)) { godot_java->force_quit(env); } + + return should_swap_buffers; } void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 927b44ddb6..e686ee5c09 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -42,7 +42,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz); void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask = 0, jfloat vertical_factor = 0, jfloat horizontal_factor = 0); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 438fc04eb6..ef53415f16 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -36,6 +36,7 @@ #include "main/main.h" #include "platform/android/display_server_android.h" #include "scene/main/scene_tree.h" +#include "servers/rendering_server.h" #include "dir_access_jandroid.h" #include "file_access_android.h" @@ -181,12 +182,18 @@ void OS_Android::main_loop_begin() { } } -bool OS_Android::main_loop_iterate() { +bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) { if (!main_loop) { return false; } DisplayServerAndroid::get_singleton()->process_events(); - return Main::iteration(); + bool exit = Main::iteration(); + + if (r_should_swap_buffers) { + *r_should_swap_buffers = !is_in_low_processor_usage_mode() || RenderingServer::get_singleton()->has_changed(); + } + + return exit; } void OS_Android::main_loop_end() { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index c0c5127224..48239b3f84 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -96,7 +96,7 @@ public: virtual MainLoop *get_main_loop() const override; void main_loop_begin(); - bool main_loop_iterate(); + bool main_loop_iterate(bool *r_should_swap_buffers = nullptr); void main_loop_end(); void main_loop_focusout(); void main_loop_focusin(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index ff8d2b88b1..ccd24ed2cf 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1162,24 +1162,23 @@ Size2 TreeItem::get_minimum_size(int p_column) { return cell.cached_minimum_size; } -Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 0; - return Variant(); + return; } if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; - return Variant(); + return; } StringName method = *p_args[0]; call_recursive(method, &p_args[1], p_argcount - 1, r_error); - return Variant(); } void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index dc786de6dc..04d4b9b935 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -189,7 +189,7 @@ protected: return d; } - Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); public: /* cell mode */ diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 208bbe4d72..9c0dce1e66 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -582,11 +582,11 @@ uint16_t Node::rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc /***** RPC FUNCTIONS ********/ -Variant Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; - return Variant(); + return; } Variant::Type type = p_args[0]->get_type(); @@ -594,7 +594,7 @@ Variant Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallEr r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; - return Variant(); + return; } StringName method = (*p_args[0]).operator StringName(); @@ -602,21 +602,20 @@ Variant Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallEr rpcp(0, method, &p_args[1], p_argcount - 1); r_error.error = Callable::CallError::CALL_OK; - return Variant(); } -Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 2) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 2; - return Variant(); + return; } if (p_args[0]->get_type() != Variant::INT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::INT; - return Variant(); + return; } Variant::Type type = p_args[1]->get_type(); @@ -624,7 +623,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; - return Variant(); + return; } int peer_id = *p_args[0]; @@ -633,7 +632,6 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal rpcp(peer_id, method, &p_args[2], p_argcount - 2); r_error.error = Callable::CallError::CALL_OK; - return Variant(); } void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { diff --git a/scene/main/node.h b/scene/main/node.h index 196d23c14f..16efc7aff5 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -178,8 +178,8 @@ private: TypedArray<Node> _get_children(bool p_include_internal = true) const; Array _get_groups() const; - Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - Variant _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; } _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; } diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 3ddce28b69..8eeedf51a9 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -912,34 +912,32 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } } -Variant SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { r_error.error = Callable::CallError::CALL_OK; - ERR_FAIL_COND_V(p_argcount < 3, Variant()); - ERR_FAIL_COND_V(!p_args[0]->is_num(), Variant()); - ERR_FAIL_COND_V(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING, Variant()); - ERR_FAIL_COND_V(p_args[2]->get_type() != Variant::STRING_NAME && p_args[2]->get_type() != Variant::STRING, Variant()); + ERR_FAIL_COND(p_argcount < 3); + ERR_FAIL_COND(!p_args[0]->is_num()); + ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); + ERR_FAIL_COND(p_args[2]->get_type() != Variant::STRING_NAME && p_args[2]->get_type() != Variant::STRING); int flags = *p_args[0]; StringName group = *p_args[1]; StringName method = *p_args[2]; call_group_flagsp(flags, group, method, p_args + 3, p_argcount - 3); - return Variant(); } -Variant SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { r_error.error = Callable::CallError::CALL_OK; - ERR_FAIL_COND_V(p_argcount < 2, Variant()); - ERR_FAIL_COND_V(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING, Variant()); - ERR_FAIL_COND_V(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING, Variant()); + ERR_FAIL_COND(p_argcount < 2); + ERR_FAIL_COND(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING); + ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); StringName group = *p_args[0]; StringName method = *p_args[1]; call_group_flagsp(0, group, method, p_args + 2, p_argcount - 2); - return Variant(); } int64_t SceneTree::get_frame() const { diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 6197e52fc1..10839ca425 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -175,8 +175,8 @@ private: void make_group_changed(const StringName &p_group); void _notify_group_pause(const StringName &p_group, int p_notification); - Variant _call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - Variant _call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void _call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error); void _flush_delete_queue(); // Optimization. diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 20cb0176e0..e50197d5fa 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1125,7 +1125,8 @@ Vector2 Viewport::get_mouse_position() const { } void Viewport::warp_mouse(const Vector2 &p_position) { - Vector2 gpos = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse().xform(p_position); + Transform2D xform = get_screen_transform(); + Vector2 gpos = xform.xform(p_position).round(); Input::get_singleton()->warp_mouse(gpos); } @@ -3113,6 +3114,10 @@ Viewport::SDFScale Viewport::get_sdf_scale() const { return sdf_scale; } +Transform2D Viewport::get_screen_transform() const { + return _get_input_pre_xform().affine_inverse() * get_final_transform(); +} + #ifndef _3D_DISABLED AudioListener3D *Viewport::get_audio_listener_3d() const { return audio_listener_3d; @@ -3971,6 +3976,20 @@ Transform2D SubViewport::_stretch_transform() { return transform; } +Transform2D SubViewport::get_screen_transform() const { + Transform2D container_transform = Transform2D(); + SubViewportContainer *c = Object::cast_to<SubViewportContainer>(get_parent()); + if (c) { + if (c->is_stretch_enabled()) { + container_transform.scale(Vector2(c->get_stretch_shrink(), c->get_stretch_shrink())); + } + container_transform = c->get_viewport()->get_screen_transform() * c->get_global_transform_with_canvas() * container_transform; + } else { + WARN_PRINT_ONCE("SubViewport is not a child of a SubViewportContainer. get_screen_transform doesn't return the actual screen position."); + } + return container_transform * Viewport::get_screen_transform(); +} + void SubViewport::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index d5fca1b881..80be53fab8 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -609,6 +609,8 @@ public: void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); + virtual Transform2D get_screen_transform() const; + #ifndef _3D_DISABLED bool use_xr = false; friend class AudioListener3D; @@ -732,6 +734,8 @@ public: void set_clear_mode(ClearMode p_mode); ClearMode get_clear_mode() const; + virtual Transform2D get_screen_transform() const override; + SubViewport(); ~SubViewport(); }; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 6837fcae21..2faa107fb4 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1101,6 +1101,14 @@ void Window::popup_centered_ratio(float p_ratio) { void Window::popup(const Rect2i &p_screen_rect) { emit_signal(SNAME("about_to_popup")); + if (!_get_embedder() && get_flag(FLAG_POPUP)) { + // Send a focus-out notification when opening a Window Manager Popup. + SceneTree *scene_tree = get_tree(); + if (scene_tree) { + scene_tree->notify_group("_viewports", NOTIFICATION_WM_WINDOW_FOCUS_OUT); + } + } + // Update window size to calculate the actual window size based on contents minimum size and minimum size. _update_window_size(); @@ -1452,6 +1460,15 @@ void Window::_validate_property(PropertyInfo &property) const { } } +Transform2D Window::get_screen_transform() const { + Transform2D embedder_transform = Transform2D(); + if (_get_embedder()) { + embedder_transform.translate(get_position()); + embedder_transform = _get_embedder()->get_screen_transform() * embedder_transform; + } + return embedder_transform * Viewport::get_screen_transform(); +} + void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title); ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title); diff --git a/scene/main/window.h b/scene/main/window.h index 3d8e337b4a..f674f6425a 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -291,6 +291,8 @@ public: Ref<Font> get_theme_default_font() const; int get_theme_default_font_size() const; + virtual Transform2D get_screen_transform() const override; + Rect2i get_parent_rect() const; virtual DisplayServer::WindowID get_window_id() const override; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 52d6f6e63b..032f43d9b9 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -809,6 +809,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(Texture2D); GDREGISTER_CLASS(Sky); GDREGISTER_CLASS(CompressedTexture2D); + GDREGISTER_CLASS(PortableCompressedTexture2D); GDREGISTER_CLASS(ImageTexture); GDREGISTER_CLASS(AtlasTexture); GDREGISTER_CLASS(MeshTexture); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index a3e356feaf..b1c2702a1e 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -362,8 +362,11 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } - const Variant *args = binds.ptr(); - callable = callable.bind(&args, binds.size()); + const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * binds.size()); + for (int j = 0; j < binds.size(); j++) { + argptrs[j] = &binds[j]; + } + callable = callable.bind(argptrs, binds.size()); } cfrom->connect(snames[c.signal], callable, varray(), CONNECT_PERSIST | c.flags); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 3113987fbc..a66d9d9f27 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -32,12 +32,12 @@ #include "core/core_string_names.h" #include "core/io/image_loader.h" +#include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/os/os.h" #include "mesh.h" #include "scene/resources/bit_map.h" #include "servers/camera/camera_feed.h" - int Texture2D::get_width() const { int ret; if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) { @@ -338,6 +338,312 @@ ImageTexture::~ImageTexture() { } } +///////////////////// + +void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { + if (p_data.size() == 0) { + return; //nothing to do + } + + const uint8_t *data = p_data.ptr(); + uint32_t data_size = p_data.size(); + ERR_FAIL_COND(data_size < 20); + compression_mode = CompressionMode(decode_uint32(data + 0)); + format = Image::Format(decode_uint32(data + 4)); + uint32_t mipmap_count = decode_uint32(data + 8); + size.width = decode_uint32(data + 12); + size.height = decode_uint32(data + 16); + mipmaps = mipmap_count > 1; + + data += 20; + data_size -= 20; + + Ref<Image> image; + + switch (compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + Vector<uint8_t> image_data; + + ERR_FAIL_COND(data_size < 4); + for (uint32_t i = 0; i < mipmap_count; i++) { + uint32_t mipsize = decode_uint32(data); + data += 4; + data_size -= 4; + ERR_FAIL_COND(mipsize < data_size); + Ref<Image> img = memnew(Image(data, data_size)); + ERR_FAIL_COND(img->is_empty()); + if (img->get_format() != format) { // May happen due to webp/png in the tiny mipmaps. + img->convert(format); + } + image_data.append_array(img->get_data()); + + data += mipsize; + data_size -= mipsize; + } + + image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, image_data))); + + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + ERR_FAIL_COND(!Image::basis_universal_unpacker_ptr); + image = Image::basis_universal_unpacker_ptr(data, data_size); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, p_data.slice(20)))); + } break; + } + ERR_FAIL_COND(image.is_null()); + + if (texture.is_null()) { + texture = RenderingServer::get_singleton()->texture_2d_create(image); + } else { + RID new_texture = RenderingServer::get_singleton()->texture_2d_create(image); + RenderingServer::get_singleton()->texture_replace(texture, new_texture); + } + + image_stored = true; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); + alpha_cache.unref(); + + if (keep_all_compressed_buffers || keep_compressed_buffer) { + compressed_buffer = p_data; + } else { + compressed_buffer.clear(); + } +} + +PortableCompressedTexture2D::CompressionMode PortableCompressedTexture2D::get_compression_mode() const { + return compression_mode; +} +Vector<uint8_t> PortableCompressedTexture2D::_get_data() const { + return compressed_buffer; +} + +void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map, float p_lossy_quality) { + ERR_FAIL_COND(p_image.is_null() || p_image->is_empty()); + + Vector<uint8_t> buffer; + + buffer.resize(20); + encode_uint32(p_compression_mode, buffer.ptrw()); + encode_uint32(p_image->get_format(), buffer.ptrw() + 4); + encode_uint32(p_image->get_mipmap_count() + 1, buffer.ptrw() + 8); + encode_uint32(p_image->get_width(), buffer.ptrw() + 12); + encode_uint32(p_image->get_height(), buffer.ptrw() + 16); + + switch (p_compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { + Vector<uint8_t> data; + if (p_compression_mode == COMPRESSION_MODE_LOSSY) { + data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + } else { + data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + } + int data_len = data.size(); + buffer.resize(buffer.size() + 4); + encode_uint32(data_len, buffer.ptrw() + buffer.size() - 4); + buffer.append_array(data); + } + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC); + Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc); + buffer.append_array(budata); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + Ref<Image> copy = p_image->duplicate(); + switch (p_compression_mode) { + case COMPRESSION_MODE_S3TC: + copy->compress(Image::COMPRESS_S3TC); + break; + case COMPRESSION_MODE_ETC2: + copy->compress(Image::COMPRESS_ETC2); + break; + case COMPRESSION_MODE_BPTC: + copy->compress(Image::COMPRESS_BPTC); + break; + default: { + }; + } + + buffer.append_array(copy->get_data()); + + } break; + } + + _set_data(buffer); +} + +Image::Format PortableCompressedTexture2D::get_format() const { + return format; +} + +Ref<Image> PortableCompressedTexture2D::get_image() const { + if (image_stored) { + return RenderingServer::get_singleton()->texture_2d_get(texture); + } else { + return Ref<Image>(); + } +} + +int PortableCompressedTexture2D::get_width() const { + return size.width; +} + +int PortableCompressedTexture2D::get_height() const { + return size.height; +} + +RID PortableCompressedTexture2D::get_rid() const { + if (texture.is_null()) { + //we are in trouble, create something temporary + texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +bool PortableCompressedTexture2D::has_alpha() const { + return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); +} + +void PortableCompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, size), texture, false, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); +} + +bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { + if (!alpha_cache.is_valid()) { + Ref<Image> img = get_image(); + if (img.is_valid()) { + if (img->is_compressed()) { //must decompress, if compressed + Ref<Image> decom = img->duplicate(); + decom->decompress(); + img = decom; + } + alpha_cache.instantiate(); + alpha_cache->create_from_image_alpha(img); + } + } + + if (alpha_cache.is_valid()) { + int aw = int(alpha_cache->get_size().width); + int ah = int(alpha_cache->get_size().height); + if (aw == 0 || ah == 0) { + return true; + } + + int x = p_x * aw / size.width; + int y = p_y * ah / size.height; + + x = CLAMP(x, 0, aw); + y = CLAMP(y, 0, ah); + + return alpha_cache->get_bit(Point2(x, y)); + } + + return true; +} + +void PortableCompressedTexture2D::set_size_override(const Size2 &p_size) { + size_override = p_size; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); +} + +Size2 PortableCompressedTexture2D::get_size_override() const { + return size_override; +} + +void PortableCompressedTexture2D::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +bool PortableCompressedTexture2D::keep_all_compressed_buffers = false; + +void PortableCompressedTexture2D::set_keep_all_compressed_buffers(bool p_keep) { + keep_all_compressed_buffers = p_keep; +} + +bool PortableCompressedTexture2D::is_keeping_all_compressed_buffers() { + return keep_all_compressed_buffers; +} + +void PortableCompressedTexture2D::set_keep_compressed_buffer(bool p_keep) { + keep_compressed_buffer = p_keep; + if (!p_keep) { + compressed_buffer.clear(); + } +} + +bool PortableCompressedTexture2D::is_keeping_compressed_buffer() const { + return keep_compressed_buffer; +} + +void PortableCompressedTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8)); + ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format); + ClassDB::bind_method(D_METHOD("get_compression_mode"), &PortableCompressedTexture2D::get_compression_mode); + + ClassDB::bind_method(D_METHOD("set_size_override", "size"), &PortableCompressedTexture2D::set_size_override); + ClassDB::bind_method(D_METHOD("get_size_override"), &PortableCompressedTexture2D::get_size_override); + + ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer); + ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer); + + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data); + + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers); + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override"), "set_size_override", "get_size_override"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); + + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSLESS); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSY); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BASIS_UNIVERSAL); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_S3TC); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_ETC2); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BPTC); +} + +PortableCompressedTexture2D::PortableCompressedTexture2D() {} + +PortableCompressedTexture2D::~PortableCompressedTexture2D() { + if (texture.is_valid()) { + RenderingServer::get_singleton()->free(texture); + } +} + ////////////////////////////////////////// Ref<Image> CompressedTexture2D::load_image_from_file(FileAccess *f, int p_size_limit) { diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 1e07b83547..7e194fd21d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -137,6 +137,78 @@ public: ~ImageTexture(); }; +class PortableCompressedTexture2D : public Texture2D { + GDCLASS(PortableCompressedTexture2D, Texture2D); + +public: + enum CompressionMode { + COMPRESSION_MODE_LOSSLESS, + COMPRESSION_MODE_LOSSY, + COMPRESSION_MODE_BASIS_UNIVERSAL, + COMPRESSION_MODE_S3TC, + COMPRESSION_MODE_ETC2, + COMPRESSION_MODE_BPTC, + }; + +private: + CompressionMode compression_mode = COMPRESSION_MODE_LOSSLESS; + static bool keep_all_compressed_buffers; + bool keep_compressed_buffer = false; + Vector<uint8_t> compressed_buffer; + Size2 size; + Size2 size_override; + bool mipmaps = false; + Image::Format format = Image::FORMAT_L8; + + mutable RID texture; + mutable Ref<BitMap> alpha_cache; + + bool image_stored = false; + +protected: + Vector<uint8_t> _get_data() const; + void _set_data(const Vector<uint8_t> &p_data); + + static void _bind_methods(); + +public: + CompressionMode get_compression_mode() const; + void create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map = false, float p_lossy_quality = 0.8); + + Image::Format get_format() const; + + void update(const Ref<Image> &p_image); + Ref<Image> get_image() const override; + + int get_width() const override; + int get_height() const override; + + virtual RID get_rid() const override; + + bool has_alpha() const override; + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + void set_size_override(const Size2 &p_size); + Size2 get_size_override() const; + + void set_keep_compressed_buffer(bool p_keep); + bool is_keeping_compressed_buffer() const; + + static void set_keep_all_compressed_buffers(bool p_keep); + static bool is_keeping_all_compressed_buffers(); + + PortableCompressedTexture2D(); + ~PortableCompressedTexture2D(); +}; + +VARIANT_ENUM_CAST(PortableCompressedTexture2D::CompressionMode) + class CompressedTexture2D : public Texture2D { GDCLASS(CompressedTexture2D, Texture2D); diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 56eaa396c8..22149c9e43 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -1664,6 +1664,11 @@ bool RendererCanvasCull::free(RID p_rid) { } */ + if (canvas_item->canvas_group != nullptr) { + memdelete(canvas_item->canvas_group); + canvas_item->canvas_group = nullptr; + } + canvas_item_owner.free(p_rid); } else if (canvas_light_owner.owns(p_rid)) { |