diff options
109 files changed, 4375 insertions, 1715 deletions
diff --git a/doc/classes/AnimatedSprite.xml b/doc/classes/AnimatedSprite.xml index 0ee1c75348..555169545e 100644 --- a/doc/classes/AnimatedSprite.xml +++ b/doc/classes/AnimatedSprite.xml @@ -23,6 +23,8 @@ </return> <argument index="0" name="anim" type="String" default=""""> </argument> + <argument index="1" name="backwards" type="bool" default="false"> + </argument> <description> Play the animation set in parameter. If no parameter is provided, the current animation is played. Property [code]backwards[/code] plays the animation in reverse if set to [code]true[/code]. </description> diff --git a/doc/classes/AudioEffectInstance.xml b/doc/classes/AudioEffectInstance.xml new file mode 100644 index 0000000000..ed42928eb1 --- /dev/null +++ b/doc/classes/AudioEffectInstance.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioEffectInstance" inherits="Reference" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/AudioEffectSpectrumAnalyzer.xml b/doc/classes/AudioEffectSpectrumAnalyzer.xml new file mode 100644 index 0000000000..5732039467 --- /dev/null +++ b/doc/classes/AudioEffectSpectrumAnalyzer.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioEffectSpectrumAnalyzer" inherits="AudioEffect" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="buffer_length" type="float" setter="set_buffer_length" getter="get_buffer_length"> + </member> + <member name="fft_size" type="int" setter="set_fft_size" getter="get_fft_size" enum="AudioEffectSpectrumAnalyzer.FFT_Size"> + </member> + <member name="tap_back_pos" type="float" setter="set_tap_back_pos" getter="get_tap_back_pos"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml b/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml new file mode 100644 index 0000000000..0c0061beb4 --- /dev/null +++ b/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioEffectSpectrumAnalyzerInstance" inherits="AudioEffectInstance" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_magnitude_for_frequency_range" qualifiers="const"> + <return type="Vector2"> + </return> + <argument index="0" name="from_hz" type="float"> + </argument> + <argument index="1" name="to_hz" type="float"> + </argument> + <argument index="2" name="mode" type="int" enum="AudioEffectSpectrumAnalyzerInstance.MagnitudeMode" default="1"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + <constant name="MAGNITUDE_AVERAGE" value="0" enum="MagnitudeMode"> + </constant> + <constant name="MAGNITUDE_MAX" value="1" enum="MagnitudeMode"> + </constant> + </constants> +</class> diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 82e17e4957..32eed54882 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -97,6 +97,18 @@ Returns the number of effects on the bus at [code]bus_idx[/code]. </description> </method> + <method name="get_bus_effect_instance"> + <return type="AudioEffectInstance"> + </return> + <argument index="0" name="bus_idx" type="int"> + </argument> + <argument index="1" name="effect_idx" type="int"> + </argument> + <argument index="2" name="channel" type="int" default="0"> + </argument> + <description> + </description> + </method> <method name="get_bus_index" qualifiers="const"> <return type="int"> </return> diff --git a/doc/classes/AudioStreamGenerator.xml b/doc/classes/AudioStreamGenerator.xml new file mode 100644 index 0000000000..e41b5827b1 --- /dev/null +++ b/doc/classes/AudioStreamGenerator.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioStreamGenerator" inherits="AudioStream" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="buffer_length" type="float" setter="set_buffer_length" getter="get_buffer_length"> + </member> + <member name="mix_rate" type="float" setter="set_mix_rate" getter="get_mix_rate"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/AudioStreamGeneratorPlayback.xml b/doc/classes/AudioStreamGeneratorPlayback.xml new file mode 100644 index 0000000000..4b4d622741 --- /dev/null +++ b/doc/classes/AudioStreamGeneratorPlayback.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioStreamGeneratorPlayback" inherits="AudioStreamPlaybackResampled" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="can_push_buffer" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="amount" type="int"> + </argument> + <description> + </description> + </method> + <method name="clear_buffer"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="get_frames_available" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_skips" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="push_buffer"> + <return type="bool"> + </return> + <argument index="0" name="frames" type="PoolVector2Array"> + </argument> + <description> + </description> + </method> + <method name="push_frame"> + <return type="bool"> + </return> + <argument index="0" name="frame" type="Vector2"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/AudioStreamPlaybackResampled.xml b/doc/classes/AudioStreamPlaybackResampled.xml new file mode 100644 index 0000000000..764f966167 --- /dev/null +++ b/doc/classes/AudioStreamPlaybackResampled.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioStreamPlaybackResampled" inherits="AudioStreamPlayback" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index d8b87a3c19..1681d796ea 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -19,6 +19,12 @@ Returns the position in the [AudioStream] in seconds. </description> </method> + <method name="get_stream_playback"> + <return type="AudioStreamPlayback"> + </return> + <description> + </description> + </method> <method name="play"> <return type="void"> </return> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index 1cd2bf7645..e001d11727 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -19,6 +19,12 @@ Returns the position in the [AudioStream]. </description> </method> + <method name="get_stream_playback"> + <return type="AudioStreamPlayback"> + </return> + <description> + </description> + </method> <method name="play"> <return type="void"> </return> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 8ff5223e4f..0d973a761a 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -19,6 +19,12 @@ Returns the position in the [AudioStream]. </description> </method> + <method name="get_stream_playback"> + <return type="AudioStreamPlayback"> + </return> + <description> + </description> + </method> <method name="play"> <return type="void"> </return> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 7b43d58c71..3710bc3de6 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -76,6 +76,12 @@ This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1,0 or 1, and returns the index of the point best representing the orientation of the object. It is mainly used by the grid map editor. For further details, refer to Godot source code. </description> </method> + <method name="get_rotation_quat"> + <return type="Quat"> + </return> + <description> + </description> + </method> <method name="get_scale"> <return type="Vector3"> </return> @@ -90,6 +96,16 @@ Return the inverse of the matrix. </description> </method> + <method name="is_equal_approx"> + <return type="bool"> + </return> + <argument index="0" name="b" type="Basis"> + </argument> + <argument index="1" name="epsilon" type="float" default="0.00001"> + </argument> + <description> + </description> + </method> <method name="orthonormalized"> <return type="Basis"> </return> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index c1cf7dcabe..61ccc167a5 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -575,7 +575,7 @@ <constant name="BLEND_MODE_DISABLED" value="5" enum="BlendMode"> Disable blending mode. Colors including alpha are written as is. Only applicable for render targets with a transparent background. No lighting will be applied. </constant> - <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="29"> + <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="2000"> Canvas item transform has changed. Notification is only received if enabled by [method set_notify_transform] or [method set_notify_local_transform]. </constant> <constant name="NOTIFICATION_DRAW" value="30"> diff --git a/doc/classes/CanvasLayer.xml b/doc/classes/CanvasLayer.xml index 3d69c92ea3..04aa7fbbf2 100644 --- a/doc/classes/CanvasLayer.xml +++ b/doc/classes/CanvasLayer.xml @@ -25,6 +25,10 @@ <member name="custom_viewport" type="Node" setter="set_custom_viewport" getter="get_custom_viewport"> The custom [Viewport] node assigned to the [CanvasLayer]. If null, uses the default viewport instead. </member> + <member name="follow_viewport_enable" type="bool" setter="set_follow_viewport" getter="is_following_viewport"> + </member> + <member name="follow_viewport_scale" type="float" setter="set_follow_viewport_scale" getter="get_follow_viewport_scale"> + </member> <member name="layer" type="int" setter="set_layer" getter="get_layer"> Layer index for draw order. Lower values are drawn first. Default value: [code]1[/code]. </member> diff --git a/doc/classes/EditorFeatureProfile.xml b/doc/classes/EditorFeatureProfile.xml new file mode 100644 index 0000000000..e0e56dc009 --- /dev/null +++ b/doc/classes/EditorFeatureProfile.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorFeatureProfile" inherits="Reference" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_feature_name"> + <return type="String"> + </return> + <argument index="0" name="feature" type="int" enum="EditorFeatureProfile.Feature"> + </argument> + <description> + </description> + </method> + <method name="is_class_disabled" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <description> + </description> + </method> + <method name="is_class_editor_disabled" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <description> + </description> + </method> + <method name="is_class_property_disabled" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <argument index="1" name="arg1" type="String"> + </argument> + <description> + </description> + </method> + <method name="is_feature_disabled" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="feature" type="int" enum="EditorFeatureProfile.Feature"> + </argument> + <description> + </description> + </method> + <method name="load_from_file"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + <method name="save_to_file"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + <method name="set_disable_class"> + <return type="void"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <argument index="1" name="disable" type="bool"> + </argument> + <description> + </description> + </method> + <method name="set_disable_class_editor"> + <return type="void"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <argument index="1" name="disable" type="bool"> + </argument> + <description> + </description> + </method> + <method name="set_disable_class_property"> + <return type="void"> + </return> + <argument index="0" name="class_name" type="String"> + </argument> + <argument index="1" name="property" type="String"> + </argument> + <argument index="2" name="arg2" type="bool"> + </argument> + <description> + </description> + </method> + <method name="set_disable_feature"> + <return type="void"> + </return> + <argument index="0" name="feature" type="int" enum="EditorFeatureProfile.Feature"> + </argument> + <argument index="1" name="disable" type="bool"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + <constant name="FEATURE_3D" value="0" enum="Feature"> + </constant> + <constant name="FEATURE_SCRIPT" value="1" enum="Feature"> + </constant> + <constant name="FEATURE_ASSET_LIB" value="2" enum="Feature"> + </constant> + <constant name="FEATURE_SCENE_TREE" value="3" enum="Feature"> + </constant> + <constant name="FEATURE_IMPORT_DOCK" value="4" enum="Feature"> + </constant> + <constant name="FEATURE_NODE_DOCK" value="5" enum="Feature"> + </constant> + <constant name="FEATURE_FILESYSTEM_DOCK" value="6" enum="Feature"> + </constant> + <constant name="FEATURE_MAX" value="7" enum="Feature"> + </constant> + </constants> +</class> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 30ff609a81..2dc8d716a7 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -48,6 +48,12 @@ Returns the editor [Viewport]. </description> </method> + <method name="get_inspector" qualifiers="const"> + <return type="EditorInspector"> + </return> + <description> + </description> + </method> <method name="get_open_scenes" qualifiers="const"> <return type="Array"> </return> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 8a785ab263..79462816e3 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -227,7 +227,7 @@ </return> <argument index="0" name="path" type="String"> </argument> - <argument index="1" name="flags" type="int"> + <argument index="1" name="flags" type="int" enum="File.ModeFlags"> </argument> <description> Opens the file for writing or reading, depending on the flags. @@ -238,9 +238,9 @@ </return> <argument index="0" name="path" type="String"> </argument> - <argument index="1" name="mode_flags" type="int"> + <argument index="1" name="mode_flags" type="int" enum="File.ModeFlags"> </argument> - <argument index="2" name="compression_mode" type="int" default="0"> + <argument index="2" name="compression_mode" type="int" enum="File.CompressionMode" default="0"> </argument> <description> Opens a compressed file for reading or writing. Use COMPRESSION_* constants to set [code]compression_mode[/code]. @@ -251,7 +251,7 @@ </return> <argument index="0" name="path" type="String"> </argument> - <argument index="1" name="mode_flags" type="int"> + <argument index="1" name="mode_flags" type="int" enum="File.ModeFlags"> </argument> <argument index="2" name="key" type="PoolByteArray"> </argument> @@ -264,7 +264,7 @@ </return> <argument index="0" name="path" type="String"> </argument> - <argument index="1" name="mode_flags" type="int"> + <argument index="1" name="mode_flags" type="int" enum="File.ModeFlags"> </argument> <argument index="2" name="pass" type="String"> </argument> diff --git a/doc/classes/MainLoop.xml b/doc/classes/MainLoop.xml index 8d8d4c46cc..6b7d9cc834 100644 --- a/doc/classes/MainLoop.xml +++ b/doc/classes/MainLoop.xml @@ -114,29 +114,29 @@ </method> </methods> <constants> - <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="2"> + <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002"> </constant> - <constant name="NOTIFICATION_WM_MOUSE_EXIT" value="3"> + <constant name="NOTIFICATION_WM_MOUSE_EXIT" value="1003"> </constant> - <constant name="NOTIFICATION_WM_FOCUS_IN" value="4"> + <constant name="NOTIFICATION_WM_FOCUS_IN" value="1004"> </constant> - <constant name="NOTIFICATION_WM_FOCUS_OUT" value="5"> + <constant name="NOTIFICATION_WM_FOCUS_OUT" value="1005"> </constant> - <constant name="NOTIFICATION_WM_QUIT_REQUEST" value="6"> + <constant name="NOTIFICATION_WM_QUIT_REQUEST" value="1006"> </constant> - <constant name="NOTIFICATION_WM_GO_BACK_REQUEST" value="7"> + <constant name="NOTIFICATION_WM_GO_BACK_REQUEST" value="1007"> </constant> - <constant name="NOTIFICATION_WM_UNFOCUS_REQUEST" value="8"> + <constant name="NOTIFICATION_WM_UNFOCUS_REQUEST" value="1008"> </constant> - <constant name="NOTIFICATION_OS_MEMORY_WARNING" value="9"> + <constant name="NOTIFICATION_OS_MEMORY_WARNING" value="1009"> </constant> - <constant name="NOTIFICATION_TRANSLATION_CHANGED" value="90"> + <constant name="NOTIFICATION_TRANSLATION_CHANGED" value="1010"> </constant> - <constant name="NOTIFICATION_WM_ABOUT" value="91"> + <constant name="NOTIFICATION_WM_ABOUT" value="1011"> </constant> - <constant name="NOTIFICATION_CRASH" value="92"> + <constant name="NOTIFICATION_CRASH" value="1012"> </constant> - <constant name="NOTIFICATION_OS_IME_UPDATE" value="93"> + <constant name="NOTIFICATION_OS_IME_UPDATE" value="1013"> </constant> </constants> </class> diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index 8fb10696f2..02d87a9c48 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -68,6 +68,14 @@ <description> </description> </method> + <method name="get_item_navmesh_transform" qualifiers="const"> + <return type="Transform"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> <method name="get_item_preview" qualifiers="const"> <return type="Texture"> </return> @@ -132,6 +140,16 @@ <description> </description> </method> + <method name="set_item_navmesh_transform"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="navmesh" type="Transform"> + </argument> + <description> + </description> + </method> <method name="set_item_preview"> <return type="void"> </return> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index bf32d4041a..646b921743 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -886,15 +886,37 @@ <constant name="NOTIFICATION_PATH_CHANGED" value="23"> Notification received when the node's [NodePath] changed. </constant> - <constant name="NOTIFICATION_TRANSLATION_CHANGED" value="24"> - Notification received when translations may have changed. Can be triggered by the user changing the locale. Can be used to respond to language changes, for example to change the UI strings on the fly. Useful when working with the built-in translation support, like [method Object.tr]. - </constant> <constant name="NOTIFICATION_INTERNAL_PROCESS" value="25"> Notification received every frame when the internal process flag is set (see [method set_process_internal]). </constant> <constant name="NOTIFICATION_INTERNAL_PHYSICS_PROCESS" value="26"> Notification received every frame when the internal physics process flag is set (see [method set_physics_process_internal]). </constant> + <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002"> + </constant> + <constant name="NOTIFICATION_WM_MOUSE_EXIT" value="1003"> + </constant> + <constant name="NOTIFICATION_WM_FOCUS_IN" value="1004"> + </constant> + <constant name="NOTIFICATION_WM_FOCUS_OUT" value="1005"> + </constant> + <constant name="NOTIFICATION_WM_QUIT_REQUEST" value="1006"> + </constant> + <constant name="NOTIFICATION_WM_GO_BACK_REQUEST" value="1007"> + </constant> + <constant name="NOTIFICATION_WM_UNFOCUS_REQUEST" value="1008"> + </constant> + <constant name="NOTIFICATION_OS_MEMORY_WARNING" value="1009"> + </constant> + <constant name="NOTIFICATION_TRANSLATION_CHANGED" value="1010"> + Notification received when translations may have changed. Can be triggered by the user changing the locale. Can be used to respond to language changes, for example to change the UI strings on the fly. Useful when working with the built-in translation support, like [method Object.tr]. + </constant> + <constant name="NOTIFICATION_WM_ABOUT" value="1011"> + </constant> + <constant name="NOTIFICATION_CRASH" value="1012"> + </constant> + <constant name="NOTIFICATION_OS_IME_UPDATE" value="1013"> + </constant> <constant name="PAUSE_MODE_INHERIT" value="0" enum="PauseMode"> Inherits pause mode from the node's parent. For the root node, it is equivalent to PAUSE_MODE_STOP. Default. </constant> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index e67e3c9a03..ee3f3260d7 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -215,6 +215,8 @@ <member name="audio/channel_disable_time" type="float" setter="" getter=""> Audio buses will disable automatically when sound goes below a given DB threshold for a given time. This saves CPU as effects assigned to that bus will no longer do any processing. </member> + <member name="audio/default_bus_layout" type="String" setter="" getter=""> + </member> <member name="audio/driver" type="String" setter="" getter=""> </member> <member name="audio/enable_audio_input" type="bool" setter="" getter=""> diff --git a/doc/classes/ResourceSaver.xml b/doc/classes/ResourceSaver.xml index 165ab0dc95..079d1388f0 100644 --- a/doc/classes/ResourceSaver.xml +++ b/doc/classes/ResourceSaver.xml @@ -27,7 +27,7 @@ </argument> <argument index="1" name="resource" type="Resource"> </argument> - <argument index="2" name="flags" type="int" default="0"> + <argument index="2" name="flags" type="int" enum="ResourceSaver.SaverFlags" default="0"> </argument> <description> Saves a resource to disk. diff --git a/doc/classes/Spatial.xml b/doc/classes/Spatial.xml index 7e42e06960..f28ddcf351 100644 --- a/doc/classes/Spatial.xml +++ b/doc/classes/Spatial.xml @@ -329,7 +329,7 @@ </signal> </signals> <constants> - <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="29"> + <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="2000"> Spatial nodes receives this notification when their global transform changes. This means that either the current or a parent node changed its transform. In order for [code]NOTIFICATION_TRANSFORM_CHANGED[/code] to work, users first need to ask for it, with [method set_notify_transform]. </constant> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index c3c4b6f938..67a9b80602 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -432,6 +432,14 @@ Returns [code]true[/code] if this string is a subsequence of the given string, without considering case. </description> </method> + <method name="is_valid_filename"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if this string is free from characters that aren't allowed in file names, those being: + [code]: / \ ? * " | % < >[/code] + </description> + </method> <method name="is_valid_float"> <return type="bool"> </return> diff --git a/doc/classes/StyleBoxFlat.xml b/doc/classes/StyleBoxFlat.xml index d74f481f55..34f99755a4 100644 --- a/doc/classes/StyleBoxFlat.xml +++ b/doc/classes/StyleBoxFlat.xml @@ -148,12 +148,12 @@ <member name="shadow_color" type="Color" setter="set_shadow_color" getter="get_shadow_color"> The color of the shadow. (This has no effect when shadow_size < 1) </member> - <member name="shadow_size" type="int" setter="set_shadow_size" getter="get_shadow_size"> - The shadow size in pixels. - </member> <member name="shadow_offset" type="Vector2" setter="set_shadow_offset" getter="get_shadow_offset"> The shadow offset in pixels. Adjusts the position of the shadow relatively to the stylebox. </member> + <member name="shadow_size" type="int" setter="set_shadow_size" getter="get_shadow_size"> + The shadow size in pixels. + </member> </members> <constants> </constants> diff --git a/doc/classes/TextureRect.xml b/doc/classes/TextureRect.xml index 7f26fe18e5..eb8014bab8 100644 --- a/doc/classes/TextureRect.xml +++ b/doc/classes/TextureRect.xml @@ -16,18 +16,18 @@ <member name="expand" type="bool" setter="set_expand" getter="has_expand"> If [code]true[/code], the texture scales to fit its bounding rectangle. Default value: [code]false[/code]. </member> - <member name="stretch_mode" type="int" setter="set_stretch_mode" getter="get_stretch_mode" enum="TextureRect.StretchMode"> - Controls the texture's behavior when resizing the node's bounding rectangle. See [enum StretchMode]. - </member> - <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> - The node's [Texture] resource. - </member> <member name="flip_h" type="bool" setter="set_flip_h" getter="is_flipped_h"> If [code]true[/code], texture is flipped horizontally. Default value: [code]false[/code]. </member> <member name="flip_v" type="bool" setter="set_flip_v" getter="is_flipped_v"> If [code]true[/code], texture is flipped vertically. Default value: [code]false[/code]. </member> + <member name="stretch_mode" type="int" setter="set_stretch_mode" getter="get_stretch_mode" enum="TextureRect.StretchMode"> + Controls the texture's behavior when resizing the node's bounding rectangle. See [enum StretchMode]. + </member> + <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> + The node's [Texture] resource. + </member> </members> <constants> <constant name="STRETCH_SCALE_ON_EXPAND" value="0" enum="StretchMode"> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index d2aa1a3f03..ea558735aa 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -34,7 +34,7 @@ </argument> <argument index="2" name="userdata" type="Variant" default="null"> </argument> - <argument index="3" name="priority" type="int" default="1"> + <argument index="3" name="priority" type="int" enum="Thread.Priority" default="1"> </argument> <description> Starts a new [Thread] that runs "method" on object "instance" with "userdata" passed as an argument. The "priority" of the [Thread] can be changed by passing a PRIORITY_* enum. diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 3c047487e0..54d9e05180 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -171,7 +171,7 @@ # Write your custom logic here. # To call the default method: .set_cell(x, y, tile, flip_x, flip_y, transpose, autotile_coord) - [/codeblock] + [/codeblock] </description> </method> <method name="set_cellv"> diff --git a/doc/classes/Transform.xml b/doc/classes/Transform.xml index e110fa7b40..8c9a1a36b3 100644 --- a/doc/classes/Transform.xml +++ b/doc/classes/Transform.xml @@ -172,9 +172,9 @@ </constant> <constant name="FLIP_X" value="Transform( -1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )"> </constant> - <constant name="FLIP_Y" value="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )"> + <constant name="FLIP_Y" value="Transform( 1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0 )"> </constant> - <constant name="FLIP_Z" value="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )"> + <constant name="FLIP_Z" value="Transform( 1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0 )"> </constant> </constants> </class> diff --git a/doc/classes/VisibilityNotifier.xml b/doc/classes/VisibilityNotifier.xml index e30078d82f..062371ba29 100644 --- a/doc/classes/VisibilityNotifier.xml +++ b/doc/classes/VisibilityNotifier.xml @@ -16,6 +16,7 @@ </return> <description> If [code]true[/code], the bounding box is on the screen. + Note: It takes one frame for the node's visibility to be assessed once added to the scene tree, so this method will return [code]false[/code] right after it is instantiated, even if it will be on screen in the draw pass. </description> </method> </methods> diff --git a/doc/classes/VisibilityNotifier2D.xml b/doc/classes/VisibilityNotifier2D.xml index 5689ced4bf..7b553c7518 100644 --- a/doc/classes/VisibilityNotifier2D.xml +++ b/doc/classes/VisibilityNotifier2D.xml @@ -16,6 +16,7 @@ </return> <description> If [code]true[/code], the bounding rectangle is on the screen. + Note: It takes one frame for the node's visibility to be assessed once added to the scene tree, so this method will return [code]false[/code] right after it is instantiated, even if it will be on screen in the draw pass. </description> </method> </methods> diff --git a/doc/classes/VisualShaderNodeBooleanConstant.xml b/doc/classes/VisualShaderNodeBooleanConstant.xml new file mode 100644 index 0000000000..dc2abfff25 --- /dev/null +++ b/doc/classes/VisualShaderNodeBooleanConstant.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeBooleanConstant" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="constant" type="bool" setter="set_constant" getter="get_constant"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeBooleanUniform.xml b/doc/classes/VisualShaderNodeBooleanUniform.xml new file mode 100644 index 0000000000..06dca13426 --- /dev/null +++ b/doc/classes/VisualShaderNodeBooleanUniform.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeBooleanUniform" inherits="VisualShaderNodeUniform" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeColorFunc.xml b/doc/classes/VisualShaderNodeColorFunc.xml new file mode 100644 index 0000000000..e906049cae --- /dev/null +++ b/doc/classes/VisualShaderNodeColorFunc.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeColorFunc" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="function" type="int" setter="set_function" getter="get_function" enum="VisualShaderNodeColorFunc.Function"> + </member> + </members> + <constants> + <constant name="FUNC_GRAYSCALE" value="0" enum="Function"> + </constant> + <constant name="FUNC_SEPIA" value="1" enum="Function"> + </constant> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeDeterminant.xml b/doc/classes/VisualShaderNodeDeterminant.xml new file mode 100644 index 0000000000..661894e7b6 --- /dev/null +++ b/doc/classes/VisualShaderNodeDeterminant.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeDeterminant" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeFaceForward.xml b/doc/classes/VisualShaderNodeFaceForward.xml new file mode 100644 index 0000000000..823e251203 --- /dev/null +++ b/doc/classes/VisualShaderNodeFaceForward.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeFaceForward" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeIf.xml b/doc/classes/VisualShaderNodeIf.xml new file mode 100644 index 0000000000..4e606b95aa --- /dev/null +++ b/doc/classes/VisualShaderNodeIf.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeIf" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeOuterProduct.xml b/doc/classes/VisualShaderNodeOuterProduct.xml new file mode 100644 index 0000000000..4ea4713428 --- /dev/null +++ b/doc/classes/VisualShaderNodeOuterProduct.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeOuterProduct" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeScalarClamp.xml b/doc/classes/VisualShaderNodeScalarClamp.xml new file mode 100644 index 0000000000..66401c6bf6 --- /dev/null +++ b/doc/classes/VisualShaderNodeScalarClamp.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeScalarClamp" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeScalarDerivativeFunc.xml b/doc/classes/VisualShaderNodeScalarDerivativeFunc.xml new file mode 100644 index 0000000000..c78be81034 --- /dev/null +++ b/doc/classes/VisualShaderNodeScalarDerivativeFunc.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeScalarDerivativeFunc" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="function" type="int" setter="set_function" getter="get_function" enum="VisualShaderNodeScalarDerivativeFunc.Function"> + </member> + </members> + <constants> + <constant name="FUNC_SUM" value="0" enum="Function"> + </constant> + <constant name="FUNC_X" value="1" enum="Function"> + </constant> + <constant name="FUNC_Y" value="2" enum="Function"> + </constant> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeScalarFunc.xml b/doc/classes/VisualShaderNodeScalarFunc.xml index 67a5d3f5c4..66bdfab5a6 100644 --- a/doc/classes/VisualShaderNodeScalarFunc.xml +++ b/doc/classes/VisualShaderNodeScalarFunc.xml @@ -55,5 +55,27 @@ </constant> <constant name="FUNC_NEGATE" value="19" enum="Function"> </constant> + <constant name="FUNC_ACOSH" value="20" enum="Function"> + </constant> + <constant name="FUNC_ASINH" value="21" enum="Function"> + </constant> + <constant name="FUNC_ATANH" value="22" enum="Function"> + </constant> + <constant name="FUNC_DEGREES" value="23" enum="Function"> + </constant> + <constant name="FUNC_EXP2" value="24" enum="Function"> + </constant> + <constant name="FUNC_INVERSE_SQRT" value="25" enum="Function"> + </constant> + <constant name="FUNC_LOG2" value="26" enum="Function"> + </constant> + <constant name="FUNC_RADIANS" value="27" enum="Function"> + </constant> + <constant name="FUNC_RECIPROCAL" value="28" enum="Function"> + </constant> + <constant name="FUNC_ROUNDEVEN" value="29" enum="Function"> + </constant> + <constant name="FUNC_TRUNC" value="30" enum="Function"> + </constant> </constants> </class> diff --git a/doc/classes/VisualShaderNodeScalarOp.xml b/doc/classes/VisualShaderNodeScalarOp.xml index 34ec89f8be..c5a331679f 100644 --- a/doc/classes/VisualShaderNodeScalarOp.xml +++ b/doc/classes/VisualShaderNodeScalarOp.xml @@ -33,5 +33,7 @@ </constant> <constant name="OP_ATAN2" value="8" enum="Operator"> </constant> + <constant name="OP_STEP" value="9" enum="Operator"> + </constant> </constants> </class> diff --git a/doc/classes/VisualShaderNodeScalarSmoothStep.xml b/doc/classes/VisualShaderNodeScalarSmoothStep.xml new file mode 100644 index 0000000000..5be54d0b4f --- /dev/null +++ b/doc/classes/VisualShaderNodeScalarSmoothStep.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeScalarSmoothStep" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeSwitch.xml b/doc/classes/VisualShaderNodeSwitch.xml new file mode 100644 index 0000000000..1c346e7a1f --- /dev/null +++ b/doc/classes/VisualShaderNodeSwitch.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeSwitch" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeTransformFunc.xml b/doc/classes/VisualShaderNodeTransformFunc.xml new file mode 100644 index 0000000000..601e66ca16 --- /dev/null +++ b/doc/classes/VisualShaderNodeTransformFunc.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeTransformFunc" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="function" type="int" setter="set_function" getter="get_function" enum="VisualShaderNodeTransformFunc.Function"> + </member> + </members> + <constants> + <constant name="FUNC_INVERSE" value="0" enum="Function"> + </constant> + <constant name="FUNC_TRANSPOSE" value="1" enum="Function"> + </constant> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeTransformMult.xml b/doc/classes/VisualShaderNodeTransformMult.xml index 47026b400e..e9c445526d 100644 --- a/doc/classes/VisualShaderNodeTransformMult.xml +++ b/doc/classes/VisualShaderNodeTransformMult.xml @@ -19,5 +19,9 @@ </constant> <constant name="OP_BxA" value="1" enum="Operator"> </constant> + <constant name="OP_AxB_COMP" value="2" enum="Operator"> + </constant> + <constant name="OP_BxA_COMP" value="3" enum="Operator"> + </constant> </constants> </class> diff --git a/doc/classes/VisualShaderNodeVectorClamp.xml b/doc/classes/VisualShaderNodeVectorClamp.xml new file mode 100644 index 0000000000..677625ebce --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorClamp.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorClamp" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorDerivativeFunc.xml b/doc/classes/VisualShaderNodeVectorDerivativeFunc.xml new file mode 100644 index 0000000000..e382cb0b58 --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorDerivativeFunc.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorDerivativeFunc" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="function" type="int" setter="set_function" getter="get_function" enum="VisualShaderNodeVectorDerivativeFunc.Function"> + </member> + </members> + <constants> + <constant name="FUNC_SUM" value="0" enum="Function"> + </constant> + <constant name="FUNC_X" value="1" enum="Function"> + </constant> + <constant name="FUNC_Y" value="2" enum="Function"> + </constant> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorDistance.xml b/doc/classes/VisualShaderNodeVectorDistance.xml new file mode 100644 index 0000000000..d83a520a46 --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorDistance.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorDistance" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorFunc.xml b/doc/classes/VisualShaderNodeVectorFunc.xml index 968d73d4e1..5e57d85b4c 100644 --- a/doc/classes/VisualShaderNodeVectorFunc.xml +++ b/doc/classes/VisualShaderNodeVectorFunc.xml @@ -27,5 +27,61 @@ </constant> <constant name="FUNC_HSV2RGB" value="5" enum="Function"> </constant> + <constant name="FUNC_ABS" value="6" enum="Function"> + </constant> + <constant name="FUNC_ACOS" value="7" enum="Function"> + </constant> + <constant name="FUNC_ACOSH" value="8" enum="Function"> + </constant> + <constant name="FUNC_ASIN" value="9" enum="Function"> + </constant> + <constant name="FUNC_ASINH" value="10" enum="Function"> + </constant> + <constant name="FUNC_ATAN" value="11" enum="Function"> + </constant> + <constant name="FUNC_ATANH" value="12" enum="Function"> + </constant> + <constant name="FUNC_CEIL" value="13" enum="Function"> + </constant> + <constant name="FUNC_COS" value="14" enum="Function"> + </constant> + <constant name="FUNC_COSH" value="15" enum="Function"> + </constant> + <constant name="FUNC_DEGREES" value="16" enum="Function"> + </constant> + <constant name="FUNC_EXP" value="17" enum="Function"> + </constant> + <constant name="FUNC_EXP2" value="18" enum="Function"> + </constant> + <constant name="FUNC_FLOOR" value="19" enum="Function"> + </constant> + <constant name="FUNC_FRAC" value="20" enum="Function"> + </constant> + <constant name="FUNC_INVERSE_SQRT" value="21" enum="Function"> + </constant> + <constant name="FUNC_LOG" value="22" enum="Function"> + </constant> + <constant name="FUNC_LOG2" value="23" enum="Function"> + </constant> + <constant name="FUNC_RADIANS" value="24" enum="Function"> + </constant> + <constant name="FUNC_ROUND" value="25" enum="Function"> + </constant> + <constant name="FUNC_ROUNDEVEN" value="26" enum="Function"> + </constant> + <constant name="FUNC_SIGN" value="27" enum="Function"> + </constant> + <constant name="FUNC_SIN" value="28" enum="Function"> + </constant> + <constant name="FUNC_SINH" value="29" enum="Function"> + </constant> + <constant name="FUNC_SQRT" value="30" enum="Function"> + </constant> + <constant name="FUNC_TAN" value="31" enum="Function"> + </constant> + <constant name="FUNC_TANH" value="32" enum="Function"> + </constant> + <constant name="FUNC_TRUNC" value="33" enum="Function"> + </constant> </constants> </class> diff --git a/doc/classes/VisualShaderNodeVectorOp.xml b/doc/classes/VisualShaderNodeVectorOp.xml index 16c1f8f25d..6524f129da 100644 --- a/doc/classes/VisualShaderNodeVectorOp.xml +++ b/doc/classes/VisualShaderNodeVectorOp.xml @@ -33,5 +33,11 @@ </constant> <constant name="OP_CROSS" value="8" enum="Operator"> </constant> + <constant name="OP_ATAN2" value="9" enum="Operator"> + </constant> + <constant name="OP_REFLECT" value="10" enum="Operator"> + </constant> + <constant name="OP_STEP" value="11" enum="Operator"> + </constant> </constants> </class> diff --git a/doc/classes/VisualShaderNodeVectorRefract.xml b/doc/classes/VisualShaderNodeVectorRefract.xml new file mode 100644 index 0000000000..32bb0692d4 --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorRefract.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorRefract" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorScalarSmoothStep.xml b/doc/classes/VisualShaderNodeVectorScalarSmoothStep.xml new file mode 100644 index 0000000000..a158b4731d --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorScalarSmoothStep.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorScalarSmoothStep" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorScalarStep.xml b/doc/classes/VisualShaderNodeVectorScalarStep.xml new file mode 100644 index 0000000000..88c9b006fc --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorScalarStep.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorScalarStep" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeVectorSmoothStep.xml b/doc/classes/VisualShaderNodeVectorSmoothStep.xml new file mode 100644 index 0000000000..1ddd5f26ce --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorSmoothStep.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorSmoothStep" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/drivers/gles2/shaders/effect_blur.glsl b/drivers/gles2/shaders/effect_blur.glsl index a531802c75..df79e89931 100644 --- a/drivers/gles2/shaders/effect_blur.glsl +++ b/drivers/gles2/shaders/effect_blur.glsl @@ -116,12 +116,13 @@ void main() { #ifdef GAUSSIAN_HORIZONTAL vec2 pix_size = pixel_size; pix_size *= 0.5; //reading from larger buffer, so use more samples + // sigma 2 vec4 color = textureLod(source_color, uv_interp + vec2(0.0, 0.0) * pix_size, lod) * 0.214607; color += textureLod(source_color, uv_interp + vec2(1.0, 0.0) * pix_size, lod) * 0.189879; - color += textureLod(source_color, uv_interp + vec2(2.0, 0.0) * pix_size, lod) * 0.157305; + color += textureLod(source_color, uv_interp + vec2(2.0, 0.0) * pix_size, lod) * 0.131514; color += textureLod(source_color, uv_interp + vec2(3.0, 0.0) * pix_size, lod) * 0.071303; color += textureLod(source_color, uv_interp + vec2(-1.0, 0.0) * pix_size, lod) * 0.189879; - color += textureLod(source_color, uv_interp + vec2(-2.0, 0.0) * pix_size, lod) * 0.157305; + color += textureLod(source_color, uv_interp + vec2(-2.0, 0.0) * pix_size, lod) * 0.131514; color += textureLod(source_color, uv_interp + vec2(-3.0, 0.0) * pix_size, lod) * 0.071303; frag_color = color; #endif diff --git a/drivers/gles3/shaders/effect_blur.glsl b/drivers/gles3/shaders/effect_blur.glsl index fc15ca31b1..ff5a9f326f 100644 --- a/drivers/gles3/shaders/effect_blur.glsl +++ b/drivers/gles3/shaders/effect_blur.glsl @@ -117,12 +117,13 @@ void main() { #ifdef GAUSSIAN_HORIZONTAL vec2 pix_size = pixel_size; pix_size *= 0.5; //reading from larger buffer, so use more samples + // sigma 2 vec4 color = textureLod(source_color, uv_interp + vec2(0.0, 0.0) * pix_size, lod) * 0.214607; color += textureLod(source_color, uv_interp + vec2(1.0, 0.0) * pix_size, lod) * 0.189879; - color += textureLod(source_color, uv_interp + vec2(2.0, 0.0) * pix_size, lod) * 0.157305; + color += textureLod(source_color, uv_interp + vec2(2.0, 0.0) * pix_size, lod) * 0.131514; color += textureLod(source_color, uv_interp + vec2(3.0, 0.0) * pix_size, lod) * 0.071303; color += textureLod(source_color, uv_interp + vec2(-1.0, 0.0) * pix_size, lod) * 0.189879; - color += textureLod(source_color, uv_interp + vec2(-2.0, 0.0) * pix_size, lod) * 0.157305; + color += textureLod(source_color, uv_interp + vec2(-2.0, 0.0) * pix_size, lod) * 0.131514; color += textureLod(source_color, uv_interp + vec2(-3.0, 0.0) * pix_size, lod) * 0.071303; frag_color = color; #endif diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 8807a01f64..2307d340d8 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -109,9 +109,17 @@ public: ERR_FAIL_COND_V(key == -1, false); String name = p_name; - if (name == "time") { + if (name == "time" || name == "frame") { float new_time = p_value; + + if (name == "frame") { + float fps = animation->get_step(); + if (fps > 0) { + fps = 1.0 / fps; + } + new_time /= fps; + } if (new_time == key_ofs) return true; @@ -413,6 +421,13 @@ public: if (name == "time") { r_ret = key_ofs; return true; + } else if (name == "frame") { + float fps = animation->get_step(); + if (fps > 0) { + fps = 1.0 / fps; + } + r_ret = key_ofs * fps; + return true; } else if (name == "easing") { r_ret = animation->track_get_key_transition(track, key); return true; @@ -527,7 +542,12 @@ public: int key = animation->track_find_key(track, key_ofs, true); ERR_FAIL_COND(key == -1); - p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); + if (use_fps && animation->get_step() > 0) { + float max_frame = animation->get_length() / animation->get_step(); + p_list->push_back(PropertyInfo(Variant::REAL, "frame", PROPERTY_HINT_RANGE, "0," + rtos(max_frame) + ",0.01")); + } else { + p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); + } switch (animation->track_get_type(track)) { @@ -648,6 +668,7 @@ public: PropertyInfo hint; NodePath base; + bool use_fps; void notify_change() { @@ -658,7 +679,13 @@ public: return root_path; } + void set_use_fps(bool p_enable) { + use_fps = p_enable; + _change_notify(); + } + AnimationTrackKeyEdit() { + use_fps = false; key_ofs = 0; track = -1; setting = false; @@ -690,6 +717,9 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) { return; p_new_len = MAX(0.001, p_new_len); + if (use_fps && animation->get_step() > 0) { + p_new_len *= animation->get_step(); + } editing = true; undo_redo->create_action(TTR("Change Animation Length")); @@ -887,20 +917,49 @@ void AnimationTimelineEdit::_notification(int p_what) { decimals = 0; } - for (int i = 0; i < zoomw; i++) { + if (use_fps) { + + float step_size = animation->get_step(); + if (step_size > 0) { + + int prev_frame_ofs = -10000000; - float pos = get_value() + double(i) / scale; - float prev = get_value() + (double(i) - 1.0) / scale; + for (int i = 0; i < zoomw; i++) { - int sc = int(Math::floor(pos * SC_ADJ)); - int prev_sc = int(Math::floor(prev * SC_ADJ)); - bool sub = (sc % SC_ADJ); + float pos = get_value() + double(i) / scale; + float prev = get_value() + (double(i) - 1.0) / scale; - if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { + int frame = pos / step_size; + int prev_frame = prev / step_size; - int scd = sc < 0 ? prev_sc : sc; - draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor); - draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i); + bool sub = Math::floor(prev) == Math::floor(pos); + + if (frame != prev_frame && i >= prev_frame_ofs) { + + draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor); + + draw_string(font, Point2(get_name_limit() + i + 3 * EDSCALE, (h - font->get_height()) / 2 + font->get_ascent()).floor(), itos(frame), sub ? color_time_dec : color_time_sec, zoomw - i); + prev_frame_ofs = i + font->get_string_size(itos(frame)).x + 5 * EDSCALE; + } + } + } + + } else { + for (int i = 0; i < zoomw; i++) { + + float pos = get_value() + double(i) / scale; + float prev = get_value() + (double(i) - 1.0) / scale; + + int sc = int(Math::floor(pos * SC_ADJ)); + int prev_sc = int(Math::floor(prev * SC_ADJ)); + bool sub = (sc % SC_ADJ); + + if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { + + int scd = sc < 0 ? prev_sc : sc; + draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor); + draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i); + } } } @@ -961,7 +1020,11 @@ void AnimationTimelineEdit::update_values() { return; editing = true; - length->set_value(animation->get_length()); + if (use_fps && animation->get_step() > 0) { + length->set_value(animation->get_length() / animation->get_step()); + } else { + length->set_value(animation->get_length()); + } loop->set_pressed(animation->has_loop()); editing = false; } @@ -1046,6 +1109,15 @@ void AnimationTimelineEdit::_gui_input(const Ref<InputEvent> &p_event) { } } +void AnimationTimelineEdit::set_use_fps(bool p_use_fps) { + use_fps = p_use_fps; + update_values(); + update(); +} +bool AnimationTimelineEdit::is_using_fps() const { + return use_fps; +} + void AnimationTimelineEdit::set_hscroll(HScrollBar *p_hscroll) { hscroll = p_hscroll; @@ -1072,6 +1144,7 @@ void AnimationTimelineEdit::_bind_methods() { AnimationTimelineEdit::AnimationTimelineEdit() { + use_fps = false; editing = false; name_limit = 150; zoom = NULL; @@ -1099,7 +1172,7 @@ AnimationTimelineEdit::AnimationTimelineEdit() { len_hb->add_child(time_icon); length = memnew(EditorSpinSlider); length->set_min(0.001); - length->set_max(3600); + length->set_max(36000); length->set_step(0.01); length->set_allow_greater(true); length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0)); @@ -2462,10 +2535,12 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { hscroll->show(); edit->set_disabled(false); step->set_block_signals(true); - step->set_value(animation->get_step()); + + _update_step_spinbox(); step->set_block_signals(false); step->set_read_only(false); snap->set_disabled(false); + snap_mode->set_disabled(true); } else { hscroll->hide(); edit->set_disabled(true); @@ -2474,6 +2549,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { step->set_block_signals(false); step->set_read_only(true); snap->set_disabled(true); + snap_mode->set_disabled(false); } } @@ -2518,6 +2594,43 @@ void AnimationTrackEditor::update_keying() { bool AnimationTrackEditor::has_keying() const { return keying; } +Dictionary AnimationTrackEditor::get_state() const { + Dictionary state; + state["fps_mode"] = timeline->is_using_fps(); + state["zoom"] = zoom->get_value(); + state["offset"] = timeline->get_value(); + state["v_scroll"] = scroll->get_v_scrollbar()->get_value(); + return state; +} +void AnimationTrackEditor::set_state(const Dictionary &p_state) { + if (p_state.has("fps_mode")) { + bool fps_mode = p_state["fps_mode"]; + if (fps_mode) { + snap_mode->select(1); + } else { + snap_mode->select(0); + } + _snap_mode_changed(snap_mode->get_selected()); + } else { + snap_mode->select(0); + _snap_mode_changed(snap_mode->get_selected()); + } + if (p_state.has("zoom")) { + zoom->set_value(p_state["zoom"]); + } else { + zoom->set_value(1.0); + } + if (p_state.has("offset")) { + timeline->set_value(p_state["offset"]); + } else { + timeline->set_value(0); + } + if (p_state.has("v_scroll")) { + scroll->get_v_scrollbar()->set_value(p_state["v_scroll"]); + } else { + scroll->get_v_scrollbar()->set_value(0); + } +} void AnimationTrackEditor::cleanup() { set_animation(Ref<Animation>()); @@ -3417,6 +3530,34 @@ void AnimationTrackEditor::_animation_changed() { call_deferred("_animation_update"); } +void AnimationTrackEditor::_snap_mode_changed(int p_mode) { + + timeline->set_use_fps(p_mode == 1); + if (key_edit) { + key_edit->set_use_fps(p_mode == 1); + } + _update_step_spinbox(); +} + +void AnimationTrackEditor::_update_step_spinbox() { + if (!animation.is_valid()) { + return; + } + step->set_block_signals(true); + + if (timeline->is_using_fps()) { + if (animation->get_step() == 0) { + step->set_value(0); + } else { + step->set_value(1.0 / animation->get_step()); + } + + } else { + step->set_value(animation->get_step()); + } + + step->set_block_signals(false); +} void AnimationTrackEditor::_animation_update() { timeline->update(); @@ -3454,9 +3595,7 @@ void AnimationTrackEditor::_animation_update() { bezier_edit->update(); - step->set_block_signals(true); - step->set_value(animation->get_step()); - step->set_block_signals(false); + _update_step_spinbox(); animation_changing_awaiting_update = false; } @@ -3497,12 +3636,18 @@ void AnimationTrackEditor::_update_scroll(double) { void AnimationTrackEditor::_update_step(double p_new_step) { undo_redo->create_action(TTR("Change Animation Step")); - undo_redo->add_do_method(animation.ptr(), "set_step", p_new_step); + float step_value = p_new_step; + if (timeline->is_using_fps()) { + if (step_value != 0.0) { + step_value = 1.0 / step_value; + } + } + undo_redo->add_do_method(animation.ptr(), "set_step", step_value); undo_redo->add_undo_method(animation.ptr(), "set_step", animation->get_step()); step->set_block_signals(true); undo_redo->commit_action(); step->set_block_signals(false); - emit_signal("animation_step_changed", p_new_step); + emit_signal("animation_step_changed", step_value); } void AnimationTrackEditor::_update_length(double p_new_len) { @@ -4787,6 +4932,7 @@ void AnimationTrackEditor::_bind_methods() { ClassDB::bind_method("_edit_menu_pressed", &AnimationTrackEditor::_edit_menu_pressed); ClassDB::bind_method("_view_group_toggle", &AnimationTrackEditor::_view_group_toggle); ClassDB::bind_method("_selection_changed", &AnimationTrackEditor::_selection_changed); + ClassDB::bind_method("_snap_mode_changed", &AnimationTrackEditor::_snap_mode_changed); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); ADD_SIGNAL(MethodInfo("keying_changed")); @@ -4875,7 +5021,7 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_child(memnew(VSeparator)); snap = memnew(ToolButton); - snap->set_text(TTR("Snap (s): ")); + snap->set_text(TTR("Snap: ")); bottom_hb->add_child(snap); snap->set_disabled(true); snap->set_toggle_mode(true); @@ -4883,7 +5029,7 @@ AnimationTrackEditor::AnimationTrackEditor() { step = memnew(EditorSpinSlider); step->set_min(0); - step->set_max(1000); + step->set_max(1000000); step->set_step(0.01); step->set_hide_slider(true); step->set_custom_minimum_size(Size2(100, 0) * EDSCALE); @@ -4892,6 +5038,13 @@ AnimationTrackEditor::AnimationTrackEditor() { step->connect("value_changed", this, "_update_step"); step->set_read_only(true); + snap_mode = memnew(OptionButton); + snap_mode->add_item(TTR("Seconds")); + snap_mode->add_item(TTR("FPS")); + bottom_hb->add_child(snap_mode); + snap_mode->connect("item_selected", this, "_snap_mode_changed"); + snap_mode->set_disabled(true); + bottom_hb->add_child(memnew(VSeparator)); zoom_icon = memnew(TextureRect); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 29ce4f189e..5ac5999b68 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -76,6 +76,7 @@ class AnimationTimelineEdit : public Range { Rect2 hsize_rect; bool editing; + bool use_fps; bool panning_timeline; float panning_timeline_from; @@ -110,6 +111,9 @@ public: void update_values(); + void set_use_fps(bool p_use_fps); + bool is_using_fps() const; + void set_hscroll(HScrollBar *p_hscroll); AnimationTimelineEdit(); @@ -303,7 +307,9 @@ class AnimationTrackEditor : public VBoxContainer { EditorSpinSlider *step; TextureRect *zoom_icon; ToolButton *snap; + OptionButton *snap_mode; + void _snap_mode_changed(int p_mode); Vector<AnimationTrackEdit *> track_edits; Vector<AnimationTrackEditGroup *> groups; @@ -328,6 +334,8 @@ class AnimationTrackEditor : public VBoxContainer { void _new_track_node_selected(NodePath p_path); void _new_track_property_selected(String p_name); + void _update_step_spinbox(); + PropertySelector *prop_selector; PropertySelector *method_selector; SceneTreeDialog *pick_track; @@ -484,6 +492,9 @@ public: void update_keying(); bool has_keying() const; + Dictionary get_state() const; + void set_state(const Dictionary &p_state); + void cleanup(); void set_anim_pos(float p_pos); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 045158504a..685c5de76c 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -37,6 +37,25 @@ #include "scene/gui/label.h" #include "scene/gui/popup_menu.h" +static Node *_find_first_script(Node *p_root, Node *p_node) { + if (p_node != p_root && p_node->get_owner() != p_root) { + return NULL; + } + if (!p_node->get_script().is_null()) { + return p_node; + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + + Node *ret = _find_first_script(p_root, p_node->get_child(i)); + if (ret) { + return ret; + } + } + + return NULL; +} + class ConnectDialogBinds : public Object { GDCLASS(ConnectDialogBinds, Object); @@ -122,17 +141,8 @@ void ConnectDialog::_tree_node_selected() { Node *current = tree->get_selected(); - if (!current) { - make_callback->hide(); - return; - } - - if (current->get_script().is_null()) - make_callback->hide(); - else - make_callback->show(); - - dst_path->set_text(source->get_path_to(current)); + dst_path = source->get_path_to(current); + get_ok()->set_disabled(false); } /* @@ -195,6 +205,7 @@ void ConnectDialog::_notification(int p_what) { void ConnectDialog::_bind_methods() { + ClassDB::bind_method("_advanced_pressed", &ConnectDialog::_advanced_pressed); ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed); ClassDB::bind_method("_tree_node_selected", &ConnectDialog::_tree_node_selected); ClassDB::bind_method("_add_bind", &ConnectDialog::_add_bind); @@ -215,7 +226,7 @@ StringName ConnectDialog::get_signal_name() const { NodePath ConnectDialog::get_dst_path() const { - return dst_path->get_text(); + return dst_path; } void ConnectDialog::set_dst_node(Node *p_node) { @@ -272,8 +283,13 @@ void ConnectDialog::init(Connection c, bool bEdit) { tree->set_selected(NULL); tree->set_marked(source, true); - set_dst_node(static_cast<Node *>(c.target)); - set_dst_method(c.method); + if (c.target) { + get_ok()->set_disabled(false); + set_dst_node(static_cast<Node *>(c.target)); + set_dst_method(c.method); + } else { + get_ok()->set_disabled(true); + } bool bDeferred = (c.flags & CONNECT_DEFERRED) == CONNECT_DEFERRED; bool bOneshot = (c.flags & CONNECT_ONESHOT) == CONNECT_ONESHOT; @@ -288,6 +304,36 @@ void ConnectDialog::init(Connection c, bool bEdit) { bEditMode = bEdit; } +void ConnectDialog::popup_dialog(const String &p_for_signal, bool p_advanced) { + + advanced->set_pressed(p_advanced); + from_signal->set_text(p_for_signal); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + + if (p_advanced) { + + popup_centered(Size2(900, 500) * EDSCALE); + connect_to_label->set_text("Connect to Node:"); + tree->set_connect_to_script_mode(false); + error_label->hide(); + } else { + popup_centered(Size2(700, 500) * EDSCALE); + connect_to_label->set_text("Connect to Script:"); + tree->set_connect_to_script_mode(true); + + if (!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root())) { + error_label->show(); + } else { + error_label->hide(); + } + } +} + +void ConnectDialog::_advanced_pressed() { + vbc_right->set_visible(advanced->is_pressed()); + popup_dialog(from_signal->get_text(), advanced->is_pressed()); +} + ConnectDialog::ConnectDialog() { VBoxContainer *vbc = memnew(VBoxContainer); @@ -301,15 +347,27 @@ ConnectDialog::ConnectDialog() { main_hb->add_child(vbc_left); vbc_left->set_h_size_flags(SIZE_EXPAND_FILL); + from_signal = memnew(LineEdit); + from_signal->set_editable(false); + vbc_left->add_margin_child(TTR("From Signal:"), from_signal); + tree = memnew(SceneTreeEditor(false)); tree->get_scene_tree()->connect("item_activated", this, "_ok"); tree->connect("node_selected", this, "_tree_node_selected"); + tree->set_connect_to_script_mode(true); - vbc_left->add_margin_child(TTR("Connect To Node:"), tree, true); + Node *mc = vbc_left->add_margin_child(TTR("Connect To Script:"), tree, true); + connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1)); - VBoxContainer *vbc_right = memnew(VBoxContainer); + error_label = memnew(Label); + error_label->set_text(TTR("Scene does not contain any script.")); + vbc_left->add_child(error_label); + error_label->hide(); + + vbc_right = memnew(VBoxContainer); main_hb->add_child(vbc_right); vbc_right->set_h_size_flags(SIZE_EXPAND_FILL); + vbc_right->hide(); HBoxContainer *add_bind_hb = memnew(HBoxContainer); @@ -347,16 +405,18 @@ ConnectDialog::ConnectDialog() { vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true); - dst_path = memnew(LineEdit); - vbc->add_margin_child(TTR("Path to Node:"), dst_path); - HBoxContainer *dstm_hb = memnew(HBoxContainer); - vbc->add_margin_child("Method In Node:", dstm_hb); + vbc_left->add_margin_child("Method to Create:", dstm_hb); dst_method = memnew(LineEdit); dst_method->set_h_size_flags(SIZE_EXPAND_FILL); dstm_hb->add_child(dst_method); + advanced = memnew(CheckBox); + dstm_hb->add_child(advanced); + advanced->set_text(TTR("Advanced..")); + advanced->connect("pressed", this, "_advanced_pressed"); + /* dst_method_list = memnew( MenuButton ); dst_method_list->set_text("List..."); @@ -368,19 +428,13 @@ ConnectDialog::ConnectDialog() { dst_method_list->set_end( Point2( 15,39 ) ); */ - make_callback = memnew(CheckButton); - make_callback->set_toggle_mode(true); - make_callback->set_pressed(EDITOR_DEF("text_editor/tools/create_signal_callbacks", true)); - make_callback->set_text(TTR("Make Function")); - dstm_hb->add_child(make_callback); - deferred = memnew(CheckButton); deferred->set_text(TTR("Deferred")); - dstm_hb->add_child(deferred); + vbc_right->add_child(deferred); oneshot = memnew(CheckButton); oneshot->set_text(TTR("Oneshot")); - dstm_hb->add_child(oneshot); + vbc_right->add_child(oneshot); set_as_toplevel(true); @@ -429,7 +483,8 @@ void ConnectionsDock::_make_or_edit_connection() { bool oshot = connect_dialog->get_oneshot(); cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); - bool add_script_function = connect_dialog->get_make_callback(); + //conditions to add function, must have a script and must have a method + bool add_script_function = !target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method); PoolStringArray script_function_args; if (add_script_function) { // pick up args here before "it" is deleted by update_tree @@ -568,6 +623,7 @@ bool ConnectionsDock::_is_item_signal(TreeItem &item) { /* Open connection dialog with TreeItem data to CREATE a brand-new connection. */ + void ConnectionsDock::_open_connection_dialog(TreeItem &item) { String signal = item.get_metadata(0).operator Dictionary()["name"]; @@ -590,6 +646,10 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &item) { } Node *dst_node = selectedNode->get_owner() ? selectedNode->get_owner() : selectedNode; + if (!dst_node || dst_node->get_script().is_null()) { + dst_node = _find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root()); + } + StringName dst_method = "_on_" + midname + "_" + signal; Connection c; @@ -598,9 +658,10 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &item) { c.target = dst_node; c.method = dst_method; + //connect_dialog->set_title(TTR("Connect Signal: ") + signalname); + connect_dialog->popup_dialog(signalname, false); connect_dialog->init(c); - connect_dialog->set_title(TTR("Connect Signal: ") + signalname); - connect_dialog->popup_centered_ratio(); + connect_dialog->set_title(TTR("Connect a Signal to a Method")); } /* @@ -612,9 +673,9 @@ void ConnectionsDock::_open_connection_dialog(Connection cToEdit) { Node *dst = static_cast<Node *>(cToEdit.target); if (src && dst) { - connect_dialog->init(cToEdit, true); - connect_dialog->set_title(TTR("Edit Connection: ") + cToEdit.signal); + connect_dialog->set_title(TTR("Edit Connection:") + cToEdit.signal); connect_dialog->popup_centered_ratio(); + connect_dialog->init(cToEdit, true); } } diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 0e7e172ebb..59fe6dacfe 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -53,12 +53,15 @@ class ConnectDialog : public ConfirmationDialog { GDCLASS(ConnectDialog, ConfirmationDialog); + Label *connect_to_label; + LineEdit *from_signal; Node *source; StringName signal; - LineEdit *dst_path; LineEdit *dst_method; ConnectDialogBinds *cdbinds; bool bEditMode; + NodePath dst_path; + VBoxContainer *vbc_right; SceneTreeEditor *tree; ConfirmationDialog *error; @@ -66,13 +69,16 @@ class ConnectDialog : public ConfirmationDialog { OptionButton *type_list; CheckButton *deferred; CheckButton *oneshot; - CheckButton *make_callback; + CheckBox *advanced; + + Label *error_label; void ok_pressed(); void _cancel_pressed(); void _tree_node_selected(); void _add_bind(); void _remove_bind(); + void _advanced_pressed(); protected: void _notification(int p_what); @@ -87,13 +93,13 @@ public: void set_dst_method(const StringName &p_method); Vector<Variant> get_binds() const; - bool get_make_callback() { return make_callback->is_visible() && make_callback->is_pressed(); } bool get_deferred() const; bool get_oneshot() const; bool is_editing() const; void init(Connection c, bool bEdit = false); + void popup_dialog(const String &p_for_signal, bool p_advanced); ConnectDialog(); ~ConnectDialog(); }; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 0869f6ce77..9641e10114 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -687,7 +687,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("button_margin", "Tree", default_margin_size * EDSCALE); theme->set_constant("draw_relationship_lines", "Tree", relationship_line_opacity >= 0.01); theme->set_constant("draw_guides", "Tree", relationship_line_opacity < 0.01); - theme->set_constant("scroll_border", "Tree", default_margin_size * EDSCALE); + theme->set_constant("scroll_border", "Tree", 40 * EDSCALE); theme->set_constant("scroll_speed", "Tree", 12); Ref<StyleBoxFlat> style_tree_btn = style_default->duplicate(); diff --git a/editor/icons/icon_auto_key.svg b/editor/icons/icon_auto_key.svg new file mode 100644 index 0000000000..cbafe1ac38 --- /dev/null +++ b/editor/icons/icon_auto_key.svg @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_auto_key.svg" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1854" + inkscape:window-height="1016" + id="namedview8" + showgrid="false" + inkscape:zoom="10.429825" + inkscape:cx="10.199345" + inkscape:cy="-4.0344119" + inkscape:window-x="66" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg6" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.0333107" + d="M 3.5469681,13.426786 C 2.7965829,13.263778 2.2774312,12.503915 2.4037297,11.753472 c 0.1081234,-0.642451 0.6006808,-1.135008 1.2431317,-1.243131 0.9667125,-0.162696 1.8555225,0.726112 1.6928259,1.692826 -0.103766,0.616558 -0.5592173,1.098057 -1.1588427,1.225117 -0.2719576,0.05763 -0.3626872,0.05741 -0.6338765,-0.0014 z m 8.0861339,-0.08275 c -0.746862,-0.13829 -1.23937,-0.720718 -1.23937,-1.465649 0,-0.527377 0.244831,-0.978806 0.679757,-1.253362 0.471386,-0.297574 1.114188,-0.297574 1.585574,0 0.682727,0.430986 0.892336,1.362194 0.460575,2.046149 -0.307786,0.487563 -0.940521,0.773963 -1.486536,0.672862 z M 0.60726032,9.8305658 V 7.7161233 L 1.1770842,7.7070075 1.7469079,7.6978939 3.1889882,5.1995916 4.6310686,2.7012893 h 3.1726318 3.1726316 l 1.442755,2.4983023 1.442755,2.4983023 0.651097,0.00903 0.651096,0.00903 v 2.1145264 2.1145257 h -0.566282 -0.566281 v -0.161225 c 0,-0.234927 -0.113135,-0.639704 -0.255664,-0.914727 -0.16895,-0.326004 -0.574198,-0.731251 -0.900202,-0.9002019 -0.656732,-0.3403483 -1.428549,-0.3403483 -2.085281,0 -0.326004,0.1689519 -0.731252,0.5741989 -0.9002019,0.9002029 -0.1425297,0.275023 -0.2556639,0.6798 -0.2556639,0.914727 v 0.161225 H 7.8570969 6.0797346 L 6.0617736,11.686851 C 6.006289,10.889347 5.447548,10.170679 4.6603773,9.884336 4.4466221,9.8065798 4.3737631,9.797427 3.9716406,9.7978134 3.5871254,9.7981885 3.4905638,9.809405 3.3054265,9.8752358 2.5067319,10.159236 1.9362359,10.884501 1.8813215,11.68568 l -0.017772,0.259329 H 1.2354063 0.60726287 Z M 12.399247,7.7466889 c 0,-0.037287 -0.02623,-0.1073444 -0.0583,-0.1556843 -0.03206,-0.04834 -0.561225,-0.958444 -1.17592,-2.0224529 L 10.047407,3.6339894 7.6977565,3.6254406 C 5.4917229,3.6174174 5.3450379,3.6204563 5.2979001,3.6754094 5.1898818,3.8013046 2.9723198,7.6840061 2.9723198,7.7472381 c 0,0.067139 0.00758,0.067247 4.7134636,0.067247 h 4.7134636 z" + id="path6243" + inkscape:connector-curvature="0" /> +</svg> diff --git a/editor/icons/icon_sprite_sheet.svg b/editor/icons/icon_sprite_sheet.svg new file mode 100644 index 0000000000..eeb804f8b9 --- /dev/null +++ b/editor/icons/icon_sprite_sheet.svg @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_sprite_sheet.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="773" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="551" + inkscape:window-y="278" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g4"> + <path + transform="translate(0 1036.4)" + d="m3 1c-1.1046 0-2 0.89543-2 2v10c0 1.1046 0.89543 2 2 2h10c1.1046 0 2-0.89543 2-2v-10c0-1.1046-0.89543-2-2-2h-10zm0 2h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm-8 4h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm-8 4h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2z" + fill="#a5efac" + id="path2" + style="fill:#e0e0e0;fill-opacity:1" /> + </g> +</svg> diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index bbaf41e3cc..41f35c3bed 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -670,6 +670,7 @@ Dictionary AnimationPlayerEditor::get_state() const { if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) { d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player); d["animation"] = player->get_assigned_animation(); + d["track_editor_state"] = track_editor->get_state(); } return d; @@ -696,6 +697,10 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { _animation_edit(); } } + + if (p_state.has("track_editor_state")) { + track_editor->set_state(p_state["track_editor_state"]); + } } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index c3cac582ad..b2923a1ff2 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1340,6 +1340,10 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { // Confirms the node rotation if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { _commit_canvas_item_state(drag_selection, TTR("Rotate CanvasItem")); + if (key_auto_insert_button->is_pressed()) { + _insert_animation_keys(false, true, false, true); + } + drag_type = DRAG_NONE; return true; } @@ -1641,6 +1645,9 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { // Confirm resize if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { _commit_canvas_item_state(drag_selection, TTR("Resize CanvasItem")); + if (key_auto_insert_button->is_pressed()) { + _insert_animation_keys(false, false, true, true); + } drag_type = DRAG_NONE; viewport->update(); return true; @@ -1747,6 +1754,10 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Confirm resize if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { _commit_canvas_item_state(drag_selection, TTR("Scale CanvasItem")); + if (key_auto_insert_button->is_pressed()) { + _insert_animation_keys(false, false, true, true); + } + drag_type = DRAG_NONE; viewport->update(); return true; @@ -1852,6 +1863,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { _commit_canvas_item_state(drag_selection, TTR("Move CanvasItem"), true); } + if (key_auto_insert_button->is_pressed()) { + _insert_animation_keys(true, false, false, true); + } drag_type = DRAG_NONE; viewport->update(); return true; @@ -3384,6 +3398,7 @@ void CanvasItemEditor::_notification(int p_what) { key_rot_button->set_icon(get_icon("KeyRotation", "EditorIcons")); key_scale_button->set_icon(get_icon("KeyScale", "EditorIcons")); key_insert_button->set_icon(get_icon("Key", "EditorIcons")); + key_auto_insert_button->set_icon(get_icon("AutoKey", "EditorIcons")); zoom_minus->set_icon(get_icon("ZoomLess", "EditorIcons")); zoom_reset->set_icon(get_icon("ZoomReset", "EditorIcons")); @@ -3716,6 +3731,77 @@ void CanvasItemEditor::_button_tool_select(int p_index) { tool = (Tool)p_index; } +void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { + + Map<Node *, Object *> &selection = editor_selection->get_selection(); + + for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { + + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + if (!canvas_item || !canvas_item->is_visible_in_tree()) + continue; + + if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) + continue; + + if (Object::cast_to<Node2D>(canvas_item)) { + Node2D *n2d = Object::cast_to<Node2D>(canvas_item); + + if (key_pos && p_location) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + if (key_rot && p_rotation) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), p_on_existing); + if (key_scale && p_scale) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + + if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { + //look for an IK chain + List<Node2D *> ik_chain; + + Node2D *n = Object::cast_to<Node2D>(n2d->get_parent_item()); + bool has_chain = false; + + while (n) { + + ik_chain.push_back(n); + if (n->has_meta("_edit_ik_")) { + has_chain = true; + break; + } + + if (!n->get_parent_item()) + break; + n = Object::cast_to<Node2D>(n->get_parent_item()); + } + + if (has_chain && ik_chain.size()) { + + for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) { + + if (key_pos) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), p_on_existing); + if (key_rot) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), p_on_existing); + if (key_scale) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), p_on_existing); + } + } + } + + } else if (Object::cast_to<Control>(canvas_item)) { + + Control *ctrl = Object::cast_to<Control>(canvas_item); + + if (key_pos) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); + if (key_rot) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), p_on_existing); + if (key_scale) + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + } + } +} + void CanvasItemEditor::_popup_callback(int p_op) { last_option = MenuOption(p_op); @@ -3983,73 +4069,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { bool existing = p_op == ANIM_INSERT_KEY_EXISTING; - Map<Node *, Object *> &selection = editor_selection->get_selection(); - - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); - if (!canvas_item || !canvas_item->is_visible_in_tree()) - continue; - - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) - continue; - - if (Object::cast_to<Node2D>(canvas_item)) { - Node2D *n2d = Object::cast_to<Node2D>(canvas_item); - - if (key_pos) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing); - if (key_rot) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing); - if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing); - - if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { - //look for an IK chain - List<Node2D *> ik_chain; - - Node2D *n = Object::cast_to<Node2D>(n2d->get_parent_item()); - bool has_chain = false; - - while (n) { - - ik_chain.push_back(n); - if (n->has_meta("_edit_ik_")) { - has_chain = true; - break; - } - - if (!n->get_parent_item()) - break; - n = Object::cast_to<Node2D>(n->get_parent_item()); - } - - if (has_chain && ik_chain.size()) { - - for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) { - - if (key_pos) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing); - if (key_rot) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing); - if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing); - } - } - } - - } else if (Object::cast_to<Control>(canvas_item)) { - - Control *ctrl = Object::cast_to<Control>(canvas_item); - - if (key_pos) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing); - if (key_rot) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing); - if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing); - } - } + _insert_animation_keys(true, true, true, existing); } break; case ANIM_INSERT_POS: { @@ -4866,6 +4886,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_loc_button->set_pressed(true); key_loc_button->set_focus_mode(FOCUS_NONE); key_loc_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_POS)); + key_loc_button->set_tooltip(TTR("Translation mask for inserting keys.")); animation_hb->add_child(key_loc_button); key_rot_button = memnew(Button); key_rot_button->set_toggle_mode(true); @@ -4873,21 +4894,30 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_rot_button->set_pressed(true); key_rot_button->set_focus_mode(FOCUS_NONE); key_rot_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_ROT)); + key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys.")); animation_hb->add_child(key_rot_button); key_scale_button = memnew(Button); key_scale_button->set_toggle_mode(true); key_scale_button->set_flat(true); key_scale_button->set_focus_mode(FOCUS_NONE); key_scale_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_SCALE)); + key_scale_button->set_tooltip(TTR("Scale mask for inserting keys.")); animation_hb->add_child(key_scale_button); key_insert_button = memnew(Button); key_insert_button->set_flat(true); key_insert_button->set_focus_mode(FOCUS_NONE); key_insert_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_KEY)); - key_insert_button->set_tooltip(TTR("Insert keys.")); + key_insert_button->set_tooltip(TTR("Insert keys (based on mask).")); key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTR("Insert Key"), KEY_INSERT)); - animation_hb->add_child(key_insert_button); + key_auto_insert_button = memnew(Button); + key_auto_insert_button->set_flat(true); + key_auto_insert_button->set_toggle_mode(true); + key_auto_insert_button->set_focus_mode(FOCUS_NONE); + //key_auto_insert_button->connect("pressed", this, "_popup_callback", varray(ANIM_INSERT_KEY)); + key_auto_insert_button->set_tooltip(TTR("Auto insert keys when objects are translated, rotated on scaled (based on mask).\nKeys are only added to existing tracks, no new tracks will be created.\nKeys must be inserted manually for the first time.")); + key_auto_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_auto_insert_key", TTR("Auto Insert Key"))); + animation_hb->add_child(key_auto_insert_button); animation_menu = memnew(MenuButton); animation_menu->set_text(TTR("Animation")); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 9173c55ae0..14ea81f302 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -351,6 +351,7 @@ private: Button *key_rot_button; Button *key_scale_button; Button *key_insert_button; + Button *key_auto_insert_button; PopupMenu *selection_menu; @@ -422,6 +423,8 @@ private: Object *_get_editor_data(Object *p_what); + void _insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing); + void _keying_changed(); void _unhandled_key_input(const Ref<InputEvent> &p_ev); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 5ba2fde763..33b8347f94 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -38,11 +38,167 @@ void SpriteFramesEditor::_gui_input(Ref<InputEvent> p_event) { } +void SpriteFramesEditor::_open_sprite_sheet() { + + file_split_sheet->clear_filters(); + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Texture", &extensions); + for (int i = 0; i < extensions.size(); i++) { + file_split_sheet->add_filter("*." + extensions[i]); + } + + file_split_sheet->popup_centered_ratio(); +} + +void SpriteFramesEditor::_sheet_preview_draw() { + Size2i size = split_sheet_preview->get_size(); + int h = split_sheet_h->get_value(); + int v = split_sheet_v->get_value(); + const float a = 0.3; + for (int i = 1; i < h; i++) { + for (int j = 1; j < v; j++) { + + int x = i * size.width / h; + int y = i * size.height / v; + + split_sheet_preview->draw_line(Point2(x, 0), Point2(x, size.height), Color(1, 1, 1, a)); + split_sheet_preview->draw_line(Point2(x + 1, 0), Point2(x + 1, size.height), Color(0, 0, 0, a)); + + split_sheet_preview->draw_line(Point2(0, y), Point2(size.width, y), Color(1, 1, 1, a)); + split_sheet_preview->draw_line(Point2(0, y + 1), Point2(size.width, y + 1), Color(0, 0, 0, a)); + } + } + + Color accent = get_color("accent_color", "Editor"); + + for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { + int idx = E->get(); + int x = (idx % h) * size.width / h; + int y = (idx / v) * size.height / v; + int width = size.width / h; + int height = size.height / v; + + split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 0.35), true); + split_sheet_preview->draw_rect(Rect2(x + 0, y + 0, width - 0, height - 0), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(x + 1, y + 1, width - 2, height - 2), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(x + 2, y + 2, width - 4, height - 4), accent, false); + split_sheet_preview->draw_rect(Rect2(x + 3, y + 3, width - 6, height - 6), accent, false); + split_sheet_preview->draw_rect(Rect2(x + 4, y + 4, width - 8, height - 8), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 1), false); + } + + if (frames_selected.size() == 0) { + split_sheet_dialog->get_ok()->set_disabled(true); + split_sheet_dialog->get_ok()->set_text(TTR("No frames selected")); + } else { + split_sheet_dialog->get_ok()->set_disabled(false); + split_sheet_dialog->get_ok()->set_text(vformat(TTR("Add %d frame(s)"), frames_selected.size())); + } +} +void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + Size2i size = split_sheet_preview->get_size(); + int h = split_sheet_h->get_value(); + int v = split_sheet_v->get_value(); + + int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); + int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); + + int idx = h * y + x; + + if (mb->get_shift() && last_frame_selected >= 0) { + //select multiple + int from = idx; + int to = last_frame_selected; + if (from > to) { + SWAP(from, to); + } + + for (int i = from; i <= to; i++) { + if (mb->get_control()) { + frames_selected.erase(i); + } else { + frames_selected.insert(i); + } + } + } else { + if (frames_selected.has(idx)) { + frames_selected.erase(idx); + } else { + frames_selected.insert(idx); + } + } + + last_frame_selected = idx; + split_sheet_preview->update(); + } +} + +void SpriteFramesEditor::_sheet_add_frames() { + + Size2i size = split_sheet_preview->get_size(); + int h = split_sheet_h->get_value(); + int v = split_sheet_v->get_value(); + + undo_redo->create_action(TTR("Add Frame")); + + int fc = frames->get_frame_count(edited_anim); + + for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { + int idx = E->get(); + int x = (idx % h) * size.width / h; + int y = (idx / v) * size.height / v; + int width = size.width / h; + int height = size.height / v; + + Ref<AtlasTexture> at; + at.instance(); + at->set_atlas(split_sheet_preview->get_texture()); + at->set_region(Rect2(x, y, width, height)); + + undo_redo->add_do_method(frames, "add_frame", edited_anim, at, -1); + undo_redo->add_undo_method(frames, "remove_frame", edited_anim, fc); + } + + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); +} + +void SpriteFramesEditor::_sheet_spin_changed(double) { + frames_selected.clear(); + last_frame_selected = -1; + split_sheet_preview->update(); +} + +void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { + + Ref<Resource> texture = ResourceLoader::load(p_file); + if (!texture.is_valid()) { + EditorNode::get_singleton()->show_warning("Unable to load images"); + ERR_FAIL_COND(!texture.is_valid()); + } + if (texture != split_sheet_preview->get_texture()) { + //different texture, reset to 4x4 + split_sheet_h->set_value(4); + split_sheet_v->set_value(4); + } + frames_selected.clear(); + last_frame_selected = -1; + + split_sheet_preview->set_texture(texture); + split_sheet_dialog->popup_centered_ratio(0.65); +} + void SpriteFramesEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { load->set_icon(get_icon("Load", "EditorIcons")); + load_sheet->set_icon(get_icon("SpriteSheet", "EditorIcons")); copy->set_icon(get_icon("ActionCopy", "EditorIcons")); paste->set_icon(get_icon("ActionPaste", "EditorIcons")); empty->set_icon(get_icon("InsertBefore", "EditorIcons")); @@ -72,6 +228,7 @@ void SpriteFramesEditor::_file_load_request(const PoolVector<String> &p_path, in if (resource.is_null()) { dialog->set_text(TTR("ERROR: Couldn't load frame resource!")); dialog->set_title(TTR("Error!")); + //dialog->get_cancel()->set_text("Close"); dialog->get_ok()->set_text(TTR("Close")); dialog->popup_centered_minsize(); @@ -655,6 +812,12 @@ void SpriteFramesEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw); ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw); ClassDB::bind_method(D_METHOD("drop_data_fw"), &SpriteFramesEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_prepare_sprite_sheet"), &SpriteFramesEditor::_prepare_sprite_sheet); + ClassDB::bind_method(D_METHOD("_open_sprite_sheet"), &SpriteFramesEditor::_open_sprite_sheet); + ClassDB::bind_method(D_METHOD("_sheet_preview_draw"), &SpriteFramesEditor::_sheet_preview_draw); + ClassDB::bind_method(D_METHOD("_sheet_preview_input"), &SpriteFramesEditor::_sheet_preview_input); + ClassDB::bind_method(D_METHOD("_sheet_spin_changed"), &SpriteFramesEditor::_sheet_spin_changed); + ClassDB::bind_method(D_METHOD("_sheet_add_frames"), &SpriteFramesEditor::_sheet_add_frames); } SpriteFramesEditor::SpriteFramesEditor() { @@ -712,9 +875,15 @@ SpriteFramesEditor::SpriteFramesEditor() { sub_vb->add_child(hbc); load = memnew(ToolButton); - load->set_tooltip(TTR("Load Resource")); + load->set_tooltip(TTR("Add a Texture from File")); hbc->add_child(load); + load_sheet = memnew(ToolButton); + load_sheet->set_tooltip(TTR("Add frames from a Sprite Sheet")); + hbc->add_child(load_sheet); + + hbc->add_child(memnew(VSeparator)); + copy = memnew(ToolButton); copy->set_tooltip(TTR("Copy")); hbc->add_child(copy); @@ -723,6 +892,8 @@ SpriteFramesEditor::SpriteFramesEditor() { paste->set_tooltip(TTR("Paste")); hbc->add_child(paste); + hbc->add_spacer(false); + empty = memnew(ToolButton); empty->set_tooltip(TTR("Insert Empty (Before)")); hbc->add_child(empty); @@ -731,7 +902,7 @@ SpriteFramesEditor::SpriteFramesEditor() { empty2->set_tooltip(TTR("Insert Empty (After)")); hbc->add_child(empty2); - hbc->add_spacer(false); + hbc->add_child(memnew(VSeparator)); move_up = memnew(ToolButton); move_up->set_tooltip(TTR("Move (Before)")); @@ -766,6 +937,7 @@ SpriteFramesEditor::SpriteFramesEditor() { add_child(dialog); load->connect("pressed", this, "_load_pressed"); + load_sheet->connect("pressed", this, "_open_sprite_sheet"); _delete->connect("pressed", this, "_delete_pressed"); copy->connect("pressed", this, "_copy_pressed"); paste->connect("pressed", this, "_paste_pressed"); @@ -780,6 +952,60 @@ SpriteFramesEditor::SpriteFramesEditor() { updating = false; edited_anim = "default"; + + split_sheet_dialog = memnew(ConfirmationDialog); + add_child(split_sheet_dialog); + VBoxContainer *split_sheet_vb = memnew(VBoxContainer); + split_sheet_dialog->add_child(split_sheet_vb); + split_sheet_dialog->set_title(TTR("Select Frames")); + split_sheet_dialog->connect("confirmed", this, "_sheet_add_frames"); + + ScrollContainer *scroll = memnew(ScrollContainer); + split_sheet_preview = memnew(TextureRect); + split_sheet_preview->set_expand(false); + split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS); + split_sheet_preview->connect("draw", this, "_sheet_preview_draw"); + split_sheet_preview->connect("gui_input", this, "_sheet_preview_input"); + + scroll->set_enable_h_scroll(true); + scroll->set_enable_v_scroll(true); + CenterContainer *cc = memnew(CenterContainer); + cc->add_child(split_sheet_preview); + cc->set_h_size_flags(SIZE_EXPAND_FILL); + cc->set_v_size_flags(SIZE_EXPAND_FILL); + scroll->add_child(cc); + + split_sheet_vb->add_margin_child(TTR("Base Image:"), scroll, true); + + HBoxContainer *split_sheet_hb = memnew(HBoxContainer); + split_sheet_hb->add_spacer(); + Label *ss_label = memnew(Label(TTR("Horizontal:"))); + split_sheet_hb->add_child(ss_label); + split_sheet_h = memnew(SpinBox); + split_sheet_h->set_min(1); + split_sheet_h->set_max(128); + split_sheet_h->set_step(1); + split_sheet_hb->add_child(split_sheet_h); + split_sheet_hb->add_spacer(); + split_sheet_h->connect("value_changed", this, "_sheet_spin_changed"); + + ss_label = memnew(Label(TTR("Vertical:"))); + split_sheet_hb->add_child(ss_label); + split_sheet_v = memnew(SpinBox); + split_sheet_v->set_min(1); + split_sheet_v->set_max(128); + split_sheet_v->set_step(1); + split_sheet_hb->add_child(split_sheet_v); + split_sheet_hb->add_spacer(); + split_sheet_v->connect("value_changed", this, "_sheet_spin_changed"); + + split_sheet_vb->add_margin_child(TTR("Split Settings:"), split_sheet_hb); + + file_split_sheet = memnew(EditorFileDialog); + file_split_sheet->set_title(TTR("Create frames from Sprite Sheet")); + file_split_sheet->set_mode(EditorFileDialog::MODE_OPEN_FILE); + add_child(file_split_sheet); + file_split_sheet->connect("file_selected", this, "_prepare_sprite_sheet"); } void SpriteFramesEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 55dd10074e..383e99f87e 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -37,6 +37,7 @@ #include "scene/gui/dialogs.h" #include "scene/gui/file_dialog.h" #include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" class SpriteFramesEditor : public HSplitContainer { @@ -44,6 +45,7 @@ class SpriteFramesEditor : public HSplitContainer { GDCLASS(SpriteFramesEditor, HSplitContainer); ToolButton *load; + ToolButton *load_sheet; ToolButton *_delete; ToolButton *copy; ToolButton *paste; @@ -71,6 +73,14 @@ class SpriteFramesEditor : public HSplitContainer { StringName edited_anim; + ConfirmationDialog *split_sheet_dialog; + TextureRect *split_sheet_preview; + SpinBox *split_sheet_h; + SpinBox *split_sheet_v; + EditorFileDialog *file_split_sheet; + Set<int> frames_selected; + int last_frame_selected; + void _load_pressed(); void _load_scene_pressed(); void _file_load_request(const PoolVector<String> &p_path, int p_at_pos = -1); @@ -99,6 +109,13 @@ class SpriteFramesEditor : public HSplitContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void _open_sprite_sheet(); + void _prepare_sprite_sheet(const String &p_file); + void _sheet_preview_draw(); + void _sheet_spin_changed(double); + void _sheet_preview_input(const Ref<InputEvent> &p_event); + void _sheet_add_frames(); + protected: void _notification(int p_what); void _gui_input(Ref<InputEvent> p_event); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 0eeb104777..0aba7f3d15 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1004,6 +1004,58 @@ void VisualShaderEditor::_duplicate_nodes() { } } +void VisualShaderEditor::_on_nodes_delete() { + + VisualShader::Type type = VisualShader::Type(edit_type->get_selected()); + List<int> to_erase; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_erase.empty()) + return; + + undo_redo->create_action(TTR("Delete Nodes")); + + for (List<int>::Element *F = to_erase.front(); F; F = F->next()) { + undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F->get()); + undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, visual_shader->get_node(type, F->get()), visual_shader->get_node_position(type, F->get()), F->get()); + } + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + List<VisualShader::Connection> used_conns; + for (List<int>::Element *F = to_erase.front(); F; F = F->next()) { + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().from_node == F->get() || E->get().to_node == F->get()) { + + bool cancel = false; + for (List<VisualShader::Connection>::Element *R = used_conns.front(); R; R = R->next()) { + if (R->get().from_node == E->get().from_node && R->get().from_port == E->get().from_port && R->get().to_node == E->get().to_node && R->get().to_port == E->get().to_port) { + cancel = true; // to avoid ERR_ALREADY_EXISTS warning + break; + } + } + if (!cancel) { + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + used_conns.push_back(E->get()); + } + } + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + void VisualShaderEditor::_mode_selected(int p_id) { _update_options_menu(); _update_graph(); @@ -1175,6 +1227,7 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_node_selected", &VisualShaderEditor::_node_selected); ClassDB::bind_method("_scroll_changed", &VisualShaderEditor::_scroll_changed); ClassDB::bind_method("_delete_request", &VisualShaderEditor::_delete_request); + ClassDB::bind_method("_on_nodes_delete", &VisualShaderEditor::_on_nodes_delete); ClassDB::bind_method("_node_changed", &VisualShaderEditor::_node_changed); ClassDB::bind_method("_edit_port_default_input", &VisualShaderEditor::_edit_port_default_input); ClassDB::bind_method("_port_edited", &VisualShaderEditor::_port_edited); @@ -1224,6 +1277,7 @@ VisualShaderEditor::VisualShaderEditor() { graph->connect("node_selected", this, "_node_selected"); graph->connect("scroll_offset_changed", this, "_scroll_changed"); graph->connect("duplicate_nodes_request", this, "_duplicate_nodes"); + graph->connect("delete_nodes_request", this, "_on_nodes_delete"); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_BOOLEAN); @@ -1346,9 +1400,10 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("ColorUniform", "Color", "Variables", "VisualShaderNodeColorUniform", TTR("Color uniform."), -1, VisualShaderNode::PORT_TYPE_COLOR)); // BOOLEAN - - add_options.push_back(AddOption("BooleanConstant", "Boolean", "Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("BooleanUniform", "Boolean", "Variables", "VisualShaderNodeBooleanUniform", TTR("Boolean uniform."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("If", "Conditional", "Functions", "VisualShaderNodeIf", TTR("Returns an associated vector if the provided scalars are equal, greater or less."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("Switch", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated vector if the provided boolean value is true or false."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("BooleanConstant", "Conditional", "Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("BooleanUniform", "Conditional", "Variables", "VisualShaderNodeBooleanUniform", TTR("Boolean uniform."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); // INPUT diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 2709d72931..4b0b48ad92 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -143,6 +143,7 @@ class VisualShaderEditor : public VBoxContainer { void _node_selected(Object *p_node); void _delete_request(int); + void _on_nodes_delete(); void _removed_from_graph(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 752c1afff2..bd245d5da9 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1741,7 +1741,7 @@ void SceneTreeDock::_update_script_button() { button_clear_script->show(); } } else { - button_create_script->show(); + button_create_script->hide(); Array selection = editor_selection->get_selected_nodes(); for (int i = 0; i < selection.size(); i++) { Node *n = Object::cast_to<Node>(selection[i]); @@ -2208,17 +2208,20 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { if (profile_allow_script_editing) { - if (!existing_script.is_valid()) { - menu->add_icon_shortcut(get_icon("ScriptCreate", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT); + if (selection.size() == 1) { + if (!existing_script.is_valid()) { + menu->add_icon_shortcut(get_icon("ScriptCreate", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT); + } else { + menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_ATTACH_SCRIPT); + } } if (selection.size() > 1 || existing_script.is_valid()) { menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/clear_script"), TOOL_CLEAR_SCRIPT); - menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_ATTACH_SCRIPT); } + menu->add_separator(); } if (profile_allow_editing) { - menu->add_separator(); if (selection.size() == 1) { menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/rename"), TOOL_RENAME); } @@ -2230,12 +2233,12 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_icon("MoveDown", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/move_down"), TOOL_MOVE_DOWN); menu->add_icon_shortcut(get_icon("Duplicate", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/duplicate"), TOOL_DUPLICATE); menu->add_icon_shortcut(get_icon("Reparent", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/reparent"), TOOL_REPARENT); + menu->add_icon_shortcut(get_icon("NewRoot", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/make_root"), TOOL_MAKE_ROOT); } } if (selection.size() == 1) { if (profile_allow_editing) { - menu->add_icon_shortcut(get_icon("NewRoot", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/make_root"), TOOL_MAKE_ROOT); menu->add_separator(); menu->add_icon_shortcut(get_icon("Blend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/merge_from_scene"), TOOL_MERGE_FROM_SCENE); menu->add_icon_shortcut(get_icon("CreateNewSceneFrom", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/save_branch_as_scene"), TOOL_NEW_SCENE_FROM); @@ -2277,7 +2280,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/batch_rename"), TOOL_BATCH_RENAME); } menu->add_separator(); - menu->add_icon_item(get_icon("Help", "EditorIcons"), TTR("Open documentation"), TOOL_OPEN_DOCUMENTATION); + menu->add_icon_item(get_icon("Help", "EditorIcons"), TTR("Open Documentation"), TOOL_OPEN_DOCUMENTATION); if (profile_allow_editing) { menu->add_separator(); @@ -2508,7 +2511,6 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel HBoxContainer *filter_hbc = memnew(HBoxContainer); filter_hbc->add_constant_override("separate", 0); - ToolButton *tb; ED_SHORTCUT("scene_tree/rename", TTR("Rename")); ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KEY_MASK_CMD | KEY_F2); @@ -2529,19 +2531,17 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel ED_SHORTCUT("scene_tree/delete_no_confirm", TTR("Delete (No Confirm)"), KEY_MASK_SHIFT | KEY_DELETE); ED_SHORTCUT("scene_tree/delete", TTR("Delete"), KEY_DELETE); - tb = memnew(ToolButton); - tb->connect("pressed", this, "_tool_selected", make_binds(TOOL_NEW, false)); - tb->set_tooltip(TTR("Add/Create a New Node")); - tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/add_child_node")); - filter_hbc->add_child(tb); - button_add = tb; + button_add = memnew(ToolButton); + button_add->connect("pressed", this, "_tool_selected", make_binds(TOOL_NEW, false)); + button_add->set_tooltip(TTR("Add/Create a New Node")); + button_add->set_shortcut(ED_GET_SHORTCUT("scene_tree/add_child_node")); + filter_hbc->add_child(button_add); - tb = memnew(ToolButton); - tb->connect("pressed", this, "_tool_selected", make_binds(TOOL_INSTANCE, false)); - tb->set_tooltip(TTR("Instance a scene file as a Node. Creates an inherited scene if no root node exists.")); - tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/instance_scene")); - filter_hbc->add_child(tb); - button_instance = tb; + button_instance = memnew(ToolButton); + button_instance->connect("pressed", this, "_tool_selected", make_binds(TOOL_INSTANCE, false)); + button_instance->set_tooltip(TTR("Instance a scene file as a Node. Creates an inherited scene if no root node exists.")); + button_instance->set_shortcut(ED_GET_SHORTCUT("scene_tree/instance_scene")); + filter_hbc->add_child(button_instance); vbc->add_child(filter_hbc); filter = memnew(LineEdit); @@ -2551,21 +2551,19 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel filter->add_constant_override("minimum_spaces", 0); filter->connect("text_changed", this, "_filter_changed"); - tb = memnew(ToolButton); - tb->connect("pressed", this, "_tool_selected", make_binds(TOOL_ATTACH_SCRIPT, false)); - tb->set_tooltip(TTR("Attach a new or existing script for the selected node.")); - tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/attach_script")); - filter_hbc->add_child(tb); - tb->hide(); - button_create_script = tb; - - tb = memnew(ToolButton); - tb->connect("pressed", this, "_tool_selected", make_binds(TOOL_CLEAR_SCRIPT, false)); - tb->set_tooltip(TTR("Clear a script for the selected node.")); - tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/clear_script")); - filter_hbc->add_child(tb); - button_clear_script = tb; - tb->hide(); + button_create_script = memnew(ToolButton); + button_create_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_ATTACH_SCRIPT, false)); + button_create_script->set_tooltip(TTR("Attach a new or existing script for the selected node.")); + button_create_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/attach_script")); + filter_hbc->add_child(button_create_script); + button_create_script->hide(); + + button_clear_script = memnew(ToolButton); + button_clear_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_CLEAR_SCRIPT, false)); + button_clear_script->set_tooltip(TTR("Clear a script for the selected node.")); + button_clear_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/clear_script")); + filter_hbc->add_child(button_clear_script); + button_clear_script->hide(); button_hb = memnew(HBoxContainer); vbc->add_child(button_hb); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index c023c41747..42272d0e6e 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -48,6 +48,9 @@ Node *SceneTreeEditor::get_scene_node() { void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_id) { + if (connect_to_script_mode) { + return; //dont do anything in this mode + } TreeItem *item = Object::cast_to<TreeItem>(p_item); ERR_FAIL_COND(!item); @@ -190,7 +193,25 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { item->set_icon(0, icon); item->set_metadata(0, p_node->get_path()); - if (part_of_subscene) { + if (connect_to_script_mode) { + Color accent = get_color("accent_color", "Editor"); + + if (!p_node->get_script().is_null()) { + //has script + item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT); + } else { + //has no script + item->set_custom_color(0, get_color("disabled_font_color", "Editor")); + item->set_selectable(0, false); + accent.a *= 0.7; + } + + if (marked.has(p_node)) { + item->set_text(0, String(p_node->get_name()) + " " + TTR("(Connecting From)")); + + item->set_custom_color(0, accent); + } + } else if (part_of_subscene) { //item->set_selectable(0,marked_selectable); if (valid_types.size() == 0) { @@ -199,7 +220,9 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { } else if (marked.has(p_node)) { - item->set_selectable(0, marked_selectable); + if (!connect_to_script_mode) { + item->set_selectable(0, marked_selectable); + } item->set_custom_color(0, get_color("error_color", "Editor")); } else if (!marked_selectable && !marked_children_selectable) { @@ -620,6 +643,7 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) { item->set_as_cursor(0); selected = p_node; tree->ensure_cursor_is_visible(); + } else { if (!p_node) selected = NULL; @@ -974,6 +998,11 @@ void SceneTreeEditor::_warning_changed(Node *p_for_node) { update_timer->start(); } +void SceneTreeEditor::set_connect_to_script_mode(bool p_enable) { + connect_to_script_mode = p_enable; + update_tree(); +} + void SceneTreeEditor::_bind_methods() { ClassDB::bind_method("_tree_changed", &SceneTreeEditor::_tree_changed); @@ -1016,6 +1045,7 @@ void SceneTreeEditor::_bind_methods() { SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_open_instance) { + connect_to_script_mode = false; undo_redo = NULL; tree_dirty = true; selected = NULL; diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index aa4d4dd58a..9158c4aa48 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -67,6 +67,8 @@ class SceneTreeEditor : public Control { AcceptDialog *error; AcceptDialog *warning; + bool connect_to_script_mode; + int blocked; void _compute_hash(Node *p_node, uint64_t &hash); @@ -151,6 +153,8 @@ public: void update_tree() { _update_tree(); } + void set_connect_to_script_mode(bool p_enable); + Tree *get_scene_tree() { return tree; } SceneTreeEditor(bool p_label = true, bool p_can_rename = false, bool p_can_open_instance = false); diff --git a/main/tests/test_gdscript.cpp b/main/tests/test_gdscript.cpp index 8c60b79200..87bd640001 100644 --- a/main/tests/test_gdscript.cpp +++ b/main/tests/test_gdscript.cpp @@ -289,6 +289,11 @@ static String _parser_expr(const GDScriptParser::Node *p_expr) { } } break; + case GDScriptParser::Node::TYPE_CAST: { + const GDScriptParser::CastNode *cast_node = static_cast<const GDScriptParser::CastNode *>(p_expr); + txt = _parser_expr(cast_node->source_node) + " as " + cast_node->cast_type.to_string(); + + } break; case GDScriptParser::Node::TYPE_NEWLINE: { //skippie @@ -669,6 +674,17 @@ static void _disassemble_class(const Ref<GDScript> &p_class, const Vector<String incr += 2; } break; + case GDScriptFunction::OPCODE_CAST_TO_SCRIPT: { + + txt += " cast "; + txt += DADDR(3); + txt += "="; + txt += DADDR(1); + txt += " as "; + txt += DADDR(2); + incr += 4; + + } break; case GDScriptFunction::OPCODE_CONSTRUCT: { Variant::Type t = Variant::Type(code[ip + 1]); @@ -1018,19 +1034,17 @@ MainLoop *test(TestType p_type) { return NULL; } - GDScript *script = memnew(GDScript); + Ref<GDScript> gds; + gds.instance(); GDScriptCompiler gdc; - err = gdc.compile(&parser, script); + err = gdc.compile(&parser, gds.ptr()); if (err) { print_line("Compile Error:\n" + itos(gdc.get_error_line()) + ":" + itos(gdc.get_error_column()) + ":" + gdc.get_error()); - memdelete(script); return NULL; } - Ref<GDScript> gds = Ref<GDScript>(script); - Ref<GDScript> current = gds; while (current.is_valid()) { diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index ee66097ffd..0330ab4604 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -113,7 +113,7 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f std::string s_path(w_path.begin(), w_path.end()); importer.SetPropertyBool(AI_CONFIG_PP_FD_REMOVE, true); // Cannot remove pivot points because the static mesh will be in the wrong place - importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); + importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); int32_t max_bone_weights = 4; //if (p_flags & IMPORT_ANIMATION_EIGHT_WEIGHTS) { // const int eight_bones = 8; @@ -282,673 +282,438 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, co ERR_FAIL_V(p_values[0]); } -Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) { - ERR_FAIL_COND_V(scene == NULL, NULL); - Spatial *root = memnew(Spatial); - AnimationPlayer *ap = NULL; - if (p_flags & IMPORT_ANIMATION) { - ap = memnew(AnimationPlayer); - root->add_child(ap); - ap->set_owner(root); - ap->set_name(TTR("AnimationPlayer")); - } - Set<String> bone_names; - Set<String> light_names; - Set<String> camera_names; - real_t factor = 1.0f; - String ext = p_path.get_file().get_extension().to_lower(); - if ((ext == "fbx")) { - if (scene->mMetaData != NULL) { - scene->mMetaData->Get("UnitScaleFactor", factor); - factor = factor * 0.01f; +void EditorSceneImporterAssimp::_generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms) { + + Transform mesh_offset = _get_global_assimp_node_transform(p_assimp_node); + //mesh_offset.basis = Basis(); + for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { + const aiMesh *mesh = state.assimp_scene->mMeshes[i]; + int owned_by = -1; + for (uint32_t j = 0; j < mesh->mNumBones; j++) { + const aiBone *bone = mesh->mBones[j]; + String name = _assimp_get_string(bone->mName); + + if (ownership.has(name)) { + owned_by = ownership[name]; + break; + } } - } - for (size_t l = 0; l < scene->mNumLights; l++) { - Light *light = NULL; - aiLight *ai_light = scene->mLights[l]; - ERR_CONTINUE(ai_light == NULL); - if (ai_light->mType == aiLightSource_DIRECTIONAL) { - light = memnew(DirectionalLight); - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Transform xform; - Quat quat; - quat.set_euler(dir); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - pos = factor * pos; - xform.origin = pos; - light->set_transform(xform); - } else if (ai_light->mType == aiLightSource_POINT) { - light = memnew(OmniLight); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - Transform xform; - xform.origin = pos; - pos = factor * pos; - light->set_transform(xform); - // No idea for energy - light->set_param(Light::PARAM_ATTENUATION, 0.0f); - } else if (ai_light->mType == aiLightSource_SPOT) { - light = memnew(SpotLight); - Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); - pos = factor * pos; - Transform xform; - xform.origin = pos; - Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); - dir.normalize(); - Quat quat; - quat.set_euler(dir); - xform.basis = quat; - light->set_transform(xform); - // No idea for energy - light->set_param(Light::PARAM_ATTENUATION, 0.0f); + + if (owned_by == -1) { //no owned, create new unique id + owned_by = 1; + for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) { + owned_by = MAX(E->get() + 1, owned_by); + } } - ERR_CONTINUE(light == NULL); - light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); - root->add_child(light); - light->set_name(_ai_string_to_string(ai_light->mName)); - light->set_owner(root); - light_names.insert(_ai_string_to_string(scene->mLights[l]->mName)); - } - for (size_t c = 0; c < scene->mNumCameras; c++) { - aiCamera *ai_camera = scene->mCameras[c]; - Camera *camera = memnew(Camera); - float near = ai_camera->mClipPlaneNear; - if (Math::is_equal_approx(near, 0.0f)) { - near = 0.1f; + + for (uint32_t j = 0; j < mesh->mNumBones; j++) { + const aiBone *bone = mesh->mBones[j]; + String name = _assimp_get_string(bone->mName); + ownership[name] = owned_by; + //store the actuall full path for the bone transform + //when skeleton finds it's place in the tree, it will be restored + bind_xforms[name] = mesh_offset * _assimp_matrix_transform(bone->mOffsetMatrix); } - camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); - Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); + } - Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); - Quat quat; - quat.set_euler(look_at); - Transform xform; - xform.basis = quat; - xform.set_origin(pos); - root->add_child(camera); - camera->set_transform(xform); - camera->set_name(_ai_string_to_string(ai_camera->mName)); - camera->set_owner(root); - camera_names.insert(_ai_string_to_string(scene->mCameras[c]->mName)); - } - Map<Skeleton *, MeshInstance *> skeletons; - Map<String, Transform> bone_rests; - Vector<MeshInstance *> meshes; - int32_t mesh_count = 0; - Skeleton *s = memnew(Skeleton); - Set<String> removed_bones; - Map<String, Map<uint32_t, String> > path_morph_mesh_names; - _generate_node(p_path, scene, scene->mRootNode, root, root, bone_names, light_names, camera_names, skeletons, bone_rests, meshes, mesh_count, s, p_max_bone_weights, removed_bones, path_morph_mesh_names); - for (Map<Skeleton *, MeshInstance *>::Element *E = skeletons.front(); E; E = E->next()) { - E->key()->localize_rests(); - } - Set<String> removed_nodes; - Set<Node *> keep_nodes; - _keep_node(p_path, root, root, keep_nodes); - _fill_kept_node(keep_nodes); - _filter_node(p_path, root, root, keep_nodes, removed_nodes); - if (p_flags & IMPORT_ANIMATION) { - for (size_t i = 0; i < scene->mNumAnimations; i++) { - _import_animation(p_path, meshes, scene, ap, i, p_bake_fps, skeletons, removed_nodes, removed_bones, path_morph_mesh_names); - } - List<StringName> animation_names; - ap->get_animation_list(&animation_names); - if (animation_names.empty()) { - root->remove_child(ap); - memdelete(ap); - } - } - return root; + for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { + _generate_bone_groups(state, p_assimp_node->mChildren[i], ownership, bind_xforms); + } } -void EditorSceneImporterAssimp::_fill_kept_node(Set<Node *> &keep_nodes) { - for (Set<Node *>::Element *E = keep_nodes.front(); E; E = E->next()) { - Node *node = E->get(); - while (node != NULL) { - if (keep_nodes.has(node) == false) { - keep_nodes.insert(node); +void EditorSceneImporterAssimp::_fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms) { + + String name = _assimp_get_string(p_assimp_node->mName); + if (name == String()) { + name = "AuxiliaryBone" + itos(holecount++); + } + + Transform pose = _assimp_matrix_transform(p_assimp_node->mTransformation); + + if (!ownership.has(name)) { + //not a bone, it's a hole + Vector<SkeletonHole> holes = p_holes; + SkeletonHole hole; //add a new one + hole.name = name; + hole.pose = pose; + hole.node = p_assimp_node; + hole.parent = p_parent_name; + holes.push_back(hole); + + for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { + _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, holes, bind_xforms); + } + + return; + } else if (ownership[name] != p_skeleton_id) { + //oh, it's from another skeleton? fine.. reparent all bones to this skeleton. + int prev_owner = ownership[name]; + ERR_EXPLAIN("A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported."); + ERR_FAIL_COND(skeleton_map.has(prev_owner)); + for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) { + if (E->get() == prev_owner) { + E->get() = p_skeleton_id; } - node = node->get_parent(); } } -} -String EditorSceneImporterAssimp::_find_skeleton_bone_root(Map<Skeleton *, MeshInstance *> &skeletons, Map<MeshInstance *, String> &meshes, Spatial *root) { - for (Map<Skeleton *, MeshInstance *>::Element *E = skeletons.front(); E; E = E->next()) { - if (meshes.has(E->get())) { - String name = meshes[E->get()]; - if (name != "") { - return name; - } + //valid bone, first fill holes if needed + for (int i = 0; i < p_holes.size(); i++) { + + int bone_idx = p_skeleton->get_bone_count(); + p_skeleton->add_bone(p_holes[i].name); + int parent_idx = p_skeleton->find_bone(p_holes[i].parent); + if (parent_idx >= 0) { + p_skeleton->set_bone_parent(bone_idx, parent_idx); } + + Transform pose_transform = _get_global_assimp_node_transform(p_holes[i].node); + p_skeleton->set_bone_rest(bone_idx, pose_transform); + + state.bone_owners[p_holes[i].name] = skeleton_map[p_skeleton_id]; + } + + //finally fill bone + + int bone_idx = p_skeleton->get_bone_count(); + p_skeleton->add_bone(name); + int parent_idx = p_skeleton->find_bone(p_parent_name); + if (parent_idx >= 0) { + p_skeleton->set_bone_parent(bone_idx, parent_idx); + } + //p_skeleton->set_bone_pose(bone_idx, pose); + if (bind_xforms.has(name)) { + //for now this is the full path to the bone in rest pose + //when skeleton finds it's place in the tree, it will get fixed + p_skeleton->set_bone_rest(bone_idx, bind_xforms[name]); + } + state.bone_owners[name] = skeleton_map[p_skeleton_id]; + //go to children + for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { + _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, Vector<SkeletonHole>(), bind_xforms); } - return ""; } -void EditorSceneImporterAssimp::_set_bone_parent(Skeleton *s, Node *p_owner, aiNode *p_node) { - for (int32_t j = 0; j < s->get_bone_count(); j++) { - String bone_name = s->get_bone_name(j); - const aiNode *ai_bone_node = _ai_find_node(p_node, bone_name); - if (ai_bone_node == NULL) { - continue; - } - ai_bone_node = ai_bone_node->mParent; - while (ai_bone_node != NULL) { - int32_t node_parent_index = -1; - String parent_bone_name = _ai_string_to_string(ai_bone_node->mName); - node_parent_index = s->find_bone(parent_bone_name); - if (node_parent_index != -1) { - s->set_bone_parent(j, node_parent_index); - break; +void EditorSceneImporterAssimp::_generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms) { + + //find skeletons at this level, there may be multiple root nodes for each + Map<int, List<aiNode *> > skeletons_found; + for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { + String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); + if (ownership.has(name)) { + int skeleton = ownership[name]; + if (!skeletons_found.has(skeleton)) { + skeletons_found[skeleton] = List<aiNode *>(); } - ai_bone_node = ai_bone_node->mParent; + skeletons_found[skeleton].push_back(p_assimp_node->mChildren[i]); } } + + //go via the potential skeletons found and generate the actual skeleton + for (Map<int, List<aiNode *> >::Element *E = skeletons_found.front(); E; E = E->next()) { + ERR_CONTINUE(skeleton_map.has(E->key())); //skeleton already exists? this can't be.. skip + Skeleton *skeleton = memnew(Skeleton); + //this the only way to reliably use multiple meshes with one skeleton, at the cost of less precision + skeleton->set_use_bones_in_world_transform(true); + skeleton_map[E->key()] = state.skeletons.size(); + state.skeletons.push_back(skeleton); + int holecount = 1; + //fill the bones and their relationships + for (List<aiNode *>::Element *F = E->get().front(); F; F = F->next()) { + _fill_node_relationships(state, F->get(), ownership, skeleton_map, E->key(), skeleton, "", holecount, Vector<SkeletonHole>(), bind_xforms); + } + } + + //go to the children + for (uint32_t i = 0; i < p_assimp_node->mNumChildren; i++) { + String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName); + if (ownership.has(name)) { + continue; //a bone, so don't bother with this + } + _generate_skeletons(state, p_assimp_node->mChildren[i], ownership, skeleton_map, bind_xforms); + } } -void EditorSceneImporterAssimp::_insert_animation_track(const aiScene *p_scene, const String p_path, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, float length, const Skeleton *sk, const aiNodeAnim *track, String node_name, NodePath node_path) { +Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) { + ERR_FAIL_COND_V(scene == NULL, NULL); - if (track->mNumRotationKeys || track->mNumPositionKeys || track->mNumScalingKeys) { - //make transform track - int track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, node_path); - //first determine animation length + ImportState state; + state.path = p_path; + state.assimp_scene = scene; + state.max_bone_weights = p_max_bone_weights; + state.root = memnew(Spatial); + state.fbx = false; + state.animation_player = NULL; - for (size_t i = 0; i < track->mNumRotationKeys; i++) { - length = MAX(length, track->mRotationKeys[i].mTime / ticks_per_second); - } - for (size_t i = 0; i < track->mNumPositionKeys; i++) { - length = MAX(length, track->mPositionKeys[i].mTime / ticks_per_second); - } - for (size_t i = 0; i < track->mNumScalingKeys; i++) { - length = MAX(length, track->mScalingKeys[i].mTime / ticks_per_second); + real_t scale_factor = 1.0f; + { + //handle scale + String ext = p_path.get_file().get_extension().to_lower(); + if (ext == "fbx") { + if (scene->mMetaData != NULL) { + float factor = 1.0; + scene->mMetaData->Get("UnitScaleFactor", factor); + scale_factor = factor * 0.01f; + } + state.fbx = true; } + } - float increment = 1.0 / float(p_bake_fps); - float time = 0.0; + state.root->set_scale(Vector3(scale_factor, scale_factor, scale_factor)); - Vector3 base_pos; - Quat base_rot; - Vector3 base_scale = Vector3(1, 1, 1); + //fill light map cache + for (size_t l = 0; l < scene->mNumLights; l++) { - if (track->mNumRotationKeys != 0) { - aiQuatKey key = track->mRotationKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - real_t w = key.mValue.w; - Quat q(x, y, z, w); - q = q.normalized(); - base_rot = q; - } + aiLight *ai_light = scene->mLights[l]; + ERR_CONTINUE(ai_light == NULL); + state.light_cache[_assimp_get_string(ai_light->mName)] = l; + } - if (track->mNumPositionKeys != 0) { - aiVectorKey key = track->mPositionKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - base_pos = Vector3(x, y, z); + //fill camera cache + for (size_t c = 0; c < scene->mNumCameras; c++) { + aiCamera *ai_camera = scene->mCameras[c]; + ERR_CONTINUE(ai_camera == NULL); + state.camera_cache[_assimp_get_string(ai_camera->mName)] = c; + } + + if (scene->mRootNode) { + Map<String, Transform> bind_xforms; //temporary map to store bind transforms + //guess the skeletons, since assimp does not really support them directly + Map<String, int> ownership; //bone names to groups + //fill this map with bone names and which group where they detected to, going mesh by mesh + _generate_bone_groups(state, state.assimp_scene->mRootNode, ownership, bind_xforms); + Map<int, int> skeleton_map; //maps previously created groups to actual skeletons + //generates the skeletons when bones are found in the hierarchy, and follows them (including gaps/holes). + _generate_skeletons(state, state.assimp_scene->mRootNode, ownership, skeleton_map, bind_xforms); + + //generate nodes + for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) { + _generate_node(state, scene->mRootNode->mChildren[i], state.root); } - if (track->mNumScalingKeys != 0) { - aiVectorKey key = track->mScalingKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - base_scale = Vector3(x, y, z); + //assign skeletons to nodes + + for (Map<MeshInstance *, Skeleton *>::Element *E = state.mesh_skeletons.front(); E; E = E->next()) { + MeshInstance *mesh = E->key(); + Skeleton *skeleton = E->get(); + NodePath skeleton_path = mesh->get_path_to(skeleton); + mesh->set_skeleton_path(skeleton_path); } + } - bool last = false; + if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) { - Vector<Vector3> pos_values; - Vector<float> pos_times; - Vector<Vector3> scale_values; - Vector<float> scale_times; - Vector<Quat> rot_values; - Vector<float> rot_times; + state.animation_player = memnew(AnimationPlayer); + state.root->add_child(state.animation_player); + state.animation_player->set_owner(state.root); - for (size_t p = 0; p < track->mNumPositionKeys; p++) { - aiVector3D pos = track->mPositionKeys[p].mValue; - pos_values.push_back(Vector3(pos.x, pos.y, pos.z)); - pos_times.push_back(track->mPositionKeys[p].mTime / ticks_per_second); + for (uint32_t i = 0; i < scene->mNumAnimations; i++) { + _import_animation(state, i, p_bake_fps); } + } + + return state.root; +} + +void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name) { + + const aiNodeAnim *assimp_track = assimp_anim->mChannels[p_track]; + //make transform track + int track_idx = animation->get_track_count(); + animation->add_track(Animation::TYPE_TRANSFORM); + animation->track_set_path(track_idx, p_path); + //first determine animation length - for (size_t r = 0; r < track->mNumRotationKeys; r++) { - aiQuaternion quat = track->mRotationKeys[r].mValue; - rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized()); - rot_times.push_back(track->mRotationKeys[r].mTime / ticks_per_second); + float increment = 1.0 / float(p_bake_fps); + float time = 0.0; + + bool last = false; + + int skeleton_bone = -1; + + if (p_skeleton) { + skeleton_bone = p_skeleton->find_bone(p_name); + } + + Vector<Vector3> pos_values; + Vector<float> pos_times; + Vector<Vector3> scale_values; + Vector<float> scale_times; + Vector<Quat> rot_values; + Vector<float> rot_times; + + for (size_t p = 0; p < assimp_track->mNumPositionKeys; p++) { + aiVector3D pos = assimp_track->mPositionKeys[p].mValue; + pos_values.push_back(Vector3(pos.x, pos.y, pos.z)); + pos_times.push_back(assimp_track->mPositionKeys[p].mTime / ticks_per_second); + } + + for (size_t r = 0; r < assimp_track->mNumRotationKeys; r++) { + aiQuaternion quat = assimp_track->mRotationKeys[r].mValue; + rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized()); + rot_times.push_back(assimp_track->mRotationKeys[r].mTime / ticks_per_second); + } + + for (size_t sc = 0; sc < assimp_track->mNumScalingKeys; sc++) { + aiVector3D scale = assimp_track->mScalingKeys[sc].mValue; + scale_values.push_back(Vector3(scale.x, scale.y, scale.z)); + scale_times.push_back(assimp_track->mScalingKeys[sc].mTime / ticks_per_second); + } + while (true) { + Vector3 pos; + Quat rot; + Vector3 scale(1, 1, 1); + + if (pos_values.size()) { + pos = _interpolate_track<Vector3>(pos_times, pos_values, time, AssetImportAnimation::INTERP_LINEAR); } - for (size_t sc = 0; sc < track->mNumScalingKeys; sc++) { - aiVector3D scale = track->mScalingKeys[sc].mValue; - scale_values.push_back(Vector3(scale.x, scale.y, scale.z)); - scale_times.push_back(track->mScalingKeys[sc].mTime / ticks_per_second); + if (rot_values.size()) { + rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized(); } - while (true) { - Vector3 pos = base_pos; - Quat rot = base_rot; - Vector3 scale = base_scale; - if (pos_values.size()) { - pos = _interpolate_track<Vector3>(pos_times, pos_values, time, AssetImportAnimation::INTERP_LINEAR); - } + if (scale_values.size()) { + scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR); + } - if (rot_values.size()) { - rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized(); - } + if (skeleton_bone >= 0) { + Transform xform; + xform.basis.set_quat_scale(rot, scale); + xform.origin = pos; - if (scale_values.size()) { - scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR); - } + Transform rest_xform = p_skeleton->get_bone_rest(skeleton_bone); + xform = rest_xform.affine_inverse() * xform; + rot = xform.basis.get_rotation_quat(); + scale = xform.basis.get_scale(); + pos = xform.origin; + } - if (sk != NULL && sk->find_bone(node_name) != -1) { - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; - - int bone = sk->find_bone(node_name); - Transform rest_xform = sk->get_bone_rest(bone); - xform = rest_xform.affine_inverse() * xform; - rot = xform.basis.get_rotation_quat(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } - { - Transform xform; - xform.basis.set_quat_scale(rot, scale); - xform.origin = pos; - Transform anim_xform; - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - real_t factor = 1.0f; - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("UnitScaleFactor", factor); - } - anim_xform = anim_xform.scaled(Vector3(factor, factor, factor)); - } - xform = anim_xform * xform; - rot = xform.basis.get_rotation_quat(); - scale = xform.basis.get_scale(); - pos = xform.origin; - } - rot.normalize(); + rot.normalize(); - animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR); - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR); + animation->transform_track_insert_key(track_idx, time, pos, rot, scale); - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } + if (last) { //done this way so a key is always inserted past the end (for proper interpolation) + break; + } + time += increment; + if (time >= animation->get_length()) { + last = true; } } } -void EditorSceneImporterAssimp::_import_animation(const String p_path, const Vector<MeshInstance *> p_meshes, const aiScene *p_scene, AnimationPlayer *ap, int32_t p_index, int p_bake_fps, Map<Skeleton *, MeshInstance *> p_skeletons, const Set<String> p_removed_nodes, const Set<String> removed_bones, const Map<String, Map<uint32_t, String> > p_path_morph_mesh_names) { - String name = "Animation"; - aiAnimation const *anim = NULL; - if (p_index != -1) { - anim = p_scene->mAnimations[p_index]; - if (anim->mName.length > 0) { - name = _ai_anim_string_to_string(anim->mName); - } +void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) { + + ERR_FAIL_INDEX(p_animation_index, (int)state.assimp_scene->mNumAnimations); + + const aiAnimation *anim = state.assimp_scene->mAnimations[p_animation_index]; + String name = _assimp_anim_string_to_string(anim->mName); + if (name == String()) { + name = "Animation " + itos(p_animation_index + 1); } - Ref<Animation> animation; - animation.instance(); - float length = 0.0f; - animation->set_name(name); - float ticks_per_second = p_scene->mAnimations[p_index]->mTicksPerSecond; + float ticks_per_second = anim->mTicksPerSecond; - if (p_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) { + if (state.assimp_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) { int32_t time_mode = 0; - p_scene->mMetaData->Get("TimeMode", time_mode); - ticks_per_second = _get_fbx_fps(time_mode, p_scene); + state.assimp_scene->mMetaData->Get("TimeMode", time_mode); + ticks_per_second = _get_fbx_fps(time_mode, state.assimp_scene); } - if ((p_path.get_file().get_extension().to_lower() == "glb" || p_path.get_file().get_extension().to_lower() == "gltf") && Math::is_equal_approx(ticks_per_second, 0.0f)) { - ticks_per_second = 1000.0f; - } + //? + //if ((p_path.get_file().get_extension().to_lower() == "glb" || p_path.get_file().get_extension().to_lower() == "gltf") && Math::is_equal_approx(ticks_per_second, 0.0f)) { + // ticks_per_second = 1000.0f; + //} if (Math::is_equal_approx(ticks_per_second, 0.0f)) { ticks_per_second = 25.0f; } - length = anim->mDuration / ticks_per_second; - if (anim) { - Map<String, Vector<const aiNodeAnim *> > node_tracks; - for (size_t i = 0; i < anim->mNumChannels; i++) { - const aiNodeAnim *track = anim->mChannels[i]; - String node_name = _ai_string_to_string(track->mNodeName); - NodePath node_path = node_name; - bool is_bone = false; - if (node_name.split(ASSIMP_FBX_KEY).size() > 1) { - String p_track_type = node_name.split(ASSIMP_FBX_KEY)[1]; - if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") { - continue; - } - } - for (Map<Skeleton *, MeshInstance *>::Element *E = p_skeletons.front(); E; E = E->next()) { - Skeleton *sk = E->key(); - const String path = ap->get_owner()->get_path_to(sk); - if (path.empty()) { - continue; - } - if (sk->find_bone(node_name) == -1) { - continue; - } - node_path = path + ":" + node_name; - ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false); - _insert_animation_track(p_scene, p_path, p_bake_fps, animation, ticks_per_second, length, sk, track, node_name, node_path); - is_bone = true; - } - if (is_bone) { - continue; - } - Node *node = ap->get_owner()->find_node(node_name); - if (node == NULL) { - continue; - } - if (p_removed_nodes.has(node_name)) { - continue; - } - const String path = ap->get_owner()->get_path_to(node); - if (path.empty()) { - print_verbose("Can't animate path"); - continue; - } - node_path = path; - if (ap->get_owner()->has_node(node_path) == false) { - continue; - } - _insert_animation_track(p_scene, p_path, p_bake_fps, animation, ticks_per_second, length, NULL, track, node_name, node_path); - } - for (size_t i = 0; i < anim->mNumChannels; i++) { - const aiNodeAnim *track = anim->mChannels[i]; - String node_name = _ai_string_to_string(track->mNodeName); - Vector<String> split_name = node_name.split(ASSIMP_FBX_KEY); - String bare_name = split_name[0]; - Node *node = ap->get_owner()->find_node(bare_name); - if (node != NULL && split_name.size() > 1) { - Map<String, Vector<const aiNodeAnim *> >::Element *E = node_tracks.find(bare_name); - Vector<const aiNodeAnim *> ai_tracks; - if (E) { - ai_tracks = E->get(); - ai_tracks.push_back(anim->mChannels[i]); - } else { - ai_tracks.push_back(anim->mChannels[i]); - } - node_tracks.insert(bare_name, ai_tracks); - } - } - for (Map<Skeleton *, MeshInstance *>::Element *E = p_skeletons.front(); E; E = E->next()) { - Skeleton *sk = E->key(); - Map<String, Vector<const aiNodeAnim *> > anim_tracks; - for (int32_t i = 0; i < sk->get_bone_count(); i++) { - String _bone_name = sk->get_bone_name(i); - Vector<const aiNodeAnim *> ai_tracks; + Ref<Animation> animation; + animation.instance(); + animation->set_name(name); + animation->set_length(anim->mDuration / ticks_per_second); - if (sk->find_bone(_bone_name) == -1) { - continue; - } - for (size_t j = 0; j < anim->mNumChannels; j++) { - if (_ai_string_to_string(anim->mChannels[j]->mNodeName).split(ASSIMP_FBX_KEY).size() == 1) { - continue; - } - String track_name = _ai_string_to_string(anim->mChannels[j]->mNodeName).split(ASSIMP_FBX_KEY)[0]; - if (track_name != _bone_name) { - continue; - } - if (sk->find_bone(_bone_name) == -1) { - continue; - } - ai_tracks.push_back(anim->mChannels[j]); - } - if (ai_tracks.size() == 0) { - continue; - } - anim_tracks.insert(_bone_name, ai_tracks); - } - for (Map<String, Vector<const aiNodeAnim *> >::Element *F = anim_tracks.front(); F; F = F->next()) { - _insert_pivot_anim_track(p_meshes, F->key(), F->get(), ap, sk, length, ticks_per_second, animation, p_bake_fps, p_path, p_scene); - } - } - for (Map<String, Vector<const aiNodeAnim *> >::Element *E = node_tracks.front(); E; E = E->next()) { - if (p_removed_nodes.has(E->key())) { - continue; - } - if (removed_bones.find(E->key())) { + //regular tracks + + for (size_t i = 0; i < anim->mNumChannels; i++) { + const aiNodeAnim *track = anim->mChannels[i]; + String node_name = _assimp_get_string(track->mNodeName); + /* + if (node_name.find(ASSIMP_FBX_KEY) != -1) { + String p_track_type = node_name.get_slice(ASSIMP_FBX_KEY, 1); + if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") { continue; } - _insert_pivot_anim_track(p_meshes, E->key(), E->get(), ap, NULL, length, ticks_per_second, animation, p_bake_fps, p_path, p_scene); } - for (size_t i = 0; i < anim->mNumMorphMeshChannels; i++) { - const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i]; - const String prop_name = _ai_string_to_string(anim_mesh->mName); - const String mesh_name = prop_name.split("*")[0]; - if (p_removed_nodes.has(mesh_name)) { - continue; - } - ERR_CONTINUE(prop_name.split("*").size() != 2); - const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(ap->get_owner()->find_node(mesh_name)); - ERR_CONTINUE(mesh_instance == NULL); - if (ap->get_owner()->find_node(mesh_instance->get_name()) == NULL) { - print_verbose("Can't find mesh in scene: " + mesh_instance->get_name()); - continue; - } - const String path = ap->get_owner()->get_path_to(mesh_instance); - if (path.empty()) { - print_verbose("Can't find mesh in scene"); - continue; - } - Ref<Mesh> mesh = mesh_instance->get_mesh(); - ERR_CONTINUE(mesh.is_null()); - const Map<String, Map<uint32_t, String> >::Element *E = p_path_morph_mesh_names.find(mesh_name); - ERR_CONTINUE(E == NULL); - for (size_t k = 0; k < anim_mesh->mNumKeys; k++) { - for (size_t j = 0; j < anim_mesh->mKeys[k].mNumValuesAndWeights; j++) { - const Map<uint32_t, String>::Element *F = E->get().find(anim_mesh->mKeys[k].mValues[j]); - ERR_CONTINUE(F == NULL); - const String prop = "blend_shapes/" + F->get(); - const NodePath node_path = String(path) + ":" + prop; - ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false); - int32_t blend_track_idx = -1; - if (animation->find_track(node_path) == -1) { - blend_track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_VALUE); - animation->track_set_interpolation_type(blend_track_idx, Animation::INTERPOLATION_LINEAR); - animation->track_set_path(blend_track_idx, node_path); - } else { - blend_track_idx = animation->find_track(node_path); - } - float t = anim_mesh->mKeys[k].mTime / ticks_per_second; - float w = anim_mesh->mKeys[k].mWeights[j]; - animation->track_insert_key(blend_track_idx, t, w); - } - } +*/ + if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) { + continue; //do not bother } - } - animation->set_length(length); - if (animation->get_track_count()) { - ap->add_animation(name, animation); - } -} -void EditorSceneImporterAssimp::_insert_pivot_anim_track(const Vector<MeshInstance *> p_meshes, const String p_node_name, Vector<const aiNodeAnim *> F, AnimationPlayer *ap, Skeleton *sk, float &length, float ticks_per_second, Ref<Animation> animation, int p_bake_fps, const String &p_path, const aiScene *p_scene) { - NodePath node_path; - if (sk != NULL) { - const String path = ap->get_owner()->get_path_to(sk); - if (path.empty()) { - return; - } - if (sk->find_bone(p_node_name) == -1) { - return; - } - node_path = path + ":" + p_node_name; - } else { - Node *node = ap->get_owner()->find_node(p_node_name); - if (node == NULL) { - return; - } - const String path = ap->get_owner()->get_path_to(node); - node_path = path; - } - if (node_path.is_empty()) { - return; - } + bool is_bone = state.bone_owners.has(node_name); + NodePath node_path; + Skeleton *skeleton = NULL; - Vector<Vector3> pos_values; - Vector<float> pos_times; - Vector<Vector3> scale_values; - Vector<float> scale_times; - Vector<Quat> rot_values; - Vector<float> rot_times; - Vector3 base_pos; - Quat base_rot; - Vector3 base_scale = Vector3(1, 1, 1); - bool is_translation = false; - bool is_rotation = false; - bool is_scaling = false; - for (int32_t k = 0; k < F.size(); k++) { - String p_track_type = _ai_string_to_string(F[k]->mNodeName).split(ASSIMP_FBX_KEY)[1]; - if (p_track_type == "_Translation") { - is_translation = is_translation || true; - } else if (p_track_type == "_Rotation") { - is_rotation = is_rotation || true; - } else if (p_track_type == "_Scaling") { - is_scaling = is_scaling || true; + if (is_bone) { + skeleton = state.skeletons[state.bone_owners[node_name]]; + String path = state.root->get_path_to(skeleton); + path += ":" + node_name; + node_path = path; } else { - continue; + + ERR_CONTINUE(!state.node_map.has(node_name)); + Node *node = state.node_map[node_name]; + node_path = state.root->get_path_to(node); } - ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false); - if (F[k]->mNumRotationKeys || F[k]->mNumPositionKeys || F[k]->mNumScalingKeys) { + _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name); + } - if (is_rotation) { - for (size_t i = 0; i < F[k]->mNumRotationKeys; i++) { - length = MAX(length, F[k]->mRotationKeys[i].mTime / ticks_per_second); - } - } - if (is_translation) { - for (size_t i = 0; i < F[k]->mNumPositionKeys; i++) { - length = MAX(length, F[k]->mPositionKeys[i].mTime / ticks_per_second); - } - } - if (is_scaling) { - for (size_t i = 0; i < F[k]->mNumScalingKeys; i++) { - length = MAX(length, F[k]->mScalingKeys[i].mTime / ticks_per_second); - } - } + //blend shape tracks - if (is_rotation == false && is_translation == false && is_scaling == false) { - return; - } + for (size_t i = 0; i < anim->mNumMorphMeshChannels; i++) { - if (is_rotation) { - if (F[k]->mNumRotationKeys != 0) { - aiQuatKey key = F[k]->mRotationKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - real_t w = key.mValue.w; - Quat q(x, y, z, w); - q = q.normalized(); - base_rot = q; - } - } + const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i]; - if (is_translation) { - if (F[k]->mNumPositionKeys != 0) { - aiVectorKey key = F[k]->mPositionKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - base_pos = Vector3(x, y, z); - } - } + const String prop_name = _assimp_get_string(anim_mesh->mName); + const String mesh_name = prop_name.split("*")[0]; - if (is_scaling) { - if (F[k]->mNumScalingKeys != 0) { - aiVectorKey key = F[k]->mScalingKeys[0]; - real_t x = key.mValue.x; - real_t y = key.mValue.y; - real_t z = key.mValue.z; - base_scale = Vector3(x, y, z); - } - } - if (is_translation) { - for (size_t p = 0; p < F[k]->mNumPositionKeys; p++) { - aiVector3D pos = F[k]->mPositionKeys[p].mValue; - pos_values.push_back(Vector3(pos.x, pos.y, pos.z)); - pos_times.push_back(F[k]->mPositionKeys[p].mTime / ticks_per_second); - } - } + ERR_CONTINUE(prop_name.split("*").size() != 2); - if (is_rotation) { - for (size_t r = 0; r < F[k]->mNumRotationKeys; r++) { - aiQuaternion quat = F[k]->mRotationKeys[r].mValue; - rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized()); - rot_times.push_back(F[k]->mRotationKeys[r].mTime / ticks_per_second); - } - } + ERR_CONTINUE(!state.node_map.has(mesh_name)); - if (is_scaling) { - for (size_t sc = 0; sc < F[k]->mNumScalingKeys; sc++) { - aiVector3D scale = F[k]->mScalingKeys[sc].mValue; - scale_values.push_back(Vector3(scale.x, scale.y, scale.z)); - scale_times.push_back(F[k]->mScalingKeys[sc].mTime / ticks_per_second); - } - } + const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(state.node_map[mesh_name]); + + ERR_CONTINUE(mesh_instance == NULL); + + String base_path = state.root->get_path_to(mesh_instance); + + Ref<Mesh> mesh = mesh_instance->get_mesh(); + ERR_CONTINUE(mesh.is_null()); + + //add the tracks for this mesh + int base_track = animation->get_track_count(); + for (int j = 0; j < mesh->get_blend_shape_count(); j++) { + + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(base_track + j, base_path + ":blend_shapes/" + mesh->get_blend_shape_name(j)); } - } - int32_t track_idx = animation->get_track_count(); - animation->add_track(Animation::TYPE_TRANSFORM); - animation->track_set_path(track_idx, node_path); - float increment = 1.0 / float(p_bake_fps); - float time = 0.0; - bool last = false; - while (true) { - Vector3 pos = Vector3(); - Quat rot = Quat(); - Vector3 scale = Vector3(1.0f, 1.0f, 1.0f); - if (is_translation && pos_values.size()) { - pos = _interpolate_track<Vector3>(pos_times, pos_values, time, AssetImportAnimation::INTERP_LINEAR); - Transform anim_xform; - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - aiNode *ai_node = _ai_find_node(p_scene->mRootNode, p_node_name); - Transform mesh_xform = _get_global_ai_node_transform(p_scene, ai_node); - pos = mesh_xform.origin + pos; - real_t factor = 1.0f; - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("UnitScaleFactor", factor); - factor = factor * 0.01f; - } - pos = pos * factor; + + for (size_t k = 0; k < anim_mesh->mNumKeys; k++) { + for (size_t j = 0; j < anim_mesh->mKeys[k].mNumValuesAndWeights; j++) { + + float t = anim_mesh->mKeys[k].mTime / ticks_per_second; + float w = anim_mesh->mKeys[k].mWeights[j]; + + animation->track_insert_key(base_track + j, t, w); } } - if (is_rotation && rot_values.size()) { - rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized(); - } - if (is_scaling && scale_values.size()) { - scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR); - } - animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR); - animation->transform_track_insert_key(track_idx, time, pos, rot, scale); + } - if (last) { - break; - } - time += increment; - if (time >= length) { - last = true; - time = length; - } + if (animation->get_track_count()) { + state.animation_player->add_animation(name, animation); } } @@ -976,611 +741,320 @@ float EditorSceneImporterAssimp::_get_fbx_fps(int32_t time_mode, const aiScene * return 0; } -Transform EditorSceneImporterAssimp::_get_global_ai_node_transform(const aiScene *p_scene, const aiNode *p_current_node) { +Transform EditorSceneImporterAssimp::_get_global_assimp_node_transform(const aiNode *p_current_node) { aiNode const *current_node = p_current_node; Transform xform; while (current_node != NULL) { - xform = _ai_matrix_transform(current_node->mTransformation) * xform; + xform = _assimp_matrix_transform(current_node->mTransformation) * xform; current_node = current_node->mParent; } return xform; } -void EditorSceneImporterAssimp::_generate_node_bone(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const String p_path, const int32_t p_max_bone_weights) { - for (size_t i = 0; i < p_node->mNumMeshes; i++) { - const unsigned int mesh_idx = p_node->mMeshes[i]; - const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx]; - for (size_t j = 0; j < ai_mesh->mNumBones; j++) { - String bone_name = _ai_string_to_string(ai_mesh->mBones[j]->mName); - if (p_skeleton->find_bone(bone_name) != -1) { - continue; - } - p_mesh_bones.insert(bone_name, true); - p_skeleton->add_bone(bone_name); - int32_t idx = p_skeleton->find_bone(bone_name); - Transform xform = _ai_matrix_transform(ai_mesh->mBones[j]->mOffsetMatrix); - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - Transform mesh_xform = _get_global_ai_node_transform(p_scene, p_node); - mesh_xform.basis = Basis(); - xform = mesh_xform.affine_inverse() * xform; - } - p_skeleton->set_bone_rest(idx, xform.affine_inverse()); - } - } -} +Ref<Texture> EditorSceneImporterAssimp::_load_texture(ImportState &state, String p_path) { + Vector<String> split_path = p_path.get_basename().split("*"); + if (split_path.size() == 2) { + size_t texture_idx = split_path[1].to_int(); + ERR_FAIL_COND_V(texture_idx >= state.assimp_scene->mNumTextures, Ref<Texture>()); + aiTexture *tex = state.assimp_scene->mTextures[texture_idx]; + String filename = _assimp_raw_string_to_string(tex->mFilename); + filename = filename.get_file(); + print_verbose("Open Asset Import: Loading embedded texture " + filename); + if (tex->mHeight == 0) { + if (tex->CheckFormat("png")) { + Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); -void EditorSceneImporterAssimp::_generate_node_bone_parents(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const MeshInstance *p_mi) { - for (size_t i = 0; i < p_node->mNumMeshes; i++) { - const unsigned int mesh_idx = p_node->mMeshes[i]; - const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx]; - - for (size_t j = 0; j < ai_mesh->mNumBones; j++) { - aiNode *bone_node = p_scene->mRootNode->FindNode(ai_mesh->mBones[j]->mName); - ERR_CONTINUE(bone_node == NULL); - aiNode *bone_node_parent = bone_node->mParent; - while (bone_node_parent != NULL) { - String bone_parent_name = _ai_string_to_string(bone_node_parent->mName); - bone_parent_name = bone_parent_name.split(ASSIMP_FBX_KEY)[0]; - if (bone_parent_name == p_mi->get_name()) { - break; - } - if (p_mi->get_parent() == NULL) { - break; - } - if (bone_parent_name == p_mi->get_parent()->get_name()) { - break; - } - if (bone_node_parent->mParent == p_scene->mRootNode) { - break; - } - if (p_skeleton->find_bone(bone_parent_name) == -1) { - p_mesh_bones.insert(bone_parent_name, true); - } - bone_node_parent = bone_node_parent->mParent; - } - } - } -} -void EditorSceneImporterAssimp::_calculate_skeleton_root(Skeleton *s, const aiScene *p_scene, aiNode *&p_ai_skeleton_root, Map<String, bool> &mesh_bones, const aiNode *p_node) { - if (s->get_bone_count() > 0) { - String bone_name = s->get_bone_name(0); - p_ai_skeleton_root = _ai_find_node(p_scene->mRootNode, bone_name); - for (size_t i = 0; i < p_scene->mRootNode->mNumChildren; i++) { - if (p_ai_skeleton_root == NULL) { - break; + Ref<ImageTexture> t; + t.instance(); + t->create_from_image(img); + t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + return t; + } else if (tex->CheckFormat("jpg")) { + Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); + Ref<ImageTexture> t; + t.instance(); + t->create_from_image(img); + t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + return t; + } else if (tex->CheckFormat("dds")) { + ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); + ERR_FAIL_COND_V(true, Ref<Texture>()); + //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); + //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); + //Ref<ImageTexture> t; + //t.instance(); + //t->create_from_image(img); + //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + //return t; } - aiNode *found = p_scene->mRootNode->mChildren[i]->FindNode(p_ai_skeleton_root->mName); - if (found) { - p_ai_skeleton_root = p_scene->mRootNode->mChildren[i]; - break; + } else { + Ref<Image> img; + img.instance(); + PoolByteArray arr; + uint32_t size = tex->mWidth * tex->mHeight; + arr.resize(size); + memcpy(arr.write().ptr(), tex->pcData, size); + ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Texture>()); + //ARGB8888 to RGBA8888 + for (int32_t i = 0; i < arr.size() / 4; i++) { + arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; + arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; + arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; + arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; } - } - } + img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); + ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - if (p_ai_skeleton_root == NULL) { - p_ai_skeleton_root = p_scene->mRootNode->FindNode(p_node->mName); - while (p_ai_skeleton_root && p_ai_skeleton_root->mParent && p_ai_skeleton_root->mParent != p_scene->mRootNode) { - p_ai_skeleton_root = p_scene->mRootNode->FindNode(p_ai_skeleton_root->mName)->mParent; + Ref<ImageTexture> t; + t.instance(); + t->create_from_image(img); + t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + return t; } + return Ref<Texture>(); } - p_ai_skeleton_root = _ai_find_node(p_scene->mRootNode, _ai_string_to_string(p_ai_skeleton_root->mName).split(ASSIMP_FBX_KEY)[0]); -} - -void EditorSceneImporterAssimp::_fill_skeleton(const aiScene *p_scene, const aiNode *p_node, Spatial *p_current, Node *p_owner, Skeleton *p_skeleton, const Map<String, bool> p_mesh_bones, const Map<String, Transform> &p_bone_rests, Set<String> p_tracks, const String p_path, Set<String> &r_removed_bones) { - String node_name = _ai_string_to_string(p_node->mName); - if (p_mesh_bones.find(node_name) != NULL && p_skeleton->find_bone(node_name) == -1) { - r_removed_bones.insert(node_name); - p_skeleton->add_bone(node_name); - int32_t idx = p_skeleton->find_bone(node_name); - Transform xform = _get_global_ai_node_transform(p_scene, p_node); - xform = _format_rot_xform(p_path, p_scene) * xform; - p_skeleton->set_bone_rest(idx, xform); - } - - for (size_t i = 0; i < p_node->mNumChildren; i++) { - _fill_skeleton(p_scene, p_node->mChildren[i], p_current, p_owner, p_skeleton, p_mesh_bones, p_bone_rests, p_tracks, p_path, r_removed_bones); - } + Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture"); + return p_texture; } -void EditorSceneImporterAssimp::_keep_node(const String &p_path, Node *p_current, Node *p_owner, Set<Node *> &r_keep_nodes) { - if (p_current == p_owner) { - r_keep_nodes.insert(p_current); - } - - if (p_current->get_class() != Spatial().get_class()) { - r_keep_nodes.insert(p_current); - } - - for (int i = 0; i < p_current->get_child_count(); i++) { - _keep_node(p_path, p_current->get_child(i), p_owner, r_keep_nodes); - } -} +Ref<Material> EditorSceneImporterAssimp::_generate_material_from_index(ImportState &state, int p_index, bool p_double_sided) { -void EditorSceneImporterAssimp::_filter_node(const String &p_path, Node *p_current, Node *p_owner, const Set<Node *> p_keep_nodes, Set<String> &r_removed_nodes) { - if (p_keep_nodes.has(p_current) == false) { - r_removed_nodes.insert(p_current->get_name()); - p_current->queue_delete(); - } - for (int i = 0; i < p_current->get_child_count(); i++) { - _filter_node(p_path, p_current->get_child(i), p_owner, p_keep_nodes, r_removed_nodes); - } -} + ERR_FAIL_INDEX_V(p_index, (int)state.assimp_scene->mNumMaterials, Ref<Material>()); -void EditorSceneImporterAssimp::_generate_node(const String &p_path, const aiScene *p_scene, const aiNode *p_node, Node *p_parent, Node *p_owner, Set<String> &r_bone_name, Set<String> p_light_names, Set<String> p_camera_names, Map<Skeleton *, MeshInstance *> &r_skeletons, const Map<String, Transform> &p_bone_rests, Vector<MeshInstance *> &r_mesh_instances, int32_t &r_mesh_count, Skeleton *p_skeleton, const int32_t p_max_bone_weights, Set<String> &r_removed_bones, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names) { - Spatial *child_node = NULL; - if (p_node == NULL) { - return; - } - String node_name = _ai_string_to_string(p_node->mName); - real_t factor = 1.0f; - String ext = p_path.get_file().get_extension().to_lower(); - if (ext == "fbx") { - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("UnitScaleFactor", factor); - factor = factor * 0.01f; - } - } - { - Transform xform = _ai_matrix_transform(p_node->mTransformation); - - child_node = memnew(Spatial); - p_parent->add_child(child_node); - child_node->set_owner(p_owner); - if (p_node == p_scene->mRootNode) { - if ((ext == "fbx") && p_node == p_scene->mRootNode) { - xform = xform.scaled(Vector3(factor, factor, factor)); - Transform format_xform = _format_rot_xform(p_path, p_scene); - xform = format_xform * xform; - } - } - child_node->set_transform(xform * child_node->get_transform()); - } + aiMaterial *ai_material = state.assimp_scene->mMaterials[p_index]; + Ref<SpatialMaterial> mat; + mat.instance(); - if (p_node->mNumMeshes > 0) { - MeshInstance *mesh_node = memnew(MeshInstance); - p_parent->add_child(mesh_node); - mesh_node->set_owner(p_owner); - mesh_node->set_transform(child_node->get_transform()); - { - Map<String, bool> mesh_bones; - p_skeleton->set_use_bones_in_world_transform(true); - _generate_node_bone(p_scene, p_node, mesh_bones, p_skeleton, p_path, p_max_bone_weights); - Set<String> tracks; - _get_track_set(p_scene, tracks); - aiNode *skeleton_root = NULL; - _calculate_skeleton_root(p_skeleton, p_scene, skeleton_root, mesh_bones, p_node); - _generate_node_bone_parents(p_scene, p_node, mesh_bones, p_skeleton, mesh_node); - if (p_skeleton->get_bone_count() > 0) { - _fill_skeleton(p_scene, skeleton_root, mesh_node, p_owner, p_skeleton, mesh_bones, p_bone_rests, tracks, p_path, r_removed_bones); - _set_bone_parent(p_skeleton, p_owner, p_scene->mRootNode); - } - MeshInstance *mi = Object::cast_to<MeshInstance>(mesh_node); - if (mi) { - r_mesh_instances.push_back(mi); - } - _add_mesh_to_mesh_instance(p_node, p_scene, p_skeleton, p_path, mesh_node, p_owner, r_bone_name, r_mesh_count, p_max_bone_weights, r_name_morph_mesh_names); - } - if (mesh_node != NULL && p_skeleton->get_bone_count() > 0 && p_owner->find_node(p_skeleton->get_name()) == NULL) { - Node *node = p_owner->find_node(_ai_string_to_string(p_scene->mRootNode->mName)); - ERR_FAIL_COND(node == NULL); - node->add_child(p_skeleton); - p_skeleton->set_owner(p_owner); - if (ext == "fbx") { - Transform mesh_xform = _get_global_ai_node_transform(p_scene, p_node); - mesh_xform.origin = Vector3(); - p_skeleton->set_transform(mesh_xform); - } - r_skeletons.insert(p_skeleton, mesh_node); - } - for (size_t i = 0; i < p_node->mNumMeshes; i++) { - if (p_scene->mMeshes[p_node->mMeshes[i]]->HasBones()) { - mesh_node->set_name(node_name); - // Meshes without skeletons must not have skeletons - mesh_node->set_skeleton_path(String(mesh_node->get_path_to(p_owner)) + "/" + p_owner->get_path_to(p_skeleton)); - } + int32_t mat_two_sided = 0; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { + if (mat_two_sided > 0) { + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); } - child_node->get_parent()->remove_child(child_node); - memdelete(child_node); - child_node = mesh_node; - } else if (p_light_names.has(node_name)) { - Spatial *light_node = Object::cast_to<Light>(p_owner->find_node(node_name)); - ERR_FAIL_COND(light_node == NULL); - if (!p_parent->has_node(light_node->get_path())) { - p_parent->add_child(light_node); - } - light_node->set_owner(p_owner); - light_node->set_transform(child_node->get_transform().scaled(Vector3(factor, factor, factor)) * - light_node->get_transform().scaled(Vector3(factor, factor, factor))); - child_node->get_parent()->remove_child(child_node); - memdelete(child_node); - child_node = light_node; - } else if (p_camera_names.has(node_name)) { - Spatial *camera_node = Object::cast_to<Camera>(p_owner->find_node(node_name)); - ERR_FAIL_COND(camera_node == NULL); - if (!p_parent->has_node(camera_node->get_path())) { - p_parent->add_child(camera_node); - } - camera_node->set_owner(p_owner); - camera_node->set_transform(child_node->get_transform().scaled(Vector3(factor, factor, factor)) * - camera_node->get_transform().scaled(Vector3(factor, factor, factor))); - camera_node->scale(Vector3(factor, factor, factor)); - child_node->get_parent()->remove_child(child_node); - memdelete(child_node); - child_node = camera_node; - } - child_node->set_name(node_name); - for (size_t i = 0; i < p_node->mNumChildren; i++) { - _generate_node(p_path, p_scene, p_node->mChildren[i], child_node, p_owner, r_bone_name, p_light_names, p_camera_names, r_skeletons, p_bone_rests, r_mesh_instances, r_mesh_count, p_skeleton, p_max_bone_weights, r_removed_bones, r_name_morph_mesh_names); } -} - -aiNode *EditorSceneImporterAssimp::_ai_find_node(aiNode *ai_child_node, const String bone_name) { - if (_ai_string_to_string(ai_child_node->mName) == bone_name) { - return ai_child_node; + //const String mesh_name = _assimp_get_string(ai_mesh->mName); + aiString mat_name; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { + mat->set_name(_assimp_get_string(mat_name)); } - aiNode *target = NULL; - for (size_t i = 0; i < ai_child_node->mNumChildren; i++) { - target = _ai_find_node(ai_child_node->mChildren[i], bone_name); - if (target != NULL) { - return target; - } - } - return target; -} + aiTextureType tex_normal = aiTextureType_NORMALS; + { + aiString ai_filename = aiString(); + String filename = ""; + aiTextureMapMode map_mode[2]; -Transform EditorSceneImporterAssimp::_format_rot_xform(const String p_path, const aiScene *p_scene) { - String ext = p_path.get_file().get_extension().to_lower(); + if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { + filename = _assimp_raw_string_to_string(ai_filename); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); - Transform xform; - int32_t up_axis = 0; - Vector3 up_axis_vec3 = Vector3(); - - int32_t front_axis = 0; - Vector3 front_axis_vec3 = Vector3(); - - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("UpAxis", up_axis); - if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_X) { - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("FrontAxis", front_axis); - if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) { - // y - } else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) { - // z - //front_axis_vec3 = Vector3(0.0f, Math::deg2rad(-180.f), 0.0f); - } - } - } else if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_Y) { - up_axis_vec3 = Vector3(Math::deg2rad(-90.f), 0.0f, 0.0f); - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("FrontAxis", front_axis); - if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) { - // x - } else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) { - // z - } - } - } else if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_Z) { - up_axis_vec3 = Vector3(0.0f, Math ::deg2rad(90.f), 0.0f); - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("FrontAxis", front_axis); - if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) { - // x - } else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) { - // y + if (texture != NULL) { + if (map_mode != NULL) { + _set_texture_mapping_mode(map_mode, texture); + } + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); } } } } - int32_t up_axis_sign = 0; - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("UpAxisSign", up_axis_sign); - up_axis_vec3 = up_axis_vec3 * up_axis_sign; - } - - int32_t front_axis_sign = 0; - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("FrontAxisSign", front_axis_sign); - front_axis_vec3 = front_axis_vec3 * front_axis_sign; - } + { + aiString ai_filename = aiString(); + String filename = ""; - int32_t coord_axis = 0; - Vector3 coord_axis_vec3 = Vector3(); - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("CoordAxis", coord_axis); - if (coord_axis == AssetImportFbx::COORD_LEFT) { - } else if (coord_axis == AssetImportFbx::COORD_RIGHT) { + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) { + filename = _assimp_raw_string_to_string(ai_filename); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); + } + } } } - int32_t coord_axis_sign = 0; - if (p_scene->mMetaData != NULL) { - p_scene->mMetaData->Get("CoordAxisSign", coord_axis_sign); - } - - Quat up_quat; - up_quat.set_euler(up_axis_vec3); - - Quat coord_quat; - coord_quat.set_euler(coord_axis_vec3); - - Quat front_quat; - front_quat.set_euler(front_axis_vec3); + aiTextureType tex_emissive = aiTextureType_EMISSIVE; - xform.basis.set_quat(up_quat * coord_quat * front_quat); - return xform; -} + if (ai_material->GetTextureCount(tex_emissive) > 0) { -void EditorSceneImporterAssimp::_get_track_set(const aiScene *p_scene, Set<String> &tracks) { - for (size_t i = 0; i < p_scene->mNumAnimations; i++) { - for (size_t j = 0; j < p_scene->mAnimations[i]->mNumChannels; j++) { - aiString ai_name = p_scene->mAnimations[i]->mChannels[j]->mNodeName; - String name = _ai_string_to_string(ai_name); - tracks.insert(name); - } - } -} + aiString ai_filename = aiString(); + String filename = ""; + aiTextureMapMode map_mode[2]; -void EditorSceneImporterAssimp::_add_mesh_to_mesh_instance(const aiNode *p_node, const aiScene *p_scene, Skeleton *s, const String &p_path, MeshInstance *p_mesh_instance, Node *p_owner, Set<String> &r_bone_name, int32_t &r_mesh_count, int32_t p_max_bone_weights, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names) { - Ref<ArrayMesh> mesh; - mesh.instance(); - bool has_uvs = false; - for (size_t i = 0; i < p_node->mNumMeshes; i++) { - const unsigned int mesh_idx = p_node->mMeshes[i]; - const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx]; - - Map<uint32_t, Vector<float> > vertex_weight; - Map<uint32_t, Vector<String> > vertex_bone_name; - for (size_t b = 0; b < ai_mesh->mNumBones; b++) { - aiBone *bone = ai_mesh->mBones[b]; - for (size_t w = 0; w < bone->mNumWeights; w++) { - String name = _ai_string_to_string(bone->mName); - aiVertexWeight ai_weights = bone->mWeights[w]; - uint32_t vertexId = ai_weights.mVertexId; - Map<uint32_t, Vector<float> >::Element *result = vertex_weight.find(vertexId); - Vector<float> weights; - if (result != NULL) { - weights.append_array(result->value()); - } - weights.push_back(ai_weights.mWeight); - if (vertex_weight.has(vertexId)) { - vertex_weight[vertexId] = weights; - } else { - vertex_weight.insert(vertexId, weights); - } - Map<uint32_t, Vector<String> >::Element *bone_result = vertex_bone_name.find(vertexId); - Vector<String> bone_names; - if (bone_result != NULL) { - bone_names.append_array(bone_result->value()); - } - bone_names.push_back(name); - if (vertex_bone_name.has(vertexId)) { - vertex_bone_name[vertexId] = bone_names; - } else { - vertex_bone_name.insert(vertexId, bone_names); + if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { + filename = _assimp_raw_string_to_string(ai_filename); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + _set_texture_mapping_mode(map_mode, texture); + mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); + mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture); } } } + } - Ref<SurfaceTool> st; - st.instance(); - st->begin(Mesh::PRIMITIVE_TRIANGLES); - - for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { - if (ai_mesh->HasTextureCoords(0)) { - has_uvs = true; - st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); - } - if (ai_mesh->HasTextureCoords(1)) { - has_uvs = true; - st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); - } - if (ai_mesh->HasVertexColors(0)) { - Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a); - st->add_color(color); - } - if (ai_mesh->mNormals != NULL) { - const aiVector3D normals = ai_mesh->mNormals[j]; - const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); - st->add_normal(godot_normal); - if (ai_mesh->HasTangentsAndBitangents()) { - const aiVector3D tangents = ai_mesh->mTangents[j]; - const Vector3 godot_tangent = Vector3(tangents.x, tangents.y, tangents.z); - const aiVector3D bitangent = ai_mesh->mBitangents[j]; - const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); - float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; - st->add_tangent(Plane(tangents.x, tangents.y, tangents.z, d)); - } - } + aiTextureType tex_albedo = aiTextureType_DIFFUSE; + if (ai_material->GetTextureCount(tex_albedo) > 0) { - if (s != NULL && s->get_bone_count() > 0) { - Map<uint32_t, Vector<String> >::Element *I = vertex_bone_name.find(j); - Vector<int32_t> bones; - if (I != NULL) { - Vector<String> bone_names; - bone_names.append_array(I->value()); - for (int32_t f = 0; f < bone_names.size(); f++) { - int32_t bone = s->find_bone(bone_names[f]); - ERR_EXPLAIN("Asset Importer: Mesh can't find bone " + bone_names[f]); - ERR_FAIL_COND(bone == -1); - bones.push_back(bone); - } - if (s->get_bone_count()) { - int32_t add = CLAMP(p_max_bone_weights - bones.size(), 0, p_max_bone_weights); - for (int32_t f = 0; f < add; f++) { - bones.push_back(0); - } - } - st->add_bones(bones); - Map<uint32_t, Vector<float> >::Element *E = vertex_weight.find(j); - Vector<float> weights; - if (E != NULL) { - weights = E->value(); - if (weights.size() != p_max_bone_weights) { - int32_t add = CLAMP(p_max_bone_weights - weights.size(), 0, p_max_bone_weights); - for (int32_t f = 0; f < add; f++) { - weights.push_back(0.0f); - } - } + aiString ai_filename = aiString(); + String filename = ""; + aiTextureMapMode map_mode[2]; + if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { + filename = _assimp_raw_string_to_string(ai_filename); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) { + _set_texture_mapping_mode(map_mode, texture); + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } - ERR_CONTINUE(weights.size() == 0); - st->add_weights(weights); + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); } } - const aiVector3D pos = ai_mesh->mVertices[j]; - Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z); - st->add_vertex(godot_pos); } - for (size_t j = 0; j < ai_mesh->mNumFaces; j++) { - const aiFace face = ai_mesh->mFaces[j]; - ERR_FAIL_COND(face.mNumIndices != 3); - Vector<size_t> order; - order.push_back(2); - order.push_back(1); - order.push_back(0); - for (int32_t k = 0; k < order.size(); k++) { - st->add_index(face.mIndices[order[k]]); + } else { + aiColor4D clr_diffuse; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) { + if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + } + mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); + } + } + + aiString tex_gltf_base_color_path = aiString(); + aiTextureMapMode map_mode[2]; + if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) { + String filename = _assimp_raw_string_to_string(tex_gltf_base_color_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); + if (texture != NULL) { + if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { + _set_texture_mapping_mode(map_mode, texture); + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + } + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); } } - if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) { - st->generate_tangents(); - } - aiMaterial *ai_material = p_scene->mMaterials[ai_mesh->mMaterialIndex]; - Ref<SpatialMaterial> mat; - mat.instance(); - - int32_t mat_two_sided = 0; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) { - if (mat_two_sided > 0) { - mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } else { + aiColor4D pbr_base_color; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) { + if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } + mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); } - - const String mesh_name = _ai_string_to_string(ai_mesh->mName); - aiString mat_name; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) { - mat->set_name(_ai_string_to_string(mat_name)); - } - - aiTextureType tex_normal = aiTextureType_NORMALS; - { - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - - if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _ai_raw_string_to_string(ai_filename); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - - if (texture != NULL) { - if (map_mode != NULL) { - _set_texture_mapping_mode(map_mode, texture); - } - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); + } + { + aiString tex_fbx_pbs_base_color_path = aiString(); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); + if (texture != NULL) { + if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); } } - } - - { - aiString ai_filename = aiString(); - String filename = ""; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) { - filename = _ai_raw_string_to_string(ai_filename); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } + } else { + aiColor4D pbr_base_color; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) { + mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); } } - aiTextureType tex_emissive = aiTextureType_EMISSIVE; - - if (ai_material->GetTextureCount(tex_emissive) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; + aiUVTransform pbr_base_color_uv_xform; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { + mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); + mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); + } + } - if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _ai_raw_string_to_string(ai_filename); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true); - mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture); - } + { + aiString tex_fbx_pbs_normal_path = aiString(); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_normal_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); + if (texture != NULL) { + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); } } } + } - aiTextureType tex_albedo = aiTextureType_DIFFUSE; - if (ai_material->GetTextureCount(tex_albedo) > 0) { - - aiString ai_filename = aiString(); - String filename = ""; - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) { - filename = _ai_raw_string_to_string(ai_filename); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) { - _set_texture_mapping_mode(map_mode, texture); - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } - } else { - aiColor4D clr_diffuse; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) { - if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); + if (p_double_sided) { + mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); + } + + { + aiString tex_fbx_stingray_normal_path = aiString(); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_stingray_normal_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); + if (texture != NULL) { + mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); + mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); } - mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a)); } } + } - aiString tex_gltf_base_color_path = aiString(); - aiTextureMapMode map_mode[2]; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) { - String filename = _ai_raw_string_to_string(tex_gltf_base_color_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); + { + aiString tex_fbx_pbs_base_color_path = aiString(); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); bool found = false; - _find_texture_path(p_path, path, found); + _find_texture_path(state.path, path, found); if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); if (texture != NULL) { if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - _set_texture_mapping_mode(map_mode, texture); mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } @@ -1589,263 +1063,274 @@ void EditorSceneImporterAssimp::_add_mesh_to_mesh_instance(const aiNode *p_node, } } else { aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) { - if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) { mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); } } - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); - } - } - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); - } + aiUVTransform pbr_base_color_uv_xform; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { + mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); + mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); } + } - { - aiString tex_fbx_pbs_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_normal_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); + { + aiString tex_fbx_pbs_emissive_path = aiString(); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_emissive_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + _find_texture_path(state.path, path, found); + if (texture != NULL) { + if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { + mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); } + mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); } } + } else { + aiColor4D pbr_emmissive_color; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) { + mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a)); + } } - aiString cull_mode; - if (p_node->mMetaData) { - p_node->mMetaData->Get("Culling", cull_mode); - } - if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) { - mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); + real_t pbr_emission_intensity; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) { + mat->set_emission_energy(pbr_emission_intensity); } + } - { - aiString tex_fbx_stingray_normal_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_stingray_normal_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); - if (texture != NULL) { - mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true); - mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture); - } - } + aiString tex_gltf_pbr_metallicroughness_path; + if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) { + String filename = _assimp_raw_string_to_string(tex_gltf_pbr_metallicroughness_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); + mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); + mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); } } + } else { + float pbr_roughness = 0.0f; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) { + mat->set_roughness(pbr_roughness); + } + float pbr_metallic = 0.0f; - { - aiString tex_fbx_pbs_base_color_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_base_color_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_base_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) { - mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a)); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) { + mat->set_metallic(pbr_metallic); + } + } + { + aiString tex_fbx_pbs_metallic_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); + mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); } } - - aiUVTransform pbr_base_color_uv_xform; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) { - mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f)); - mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f)); + } else { + float pbr_metallic = 0.0f; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) { + mat->set_metallic(pbr_metallic); } } - { - aiString tex_fbx_pbs_emissive_path = aiString(); - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_emissive_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - _find_texture_path(p_path, path, found); - if (texture != NULL) { - if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) { - mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); - } - mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture); - } - } - } else { - aiColor4D pbr_emmissive_color; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) { - mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a)); + aiString tex_fbx_pbs_rough_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); + mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); } } + } else { + float pbr_roughness = 0.04f; - real_t pbr_emission_intensity; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) { - mat->set_emission_energy(pbr_emission_intensity); + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) { + mat->set_roughness(pbr_roughness); } } + } - aiString tex_gltf_pbr_metallicroughness_path; - if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) { - String filename = _ai_raw_string_to_string(tex_gltf_pbr_metallicroughness_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); + { + aiString tex_fbx_pbs_metallic_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); bool found = false; - _find_texture_path(p_path, path, found); + _find_texture_path(state.path, path, found); if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); + Ref<Texture> texture = _load_texture(state, path); if (texture != NULL) { mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE); - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN); + mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); } } } else { - float pbr_roughness = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); - } float pbr_metallic = 0.0f; - - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) { + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) { mat->set_metallic(pbr_metallic); } } - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); + + aiString tex_fbx_pbs_rough_path; + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { + String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path); + String path = state.path.get_base_dir() + "/" + filename.replace("\\", "/"); + bool found = false; + _find_texture_path(state.path, path, found); + if (found) { + Ref<Texture> texture = _load_texture(state, path); + if (texture != NULL) { + mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); + mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); } } + } else { + float pbr_roughness = 0.04f; + + if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) { + mat->set_roughness(pbr_roughness); + } + } + } + + return mat; +} + +Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton, bool p_double_sided_material) { + + Ref<ArrayMesh> mesh; + mesh.instance(); + bool has_uvs = false; - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); + for (int i = 0; i < p_surface_indices.size(); i++) { + const unsigned int mesh_idx = p_surface_indices[i]; + const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx]; + + Map<uint32_t, Vector<BoneInfo> > vertex_weights; + + if (p_skeleton) { + for (size_t b = 0; b < ai_mesh->mNumBones; b++) { + aiBone *bone = ai_mesh->mBones[b]; + String bone_name = _assimp_get_string(bone->mName); + int bone_index = p_skeleton->find_bone(bone_name); + ERR_CONTINUE(bone_index == -1); //bone refers to an unexisting index, wtf. + + for (size_t w = 0; w < bone->mNumWeights; w++) { + + aiVertexWeight ai_weights = bone->mWeights[w]; + + BoneInfo bi; + + uint32_t vertex_index = ai_weights.mVertexId; + bi.bone = bone_index; + bi.weight = ai_weights.mWeight; + ; + + if (!vertex_weights.has(vertex_index)) { + vertex_weights[vertex_index] = Vector<BoneInfo>(); } - } - } else { - float pbr_roughness = 0.04f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); + vertex_weights[vertex_index].push_back(bi); } } } - { - aiString tex_fbx_pbs_metallic_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_metallic_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture); - mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_metallic = 0.0f; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) { - mat->set_metallic(pbr_metallic); + Ref<SurfaceTool> st; + st.instance(); + st->begin(Mesh::PRIMITIVE_TRIANGLES); + + for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { + if (ai_mesh->HasTextureCoords(0)) { + has_uvs = true; + st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y)); + } + if (ai_mesh->HasTextureCoords(1)) { + has_uvs = true; + st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y)); + } + if (ai_mesh->HasVertexColors(0)) { + Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a); + st->add_color(color); + } + if (ai_mesh->mNormals != NULL) { + const aiVector3D normals = ai_mesh->mNormals[j]; + const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z); + st->add_normal(godot_normal); + if (ai_mesh->HasTangentsAndBitangents()) { + const aiVector3D tangents = ai_mesh->mTangents[j]; + const Vector3 godot_tangent = Vector3(tangents.x, tangents.y, tangents.z); + const aiVector3D bitangent = ai_mesh->mBitangents[j]; + const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z); + float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f; + st->add_tangent(Plane(tangents.x, tangents.y, tangents.z, d)); } } - aiString tex_fbx_pbs_rough_path; - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) { - String filename = _ai_raw_string_to_string(tex_fbx_pbs_rough_path); - String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/"); - bool found = false; - _find_texture_path(p_path, path, found); - if (found) { - Ref<Texture> texture = _load_texture(p_scene, path); - if (texture != NULL) { - mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture); - mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE); - } - } - } else { - float pbr_roughness = 0.04f; + if (vertex_weights.has(j)) { - if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) { - mat->set_roughness(pbr_roughness); + Vector<BoneInfo> bone_info = vertex_weights[j]; + Vector<int> bones; + bones.resize(bone_info.size()); + Vector<float> weights; + weights.resize(bone_info.size()); + for (int k = 0; k < bone_info.size(); k++) { + bones.write[k] = bone_info[k].bone; + weights.write[k] = bone_info[k].weight; } + + st->add_bones(bones); + st->add_weights(weights); } + + const aiVector3D pos = ai_mesh->mVertices[j]; + Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z); + st->add_vertex(godot_pos); + } + + for (size_t j = 0; j < ai_mesh->mNumFaces; j++) { + const aiFace face = ai_mesh->mFaces[j]; + ERR_CONTINUE(face.mNumIndices != 3); + Vector<size_t> order; + order.push_back(2); + order.push_back(1); + order.push_back(0); + for (int32_t k = 0; k < order.size(); k++) { + st->add_index(face.mIndices[order[k]]); + } + } + if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) { + st->generate_tangents(); + } + + Ref<Material> material; + + if (!state.material_cache.has(ai_mesh->mMaterialIndex)) { + material = _generate_material_from_index(state, ai_mesh->mMaterialIndex, p_double_sided_material); } Array array_mesh = st->commit_to_arrays(); @@ -1855,13 +1340,16 @@ void EditorSceneImporterAssimp::_add_mesh_to_mesh_instance(const aiNode *p_node, Map<uint32_t, String> morph_mesh_idx_names; for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) { - String ai_anim_mesh_name = _ai_string_to_string(ai_mesh->mAnimMeshes[j]->mName); - mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); - if (ai_anim_mesh_name.empty()) { - ai_anim_mesh_name = String("morph_") + itos(j); + if (i == 0) { + //only do this the first time + String ai_anim_mesh_name = _assimp_get_string(ai_mesh->mAnimMeshes[j]->mName); + mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED); + if (ai_anim_mesh_name.empty()) { + ai_anim_mesh_name = String("morph_") + itos(j); + } + mesh->add_blend_shape(ai_anim_mesh_name); } - mesh->add_blend_shape(ai_anim_mesh_name); - morph_mesh_idx_names.insert(j, ai_anim_mesh_name); + Array array_copy; array_copy.resize(VisualServer::ARRAY_MAX); @@ -1945,82 +1433,192 @@ void EditorSceneImporterAssimp::_add_mesh_to_mesh_instance(const aiNode *p_node, morphs[j] = array_copy; } - r_name_morph_mesh_names.insert(_ai_string_to_string(p_node->mName), morph_mesh_idx_names); + mesh->add_surface_from_arrays(primitive, array_mesh, morphs); - mesh->surface_set_material(i, mat); - mesh->surface_set_name(i, _ai_string_to_string(ai_mesh->mName)); - r_mesh_count++; - print_line(String("Open Asset Import: Created mesh (including instances) ") + _ai_string_to_string(ai_mesh->mName) + " " + itos(r_mesh_count) + " of " + itos(p_scene->mNumMeshes)); + mesh->surface_set_material(i, material); + mesh->surface_set_name(i, _assimp_get_string(ai_mesh->mName)); } - p_mesh_instance->set_mesh(mesh); + + return mesh; } -Ref<Texture> EditorSceneImporterAssimp::_load_texture(const aiScene *p_scene, String p_path) { - Vector<String> split_path = p_path.get_basename().split("*"); - if (split_path.size() == 2) { - size_t texture_idx = split_path[1].to_int(); - ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Texture>()); - aiTexture *tex = p_scene->mTextures[texture_idx]; - String filename = _ai_raw_string_to_string(tex->mFilename); - filename = filename.get_file(); - print_verbose("Open Asset Import: Loading embedded texture " + filename); - if (tex->mHeight == 0) { - if (tex->CheckFormat("png")) { - Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); +void EditorSceneImporterAssimp::_generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent) { - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("jpg")) { - Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; - } else if (tex->CheckFormat("dds")) { - ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); - ERR_FAIL_COND_V(true, Ref<Texture>()); - //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); - //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - //Ref<ImageTexture> t; - //t.instance(); - //t->create_from_image(img); - //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - //return t; + Spatial *new_node = NULL; + String node_name = _assimp_get_string(p_assimp_node->mName); + Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation); + + if (p_assimp_node->mNumMeshes > 0) { + /* MESH NODE */ + Ref<Mesh> mesh; + Skeleton *skeleton = NULL; + { + + //see if we have mesh cache for this. + Vector<int> surface_indices; + for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) { + int mesh_index = p_assimp_node->mMeshes[i]; + surface_indices.push_back(mesh_index); + + //take the chane and attempt to find the skeleton from the bones + if (!skeleton) { + aiMesh *ai_mesh = state.assimp_scene->mMeshes[p_assimp_node->mMeshes[i]]; + for (uint32_t j = 0; j < ai_mesh->mNumBones; j++) { + aiBone *bone = ai_mesh->mBones[j]; + String bone_name = _assimp_get_string(bone->mName); + if (state.bone_owners.has(bone_name)) { + skeleton = state.skeletons[state.bone_owners[bone_name]]; + break; + } + } + } } - } else { - Ref<Image> img; - img.instance(); - PoolByteArray arr; - uint32_t size = tex->mWidth * tex->mHeight; - arr.resize(size); - memcpy(arr.write().ptr(), tex->pcData, size); - ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Texture>()); - //ARGB8888 to RGBA8888 - for (int32_t i = 0; i < arr.size() / 4; i++) { - arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0]; - arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1]; - arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2]; - arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3]; + surface_indices.sort(); + String mesh_key; + for (int i = 0; i < surface_indices.size(); i++) { + if (i > 0) { + mesh_key += ":"; + } + mesh_key += itos(surface_indices[i]); } - img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr); - ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); - Ref<ImageTexture> t; - t.instance(); - t->create_from_image(img); - t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); - return t; + if (!state.mesh_cache.has(mesh_key)) { + //adding cache + aiString cull_mode; //cull is on mesh, which is kind of stupid tbh + bool double_sided_material = false; + if (p_assimp_node->mMetaData) { + p_assimp_node->mMetaData->Get("Culling", cull_mode); + } + if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) { + double_sided_material = true; + } + + mesh = _generate_mesh_from_surface_indices(state, surface_indices, skeleton, double_sided_material); + state.mesh_cache[mesh_key] = mesh; + } + + mesh = state.mesh_cache[mesh_key]; } - return Ref<Texture>(); + + MeshInstance *mesh_node = memnew(MeshInstance); + if (skeleton) { + state.mesh_skeletons[mesh_node] = skeleton; + } + mesh_node->set_mesh(mesh); + new_node = mesh_node; + + } else if (state.light_cache.has(node_name)) { + + Light *light = NULL; + aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[node_name]]; + ERR_FAIL_COND(!ai_light); + + if (ai_light->mType == aiLightSource_DIRECTIONAL) { + light = memnew(DirectionalLight); + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); + + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + + node_transform *= light_transform; + + } else if (ai_light->mType == aiLightSource_POINT) { + light = memnew(OmniLight); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Transform xform; + xform.origin = pos; + + node_transform *= xform; + + light->set_transform(xform); + + //light->set_param(Light::PARAM_ATTENUATION, 1); + } else if (ai_light->mType == aiLightSource_SPOT) { + light = memnew(SpotLight); + + Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z); + dir.normalize(); + Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z); + Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z); + up.normalize(); + + Transform light_transform; + light_transform.set_look_at(pos, pos + dir, up); + node_transform *= light_transform; + + //light->set_param(Light::PARAM_ATTENUATION, 0.0f); + } + ERR_FAIL_COND(light == NULL); + light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b)); + new_node = light; + } else if (state.camera_cache.has(node_name)) { + + aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[node_name]]; + ERR_FAIL_COND(!ai_camera); + + Camera *camera = memnew(Camera); + + float near = ai_camera->mClipPlaneNear; + if (Math::is_equal_approx(near, 0.0f)) { + near = 0.1f; + } + camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar); + + Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z); + Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized(); + Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z); + + Transform xform; + xform.set_look_at(pos, look_at, up); + + new_node = camera; + } else if (state.bone_owners.has(node_name)) { + + //have to actually put the skeleton somewhere, you know. + Skeleton *skeleton = state.skeletons[state.bone_owners[node_name]]; + if (skeleton->get_parent()) { + //a bone for a skeleton already added.. + //could go downwards here to add meshes children of skeleton bones + //but let's not support it for now. + return; + } + //restore rest poses to local, now that we know where the skeleton finally is + Transform skeleton_transform; + if (p_assimp_node->mParent) { + skeleton_transform = _get_global_assimp_node_transform(p_assimp_node->mParent); + } + for (int i = 0; i < skeleton->get_bone_count(); i++) { + Transform rest = skeleton_transform.affine_inverse() * skeleton->get_bone_rest(i); + skeleton->set_bone_rest(i, rest.affine_inverse()); + } + + skeleton->localize_rests(); + node_name = "Skeleton"; //don't use the bone root name + node_transform = Transform(); //dont transform + + new_node = skeleton; + } else { + //generic node + new_node = memnew(Spatial); + } + + { + + new_node->set_name(node_name); + new_node->set_transform(node_transform); + p_parent->add_child(new_node); + new_node->set_owner(state.root); + } + + state.node_map[node_name] = new_node; + + for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) { + _generate_node(state, p_assimp_node->mChildren[i], new_node); } - Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture"); - return p_texture; } void EditorSceneImporterAssimp::_calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) { @@ -2122,31 +1720,25 @@ void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Direct } } -String EditorSceneImporterAssimp::_ai_string_to_string(const aiString p_string) const { - Vector<char> raw_name; - raw_name.resize(p_string.length); - memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length); +String EditorSceneImporterAssimp::_assimp_get_string(const aiString p_string) const { + //convert an assimp String to a Godot String String name; - name.parse_utf8(raw_name.ptrw(), raw_name.size()); + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); if (name.find(":") != -1) { String replaced_name = name.split(":")[1]; print_verbose("Replacing " + name + " containing : with " + replaced_name); name = replaced_name; } - if (name.find(".") != -1) { - String replaced_name = name.replace(".", ""); - print_verbose("Replacing " + name + " containing . with " + replaced_name); - name = replaced_name; - } + + name = name.replace(".", ""); //can break things, specially bone names + return name; } -String EditorSceneImporterAssimp::_ai_anim_string_to_string(const aiString p_string) const { - Vector<char> raw_name; - raw_name.resize(p_string.length); - memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length); +String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString p_string) const { + String name; - name.parse_utf8(raw_name.ptrw(), raw_name.size()); + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); if (name.find(":") != -1) { String replaced_name = name.split(":")[1]; print_verbose("Replacing " + name + " containing : with " + replaced_name); @@ -2155,12 +1747,9 @@ String EditorSceneImporterAssimp::_ai_anim_string_to_string(const aiString p_str return name; } -String EditorSceneImporterAssimp::_ai_raw_string_to_string(const aiString p_string) const { - Vector<char> raw_name; - raw_name.resize(p_string.length); - memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length); +String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString p_string) const { String name; - name.parse_utf8(raw_name.ptrw(), raw_name.size()); + name.parse_utf8(p_string.C_Str() /*,p_string.length*/); return name; } @@ -2168,14 +1757,10 @@ Ref<Animation> EditorSceneImporterAssimp::import_animation(const String &p_path, return Ref<Animation>(); } -const Transform EditorSceneImporterAssimp::_ai_matrix_transform(const aiMatrix4x4 p_matrix) { +const Transform EditorSceneImporterAssimp::_assimp_matrix_transform(const aiMatrix4x4 p_matrix) { aiMatrix4x4 matrix = p_matrix; Transform xform; - xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4); - xform.basis.inverse(); - xform.basis.transpose(); - Vector3 scale = xform.basis.get_scale(); - Quat rot = xform.basis.get_rotation_quat(); - xform.basis.set_quat_scale(rot, scale); + //xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4); + xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4); return xform; } diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h index 8f9ed434ae..598845236e 100644 --- a/modules/assimp/editor_scene_importer_assimp.h +++ b/modules/assimp/editor_scene_importer_assimp.h @@ -146,37 +146,65 @@ private: COORD_LEFT = 1 }; }; - Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); - void _fill_kept_node(Set<Node *> &keep_nodes); - String _find_skeleton_bone_root(Map<Skeleton *, MeshInstance *> &skeletons, Map<MeshInstance *, String> &meshes, Spatial *root); - void _set_bone_parent(Skeleton *s, Node *p_owner, aiNode *p_node); - Transform _get_global_ai_node_transform(const aiScene *p_scene, const aiNode *p_current_node); - void _generate_node_bone(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const String p_path, const int32_t p_max_bone_weights); - void _generate_node_bone_parents(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const MeshInstance *p_mi); - void _calculate_skeleton_root(Skeleton *s, const aiScene *p_scene, aiNode *&p_ai_skeleton_root, Map<String, bool> &mesh_bones, const aiNode *p_node); - void _fill_skeleton(const aiScene *p_scene, const aiNode *p_node, Spatial *p_current, Node *p_owner, Skeleton *p_skeleton, const Map<String, bool> p_mesh_bones, const Map<String, Transform> &p_bone_rests, Set<String> p_tracks, const String p_path, Set<String> &r_removed_bones); - void _keep_node(const String &p_path, Node *p_current, Node *p_owner, Set<Node *> &r_keep_nodes); - void _filter_node(const String &p_path, Node *p_current, Node *p_owner, const Set<Node *> p_keep_nodes, Set<String> &r_removed_nodes); - void _generate_node(const String &p_path, const aiScene *p_scene, const aiNode *p_node, Node *p_parent, Node *p_owner, Set<String> &r_bone_name, Set<String> p_light_names, Set<String> p_camera_names, Map<Skeleton *, MeshInstance *> &r_skeletons, const Map<String, Transform> &p_bone_rests, Vector<MeshInstance *> &r_mesh_instances, int32_t &r_mesh_count, Skeleton *p_skeleton, const int32_t p_max_bone_weights, Set<String> &r_removed_bones, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names); - aiNode *_ai_find_node(aiNode *ai_child_node, const String bone_name); - Transform _format_rot_xform(const String p_path, const aiScene *p_scene); - void _get_track_set(const aiScene *p_scene, Set<String> &tracks); - void _insert_animation_track(const aiScene *p_scene, const String p_path, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, float length, const Skeleton *sk, const aiNodeAnim *track, String node_name, NodePath node_path); - void _add_mesh_to_mesh_instance(const aiNode *p_node, const aiScene *p_scene, Skeleton *s, const String &p_path, MeshInstance *p_mesh_instance, Node *p_owner, Set<String> &r_bone_name, int32_t &r_mesh_count, int32_t p_max_bone_weights, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names); - Ref<Texture> _load_texture(const aiScene *p_scene, String p_path); + + struct ImportState { + + String path; + const aiScene *assimp_scene; + uint32_t max_bone_weights; + Spatial *root; + Map<String, Ref<Mesh> > mesh_cache; + Map<int, Ref<Material> > material_cache; + Map<String, int> light_cache; + Map<String, int> camera_cache; + Vector<Skeleton *> skeletons; + Map<String, int> bone_owners; //maps bones to skeleton index owned by + Map<String, Node *> node_map; + Map<MeshInstance *, Skeleton *> mesh_skeletons; + bool fbx; //for some reason assimp does some things different for FBX + AnimationPlayer *animation_player; + }; + + struct BoneInfo { + uint32_t bone; + float weight; + }; + + struct SkeletonHole { //nodes may be part of the skeleton by used by vertex + String name; + String parent; + Transform pose; + const aiNode *node; + }; + + const Transform _assimp_matrix_transform(const aiMatrix4x4 p_matrix); + String _assimp_get_string(const aiString p_string) const; + Transform _get_global_assimp_node_transform(const aiNode *p_current_node); + void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w); void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture); void _find_texture_path(const String &p_path, String &path, bool &r_found); void _find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension); - String _ai_string_to_string(const aiString p_string) const; - String _ai_anim_string_to_string(const aiString p_string) const; - String _ai_raw_string_to_string(const aiString p_string) const; - void _import_animation(const String p_path, const Vector<MeshInstance *> p_meshes, const aiScene *p_scene, AnimationPlayer *ap, int32_t p_index, int p_bake_fps, Map<Skeleton *, MeshInstance *> p_skeletons, const Set<String> p_removed_nodes, const Set<String> removed_bones, const Map<String, Map<uint32_t, String> > p_path_morph_mesh_names); - void _insert_pivot_anim_track(const Vector<MeshInstance *> p_meshes, const String p_node_name, Vector<const aiNodeAnim *> F, AnimationPlayer *ap, Skeleton *sk, float &length, float ticks_per_second, Ref<Animation> animation, int p_bake_fps, const String &p_path, const aiScene *p_scene); + + Ref<Texture> _load_texture(ImportState &state, String p_path); + Ref<Material> _generate_material_from_index(ImportState &state, int p_index, bool p_double_sided); + Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton = NULL, bool p_double_sided_material = false); + void _generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent); + void _generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms); + void _fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms); + void _generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms); + + void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name); + + void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps); + + Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights); + + String _assimp_anim_string_to_string(const aiString p_string) const; + String _assimp_raw_string_to_string(const aiString p_string) const; float _get_fbx_fps(int32_t time_mode, const aiScene *p_scene); template <class T> T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp); - const Transform _ai_matrix_transform(const aiMatrix4x4 p_matrix); void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const; struct ImportFormat { diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 71e7eecd1d..9882a89794 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -6427,7 +6427,24 @@ "major": 3, "minor": 1 }, - "next": null, + "next": { + "type": "NET", + "version": { + "major": 3, + "minor": 2 + }, + "next": null, + "api": [ + { + "name": "godot_net_bind_webrtc_peer", + "return_type": "void", + "arguments": [ + ["godot_object *", "p_obj"], + ["const godot_net_webrtc_peer *", "p_interface"] + ] + } + ] + }, "api": [ { "name": "godot_net_bind_stream_peer", diff --git a/modules/gdnative/include/net/godot_net.h b/modules/gdnative/include/net/godot_net.h index d7de04e725..c1bc9daab5 100644 --- a/modules/gdnative/include/net/godot_net.h +++ b/modules/gdnative/include/net/godot_net.h @@ -111,6 +111,35 @@ typedef struct { /* Binds a MultiplayerPeerGDNative to the provided interface */ void GDAPI godot_net_bind_multiplayer_peer(godot_object *p_obj, const godot_net_multiplayer_peer *); +typedef struct { + godot_gdnative_api_version version; /* version of our API */ + + godot_object *data; /* User reference */ + + /* This is PacketPeer */ + godot_error (*get_packet)(void *, const uint8_t **, int *); + godot_error (*put_packet)(void *, const uint8_t *, int); + godot_int (*get_available_packet_count)(const void *); + godot_int (*get_max_packet_size)(const void *); + + /* This is WebRTCPeer */ + void (*set_write_mode)(void *, godot_int); + godot_int (*get_write_mode)(const void *); + bool (*was_string_packet)(const void *); + godot_int (*get_connection_state)(const void *); + + godot_error (*create_offer)(void *); + godot_error (*set_remote_description)(void *, const char *, const char *); + godot_error (*set_local_description)(void *, const char *, const char *); + godot_error (*add_ice_candidate)(void *, const char *, int, const char *); + godot_error (*poll)(void *); + + void *next; /* For extension? */ +} godot_net_webrtc_peer; + +/* Binds a PacketPeerGDNative to the provided interface */ +void GDAPI godot_net_bind_webrtc_peer(godot_object *p_obj, const godot_net_webrtc_peer *); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/net/SCsub b/modules/gdnative/net/SCsub index e915703935..18ab9986b0 100644 --- a/modules/gdnative/net/SCsub +++ b/modules/gdnative/net/SCsub @@ -3,5 +3,11 @@ Import('env') Import('env_gdnative') -env_gdnative.add_source_files(env.modules_sources, '*.cpp') +env_net = env_gdnative.Clone() + +has_webrtc = env_net["module_webrtc_enabled"] +if has_webrtc: + env_net.Append(CPPDEFINES=['WEBRTC_GDNATIVE_ENABLED']) + +env_net.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/net/webrtc_peer_gdnative.cpp b/modules/gdnative/net/webrtc_peer_gdnative.cpp new file mode 100644 index 0000000000..60b1ed4fe4 --- /dev/null +++ b/modules/gdnative/net/webrtc_peer_gdnative.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* packet_peer_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "modules/gdnative/gdnative.h" +#include "modules/gdnative/include/net/godot_net.h" + +#ifdef WEBRTC_GDNATIVE_ENABLED +#include "modules/webrtc/webrtc_peer_gdnative.h" +#endif + +extern "C" { + +void GDAPI godot_net_bind_webrtc_peer(godot_object *p_obj, const godot_net_webrtc_peer *p_impl) { +#ifdef WEBRTC_GDNATIVE_ENABLED + ((WebRTCPeerGDNative *)p_obj)->set_native_webrtc_peer(p_impl); +#endif +} +} diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ae67521749..f7be0ce37c 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -480,16 +480,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: switch (cast_type.kind) { case GDScriptDataType::BUILTIN: { codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); - codegen.opcodes.push_back(cn->cast_type.builtin_type); + codegen.opcodes.push_back(cast_type.builtin_type); } break; case GDScriptDataType::NATIVE: { int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(cn->cast_type.native_type)) { + if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cn->cast_type.native_type]; + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type]; class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } else { - _set_error("Invalid native class type '" + String(cn->cast_type.native_type) + "'.", cn); + _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn); return -1; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator @@ -498,7 +498,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { - Variant script = cn->cast_type.script_type; + Variant script = cast_type.script_type; int idx = codegen.get_constant_pos(script); idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) @@ -1863,6 +1863,19 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->base = base; p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; + + if (p_class->base_type.kind == GDScriptParser::DataType::CLASS) { + if (!parsed_classes.has(p_script->_base)) { + if (parsing_classes.has(p_script->_base)) { + _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class); + return ERR_PARSE_ERROR; + } + Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } + } + } } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6ef906b98d..8a9eacd835 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3690,6 +3690,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name); } } + for (int i = 0; i < p_class->subclasses.size(); i++) { + if (p_class->subclasses[i]->name == name) { + _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); + } + } #endif // DEBUG_ENABLED if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { @@ -4634,6 +4639,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } } + + for (int i = 0; i < current_class->subclasses.size(); i++) { + if (current_class->subclasses[i]->name == member.identifier) { + _set_error("A class named '" + String(member.identifier) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + return; + } + } #ifdef DEBUG_ENABLED for (int i = 0; i < current_class->functions.size(); i++) { if (current_class->functions[i]->name == member.identifier) { @@ -4878,6 +4890,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + for (int i = 0; i < current_class->subclasses.size(); i++) { + if (current_class->subclasses[i]->name == const_id) { + _set_error("A class named '" + String(const_id) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + return; + } + } + tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { @@ -4948,6 +4967,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + for (int i = 0; i < current_class->subclasses.size(); i++) { + if (current_class->subclasses[i]->name == enum_name) { + _set_error("A class named '" + String(enum_name) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + return; + } + } + tokenizer->advance(); } if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { @@ -5033,6 +5059,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + for (int i = 0; i < current_class->subclasses.size(); i++) { + if (current_class->subclasses[i]->name == const_id) { + _set_error("A class named '" + String(const_id) + "' already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); + return; + } + } + ClassNode::Constant constant; constant.type.has_type = true; constant.type.kind = DataType::BUILTIN; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 5f97069485..3c9644127c 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1424,6 +1424,34 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { p_properties->push_back(E->value()); } + + // Call _get_property_list + + ERR_FAIL_COND(!script.is_valid()); + + MonoObject *mono_object = get_mono_object(); + ERR_FAIL_NULL(mono_object); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get_property_list), 0); + + if (method) { + MonoObject *ret = method->invoke(mono_object); + + if (ret) { + Array array = Array(GDMonoMarshal::mono_object_to_variant(ret)); + for (int i = 0, size = array.size(); i < size; i++) + p_properties->push_back(PropertyInfo::from_dict(array.get(i))); + return; + } + + break; + } + + top = top->get_parent_class(); + } } Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { @@ -3027,6 +3055,7 @@ CSharpLanguage::StringNameCache::StringNameCache() { _signal_callback = StaticCString::create("_signal_callback"); _set = StaticCString::create("_set"); _get = StaticCString::create("_get"); + _get_property_list = StaticCString::create("_get_property_list"); _notification = StaticCString::create("_notification"); _script_source = StaticCString::create("script/source"); dotctor = StaticCString::create(".ctor"); diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 99877a4379..050527d52b 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -298,6 +298,7 @@ class CSharpLanguage : public ScriptLanguage { StringName _signal_callback; StringName _set; StringName _get; + StringName _get_property_list; StringName _notification; StringName _script_source; StringName dotctor; // .ctor diff --git a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml index b5bc35df69..06d22a2d65 100644 --- a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml +++ b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml @@ -37,6 +37,14 @@ Generate a noise image with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters. </description> </method> + <method name="get_noise_1d"> + <return type="float"> + </return> + <argument index="0" name="x" type="float"> + </argument> + <description> + </description> + </method> <method name="get_noise_2d"> <return type="float"> </return> diff --git a/modules/webrtc/SCsub b/modules/webrtc/SCsub new file mode 100644 index 0000000000..446bd530c2 --- /dev/null +++ b/modules/webrtc/SCsub @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +# Thirdparty source files + +env_webrtc = env_modules.Clone() +use_gdnative = env_webrtc["module_gdnative_enabled"] + +if use_gdnative: # GDNative is retained in Javascript for export compatibility + env_webrtc.Append(CPPDEFINES=['WEBRTC_GDNATIVE_ENABLED']) + gdnative_includes = ["#modules/gdnative/include/"] + env_webrtc.Append(CPPPATH=gdnative_includes) + +env_webrtc.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webrtc/config.py b/modules/webrtc/config.py new file mode 100644 index 0000000000..5ed245bad2 --- /dev/null +++ b/modules/webrtc/config.py @@ -0,0 +1,13 @@ +def can_build(env, platform): + return True + +def configure(env): + pass + +def get_doc_classes(): + return [ + "WebRTCPeer" + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp new file mode 100644 index 0000000000..ee7a766bd9 --- /dev/null +++ b/modules/webrtc/register_types.cpp @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "webrtc_peer.h" + +#ifdef JAVASCRIPT_ENABLED +#include "emscripten.h" +#include "webrtc_peer_js.h" +#endif +#ifdef WEBRTC_GDNATIVE_ENABLED +#include "webrtc_peer_gdnative.h" +#endif + +void register_webrtc_types() { +#ifdef JAVASCRIPT_ENABLED + WebRTCPeerJS::make_default(); +#elif defined(WEBRTC_GDNATIVE_ENABLED) + WebRTCPeerGDNative::make_default(); +#endif + + ClassDB::register_custom_instance_class<WebRTCPeer>(); +#ifdef WEBRTC_GDNATIVE_ENABLED + ClassDB::register_class<WebRTCPeerGDNative>(); +#endif +} + +void unregister_webrtc_types() {} diff --git a/modules/webrtc/register_types.h b/modules/webrtc/register_types.h new file mode 100644 index 0000000000..18a5dcc5aa --- /dev/null +++ b/modules/webrtc/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +void register_webrtc_types(); +void unregister_webrtc_types(); diff --git a/modules/webrtc/webrtc_peer.cpp b/modules/webrtc/webrtc_peer.cpp new file mode 100644 index 0000000000..30c4505df9 --- /dev/null +++ b/modules/webrtc/webrtc_peer.cpp @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* webrtc_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "webrtc_peer.h" + +WebRTCPeer *(*WebRTCPeer::_create)() = NULL; + +Ref<WebRTCPeer> WebRTCPeer::create_ref() { + + if (!_create) + return Ref<WebRTCPeer>(); + return Ref<WebRTCPeer>(_create()); +} + +WebRTCPeer *WebRTCPeer::create() { + + if (!_create) + return NULL; + return _create(); +} + +void WebRTCPeer::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_offer"), &WebRTCPeer::create_offer); + ClassDB::bind_method(D_METHOD("set_local_description", "type", "sdp"), &WebRTCPeer::set_local_description); + ClassDB::bind_method(D_METHOD("set_remote_description", "type", "sdp"), &WebRTCPeer::set_remote_description); + ClassDB::bind_method(D_METHOD("poll"), &WebRTCPeer::poll); + ClassDB::bind_method(D_METHOD("add_ice_candidate", "media", "index", "name"), &WebRTCPeer::add_ice_candidate); + + ClassDB::bind_method(D_METHOD("was_string_packet"), &WebRTCPeer::was_string_packet); + ClassDB::bind_method(D_METHOD("set_write_mode", "write_mode"), &WebRTCPeer::set_write_mode); + ClassDB::bind_method(D_METHOD("get_write_mode"), &WebRTCPeer::get_write_mode); + ClassDB::bind_method(D_METHOD("get_connection_state"), &WebRTCPeer::get_connection_state); + + ADD_SIGNAL(MethodInfo("offer_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp"))); + ADD_SIGNAL(MethodInfo("new_ice_candidate", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name"))); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "write_mode", PROPERTY_HINT_ENUM), "set_write_mode", "get_write_mode"); + + BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); + BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); + + BIND_ENUM_CONSTANT(STATE_NEW); + BIND_ENUM_CONSTANT(STATE_CONNECTING); + BIND_ENUM_CONSTANT(STATE_CONNECTED); + BIND_ENUM_CONSTANT(STATE_DISCONNECTED); + BIND_ENUM_CONSTANT(STATE_FAILED); + BIND_ENUM_CONSTANT(STATE_CLOSED); +} + +WebRTCPeer::WebRTCPeer() { +} + +WebRTCPeer::~WebRTCPeer() { +} diff --git a/modules/webrtc/webrtc_peer.h b/modules/webrtc/webrtc_peer.h new file mode 100644 index 0000000000..e141c14655 --- /dev/null +++ b/modules/webrtc/webrtc_peer.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* webrtc_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef WEBRTC_PEER_H +#define WEBRTC_PEER_H + +#include "core/io/packet_peer.h" + +class WebRTCPeer : public PacketPeer { + GDCLASS(WebRTCPeer, PacketPeer); + +public: + enum WriteMode { + WRITE_MODE_TEXT, + WRITE_MODE_BINARY, + }; + + enum ConnectionState { + STATE_NEW, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTED, + STATE_FAILED, + STATE_CLOSED + }; + +protected: + static void _bind_methods(); + static WebRTCPeer *(*_create)(); + +public: + virtual void set_write_mode(WriteMode mode) = 0; + virtual WriteMode get_write_mode() const = 0; + virtual bool was_string_packet() const = 0; + virtual ConnectionState get_connection_state() const = 0; + + virtual Error create_offer() = 0; + virtual Error set_remote_description(String type, String sdp) = 0; + virtual Error set_local_description(String type, String sdp) = 0; + virtual Error add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName) = 0; + virtual Error poll() = 0; + + /** Inherited from PacketPeer: **/ + virtual int get_available_packet_count() const = 0; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) = 0; ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) = 0; + + virtual int get_max_packet_size() const = 0; + + static Ref<WebRTCPeer> create_ref(); + static WebRTCPeer *create(); + + WebRTCPeer(); + ~WebRTCPeer(); +}; + +VARIANT_ENUM_CAST(WebRTCPeer::WriteMode); +VARIANT_ENUM_CAST(WebRTCPeer::ConnectionState); +#endif // WEBRTC_PEER_H diff --git a/modules/webrtc/webrtc_peer_gdnative.cpp b/modules/webrtc/webrtc_peer_gdnative.cpp new file mode 100644 index 0000000000..f782944980 --- /dev/null +++ b/modules/webrtc/webrtc_peer_gdnative.cpp @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* webrtc_peer_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef WEBRTC_GDNATIVE_ENABLED + +#include "webrtc_peer_gdnative.h" + +void WebRTCPeerGDNative::_bind_methods() { +} + +WebRTCPeerGDNative::WebRTCPeerGDNative() { + interface = NULL; +} + +WebRTCPeerGDNative::~WebRTCPeerGDNative() { +} + +Error WebRTCPeerGDNative::create_offer() { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->create_offer(interface->data); +} + +Error WebRTCPeerGDNative::set_local_description(String p_type, String p_sdp) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->set_local_description(interface->data, p_type.utf8().get_data(), p_sdp.utf8().get_data()); +} + +Error WebRTCPeerGDNative::set_remote_description(String p_type, String p_sdp) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->set_remote_description(interface->data, p_type.utf8().get_data(), p_sdp.utf8().get_data()); +} + +Error WebRTCPeerGDNative::add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->add_ice_candidate(interface->data, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data()); +} + +Error WebRTCPeerGDNative::poll() { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->poll(interface->data); +} + +void WebRTCPeerGDNative::set_write_mode(WriteMode p_mode) { + ERR_FAIL_COND(interface == NULL); + interface->set_write_mode(interface->data, p_mode); +} + +WebRTCPeer::WriteMode WebRTCPeerGDNative::get_write_mode() const { + ERR_FAIL_COND_V(interface == NULL, WRITE_MODE_BINARY); + return (WriteMode)interface->get_write_mode(interface->data); +} + +bool WebRTCPeerGDNative::was_string_packet() const { + ERR_FAIL_COND_V(interface == NULL, false); + return interface->was_string_packet(interface->data); +} + +WebRTCPeer::ConnectionState WebRTCPeerGDNative::get_connection_state() const { + ERR_FAIL_COND_V(interface == NULL, STATE_DISCONNECTED); + return STATE_DISCONNECTED; +} + +Error WebRTCPeerGDNative::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->get_packet(interface->data, r_buffer, &r_buffer_size); +} + +Error WebRTCPeerGDNative::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->put_packet(interface->data, p_buffer, p_buffer_size); +} + +int WebRTCPeerGDNative::get_max_packet_size() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_max_packet_size(interface->data); +} + +int WebRTCPeerGDNative::get_available_packet_count() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_available_packet_count(interface->data); +} + +void WebRTCPeerGDNative::set_native_webrtc_peer(const godot_net_webrtc_peer *p_impl) { + interface = p_impl; +} + +#endif // WEBRTC_GDNATIVE_ENABLED diff --git a/modules/webrtc/webrtc_peer_gdnative.h b/modules/webrtc/webrtc_peer_gdnative.h new file mode 100644 index 0000000000..6786cec8ea --- /dev/null +++ b/modules/webrtc/webrtc_peer_gdnative.h @@ -0,0 +1,78 @@ +/*************************************************************************/ +/* webrtc_peer_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef WEBRTC_GDNATIVE_ENABLED + +#ifndef WEBRTC_PEER_GDNATIVE_H +#define WEBRTC_PEER_GDNATIVE_H + +#include "modules/gdnative/include/net/godot_net.h" +#include "webrtc_peer.h" + +class WebRTCPeerGDNative : public WebRTCPeer { + GDCLASS(WebRTCPeerGDNative, WebRTCPeer); + +protected: + static void _bind_methods(); + +private: + const godot_net_webrtc_peer *interface; + +public: + static WebRTCPeer *_create() { return memnew(WebRTCPeerGDNative); } + static void make_default() { WebRTCPeer::_create = WebRTCPeerGDNative::_create; } + + void set_native_webrtc_peer(const godot_net_webrtc_peer *p_impl); + + virtual void set_write_mode(WriteMode mode); + virtual WriteMode get_write_mode() const; + virtual bool was_string_packet() const; + virtual ConnectionState get_connection_state() const; + + virtual Error create_offer(); + virtual Error set_remote_description(String type, String sdp); + virtual Error set_local_description(String type, String sdp); + virtual Error add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName); + virtual Error poll(); + + /** Inherited from PacketPeer: **/ + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + + virtual int get_max_packet_size() const; + + WebRTCPeerGDNative(); + ~WebRTCPeerGDNative(); +}; + +#endif // WEBRTC_PEER_GDNATIVE_H + +#endif // WEBRTC_GDNATIVE_ENABLED diff --git a/modules/webrtc/webrtc_peer_js.cpp b/modules/webrtc/webrtc_peer_js.cpp new file mode 100644 index 0000000000..1282e075ab --- /dev/null +++ b/modules/webrtc/webrtc_peer_js.cpp @@ -0,0 +1,455 @@ +/*************************************************************************/ +/* webrtc_peer_js.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef JAVASCRIPT_ENABLED + +#include "webrtc_peer_js.h" +#include "emscripten.h" + +extern "C" { +EMSCRIPTEN_KEEPALIVE void _emrtc_on_ice_candidate(void *obj, char *p_MidName, int p_MlineIndexName, char *p_sdpName) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->emit_signal("new_ice_candidate", String(p_MidName), p_MlineIndexName, String(p_sdpName)); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_offer_created(void *obj, char *p_type, char *p_offer) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->emit_signal("offer_created", String(p_type), String(p_offer)); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_on_error(void *obj) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->_on_error(); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_on_open(void *obj) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->_on_open(); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_on_close(void *obj) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->_on_close(); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_on_message(void *obj, uint8_t *p_data, uint32_t p_size, bool p_is_string) { + WebRTCPeerJS *peer = static_cast<WebRTCPeerJS *>(obj); + peer->_on_message(p_data, p_size, p_is_string); +} + +EMSCRIPTEN_KEEPALIVE void _emrtc_bind_channel(int p_id) { + /* clang-format off */ + EM_ASM({ + if (!Module.IDHandler.has($0)) { + return; // Godot Object is gone! + } + var dict = Module.IDHandler.get($0); + var channel = dict["channel"]; + var c_ptr = dict["ptr"]; + + channel.onopen = function (evt) { + ccall("_emrtc_on_open", + "void", + ["number"], + [c_ptr] + ); + }; + channel.onclose = function (evt) { + ccall("_emrtc_on_close", + "void", + ["number"], + [c_ptr] + ); + }; + channel.onerror = function (evt) { + ccall("_emrtc_on_error", + "void", + ["number"], + [c_ptr] + ); + }; + + channel.binaryType = "arraybuffer"; + channel.onmessage = function(event) { + var buffer; + var is_string = 0; + if (event.data instanceof ArrayBuffer) { + + buffer = new Uint8Array(event.data); + + } else if (event.data instanceof Blob) { + + alert("Blob type not supported"); + return; + + } else if (typeof event.data === "string") { + + is_string = 1; + var enc = new TextEncoder("utf-8"); + buffer = new Uint8Array(enc.encode(event.data)); + + } else { + + alert("Unknown message type"); + return; + + } + var len = buffer.length*buffer.BYTES_PER_ELEMENT; + var out = Module._malloc(len); + Module.HEAPU8.set(buffer, out); + ccall("_emrtc_on_message", + "void", + ["number", "number", "number", "number"], + [c_ptr, out, len, is_string] + ); + Module._free(out); + } + }, p_id); + /* clang-format on */ +} +} + +void _emrtc_create_pc(int p_id) { + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var c_ptr = dict["ptr"]; + // Setup local connaction + var conn = new RTCPeerConnection(); + conn.onicecandidate = function(event) { + if (!Module.IDHandler.get($0)) return; + if (!event.candidate) return; + + var c = event.candidate; + // should emit on ice candidate + ccall("_emrtc_on_ice_candidate", + "void", + ["number", "string", "number", "string"], + [c_ptr, c.sdpMid, c.sdpMLineIndex, c.candidate] + ); + }; + conn.ondatachannel = function (evt) { + var dict = Module.IDHandler.get($0); + if (!dict || dict["channel"]) { + return; + } + var channel = evt.channel; + dict["channel"] = channel; + ccall("_emrtc_bind_channel", + "void", + ["number"], + [$0] + ); + }; + dict["conn"] = conn; + }, p_id); + /* clang-format on */ +} + +void WebRTCPeerJS::_on_open() { + in_buffer.resize(16); + _conn_state = STATE_CONNECTED; +} + +void WebRTCPeerJS::_on_close() { + close(); +} + +void WebRTCPeerJS::_on_error() { + close(); + _conn_state = STATE_FAILED; +} + +void WebRTCPeerJS::_on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string) { + if (in_buffer.space_left() < p_size + 5) { + ERR_EXPLAIN("Buffer full! Dropping data"); + ERR_FAIL(); + } + + uint8_t is_string = p_is_string ? 1 : 0; + in_buffer.write((uint8_t *)&p_size, 4); + in_buffer.write((uint8_t *)&is_string, 1); + in_buffer.write(p_data, p_size); + queue_count++; +} + +void WebRTCPeerJS::close() { + in_buffer.resize(0); + queue_count = 0; + _was_string = false; + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + if (!dict) return; + if (dict["channel"]) { + dict["channel"].close(); + dict["channel"] = null; + } + if (dict["conn"]) { + dict["conn"].close(); + } + }, _js_id); + /* clang-format on */ + _conn_state = STATE_CLOSED; +} + +Error WebRTCPeerJS::create_offer() { + ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED); + + _conn_state = STATE_CONNECTING; + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var conn = dict["conn"]; + var c_ptr = dict["ptr"]; + var onError = function(error) { + console.log(error); + ccall("_emrtc_on_error", + "void", + ["number"], + [c_ptr] + ); + }; + var onCreated = function(offer) { + ccall("_emrtc_offer_created", + "void", + ["number", "string", "string"], + [c_ptr, offer.type, offer.sdp] + ); + }; + + var channel = conn.createDataChannel("default"); + dict["channel"] = channel; + ccall("_emrtc_bind_channel", + "void", + ["number"], + [$0] + ); + conn.createOffer().then(onCreated).catch(onError); + }, _js_id); + /* clang-format on */ + return OK; +} + +Error WebRTCPeerJS::set_local_description(String type, String sdp) { + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var conn = dict["conn"]; + var c_ptr = dict["ptr"]; + var type = UTF8ToString($1); + var sdp = UTF8ToString($2); + var onError = function(error) { + console.log(error); + ccall("_emrtc_on_error", + "void", + ["number"], + [c_ptr] + ); + }; + conn.setLocalDescription({ + "sdp": sdp, + "type": type + }).catch(onError); + }, _js_id, type.utf8().get_data(), sdp.utf8().get_data()); + /* clang-format on */ + return OK; +} + +Error WebRTCPeerJS::set_remote_description(String type, String sdp) { + if (type == "offer") { + ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED); + _conn_state = STATE_CONNECTING; + } + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var conn = dict["conn"]; + var c_ptr = dict["ptr"]; + var type = UTF8ToString($1); + var sdp = UTF8ToString($2); + + var onError = function(error) { + console.log(error); + ccall("_emrtc_on_error", + "void", + ["number"], + [c_ptr] + ); + }; + var onCreated = function(offer) { + ccall("_emrtc_offer_created", + "void", + ["number", "string", "string"], + [c_ptr, offer.type, offer.sdp] + ); + }; + var onSet = function() { + if (type != "offer") { + return; + } + conn.createAnswer().then(onCreated); + }; + conn.setRemoteDescription({ + "sdp": sdp, + "type": type + }).then(onSet).catch(onError); + }, _js_id, type.utf8().get_data(), sdp.utf8().get_data()); + /* clang-format on */ + return OK; +} + +Error WebRTCPeerJS::add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName) { + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var conn = dict["conn"]; + var c_ptr = dict["ptr"]; + var sdpMidName = UTF8ToString($1); + var sdpMlineIndexName = UTF8ToString($2); + var sdpName = UTF8ToString($3); + conn.addIceCandidate(new RTCIceCandidate({ + "candidate": sdpName, + "sdpMid": sdpMidName, + "sdpMlineIndex": sdpMlineIndexName + })); + }, _js_id, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data()); + /* clang-format on */ + return OK; +} + +Error WebRTCPeerJS::poll() { + return OK; +} + +WebRTCPeer::ConnectionState WebRTCPeerJS::get_connection_state() const { + return _conn_state; +} + +int WebRTCPeerJS::get_available_packet_count() const { + return queue_count; +} + +Error WebRTCPeerJS::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + ERR_FAIL_COND_V(_conn_state != STATE_CONNECTED, ERR_UNCONFIGURED); + + if (queue_count == 0) + return ERR_UNAVAILABLE; + + uint32_t to_read = 0; + uint32_t left = 0; + uint8_t is_string = 0; + r_buffer_size = 0; + + in_buffer.read((uint8_t *)&to_read, 4); + --queue_count; + left = in_buffer.data_left(); + + if (left < to_read + 1) { + in_buffer.advance_read(left); + return FAILED; + } + + in_buffer.read(&is_string, 1); + _was_string = is_string == 1; + in_buffer.read(packet_buffer, to_read); + *r_buffer = packet_buffer; + r_buffer_size = to_read; + + return OK; +} + +Error WebRTCPeerJS::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(_conn_state != STATE_CONNECTED, ERR_UNCONFIGURED); + + int is_bin = _write_mode == WebRTCPeer::WRITE_MODE_BINARY ? 1 : 0; + + /* clang-format off */ + EM_ASM({ + var dict = Module.IDHandler.get($0); + var channel = dict["channel"]; + var bytes_array = new Uint8Array($2); + var i = 0; + + for(i=0; i<$2; i++) { + bytes_array[i] = getValue($1+i, 'i8'); + } + + if ($3) { + channel.send(bytes_array.buffer); + } else { + var string = new TextDecoder("utf-8").decode(bytes_array); + channel.send(string); + } + }, _js_id, p_buffer, p_buffer_size, is_bin); + /* clang-format on */ + + return OK; +} + +int WebRTCPeerJS::get_max_packet_size() const { + return 1200; +} + +void WebRTCPeerJS::set_write_mode(WriteMode p_mode) { + _write_mode = p_mode; +} + +WebRTCPeer::WriteMode WebRTCPeerJS::get_write_mode() const { + return _write_mode; +} + +bool WebRTCPeerJS::was_string_packet() const { + return _was_string; +} + +WebRTCPeerJS::WebRTCPeerJS() { + queue_count = 0; + _was_string = false; + _write_mode = WRITE_MODE_BINARY; + _conn_state = STATE_NEW; + + /* clang-format off */ + _js_id = EM_ASM_INT({ + return Module.IDHandler.add({"conn": null, "ptr": $0, "channel": null}); + }, this); + /* clang-format on */ + _emrtc_create_pc(_js_id); +} + +WebRTCPeerJS::~WebRTCPeerJS() { + close(); + /* clang-format off */ + EM_ASM({ + Module.IDHandler.remove($0); + }, _js_id); + /* clang-format on */ +}; +#endif diff --git a/modules/webrtc/webrtc_peer_js.h b/modules/webrtc/webrtc_peer_js.h new file mode 100644 index 0000000000..02f0c9b55d --- /dev/null +++ b/modules/webrtc/webrtc_peer_js.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* webrtc_peer_js.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef WEBRTC_PEER_JS_H +#define WEBRTC_PEER_JS_H + +#ifdef JAVASCRIPT_ENABLED + +#include "webrtc_peer.h" + +class WebRTCPeerJS : public WebRTCPeer { + +private: + enum { + PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type + }; + + bool _was_string; + WriteMode _write_mode; + + int _js_id; + RingBuffer<uint8_t> in_buffer; + int queue_count; + uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + ConnectionState _conn_state; + +public: + static WebRTCPeer *_create() { return memnew(WebRTCPeerJS); } + static void make_default() { WebRTCPeer::_create = WebRTCPeerJS::_create; } + + virtual void set_write_mode(WriteMode mode); + virtual WriteMode get_write_mode() const; + virtual bool was_string_packet() const; + virtual ConnectionState get_connection_state() const; + + virtual Error create_offer(); + virtual Error set_remote_description(String type, String sdp); + virtual Error set_local_description(String type, String sdp); + virtual Error add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName); + virtual Error poll(); + + /** Inherited from PacketPeer: **/ + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + + virtual int get_max_packet_size() const; + + void close(); + void _on_open(); + void _on_close(); + void _on_error(); + void _on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string); + + WebRTCPeerJS(); + ~WebRTCPeerJS(); +}; + +#endif + +#endif // WEBRTC_PEER_JS_H diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index ed6cc5638e..39bf3de982 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -60,25 +60,6 @@ void register_websocket_types() { _SET_HINT(WSS_OUT_PKT, 1024, 16384); #ifdef JAVASCRIPT_ENABLED - EM_ASM({ - var IDHandler = {}; - IDHandler["ids"] = {}; - IDHandler["has"] = function(id) { - return IDHandler.ids.hasOwnProperty(id); - }; - IDHandler["add"] = function(obj) { - var id = crypto.getRandomValues(new Int32Array(32))[0]; - IDHandler.ids[id] = obj; - return id; - }; - IDHandler["get"] = function(id) { - return IDHandler.ids[id]; - }; - IDHandler["remove"] = function(id) { - delete IDHandler.ids[id]; - }; - Module["IDHandler"] = IDHandler; - }); EMWSPeer::make_default(); EMWSClient::make_default(); EMWSServer::make_default(); diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index a93c98a89f..85a633442e 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,6 +20,13 @@ for lib in js_libraries: env.Append(LINKFLAGS=['--js-library', env.File(lib).path]) env.Depends(build, js_libraries) +js_modules = [ + 'id_handler.js', +] +for module in js_modules: + env.Append(LINKFLAGS=['--pre-js', env.File(module).path]) +env.Depends(build, js_modules) + wrapper_start = env.File('pre.js') wrapper_end = env.File('engine.js') js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js') diff --git a/platform/javascript/id_handler.js b/platform/javascript/id_handler.js new file mode 100644 index 0000000000..36ef5aa8ef --- /dev/null +++ b/platform/javascript/id_handler.js @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* id_handler.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 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. */ +/*************************************************************************/ + +var IDHandler = function() { + + var ids = {}; + var size = 0; + + this.has = function(id) { + return ids.hasOwnProperty(id); + } + + this.add = function(obj) { + size += 1; + var id = crypto.getRandomValues(new Int32Array(32))[0]; + ids[id] = obj; + return id; + } + + this.get = function(id) { + return ids[id]; + } + + this.remove = function(id) { + size -= 1; + delete ids[id]; + } + + this.size = function() { + return size; + } + + this.ids = ids; +}; + +Module.IDHandler = new IDHandler; diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index b7279e4d4f..dafcf95b1f 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -540,10 +540,11 @@ void Skeleton::clear_bones() { void Skeleton::set_bone_pose(int p_bone, const Transform &p_pose) { ERR_FAIL_INDEX(p_bone, bones.size()); - ERR_FAIL_COND(!is_inside_tree()); bones.write[p_bone].pose = p_pose; - _make_dirty(); + if (is_inside_tree()) { + _make_dirty(); + } } Transform Skeleton::get_bone_pose(int p_bone) const { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 9acf8d40c9..39a0b0aaf2 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -590,6 +590,12 @@ void TextEdit::_notification(int p_what) { } } break; case NOTIFICATION_DRAW: { + + if (first_draw) { + //size may not be the final one, so attempts to ensure cursor was visible may have failed + adjust_viewport_to_cursor(); + first_draw = false; + } Size2 size = get_size(); if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { draw_caret = false; @@ -6356,6 +6362,7 @@ TextEdit::TextEdit() { menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); menu->connect("id_pressed", this, "menu_option"); + first_draw = true; } TextEdit::~TextEdit() { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 33f0a3f45d..96b409c6d4 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -275,6 +275,7 @@ private: int wrap_at; int wrap_right_offset; + bool first_draw; bool setting_row; bool draw_tabs; bool override_selected_font_color; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 7f00e7bd24..7e59606d2b 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2850,7 +2850,7 @@ void Tree::_notification(int p_what) { if (p_what == NOTIFICATION_DRAG_BEGIN) { single_select_defer = NULL; - if (cache.scroll_speed > 0 && get_rect().has_point(get_viewport()->get_mouse_position() - get_global_position())) { + if (cache.scroll_speed > 0) { scrolling = true; set_physics_process_internal(true); } @@ -2897,22 +2897,22 @@ void Tree::_notification(int p_what) { } } - if (scrolling) { - Point2 point = get_viewport()->get_mouse_position() - get_global_position(); - if (point.x < cache.scroll_border) { - point.x -= cache.scroll_border; - } else if (point.x > get_size().width - cache.scroll_border) { - point.x -= get_size().width - cache.scroll_border; - } else { - point.x = 0; + Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); + if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + Point2 point; + + if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < cache.scroll_border)) { + point.x = mouse_position.x - cache.scroll_border; + } else if (ABS(mouse_position.x - get_size().width) < cache.scroll_border) { + point.x = mouse_position.x - (get_size().width - cache.scroll_border); } - if (point.y < cache.scroll_border) { - point.y -= cache.scroll_border; - } else if (point.y > get_size().height - cache.scroll_border) { - point.y -= get_size().height - cache.scroll_border; - } else { - point.y = 0; + + if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < cache.scroll_border)) { + point.y = mouse_position.y - cache.scroll_border; + } else if (ABS(mouse_position.y - get_size().height) < cache.scroll_border) { + point.y = mouse_position.y - (get_size().height - cache.scroll_border); } + point *= cache.scroll_speed * get_physics_process_delta_time(); point += get_scroll(); h_scroll->set_value(point.x); @@ -3388,10 +3388,13 @@ void Tree::ensure_cursor_is_visible() { int h = compute_item_height(selected) + cache.vseparation; int screenh = get_size().height - h_scroll->get_combined_minimum_size().height; - if (ofs + h > v_scroll->get_value() + screenh) + if (h > screenh) { //screen size is too small, maybe it was not resized yet. + v_scroll->set_value(ofs); + } else if (ofs + h > v_scroll->get_value() + screenh) { v_scroll->call_deferred("set_value", ofs - screenh + h); - else if (ofs < v_scroll->get_value()) + } else if (ofs < v_scroll->get_value()) { v_scroll->set_value(ofs); + } } int Tree::get_pressed_button() const { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 87a15bbeda..3148df15dd 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -521,6 +521,8 @@ void register_scene_types() { ClassDB::register_class<VisualShaderNodeTransformUniform>(); ClassDB::register_class<VisualShaderNodeTextureUniform>(); ClassDB::register_class<VisualShaderNodeCubeMapUniform>(); + ClassDB::register_class<VisualShaderNodeIf>(); + ClassDB::register_class<VisualShaderNodeSwitch>(); ClassDB::register_class<ShaderMaterial>(); ClassDB::register_virtual_class<CanvasItem>(); diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index c0abee2030..3ba43006a3 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -108,8 +108,55 @@ void SurfaceTool::add_vertex(const Vector3 &p_vertex) { vtx.bones = last_bones; vtx.tangent = last_tangent.normal; vtx.binormal = last_normal.cross(last_tangent.normal).normalized() * last_tangent.d; + + const int expected_vertices = 4; + + if ((format & Mesh::ARRAY_FORMAT_WEIGHTS || format & Mesh::ARRAY_FORMAT_BONES) && (vtx.weights.size() != expected_vertices || vtx.bones.size() != expected_vertices)) { + //ensure vertices are the expected amount + ERR_FAIL_COND(vtx.weights.size() != vtx.bones.size()); + if (vtx.weights.size() < expected_vertices) { + //less than requred, fill + for (int i = vtx.weights.size(); i < expected_vertices; i++) { + vtx.weights.push_back(0); + vtx.bones.push_back(0); + } + } else if (vtx.weights.size() > expected_vertices) { + //more than required, sort, cap and normalize. + Vector<WeightSort> weights; + for (int i = 0; i < vtx.weights.size(); i++) { + WeightSort ws; + ws.index = vtx.bones[i]; + ws.weight = vtx.weights[i]; + weights.push_back(ws); + } + + //sort + weights.sort(); + //cap + weights.resize(expected_vertices); + //renormalize + float total = 0; + for (int i = 0; i < expected_vertices; i++) { + total += weights[i].weight; + } + + vtx.weights.resize(expected_vertices); + vtx.bones.resize(expected_vertices); + + for (int i = 0; i < expected_vertices; i++) { + if (total > 0) { + vtx.weights.write[i] = weights[i].weight / total; + } else { + vtx.weights.write[i] = 0; + } + vtx.bones.write[i] = weights[i].index; + } + } + } + vertex_array.push_back(vtx); first = false; + format |= Mesh::ARRAY_FORMAT_VERTEX; } void SurfaceTool::add_color(Color p_color) { @@ -161,7 +208,6 @@ void SurfaceTool::add_uv2(const Vector2 &p_uv2) { void SurfaceTool::add_bones(const Vector<int> &p_bones) { ERR_FAIL_COND(!begun); - ERR_FAIL_COND(p_bones.size() != 4); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_BONES)); format |= Mesh::ARRAY_FORMAT_BONES; @@ -171,8 +217,6 @@ void SurfaceTool::add_bones(const Vector<int> &p_bones) { void SurfaceTool::add_weights(const Vector<float> &p_weights) { ERR_FAIL_COND(!begun); - - ERR_FAIL_COND(p_weights.size() != 4); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_WEIGHTS)); format |= Mesh::ARRAY_FORMAT_WEIGHTS; diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index a3b110f0d8..c55cade813 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -62,6 +62,14 @@ private: static _FORCE_INLINE_ uint32_t hash(const Vertex &p_vtx); }; + struct WeightSort { + int index; + float weight; + bool operator<(const WeightSort &p_right) const { + return weight < p_right.weight; + } + }; + bool begun; bool first; Mesh::PrimitiveType primitive; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index fdb6f2ce09..4229147ba2 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -782,7 +782,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui } else if (in_type == VisualShaderNode::PORT_TYPE_VECTOR && out_type == VisualShaderNode::PORT_TYPE_SCALAR) { inputs[i] = "vec3(" + src_var + ")"; } else if (in_type == VisualShaderNode::PORT_TYPE_BOOLEAN && out_type == VisualShaderNode::PORT_TYPE_VECTOR) { - inputs[i] = "all(" + src_var + ")"; + inputs[i] = "all(bvec3(" + src_var + "))"; } else if (in_type == VisualShaderNode::PORT_TYPE_BOOLEAN && out_type == VisualShaderNode::PORT_TYPE_SCALAR) { inputs[i] = src_var + ">0.0?true:false"; } else if (in_type == VisualShaderNode::PORT_TYPE_SCALAR && out_type == VisualShaderNode::PORT_TYPE_BOOLEAN) { diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index dac999bf1d..d02902572c 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -2948,3 +2948,140 @@ String VisualShaderNodeCubeMapUniform::generate_code(Shader::Mode p_mode, Visual VisualShaderNodeCubeMapUniform::VisualShaderNodeCubeMapUniform() { } + +////////////// If + +String VisualShaderNodeIf::get_caption() const { + return "If"; +} + +int VisualShaderNodeIf::get_input_port_count() const { + return 6; +} + +VisualShaderNodeIf::PortType VisualShaderNodeIf::get_input_port_type(int p_port) const { + if (p_port == 0 || p_port == 1 || p_port == 2) { + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeIf::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "a"; + case 1: + return "b"; + case 2: + return "tolerance"; + case 3: + return "a == b"; + case 4: + return "a > b"; + case 5: + return "a < b"; + default: + return ""; + } +} + +int VisualShaderNodeIf::get_output_port_count() const { + return 1; +} + +VisualShaderNodeIf::PortType VisualShaderNodeIf::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeIf::get_output_port_name(int p_port) const { + return "result"; +} + +String VisualShaderNodeIf::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String code; + code += "\tif(abs(" + p_input_vars[0] + "-" + p_input_vars[1] + ")<" + p_input_vars[2] + ")\n"; // abs(a - b) < tolerance eg. a == b + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[3] + ";\n"; + code += "\t}\n"; + code += "\telse if(" + p_input_vars[0] + "<" + p_input_vars[1] + ")\n"; // a < b + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[5] + ";\n"; + code += "\t}\n"; + code += "\telse\n"; // a > b (or a >= b if abs(a - b) < tolerance is false) + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[4] + ";\n"; + code += "\t}\n"; + return code; +} + +VisualShaderNodeIf::VisualShaderNodeIf() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, CMP_EPSILON); + set_input_port_default_value(3, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(4, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(5, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Switch + +String VisualShaderNodeSwitch::get_caption() const { + return "Switch"; +} + +int VisualShaderNodeSwitch::get_input_port_count() const { + return 3; +} + +VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_BOOLEAN; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeSwitch::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "value"; + case 1: + return "true"; + case 2: + return "false"; + default: + return ""; + } +} + +int VisualShaderNodeSwitch::get_output_port_count() const { + return 1; +} + +VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeSwitch::get_output_port_name(int p_port) const { + return "result"; +} + +String VisualShaderNodeSwitch::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String code; + code += "\tif(" + p_input_vars[0] + ")\n"; + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[1] + ";\n"; + code += "\t}\n"; + code += "\telse\n"; + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[2] + ";\n"; + code += "\t}\n"; + return code; +} + +VisualShaderNodeSwitch::VisualShaderNodeSwitch() { + set_input_port_default_value(0, false); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 28ead64fe2..90c479bd48 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -1440,4 +1440,48 @@ public: VisualShaderNodeCubeMapUniform(); }; +/////////////////////////////////////// +/// IF +/////////////////////////////////////// + +class VisualShaderNodeIf : public VisualShaderNode { + GDCLASS(VisualShaderNodeIf, VisualShaderNode) +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeIf(); +}; + +/////////////////////////////////////// +/// SWITCH +/////////////////////////////////////// + +class VisualShaderNodeSwitch : public VisualShaderNode { + GDCLASS(VisualShaderNodeSwitch, VisualShaderNode) +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeSwitch(); +}; + #endif // VISUAL_SHADER_NODES_H |