diff options
88 files changed, 2651 insertions, 736 deletions
diff --git a/.github/actions/godot-deps/action.yml b/.github/actions/godot-deps/action.yml index bd9a1f55ed..3344323fd4 100644 --- a/.github/actions/godot-deps/action.yml +++ b/.github/actions/godot-deps/action.yml @@ -10,7 +10,7 @@ inputs: default: x64 scons-version: description: The SCons version to use. - default: 4.8.0 + default: 4.8.1 runs: using: composite diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 203f7960dc..85d53c31ec 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -700,6 +700,91 @@ static GDExtensionTypeFromVariantConstructorFunc gdextension_get_variant_to_type ERR_FAIL_V_MSG(nullptr, "Getting Variant conversion function with invalid type"); } +static GDExtensionVariantGetInternalPtrFunc gdextension_variant_get_ptr_internal_getter(GDExtensionVariantType p_type) { + switch (p_type) { + case GDEXTENSION_VARIANT_TYPE_BOOL: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<bool *(*)(Variant *)>(VariantInternal::get_bool)); + case GDEXTENSION_VARIANT_TYPE_INT: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<int64_t *(*)(Variant *)>(VariantInternal::get_int)); + case GDEXTENSION_VARIANT_TYPE_FLOAT: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<double *(*)(Variant *)>(VariantInternal::get_float)); + case GDEXTENSION_VARIANT_TYPE_STRING: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<String *(*)(Variant *)>(VariantInternal::get_string)); + case GDEXTENSION_VARIANT_TYPE_VECTOR2: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector2 *(*)(Variant *)>(VariantInternal::get_vector2)); + case GDEXTENSION_VARIANT_TYPE_VECTOR2I: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector2i *(*)(Variant *)>(VariantInternal::get_vector2i)); + case GDEXTENSION_VARIANT_TYPE_RECT2: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Rect2 *(*)(Variant *)>(VariantInternal::get_rect2)); + case GDEXTENSION_VARIANT_TYPE_RECT2I: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Rect2i *(*)(Variant *)>(VariantInternal::get_rect2i)); + case GDEXTENSION_VARIANT_TYPE_VECTOR3: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector3 *(*)(Variant *)>(VariantInternal::get_vector3)); + case GDEXTENSION_VARIANT_TYPE_VECTOR3I: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector3i *(*)(Variant *)>(VariantInternal::get_vector3i)); + case GDEXTENSION_VARIANT_TYPE_TRANSFORM2D: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Transform2D *(*)(Variant *)>(VariantInternal::get_transform2d)); + case GDEXTENSION_VARIANT_TYPE_VECTOR4: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector4 *(*)(Variant *)>(VariantInternal::get_vector4)); + case GDEXTENSION_VARIANT_TYPE_VECTOR4I: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector4i *(*)(Variant *)>(VariantInternal::get_vector4i)); + case GDEXTENSION_VARIANT_TYPE_PLANE: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Plane *(*)(Variant *)>(VariantInternal::get_plane)); + case GDEXTENSION_VARIANT_TYPE_QUATERNION: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Quaternion *(*)(Variant *)>(VariantInternal::get_quaternion)); + case GDEXTENSION_VARIANT_TYPE_AABB: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<AABB *(*)(Variant *)>(VariantInternal::get_aabb)); + case GDEXTENSION_VARIANT_TYPE_BASIS: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Basis *(*)(Variant *)>(VariantInternal::get_basis)); + case GDEXTENSION_VARIANT_TYPE_TRANSFORM3D: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Transform3D *(*)(Variant *)>(VariantInternal::get_transform)); + case GDEXTENSION_VARIANT_TYPE_PROJECTION: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Projection *(*)(Variant *)>(VariantInternal::get_projection)); + case GDEXTENSION_VARIANT_TYPE_COLOR: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Color *(*)(Variant *)>(VariantInternal::get_color)); + case GDEXTENSION_VARIANT_TYPE_STRING_NAME: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<StringName *(*)(Variant *)>(VariantInternal::get_string_name)); + case GDEXTENSION_VARIANT_TYPE_NODE_PATH: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<NodePath *(*)(Variant *)>(VariantInternal::get_node_path)); + case GDEXTENSION_VARIANT_TYPE_RID: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<RID *(*)(Variant *)>(VariantInternal::get_rid)); + case GDEXTENSION_VARIANT_TYPE_OBJECT: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Object **(*)(Variant *)>(VariantInternal::get_object)); + case GDEXTENSION_VARIANT_TYPE_CALLABLE: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Callable *(*)(Variant *)>(VariantInternal::get_callable)); + case GDEXTENSION_VARIANT_TYPE_SIGNAL: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Signal *(*)(Variant *)>(VariantInternal::get_signal)); + case GDEXTENSION_VARIANT_TYPE_DICTIONARY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Dictionary *(*)(Variant *)>(VariantInternal::get_dictionary)); + case GDEXTENSION_VARIANT_TYPE_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Array *(*)(Variant *)>(VariantInternal::get_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_BYTE_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedByteArray *(*)(Variant *)>(VariantInternal::get_byte_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_INT32_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedInt32Array *(*)(Variant *)>(VariantInternal::get_int32_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_INT64_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedInt64Array *(*)(Variant *)>(VariantInternal::get_int64_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_FLOAT32_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedFloat32Array *(*)(Variant *)>(VariantInternal::get_float32_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_FLOAT64_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedFloat64Array *(*)(Variant *)>(VariantInternal::get_float64_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_STRING_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedStringArray *(*)(Variant *)>(VariantInternal::get_string_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector2Array *(*)(Variant *)>(VariantInternal::get_vector2_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR3_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector3Array *(*)(Variant *)>(VariantInternal::get_vector3_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_COLOR_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedColorArray *(*)(Variant *)>(VariantInternal::get_color_array)); + case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR4_ARRAY: + return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector4Array *(*)(Variant *)>(VariantInternal::get_vector4_array)); + case GDEXTENSION_VARIANT_TYPE_NIL: + case GDEXTENSION_VARIANT_TYPE_VARIANT_MAX: + ERR_FAIL_V_MSG(nullptr, "Getting Variant get internal pointer function with invalid type."); + } + ERR_FAIL_V_MSG(nullptr, "Getting Variant get internal pointer function with invalid type."); +} + // ptrcalls static GDExtensionPtrOperatorEvaluator gdextension_variant_get_ptr_operator_evaluator(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b) { return (GDExtensionPtrOperatorEvaluator)Variant::get_ptr_operator_evaluator(Variant::Operator(p_operator), Variant::Type(p_type_a), Variant::Type(p_type_b)); @@ -1625,6 +1710,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(variant_can_convert_strict); REGISTER_INTERFACE_FUNC(get_variant_from_type_constructor); REGISTER_INTERFACE_FUNC(get_variant_to_type_constructor); + REGISTER_INTERFACE_FUNC(variant_get_ptr_internal_getter); REGISTER_INTERFACE_FUNC(variant_get_ptr_operator_evaluator); REGISTER_INTERFACE_FUNC(variant_get_ptr_builtin_method); REGISTER_INTERFACE_FUNC(variant_get_ptr_constructor); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 374dbfd071..8268afc3ad 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -198,6 +198,7 @@ typedef struct { typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr); typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr); +typedef void *(*GDExtensionVariantGetInternalPtrFunc)(GDExtensionVariantPtr); typedef void (*GDExtensionPtrOperatorEvaluator)(GDExtensionConstTypePtr p_left, GDExtensionConstTypePtr p_right, GDExtensionTypePtr r_result); typedef void (*GDExtensionPtrBuiltInMethod)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_return, int p_argument_count); typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args); @@ -1384,6 +1385,23 @@ typedef GDExtensionVariantFromTypeConstructorFunc (*GDExtensionInterfaceGetVaria typedef GDExtensionTypeFromVariantConstructorFunc (*GDExtensionInterfaceGetVariantToTypeConstructor)(GDExtensionVariantType p_type); /** + * @name variant_get_ptr_internal_getter + * @since 4.4 + * + * Provides a function pointer for retrieving a pointer to a variant's internal value. + * Access to a variant's internal value can be used to modify it in-place, or to retrieve its value without the overhead of variant conversion functions. + * It is recommended to cache the getter for all variant types in a function table to avoid retrieval overhead upon use. + * + * @note Each function assumes the variant's type has already been determined and matches the function. + * Invoking the function with a variant of a mismatched type has undefined behavior, and may lead to a segmentation fault. + * + * @param p_type The Variant type. + * + * @return A pointer to a type-specific function that returns a pointer to the internal value of a variant. Check the implementation of this function (gdextension_variant_get_ptr_internal_getter) for pointee type info of each variant type. + */ +typedef GDExtensionVariantGetInternalPtrFunc (*GDExtensionInterfaceGetVariantGetInternalPtrFunc)(GDExtensionVariantType p_type); + +/** * @name variant_get_ptr_operator_evaluator * @since 4.1 * diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp index b6b914f36d..8ac2c4bf1f 100644 --- a/core/math/vector4.cpp +++ b/core/math/vector4.cpp @@ -213,7 +213,7 @@ Vector4 Vector4::clampf(real_t p_min, real_t p_max) const { } Vector4::operator String() const { - return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")"; + return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ", " + String::num_real(w, true) + ")"; } static_assert(sizeof(Vector4) == 4 * sizeof(real_t)); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 06aec6a2f7..182848db71 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2143,49 +2143,49 @@ Space key. </constant> <constant name="KEY_EXCLAM" value="33" enum="Key"> - ! key. + Exclamation mark ([code]![/code]) key. </constant> <constant name="KEY_QUOTEDBL" value="34" enum="Key"> - " key. + Double quotation mark ([code]"[/code]) key. </constant> - <constant name="KEY_NUMBERSIGN" value="35" enum="Key"> - # key. + <constant name="KEY_NUMBERSIGN" value="35" enum="Key" keywords="pound, hash"> + Number sign or [i]hash[/i] ([code]#[/code]) key. </constant> <constant name="KEY_DOLLAR" value="36" enum="Key"> - $ key. + Dollar sign ([code]$[/code]) key. </constant> <constant name="KEY_PERCENT" value="37" enum="Key"> - % key. + Percent sign ([code]%[/code]) key. </constant> <constant name="KEY_AMPERSAND" value="38" enum="Key"> - & key. + Ampersand ([code]&[/code]) key. </constant> <constant name="KEY_APOSTROPHE" value="39" enum="Key"> - ' key. + Apostrophe ([code]'[/code]) key. </constant> - <constant name="KEY_PARENLEFT" value="40" enum="Key"> - ( key. + <constant name="KEY_PARENLEFT" value="40" enum="Key" keywords="open round bracket"> + Left parenthesis ([code]([/code]) key. </constant> - <constant name="KEY_PARENRIGHT" value="41" enum="Key"> - ) key. + <constant name="KEY_PARENRIGHT" value="41" enum="Key" keywords="close round bracket"> + Right parenthesis ([code])[/code]) key. </constant> <constant name="KEY_ASTERISK" value="42" enum="Key"> - * key. + Asterisk ([code]*[/code]) key. </constant> <constant name="KEY_PLUS" value="43" enum="Key"> - + key. + Plus ([code]+[/code]) key. </constant> <constant name="KEY_COMMA" value="44" enum="Key"> - , key. + Comma ([code],[/code]) key. </constant> - <constant name="KEY_MINUS" value="45" enum="Key"> - - key. + <constant name="KEY_MINUS" value="45" enum="Key" keywords="hyphen"> + Minus ([code]-[/code]) key. </constant> - <constant name="KEY_PERIOD" value="46" enum="Key"> - . key. + <constant name="KEY_PERIOD" value="46" enum="Key" keywords="dot"> + Period ([code].[/code]) key. </constant> <constant name="KEY_SLASH" value="47" enum="Key"> - / key. + Slash ([code]/[/code]) key. </constant> <constant name="KEY_0" value="48" enum="Key"> Number 0 key. @@ -2218,25 +2218,25 @@ Number 9 key. </constant> <constant name="KEY_COLON" value="58" enum="Key"> - : key. + Colon ([code]:[/code]) key. </constant> <constant name="KEY_SEMICOLON" value="59" enum="Key"> - ; key. + Semicolon ([code];[/code]) key. </constant> <constant name="KEY_LESS" value="60" enum="Key"> - < key. + Less-than sign ([code]<[/code]) key. </constant> <constant name="KEY_EQUAL" value="61" enum="Key"> - = key. + Equal sign ([code]=[/code]) key. </constant> <constant name="KEY_GREATER" value="62" enum="Key"> - > key. + Greater-than sign ([code]>[/code]) key. </constant> <constant name="KEY_QUESTION" value="63" enum="Key"> - ? key. + Question mark ([code]?[/code]) key. </constant> - <constant name="KEY_AT" value="64" enum="Key"> - @ key. + <constant name="KEY_AT" value="64" enum="Key" keywords="commercial at"> + At sign ([code]@[/code]) key. </constant> <constant name="KEY_A" value="65" enum="Key"> A key. @@ -2316,41 +2316,41 @@ <constant name="KEY_Z" value="90" enum="Key"> Z key. </constant> - <constant name="KEY_BRACKETLEFT" value="91" enum="Key"> - [ key. + <constant name="KEY_BRACKETLEFT" value="91" enum="Key" keywords="open square bracket"> + Left bracket ([code][lb][/code]) key. </constant> <constant name="KEY_BACKSLASH" value="92" enum="Key"> - \ key. + Backslash ([code]\[/code]) key. </constant> - <constant name="KEY_BRACKETRIGHT" value="93" enum="Key"> - ] key. + <constant name="KEY_BRACKETRIGHT" value="93" enum="Key" keywords="close square bracket"> + Right bracket ([code][rb][/code]) key. </constant> - <constant name="KEY_ASCIICIRCUM" value="94" enum="Key"> - ^ key. + <constant name="KEY_ASCIICIRCUM" value="94" enum="Key" keywords="caret"> + Caret ([code]^[/code]) key. </constant> - <constant name="KEY_UNDERSCORE" value="95" enum="Key"> - _ key. + <constant name="KEY_UNDERSCORE" value="95" enum="Key" keywords="underline"> + Underscore ([code]_[/code]) key. </constant> - <constant name="KEY_QUOTELEFT" value="96" enum="Key"> - ` key. + <constant name="KEY_QUOTELEFT" value="96" enum="Key" keywords="backtick, backquote"> + Backtick ([code]`[/code]) key. </constant> - <constant name="KEY_BRACELEFT" value="123" enum="Key"> - { key. + <constant name="KEY_BRACELEFT" value="123" enum="Key" keywords="open curly bracket"> + Left brace ([code]{[/code]) key. </constant> - <constant name="KEY_BAR" value="124" enum="Key"> - | key. + <constant name="KEY_BAR" value="124" enum="Key" keywords="pipe"> + Vertical bar or [i]pipe[/i] ([code]|[/code]) key. </constant> - <constant name="KEY_BRACERIGHT" value="125" enum="Key"> - } key. + <constant name="KEY_BRACERIGHT" value="125" enum="Key" keywords="close curly bracket"> + Right brace ([code]}[/code]) key. </constant> <constant name="KEY_ASCIITILDE" value="126" enum="Key"> - ~ key. + Tilde ([code]~[/code]) key. </constant> <constant name="KEY_YEN" value="165" enum="Key"> - ¥ key. + Yen symbol ([code]¥[/code]) key. </constant> - <constant name="KEY_SECTION" value="167" enum="Key"> - § key. + <constant name="KEY_SECTION" value="167" enum="Key" keywords="silcrow"> + Section sign ([code]§[/code]) key. </constant> <constant name="KEY_CODE_MASK" value="8388607" enum="KeyModifierMask" is_bitfield="true"> Key Code mask. diff --git a/doc/classes/AnimationLibrary.xml b/doc/classes/AnimationLibrary.xml index 7f87ea4616..51588a6052 100644 --- a/doc/classes/AnimationLibrary.xml +++ b/doc/classes/AnimationLibrary.xml @@ -31,6 +31,12 @@ Returns the keys for the [Animation]s stored in the library. </description> </method> + <method name="get_animation_list_size" qualifiers="const"> + <return type="int" /> + <description> + Returns the key count for the [Animation]s stored in the library. + </description> + </method> <method name="has_animation" qualifiers="const"> <return type="bool" /> <param index="0" name="name" type="StringName" /> diff --git a/doc/classes/CollisionShape3D.xml b/doc/classes/CollisionShape3D.xml index a4e0ed0b28..69a7dd2b36 100644 --- a/doc/classes/CollisionShape3D.xml +++ b/doc/classes/CollisionShape3D.xml @@ -29,6 +29,12 @@ </method> </methods> <members> + <member name="debug_color" type="Color" setter="set_debug_color" getter="get_debug_color" default="Color(0, 0, 0, 0)"> + The collision shape color that is displayed in the editor, or in the running project if [b]Debug > Visible Collision Shapes[/b] is checked at the top of the editor. If this is reset to its default value of [code]Color(0, 0, 0, 0)[/code], the value of [member ProjectSettings.debug/shapes/collision/shape_color] will be used instead. + </member> + <member name="debug_fill" type="bool" setter="set_enable_debug_fill" getter="get_enable_debug_fill" default="true"> + If [code]true[/code], when the shape is displayed, it will show a solid fill color in addition to its wireframe. + </member> <member name="disabled" type="bool" setter="set_disabled" getter="is_disabled" default="false" keywords="enabled"> A disabled collision shape has no effect in the world. </member> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 342e20759e..d22be4a52d 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -12,7 +12,7 @@ Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. - [Theme] resources change the Control's appearance. If you change the [Theme] on a [Control] node, it affects all of its children. To override some of the theme's parameters, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can override the theme with the Inspector. + [Theme] resources change the control's appearance. The [member theme] of a [Control] node affects all of its direct and indirect children (as long as a chain of controls is uninterrupted). To override some of the theme items, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can also override theme items in the Inspector. [b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class. </description> <tutorials> diff --git a/doc/classes/LookAtModifier3D.xml b/doc/classes/LookAtModifier3D.xml index b6d106c4c5..2475de1868 100644 --- a/doc/classes/LookAtModifier3D.xml +++ b/doc/classes/LookAtModifier3D.xml @@ -32,8 +32,11 @@ </method> </methods> <members> - <member name="bone" type="int" setter="set_bone" getter="get_bone" default="0"> - The bone index of the [Skeleton3D] that the modification will operate on. + <member name="bone" type="int" setter="set_bone" getter="get_bone" default="-1"> + Index of the [member bone_name] in the parent [Skeleton3D]. + </member> + <member name="bone_name" type="String" setter="set_bone_name" getter="get_bone_name" default=""""> + The bone name of the [Skeleton3D] that the modification will operate on. </member> <member name="duration" type="float" setter="set_duration" getter="get_duration" default="0.0"> The duration of the time-based interpolation. Interpolation is triggered at the following cases: @@ -48,6 +51,9 @@ The forward axis of the bone. This [SkeletonModifier3D] modifies the bone so that this axis points toward the [member target_node]. </member> <member name="origin_bone" type="int" setter="set_origin_bone" getter="get_origin_bone"> + Index of the [member origin_bone_name] in the parent [Skeleton3D]. + </member> + <member name="origin_bone_name" type="String" setter="set_origin_bone_name" getter="get_origin_bone_name"> If [member origin_from] is [constant ORIGIN_FROM_SPECIFIC_BONE], the bone global pose position specified for this is used as origin. </member> <member name="origin_external_node" type="NodePath" setter="set_origin_external_node" getter="get_origin_external_node"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 5ab7c27f4f..2389db1301 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -721,6 +721,7 @@ - If standard input is console, this method will block until the program receives a line break in standard input (usually by the user pressing [kbd]Enter[/kbd]). - If standard input is pipe, this method will block until a specific amount of data is read or pipe is closed. - If standard input is a file, this method will read a specific amount of data (or less if end-of-file is reached) and return immediately. + [b]Note:[/b] This method automatically replaces [code]\r\n[/code] line breaks with [code]\n[/code] and removes them from the end of the string. Use [method read_buffer_from_stdin] to read the unprocessed data. [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. [b]Note:[/b] On exported Windows builds, run the console wrapper executable to access the terminal. If standard input is console, calling this method without console wrapped will freeze permanently. If standard input is pipe or file, it can be used without console wrapper. If you need a single executable with full console support, use a custom build compiled with the [code]windows_subsystem=console[/code] flag. </description> diff --git a/doc/classes/RetargetModifier3D.xml b/doc/classes/RetargetModifier3D.xml new file mode 100644 index 0000000000..522b954aba --- /dev/null +++ b/doc/classes/RetargetModifier3D.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="RetargetModifier3D" inherits="SkeletonModifier3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A modifier to transfer parent skeleton poses (or global poses) to child skeletons in model space with different rests. + </brief_description> + <description> + Retrieves the pose (or global pose) relative to the parent Skeleton's rest in model space and transfers it to the child Skeleton. + This modifier rewrites the pose of the child skeleton directly in the parent skeleton's update process. This means that it overwrites the mapped bone pose set in the normal process on the target skeleton. If you want to set the target skeleton bone pose after retargeting, you will need to add a [SkeletonModifier3D] child to the target skeleton and thereby modify the pose. + [b]Note:[/b] When the [member use_global_pose] is enabled, even if it is an unmapped bone, it can cause visual problems because the global pose is applied ignoring the parent bone's pose [b]if it has mapped bone children[/b]. See also [member use_global_pose]. + </description> + <tutorials> + </tutorials> + <members> + <member name="position_enabled" type="bool" setter="set_position_enabled" getter="is_position_enabled" default="true"> + If [code]true[/code], allows to retarget the position. + </member> + <member name="profile" type="SkeletonProfile" setter="set_profile" getter="get_profile"> + [SkeletonProfile] for retargeting bones with names matching the bone list. + </member> + <member name="rotation_enabled" type="bool" setter="set_rotation_enabled" getter="is_rotation_enabled" default="true"> + If [code]true[/code], allows to retarget the rotation. + </member> + <member name="scale_enabled" type="bool" setter="set_scale_enabled" getter="is_scale_enabled" default="true"> + If [code]true[/code], allows to retarget the scale. + </member> + <member name="use_global_pose" type="bool" setter="set_use_global_pose" getter="is_using_global_pose" default="false"> + If [code]false[/code], in case the target skeleton has fewer bones than the source skeleton, the source bone parent's transform will be ignored. + Instead, it is possible to retarget between models with different body shapes, and position, rotation, and scale can be retargeted separately. + If [code]true[/code], retargeting is performed taking into account global pose. + In case the target skeleton has fewer bones than the source skeleton, the source bone parent's transform is taken into account. However, bone length between skeletons must match exactly, if not, the bones will be forced to expand or shrink. + This is useful for using dummy bone with length [code]0[/code] to match postures when retargeting between models with different number of bones. + </member> + </members> +</class> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index f5b808be8e..aa751de5f2 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -393,6 +393,11 @@ [b]Note:[/b] During the update process, this signal is not fired, so modification by [SkeletonModifier3D] is not detected. </description> </signal> + <signal name="rest_updated"> + <description> + Emitted when the rest is updated. + </description> + </signal> <signal name="show_rest_only_changed"> <description> Emitted when the value of [member show_rest_only] changes. diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ffc270cd36..299ac6536f 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -191,7 +191,7 @@ String OS_Unix::get_stdin_string(int64_t p_buffer_size) { Vector<uint8_t> data; data.resize(p_buffer_size); if (fgets((char *)data.ptrw(), data.size(), stdin)) { - return String::utf8((char *)data.ptr()); + return String::utf8((char *)data.ptr()).replace("\r\n", "\n").rstrip("\n"); } return String(); } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 19e0647fb7..0c2b230786 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1400,12 +1400,26 @@ void AnimationTimelineEdit::_anim_loop_pressed() { undo_redo->add_undo_method(this, "update_values"); undo_redo->commit_action(); } else { - String base_path = animation->get_path(); - if (FileAccess::exists(base_path + ".import")) { - EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from imported scene.")); + String base = animation->get_path(); + int srpos = base.find("::"); + if (srpos != -1) { + base = animation->get_path().substr(0, srpos); + } + + if (FileAccess::exists(base + ".import")) { + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from an imported scene.\n\nTo change this animation's loop mode, navigate to the scene's Advanced Import settings and select the animation.\nYou can then change the loop mode from the inspector menu.")); + } else { + EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from an imported resource.")); + } } else { - EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another scene.")); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another scene.\n\nYou must open this scene and change the animation's loop mode from there.")); + } else { + EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another resource.")); + } } + update_values(); } } diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index 1ea9a66534..31c09c9b37 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -81,6 +81,9 @@ void EditorPerformanceProfiler::Monitor::reset() { String EditorPerformanceProfiler::_create_label(float p_value, Performance::MonitorType p_type) { switch (p_type) { + case Performance::MONITOR_TYPE_QUANTITY: { + return TS->format_number(itos(p_value)); + } case Performance::MONITOR_TYPE_MEMORY: { return String::humanize_size(p_value); } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 6e837560f6..1a973d7b77 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3472,6 +3472,14 @@ void EditorInspector::update_tree() { editors.append_array(late_editors); + const Node *node = Object::cast_to<Node>(object); + + Vector<SceneState::PackState> sstack; + if (node != nullptr) { + const Node *es = EditorNode::get_singleton()->get_edited_scene(); + sstack = PropertyUtils::get_node_states_stack(node, es); + } + for (int i = 0; i < editors.size(); i++) { EditorProperty *ep = Object::cast_to<EditorProperty>(editors[i].property_editor); const Vector<String> &properties = editors[i].properties; @@ -3525,7 +3533,15 @@ void EditorInspector::update_tree() { ep->set_checked(checked); ep->set_keying(keying); ep->set_read_only(property_read_only || all_read_only); - ep->set_deletable(deletable_properties || p.name.begins_with("metadata/")); + if (p.name.begins_with("metadata/")) { + Variant _default = Variant(); + if (node != nullptr) { + _default = PropertyUtils::get_property_default_value(node, p.name, nullptr, &sstack, false, nullptr, nullptr); + } + ep->set_deletable(_default == Variant()); + } else { + ep->set_deletable(deletable_properties); + } } if (ep && ep->is_favoritable() && current_favorites.has(p.name)) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f056a477c4..222696a608 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5190,7 +5190,8 @@ void EditorNode::show_accept(const String &p_text, const String &p_title) { _close_save_scene_progress(); accept->set_ok_button_text(p_title); accept->set_text(p_text); - EditorInterface::get_singleton()->popup_dialog_centered(accept); + accept->reset_size(); + EditorInterface::get_singleton()->popup_dialog_centered_clamped(accept, Size2i(), 0.0); } } @@ -5200,7 +5201,8 @@ void EditorNode::show_save_accept(const String &p_text, const String &p_title) { _close_save_scene_progress(); save_accept->set_ok_button_text(p_title); save_accept->set_text(p_text); - EditorInterface::get_singleton()->popup_dialog_centered(save_accept); + save_accept->reset_size(); + EditorInterface::get_singleton()->popup_dialog_centered_clamped(save_accept, Size2i(), 0.0); } } @@ -5209,7 +5211,8 @@ void EditorNode::show_warning(const String &p_text, const String &p_title) { _close_save_scene_progress(); warning->set_text(p_text); warning->set_title(p_title); - EditorInterface::get_singleton()->popup_dialog_centered(warning); + warning->reset_size(); + EditorInterface::get_singleton()->popup_dialog_centered_clamped(warning, Size2i(), 0.0); } else { WARN_PRINT(p_title + " " + p_text); } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 2b2b32eb22..a8f362f969 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2645,7 +2645,7 @@ EditorPropertyColor::EditorPropertyColor() { add_child(picker); picker->set_flat(true); picker->connect("color_changed", callable_mp(this, &EditorPropertyColor::_color_changed)); - picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed)); + picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed), CONNECT_DEFERRED); picker->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(picker->get_picker())); picker->get_popup()->connect("about_to_popup", callable_mp(this, &EditorPropertyColor::_picker_opening)); } diff --git a/editor/icons/RetargetModifier3D.svg b/editor/icons/RetargetModifier3D.svg new file mode 100644 index 0000000000..2ca7af6c6e --- /dev/null +++ b/editor/icons/RetargetModifier3D.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#fc7f7f"><path d="m11.667 4.166h-3.334c-1.841 0-3.333 1.492-3.333 3.334.003 1.188.638 2.283 1.667 2.877v2.956c0 .597.317 1.146.833 1.444.254.146.54.221.833.221v.002h3.334v-.002c.293 0 .579-.075.833-.221.516-.299.833-.851.833-1.444v-2.956c1.028-.594 1.664-1.689 1.667-2.877 0-1.842-1.492-3.334-3.333-3.334zm-2.5 4.166h1.666v.834h-1.666zm-2.5-.832c0-.461.372-.834.833-.834s.833.373.833.834-.372.832-.833.832-.833-.371-.833-.832zm5.833 3.223v2.61h-.833v-.833h-.834v.833h-1.666v-.833h-.834v.833h-.833v-2.608-.725h.833v.832h.834v-.832h1.666v.832h.834v-.832h.833zm0-2.391c-.461 0-.833-.371-.833-.832s.372-.834.833-.834.833.373.833.834-.372.832-.833.832z"/><path d="m4.418 9.334h-.085v.833h-.833v-2.608-.725h.567c.323-2.072 2.104-3.668 4.266-3.668h2.445c-.473-1.263-1.682-2.166-3.111-2.166h-3.334c-1.841 0-3.333 1.492-3.333 3.334.003 1.188.638 2.283 1.667 2.877v2.956c0 .597.317 1.146.833 1.444.254.146.54.221.833.221v.002h1.334v-.929c-.538-.421-.962-.962-1.249-1.571zm-1.751-5c0-.461.372-.834.833-.834s.833.373.833.834-.372.832-.833.832-.833-.371-.833-.832z"/></g></svg>
\ No newline at end of file diff --git a/editor/import/3d/post_import_plugin_skeleton_renamer.cpp b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp index 3f6bfdcf05..700b2bc719 100644 --- a/editor/import/3d/post_import_plugin_skeleton_renamer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp @@ -39,7 +39,7 @@ void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) { if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) { - r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true)); + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/unique_node/make_unique"), true)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/bone_renamer/unique_node/skeleton_name"), "GeneralSkeleton")); } diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index 64bec0532b..82940f9cef 100644 --- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -33,6 +33,7 @@ #include "editor/import/3d/scene_import_settings.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/3d/retarget_modifier_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/animation/animation_player.h" #include "scene/resources/bone_map.h" @@ -42,8 +43,18 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/apply_node_transforms"), true)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/normalize_position_tracks"), true)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/reset_all_bone_poses_after_import"), true)); - r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/overwrite_axis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "retarget/rest_fixer/retarget_method", PROPERTY_HINT_ENUM, "None,Overwrite Axis,Use Retarget Modifier", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/keep_global_rest_on_leftovers"), true)); + String skeleton_bones_must_be_renamed_warning = String( + "The skeleton modifier option uses SkeletonProfile as a list of bone names and retargets by name matching. Without renaming, retargeting by modifier will not work and the track path of the animation will be broken and it will be not playbacked correctly."); // TODO: translate. + r_options->push_back(ResourceImporter::ImportOption( + PropertyInfo( + Variant::STRING, U"retarget/rest_fixer/\u26A0_validation_warning/skeleton_bones_must_be_renamed", + PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), + Variant(skeleton_bones_must_be_renamed_warning))); + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/use_global_pose"), true)); + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/rest_fixer/original_skeleton_name"), "OriginalSkeleton")); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); // TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options). // get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values. @@ -63,7 +74,11 @@ Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(Intern } } } else if (p_option == "retarget/rest_fixer/keep_global_rest_on_leftovers") { - return bool(p_options["retarget/rest_fixer/overwrite_axis"]); + return int(p_options["retarget/rest_fixer/retarget_method"]) == 1; + } else if (p_option == "retarget/rest_fixer/original_skeleton_name" || p_option == "retarget/rest_fixer/use_global_pose") { + return int(p_options["retarget/rest_fixer/retarget_method"]) == 2; + } else if (p_option.begins_with("retarget/") && p_option.ends_with("skeleton_bones_must_be_renamed")) { + return int(p_options["retarget/rest_fixer/retarget_method"]) == 2 && bool(p_options["retarget/bone_renamer/rename_bones"]) == false; } } return true; @@ -147,7 +162,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory src_skeleton->set_bone_pose_position(src_idx, src_skeleton->get_bone_pose_position(src_idx) * scl); } - // Fix animation. + // Fix animation by changing node transform. bones_to_process = src_skeleton->get_parentless_bones(); { TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer"); @@ -224,6 +239,10 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory List<StringName> anims; ap->get_animation_list(&anims); for (const StringName &name : anims) { + if (String(name).contains("/")) { + continue; // Avoid animation library which may be created by importer dynamically. + } + Ref<Animation> anim = ap->get_animation(name); int track_len = anim->get_track_count(); @@ -454,8 +473,13 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } } - // Overwrite axis. - if (bool(p_options["retarget/rest_fixer/overwrite_axis"])) { + bool is_using_modifier = int(p_options["retarget/rest_fixer/retarget_method"]) == 2; + bool is_using_global_pose = bool(p_options["retarget/rest_fixer/use_global_pose"]); + Skeleton3D *orig_skeleton = nullptr; + Skeleton3D *profile_skeleton = nullptr; + + // Retarget in some way. + if (int(p_options["retarget/rest_fixer/retarget_method"]) > 0) { LocalVector<Transform3D> old_skeleton_rest; LocalVector<Transform3D> old_skeleton_global_rest; for (int i = 0; i < src_skeleton->get_bone_count(); i++) { @@ -463,11 +487,151 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i)); } + // Build structure for modifier. + if (is_using_modifier) { + orig_skeleton = src_skeleton; + + // Duplicate src_skeleton to modify animation tracks, it will memdelele after that animation track modification. + src_skeleton = memnew(Skeleton3D); + for (int i = 0; i < orig_skeleton->get_bone_count(); i++) { + src_skeleton->add_bone(orig_skeleton->get_bone_name(i)); + src_skeleton->set_bone_rest(i, orig_skeleton->get_bone_rest(i)); + src_skeleton->set_bone_pose(i, orig_skeleton->get_bone_pose(i)); + } + for (int i = 0; i < orig_skeleton->get_bone_count(); i++) { + src_skeleton->set_bone_parent(i, orig_skeleton->get_bone_parent(i)); + } + src_skeleton->set_motion_scale(orig_skeleton->get_motion_scale()); + + // Rename orig_skeleton (previous src_skeleton), since it is not animated by animation track with GeneralSkeleton. + String original_skeleton_name = String(p_options["retarget/rest_fixer/original_skeleton_name"]); + String skel_name = orig_skeleton->get_name(); + ERR_FAIL_COND_MSG(original_skeleton_name.is_empty(), "Original skeleton name cannot be empty."); + ERR_FAIL_COND_MSG(original_skeleton_name == skel_name, "Original skeleton name must be different from unique skeleton name."); + + // Rename profile skeleton to be general skeleton. + profile_skeleton = memnew(Skeleton3D); + bool is_unique = orig_skeleton->is_unique_name_in_owner(); + if (is_unique) { + orig_skeleton->set_unique_name_in_owner(false); + } + orig_skeleton->set_name(original_skeleton_name); + profile_skeleton->set_name(skel_name); + if (is_unique) { + profile_skeleton->set_unique_name_in_owner(true); + } + // Build profile skeleton bones. + int len = profile->get_bone_size(); + for (int i = 0; i < len; i++) { + profile_skeleton->add_bone(profile->get_bone_name(i)); + profile_skeleton->set_bone_rest(i, profile->get_reference_pose(i)); + } + for (int i = 0; i < len; i++) { + int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i)); + if (target_parent >= 0) { + profile_skeleton->set_bone_parent(i, target_parent); + } + } + for (int i = 0; i < len; i++) { + Vector3 origin; + int found = orig_skeleton->find_bone(profile->get_bone_name(i)); + String parent_name = profile->get_bone_parent(i); + if (found >= 0) { + origin = orig_skeleton->get_bone_global_rest(found).origin; + if (profile->get_bone_name(i) != profile->get_root_bone()) { + int src_parent = -1; + while (src_parent < 0 && !parent_name.is_empty()) { + src_parent = orig_skeleton->find_bone(parent_name); + parent_name = profile->get_bone_parent(profile->find_bone(parent_name)); + } + if (src_parent >= 0) { + Transform3D parent_grest = orig_skeleton->get_bone_global_rest(src_parent); + origin = origin - parent_grest.origin; + } + } + } + int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i)); + if (target_parent >= 0) { + origin = profile_skeleton->get_bone_global_rest(target_parent).basis.get_rotation_quaternion().xform_inv(origin); + } + profile_skeleton->set_bone_rest(i, Transform3D(profile_skeleton->get_bone_rest(i).basis, origin)); + } + profile_skeleton->set_motion_scale(orig_skeleton->get_motion_scale()); + profile_skeleton->reset_bone_poses(); + // Make structure with modifier. + Node *owner = p_node->get_owner(); + + Node *pr = orig_skeleton->get_parent(); + pr->add_child(profile_skeleton); + profile_skeleton->set_owner(owner); + + RetargetModifier3D *mod = memnew(RetargetModifier3D); + profile_skeleton->add_child(mod); + mod->set_owner(owner); + mod->set_name("RetargetModifier3D"); + + orig_skeleton->set_owner(nullptr); + orig_skeleton->reparent(mod, false); + orig_skeleton->set_owner(owner); + orig_skeleton->set_unique_name_in_owner(true); + + mod->set_use_global_pose(is_using_global_pose); + mod->set_profile(profile); + + // Fix skeleton name in animation. + // Mapped skeleton is animated by %GenerarSkeleton:RenamedBoneName. + // Unmapped skeleton is animated by %OriginalSkeleton:OriginalBoneName. + if (is_using_modifier) { + TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer"); + String general_skeleton_pathname = UNIQUE_NODE_PREFIX + profile_skeleton->get_name(); + while (nodes.size()) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back()); + List<StringName> anims; + ap->get_animation_list(&anims); + for (const StringName &name : anims) { + Ref<Animation> anim = ap->get_animation(name); + int track_len = anim->get_track_count(); + for (int i = 0; i < track_len; i++) { + if (anim->track_get_path(i).get_name_count() == 0) { + return; + } + if (anim->track_get_path(i).get_name(0) == general_skeleton_pathname) { + bool replace = false; + if (anim->track_get_path(i).get_subname_count() > 0) { + int found = profile_skeleton->find_bone(anim->track_get_path(i).get_concatenated_subnames()); + if (found < 0) { + replace = true; + } + } else { + replace = true; + } + if (replace) { + String path_string = UNIQUE_NODE_PREFIX + original_skeleton_name; + if (anim->track_get_path(i).get_name_count() > 1) { + Vector<StringName> names = anim->track_get_path(i).get_names(); + names.remove_at(0); + for (int j = 0; j < names.size(); j++) { + path_string += "/" + names[i].operator String(); + } + } + if (anim->track_get_path(i).get_subname_count() > 0) { + path_string = path_string + String(":") + anim->track_get_path(i).get_concatenated_subnames(); + } + anim->track_set_path(i, path_string); + } + } + } + } + } + } + } + bool keep_global_rest_leftovers = bool(p_options["retarget/rest_fixer/keep_global_rest_on_leftovers"]); // Scan hierarchy and populate a whitelist of unmapped bones without mapped descendants. + // When both is_using_modifier and is_using_global_pose are enabled, this array is used for detecting warning. Vector<int> keep_bone_rest; - if (keep_global_rest_leftovers) { + if (is_using_modifier || keep_global_rest_leftovers) { Vector<int> bones_to_process = src_skeleton->get_parentless_bones(); while (bones_to_process.size() > 0) { int src_idx = bones_to_process[0]; @@ -526,12 +690,14 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory if (src_parent_idx >= 0) { src_pg = src_skeleton->get_bone_global_rest(src_parent_idx).basis; } - int prof_idx = profile->find_bone(src_bone_name); if (prof_idx >= 0) { - tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; // Mapped bone uses reference pose. + // Mapped bone uses reference pose. + // It is fine to change rest here even though is_using_modifier is enabled, since next process is aborted with unmapped bones. + tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; } else if (keep_global_rest_leftovers && keep_bone_rest.has(src_idx)) { - tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; // Non-Mapped bone without mapped children keeps global rest. + // Non-Mapped bones without mapped children keeps global rest. + tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; } } @@ -548,7 +714,8 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory src_skeleton->set_bone_rest(src_idx, Transform3D(tgt_rot, diff.xform(src_skeleton->get_bone_rest(src_idx).origin))); } - // Fix animation. + // Fix animation by changing rest. + bool warning_detected = false; { TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer"); while (nodes.size()) { @@ -573,7 +740,9 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node); - if (!track_skeleton || track_skeleton != src_skeleton) { + if (!track_skeleton || + (is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) || + (!is_using_modifier && track_skeleton != src_skeleton)) { continue; } @@ -584,6 +753,16 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory int bone_idx = src_skeleton->find_bone(bn); + if (is_using_modifier) { + int prof_idx = profile->find_bone(bn); + if (prof_idx < 0) { + if (keep_bone_rest.has(bone_idx)) { + warning_detected = true; + } + continue; // If is_using_modifier, the original skeleton rest is not changed. + } + } + Transform3D old_rest = old_skeleton_rest[bone_idx]; Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx); Transform3D old_pg; @@ -629,6 +808,13 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } } } + if (is_using_global_pose && warning_detected) { + // TODO: + // Theoretically, if A and its conversion are calculated correctly taking into account the difference in the number of bones, + // there is no need to disable use_global_pose, but this is probably a fairly niche case. + WARN_PRINT_ED("Animated extra bone between mapped bones detected, consider disabling Use Global Pose option to prevent that the pose origin be overridden by the RetargetModifier3D."); + } + if (p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") && !bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) { // If Reset All Bone Poses After Import is disabled, preserve the original bone pose, adjusted for the new bone rolls. for (int bone_idx = 0; bone_idx < src_skeleton->get_bone_count(); bone_idx++) { @@ -654,6 +840,11 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } } + if (is_using_modifier) { + memdelete(src_skeleton); + src_skeleton = profile_skeleton; + } + is_rest_changed = true; } @@ -681,7 +872,9 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node); - if (!track_skeleton || track_skeleton != src_skeleton) { + if (!track_skeleton || + (is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) || + (!is_using_modifier && track_skeleton != src_skeleton)) { continue; } @@ -696,7 +889,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } } - if (is_rest_changed) { + if (!is_using_modifier && is_rest_changed) { // Fix skin. { HashSet<Ref<Skin>> mutated_skins; @@ -766,6 +959,14 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory src_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion()); src_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale()); } + if (orig_skeleton) { + for (int i = 0; i < orig_skeleton->get_bone_count(); i++) { + Transform3D fixed_rest = orig_skeleton->get_bone_rest(i); + orig_skeleton->set_bone_pose_position(i, fixed_rest.origin); + orig_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion()); + orig_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale()); + } + } } } diff --git a/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp index 53bcc59fcb..98312d3521 100644 --- a/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp @@ -39,7 +39,7 @@ void PostImportPluginSkeletonTrackOrganizer::get_internal_import_options(Interna if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) { r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/except_bone_transform"), false)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unimportant_positions"), true)); - r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unmapped_bones"), false)); + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "retarget/remove_tracks/unmapped_bones", PROPERTY_HINT_ENUM, "None,Remove,Separate Library"), 0)); } } @@ -61,9 +61,9 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate } bool remove_except_bone = bool(p_options["retarget/remove_tracks/except_bone_transform"]); bool remove_positions = bool(p_options["retarget/remove_tracks/unimportant_positions"]); - bool remove_unmapped_bones = bool(p_options["retarget/remove_tracks/unmapped_bones"]); + int separate_unmapped_bones = int(p_options["retarget/remove_tracks/unmapped_bones"]); - if (!remove_positions && !remove_unmapped_bones) { + if (!remove_positions && separate_unmapped_bones == 0) { return; } @@ -72,10 +72,16 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back()); List<StringName> anims; ap->get_animation_list(&anims); + + Ref<AnimationLibrary> unmapped_al; + unmapped_al.instantiate(); + for (const StringName &name : anims) { Ref<Animation> anim = ap->get_animation(name); int track_len = anim->get_track_count(); Vector<int> remove_indices; + Vector<int> mapped_bone_indices; + Vector<int> unmapped_bone_indices; for (int i = 0; i < track_len; i++) { String track_path = String(anim->track_get_path(i).get_concatenated_names()); Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); @@ -96,16 +102,19 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate StringName bn = anim->track_get_path(i).get_subname(0); if (bn) { int prof_idx = profile->find_bone(bone_map->find_profile_bone_name(bn)); - if (remove_unmapped_bones && prof_idx < 0) { - remove_indices.push_back(i); + if (prof_idx < 0) { + unmapped_bone_indices.push_back(i); continue; } if (remove_positions && anim->track_get_type(i) == Animation::TYPE_POSITION_3D && prof_idx >= 0) { StringName prof_bn = profile->get_bone_name(prof_idx); if (prof_bn == profile->get_root_bone() || prof_bn == profile->get_scale_base_bone()) { + mapped_bone_indices.push_back(i); continue; } remove_indices.push_back(i); + } else { + mapped_bone_indices.push_back(i); } } } @@ -114,11 +123,34 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate } } + if (separate_unmapped_bones == 2 && !unmapped_bone_indices.is_empty()) { + Ref<Animation> unmapped_anim = anim->duplicate(); + Vector<int> to_delete; + to_delete.append_array(mapped_bone_indices); + to_delete.append_array(remove_indices); + to_delete.sort(); + to_delete.reverse(); + for (int E : to_delete) { + unmapped_anim->remove_track(E); + } + unmapped_al->add_animation(name, unmapped_anim); + } + + if (separate_unmapped_bones >= 1) { + remove_indices.append_array(unmapped_bone_indices); + remove_indices.sort(); + } remove_indices.reverse(); for (int i = 0; i < remove_indices.size(); i++) { anim->remove_track(remove_indices[i]); } } + + if (unmapped_al->get_animation_list_size() == 0) { + unmapped_al.unref(); + } else if (separate_unmapped_bones == 2) { + ap->add_animation_library("unmapped_bones", unmapped_al); + } } } } diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 86af9caf26..75f7d35b1c 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2312,6 +2312,7 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor } } + // TODO: If there are more than 2 or equal get_internal_option_visibility method, visibility state is broken. for (int i = 0; i < post_importer_plugins.size(); i++) { Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), _scene_import_type, p_option, p_options); if (ret.get_type() == Variant::BOOL) { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d3bae447cc..f73867575d 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1995,15 +1995,14 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } } + Transform2D edit_transform; + bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y); + if (using_temp_pivot) { + edit_transform = Transform2D(drag_selection.front()->get()->_edit_get_rotation(), temp_pivot); + } else { + edit_transform = drag_selection.front()->get()->_edit_get_transform(); + } for (CanvasItem *ci : drag_selection) { - Transform2D edit_transform; - bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y); - if (using_temp_pivot) { - edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot); - } else { - edit_transform = ci->_edit_get_transform(); - } - Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse(); Transform2D unscaled_transform = (transform * parent_xform * edit_transform).orthonormalized(); Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp index 573c686d57..b92abbcf79 100644 --- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp @@ -49,17 +49,47 @@ CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() { helper.instantiate(); - const Color gizmo_color = SceneTree::get_singleton()->get_debug_collisions_color(); - create_material("shape_material", gizmo_color); - const float gizmo_value = gizmo_color.get_v(); - const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); - create_material("shape_material_disabled", gizmo_color_disabled); + + create_collision_material("shape_material", 2.0); + create_collision_material("shape_material_arraymesh", 0.0625); + + create_collision_material("shape_material_disabled", 0.0625); + create_collision_material("shape_material_arraymesh_disabled", 0.015625); + create_handle_material("handles"); } CollisionShape3DGizmoPlugin::~CollisionShape3DGizmoPlugin() { } +void CollisionShape3DGizmoPlugin::create_collision_material(const String &p_name, float p_alpha) { + Vector<Ref<StandardMaterial3D>> mats; + + const Color collision_color(1.0, 1.0, 1.0, p_alpha); + + for (int i = 0; i < 4; i++) { + bool instantiated = i < 2; + + Ref<StandardMaterial3D> material = memnew(StandardMaterial3D); + + Color color = collision_color; + color.a *= instantiated ? 0.25 : 1.0; + + material->set_albedo(color); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); + material->set_cull_mode(StandardMaterial3D::CULL_BACK); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + mats.push_back(material); + } + + materials[p_name] = mats; +} + bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<CollisionShape3D>(p_spatial) != nullptr; } @@ -311,9 +341,20 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { return; } - const Ref<Material> material = + const Ref<StandardMaterial3D> material = get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); - Ref<Material> handles_material = get_material("handles"); + const Ref<StandardMaterial3D> material_arraymesh = + get_material(!cs->is_disabled() ? "shape_material_arraymesh" : "shape_material_arraymesh_disabled", p_gizmo); + const Ref<Material> handles_material = get_material("handles"); + + const Color collision_color = cs->is_disabled() ? Color(1.0, 1.0, 1.0, 0.75) : cs->get_debug_color(); + + if (cs->get_debug_fill_enabled()) { + Ref<ArrayMesh> array_mesh = s->get_debug_arraymesh_faces(collision_color); + if (array_mesh.is_valid()) { + p_gizmo->add_mesh(array_mesh, material_arraymesh); + } + } if (Object::cast_to<SphereShape3D>(*s)) { Ref<SphereShape3D> sp = s; @@ -351,7 +392,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { collision_segments.push_back(Vector3(b.x, b.y, 0)); } - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points, material, false, collision_color); p_gizmo->add_collision_segments(collision_segments); Vector<Vector3> handles; handles.push_back(Vector3(r, 0, 0)); @@ -374,7 +415,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { const Vector<Vector3> handles = helper->box_get_handles(bs->get_size()); - p_gizmo->add_lines(lines, material); + p_gizmo->add_lines(lines, material, false, collision_color); p_gizmo->add_collision_segments(lines); p_gizmo->add_handles(handles, handles_material); } @@ -412,7 +453,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { points.push_back(Vector3(b.y, b.x, 0) + dud); } - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points, material, false, collision_color); Vector<Vector3> collision_segments; @@ -476,7 +517,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points, material, false, collision_color); Vector<Vector3> collision_segments; @@ -531,7 +572,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p.normal * p.d + p.normal * 3 }; - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points, material, false, collision_color); p_gizmo->add_collision_segments(points); } @@ -549,7 +590,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; } - p_gizmo->add_lines(lines, material); + p_gizmo->add_lines(lines, material, false, collision_color); p_gizmo->add_collision_segments(lines); } } @@ -558,7 +599,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<ConcavePolygonShape3D>(*s)) { Ref<ConcavePolygonShape3D> cs2 = s; Ref<ArrayMesh> mesh = cs2->get_debug_mesh(); - p_gizmo->add_mesh(mesh, material); + p_gizmo->add_lines(cs2->get_debug_mesh_lines(), material, false, collision_color); p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines()); } @@ -569,7 +610,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector3(), Vector3(0, 0, rs->get_length()) }; - p_gizmo->add_lines(points, material); + p_gizmo->add_lines(points, material, false, collision_color); p_gizmo->add_collision_segments(points); Vector<Vector3> handles; handles.push_back(Vector3(0, 0, rs->get_length())); @@ -579,7 +620,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<HeightMapShape3D>(*s)) { Ref<HeightMapShape3D> hms = s; - Ref<ArrayMesh> mesh = hms->get_debug_mesh(); - p_gizmo->add_mesh(mesh, material); + Vector<Vector3> lines = hms->get_debug_mesh_lines(); + p_gizmo->add_lines(lines, material, false, collision_color); } } diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.h b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.h index 464012acf9..09590fba58 100644 --- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.h +++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.h @@ -38,6 +38,8 @@ class Gizmo3DHelper; class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin); + void create_collision_material(const String &p_name, float p_alpha); + Ref<Gizmo3DHelper> helper; public: diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 8aff3c9aec..b716e925cb 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -292,14 +292,11 @@ void EditorNode3DGizmo::add_vertices(const Vector<Vector3> &p_vertices, const Re Vector<Color> color; color.resize(p_vertices.size()); + const Color vertex_color = (is_selected() ? Color(1, 1, 1, 0.8) : Color(1, 1, 1, 0.2)) * p_modulate; { Color *w = color.ptrw(); for (int i = 0; i < p_vertices.size(); i++) { - if (is_selected()) { - w[i] = Color(1, 1, 1, 0.8) * p_modulate; - } else { - w[i] = Color(1, 1, 1, 0.2) * p_modulate; - } + w[i] = vertex_color; } } diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 68fe013c08..527138e060 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -146,15 +146,15 @@ void VoxelGIEditorPlugin::make_visible(bool p_visible) { EditorProgress *VoxelGIEditorPlugin::tmp_progress = nullptr; -void VoxelGIEditorPlugin::bake_func_begin(int p_steps) { +void VoxelGIEditorPlugin::bake_func_begin() { ERR_FAIL_COND(tmp_progress != nullptr); - tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake VoxelGI"), p_steps)); + tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake VoxelGI"), 1000, true)); } -void VoxelGIEditorPlugin::bake_func_step(int p_step, const String &p_description) { - ERR_FAIL_NULL(tmp_progress); - tmp_progress->step(p_description, p_step, false); +bool VoxelGIEditorPlugin::bake_func_step(int p_progress, const String &p_description) { + ERR_FAIL_NULL_V(tmp_progress, false); + return tmp_progress->step(p_description, p_progress, false); } void VoxelGIEditorPlugin::bake_func_end() { diff --git a/editor/plugins/voxel_gi_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h index d09822dda6..01a2ab4bd1 100644 --- a/editor/plugins/voxel_gi_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -50,8 +50,8 @@ class VoxelGIEditorPlugin : public EditorPlugin { EditorFileDialog *probe_file = nullptr; static EditorProgress *tmp_progress; - static void bake_func_begin(int p_steps); - static void bake_func_step(int p_step, const String &p_description); + static void bake_func_begin(); + static bool bake_func_step(int p_progress, const String &p_description); static void bake_func_end(); void _bake(); diff --git a/methods.py b/methods.py index 203f0dd8a5..be290f8128 100644 --- a/methods.py +++ b/methods.py @@ -102,6 +102,7 @@ def add_source_files_scu(self, sources, files, allow_gen=False): subdir = os.path.dirname(files) subdir = subdir if subdir == "" else subdir + "/" section_name = self.Dir(subdir).tpath + section_name = section_name.replace("\\", "/") # win32 # if the section name is in the hash table? # i.e. is it part of the SCU build? global _scu_folders diff --git a/misc/dist/html/editor.html b/misc/dist/html/editor.html index 3a22055546..4f2a3bc053 100644 --- a/misc/dist/html/editor.html +++ b/misc/dist/html/editor.html @@ -363,24 +363,28 @@ window.addEventListener('load', () => { btn.style.display = ''; } if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('service.worker.js').then(function (reg) { - if (reg.waiting) { - notifyUpdate(reg.waiting); - } - reg.addEventListener('updatefound', function () { - const update = reg.installing; - update.addEventListener('statechange', function () { - if (update.state === 'installed') { - // It's a new install, claim and perform aggressive caching. - if (!reg.active) { - update.postMessage('claim'); - } else { - notifyUpdate(update); + try { + navigator.serviceWorker.register('service.worker.js').then(function (reg) { + if (reg.waiting) { + notifyUpdate(reg.waiting); + } + reg.addEventListener('updatefound', function () { + const update = reg.installing; + update.addEventListener('statechange', function () { + if (update.state === 'installed') { + // It's a new install, claim and perform aggressive caching. + if (!reg.active) { + update.postMessage('claim'); + } else { + notifyUpdate(update); + } } - } + }); }); }); - }); + } catch (e) { + console.error('Error while registering service worker:', e); + } } const missing = Engine.getMissingFeatures({ diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index b59c417d36..3d68b66f49 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -152,9 +152,15 @@ const engine = new Engine(GODOT_CONFIG); if (missing.length !== 0) { if (GODOT_CONFIG['serviceWorker'] && GODOT_CONFIG['ensureCrossOriginIsolationHeaders'] && 'serviceWorker' in navigator) { + let serviceWorkerRegistrationPromise; + try { + serviceWorkerRegistrationPromise = navigator.serviceWorker.getRegistration(); + } catch (err) { + serviceWorkerRegistrationPromise = Promise.reject(new Error('Service worker registration failed.')); + } // There's a chance that installing the service worker would fix the issue Promise.race([ - navigator.serviceWorker.getRegistration().then((registration) => { + serviceWorkerRegistrationPromise.then((registration) => { if (registration != null) { return Promise.reject(new Error('Service worker already exists.')); } diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index 8579baf876..463d207e59 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -4,15 +4,18 @@ func test(): print(-1.25, 0.25, 1.25) print("hello world") - print(Vector2(0.25, 0.25)) + print(Vector2(0.25, 1)) print(Vector2i(0, 0)) - print(Rect2(0.25, 0.25, 0.5, 0.5)) + print(Rect2(0.25, 0.25, 0.5, 1)) print(Rect2i(0, 0, 0, 0)) - print(Vector3(0.25, 0.25, 0.25)) + print(Vector3(0.25, 0.25, 1)) print(Vector3i(0, 0, 0)) + print(Vector4(0.25, 0.25, 0.25, 1)) + print(Vector4i(0, 0, 0, 0)) + print(Transform2D.IDENTITY) print(Plane(1, 2, 3, 4)) print(Quaternion(1, 2, 3, 4)) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out index 2463d70ef4..9983366db0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.out +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out @@ -3,12 +3,14 @@ truefalse -101 -1.250.251.25 hello world -(0.25, 0.25) +(0.25, 1.0) (0, 0) -[P: (0.25, 0.25), S: (0.5, 0.5)] +[P: (0.25, 0.25), S: (0.5, 1.0)] [P: (0, 0), S: (0, 0)] -(0.25, 0.25, 0.25) +(0.25, 0.25, 1.0) (0, 0, 0) +(0.25, 0.25, 0.25, 1.0) +(0, 0, 0, 0) [X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)] [N: (1.0, 2.0, 3.0), D: 4] (1, 2, 3, 4) @@ -32,4 +34,4 @@ Node::[signal]property_list_changed [(1.0, 1.0), (0.0, 0.0)] [(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)] [(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)] -[(1, 1, 1, 1), (0, 0, 0, 0)] +[(1.0, 1.0, 1.0, 1.0), (0.0, 0.0, 0.0, 0.0)] diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub index 6657d75cae..ba4a842cb6 100644 --- a/modules/upnp/SCsub +++ b/modules/upnp/SCsub @@ -10,7 +10,7 @@ env_upnp = env_modules.Clone() thirdparty_obj = [] -if env["builtin_miniupnpc"]: +if env["builtin_miniupnpc"] and env["platform"] != "web": thirdparty_dir = "#thirdparty/miniupnpc/" thirdparty_sources = [ "igd_desc_parse.c", diff --git a/modules/upnp/register_types.cpp b/modules/upnp/register_types.cpp index f6a34837a2..fdf39c0b33 100644 --- a/modules/upnp/register_types.cpp +++ b/modules/upnp/register_types.cpp @@ -33,6 +33,11 @@ #include "upnp.h" #include "upnp_device.h" +#ifndef WEB_ENABLED +#include "upnp_device_miniupnp.h" +#include "upnp_miniupnp.h" +#endif + #include "core/error/error_macros.h" void initialize_upnp_module(ModuleInitializationLevel p_level) { @@ -40,8 +45,13 @@ void initialize_upnp_module(ModuleInitializationLevel p_level) { return; } - GDREGISTER_CLASS(UPNP); - GDREGISTER_CLASS(UPNPDevice); + ClassDB::register_custom_instance_class<UPNP>(); + ClassDB::register_custom_instance_class<UPNPDevice>(); + +#ifndef WEB_ENABLED + UPNPMiniUPNP::make_default(); + UPNPDeviceMiniUPNP::make_default(); +#endif } void uninitialize_upnp_module(ModuleInitializationLevel p_level) { diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index 4305bf842a..5ec0b984fc 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -30,298 +30,7 @@ #include "upnp.h" -#include <miniwget.h> -#include <upnpcommands.h> - -#include <stdlib.h> - -bool UPNP::is_common_device(const String &dev) const { - return dev.is_empty() || - dev.contains("InternetGatewayDevice") || - dev.contains("WANIPConnection") || - dev.contains("WANPPPConnection") || - dev.contains("rootdevice"); -} - -int UPNP::discover(int timeout, int ttl, const String &device_filter) { - ERR_FAIL_COND_V_MSG(timeout < 0, UPNP_RESULT_INVALID_PARAM, "The response's wait time can't be negative."); - ERR_FAIL_COND_V_MSG(ttl < 0 || ttl > 255, UPNP_RESULT_INVALID_PARAM, "The time-to-live must be set between 0 and 255 (inclusive)."); - - devices.clear(); - - int error = 0; - struct UPNPDev *devlist; - - CharString cs = discover_multicast_if.utf8(); - const char *m_if = cs.length() ? cs.get_data() : nullptr; - if (is_common_device(device_filter)) { - devlist = upnpDiscover(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); - } else { - devlist = upnpDiscoverAll(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); - } - - if (error != UPNPDISCOVER_SUCCESS) { - switch (error) { - case UPNPDISCOVER_SOCKET_ERROR: - return UPNP_RESULT_SOCKET_ERROR; - case UPNPDISCOVER_MEMORY_ERROR: - return UPNP_RESULT_MEM_ALLOC_ERROR; - default: - return UPNP_RESULT_UNKNOWN_ERROR; - } - } - - if (!devlist) { - return UPNP_RESULT_NO_DEVICES; - } - - struct UPNPDev *dev = devlist; - - while (dev) { - if (device_filter.is_empty() || strstr(dev->st, device_filter.utf8().get_data())) { - add_device_to_list(dev, devlist); - } - - dev = dev->pNext; - } - - freeUPNPDevlist(devlist); - - return UPNP_RESULT_SUCCESS; -} - -void UPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) { - Ref<UPNPDevice> new_device; - new_device.instantiate(); - - new_device->set_description_url(dev->descURL); - new_device->set_service_type(dev->st); - - parse_igd(new_device, devlist); - - devices.push_back(new_device); -} - -char *UPNP::load_description(const String &url, int *size, int *status_code) const { - return (char *)miniwget(url.utf8().get_data(), size, 0, status_code); -} - -void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) { - int size = 0; - int status_code = -1; - char *xml = load_description(dev->get_description_url(), &size, &status_code); - - if (status_code != 200) { - dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_ERROR); - return; - } - - if (!xml || size < 1) { - dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_EMPTY); - return; - } - - struct UPNPUrls urls = {}; - struct IGDdatas data; - - parserootdesc(xml, size, &data); - free(xml); - xml = nullptr; - - GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0); - - char addr[16]; -#if MINIUPNPC_API_VERSION >= 18 - int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16, nullptr, 0); -#else - int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16); -#endif - - if (i != 1) { - FreeUPNPUrls(&urls); - - switch (i) { - case 0: - dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_IGD); - return; - case 2: - dev->set_igd_status(UPNPDevice::IGD_STATUS_DISCONNECTED); - return; - case 3: - dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_DEVICE); - return; - default: - dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_ERROR); - return; - } - } - - if (urls.controlURL[0] == '\0') { - FreeUPNPUrls(&urls); - dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL); - return; - } - - dev->set_igd_control_url(urls.controlURL); - dev->set_igd_service_type(data.first.servicetype); - dev->set_igd_our_addr(addr); - dev->set_igd_status(UPNPDevice::IGD_STATUS_OK); - - FreeUPNPUrls(&urls); -} - -int UPNP::upnp_result(int in) { - switch (in) { - case UPNPCOMMAND_SUCCESS: - return UPNP_RESULT_SUCCESS; - case UPNPCOMMAND_UNKNOWN_ERROR: - return UPNP_RESULT_UNKNOWN_ERROR; - case UPNPCOMMAND_INVALID_ARGS: - return UPNP_RESULT_INVALID_ARGS; - case UPNPCOMMAND_HTTP_ERROR: - return UPNP_RESULT_HTTP_ERROR; - case UPNPCOMMAND_INVALID_RESPONSE: - return UPNP_RESULT_INVALID_RESPONSE; - case UPNPCOMMAND_MEM_ALLOC_ERROR: - return UPNP_RESULT_MEM_ALLOC_ERROR; - - case 402: - return UPNP_RESULT_INVALID_ARGS; - case 403: - return UPNP_RESULT_NOT_AUTHORIZED; - case 501: - return UPNP_RESULT_ACTION_FAILED; - case 606: - return UPNP_RESULT_NOT_AUTHORIZED; - case 714: - return UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY; - case 715: - return UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED; - case 716: - return UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED; - case 718: - return UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING; - case 724: - return UPNP_RESULT_SAME_PORT_VALUES_REQUIRED; - case 725: - return UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED; - case 726: - return UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD; - case 727: - return UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD; - case 728: - return UPNP_RESULT_NO_PORT_MAPS_AVAILABLE; - case 729: - return UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM; - case 732: - return UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED; - case 733: - return UPNP_RESULT_INCONSISTENT_PARAMETERS; - } - - return UPNP_RESULT_UNKNOWN_ERROR; -} - -int UPNP::get_device_count() const { - return devices.size(); -} - -Ref<UPNPDevice> UPNP::get_device(int index) const { - ERR_FAIL_INDEX_V(index, devices.size(), nullptr); - - return devices.get(index); -} - -void UPNP::add_device(Ref<UPNPDevice> device) { - ERR_FAIL_COND(device.is_null()); - - devices.push_back(device); -} - -void UPNP::set_device(int index, Ref<UPNPDevice> device) { - ERR_FAIL_INDEX(index, devices.size()); - ERR_FAIL_COND(device.is_null()); - - devices.set(index, device); -} - -void UPNP::remove_device(int index) { - ERR_FAIL_INDEX(index, devices.size()); - - devices.remove_at(index); -} - -void UPNP::clear_devices() { - devices.clear(); -} - -Ref<UPNPDevice> UPNP::get_gateway() const { - ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices."); - - for (int i = 0; i < devices.size(); i++) { - Ref<UPNPDevice> dev = get_device(i); - - if (dev.is_valid() && dev->is_valid_gateway()) { - return dev; - } - } - - return nullptr; -} - -void UPNP::set_discover_multicast_if(const String &m_if) { - discover_multicast_if = m_if; -} - -String UPNP::get_discover_multicast_if() const { - return discover_multicast_if; -} - -void UPNP::set_discover_local_port(int port) { - discover_local_port = port; -} - -int UPNP::get_discover_local_port() const { - return discover_local_port; -} - -void UPNP::set_discover_ipv6(bool ipv6) { - discover_ipv6 = ipv6; -} - -bool UPNP::is_discover_ipv6() const { - return discover_ipv6; -} - -String UPNP::query_external_address() const { - Ref<UPNPDevice> dev = get_gateway(); - - if (dev.is_null()) { - return ""; - } - - return dev->query_external_address(); -} - -int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { - Ref<UPNPDevice> dev = get_gateway(); - - if (dev.is_null()) { - return UPNP_RESULT_NO_GATEWAY; - } - - return dev->add_port_mapping(port, port_internal, desc, proto, duration); -} - -int UPNP::delete_port_mapping(int port, String proto) const { - Ref<UPNPDevice> dev = get_gateway(); - - if (dev.is_null()) { - return UPNP_RESULT_NO_GATEWAY; - } - - return dev->delete_port_mapping(port, proto); -} +UPNP *(*UPNP::_create)(bool p_notify_postinitialize) = nullptr; void UPNP::_bind_methods() { ClassDB::bind_method(D_METHOD("get_device_count"), &UPNP::get_device_count); @@ -382,9 +91,3 @@ void UPNP::_bind_methods() { BIND_ENUM_CONSTANT(UPNP_RESULT_NO_DEVICES); BIND_ENUM_CONSTANT(UPNP_RESULT_UNKNOWN_ERROR); } - -UPNP::UPNP() { -} - -UPNP::~UPNP() { -} diff --git a/modules/upnp/upnp.h b/modules/upnp/upnp.h index dc9bbdbc22..566b01ecdc 100644 --- a/modules/upnp/upnp.h +++ b/modules/upnp/upnp.h @@ -35,26 +35,14 @@ #include "core/object/ref_counted.h" -#include <miniupnpc.h> - class UPNP : public RefCounted { GDCLASS(UPNP, RefCounted); -private: - String discover_multicast_if = ""; - int discover_local_port = 0; - bool discover_ipv6 = false; - - Vector<Ref<UPNPDevice>> devices; - - bool is_common_device(const String &dev) const; - void add_device_to_list(UPNPDev *dev, UPNPDev *devlist); - void parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist); - char *load_description(const String &url, int *size, int *status_code) const; - protected: static void _bind_methods(); + static UPNP *(*_create)(bool p_notify_postinitialize); + public: enum UPNPResult { UPNP_RESULT_SUCCESS, @@ -88,35 +76,40 @@ public: UPNP_RESULT_UNKNOWN_ERROR, }; - static int upnp_result(int in); + static UPNP *create(bool p_notify_postinitialize = true) { + if (!_create) { + return nullptr; + } + return _create(p_notify_postinitialize); + } - int get_device_count() const; - Ref<UPNPDevice> get_device(int index) const; - void add_device(Ref<UPNPDevice> device); - void set_device(int index, Ref<UPNPDevice> device); - void remove_device(int index); - void clear_devices(); + virtual int get_device_count() const = 0; + virtual Ref<UPNPDevice> get_device(int index) const = 0; + virtual void add_device(Ref<UPNPDevice> device) = 0; + virtual void set_device(int index, Ref<UPNPDevice> device) = 0; + virtual void remove_device(int index) = 0; + virtual void clear_devices() = 0; - Ref<UPNPDevice> get_gateway() const; + virtual Ref<UPNPDevice> get_gateway() const = 0; - int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice"); + virtual int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice") = 0; - String query_external_address() const; + virtual String query_external_address() const = 0; - int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const; - int delete_port_mapping(int port, String proto = "UDP") const; + virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const = 0; + virtual int delete_port_mapping(int port, String proto = "UDP") const = 0; - void set_discover_multicast_if(const String &m_if); - String get_discover_multicast_if() const; + virtual void set_discover_multicast_if(const String &m_if) = 0; + virtual String get_discover_multicast_if() const = 0; - void set_discover_local_port(int port); - int get_discover_local_port() const; + virtual void set_discover_local_port(int port) = 0; + virtual int get_discover_local_port() const = 0; - void set_discover_ipv6(bool ipv6); - bool is_discover_ipv6() const; + virtual void set_discover_ipv6(bool ipv6) = 0; + virtual bool is_discover_ipv6() const = 0; - UPNP(); - ~UPNP(); + UPNP() {} + virtual ~UPNP() {} }; VARIANT_ENUM_CAST(UPNP::UPNPResult) diff --git a/modules/upnp/upnp_device.cpp b/modules/upnp/upnp_device.cpp index 11ee3681af..45766281f1 100644 --- a/modules/upnp/upnp_device.cpp +++ b/modules/upnp/upnp_device.cpp @@ -30,119 +30,7 @@ #include "upnp_device.h" -#include "upnp.h" - -#include <upnpcommands.h> - -String UPNPDevice::query_external_address() const { - ERR_FAIL_COND_V_MSG(!is_valid_gateway(), "", "The Internet Gateway Device must be valid."); - - char addr[16]; - int i = UPNP_GetExternalIPAddress( - igd_control_url.utf8().get_data(), - igd_service_type.utf8().get_data(), - (char *)&addr); - - ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, "", "Couldn't get external IP address."); - - return String(addr); -} - -int UPNPDevice::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { - ERR_FAIL_COND_V_MSG(!is_valid_gateway(), UPNP::UPNP_RESULT_INVALID_GATEWAY, "The Internet Gateway Device must be valid."); - ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive)."); - ERR_FAIL_COND_V_MSG(port_internal < 0 || port_internal > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 0 and 65535 (inclusive)."); // Needs to allow 0 because 0 signifies "use external port as internal port" - ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP."); - ERR_FAIL_COND_V_MSG(duration < 0, UPNP::UPNP_RESULT_INVALID_DURATION, "The port mapping's lease duration can't be negative."); - - if (port_internal < 1) { - port_internal = port; - } - - int i = UPNP_AddPortMapping( - igd_control_url.utf8().get_data(), - igd_service_type.utf8().get_data(), - itos(port).utf8().get_data(), - itos(port_internal).utf8().get_data(), - igd_our_addr.utf8().get_data(), - desc.is_empty() ? nullptr : desc.utf8().get_data(), - proto.utf8().get_data(), - nullptr, // Remote host, always nullptr as IGDs don't support it - duration > 0 ? itos(duration).utf8().get_data() : nullptr); - - ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i), "Couldn't add port mapping."); - - return UPNP::UPNP_RESULT_SUCCESS; -} - -int UPNPDevice::delete_port_mapping(int port, String proto) const { - ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive)."); - ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP."); - - int i = UPNP_DeletePortMapping( - igd_control_url.utf8().get_data(), - igd_service_type.utf8().get_data(), - itos(port).utf8().get_data(), - proto.utf8().get_data(), - nullptr // Remote host, always nullptr as IGDs don't support it - ); - - ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i), "Couldn't delete port mapping."); - - return UPNP::UPNP_RESULT_SUCCESS; -} - -void UPNPDevice::set_description_url(const String &url) { - description_url = url; -} - -String UPNPDevice::get_description_url() const { - return description_url; -} - -void UPNPDevice::set_service_type(const String &type) { - service_type = type; -} - -String UPNPDevice::get_service_type() const { - return service_type; -} - -void UPNPDevice::set_igd_control_url(const String &url) { - igd_control_url = url; -} - -String UPNPDevice::get_igd_control_url() const { - return igd_control_url; -} - -void UPNPDevice::set_igd_service_type(const String &type) { - igd_service_type = type; -} - -String UPNPDevice::get_igd_service_type() const { - return igd_service_type; -} - -void UPNPDevice::set_igd_our_addr(const String &addr) { - igd_our_addr = addr; -} - -String UPNPDevice::get_igd_our_addr() const { - return igd_our_addr; -} - -void UPNPDevice::set_igd_status(IGDStatus status) { - igd_status = status; -} - -UPNPDevice::IGDStatus UPNPDevice::get_igd_status() const { - return igd_status; -} - -bool UPNPDevice::is_valid_gateway() const { - return igd_status == IGD_STATUS_OK; -} +UPNPDevice *(*UPNPDevice::_create)(bool p_notify_postinitialize) = nullptr; void UPNPDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("is_valid_gateway"), &UPNPDevice::is_valid_gateway); @@ -185,15 +73,3 @@ void UPNPDevice::_bind_methods() { BIND_ENUM_CONSTANT(IGD_STATUS_MALLOC_ERROR); BIND_ENUM_CONSTANT(IGD_STATUS_UNKNOWN_ERROR); } - -UPNPDevice::UPNPDevice() { - description_url = ""; - service_type = ""; - igd_control_url = ""; - igd_service_type = ""; - igd_our_addr = ""; - igd_status = IGD_STATUS_UNKNOWN_ERROR; -} - -UPNPDevice::~UPNPDevice() { -} diff --git a/modules/upnp/upnp_device.h b/modules/upnp/upnp_device.h index a49e574890..fdc5bab110 100644 --- a/modules/upnp/upnp_device.h +++ b/modules/upnp/upnp_device.h @@ -36,6 +36,11 @@ class UPNPDevice : public RefCounted { GDCLASS(UPNPDevice, RefCounted); +protected: + static void _bind_methods(); + + static UPNPDevice *(*_create)(bool p_notify_postinitialize); + public: enum IGDStatus { IGD_STATUS_OK, @@ -50,42 +55,38 @@ public: IGD_STATUS_UNKNOWN_ERROR, }; - void set_description_url(const String &url); - String get_description_url() const; + static UPNPDevice *create(bool p_notify_postinitialize = true) { + if (!_create) { + return nullptr; + } + return _create(p_notify_postinitialize); + } - void set_service_type(const String &type); - String get_service_type() const; + virtual void set_description_url(const String &url) = 0; + virtual String get_description_url() const = 0; - void set_igd_control_url(const String &url); - String get_igd_control_url() const; + virtual void set_service_type(const String &type) = 0; + virtual String get_service_type() const = 0; - void set_igd_service_type(const String &type); - String get_igd_service_type() const; + virtual void set_igd_control_url(const String &url) = 0; + virtual String get_igd_control_url() const = 0; - void set_igd_our_addr(const String &addr); - String get_igd_our_addr() const; + virtual void set_igd_service_type(const String &type) = 0; + virtual String get_igd_service_type() const = 0; - void set_igd_status(IGDStatus status); - IGDStatus get_igd_status() const; + virtual void set_igd_our_addr(const String &addr) = 0; + virtual String get_igd_our_addr() const = 0; - bool is_valid_gateway() const; - String query_external_address() const; - int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const; - int delete_port_mapping(int port, String proto = "UDP") const; + virtual void set_igd_status(IGDStatus status) = 0; + virtual IGDStatus get_igd_status() const = 0; - UPNPDevice(); - ~UPNPDevice(); - -protected: - static void _bind_methods(); + virtual bool is_valid_gateway() const = 0; + virtual String query_external_address() const = 0; + virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const = 0; + virtual int delete_port_mapping(int port, String proto = "UDP") const = 0; -private: - String description_url; - String service_type; - String igd_control_url; - String igd_service_type; - String igd_our_addr; - IGDStatus igd_status; + UPNPDevice() {} + virtual ~UPNPDevice() {} }; VARIANT_ENUM_CAST(UPNPDevice::IGDStatus) diff --git a/modules/upnp/upnp_device_miniupnp.cpp b/modules/upnp/upnp_device_miniupnp.cpp new file mode 100644 index 0000000000..46319f83d3 --- /dev/null +++ b/modules/upnp/upnp_device_miniupnp.cpp @@ -0,0 +1,153 @@ +/**************************************************************************/ +/* upnp_device_miniupnp.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef WEB_ENABLED + +#include "upnp_device_miniupnp.h" + +#include "upnp_miniupnp.h" + +#include <upnpcommands.h> + +void UPNPDeviceMiniUPNP::make_default() { + UPNPDevice::_create = UPNPDeviceMiniUPNP::_create; +} + +String UPNPDeviceMiniUPNP::query_external_address() const { + ERR_FAIL_COND_V_MSG(!is_valid_gateway(), "", "The Internet Gateway Device must be valid."); + + char addr[16]; + int i = UPNP_GetExternalIPAddress( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + (char *)&addr); + + ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, "", "Couldn't get external IP address."); + + return String(addr); +} + +int UPNPDeviceMiniUPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { + ERR_FAIL_COND_V_MSG(!is_valid_gateway(), UPNP::UPNP_RESULT_INVALID_GATEWAY, "The Internet Gateway Device must be valid."); + ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive)."); + ERR_FAIL_COND_V_MSG(port_internal < 0 || port_internal > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 0 and 65535 (inclusive)."); // Needs to allow 0 because 0 signifies "use external port as internal port" + ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP."); + ERR_FAIL_COND_V_MSG(duration < 0, UPNP::UPNP_RESULT_INVALID_DURATION, "The port mapping's lease duration can't be negative."); + + if (port_internal < 1) { + port_internal = port; + } + + int i = UPNP_AddPortMapping( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + itos(port).utf8().get_data(), + itos(port_internal).utf8().get_data(), + igd_our_addr.utf8().get_data(), + desc.is_empty() ? nullptr : desc.utf8().get_data(), + proto.utf8().get_data(), + nullptr, // Remote host, always nullptr as IGDs don't support it + duration > 0 ? itos(duration).utf8().get_data() : nullptr); + + ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNPMiniUPNP::upnp_result(i), "Couldn't add port mapping."); + + return UPNP::UPNP_RESULT_SUCCESS; +} + +int UPNPDeviceMiniUPNP::delete_port_mapping(int port, String proto) const { + ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive)."); + ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP."); + + int i = UPNP_DeletePortMapping( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + itos(port).utf8().get_data(), + proto.utf8().get_data(), + nullptr // Remote host, always nullptr as IGDs don't support it + ); + + ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNPMiniUPNP::upnp_result(i), "Couldn't delete port mapping."); + + return UPNP::UPNP_RESULT_SUCCESS; +} + +void UPNPDeviceMiniUPNP::set_description_url(const String &url) { + description_url = url; +} + +String UPNPDeviceMiniUPNP::get_description_url() const { + return description_url; +} + +void UPNPDeviceMiniUPNP::set_service_type(const String &type) { + service_type = type; +} + +String UPNPDeviceMiniUPNP::get_service_type() const { + return service_type; +} + +void UPNPDeviceMiniUPNP::set_igd_control_url(const String &url) { + igd_control_url = url; +} + +String UPNPDeviceMiniUPNP::get_igd_control_url() const { + return igd_control_url; +} + +void UPNPDeviceMiniUPNP::set_igd_service_type(const String &type) { + igd_service_type = type; +} + +String UPNPDeviceMiniUPNP::get_igd_service_type() const { + return igd_service_type; +} + +void UPNPDeviceMiniUPNP::set_igd_our_addr(const String &addr) { + igd_our_addr = addr; +} + +String UPNPDeviceMiniUPNP::get_igd_our_addr() const { + return igd_our_addr; +} + +void UPNPDeviceMiniUPNP::set_igd_status(IGDStatus status) { + igd_status = status; +} + +UPNPDeviceMiniUPNP::IGDStatus UPNPDeviceMiniUPNP::get_igd_status() const { + return igd_status; +} + +bool UPNPDeviceMiniUPNP::is_valid_gateway() const { + return igd_status == IGD_STATUS_OK; +} + +#endif // WEB_ENABLED diff --git a/modules/upnp/upnp_device_miniupnp.h b/modules/upnp/upnp_device_miniupnp.h new file mode 100644 index 0000000000..bea3b1d542 --- /dev/null +++ b/modules/upnp/upnp_device_miniupnp.h @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* upnp_device_miniupnp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef UPNP_DEVICE_MINIUPNP_H +#define UPNP_DEVICE_MINIUPNP_H + +#ifndef WEB_ENABLED + +#include "upnp_device.h" + +class UPNPDeviceMiniUPNP : public UPNPDevice { + GDCLASS(UPNPDeviceMiniUPNP, UPNPDevice); + +private: + static UPNPDevice *_create(bool p_notify_postinitialize) { return static_cast<UPNPDevice *>(ClassDB::creator<UPNPDeviceMiniUPNP>(p_notify_postinitialize)); } + + String description_url; + String service_type; + String igd_control_url; + String igd_service_type; + String igd_our_addr; + IGDStatus igd_status = IGD_STATUS_UNKNOWN_ERROR; + +public: + static void make_default(); + + virtual void set_description_url(const String &url) override; + virtual String get_description_url() const override; + + virtual void set_service_type(const String &type) override; + virtual String get_service_type() const override; + + virtual void set_igd_control_url(const String &url) override; + virtual String get_igd_control_url() const override; + + virtual void set_igd_service_type(const String &type) override; + virtual String get_igd_service_type() const override; + + virtual void set_igd_our_addr(const String &addr) override; + virtual String get_igd_our_addr() const override; + + virtual void set_igd_status(IGDStatus status) override; + virtual IGDStatus get_igd_status() const override; + + virtual bool is_valid_gateway() const override; + virtual String query_external_address() const override; + virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const override; + virtual int delete_port_mapping(int port, String proto = "UDP") const override; + + UPNPDeviceMiniUPNP() {} + virtual ~UPNPDeviceMiniUPNP() {} +}; + +#endif // WEB_ENABLED + +#endif // UPNP_DEVICE_MINIUPNP_H diff --git a/modules/upnp/upnp_miniupnp.cpp b/modules/upnp/upnp_miniupnp.cpp new file mode 100644 index 0000000000..0714d56a08 --- /dev/null +++ b/modules/upnp/upnp_miniupnp.cpp @@ -0,0 +1,334 @@ +/**************************************************************************/ +/* upnp_miniupnp.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef WEB_ENABLED + +#include "upnp_miniupnp.h" + +#include "upnp_device_miniupnp.h" + +#include <miniwget.h> +#include <upnpcommands.h> + +#include <stdlib.h> + +void UPNPMiniUPNP::make_default() { + UPNP::_create = UPNPMiniUPNP::_create; +} + +bool UPNPMiniUPNP::is_common_device(const String &dev) const { + return dev.is_empty() || + dev.contains("InternetGatewayDevice") || + dev.contains("WANIPConnection") || + dev.contains("WANPPPConnection") || + dev.contains("rootdevice"); +} + +int UPNPMiniUPNP::discover(int timeout, int ttl, const String &device_filter) { + ERR_FAIL_COND_V_MSG(timeout < 0, UPNP_RESULT_INVALID_PARAM, "The response's wait time can't be negative."); + ERR_FAIL_COND_V_MSG(ttl < 0 || ttl > 255, UPNP_RESULT_INVALID_PARAM, "The time-to-live must be set between 0 and 255 (inclusive)."); + + devices.clear(); + + int error = 0; + struct UPNPDev *devlist; + + CharString cs = discover_multicast_if.utf8(); + const char *m_if = cs.length() ? cs.get_data() : nullptr; + if (is_common_device(device_filter)) { + devlist = upnpDiscover(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); + } else { + devlist = upnpDiscoverAll(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); + } + + if (error != UPNPDISCOVER_SUCCESS) { + switch (error) { + case UPNPDISCOVER_SOCKET_ERROR: + return UPNP_RESULT_SOCKET_ERROR; + case UPNPDISCOVER_MEMORY_ERROR: + return UPNP_RESULT_MEM_ALLOC_ERROR; + default: + return UPNP_RESULT_UNKNOWN_ERROR; + } + } + + if (!devlist) { + return UPNP_RESULT_NO_DEVICES; + } + + struct UPNPDev *dev = devlist; + + while (dev) { + if (device_filter.is_empty() || strstr(dev->st, device_filter.utf8().get_data())) { + add_device_to_list(dev, devlist); + } + + dev = dev->pNext; + } + + freeUPNPDevlist(devlist); + + return UPNP_RESULT_SUCCESS; +} + +void UPNPMiniUPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) { + Ref<UPNPDeviceMiniUPNP> new_device; + new_device.instantiate(); + + new_device->set_description_url(dev->descURL); + new_device->set_service_type(dev->st); + + parse_igd(new_device, devlist); + + devices.push_back(new_device); +} + +char *UPNPMiniUPNP::load_description(const String &url, int *size, int *status_code) const { + return (char *)miniwget(url.utf8().get_data(), size, 0, status_code); +} + +void UPNPMiniUPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) { + int size = 0; + int status_code = -1; + char *xml = load_description(dev->get_description_url(), &size, &status_code); + + if (status_code != 200) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_ERROR); + return; + } + + if (!xml || size < 1) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_EMPTY); + return; + } + + struct UPNPUrls urls = {}; + struct IGDdatas data; + + parserootdesc(xml, size, &data); + free(xml); + xml = nullptr; + + GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0); + + char addr[16]; +#if MINIUPNPC_API_VERSION >= 18 + int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16, nullptr, 0); +#else + int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16); +#endif + + if (i != 1) { + FreeUPNPUrls(&urls); + + switch (i) { + case 0: + dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_IGD); + return; + case 2: + dev->set_igd_status(UPNPDevice::IGD_STATUS_DISCONNECTED); + return; + case 3: + dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_DEVICE); + return; + default: + dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_ERROR); + return; + } + } + + if (urls.controlURL[0] == '\0') { + FreeUPNPUrls(&urls); + dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL); + return; + } + + dev->set_igd_control_url(urls.controlURL); + dev->set_igd_service_type(data.first.servicetype); + dev->set_igd_our_addr(addr); + dev->set_igd_status(UPNPDevice::IGD_STATUS_OK); + + FreeUPNPUrls(&urls); +} + +int UPNPMiniUPNP::upnp_result(int in) { + switch (in) { + case UPNPCOMMAND_SUCCESS: + return UPNP_RESULT_SUCCESS; + case UPNPCOMMAND_UNKNOWN_ERROR: + return UPNP_RESULT_UNKNOWN_ERROR; + case UPNPCOMMAND_INVALID_ARGS: + return UPNP_RESULT_INVALID_ARGS; + case UPNPCOMMAND_HTTP_ERROR: + return UPNP_RESULT_HTTP_ERROR; + case UPNPCOMMAND_INVALID_RESPONSE: + return UPNP_RESULT_INVALID_RESPONSE; + case UPNPCOMMAND_MEM_ALLOC_ERROR: + return UPNP_RESULT_MEM_ALLOC_ERROR; + + case 402: + return UPNP_RESULT_INVALID_ARGS; + case 403: + return UPNP_RESULT_NOT_AUTHORIZED; + case 501: + return UPNP_RESULT_ACTION_FAILED; + case 606: + return UPNP_RESULT_NOT_AUTHORIZED; + case 714: + return UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY; + case 715: + return UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED; + case 716: + return UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED; + case 718: + return UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING; + case 724: + return UPNP_RESULT_SAME_PORT_VALUES_REQUIRED; + case 725: + return UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED; + case 726: + return UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD; + case 727: + return UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD; + case 728: + return UPNP_RESULT_NO_PORT_MAPS_AVAILABLE; + case 729: + return UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM; + case 732: + return UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED; + case 733: + return UPNP_RESULT_INCONSISTENT_PARAMETERS; + } + + return UPNP_RESULT_UNKNOWN_ERROR; +} + +int UPNPMiniUPNP::get_device_count() const { + return devices.size(); +} + +Ref<UPNPDevice> UPNPMiniUPNP::get_device(int index) const { + ERR_FAIL_INDEX_V(index, devices.size(), nullptr); + + return devices.get(index); +} + +void UPNPMiniUPNP::add_device(Ref<UPNPDevice> device) { + ERR_FAIL_COND(device.is_null()); + + devices.push_back(device); +} + +void UPNPMiniUPNP::set_device(int index, Ref<UPNPDevice> device) { + ERR_FAIL_INDEX(index, devices.size()); + ERR_FAIL_COND(device.is_null()); + + devices.set(index, device); +} + +void UPNPMiniUPNP::remove_device(int index) { + ERR_FAIL_INDEX(index, devices.size()); + + devices.remove_at(index); +} + +void UPNPMiniUPNP::clear_devices() { + devices.clear(); +} + +Ref<UPNPDevice> UPNPMiniUPNP::get_gateway() const { + ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices."); + + for (int i = 0; i < devices.size(); i++) { + Ref<UPNPDevice> dev = get_device(i); + + if (dev.is_valid() && dev->is_valid_gateway()) { + return dev; + } + } + + return nullptr; +} + +void UPNPMiniUPNP::set_discover_multicast_if(const String &m_if) { + discover_multicast_if = m_if; +} + +String UPNPMiniUPNP::get_discover_multicast_if() const { + return discover_multicast_if; +} + +void UPNPMiniUPNP::set_discover_local_port(int port) { + discover_local_port = port; +} + +int UPNPMiniUPNP::get_discover_local_port() const { + return discover_local_port; +} + +void UPNPMiniUPNP::set_discover_ipv6(bool ipv6) { + discover_ipv6 = ipv6; +} + +bool UPNPMiniUPNP::is_discover_ipv6() const { + return discover_ipv6; +} + +String UPNPMiniUPNP::query_external_address() const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev.is_null()) { + return ""; + } + + return dev->query_external_address(); +} + +int UPNPMiniUPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev.is_null()) { + return UPNP_RESULT_NO_GATEWAY; + } + + return dev->add_port_mapping(port, port_internal, desc, proto, duration); +} + +int UPNPMiniUPNP::delete_port_mapping(int port, String proto) const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev.is_null()) { + return UPNP_RESULT_NO_GATEWAY; + } + + return dev->delete_port_mapping(port, proto); +} + +#endif // WEB_ENABLED diff --git a/modules/upnp/upnp_miniupnp.h b/modules/upnp/upnp_miniupnp.h new file mode 100644 index 0000000000..0c7dba9d0b --- /dev/null +++ b/modules/upnp/upnp_miniupnp.h @@ -0,0 +1,93 @@ +/**************************************************************************/ +/* upnp_miniupnp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef UPNP_MINIUPNP_H +#define UPNP_MINIUPNP_H + +#ifndef WEB_ENABLED + +#include "upnp.h" + +#include <miniupnpc.h> + +class UPNPMiniUPNP : public UPNP { + GDCLASS(UPNPMiniUPNP, UPNP); + +private: + static UPNP *_create(bool p_notify_postinitialize) { return static_cast<UPNP *>(ClassDB::creator<UPNPMiniUPNP>(p_notify_postinitialize)); } + + String discover_multicast_if = ""; + int discover_local_port = 0; + bool discover_ipv6 = false; + + Vector<Ref<UPNPDevice>> devices; + + bool is_common_device(const String &dev) const; + void add_device_to_list(UPNPDev *dev, UPNPDev *devlist); + void parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist); + char *load_description(const String &url, int *size, int *status_code) const; + +public: + static void make_default(); + + static int upnp_result(int in); + + virtual int get_device_count() const override; + virtual Ref<UPNPDevice> get_device(int index) const override; + virtual void add_device(Ref<UPNPDevice> device) override; + virtual void set_device(int index, Ref<UPNPDevice> device) override; + virtual void remove_device(int index) override; + virtual void clear_devices() override; + + virtual Ref<UPNPDevice> get_gateway() const override; + + virtual int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice") override; + + virtual String query_external_address() const override; + + virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const override; + virtual int delete_port_mapping(int port, String proto = "UDP") const override; + + virtual void set_discover_multicast_if(const String &m_if) override; + virtual String get_discover_multicast_if() const override; + + virtual void set_discover_local_port(int port) override; + virtual int get_discover_local_port() const override; + + virtual void set_discover_ipv6(bool ipv6) override; + virtual bool is_discover_ipv6() const override; + + UPNPMiniUPNP() {} + virtual ~UPNPMiniUPNP() {} +}; + +#endif // WEB_ENABLED + +#endif // UPNP_MINIUPNP_H diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index e20de99c2d..a7b0879056 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2529,7 +2529,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Check for the bin directory. Ref<DirAccess> da = DirAccess::open(java_sdk_path.path_join("bin"), &errn); if (errn != OK) { - err += TTR("Invalid Java SDK path in Editor Settings."); + err += TTR("Invalid Java SDK path in Editor Settings.") + " "; err += TTR("Missing 'bin' directory!"); err += "\n"; valid = false; @@ -2537,7 +2537,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Check for the `java` command. String java_path = get_java_path(); if (!FileAccess::exists(java_path)) { - err += TTR("Unable to find 'java' command using the Java SDK path."); + err += TTR("Unable to find 'java' command using the Java SDK path.") + " "; err += TTR("Please check the Java SDK directory specified in Editor Settings."); err += "\n"; valid = false; @@ -2554,7 +2554,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Check for the platform-tools directory. Ref<DirAccess> da = DirAccess::open(sdk_path.path_join("platform-tools"), &errn); if (errn != OK) { - err += TTR("Invalid Android SDK path in Editor Settings."); + err += TTR("Invalid Android SDK path in Editor Settings.") + " "; err += TTR("Missing 'platform-tools' directory!"); err += "\n"; valid = false; @@ -2563,7 +2563,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Validate that adb is available. String adb_path = get_adb_path(); if (!FileAccess::exists(adb_path)) { - err += TTR("Unable to find Android SDK platform-tools' adb command."); + err += TTR("Unable to find Android SDK platform-tools' adb command.") + " "; err += TTR("Please check in the Android SDK directory specified in Editor Settings."); err += "\n"; valid = false; @@ -2572,7 +2572,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Check for the build-tools directory. Ref<DirAccess> build_tools_da = DirAccess::open(sdk_path.path_join("build-tools"), &errn); if (errn != OK) { - err += TTR("Invalid Android SDK path in Editor Settings."); + err += TTR("Invalid Android SDK path in Editor Settings.") + " "; err += TTR("Missing 'build-tools' directory!"); err += "\n"; valid = false; @@ -2585,7 +2585,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Validate that apksigner is available. String apksigner_path = get_apksigner_path(target_sdk_version.to_int()); if (!FileAccess::exists(apksigner_path)) { - err += TTR("Unable to find Android SDK build-tools' apksigner command."); + err += TTR("Unable to find Android SDK build-tools' apksigner command.") + " "; err += TTR("Please check in the Android SDK directory specified in Editor Settings."); err += "\n"; valid = false; diff --git a/platform/web/js/engine/engine.js b/platform/web/js/engine/engine.js index 04c4c44c5e..1aeeb62f18 100644 --- a/platform/web/js/engine/engine.js +++ b/platform/web/js/engine/engine.js @@ -241,7 +241,11 @@ const Engine = (function () { */ installServiceWorker: function () { if (this.config.serviceWorker && 'serviceWorker' in navigator) { - return navigator.serviceWorker.register(this.config.serviceWorker); + try { + return navigator.serviceWorker.register(this.config.serviceWorker); + } catch (e) { + return Promise.reject(e); + } } return Promise.resolve(); }, diff --git a/platform/web/js/libs/library_godot_os.js b/platform/web/js/libs/library_godot_os.js index 568212275b..2899d7e45f 100644 --- a/platform/web/js/libs/library_godot_os.js +++ b/platform/web/js/libs/library_godot_os.js @@ -441,8 +441,12 @@ const GodotPWA = { godot_js_pwa_cb__sig: 'vi', godot_js_pwa_cb: function (p_update_cb) { if ('serviceWorker' in navigator) { - const cb = GodotRuntime.get_func(p_update_cb); - navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb)); + try { + const cb = GodotRuntime.get_func(p_update_cb); + navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb)); + } catch (e) { + GodotRuntime.error('Failed to assign PWA callback', e); + } } }, @@ -450,12 +454,17 @@ const GodotPWA = { godot_js_pwa_update__sig: 'i', godot_js_pwa_update: function () { if ('serviceWorker' in navigator && GodotPWA.hasUpdate) { - navigator.serviceWorker.getRegistration().then(function (reg) { - if (!reg || !reg.waiting) { - return; - } - reg.waiting.postMessage('update'); - }); + try { + navigator.serviceWorker.getRegistration().then(function (reg) { + if (!reg || !reg.waiting) { + return; + } + reg.waiting.postMessage('update'); + }); + } catch (e) { + GodotRuntime.error(e); + return 1; + } return 0; } return 1; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index a25b7ea4ca..984c9ae90e 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -817,22 +817,10 @@ double OS_Windows::get_unix_time() const { } void OS_Windows::delay_usec(uint32_t p_usec) const { - constexpr uint32_t tolerance = 1000 + 20; - - uint64_t t0 = get_ticks_usec(); - uint64_t target_time = t0 + p_usec; - - // Calculate sleep duration with a tolerance for fine-tuning. - if (p_usec > tolerance) { - uint32_t coarse_sleep_usec = p_usec - tolerance; - if (coarse_sleep_usec >= 1000) { - Sleep(coarse_sleep_usec / 1000); - } - } - - // Spin-wait until we reach the precise target time. - while (get_ticks_usec() < target_time) { - YieldProcessor(); + if (p_usec < 1000) { + Sleep(1); + } else { + Sleep(p_usec / 1000); } } @@ -1751,7 +1739,7 @@ String OS_Windows::get_stdin_string(int64_t p_buffer_size) { data.resize(p_buffer_size); DWORD count = 0; if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), data.ptrw(), data.size(), &count, nullptr)) { - return String::utf8((const char *)data.ptr(), count); + return String::utf8((const char *)data.ptr(), count).replace("\r\n", "\n").rstrip("\n"); } return String(); diff --git a/scene/3d/look_at_modifier_3d.cpp b/scene/3d/look_at_modifier_3d.cpp index ad33cd420a..04dae61dce 100644 --- a/scene/3d/look_at_modifier_3d.cpp +++ b/scene/3d/look_at_modifier_3d.cpp @@ -33,7 +33,7 @@ void LookAtModifier3D::_validate_property(PropertyInfo &p_property) const { SkeletonModifier3D::_validate_property(p_property); - if (p_property.name == "bone" || p_property.name == "origin_bone") { + if (p_property.name == "bone_name" || p_property.name == "origin_bone_name") { Skeleton3D *skeleton = get_skeleton(); if (skeleton) { p_property.hint = PROPERTY_HINT_ENUM; @@ -49,11 +49,11 @@ void LookAtModifier3D::_validate_property(PropertyInfo &p_property) const { p_property.usage = PROPERTY_USAGE_NONE; } } else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) { - if (p_property.name == "origin_bone") { + if (p_property.name == "origin_bone" || p_property.name == "origin_bone_name") { p_property.usage = PROPERTY_USAGE_NONE; } } else { - if (p_property.name == "origin_external_node" || p_property.name == "origin_bone") { + if (p_property.name == "origin_external_node" || p_property.name == "origin_bone" || p_property.name == "origin_bone_name") { p_property.usage = PROPERTY_USAGE_NONE; } } @@ -75,8 +75,29 @@ PackedStringArray LookAtModifier3D::get_configuration_warnings() const { return warnings; } +void LookAtModifier3D::set_bone_name(const String &p_bone_name) { + bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_bone(sk->find_bone(bone_name)); + } +} + +String LookAtModifier3D::get_bone_name() const { + return bone_name; +} + void LookAtModifier3D::set_bone(int p_bone) { bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (bone <= -1 || bone >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range!"); + bone = -1; + } else { + bone_name = sk->get_bone_name(bone); + } + } } int LookAtModifier3D::get_bone() const { @@ -132,8 +153,29 @@ LookAtModifier3D::OriginFrom LookAtModifier3D::get_origin_from() const { return origin_from; } +void LookAtModifier3D::set_origin_bone_name(const String &p_bone_name) { + origin_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_origin_bone(sk->find_bone(origin_bone_name)); + } +} + +String LookAtModifier3D::get_origin_bone_name() const { + return origin_bone_name; +} + void LookAtModifier3D::set_origin_bone(int p_bone) { origin_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (origin_bone <= -1 || origin_bone >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range!"); + origin_bone = -1; + } else { + origin_bone_name = sk->get_bone_name(origin_bone); + } + } } int LookAtModifier3D::get_origin_bone() const { @@ -330,6 +372,8 @@ void LookAtModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_node"), &LookAtModifier3D::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &LookAtModifier3D::get_target_node); + ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &LookAtModifier3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name"), &LookAtModifier3D::get_bone_name); ClassDB::bind_method(D_METHOD("set_bone", "bone"), &LookAtModifier3D::set_bone); ClassDB::bind_method(D_METHOD("get_bone"), &LookAtModifier3D::get_bone); ClassDB::bind_method(D_METHOD("set_forward_axis", "forward_axis"), &LookAtModifier3D::set_forward_axis); @@ -343,6 +387,8 @@ void LookAtModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_origin_from", "origin_from"), &LookAtModifier3D::set_origin_from); ClassDB::bind_method(D_METHOD("get_origin_from"), &LookAtModifier3D::get_origin_from); + ClassDB::bind_method(D_METHOD("set_origin_bone_name", "bone_name"), &LookAtModifier3D::set_origin_bone_name); + ClassDB::bind_method(D_METHOD("get_origin_bone_name"), &LookAtModifier3D::get_origin_bone_name); ClassDB::bind_method(D_METHOD("set_origin_bone", "bone"), &LookAtModifier3D::set_origin_bone); ClassDB::bind_method(D_METHOD("get_origin_bone"), &LookAtModifier3D::get_origin_bone); ClassDB::bind_method(D_METHOD("set_origin_external_node", "external_node"), &LookAtModifier3D::set_origin_external_node); @@ -397,14 +443,16 @@ void LookAtModifier3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_ENUM, ""), "set_bone", "get_bone"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "bone_name", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_bone", "get_bone"); ADD_PROPERTY(PropertyInfo(Variant::INT, "forward_axis", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z"), "set_forward_axis", "get_forward_axis"); ADD_PROPERTY(PropertyInfo(Variant::INT, "primary_rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z"), "set_primary_rotation_axis", "get_primary_rotation_axis"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_secondary_rotation"), "set_use_secondary_rotation", "is_using_secondary_rotation"); ADD_GROUP("Origin Settings", "origin_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_from", PROPERTY_HINT_ENUM, "Self,SpecificBone,ExternalNode"), "set_origin_from", "get_origin_from"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_bone", PROPERTY_HINT_ENUM, ""), "set_origin_bone", "get_origin_bone"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "origin_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_origin_bone_name", "get_origin_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_origin_bone", "get_origin_bone"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "origin_external_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_origin_external_node", "get_origin_external_node"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "origin_safe_margin", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_origin_safe_margin", "get_origin_safe_margin"); @@ -474,7 +522,7 @@ void LookAtModifier3D::_process_modification() { destination = skeleton->get_bone_pose_rotation(bone); } else { Transform3D origin_tr; - if (origin_from == ORIGIN_FROM_SPECIFIC_BONE && origin_bone < skeleton->get_bone_count()) { + if (origin_from == ORIGIN_FROM_SPECIFIC_BONE && origin_bone >= 0 && origin_bone < skeleton->get_bone_count()) { origin_tr = skeleton->get_global_transform() * skeleton->get_bone_global_pose(origin_bone); } else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) { Node3D *origin_src = Object::cast_to<Node3D>(get_node_or_null(origin_external_node)); @@ -486,7 +534,7 @@ void LookAtModifier3D::_process_modification() { } else { origin_tr = bone_rest_space; } - forward_vector = bone_rest_space.basis.xform_inv((target->get_global_position() - origin_tr.translated_local(origin_offset).origin)); + forward_vector = bone_rest_space.orthonormalized().basis.xform_inv((target->get_global_position() - origin_tr.translated_local(origin_offset).origin)); forward_vector_nrm = forward_vector.normalized(); if (forward_vector_nrm.abs().is_equal_approx(get_vector_from_axis(primary_rotation_axis))) { destination = skeleton->get_bone_pose_rotation(bone); @@ -497,6 +545,8 @@ void LookAtModifier3D::_process_modification() { } // Detect flipping. + bool is_not_max_influence = influence < 1.0; + bool is_flippable = use_angle_limitation || is_not_max_influence; Vector3::Axis current_forward_axis = get_axis_from_bone_axis(forward_axis); if (is_intersecting_axis(prev_forward_vector, forward_vector, current_forward_axis, secondary_rotation_axis) || is_intersecting_axis(prev_forward_vector, forward_vector, primary_rotation_axis, primary_rotation_axis, true) || @@ -504,16 +554,20 @@ void LookAtModifier3D::_process_modification() { (prev_forward_vector != Vector3(0, 0, 0) && forward_vector == Vector3(0, 0, 0)) || (prev_forward_vector == Vector3(0, 0, 0) && forward_vector != Vector3(0, 0, 0))) { init_transition(); - } else if (use_angle_limitation && signbit(prev_forward_vector[secondary_rotation_axis]) != signbit(forward_vector[secondary_rotation_axis])) { + } else if (is_flippable && signbit(prev_forward_vector[secondary_rotation_axis]) != signbit(forward_vector[secondary_rotation_axis])) { // Flipping by angle_limitation can be detected by sign of secondary rotation axes during forward_vector is rotated more than 90 degree from forward_axis (means dot production is negative). Vector3 prev_forward_vector_nrm = forward_vector.normalized(); Vector3 rest_forward_vector = get_vector_from_bone_axis(forward_axis); if (symmetry_limitation) { - if (!Math::is_equal_approx(primary_limit_angle, (float)Math_TAU) && prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && forward_vector_nrm.dot(rest_forward_vector) < 0) { + if ((is_not_max_influence || !Math::is_equal_approx(primary_limit_angle, (float)Math_TAU)) && + prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && + forward_vector_nrm.dot(rest_forward_vector) < 0) { init_transition(); } } else { - if (!Math::is_equal_approx(primary_positive_limit_angle + primary_negative_limit_angle, (float)Math_TAU) && prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && forward_vector_nrm.dot(rest_forward_vector) < 0) { + if ((is_not_max_influence || !Math::is_equal_approx(primary_positive_limit_angle + primary_negative_limit_angle, (float)Math_TAU)) && + prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && + forward_vector_nrm.dot(rest_forward_vector) < 0) { init_transition(); } } @@ -528,7 +582,7 @@ void LookAtModifier3D::_process_modification() { delta = get_physics_process_delta_time(); } remaining = MAX(0, remaining - time_step * delta); - if (use_angle_limitation) { + if (is_flippable) { // Interpolate through the rest same as AnimationTree blending for preventing to penetrate the bone into the body. Quaternion rest = skeleton->get_bone_rest(bone).basis.get_rotation_quaternion(); float weight = Tween::run_equation(transition_type, ease_type, 1 - remaining, 0.0, 1.0, 1.0); diff --git a/scene/3d/look_at_modifier_3d.h b/scene/3d/look_at_modifier_3d.h index 5f3c4e8b1c..9329edf3c9 100644 --- a/scene/3d/look_at_modifier_3d.h +++ b/scene/3d/look_at_modifier_3d.h @@ -54,7 +54,8 @@ public: }; private: - int bone = 0; + String bone_name; + int bone = -1; Vector3 forward_vector; Vector3 forward_vector_nrm; @@ -64,6 +65,7 @@ private: bool use_secondary_rotation = true; OriginFrom origin_from = ORIGIN_FROM_SELF; + String origin_bone_name; int origin_bone = -1; NodePath origin_external_node; @@ -123,6 +125,8 @@ protected: virtual void _process_modification() override; public: + void set_bone_name(const String &p_bone_name); + String get_bone_name() const; void set_bone(int p_bone); int get_bone() const; @@ -135,6 +139,8 @@ public: void set_origin_from(OriginFrom p_origin_from); OriginFrom get_origin_from() const; + void set_origin_bone_name(const String &p_bone_name); + String get_origin_bone_name() const; void set_origin_bone(int p_bone); int get_origin_bone() const; void set_origin_external_node(const NodePath &p_external_node); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 85de85a9a6..cd77a32455 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -841,10 +841,10 @@ void Node3D::reparent(Node *p_parent, bool p_keep_global_transform) { ERR_THREAD_GUARD; if (p_keep_global_transform) { Transform3D temp = get_global_transform(); - Node::reparent(p_parent); + Node::reparent(p_parent, p_keep_global_transform); set_global_transform(temp); } else { - Node::reparent(p_parent); + Node::reparent(p_parent, p_keep_global_transform); } } diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp index 304fa74b06..362c61026b 100644 --- a/scene/3d/physics/collision_shape_3d.cpp +++ b/scene/3d/physics/collision_shape_3d.cpp @@ -81,12 +81,19 @@ void CollisionShape3D::_update_in_shape_owner(bool p_xform_only) { void CollisionShape3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_PARENTED: { +#ifdef DEBUG_ENABLED + if (debug_color == get_placeholder_default_color()) { + debug_color = SceneTree::get_singleton()->get_debug_collisions_color(); + } +#endif // DEBUG_ENABLED + collision_object = Object::cast_to<CollisionObject3D>(get_parent()); if (collision_object) { owner_id = collision_object->create_shape_owner(this); if (shape.is_valid()) { collision_object->shape_owner_add_shape(owner_id, shape); } + _update_in_shape_owner(); } } break; @@ -166,11 +173,26 @@ void CollisionShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape3D::get_shape); ClassDB::bind_method(D_METHOD("set_disabled", "enable"), &CollisionShape3D::set_disabled); ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionShape3D::is_disabled); + ClassDB::bind_method(D_METHOD("make_convex_from_siblings"), &CollisionShape3D::make_convex_from_siblings); ClassDB::set_method_flags("CollisionShape3D", "make_convex_from_siblings", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape3D"), "set_shape", "get_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); + +#ifdef DEBUG_ENABLED + ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &CollisionShape3D::set_debug_color); + ClassDB::bind_method(D_METHOD("get_debug_color"), &CollisionShape3D::get_debug_color); + + ClassDB::bind_method(D_METHOD("set_enable_debug_fill", "enable"), &CollisionShape3D::set_debug_fill_enabled); + ClassDB::bind_method(D_METHOD("get_enable_debug_fill"), &CollisionShape3D::get_debug_fill_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color"); + // Default value depends on a project setting, override for doc generation purposes. + ADD_PROPERTY_DEFAULT("debug_color", get_placeholder_default_color()); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_fill"), "set_enable_debug_fill", "get_enable_debug_fill"); +#endif // DEBUG_ENABLED } void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) { @@ -178,11 +200,27 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) { return; } if (shape.is_valid()) { + shape->disconnect_changed(callable_mp(this, &CollisionShape3D::shape_changed)); shape->disconnect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos)); } shape = p_shape; if (shape.is_valid()) { +#ifdef DEBUG_ENABLED + if (shape->get_debug_color() != get_placeholder_default_color()) { + set_debug_color(shape->get_debug_color()); + set_debug_fill_enabled(shape->get_debug_fill()); + } else if (get_debug_color() != get_placeholder_default_color()) { + shape->set_debug_color(debug_color); + shape->set_debug_fill(debug_fill); + } else { + set_debug_color(SceneTree::get_singleton()->get_debug_collisions_color()); + shape->set_debug_color(SceneTree::get_singleton()->get_debug_collisions_color()); + shape->set_debug_fill(debug_fill); + } +#endif // DEBUG_ENABLED + shape->connect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos)); + shape->connect_changed(callable_mp(this, &CollisionShape3D::shape_changed)); } update_gizmos(); if (collision_object) { @@ -215,6 +253,66 @@ bool CollisionShape3D::is_disabled() const { return disabled; } +#ifdef DEBUG_ENABLED +void CollisionShape3D::set_debug_color(const Color &p_color) { + if (p_color == get_placeholder_default_color()) { + debug_color = SceneTree::get_singleton()->get_debug_collisions_color(); + } else if (debug_color != p_color) { + debug_color = p_color; + + if (shape.is_valid()) { + shape->set_debug_color(p_color); + } + } +} + +Color CollisionShape3D::get_debug_color() const { + return debug_color; +} + +void CollisionShape3D::set_debug_fill_enabled(bool p_enable) { + if (debug_fill == p_enable) { + return; + } + + debug_fill = p_enable; + + if (shape.is_valid()) { + shape->set_debug_fill(p_enable); + } +} + +bool CollisionShape3D::get_debug_fill_enabled() const { + return debug_fill; +} + +bool CollisionShape3D::_property_can_revert(const StringName &p_name) const { + if (p_name == "debug_color") { + return true; + } + return false; +} + +bool CollisionShape3D::_property_get_revert(const StringName &p_name, Variant &r_property) const { + if (p_name == "debug_color") { + r_property = SceneTree::get_singleton()->get_debug_collisions_color(); + return true; + } + return false; +} +#endif // DEBUG_ENABLED + +void CollisionShape3D::shape_changed() { +#ifdef DEBUG_ENABLED + if (shape->get_debug_color() != debug_color) { + set_debug_color(shape->get_debug_color()); + } + if (shape->get_debug_fill() != debug_fill) { + set_debug_fill_enabled(shape->get_debug_fill()); + } +#endif // DEBUG_ENABLED +} + CollisionShape3D::CollisionShape3D() { //indicator = RenderingServer::get_singleton()->mesh_create(); set_notify_local_transform(true); diff --git a/scene/3d/physics/collision_shape_3d.h b/scene/3d/physics/collision_shape_3d.h index 15f6ef73cb..0eaecb9f61 100644 --- a/scene/3d/physics/collision_shape_3d.h +++ b/scene/3d/physics/collision_shape_3d.h @@ -43,6 +43,13 @@ class CollisionShape3D : public Node3D { uint32_t owner_id = 0; CollisionObject3D *collision_object = nullptr; +#ifdef DEBUG_ENABLED + Color debug_color = get_placeholder_default_color(); + bool debug_fill = true; + + static const Color get_placeholder_default_color() { return Color(0.0, 0.0, 0.0, 0.0); } +#endif // DEBUG_ENABLED + #ifndef DISABLE_DEPRECATED void resource_changed(Ref<Resource> res); #endif @@ -55,6 +62,13 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifdef DEBUG_ENABLED + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; +#endif // DEBUG_ENABLED + + void shape_changed(); + public: void make_convex_from_siblings(); @@ -64,6 +78,14 @@ public: void set_disabled(bool p_disabled); bool is_disabled() const; +#ifdef DEBUG_ENABLED + void set_debug_color(const Color &p_color); + Color get_debug_color() const; + + void set_debug_fill_enabled(bool p_enable); + bool get_debug_fill_enabled() const; +#endif // DEBUG_ENABLED + PackedStringArray get_configuration_warnings() const override; CollisionShape3D(); diff --git a/scene/3d/retarget_modifier_3d.cpp b/scene/3d/retarget_modifier_3d.cpp new file mode 100644 index 0000000000..90cc316a56 --- /dev/null +++ b/scene/3d/retarget_modifier_3d.cpp @@ -0,0 +1,441 @@ +/**************************************************************************/ +/* retarget_modifier_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "retarget_modifier_3d.h" + +PackedStringArray RetargetModifier3D::get_configuration_warnings() const { + PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings(); + if (child_skeletons.is_empty()) { + warnings.push_back(RTR("There is no child Skeleton3D!")); + } + return warnings; +} + +/// Caching + +void RetargetModifier3D::_profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new) { + if (p_old.is_valid() && p_old->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) { + p_old->disconnect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset)); + } + profile = p_new; + if (p_new.is_valid() && !p_new->is_connected(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset))) { + p_new->connect(SNAME("profile_updated"), callable_mp(this, &RetargetModifier3D::cache_rests_with_reset)); + } + cache_rests_with_reset(); +} + +void RetargetModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { + if (p_old && p_old->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) { + p_old->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests)); + } + if (p_new && !p_new->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests))) { + p_new->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::cache_rests)); + } + cache_rests(); +} + +void RetargetModifier3D::cache_rests_with_reset() { + _reset_child_skeleton_poses(); + cache_rests(); +} + +void RetargetModifier3D::cache_rests() { + source_bone_ids.clear(); + + Skeleton3D *source_skeleton = get_skeleton(); + if (profile.is_null() || !source_skeleton) { + return; + } + + PackedStringArray bone_names = profile->get_bone_names(); + for (const String &E : bone_names) { + source_bone_ids.push_back(source_skeleton->find_bone(E)); + } + + for (int i = 0; i < child_skeletons.size(); i++) { + _update_child_skeleton_rests(i); + } +} + +Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_global_rests(Skeleton3D *p_skeleton) { + // Retarget global pose in model space: + // tgt_global_pose.basis = src_global_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis + // tgt_global_pose.origin = src_global_pose.origin + Skeleton3D *source_skeleton = get_skeleton(); + Vector<RetargetBoneInfo> bone_rests; + if (profile.is_null() || !source_skeleton) { + return bone_rests; + } + PackedStringArray bone_names = profile->get_bone_names(); + for (const String &E : bone_names) { + RetargetBoneInfo rbi; + int source_bone_id = source_skeleton->find_bone(E); + if (source_bone_id >= 0) { + Transform3D parent_global_rest; + int bone_parent = source_skeleton->get_bone_parent(source_bone_id); + if (bone_parent >= 0) { + parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent); + } + rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse(); + } + int target_bone_id = p_skeleton->find_bone(E); + rbi.bone_id = target_bone_id; + if (target_bone_id >= 0) { + Transform3D parent_global_rest; + int bone_parent = p_skeleton->get_bone_parent(target_bone_id); + if (bone_parent >= 0) { + parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent); + } + rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis; + } + bone_rests.push_back(rbi); + } + return bone_rests; +} + +Vector<RetargetModifier3D::RetargetBoneInfo> RetargetModifier3D::cache_bone_rests(Skeleton3D *p_skeleton) { + // Retarget pose in model space: + // tgt_pose.basis = tgt_parent_global_rest.basis.inv * src_parent_global_rest.basis * src_pose.basis * src_rest.basis.inv * src_parent_global_rest.basis.inv * tgt_parent_global_rest.basis * tgt_rest.basis + // tgt_pose.origin = tgt_parent_global_rest.basis.inv.xform(src_parent_global_rest.basis.xform(src_pose.origin - src_rest.origin)) + tgt_rest.origin + Skeleton3D *source_skeleton = get_skeleton(); + Vector<RetargetBoneInfo> bone_rests; + if (profile.is_null() || !source_skeleton) { + return bone_rests; + } + PackedStringArray bone_names = profile->get_bone_names(); + for (const String &E : bone_names) { + RetargetBoneInfo rbi; + int source_bone_id = source_skeleton->find_bone(E); + if (source_bone_id >= 0) { + Transform3D parent_global_rest; + int bone_parent = source_skeleton->get_bone_parent(source_bone_id); + if (bone_parent >= 0) { + parent_global_rest = source_skeleton->get_bone_global_rest(bone_parent); + } + rbi.pre_basis = parent_global_rest.basis; + rbi.post_basis = source_skeleton->get_bone_rest(source_bone_id).basis.inverse() * parent_global_rest.basis.inverse(); + } + + int target_bone_id = p_skeleton->find_bone(E); + rbi.bone_id = target_bone_id; + if (target_bone_id >= 0) { + Transform3D parent_global_rest; + int bone_parent = p_skeleton->get_bone_parent(target_bone_id); + if (bone_parent >= 0) { + parent_global_rest = p_skeleton->get_bone_global_rest(bone_parent); + } + rbi.pre_basis = parent_global_rest.basis.inverse() * rbi.pre_basis; + rbi.post_basis = rbi.post_basis * parent_global_rest.basis * p_skeleton->get_bone_rest(target_bone_id).basis; + } + bone_rests.push_back(rbi); + } + return bone_rests; +} + +void RetargetModifier3D::_update_child_skeleton_rests(int p_child_skeleton_idx) { + ERR_FAIL_INDEX(p_child_skeleton_idx, child_skeletons.size()); + Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(child_skeletons[p_child_skeleton_idx].skeleton_id)); + if (!c) { + return; + } + if (use_global_pose) { + child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_global_rests(c); + } else { + child_skeletons.write[p_child_skeleton_idx].humanoid_bone_rests = cache_bone_rests(c); + } +} + +void RetargetModifier3D::_update_child_skeletons() { + _reset_child_skeletons(); + + for (int i = 0; i < get_child_count(); i++) { + RetargetInfo ri; + Skeleton3D *c = Object::cast_to<Skeleton3D>(get_child(i)); + if (c) { + int id = child_skeletons.size(); + ri.skeleton_id = c->get_instance_id(); + child_skeletons.push_back(ri); + c->connect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests).bind(id)); + } + } + + cache_rests(); + update_configuration_warnings(); +} + +void RetargetModifier3D::_reset_child_skeleton_poses() { + for (const RetargetInfo &E : child_skeletons) { + Skeleton3D *c = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id)); + if (!c) { + continue; + } + if (c->is_connected(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests))) { + c->disconnect(SNAME("rest_updated"), callable_mp(this, &RetargetModifier3D::_update_child_skeleton_rests)); + } + for (const RetargetBoneInfo &F : E.humanoid_bone_rests) { + if (F.bone_id < 0) { + continue; + } + c->reset_bone_pose(F.bone_id); + } + } +} + +void RetargetModifier3D::_reset_child_skeletons() { + _reset_child_skeleton_poses(); + child_skeletons.clear(); +} + +/// General functions + +void RetargetModifier3D::add_child_notify(Node *p_child) { + if (Object::cast_to<Skeleton3D>(p_child)) { + _update_child_skeletons(); + } +} + +void RetargetModifier3D::move_child_notify(Node *p_child) { + if (Object::cast_to<Skeleton3D>(p_child)) { + _update_child_skeletons(); + } +} + +void RetargetModifier3D::remove_child_notify(Node *p_child) { + if (Object::cast_to<Skeleton3D>(p_child)) { + // Reset after process. + callable_mp(this, &RetargetModifier3D::_update_child_skeletons).call_deferred(); + } +} + +void RetargetModifier3D::_validate_property(PropertyInfo &p_property) const { + if (use_global_pose) { + if (p_property.name == "position_enabled" || p_property.name == "rotation_enabled" || p_property.name == "scale_enabled") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } +} + +void RetargetModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_profile", "profile"), &RetargetModifier3D::set_profile); + ClassDB::bind_method(D_METHOD("get_profile"), &RetargetModifier3D::get_profile); + ClassDB::bind_method(D_METHOD("set_use_global_pose", "use_global_pose"), &RetargetModifier3D::set_use_global_pose); + ClassDB::bind_method(D_METHOD("is_using_global_pose"), &RetargetModifier3D::is_using_global_pose); + ClassDB::bind_method(D_METHOD("set_position_enabled", "enabled"), &RetargetModifier3D::set_position_enabled); + ClassDB::bind_method(D_METHOD("is_position_enabled"), &RetargetModifier3D::is_position_enabled); + ClassDB::bind_method(D_METHOD("set_rotation_enabled", "enabled"), &RetargetModifier3D::set_rotation_enabled); + ClassDB::bind_method(D_METHOD("is_rotation_enabled"), &RetargetModifier3D::is_rotation_enabled); + ClassDB::bind_method(D_METHOD("set_scale_enabled", "enabled"), &RetargetModifier3D::set_scale_enabled); + ClassDB::bind_method(D_METHOD("is_scale_enabled"), &RetargetModifier3D::is_scale_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_pose"), "set_use_global_pose", "is_using_global_pose"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "position_enabled"), "set_position_enabled", "is_position_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotation_enabled"), "set_rotation_enabled", "is_rotation_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scale_enabled"), "set_scale_enabled", "is_scale_enabled"); +} + +void RetargetModifier3D::_set_active(bool p_active) { + if (!p_active) { + _reset_child_skeleton_poses(); + } +} + +void RetargetModifier3D::_retarget_global_pose() { + Skeleton3D *source_skeleton = get_skeleton(); + if (profile.is_null() || !source_skeleton) { + return; + } + + LocalVector<Transform3D> source_poses; + if (influence < 1.0) { + for (int source_bone_id : source_bone_ids) { + source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_global_pose(source_bone_id), influence)); + } + } else { + for (int source_bone_id : source_bone_ids) { + source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_global_pose(source_bone_id)); + } + } + + for (const RetargetInfo &E : child_skeletons) { + Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id)); + if (!target_skeleton) { + continue; + } + for (int i = 0; i < source_bone_ids.size(); i++) { + int target_bone_id = E.humanoid_bone_rests[i].bone_id; + if (target_bone_id < 0) { + continue; + } + Transform3D retarget_pose = source_poses[i]; + retarget_pose.basis = retarget_pose.basis * E.humanoid_bone_rests[i].post_basis; + target_skeleton->set_bone_global_pose(target_bone_id, retarget_pose); + } + } +} + +void RetargetModifier3D::_retarget_pose() { + Skeleton3D *source_skeleton = get_skeleton(); + if (profile.is_null() || !source_skeleton) { + return; + } + + LocalVector<Transform3D> source_poses; + if (influence < 1.0) { + for (int source_bone_id : source_bone_ids) { + source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_rest(source_bone_id).interpolate_with(source_skeleton->get_bone_pose(source_bone_id), influence)); + } + } else { + for (int source_bone_id : source_bone_ids) { + source_poses.push_back(source_bone_id < 0 ? Transform3D() : source_skeleton->get_bone_pose(source_bone_id)); + } + } + + for (const RetargetInfo &E : child_skeletons) { + Skeleton3D *target_skeleton = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(E.skeleton_id)); + if (!target_skeleton) { + continue; + } + float motion_scale_ratio = target_skeleton->get_motion_scale() / source_skeleton->get_motion_scale(); + for (int i = 0; i < source_bone_ids.size(); i++) { + int target_bone_id = E.humanoid_bone_rests[i].bone_id; + if (target_bone_id < 0) { + continue; + } + int source_bone_id = source_bone_ids[i]; + if (source_bone_id < 0) { + continue; + } + + Transform3D extracted_transform = source_poses[i]; + extracted_transform.basis = E.humanoid_bone_rests[i].pre_basis * extracted_transform.basis * E.humanoid_bone_rests[i].post_basis; + extracted_transform.origin = E.humanoid_bone_rests[i].pre_basis.xform((extracted_transform.origin - source_skeleton->get_bone_rest(source_bone_id).origin) * motion_scale_ratio) + target_skeleton->get_bone_rest(target_bone_id).origin; + + Transform3D retarget_pose = target_skeleton->get_bone_pose(target_bone_id); + if (enable_position) { + retarget_pose.origin = extracted_transform.origin; + } + if (enable_rotation) { + retarget_pose.basis = extracted_transform.basis.get_rotation_quaternion(); + } + if (enable_scale) { + retarget_pose.basis.scale_local(extracted_transform.basis.get_scale()); + } + target_skeleton->set_bone_pose(target_bone_id, retarget_pose); + } + } +} + +void RetargetModifier3D::_process_modification() { + if (use_global_pose) { + _retarget_global_pose(); + } else { + _retarget_pose(); + } +} + +void RetargetModifier3D::set_profile(Ref<SkeletonProfile> p_profile) { + if (profile == p_profile) { + return; + } + _profile_changed(profile, p_profile); +} + +Ref<SkeletonProfile> RetargetModifier3D::get_profile() const { + return profile; +} + +void RetargetModifier3D::set_use_global_pose(bool p_use_global_pose) { + if (use_global_pose == p_use_global_pose) { + return; + } + + use_global_pose = p_use_global_pose; + cache_rests_with_reset(); + + notify_property_list_changed(); +} + +bool RetargetModifier3D::is_using_global_pose() const { + return use_global_pose; +} + +void RetargetModifier3D::set_position_enabled(bool p_enabled) { + if (enable_position != p_enabled) { + _reset_child_skeleton_poses(); + } + enable_position = p_enabled; +} + +bool RetargetModifier3D::is_position_enabled() const { + return enable_position; +} + +void RetargetModifier3D::set_rotation_enabled(bool p_enabled) { + if (enable_rotation != p_enabled) { + _reset_child_skeleton_poses(); + } + enable_rotation = p_enabled; +} + +bool RetargetModifier3D::is_rotation_enabled() const { + return enable_rotation; +} + +void RetargetModifier3D::set_scale_enabled(bool p_enabled) { + if (enable_scale != p_enabled) { + _reset_child_skeleton_poses(); + } + enable_scale = p_enabled; +} + +bool RetargetModifier3D::is_scale_enabled() const { + return enable_scale; +} + +void RetargetModifier3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_child_skeletons(); + } break; + case NOTIFICATION_EDITOR_PRE_SAVE: { + _reset_child_skeleton_poses(); + } break; + case NOTIFICATION_EXIT_TREE: { + _reset_child_skeletons(); + } break; + } +} + +RetargetModifier3D::RetargetModifier3D() { +} + +RetargetModifier3D::~RetargetModifier3D() { +} diff --git a/scene/3d/retarget_modifier_3d.h b/scene/3d/retarget_modifier_3d.h new file mode 100644 index 0000000000..75112d74bf --- /dev/null +++ b/scene/3d/retarget_modifier_3d.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* retarget_modifier_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef RETARGET_MODIFIER_3D_H +#define RETARGET_MODIFIER_3D_H + +#include "scene/3d/skeleton_modifier_3d.h" +#include "scene/resources/skeleton_profile.h" + +class RetargetModifier3D : public SkeletonModifier3D { + GDCLASS(RetargetModifier3D, SkeletonModifier3D); + + Ref<SkeletonProfile> profile; + + bool use_global_pose = false; + bool enable_position = true; + bool enable_rotation = true; + bool enable_scale = true; + + struct RetargetBoneInfo { + int bone_id = -1; + Basis pre_basis; + Basis post_basis; + }; + + struct RetargetInfo { + ObjectID skeleton_id; + Vector<RetargetBoneInfo> humanoid_bone_rests; + }; + + Vector<RetargetInfo> child_skeletons; + Vector<int> source_bone_ids; + + void _update_child_skeleton_rests(int p_child_skeleton_idx); + void _update_child_skeletons(); + void _reset_child_skeleton_poses(); + void _reset_child_skeletons(); + + void cache_rests_with_reset(); + void cache_rests(); + Vector<RetargetBoneInfo> cache_bone_global_rests(Skeleton3D *p_skeleton); + Vector<RetargetBoneInfo> cache_bone_rests(Skeleton3D *p_skeleton); + Vector<RetargetBoneInfo> get_humanoid_bone_rests(Skeleton3D *p_skeleton); + + void _retarget_global_pose(); + void _retarget_pose(); + +protected: + virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override; + void _profile_changed(Ref<SkeletonProfile> p_old, Ref<SkeletonProfile> p_new); + + void _validate_property(PropertyInfo &p_property) const; + + static void _bind_methods(); + virtual void _notification(int p_what); + + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + + virtual void _set_active(bool p_active) override; + virtual void _process_modification() override; + +public: + virtual PackedStringArray get_configuration_warnings() const override; + + void set_use_global_pose(bool p_use_global_pose); + bool is_using_global_pose() const; + void set_position_enabled(bool p_enabled); + bool is_position_enabled() const; + void set_rotation_enabled(bool p_enabled); + bool is_rotation_enabled() const; + void set_scale_enabled(bool p_enabled); + bool is_scale_enabled() const; + + void set_profile(Ref<SkeletonProfile> p_profile); + Ref<SkeletonProfile> get_profile() const; + + RetargetModifier3D(); + virtual ~RetargetModifier3D(); +}; + +#endif // RETARGET_MODIFIER_3D_H diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index b51d8a3438..08bd60034b 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -1048,7 +1048,12 @@ void Skeleton3D::force_update_all_bone_transforms() { for (int i = 0; i < parentless_bones.size(); i++) { force_update_bone_children_transforms(parentless_bones[i]); } - rest_dirty = false; + if (rest_dirty) { + rest_dirty = false; + emit_signal(SNAME("rest_updated")); + } else { + rest_dirty = false; + } dirty = false; if (updating) { return; @@ -1258,6 +1263,7 @@ void Skeleton3D::_bind_methods() { ADD_GROUP("Modifier", "modifier_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "modifier_callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_modifier_callback_mode_process", "get_modifier_callback_mode_process"); + ADD_SIGNAL(MethodInfo("rest_updated")); ADD_SIGNAL(MethodInfo("pose_updated")); ADD_SIGNAL(MethodInfo("skeleton_updated")); ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx"))); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 6c51c172af..c224a79550 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -222,6 +222,7 @@ public: // Skeleton creation API uint64_t get_version() const; int add_bone(const String &p_name); + void remove_bone(int p_bone); int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; void set_bone_name(int p_bone, const String &p_name); diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 80ff176a98..5fec7021eb 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -389,6 +389,17 @@ VoxelGI::BakeBeginFunc VoxelGI::bake_begin_function = nullptr; VoxelGI::BakeStepFunc VoxelGI::bake_step_function = nullptr; VoxelGI::BakeEndFunc VoxelGI::bake_end_function = nullptr; +static int voxelizer_plot_bake_base = 0; +static int voxelizer_plot_bake_total = 0; + +static bool voxelizer_plot_bake_step_function(int current, int) { + return VoxelGI::bake_step_function((voxelizer_plot_bake_base + current) * 500 / voxelizer_plot_bake_total, RTR("Plotting Meshes")); +} + +static bool voxelizer_sdf_bake_step_function(int current, int total) { + return VoxelGI::bake_step_function(500 + current * 500 / total, RTR("Generating Distance Field")); +} + Vector3i VoxelGI::get_estimated_cell_size() const { static const int subdiv_value[SUBDIV_MAX] = { 6, 7, 8, 9 }; int cell_subdiv = subdiv_value[subdiv]; @@ -432,22 +443,27 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { _find_meshes(p_from_node, mesh_list); if (bake_begin_function) { - bake_begin_function(mesh_list.size() + 1); + bake_begin_function(); } - int pmc = 0; + Voxelizer::BakeStepFunc voxelizer_step_func = bake_step_function != nullptr ? voxelizer_plot_bake_step_function : nullptr; + voxelizer_plot_bake_total = voxelizer_plot_bake_base = 0; for (PlotMesh &E : mesh_list) { - if (bake_step_function) { - bake_step_function(pmc, RTR("Plotting Meshes") + " " + itos(pmc) + "/" + itos(mesh_list.size())); + voxelizer_plot_bake_total += baker.get_bake_steps(E.mesh); + } + for (PlotMesh &E : mesh_list) { + if (baker.plot_mesh(E.local_xform, E.mesh, E.instance_materials, E.override_material, voxelizer_step_func) != Voxelizer::BAKE_RESULT_OK) { + baker.end_bake(); + if (bake_end_function) { + bake_end_function(); + } + return; } - - pmc++; - - baker.plot_mesh(E.local_xform, E.mesh, E.instance_materials, E.override_material); + voxelizer_plot_bake_base += baker.get_bake_steps(E.mesh); } if (bake_step_function) { - bake_step_function(pmc++, RTR("Finishing Plot")); + bake_step_function(500, RTR("Finishing Plot")); } baker.end_bake(); @@ -476,19 +492,22 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { } if (bake_step_function) { - bake_step_function(pmc++, RTR("Generating Distance Field")); + bake_step_function(500, RTR("Generating Distance Field")); } - Vector<uint8_t> df = baker.get_sdf_3d_image(); + voxelizer_step_func = bake_step_function != nullptr ? voxelizer_sdf_bake_step_function : nullptr; - RS::get_singleton()->voxel_gi_set_baked_exposure_normalization(probe_data_new->get_rid(), exposure_normalization); + Vector<uint8_t> df; + if (baker.get_sdf_3d_image(df, voxelizer_step_func) == Voxelizer::BAKE_RESULT_OK) { + RS::get_singleton()->voxel_gi_set_baked_exposure_normalization(probe_data_new->get_rid(), exposure_normalization); - probe_data_new->allocate(baker.get_to_cell_space_xform(), AABB(-size / 2, size), baker.get_voxel_gi_octree_size(), baker.get_voxel_gi_octree_cells(), baker.get_voxel_gi_data_cells(), df, baker.get_voxel_gi_level_cell_count()); + probe_data_new->allocate(baker.get_to_cell_space_xform(), AABB(-size / 2, size), baker.get_voxel_gi_octree_size(), baker.get_voxel_gi_octree_cells(), baker.get_voxel_gi_data_cells(), df, baker.get_voxel_gi_level_cell_count()); - set_probe_data(probe_data_new); + set_probe_data(probe_data_new); #ifdef TOOLS_ENABLED - probe_data_new->set_edited(true); //so it gets saved + probe_data_new->set_edited(true); //so it gets saved #endif + } } if (bake_end_function) { diff --git a/scene/3d/voxel_gi.h b/scene/3d/voxel_gi.h index 7d7787f721..d7e0d74d36 100644 --- a/scene/3d/voxel_gi.h +++ b/scene/3d/voxel_gi.h @@ -108,8 +108,8 @@ public: }; - typedef void (*BakeBeginFunc)(int); - typedef void (*BakeStepFunc)(int, const String &); + typedef void (*BakeBeginFunc)(); + typedef bool (*BakeStepFunc)(int, const String &); typedef void (*BakeEndFunc)(); private: diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index 99392e9ba0..1074cad11e 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -382,8 +382,24 @@ Voxelizer::MaterialCache Voxelizer::_get_material_cache(Ref<Material> p_material return mc; } -void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material>> &p_materials, const Ref<Material> &p_override_material) { - ERR_FAIL_COND_MSG(!p_xform.is_finite(), "Invalid mesh bake transform."); +int Voxelizer::get_bake_steps(Ref<Mesh> &p_mesh) const { + int bake_total = 0; + for (int i = 0; i < p_mesh->get_surface_count(); i++) { + if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; // Only triangles. + } + Array a = p_mesh->surface_get_arrays(i); + Vector<Vector3> vertices = a[Mesh::ARRAY_VERTEX]; + Vector<int> index = a[Mesh::ARRAY_INDEX]; + bake_total += (index.size() > 0 ? index.size() : vertices.size()) / 3; + } + return bake_total; +} + +Voxelizer::BakeResult Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material>> &p_materials, const Ref<Material> &p_override_material, BakeStepFunc p_bake_step_func) { + ERR_FAIL_COND_V_MSG(!p_xform.is_finite(), BAKE_RESULT_INVALID_PARAMETER, "Invalid mesh bake transform."); + + int bake_total = get_bake_steps(p_mesh), bake_current = 0; for (int i = 0; i < p_mesh->get_surface_count(); i++) { if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { @@ -428,6 +444,13 @@ void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const V Vector2 uvs[3]; Vector3 normal[3]; + bake_current++; + if (p_bake_step_func != nullptr && (bake_current & 2047) == 1) { + if (p_bake_step_func(bake_current, bake_total)) { + return BAKE_RESULT_CANCELLED; + } + } + for (int k = 0; k < 3; k++) { vtxs[k] = p_xform.xform(vr[ir[j * 3 + k]]); } @@ -460,6 +483,13 @@ void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const V Vector2 uvs[3]; Vector3 normal[3]; + bake_current++; + if (p_bake_step_func != nullptr && (bake_current & 2047) == 1) { + if (p_bake_step_func(bake_current, bake_total)) { + return BAKE_RESULT_CANCELLED; + } + } + for (int k = 0; k < 3; k++) { vtxs[k] = p_xform.xform(vr[j * 3 + k]); } @@ -487,6 +517,8 @@ void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const V } max_original_cells = bake_cells.size(); + + return BAKE_RESULT_OK; } void Voxelizer::_sort() { @@ -821,7 +853,7 @@ static void edt(float *f, int stride, int n) { #undef square -Vector<uint8_t> Voxelizer::get_sdf_3d_image() const { +Voxelizer::BakeResult Voxelizer::get_sdf_3d_image(Vector<uint8_t> &r_image, BakeStepFunc p_bake_step_function) const { Vector3i octree_size = get_voxel_gi_octree_size(); uint32_t float_count = octree_size.x * octree_size.y * octree_size.z; @@ -849,9 +881,17 @@ Vector<uint8_t> Voxelizer::get_sdf_3d_image() const { //process in each direction + int bake_total = octree_size.x * 2 + octree_size.y, bake_current = 0; + //xy->z - for (int i = 0; i < octree_size.x; i++) { + for (int i = 0; i < octree_size.x; i++, bake_current++) { + if (p_bake_step_function) { + if (p_bake_step_function(bake_current, bake_total)) { + memdelete_arr(work_memory); + return BAKE_RESULT_CANCELLED; + } + } for (int j = 0; j < octree_size.y; j++) { edt(&work_memory[i + j * y_mult], z_mult, octree_size.z); } @@ -859,23 +899,34 @@ Vector<uint8_t> Voxelizer::get_sdf_3d_image() const { //xz->y - for (int i = 0; i < octree_size.x; i++) { + for (int i = 0; i < octree_size.x; i++, bake_current++) { + if (p_bake_step_function) { + if (p_bake_step_function(bake_current, bake_total)) { + memdelete_arr(work_memory); + return BAKE_RESULT_CANCELLED; + } + } for (int j = 0; j < octree_size.z; j++) { edt(&work_memory[i + j * z_mult], y_mult, octree_size.y); } } //yz->x - for (int i = 0; i < octree_size.y; i++) { + for (int i = 0; i < octree_size.y; i++, bake_current++) { + if (p_bake_step_function) { + if (p_bake_step_function(bake_current, bake_total)) { + memdelete_arr(work_memory); + return BAKE_RESULT_CANCELLED; + } + } for (int j = 0; j < octree_size.z; j++) { edt(&work_memory[i * y_mult + j * z_mult], 1, octree_size.x); } } - Vector<uint8_t> image3d; - image3d.resize(float_count); + r_image.resize(float_count); { - uint8_t *w = image3d.ptrw(); + uint8_t *w = r_image.ptrw(); for (uint32_t i = 0; i < float_count; i++) { uint32_t d = uint32_t(Math::sqrt(work_memory[i])); if (d == 0) { @@ -888,7 +939,7 @@ Vector<uint8_t> Voxelizer::get_sdf_3d_image() const { memdelete_arr(work_memory); - return image3d; + return BAKE_RESULT_OK; } #undef INF diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index 08d018eee9..41e7767308 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -34,6 +34,15 @@ #include "scene/resources/multimesh.h" class Voxelizer { +public: + enum BakeResult { + BAKE_RESULT_OK, + BAKE_RESULT_INVALID_PARAMETER, + BAKE_RESULT_CANCELLED, + }; + + typedef bool (*BakeStepFunc)(int, int); + private: enum : uint32_t { CHILD_EMPTY = 0xFFFFFFFF @@ -112,7 +121,8 @@ private: public: void begin_bake(int p_subdiv, const AABB &p_bounds, float p_exposure_normalization); - void plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material>> &p_materials, const Ref<Material> &p_override_material); + int get_bake_steps(Ref<Mesh> &p_mesh) const; + BakeResult plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material>> &p_materials, const Ref<Material> &p_override_material, BakeStepFunc p_bake_step_function); void end_bake(); int get_voxel_gi_octree_depth() const; @@ -121,7 +131,7 @@ public: Vector<uint8_t> get_voxel_gi_octree_cells() const; Vector<uint8_t> get_voxel_gi_data_cells() const; Vector<int> get_voxel_gi_level_cell_count() const; - Vector<uint8_t> get_sdf_3d_image() const; + BakeResult get_sdf_3d_image(Vector<uint8_t> &r_image, BakeStepFunc p_bake_step_function) const; Ref<MultiMesh> create_debug_multimesh(); diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 7d28aead6e..8d24859422 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -164,8 +164,8 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f double delta = p_started ? 0 : p_delta * speed; double next_pos = cd.pos + delta; - double start = get_section_start_time(); - double end = get_section_end_time(); + double start = cd.get_start_time(); + double end = cd.get_end_time(); Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; @@ -389,7 +389,7 @@ void AnimationPlayer::play_section_with_markers_backwards(const StringName &p_na } void AnimationPlayer::play_section_backwards(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend) { - play_section(p_name, p_start_time, p_end_time, -1, true); + play_section(p_name, p_start_time, p_end_time, p_custom_blend, -1, true); } void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { @@ -495,8 +495,8 @@ void AnimationPlayer::play_section(const StringName &p_name, double p_start_time c.current.start_time = p_start_time; c.current.end_time = p_end_time; - double start = get_section_start_time(); - double end = get_section_end_time(); + double start = playback.current.get_start_time(); + double end = playback.current.get_end_time(); if (!end_reached) { playback_queue.clear(); @@ -660,8 +660,8 @@ void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_ } } - double start = get_section_start_time(); - double end = get_section_end_time(); + double start = playback.current.get_start_time(); + double end = playback.current.get_end_time(); // Clamp the seek position. p_time = CLAMP(p_time, start, end); @@ -725,7 +725,7 @@ void AnimationPlayer::set_section(double p_start_time, double p_end_time) { ERR_FAIL_COND_MSG(Animation::is_greater_or_equal_approx(p_start_time, 0) && Animation::is_greater_or_equal_approx(p_end_time, 0) && Animation::is_greater_or_equal_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time)); playback.current.start_time = p_start_time; playback.current.end_time = p_end_time; - playback.current.pos = CLAMP(playback.current.pos, get_section_start_time(), get_section_end_time()); + playback.current.pos = CLAMP(playback.current.pos, playback.current.get_start_time(), playback.current.get_end_time()); } void AnimationPlayer::reset_section() { @@ -735,18 +735,12 @@ void AnimationPlayer::reset_section() { double AnimationPlayer::get_section_start_time() const { ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.start_time, "AnimationPlayer has no current animation."); - if (Animation::is_less_approx(playback.current.start_time, 0) || playback.current.start_time > playback.current.from->animation->get_length()) { - return 0; - } - return playback.current.start_time; + return playback.current.get_start_time(); } double AnimationPlayer::get_section_end_time() const { ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.end_time, "AnimationPlayer has no current animation."); - if (Animation::is_less_approx(playback.current.end_time, 0) || playback.current.end_time > playback.current.from->animation->get_length()) { - return playback.current.from->animation->get_length(); - } - return playback.current.end_time; + return playback.current.get_end_time(); } bool AnimationPlayer::has_section() const { @@ -777,7 +771,7 @@ void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) { _clear_caches(); Playback &c = playback; // c.blend.clear(); - double start = c.current.from ? get_section_start_time() : 0; + double start = c.current.from ? playback.current.get_start_time() : 0; if (p_reset) { c.blend.clear(); if (p_keep_state) { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 6d7e8aa996..c87719a2b3 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -70,6 +70,18 @@ private: float speed_scale = 1.0; double start_time = 0.0; double end_time = 0.0; + double get_start_time() const { + if (from && (Animation::is_less_approx(start_time, 0) || Animation::is_greater_approx(start_time, from->animation->get_length()))) { + return 0; + } + return start_time; + } + double get_end_time() const { + if (from && (Animation::is_less_approx(end_time, 0) || Animation::is_greater_approx(end_time, from->animation->get_length()))) { + return from->animation->get_length(); + } + return end_time; + } }; struct Blend { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 8a048e9cc3..ff739bbead 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -277,6 +277,7 @@ #include "scene/3d/physics/vehicle_body_3d.h" #include "scene/3d/reflection_probe.h" #include "scene/3d/remote_transform_3d.h" +#include "scene/3d/retarget_modifier_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_ik_3d.h" #include "scene/3d/skeleton_modifier_3d.h" @@ -594,6 +595,7 @@ void register_scene_types() { GDREGISTER_CLASS(Marker3D); GDREGISTER_CLASS(RootMotionView); GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); + GDREGISTER_CLASS(RetargetModifier3D); OS::get_singleton()->yield(); // may take time to init diff --git a/scene/resources/3d/box_shape_3d.cpp b/scene/resources/3d/box_shape_3d.cpp index 313aeb1bca..afb03a8ba1 100644 --- a/scene/resources/3d/box_shape_3d.cpp +++ b/scene/resources/3d/box_shape_3d.cpp @@ -29,6 +29,8 @@ /**************************************************************************/ #include "box_shape_3d.h" + +#include "scene/resources/3d/primitive_meshes.h" #include "servers/physics_server_3d.h" Vector<Vector3> BoxShape3D::get_debug_mesh_lines() const { @@ -47,6 +49,24 @@ Vector<Vector3> BoxShape3D::get_debug_mesh_lines() const { return lines; } +Ref<ArrayMesh> BoxShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Array box_array; + box_array.resize(RS::ARRAY_MAX); + BoxMesh::create_mesh_array(box_array, size); + + Vector<Color> colors; + const PackedVector3Array &verts = box_array[RS::ARRAY_VERTEX]; + const int32_t verts_size = verts.size(); + for (int i = 0; i < verts_size; i++) { + colors.append(p_modulate); + } + + Ref<ArrayMesh> box_mesh = memnew(ArrayMesh); + box_array[RS::ARRAY_COLOR] = colors; + box_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array); + return box_mesh; +} + real_t BoxShape3D::get_enclosing_radius() const { return size.length() / 2; } diff --git a/scene/resources/3d/box_shape_3d.h b/scene/resources/3d/box_shape_3d.h index 45c1cde5d7..a9137fdcaf 100644 --- a/scene/resources/3d/box_shape_3d.h +++ b/scene/resources/3d/box_shape_3d.h @@ -51,6 +51,7 @@ public: Vector3 get_size() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; BoxShape3D(); diff --git a/scene/resources/3d/capsule_shape_3d.cpp b/scene/resources/3d/capsule_shape_3d.cpp index 9e16801060..b63bf69aee 100644 --- a/scene/resources/3d/capsule_shape_3d.cpp +++ b/scene/resources/3d/capsule_shape_3d.cpp @@ -30,6 +30,7 @@ #include "capsule_shape_3d.h" +#include "scene/resources/3d/primitive_meshes.h" #include "servers/physics_server_3d.h" Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const { @@ -67,6 +68,24 @@ Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> CapsuleShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Array capsule_array; + capsule_array.resize(RS::ARRAY_MAX); + CapsuleMesh::create_mesh_array(capsule_array, radius, height, 32, 8); + + Vector<Color> colors; + const PackedVector3Array &verts = capsule_array[RS::ARRAY_VERTEX]; + const int32_t verts_size = verts.size(); + for (int i = 0; i < verts_size; i++) { + colors.append(p_modulate); + } + + Ref<ArrayMesh> capsule_mesh = memnew(ArrayMesh); + capsule_array[RS::ARRAY_COLOR] = colors; + capsule_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, capsule_array); + return capsule_mesh; +} + real_t CapsuleShape3D::get_enclosing_radius() const { return height * 0.5; } diff --git a/scene/resources/3d/capsule_shape_3d.h b/scene/resources/3d/capsule_shape_3d.h index 90ee3b584a..2ad7fa452c 100644 --- a/scene/resources/3d/capsule_shape_3d.h +++ b/scene/resources/3d/capsule_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class CapsuleShape3D : public Shape3D { GDCLASS(CapsuleShape3D, Shape3D); float radius = 0.5; @@ -50,6 +52,7 @@ public: float get_height() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; CapsuleShape3D(); diff --git a/scene/resources/3d/concave_polygon_shape_3d.cpp b/scene/resources/3d/concave_polygon_shape_3d.cpp index 82b125905f..1254cc4306 100644 --- a/scene/resources/3d/concave_polygon_shape_3d.cpp +++ b/scene/resources/3d/concave_polygon_shape_3d.cpp @@ -30,6 +30,7 @@ #include "concave_polygon_shape_3d.h" +#include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const { @@ -59,6 +60,23 @@ Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> ConcavePolygonShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Vector<Color> colors; + + for (int i = 0; i < faces.size(); i++) { + colors.push_back(p_modulate); + } + + Ref<ArrayMesh> mesh = memnew(ArrayMesh); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[RS::ARRAY_VERTEX] = faces; + a[RS::ARRAY_COLOR] = colors; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); + + return mesh; +} + real_t ConcavePolygonShape3D::get_enclosing_radius() const { Vector<Vector3> data = get_faces(); const Vector3 *read = data.ptr(); diff --git a/scene/resources/3d/concave_polygon_shape_3d.h b/scene/resources/3d/concave_polygon_shape_3d.h index a5e46474d5..d5e5bc394b 100644 --- a/scene/resources/3d/concave_polygon_shape_3d.h +++ b/scene/resources/3d/concave_polygon_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class ConcavePolygonShape3D : public Shape3D { GDCLASS(ConcavePolygonShape3D, Shape3D); @@ -72,6 +74,7 @@ public: bool is_backface_collision_enabled() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; ConcavePolygonShape3D(); diff --git a/scene/resources/3d/convex_polygon_shape_3d.cpp b/scene/resources/3d/convex_polygon_shape_3d.cpp index 586d5f4678..809c089e6c 100644 --- a/scene/resources/3d/convex_polygon_shape_3d.cpp +++ b/scene/resources/3d/convex_polygon_shape_3d.cpp @@ -30,6 +30,7 @@ #include "convex_polygon_shape_3d.h" #include "core/math/convex_hull.h" +#include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { @@ -53,6 +54,44 @@ Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { return Vector<Vector3>(); } +Ref<ArrayMesh> ConvexPolygonShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + const Vector<Vector3> hull_points = get_points(); + + Vector<Vector3> verts; + Vector<Color> colors; + Vector<int> indices; + + if (hull_points.size() >= 3) { + Geometry3D::MeshData md; + Error err = ConvexHullComputer::convex_hull(hull_points, md); + if (err == OK) { + verts = md.vertices; + for (int i = 0; i < verts.size(); i++) { + colors.push_back(p_modulate); + } + for (const Geometry3D::MeshData::Face &face : md.faces) { + const int first_point = face.indices[0]; + const int indices_count = face.indices.size(); + for (int i = 1; i < indices_count - 1; i++) { + indices.push_back(first_point); + indices.push_back(face.indices[i]); + indices.push_back(face.indices[i + 1]); + } + } + } + } + + Ref<ArrayMesh> mesh = memnew(ArrayMesh); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[RS::ARRAY_VERTEX] = verts; + a[RS::ARRAY_COLOR] = colors; + a[RS::ARRAY_INDEX] = indices; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); + + return mesh; +} + real_t ConvexPolygonShape3D::get_enclosing_radius() const { Vector<Vector3> data = get_points(); const Vector3 *read = data.ptr(); diff --git a/scene/resources/3d/convex_polygon_shape_3d.h b/scene/resources/3d/convex_polygon_shape_3d.h index 7d1ac123c6..2dd4ce66db 100644 --- a/scene/resources/3d/convex_polygon_shape_3d.h +++ b/scene/resources/3d/convex_polygon_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class ConvexPolygonShape3D : public Shape3D { GDCLASS(ConvexPolygonShape3D, Shape3D); Vector<Vector3> points; @@ -47,6 +49,7 @@ public: Vector<Vector3> get_points() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; ConvexPolygonShape3D(); diff --git a/scene/resources/3d/cylinder_shape_3d.cpp b/scene/resources/3d/cylinder_shape_3d.cpp index a91282fd33..700ff5884d 100644 --- a/scene/resources/3d/cylinder_shape_3d.cpp +++ b/scene/resources/3d/cylinder_shape_3d.cpp @@ -30,6 +30,7 @@ #include "cylinder_shape_3d.h" +#include "scene/resources/3d/primitive_meshes.h" #include "servers/physics_server_3d.h" Vector<Vector3> CylinderShape3D::get_debug_mesh_lines() const { @@ -60,6 +61,24 @@ Vector<Vector3> CylinderShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> CylinderShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Array cylinder_array; + cylinder_array.resize(RS::ARRAY_MAX); + CylinderMesh::create_mesh_array(cylinder_array, radius, radius, height, 32); + + Vector<Color> colors; + const PackedVector3Array &verts = cylinder_array[RS::ARRAY_VERTEX]; + const int32_t verts_size = verts.size(); + for (int i = 0; i < verts_size; i++) { + colors.append(p_modulate); + } + + Ref<ArrayMesh> cylinder_mesh = memnew(ArrayMesh); + cylinder_array[RS::ARRAY_COLOR] = colors; + cylinder_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array); + return cylinder_mesh; +} + real_t CylinderShape3D::get_enclosing_radius() const { return Vector2(radius, height * 0.5).length(); } diff --git a/scene/resources/3d/cylinder_shape_3d.h b/scene/resources/3d/cylinder_shape_3d.h index bd57bc2a97..9388cab368 100644 --- a/scene/resources/3d/cylinder_shape_3d.h +++ b/scene/resources/3d/cylinder_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class CylinderShape3D : public Shape3D { GDCLASS(CylinderShape3D, Shape3D); float radius = 0.5; @@ -49,6 +51,7 @@ public: float get_height() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; CylinderShape3D(); diff --git a/scene/resources/3d/height_map_shape_3d.cpp b/scene/resources/3d/height_map_shape_3d.cpp index 5b55b66152..65b1425670 100644 --- a/scene/resources/3d/height_map_shape_3d.cpp +++ b/scene/resources/3d/height_map_shape_3d.cpp @@ -31,6 +31,7 @@ #include "height_map_shape_3d.h" #include "core/io/image.h" +#include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { @@ -82,6 +83,60 @@ Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> HeightMapShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Vector<Vector3> verts; + Vector<Color> colors; + Vector<int> indices; + + // This will be slow for large maps... + + if ((map_width != 0) && (map_depth != 0)) { + Vector2 size = Vector2(map_width - 1, map_depth - 1) * -0.5; + const real_t *r = map_data.ptr(); + + for (int d = 0; d <= map_depth - 2; d++) { + const int this_row_offset = map_width * d; + const int next_row_offset = this_row_offset + map_width; + + for (int w = 0; w <= map_width - 2; w++) { + const float height_tl = r[next_row_offset + w]; + const float height_bl = r[this_row_offset + w]; + const float height_br = r[this_row_offset + w + 1]; + const float height_tr = r[next_row_offset + w + 1]; + + const int index_offset = verts.size(); + + verts.push_back(Vector3(size.x + w, height_tl, size.y + d + 1)); + verts.push_back(Vector3(size.x + w, height_bl, size.y + d)); + verts.push_back(Vector3(size.x + w + 1, height_br, size.y + d)); + verts.push_back(Vector3(size.x + w + 1, height_tr, size.y + d + 1)); + + colors.push_back(p_modulate); + colors.push_back(p_modulate); + colors.push_back(p_modulate); + colors.push_back(p_modulate); + + indices.push_back(index_offset); + indices.push_back(index_offset + 1); + indices.push_back(index_offset + 2); + indices.push_back(index_offset); + indices.push_back(index_offset + 2); + indices.push_back(index_offset + 3); + } + } + } + + Ref<ArrayMesh> mesh = memnew(ArrayMesh); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[RS::ARRAY_VERTEX] = verts; + a[RS::ARRAY_COLOR] = colors; + a[RS::ARRAY_INDEX] = indices; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); + + return mesh; +} + real_t HeightMapShape3D::get_enclosing_radius() const { return Vector3(real_t(map_width), max_height - min_height, real_t(map_depth)).length(); } diff --git a/scene/resources/3d/height_map_shape_3d.h b/scene/resources/3d/height_map_shape_3d.h index 33ba9c4472..b5be53092d 100644 --- a/scene/resources/3d/height_map_shape_3d.h +++ b/scene/resources/3d/height_map_shape_3d.h @@ -33,6 +33,7 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; class Image; class HeightMapShape3D : public Shape3D { @@ -62,6 +63,7 @@ public: void update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max); virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; HeightMapShape3D(); diff --git a/scene/resources/3d/separation_ray_shape_3d.cpp b/scene/resources/3d/separation_ray_shape_3d.cpp index 07e93b8b79..55529be624 100644 --- a/scene/resources/3d/separation_ray_shape_3d.cpp +++ b/scene/resources/3d/separation_ray_shape_3d.cpp @@ -30,6 +30,7 @@ #include "separation_ray_shape_3d.h" +#include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const { @@ -41,6 +42,10 @@ Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> SeparationRayShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + return memnew(ArrayMesh); +} + real_t SeparationRayShape3D::get_enclosing_radius() const { return length; } diff --git a/scene/resources/3d/separation_ray_shape_3d.h b/scene/resources/3d/separation_ray_shape_3d.h index f24f0eae9e..c1c273c448 100644 --- a/scene/resources/3d/separation_ray_shape_3d.h +++ b/scene/resources/3d/separation_ray_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class SeparationRayShape3D : public Shape3D { GDCLASS(SeparationRayShape3D, Shape3D); float length = 1.0; @@ -50,6 +52,7 @@ public: bool get_slide_on_slope() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; SeparationRayShape3D(); diff --git a/scene/resources/3d/shape_3d.cpp b/scene/resources/3d/shape_3d.cpp index 259d82b7a0..a1ee7b85dd 100644 --- a/scene/resources/3d/shape_3d.cpp +++ b/scene/resources/3d/shape_3d.cpp @@ -66,6 +66,34 @@ void Shape3D::set_margin(real_t p_margin) { PhysicsServer3D::get_singleton()->shape_set_margin(shape, margin); } +#ifdef DEBUG_ENABLED +void Shape3D::set_debug_color(const Color &p_color) { + if (p_color == debug_color) { + return; + } + + debug_color = p_color; + _update_shape(); +} + +Color Shape3D::get_debug_color() const { + return debug_color; +} + +void Shape3D::set_debug_fill(bool p_fill) { + if (p_fill == debug_fill) { + return; + } + + debug_fill = p_fill; + _update_shape(); +} + +bool Shape3D::get_debug_fill() const { + return debug_fill; +} +#endif // DEBUG_ENABLED + Ref<ArrayMesh> Shape3D::get_debug_mesh() { if (debug_mesh_cache.is_valid()) { return debug_mesh_cache; @@ -79,29 +107,57 @@ Ref<ArrayMesh> Shape3D::get_debug_mesh() { //make mesh Vector<Vector3> array; array.resize(lines.size()); - { - Vector3 *w = array.ptrw(); - for (int i = 0; i < lines.size(); i++) { - w[i] = lines[i]; - } + Vector3 *v = array.ptrw(); + + Vector<Color> arraycol; + arraycol.resize(lines.size()); + Color *c = arraycol.ptrw(); + + for (int i = 0; i < lines.size(); i++) { + v[i] = lines[i]; + c[i] = debug_color; } - Array arr; - arr.resize(Mesh::ARRAY_MAX); - arr[Mesh::ARRAY_VERTEX] = array; + Array lines_array; + lines_array.resize(Mesh::ARRAY_MAX); + lines_array[Mesh::ARRAY_VERTEX] = array; + lines_array[Mesh::ARRAY_COLOR] = arraycol; - SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); + Ref<StandardMaterial3D> material = get_debug_collision_material(); - debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arr); + debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, lines_array); + debug_mesh_cache->surface_set_material(0, material); - if (st) { - debug_mesh_cache->surface_set_material(0, st->get_debug_collision_material()); + if (debug_fill) { + Array solid_array = get_debug_arraymesh_faces(debug_color * Color(1.0, 1.0, 1.0, 0.0625))->surface_get_arrays(0); + debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, solid_array); + debug_mesh_cache->surface_set_material(1, material); } } return debug_mesh_cache; } +Ref<Material> Shape3D::get_debug_collision_material() { + if (collision_material.is_valid()) { + return collision_material; + } + + Ref<StandardMaterial3D> material = memnew(StandardMaterial3D); + material->set_albedo(Color(1.0, 1.0, 1.0)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); + material->set_cull_mode(StandardMaterial3D::CULL_BACK); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + collision_material = material; + + return collision_material; +} + void Shape3D::_update_shape() { emit_changed(); debug_mesh_cache.unref(); diff --git a/scene/resources/3d/shape_3d.h b/scene/resources/3d/shape_3d.h index 5e6cdbe421..e956f4c322 100644 --- a/scene/resources/3d/shape_3d.h +++ b/scene/resources/3d/shape_3d.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" class ArrayMesh; +class Material; class Shape3D : public Resource { GDCLASS(Shape3D, Resource); @@ -44,6 +45,10 @@ class Shape3D : public Resource { real_t margin = 0.04; Ref<ArrayMesh> debug_mesh_cache; + Ref<Material> collision_material; + + Color debug_color = Color(0.0, 0.0, 0.0, 0.0); + bool debug_fill = true; protected: static void _bind_methods(); @@ -51,6 +56,8 @@ protected: _FORCE_INLINE_ RID get_shape() const { return shape; } Shape3D(RID p_shape); + Ref<Material> get_debug_collision_material(); + virtual void _update_shape(); public: @@ -58,6 +65,7 @@ public: Ref<ArrayMesh> get_debug_mesh(); virtual Vector<Vector3> get_debug_mesh_lines() const = 0; // { return Vector<Vector3>(); } + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const = 0; /// Returns the radius of a sphere that fully enclose this shape virtual real_t get_enclosing_radius() const = 0; @@ -69,6 +77,14 @@ public: real_t get_margin() const; void set_margin(real_t p_margin); +#ifdef DEBUG_ENABLED + void set_debug_color(const Color &p_color); + Color get_debug_color() const; + + void set_debug_fill(bool p_fill); + bool get_debug_fill() const; +#endif // DEBUG_ENABLED + Shape3D(); ~Shape3D(); }; diff --git a/scene/resources/3d/sphere_shape_3d.cpp b/scene/resources/3d/sphere_shape_3d.cpp index 56b78471ec..bdce41c16f 100644 --- a/scene/resources/3d/sphere_shape_3d.cpp +++ b/scene/resources/3d/sphere_shape_3d.cpp @@ -30,6 +30,8 @@ #include "sphere_shape_3d.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/material.h" #include "servers/physics_server_3d.h" Vector<Vector3> SphereShape3D::get_debug_mesh_lines() const { @@ -54,6 +56,24 @@ Vector<Vector3> SphereShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> SphereShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Array sphere_array; + sphere_array.resize(RS::ARRAY_MAX); + SphereMesh::create_mesh_array(sphere_array, radius, radius * 2, 32); + + Vector<Color> colors; + const PackedVector3Array &verts = sphere_array[RS::ARRAY_VERTEX]; + const int32_t verts_size = verts.size(); + for (int i = 0; i < verts_size; i++) { + colors.append(p_modulate); + } + + Ref<ArrayMesh> sphere_mesh = memnew(ArrayMesh); + sphere_array[RS::ARRAY_COLOR] = colors; + sphere_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, sphere_array); + return sphere_mesh; +} + real_t SphereShape3D::get_enclosing_radius() const { return radius; } diff --git a/scene/resources/3d/sphere_shape_3d.h b/scene/resources/3d/sphere_shape_3d.h index 8e95cea608..cb0685287d 100644 --- a/scene/resources/3d/sphere_shape_3d.h +++ b/scene/resources/3d/sphere_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class SphereShape3D : public Shape3D { GDCLASS(SphereShape3D, Shape3D); float radius = 0.5f; @@ -47,6 +49,7 @@ public: float get_radius() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override; SphereShape3D(); diff --git a/scene/resources/3d/world_boundary_shape_3d.cpp b/scene/resources/3d/world_boundary_shape_3d.cpp index beaaddc95e..25f060aa97 100644 --- a/scene/resources/3d/world_boundary_shape_3d.cpp +++ b/scene/resources/3d/world_boundary_shape_3d.cpp @@ -30,6 +30,7 @@ #include "world_boundary_shape_3d.h" +#include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" Vector<Vector3> WorldBoundaryShape3D::get_debug_mesh_lines() const { @@ -61,6 +62,53 @@ Vector<Vector3> WorldBoundaryShape3D::get_debug_mesh_lines() const { return points; } +Ref<ArrayMesh> WorldBoundaryShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const { + Plane p = get_plane(); + + Vector3 n1 = p.get_any_perpendicular_normal(); + Vector3 n2 = p.normal.cross(n1).normalized(); + + Vector3 pface[4] = { + p.normal * p.d + n1 * 10.0 + n2 * 10.0, + p.normal * p.d + n1 * 10.0 + n2 * -10.0, + p.normal * p.d + n1 * -10.0 + n2 * -10.0, + p.normal * p.d + n1 * -10.0 + n2 * 10.0, + }; + + Vector<Vector3> points = { + pface[0], + pface[1], + pface[2], + pface[3], + }; + + Vector<Color> colors = { + p_modulate, + p_modulate, + p_modulate, + p_modulate, + }; + + Vector<int> indices = { + 0, + 1, + 2, + 0, + 2, + 3, + }; + + Ref<ArrayMesh> mesh = memnew(ArrayMesh); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[RS::ARRAY_VERTEX] = points; + a[RS::ARRAY_COLOR] = colors; + a[RS::ARRAY_INDEX] = indices; + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); + + return mesh; +} + void WorldBoundaryShape3D::_update_shape() { PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), plane); Shape3D::_update_shape(); diff --git a/scene/resources/3d/world_boundary_shape_3d.h b/scene/resources/3d/world_boundary_shape_3d.h index 06cff6aa9a..456316df2e 100644 --- a/scene/resources/3d/world_boundary_shape_3d.h +++ b/scene/resources/3d/world_boundary_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class ArrayMesh; + class WorldBoundaryShape3D : public Shape3D { GDCLASS(WorldBoundaryShape3D, Shape3D); Plane plane; @@ -46,6 +48,7 @@ public: const Plane &get_plane() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; + virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override; virtual real_t get_enclosing_radius() const override { // Should be infinite? return 0; diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 22666876ae..92612fc3d7 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -125,6 +125,10 @@ void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const } } +int AnimationLibrary::get_animation_list_size() const { + return animations.size(); +} + void AnimationLibrary::_set_data(const Dictionary &p_data) { for (KeyValue<StringName, Ref<Animation>> &K : animations) { K.value->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed)); @@ -166,6 +170,7 @@ void AnimationLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationLibrary::has_animation); ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationLibrary::get_animation); ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationLibrary::_get_animation_list); + ClassDB::bind_method(D_METHOD("get_animation_list_size"), &AnimationLibrary::get_animation_list_size); ClassDB::bind_method(D_METHOD("_set_data", "data"), &AnimationLibrary::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data); diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h index 00baf9d302..794f142744 100644 --- a/scene/resources/animation_library.h +++ b/scene/resources/animation_library.h @@ -61,6 +61,7 @@ public: bool has_animation(const StringName &p_name) const; Ref<Animation> get_animation(const StringName &p_name) const; void get_animation_list(List<StringName> *p_animations) const; + int get_animation_list_size() const; #ifdef TOOLS_ENABLED virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp index c2d77ec7ff..2a1b64078a 100644 --- a/scene/resources/skeleton_profile.cpp +++ b/scene/resources/skeleton_profile.cpp @@ -269,6 +269,14 @@ int SkeletonProfile::find_bone(const StringName &p_bone_name) const { return -1; } +PackedStringArray SkeletonProfile::get_bone_names() { + PackedStringArray s; + for (const SkeletonProfileBone &bone : bones) { + s.push_back(bone.bone_name); + } + return s; +} + StringName SkeletonProfile::get_bone_name(int p_bone_idx) const { ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); return bones[p_bone_idx].bone_name; diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h index b5a3bce940..9361c6a9ce 100644 --- a/scene/resources/skeleton_profile.h +++ b/scene/resources/skeleton_profile.h @@ -97,6 +97,7 @@ public: int find_bone(const StringName &p_bone_name) const; + PackedStringArray get_bone_names(); StringName get_bone_name(int p_bone_idx) const; void set_bone_name(int p_bone_idx, const StringName &p_bone_name); |