diff options
187 files changed, 5954 insertions, 1454 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 96aedccd7f..6684978318 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -87,6 +87,8 @@ Files: ./servers/physics_3d/gjk_epa.cpp ./servers/physics_3d/joints/pin_joint_3d_sw.h ./servers/physics_3d/joints/slider_joint_3d_sw.cpp ./servers/physics_3d/joints/slider_joint_3d_sw.h + ./servers/physics_3d/soft_body_3d_sw.cpp + ./servers/physics_3d/soft_body_3d_sw.h Comment: Bullet Continuous Collision Detection and Physics Library Copyright: 2003-2008, Erwin Coumans 2007-2021, Juan Linietsky, Ariel Manzur. diff --git a/SConstruct b/SConstruct index 615ca447f9..aa38e568b5 100644 --- a/SConstruct +++ b/SConstruct @@ -115,7 +115,7 @@ opts.Add(BoolVariable("tools", "Build the tools (a.k.a. the Godot editor)", True opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "release_debug", "release"))) opts.Add("arch", "Platform-dependent architecture (arm/arm64/x86/x64/mips/...)", "") opts.Add(EnumVariable("bits", "Target platform bits", "default", ("default", "32", "64"))) -opts.Add(EnumVariable("optimize", "Optimization type", "speed", ("speed", "size"))) +opts.Add(EnumVariable("optimize", "Optimization type", "speed", ("speed", "size", "none"))) opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False)) opts.Add(BoolVariable("use_lto", "Use link-time optimization", False)) diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index 015c1f0d90..10f68f3cef 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -295,6 +295,9 @@ Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream) return OK; } +void ConfigFile::clear() { + values.clear(); +} void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value); ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant())); @@ -317,4 +320,6 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("save_encrypted", "path", "key"), &ConfigFile::save_encrypted); ClassDB::bind_method(D_METHOD("save_encrypted_pass", "path", "password"), &ConfigFile::save_encrypted_pass); + + ClassDB::bind_method(D_METHOD("clear"), &ConfigFile::clear); } diff --git a/core/io/config_file.h b/core/io/config_file.h index 386d304f07..1b28257c60 100644 --- a/core/io/config_file.h +++ b/core/io/config_file.h @@ -68,6 +68,8 @@ public: Error load(const String &p_path); Error parse(const String &p_data); + void clear(); + Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key); Error load_encrypted_pass(const String &p_path, const String &p_pass); diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index 91efec5534..eeb486073e 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -115,6 +115,9 @@ public: ImportOption() {} }; + virtual bool has_advanced_options() const { return false; } + virtual void show_advanced_options(const String &p_path) {} + virtual int get_preset_count() const { return 0; } virtual String get_preset_name(int p_idx) const { return String(); } diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 9cee218735..ade5f7b4d8 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -853,7 +853,7 @@ void Translation::set_locale(const String &p_locale) { locale = univ_locale; } - if (OS::get_singleton()->get_main_loop()) { + if (OS::get_singleton()->get_main_loop() && TranslationServer::get_singleton()->get_loaded_locales().has(this)) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index e192e85cd1..7e9da01275 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -777,7 +777,7 @@ <return type="int"> </return> <description> - Returns a random unsigned 32 bit integer. Use remainder to obtain a random value in the interval [code][0, N - 1][/code] (where N is smaller than 2^32). + Returns a random unsigned 32-bit integer. Use remainder to obtain a random value in the interval [code][0, N - 1][/code] (where N is smaller than 2^32). [codeblock] randi() # Returns random integer between 0 and 2^32 - 1 randi() % 20 # Returns random integer between 0 and 19 diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index b66f4d11f2..a28bde9946 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -264,7 +264,7 @@ Beginning corner. Typically has values lower than [member end]. </member> <member name="size" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )"> - Size from [member position] to [member end]. Typically all components are positive. + Size from [member position] to [member end]. Typically, all components are positive. If the size is negative, you can use [method abs] to fix it. </member> </members> diff --git a/doc/classes/AStar.xml b/doc/classes/AStar.xml index bfdc66623d..e975b8ed28 100644 --- a/doc/classes/AStar.xml +++ b/doc/classes/AStar.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AStar" inherits="Reference" version="4.0"> <brief_description> - An implementation of A* to find shortest paths among connected points in space. + An implementation of A* to find the shortest paths among connected points in space. </brief_description> <description> A* (A star) is a computer algorithm that is widely used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in three-dimensional space and Euclidean distances by default. diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 9720405ffd..7ceb21d22e 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -710,7 +710,7 @@ [b]Note:[/b] Length is not delimited by the last key, as this one may be before or after the end to ensure correct interpolation and looping. </member> <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false"> - A flag indicating that the animation must loop. This is uses for correct interpolation of animation cycles, and for hinting the player that it must restart the animation. + A flag indicating that the animation must loop. This is used for correct interpolation of animation cycles, and for hinting the player that it must restart the animation. </member> <member name="step" type="float" setter="set_step" getter="get_step" default="0.1"> The animation step value. diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index e5ba1d58f7..7696f36009 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -237,7 +237,7 @@ </member> <member name="current_animation" type="String" setter="set_current_animation" getter="get_current_animation" default=""""> The name of the currently playing animation. If no animation is playing, the property's value is an empty string. Changing this value does not restart the animation. See [method play] for more information on playing animations. - [b]Note[/b]: while this property appears in the inspector, it's not meant to be edited and it's not saved in the scene. This property is mainly used to get the currently playing animation, and internally for animation playback tracks. For more information, see [Animation]. + [b]Note[/b]: while this property appears in the inspector, it's not meant to be edited, and it's not saved in the scene. This property is mainly used to get the currently playing animation, and internally for animation playback tracks. For more information, see [Animation]. </member> <member name="current_animation_length" type="float" setter="" getter="get_current_animation_length"> The length (in seconds) of the currently being played animation. diff --git a/doc/classes/BackBufferCopy.xml b/doc/classes/BackBufferCopy.xml index 7cc6a5613b..9a70b8f20c 100644 --- a/doc/classes/BackBufferCopy.xml +++ b/doc/classes/BackBufferCopy.xml @@ -4,7 +4,7 @@ Copies a region of the screen (or the whole screen) to a buffer so it can be accessed in your shader scripts through the [code]texture(SCREEN_TEXTURE, ...)[/code] function. </brief_description> <description> - Node for back-buffering the currently-displayed screen. The region defined in the BackBufferCopy node is bufferized with the content of the screen it covers, or the entire screen according to the copy mode set. Use the [code]texture(SCREEN_TEXTURE, ...)[/code] function in your shader scripts to access the buffer. + Node for back-buffering the currently-displayed screen. The region defined in the BackBufferCopy node is buffered with the content of the screen it covers, or the entire screen according to the copy mode set. Use the [code]texture(SCREEN_TEXTURE, ...)[/code] function in your shader scripts to access the buffer. [b]Note:[/b] Since this node inherits from [Node2D] (and not [Control]), anchors and margins won't apply to child [Control]-derived nodes. This can be problematic when resizing the window. To avoid this, add [Control]-derived nodes as [i]siblings[/i] to the BackBufferCopy node instead of adding them as children. </description> <tutorials> diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index fa0c3b167a..c87398ac8f 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -82,7 +82,7 @@ Texture to multiply by [member albedo_color]. Used for basic texturing of objects. </member> <member name="alpha_antialiasing_edge" type="float" setter="set_alpha_antialiasing_edge" getter="get_alpha_antialiasing_edge"> - Threshold at which antialiasing will by applied on the alpha channel. + Threshold at which antialiasing will be applied on the alpha channel. </member> <member name="alpha_antialiasing_mode" type="int" setter="set_alpha_antialiasing" getter="get_alpha_antialiasing" enum="BaseMaterial3D.AlphaAntiAliasing"> The type of alpha antialiasing to apply. See [enum AlphaAntiAliasing]. @@ -127,7 +127,7 @@ Texture used to control the backlight effect per-pixel. Added to [member backlight]. </member> <member name="billboard_keep_scale" type="bool" setter="set_flag" getter="get_flag" default="false"> - If [code]true[/code], the shader will keep the scale set for the mesh. Otherwise the scale is lost when billboarding. Only applies when [member billboard_mode] is [constant BILLBOARD_ENABLED]. + If [code]true[/code], the shader will keep the scale set for the mesh. Otherwise, the scale is lost when billboarding. Only applies when [member billboard_mode] is [constant BILLBOARD_ENABLED]. </member> <member name="billboard_mode" type="int" setter="set_billboard_mode" getter="get_billboard_mode" enum="BaseMaterial3D.BillboardMode" default="0"> Controls how the object faces the camera. See [enum BillboardMode]. diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml index 034b2e9629..9f1d6d8e31 100644 --- a/doc/classes/Camera3D.xml +++ b/doc/classes/Camera3D.xml @@ -194,7 +194,7 @@ </member> <member name="fov" type="float" setter="set_fov" getter="get_fov" default="75.0"> The camera's field of view angle (in degrees). Only applicable in perspective mode. Since [member keep_aspect] locks one axis, [code]fov[/code] sets the other axis' field of view angle. - For reference, the default vertical field of view value ([code]75.0[/code]) is equivalent to an horizontal FOV of: + For reference, the default vertical field of view value ([code]75.0[/code]) is equivalent to a horizontal FOV of: - ~91.31 degrees in a 4:3 viewport - ~101.67 degrees in a 16:10 viewport - ~107.51 degrees in a 16:9 viewport diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index d1483ac88e..81795abcdd 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -3,7 +3,7 @@ <brief_description> </brief_description> <description> - [b]Note[/b]: By default [CodeEdit] always use left-to-right text direction to correcly display source code. + [b]Note[/b]: By default [CodeEdit] always use left-to-right text direction to correctly display source code. </description> <tutorials> </tutorials> diff --git a/doc/classes/ConcavePolygonShape3D.xml b/doc/classes/ConcavePolygonShape3D.xml index 3e83202472..a9687abedc 100644 --- a/doc/classes/ConcavePolygonShape3D.xml +++ b/doc/classes/ConcavePolygonShape3D.xml @@ -28,6 +28,11 @@ </description> </method> </methods> + <members> + <member name="backface_collision" type="bool" setter="set_backface_collision_enabled" getter="is_backface_collision_enabled" default="false"> + If set to [code]true[/code], collisions occur on both sides of the concave shape faces. Otherwise they occur only along the face normals. + </member> + </members> <constants> </constants> </class> diff --git a/doc/classes/ConfigFile.xml b/doc/classes/ConfigFile.xml index da17d993e3..2cac424f2e 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -158,7 +158,7 @@ <argument index="0" name="data" type="String"> </argument> <description> - Parses the the passed string as the contents of a config file. The string is parsed and loaded in the ConfigFile object which the method was called on. + Parses the passed string as the contents of a config file. The string is parsed and loaded in the ConfigFile object which the method was called on. Returns one of the [enum Error] code constants ([code]OK[/code] on success). </description> </method> diff --git a/doc/classes/CryptoKey.xml b/doc/classes/CryptoKey.xml index 410c2262f9..26b3087b21 100644 --- a/doc/classes/CryptoKey.xml +++ b/doc/classes/CryptoKey.xml @@ -27,7 +27,7 @@ </argument> <description> Loads a key from [code]path[/code]. If [code]public_only[/code] is [code]true[/code], only the public key will be loaded. - [b]Note[/b]: [code]path[/code] should should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise. + [b]Note[/b]: [code]path[/code] should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise. </description> </method> <method name="load_from_string"> @@ -50,7 +50,7 @@ </argument> <description> Saves a key to the given [code]path[/code]. If [code]public_only[/code] is [code]true[/code], only the public key will be saved. - [b]Note[/b]: [code]path[/code] should should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise. + [b]Note[/b]: [code]path[/code] should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise. </description> </method> <method name="save_to_string"> diff --git a/doc/classes/Decal.xml b/doc/classes/Decal.xml index ca36b2400c..14c35ae6d3 100644 --- a/doc/classes/Decal.xml +++ b/doc/classes/Decal.xml @@ -6,7 +6,7 @@ <description> [Decal]s are used to project a texture onto a [Mesh] in the scene. Use Decals to add detail to a scene without affecting the underlying [Mesh]. They are often used to add weathering to building, add dirt or mud to the ground, or add variety to props. Decals can be moved at any time, making them suitable for things like blob shadows or laser sight dots. They are made of an [AABB] and a group of [Texture2D]s specifying [Color], normal, ORM (ambient occlusion, roughness, metallic), and emission. Decals are projected within their [AABB] so altering the orientation of the Decal affects the direction in which they are projected. By default, Decals are projected down (i.e. from positive Y to negative Y). - The [Texture2D]s associated with the Decal are automatically stored in a texture atlas which is used for drawing the decals so all decals can be drawn at once. Godot uses clustered decals, meaning they are stored in cluster data and drawn when the mesh is drawn, they are not drawn as a postprocessing effect after. + The [Texture2D]s associated with the Decal are automatically stored in a texture atlas which is used for drawing the decals so all decals can be drawn at once. Godot uses clustered decals, meaning they are stored in cluster data and drawn when the mesh is drawn, they are not drawn as a post-processing effect after. </description> <tutorials> </tutorials> @@ -65,7 +65,7 @@ Blends the albedo [Color] of the decal with albedo [Color] of the underlying mesh. </member> <member name="cull_mask" type="int" setter="set_cull_mask" getter="get_cull_mask" default="1048575"> - Specifies which [member VisualInstance3D.layers] this decal will project on. By default, Decals affect all layers. This is used so you can specify which types of objects receive the Decal and which do not. This is especially useful so you an ensure that dynamic objects don't accidentally receive a Decal intended for the terrain under them. + Specifies which [member VisualInstance3D.layers] this decal will project on. By default, Decals affect all layers. This is used so you can specify which types of objects receive the Decal and which do not. This is especially useful so you can ensure that dynamic objects don't accidentally receive a Decal intended for the terrain under them. </member> <member name="distance_fade_begin" type="float" setter="set_distance_fade_begin" getter="get_distance_fade_begin" default="10.0"> Distance from the camera at which the Decal begins to fade away. diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 05ffd7b2ac..16c4348994 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -4,7 +4,7 @@ Dictionary type. </brief_description> <description> - Dictionary type. Associative container which contains values referenced by unique keys. Dictionaries are composed of pairs of keys (which must be unique) and values. Dictionaries will preserve the insertion order when adding elements, even though this may not be reflected when printing the dictionary. In other programming languages, this data structure is sometimes referred to as an hash map or associative array. + Dictionary type. Associative container which contains values referenced by unique keys. Dictionaries are composed of pairs of keys (which must be unique) and values. Dictionaries will preserve the insertion order when adding elements, even though this may not be reflected when printing the dictionary. In other programming languages, this data structure is sometimes referred to as a hash map or associative array. You can define a dictionary by placing a comma-separated list of [code]key: value[/code] pairs in curly braces [code]{}[/code]. Erasing elements while iterating over them [b]is not supported[/b] and will result in undefined behavior. [b]Note:[/b] Dictionaries are always passed by reference. To get a copy of a dictionary which can be modified independently of the original dictionary, use [method duplicate]. @@ -266,7 +266,7 @@ <argument index="0" name="keys" type="Array"> </argument> <description> - Returns [code]true[/code] if the dictionary has all of the keys in the given array. + Returns [code]true[/code] if the dictionary has all the keys in the given array. </description> </method> <method name="hash" qualifiers="const"> diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index 2d7292717d..6a126204c6 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -125,7 +125,7 @@ <argument index="0" name="idx" type="int"> </argument> <description> - On Windows, returns the name of the drive (partition) passed as an argument (e.g. [code]C:[/code]). On other platforms, or if the requested drive does not existed, the method returns an empty String. + On Windows, returns the name of the drive (partition) passed as an argument (e.g. [code]C:[/code]). On other platforms, or if the requested drive does not exist, the method returns an empty String. </description> </method> <method name="get_drive_count"> @@ -167,7 +167,7 @@ <return type="void"> </return> <description> - Closes the current stream opened with [method list_dir_begin] (whether it has been fully processed with [method get_next] or not does not matter). + Closes the current stream opened with [method list_dir_begin] (whether it has been fully processed with [method get_next] does not matter). </description> </method> <method name="make_dir"> diff --git a/doc/classes/EditorFileSystem.xml b/doc/classes/EditorFileSystem.xml index 5461dccd27..3a045817c2 100644 --- a/doc/classes/EditorFileSystem.xml +++ b/doc/classes/EditorFileSystem.xml @@ -90,7 +90,7 @@ <argument index="0" name="resources" type="PackedStringArray"> </argument> <description> - Remitted if a resource is reimported. + Emitted if a resource is reimported. </description> </signal> <signal name="resources_reload"> diff --git a/doc/classes/EditorInspectorPlugin.xml b/doc/classes/EditorInspectorPlugin.xml index 3cc624f49b..8204dc931e 100644 --- a/doc/classes/EditorInspectorPlugin.xml +++ b/doc/classes/EditorInspectorPlugin.xml @@ -4,12 +4,12 @@ Plugin for adding custom property editors on inspector. </brief_description> <description> - This plugins allows adding custom property editors to [EditorInspector]. + These plugins allow adding custom property editors to [EditorInspector]. Plugins are registered via [method EditorPlugin.add_inspector_plugin]. When an object is edited, the [method can_handle] function is called and must return [code]true[/code] if the object type is supported. If supported, the function [method parse_begin] will be called, allowing to place custom controls at the beginning of the class. Subsequently, the [method parse_category] and [method parse_property] are called for every category and property. They offer the ability to add custom controls to the inspector too. - Finally [method parse_end] will be called. + Finally, [method parse_end] will be called. On each of these calls, the "add" functions can be called. </description> <tutorials> diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml index 1e9d089962..34657a1c08 100644 --- a/doc/classes/EditorNode3DGizmoPlugin.xml +++ b/doc/classes/EditorNode3DGizmoPlugin.xml @@ -170,7 +170,7 @@ <return type="bool"> </return> <description> - Override this method to define whether Node3D with this gizmo should be selecteble even when the gizmo is hidden. + Override this method to define whether Node3D with this gizmo should be selectable even when the gizmo is hidden. </description> </method> <method name="redraw" qualifiers="virtual"> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index a24e4bbdc5..8dcffb0b74 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -561,7 +561,7 @@ <argument index="0" name="script" type="Script"> </argument> <description> - Removes the debugger plugin with given script fromm the Debugger. + Removes the debugger plugin with given script from the Debugger. </description> </method> <method name="remove_export_plugin"> diff --git a/doc/classes/EditorVCSInterface.xml b/doc/classes/EditorVCSInterface.xml index bb356c2183..0056b5ce16 100644 --- a/doc/classes/EditorVCSInterface.xml +++ b/doc/classes/EditorVCSInterface.xml @@ -38,7 +38,7 @@ <return type="Dictionary"> </return> <description> - Returns a [Dictionary] containing the path of the detected file change mapped to an integer signifying what kind of a change the corresponding file has experienced. + Returns a [Dictionary] containing the path of the detected file change mapped to an integer signifying what kind of change the corresponding file has experienced. The following integer values are being used to signify that the detected file is: - [code]0[/code]: New to the VCS working directory - [code]1[/code]: Modified diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 02b81ee9b7..f9d8cf574a 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -162,7 +162,7 @@ The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. This value should generally always be set to [code]60[/code] or above, as Godot doesn't interpolate the physics step. As a result, values lower than [code]60[/code] will look stuttery. This value can be increased to make input more reactive or work around tunneling issues, but keep in mind doing so will increase CPU usage. </member> <member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix" default="0.5"> - Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows to smooth out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. + Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. </member> <member name="target_fps" type="int" setter="set_target_fps" getter="get_target_fps" default="0"> The desired frames per second. If the hardware cannot keep up, this setting may not be respected. A value of 0 means no limit. diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index bf5237422b..20d5b6ce9b 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -247,7 +247,7 @@ <argument index="1" name="size" type="int" default="-1"> </argument> <description> - Returns the size size of a bounding box of a string, taking kerning and advance into account. + Returns the size of a bounding box of a string, taking kerning and advance into account. [b]Note:[/b] Real height of the string is context-dependent and can be significantly different from the value returned by [method get_height]. See also [method draw_string]. </description> diff --git a/doc/classes/FontData.xml b/doc/classes/FontData.xml index 6c54af05cd..e426c8fb36 100644 --- a/doc/classes/FontData.xml +++ b/doc/classes/FontData.xml @@ -391,7 +391,7 @@ If [code]true[/code], distance field hint is enabled. </member> <member name="extra_spacing_glyph" type="int" setter="set_spacing" getter="get_spacing" default="0"> - Extra spacing for each glyphs in pixels. + Extra spacing for each glyph in pixels. This can be a negative number to make the distance between glyphs smaller. </member> <member name="extra_spacing_space" type="int" setter="set_spacing" getter="get_spacing" default="0"> diff --git a/doc/classes/Geometry3D.xml b/doc/classes/Geometry3D.xml index d0b930defb..9f012008e3 100644 --- a/doc/classes/Geometry3D.xml +++ b/doc/classes/Geometry3D.xml @@ -129,7 +129,7 @@ <argument index="2" name="planes" type="Array"> </argument> <description> - Given a convex hull defined though the [Plane]s in the array [code]planes[/code], tests if the segment ([code]from[/code], [code]to[/code]) intersects with that hull. If an intersection is found, returns a [PackedVector3Array] containing the point the intersection and the hull's normal. If no intersecion is found, an the returned array is empty. + Given a convex hull defined though the [Plane]s in the array [code]planes[/code], tests if the segment ([code]from[/code], [code]to[/code]) intersects with that hull. If an intersection is found, returns a [PackedVector3Array] containing the point the intersection and the hull's normal. Otherwise, returns an empty array. </description> </method> <method name="segment_intersects_cylinder"> diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 10afa4c339..b4536c0589 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -88,7 +88,7 @@ </return> <description> Gets the [HBoxContainer] that contains the zooming and grid snap controls in the top left of the graph. - Warning: The intended usage of this function is to allow you to reposition or add your own custom controls to the container. This is an internal control and as such should not be freed. If you wish to hide this or any of it's children use their [member CanvasItem.visible] property instead. + Warning: The intended usage of this function is to allow you to reposition or add your own custom controls to the container. This is an internal control and as such should not be freed. If you wish to hide this or any of its children, use their [member CanvasItem.visible] property instead. </description> </method> <method name="is_node_connected"> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index 279c4c4c94..aae3126c0f 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -6,7 +6,7 @@ <description> A GraphNode is a container. Each GraphNode can have several input and output slots, sometimes referred to as ports, allowing connections between GraphNodes. To add a slot to GraphNode, add any [Control]-derived child node to it. After adding at least one child to GraphNode new sections will be automatically created in the Inspector called 'Slot'. When 'Slot' is expanded you will see list with index number for each slot. You can click on each of them to expand further. - In the Inspector you can enable (show) or disable (hide) slots. By default all slots are disabled so you may not see any slots on your GraphNode initially. You can assign a type to each slot. Only slots of the same type will be able to connect to each other. You can also assign colors to slots. A tuple of input and output slots is defined for each GUI element included in the GraphNode. Input connections are on the left and output connections are on the right side of GraphNode. Only enabled slots are counted as connections. + In the Inspector you can enable (show) or disable (hide) slots. By default, all slots are disabled so you may not see any slots on your GraphNode initially. You can assign a type to each slot. Only slots of the same type will be able to connect to each other. You can also assign colors to slots. A tuple of input and output slots is defined for each GUI element included in the GraphNode. Input connections are on the left and output connections are on the right side of GraphNode. Only enabled slots are counted as connections. </description> <tutorials> </tutorials> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 9ff682f79d..ddfcdf7724 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -4,7 +4,7 @@ Low-level hyper-text transfer protocol client. </brief_description> <description> - Hyper-text transfer protocol client (sometimes called "User Agent"). Used to make HTTP requests to download web content, upload files and other data or to communicate with various services, among other use cases. [b]See the [HTTPRequest] node for an higher-level alternative.[/b] + Hyper-text transfer protocol client (sometimes called "User Agent"). Used to make HTTP requests to download web content, upload files and other data or to communicate with various services, among other use cases. [b]See the [HTTPRequest] node for a higher-level alternative.[/b] [b]Note:[/b] This client only needs to connect to a host once (see [method connect_to_host]) to send multiple requests. Because of this, methods that take URLs usually take just the part after the host instead of the full URL, as the client is already connected to a host. See [method request] for a full example and to get started. A [HTTPClient] should be reused between multiple requests or to connect to different hosts instead of creating one client per request. Supports SSL and SSL server certificate verification. HTTP status codes in the 2xx range indicate success, 3xx redirection (i.e. "try again, but over here"), 4xx something was wrong with the request, and 5xx something went wrong on the server's side. For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616). diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index a65f66c72a..25667d8f79 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -229,7 +229,7 @@ <member name="accept_gzip" type="bool" setter="set_accept_gzip" getter="is_accepting_gzip" default="true"> If [code]true[/code], this header will be added to each request: [code]Accept-Encoding: gzip, deflate[/code] telling servers that it's okay to compress response bodies. Any Response body declaring a [code]Content-Encoding[/code] of either [code]gzip[/code] or [code]deflate[/code] will then be automatically decompressed, and the uncompressed bytes will be delivered via [code]request_completed[/code]. - If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regaurdless of [code]accept_gzip[/code]. + If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regardless of [code]accept_gzip[/code]. If [code]false[/code] no header will be added, and no decompression will be performed on response bodies. The raw bytes of the response body will be returned via [code]request_completed[/code]. </member> <member name="body_size_limit" type="int" setter="set_body_size_limit" getter="get_body_size_limit" default="-1"> diff --git a/doc/classes/IP.xml b/doc/classes/IP.xml index 152f381a83..849f036bbd 100644 --- a/doc/classes/IP.xml +++ b/doc/classes/IP.xml @@ -31,7 +31,7 @@ <return type="Array"> </return> <description> - Returns all of the user's current IPv4 and IPv6 addresses as an array. + Returns all the user's current IPv4 and IPv6 addresses as an array. </description> </method> <method name="get_local_interfaces" qualifiers="const"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index bad8127c03..9d87c9bf9a 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -240,7 +240,7 @@ <argument index="0" name="renormalize" type="bool" default="false"> </argument> <description> - Generates mipmaps for the image. Mipmaps are pre-calculated and lower resolution copies of the image. Mipmaps are automatically used if the image needs to be scaled down when rendered. This improves image quality and the performance of the rendering. Returns an error if the image is compressed, in a custom format or if the image's width/height is 0. + Generates mipmaps for the image. Mipmaps are precalculated and lower resolution copies of the image. Mipmaps are automatically used if the image needs to be scaled down when rendered. This improves image quality and the performance of the rendering. Returns an error if the image is compressed, in a custom format or if the image's width/height is 0. </description> </method> <method name="get_data" qualifiers="const"> @@ -560,7 +560,7 @@ </methods> <members> <member name="data" type="Dictionary" setter="_set_data" getter="_get_data" default="{"data": PackedByteArray( ),"format": "Lum8","height": 0,"mipmaps": false,"width": 0}"> - Holds all of the image's color data in a given format. See [enum Format] constants. + Holds all the image's color data in a given format. See [enum Format] constants. </member> </members> <constants> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 977f2bb565..659d791ccf 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -85,7 +85,7 @@ </argument> <description> Get axis input by specifying two actions, one negative and one positive. - This is a horthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code]. + This is a shorthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code]. </description> </method> <method name="get_connected_joypads"> @@ -176,7 +176,7 @@ <return type="Vector3"> </return> <description> - Returns the the magnetic field strength in micro-Tesla for all axes of the device's magnetometer sensor, if the device has one. Otherwise, the method returns [constant Vector3.ZERO]. + Returns the magnetic field strength in micro-Tesla for all axes of the device's magnetometer sensor, if the device has one. Otherwise, the method returns [constant Vector3.ZERO]. [b]Note:[/b] This method only works on Android, iOS and UWP. On other platforms, it always returns [constant Vector3.ZERO]. </description> </method> diff --git a/doc/classes/InputEventAction.xml b/doc/classes/InputEventAction.xml index 1fe85a5ae8..ed290fc7e2 100644 --- a/doc/classes/InputEventAction.xml +++ b/doc/classes/InputEventAction.xml @@ -21,7 +21,7 @@ If [code]true[/code], the action's state is pressed. If [code]false[/code], the action's state is released. </member> <member name="strength" type="float" setter="set_strength" getter="get_strength" default="1.0"> - The action's strength between 0 and 1. This value is considered as equal to 0 if pressed is [code]false[/code]. The event strength allows faking analog joypad motion events, by precising how strongly is the joypad axis bent or pressed. + The action's strength between 0 and 1. This value is considered as equal to 0 if pressed is [code]false[/code]. The event strength allows faking analog joypad motion events, by specifying how strongly the joypad axis is bent or pressed. </member> </members> <constants> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index 8574ff9836..76b9686393 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -32,7 +32,7 @@ </argument> <description> Returns the height of the line [code]line[/code]. - If [code]line[/code] is set to [code]-1[/code], returns biggest line height. + If [code]line[/code] is set to [code]-1[/code], returns the biggest line height. If there're no lines returns font size in pixels. </description> </method> diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 111473e098..6bae612c9f 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -58,7 +58,7 @@ If [code]true[/code], the light's effect is reversed, darkening areas and casting bright shadows. </member> <member name="light_projector" type="Texture2D" setter="set_projector" getter="get_projector"> - [Texture2D] projected by light. [member shadow_enabled] must be on for the projector to work. Light projectors make the light appear as if it is shining through a colored but transparent object, almost like light shining through stained glass. + [Texture2D] projected by light. [member shadow_enabled] must be on for the projector to work. Light projectors make the light appear as if it is shining through a colored but transparent object, almost like light shining through stained-glass. </member> <member name="light_size" type="float" setter="set_param" getter="get_param" default="0.0"> The size of the light in Godot units. Only available for [OmniLight3D]s and [SpotLight3D]s. Increasing this value will make the light fade out slower and shadows appear blurrier. This can be used to simulate area lights to an extent. diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml index 481b737eee..7cbf9d3dfe 100644 --- a/doc/classes/MenuButton.xml +++ b/doc/classes/MenuButton.xml @@ -5,7 +5,7 @@ </brief_description> <description> Special button that brings up a [PopupMenu] when clicked. - New items can be created inside this [PopupMenu] using [code]get_popup().add_item("My Item Name")[/code]. You can also create them directly from the editor. To do so, select the [MenuButton] node, then in the toolbar at the top of the 2D editor, click [b]Items[/b] then click [b]Add[/b] in the popup. You will be able to give each items new properties. + New items can be created inside this [PopupMenu] using [code]get_popup().add_item("My Item Name")[/code]. You can also create them directly from the editor. To do so, select the [MenuButton] node, then in the toolbar at the top of the 2D editor, click [b]Items[/b] then click [b]Add[/b] in the popup. You will be able to give each item new properties. See also [BaseButton] which contains common properties and methods associated with this node. </description> <tutorials> diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 6ebfc946dc..2adebdb306 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -6,7 +6,7 @@ <description> MultiMesh provides low-level mesh instancing. Drawing thousands of [MeshInstance3D] nodes can be slow, since each object is submitted to the GPU then drawn individually. MultiMesh is much faster as it can draw thousands of instances with a single draw call, resulting in less API overhead. - As a drawback, if the instances are too far away of each other, performance may be reduced as every single instance will always rendered (they are spatially indexed as one, for the whole object). + As a drawback, if the instances are too far away of each other, performance may be reduced as every single instance will always render (they are spatially indexed as one, for the whole object). Since instances may have any behavior, the AABB used for visibility must be provided by the user. </description> <tutorials> diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index 59bf06eaf2..1060e2de41 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -111,7 +111,7 @@ The distance threshold before a target is considered to be reached. This will allow an agent to not have to hit a point on the path exactly, but in the area. </member> <member name="time_horizon" type="float" setter="set_time_horizon" getter="get_time_horizon" default="20.0"> - The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithim, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. </member> </members> <signals> diff --git a/doc/classes/NavigationAgent3D.xml b/doc/classes/NavigationAgent3D.xml index 7a130e9591..00e9db0a33 100644 --- a/doc/classes/NavigationAgent3D.xml +++ b/doc/classes/NavigationAgent3D.xml @@ -117,7 +117,7 @@ The distance threshold before a target is considered to be reached. This will allow an agent to not have to hit a point on the path exactly, but in the area. </member> <member name="time_horizon" type="float" setter="set_time_horizon" getter="get_time_horizon" default="5.0"> - The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithim, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. </member> </members> <signals> diff --git a/doc/classes/NavigationMesh.xml b/doc/classes/NavigationMesh.xml index dd7464ac0e..1f6e582731 100644 --- a/doc/classes/NavigationMesh.xml +++ b/doc/classes/NavigationMesh.xml @@ -111,7 +111,7 @@ The physics layers used to generate the [NavigationMesh]. </member> <member name="geometry/parsed_geometry_type" type="int" setter="set_parsed_geometry_type" getter="get_parsed_geometry_type" default="0"> - What kind of geomerty is used to generate the [NavigationMesh]. + What kind of geometry is used to generate the [NavigationMesh]. </member> <member name="geometry/source_geometry_mode" type="int" setter="set_source_geometry_mode" getter="get_source_geometry_mode" default="0"> Which geometry is used to generate the [NavigationMesh]. diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index b811607bad..b0a57ed227 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> NavigationServer2D is the server responsible for all 2D navigation. It handles several objects, namely maps, regions and agents. - Maps are made up of regions, which are made of navigation polygons. Together, they define the navigable areas in the 2D world. For two regions to be connected to each other, they must share a similar edge. An edges is considered connected to another if both of its two vertices are at a distance less than [code]edge_connection_margin[/code] to the respective other edge's vertex. + Maps are made up of regions, which are made of navigation polygons. Together, they define the navigable areas in the 2D world. For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than [code]edge_connection_margin[/code] to the respective other edge's vertex. You may assign navigation layers to regions with [method NavigationServer2D.region_set_layers], which then can be checked upon when requesting a path with [method NavigationServer2D.map_get_path]. This allows allowing or forbidding some areas to 2D objects. To use the collision avoidance system, you may use agents. You can set an agent's target velocity, then the servers will emit a callback with a modified velocity. [b]Note:[/b] the collision avoidance system ignores regions. Using the modified velocity as-is might lead to pushing and agent outside of a navigable area. This is a limitation of the collision avoidance system, any more complex situation may require the use of the physics engine. diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 6d56428933..b098a7fc20 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -5,7 +5,7 @@ </brief_description> <description> NavigationServer3D is the server responsible for all 3D navigation. It handles several objects, namely maps, regions and agents. - Maps are made up of regions, which are made of navigation meshes. Together, they define the navigable areas in the 3D world. For two regions to be connected to each other, they must share a similar edge. An edges is considered connected to another if both of its two vertices are at a distance less than [code]edge_connection_margin[/code] to the respective other edge's vertex. + Maps are made up of regions, which are made of navigation meshes. Together, they define the navigable areas in the 3D world. For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than [code]edge_connection_margin[/code] to the respective other edge's vertex. You may assign navigation layers to regions with [method NavigationServer3D.region_set_layers], which then can be checked upon when requesting a path with [method NavigationServer3D.map_get_path]. This allows allowing or forbidding some areas to 3D objects. To use the collision avoidance system, you may use agents. You can set an agent's target velocity, then the servers will emit a callback with a modified velocity. [b]Note:[/b] the collision avoidance system ignores regions. Using the modified velocity as-is might lead to pushing and agent outside of a navigable area. This is a limitation of the collision avoidance system, any more complex situation may require the use of the physics engine. diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7ee6860dfc..7750d45226 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -128,7 +128,7 @@ </argument> <description> Adds a child node. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node. - If [code]legible_unique_name[/code] is [code]true[/code], the child node will have an human-readable name based on the name of the node being instanced instead of its type. + If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instanced instead of its type. [b]Note:[/b] If the child node already has a parent, the function will fail. Use [method remove_child] first to remove the node from its current parent. For example: [codeblocks] [gdscript] @@ -158,8 +158,8 @@ <argument index="1" name="legible_unique_name" type="bool" default="false"> </argument> <description> - Adds a [code]sibling[/code] node to current's node parent, at the the same level as that node, right below it. - If [code]legible_unique_name[/code] is [code]true[/code], the child node will have an human-readable name based on the name of the node being instanced instead of its type. + Adds a [code]sibling[/code] node to current's node parent, at the same level as that node, right below it. + If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instanced instead of its type. Use [method add_child] instead of this method if you don't need the child node to be added below a specific node in the list of children. </description> </method> diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml index e3f5fa1ce5..817ccd5160 100644 --- a/doc/classes/NodePath.xml +++ b/doc/classes/NodePath.xml @@ -70,7 +70,7 @@ <return type="NodePath"> </return> <description> - Returns a node path with a colon character ([code]:[/code]) prepended, transforming it to a pure property path with no node name (defaults to resolving from the from the current node). + Returns a node path with a colon character ([code]:[/code]) prepended, transforming it to a pure property path with no node name (defaults to resolving from the current node). [codeblocks] [gdscript] # This will be parsed as a node path to the "x" property in the "position" node. diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 548147beab..a76ea5868c 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -244,9 +244,9 @@ </return> <description> Returns the host OS locale as a string of the form [code]language_Script_COUNTRY_VARIANT@extra[/code]. - [code]language[/code] - 2 or 3 letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. - [code]Script[/code] - optional, 4 letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. - [code]COUNTRY[/code] - optional, 2 or 3 letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. + [code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. + [code]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. + [code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored key words. [code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information. </description> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index ad3ce8e93e..7da9c1ac38 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Object" version="4.0"> <brief_description> - Base class for all non built-in types. + Base class for all non-built-in types. </brief_description> <description> Every class which is not a built-in type inherits from this class. @@ -179,7 +179,7 @@ </argument> <description> Connects a [code]signal[/code] to a [code]callable[/code]. Pass optional [code]binds[/code] to the call as an [Array] of parameters. These parameters will be passed to the [Callable]'s method after any parameter used in the call to [method emit_signal]. Use [code]flags[/code] to set deferred or one-shot connections. See [enum ConnectFlags] constants. - [b]Note:[/b] This method is the legacy implementation for connecting signals. The recommend modern approach is to use [method Signal.connect] and to use [method Callable.bind] to add and validate parameter binds. Both syntaxes are shown below. + [b]Note:[/b] This method is the legacy implementation for connecting signals. The recommended modern approach is to use [method Signal.connect] and to use [method Callable.bind] to add and validate parameter binds. Both syntaxes are shown below. A signal can only be connected once to a [Callable]. It will throw an error if already connected, unless the signal was connected with [constant CONNECT_REFERENCE_COUNTED]. To avoid this, first, use [method is_connected] to check for existing connections. If the callable's target is destroyed in the game's lifecycle, the connection will be lost. [b]Examples with recommended syntax:[/b] diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index 34a55d4fe7..3c7f0f4fad 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -81,7 +81,7 @@ </argument> <description> Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum File.CompressionMode]'s constants. [b]This method only accepts gzip and deflate compression modes.[/b] - This method is potentially slower than [code]decompress[/code], as it may have to re-allocate it's output buffer multiple times while decompressing, where as [code]decompress[/code] knows it's output buffer size from the beginning. + This method is potentially slower than [code]decompress[/code], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [code]decompress[/code] knows it's output buffer size from the beginning. GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [code]max_output_size[/code]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned. </description> </method> diff --git a/doc/classes/PhysicalBone3D.xml b/doc/classes/PhysicalBone3D.xml index dcf24c1d32..38d9f722b1 100644 --- a/doc/classes/PhysicalBone3D.xml +++ b/doc/classes/PhysicalBone3D.xml @@ -91,7 +91,7 @@ The body's bounciness. Values range from [code]0[/code] (no bounce) to [code]1[/code] (full bounciness). </member> <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" default="true"> - If [code]true[/code], the body is deactivated when there is no movement, so it will not take part in the simulation until it is awaken by an external force. + If [code]true[/code], the body is deactivated when there is no movement, so it will not take part in the simulation until it is awakened by an external force. </member> <member name="friction" type="float" setter="set_friction" getter="get_friction" default="1.0"> The body's friction, from [code]0[/code] (frictionless) to [code]1[/code] (max friction). diff --git a/doc/classes/PhysicalSkyMaterial.xml b/doc/classes/PhysicalSkyMaterial.xml index 2e0f9c52f2..381371b973 100644 --- a/doc/classes/PhysicalSkyMaterial.xml +++ b/doc/classes/PhysicalSkyMaterial.xml @@ -23,22 +23,22 @@ Modulates the [Color] on the bottom half of the sky to represent the ground. </member> <member name="mie_coefficient" type="float" setter="set_mie_coefficient" getter="get_mie_coefficient" default="0.005"> - Controls the strength of mie scattering for the sky. Mie scattering results from light colliding with larger particles (like water). On earth, mie scattering results in a whiteish color around the sun and horizon. + Controls the strength of mie scattering for the sky. Mie scattering results from light colliding with larger particles (like water). On earth, mie scattering results in a whitish color around the sun and horizon. </member> <member name="mie_color" type="Color" setter="set_mie_color" getter="get_mie_color" default="Color( 0.36, 0.56, 0.82, 1 )"> Controls the [Color] of the mie scattering effect. While not physically accurate, this allows for the creation of alien looking planets. </member> <member name="mie_eccentricity" type="float" setter="set_mie_eccentricity" getter="get_mie_eccentricity" default="0.8"> - Controls the direction of the mie scattering. A value of [code]1[/code] means that when light hits a particle it passing through straight forward. A value of [code]-1[/code] means that all light is scatter backwards. + Controls the direction of the mie scattering. A value of [code]1[/code] means that when light hits a particle it's passing through straight forward. A value of [code]-1[/code] means that all light is scatter backwards. </member> <member name="night_sky" type="Texture2D" setter="set_night_sky" getter="get_night_sky"> [Texture2D] for the night sky. This is added to the sky, so if it is bright enough, it may be visible during the day. </member> <member name="rayleigh_coefficient" type="float" setter="set_rayleigh_coefficient" getter="get_rayleigh_coefficient" default="2.0"> - Controls the strength of the rayleigh scattering. Rayleigh scattering results from light colliding with small particles. It is responsible for the blue color of the sky. + Controls the strength of the Rayleigh scattering. Rayleigh scattering results from light colliding with small particles. It is responsible for the blue color of the sky. </member> <member name="rayleigh_color" type="Color" setter="set_rayleigh_color" getter="get_rayleigh_color" default="Color( 0.056, 0.14, 0.3, 1 )"> - Controls the [Color] of the rayleigh scattering. While not physically accurate, this allows for the creation of alien looking planets. For example, setting this to a red [Color] results in a mars looking atmosphere with a corresponding blue sunset. + Controls the [Color] of the Rayleigh scattering. While not physically accurate, this allows for the creation of alien looking planets. For example, setting this to a red [Color] results in a Mars looking atmosphere with a corresponding blue sunset. </member> <member name="sun_disk_scale" type="float" setter="set_sun_disk_scale" getter="get_sun_disk_scale" default="1.0"> Sets the size of the sun disk. Default value is based on Sol's perceived size from Earth. diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 51ec509a14..418785222e 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -565,7 +565,7 @@ <argument index="1" name="state" type="int"> </argument> <description> - Sets the state of an multistate item. See [method add_multistate_item] for details. + Sets the state of a multistate item. See [method add_multistate_item] for details. </description> </method> <method name="set_item_opentype_feature"> @@ -664,13 +664,13 @@ <argument index="0" name="idx" type="int"> </argument> <description> - Cycle to the next state of an multistate item. See [method add_multistate_item] for details. + Cycle to the next state of a multistate item. See [method add_multistate_item] for details. </description> </method> </methods> <members> <member name="allow_search" type="bool" setter="set_allow_search" getter="get_allow_search" default="true"> - If [code]true[/code], allows to navigate [PopupMenu] with letter keys. + If [code]true[/code], allows navigating [PopupMenu] with letter keys. </member> <member name="hide_on_checkable_item_selection" type="bool" setter="set_hide_on_checkable_item_selection" getter="is_hide_on_checkable_item_selection" default="true"> If [code]true[/code], hides the [PopupMenu] when a checkbox or radio button is selected. diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml index 25943f6d47..8b73bcb9c1 100644 --- a/doc/classes/PrimitiveMesh.xml +++ b/doc/classes/PrimitiveMesh.xml @@ -31,7 +31,7 @@ </methods> <members> <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb" default="AABB( 0, 0, 0, 0, 0, 0 )"> - Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unnexpected culling when using a shader to offset vertices. + Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unexpected culling when using a shader to offset vertices. </member> <member name="flip_faces" type="bool" setter="set_flip_faces" getter="get_flip_faces" default="false"> If set, the order of the vertices in each triangle are reversed resulting in the backside of the mesh being drawn. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 9dad01e6dc..d7ccb03d04 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -443,7 +443,7 @@ <member name="debug/settings/fps/force_fps" type="int" setter="" getter="" default="0"> Maximum number of frames per second allowed. The actual number of frames per second may still be below this value if the game is lagging. If [member display/window/vsync/use_vsync] is enabled, it takes precedence and the forced FPS number cannot exceed the monitor's refresh rate. - This setting is therefore mostly relevant for lowering the maximum FPS below VSync, e.g. to perform non real-time rendering of static frames, or test the project under lag conditions. + This setting is therefore mostly relevant for lowering the maximum FPS below VSync, e.g. to perform non-real-time rendering of static frames, or test the project under lag conditions. </member> <member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024"> Maximum call stack allowed for debugging GDScript. diff --git a/doc/classes/Quat.xml b/doc/classes/Quat.xml index e801de1254..1c0a3e37c0 100644 --- a/doc/classes/Quat.xml +++ b/doc/classes/Quat.xml @@ -127,7 +127,7 @@ <argument index="0" name="to" type="Quat"> </argument> <description> - Returns [code]true[/code] if this quaterion and [code]quat[/code] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component. + Returns [code]true[/code] if this quaternion and [code]quat[/code] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component. </description> </method> <method name="is_normalized" qualifiers="const"> diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml index ba6342ec24..352a18e326 100644 --- a/doc/classes/Rect2.xml +++ b/doc/classes/Rect2.xml @@ -221,7 +221,7 @@ Beginning corner. Typically has values lower than [member end]. </member> <member name="size" type="Vector2" setter="" getter="" default="Vector2( 0, 0 )"> - Size from [member position] to [member end]. Typically all components are positive. + Size from [member position] to [member end]. Typically, all components are positive. If the size is negative, you can use [method abs] to fix it. </member> </members> diff --git a/doc/classes/Rect2i.xml b/doc/classes/Rect2i.xml index dd71b5a630..84bef9b406 100644 --- a/doc/classes/Rect2i.xml +++ b/doc/classes/Rect2i.xml @@ -200,7 +200,7 @@ Beginning corner. Typically has values lower than [member end]. </member> <member name="size" type="Vector2i" setter="" getter="" default="Vector2i( 0, 0 )"> - Size from [member position] to [member end]. Typically all components are positive. + Size from [member position] to [member end]. Typically, all components are positive. If the size is negative, you can use [method abs] to fix it. </member> </members> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index d2b95fda20..f82301bcf4 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -10,7 +10,7 @@ Resources are created using the [code]*_create[/code] functions. All objects are drawn to a viewport. You can use the [Viewport] attached to the [SceneTree] or you can create one yourself with [method viewport_create]. When using a custom scenario or canvas, the scenario or canvas needs to be attached to the viewport using [method viewport_set_scenario] or [method viewport_attach_canvas]. In 3D, all visual objects must be associated with a scenario. The scenario is a visual representation of the world. If accessing the rendering server from a running game, the scenario can be accessed from the scene tree from any [Node3D] node with [method Node3D.get_world_3d]. Otherwise, a scenario can be created with [method scenario_create]. - Similarly in 2D, a canvas is needed to draw all canvas items. + Similarly, in 2D, a canvas is needed to draw all canvas items. In 3D, all visible objects are comprised of a resource and an instance. A resource can be a mesh, a particle system, a light, or any other 3D object. In order to be visible resources must be attached to an instance using [method instance_set_base]. The instance must also be attached to the scenario using [method instance_set_scenario] in order to be visible. In 2D, all visible objects are some form of canvas item. In order to be visible, a canvas item needs to be the child of a canvas attached to a viewport, or it needs to be the child of another canvas item that is eventually attached to the canvas. </description> @@ -1292,7 +1292,7 @@ <argument index="1" name="margin" type="float"> </argument> <description> - Sets a margin to increase the size of the AABB when culling objects from the view frustum. This allows you avoid culling objects that fall outside the view frustum. Equivalent to [member GeometryInstance3D.extra_cull_margin]. + Sets a margin to increase the size of the AABB when culling objects from the view frustum. This allows you to avoid culling objects that fall outside the view frustum. Equivalent to [member GeometryInstance3D.extra_cull_margin]. </description> </method> <method name="instance_set_layer_mask"> @@ -1520,7 +1520,7 @@ <argument index="1" name="enabled" type="bool"> </argument> <description> - If [code]true[/code], reverses the backface culling of the mesh. This can be useful when you have a flat mesh that has a light behind it. If you need to cast a shadow on both sides of the mesh, set the mesh to use double sided shadows with [method instance_geometry_set_cast_shadows_setting]. Equivalent to [member Light3D.shadow_reverse_cull_face]. + If [code]true[/code], reverses the backface culling of the mesh. This can be useful when you have a flat mesh that has a light behind it. If you need to cast a shadow on both sides of the mesh, set the mesh to use double-sided shadows with [method instance_geometry_set_cast_shadows_setting]. Equivalent to [member Light3D.shadow_reverse_cull_face]. </description> </method> <method name="light_set_shadow"> @@ -2204,7 +2204,7 @@ <argument index="1" name="time" type="float"> </argument> <description> - Sets the preprocess time for the particles animation. This lets you delay starting an animation until after the particles have begun emitting. Equivalent to [member GPUParticles3D.preprocess]. + Sets the preprocess time for the particles' animation. This lets you delay starting an animation until after the particles have begun emitting. Equivalent to [member GPUParticles3D.preprocess]. </description> </method> <method name="particles_set_process_material"> diff --git a/doc/classes/ResourceFormatLoader.xml b/doc/classes/ResourceFormatLoader.xml index 2683156ec5..9943f644cf 100644 --- a/doc/classes/ResourceFormatLoader.xml +++ b/doc/classes/ResourceFormatLoader.xml @@ -6,7 +6,7 @@ <description> Godot loads resources in the editor or in exported games using ResourceFormatLoaders. They are queried automatically via the [ResourceLoader] singleton, or when a resource with internal dependencies is loaded. Each file type may load as a different resource type, so multiple ResourceFormatLoaders are registered in the engine. Extending this class allows you to define your own loader. Be sure to respect the documented return types and values. You should give it a global class name with [code]class_name[/code] for it to be registered. Like built-in ResourceFormatLoaders, it will be called automatically when loading resources of its handled type(s). You may also implement a [ResourceFormatSaver]. - [b]Note:[/b] You can also extend [EditorImportPlugin] if the resource type you need exists but Godot is unable to load its format. Choosing one way over another depends if the format is suitable or not for the final exported game. For example, it's better to import [code].png[/code] textures as [code].stex[/code] ([StreamTexture2D]) first, so they can be loaded with better efficiency on the graphics card. + [b]Note:[/b] You can also extend [EditorImportPlugin] if the resource type you need exists but Godot is unable to load its format. Choosing one way over another depends on if the format is suitable or not for the final exported game. For example, it's better to import [code].png[/code] textures as [code].stex[/code] ([StreamTexture2D]) first, so they can be loaded with better efficiency on the graphics card. </description> <tutorials> </tutorials> diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index 6b27c77f26..ed375a8b1e 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -4,7 +4,7 @@ A body that is controlled by the 2D physics engine. </brief_description> <description> - This node implements simulated 2D physics. You do not control a RigidBody2D directly. Instead you apply forces to it (gravity, impulses, etc.) and the physics simulation calculates the resulting movement based on its mass, friction, and other physical properties. + This node implements simulated 2D physics. You do not control a RigidBody2D directly. Instead, you apply forces to it (gravity, impulses, etc.) and the physics simulation calculates the resulting movement based on its mass, friction, and other physical properties. A RigidBody2D has 4 behavior [member mode]s: Rigid, Static, Character, and Kinematic. [b]Note:[/b] You should not change a RigidBody2D's [code]position[/code] or [code]linear_velocity[/code] every frame or even very often. If you need to directly affect the body's state, use [method _integrate_forces], which allows you to directly access the physics state. Please also keep in mind that physics bodies manage their own transform which overwrites the ones you set. So any direct or indirect transformation (including scaling of the node or its parent) will be visible in the editor only, and immediately reset at runtime. diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index e1f6830eb2..9366d7dd44 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -5,7 +5,7 @@ </brief_description> <description> As one of the most important classes, the [SceneTree] manages the hierarchy of nodes in a scene as well as scenes themselves. Nodes can be added, retrieved and removed. The whole scene tree (and thus the current scene) can be paused. Scenes can be loaded, switched and reloaded. - You can also use the [SceneTree] to organize your nodes into groups: every node can be assigned as many groups as you want to create, e.g. a "enemy" group. You can then iterate these groups or even call methods and set properties on all the group's members at once. + You can also use the [SceneTree] to organize your nodes into groups: every node can be assigned as many groups as you want to create, e.g. an "enemy" group. You can then iterate these groups or even call methods and set properties on all the group's members at once. [SceneTree] is the default [MainLoop] implementation used by scenes, and is thus in charge of the game loop. </description> <tutorials> diff --git a/doc/classes/Shape3D.xml b/doc/classes/Shape3D.xml index f3e62175c6..f8b749aebf 100644 --- a/doc/classes/Shape3D.xml +++ b/doc/classes/Shape3D.xml @@ -14,7 +14,7 @@ <members> <member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.04"> The collision margin for the shape. Used in Bullet Physics only. - Collision margins allows collision detection to be more efficient by adding an extra shell around shapes. Collision algorithms are more expensive when objects overlap by more than their margin, so a higher value for margins is better for performance, at the cost of accuracy around edges as it makes them less sharp. + Collision margins allow collision detection to be more efficient by adding an extra shell around shapes. Collision algorithms are more expensive when objects overlap by more than their margin, so a higher value for margins is better for performance, at the cost of accuracy around edges as it makes them less sharp. </member> </members> <constants> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index fe2cc1f5ad..cb72ae7e4c 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -297,7 +297,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> - Unparents the bone at [code]bone_idx[/code] and sets its rest position to that of it's parent prior to being reset. + Unparents the bone at [code]bone_idx[/code] and sets its rest position to that of its parent prior to being reset. </description> </method> <method name="world_transform_to_bone_transform"> diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml index 04e201e1bd..29f8ecd432 100644 --- a/doc/classes/SoftBody3D.xml +++ b/doc/classes/SoftBody3D.xml @@ -77,8 +77,6 @@ </method> </methods> <members> - <member name="angular_stiffness" type="float" setter="set_angular_stiffness" getter="get_angular_stiffness" default="0.0"> - </member> <member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1"> The physics layers this SoftBody3D is in. Collidable objects can exist in any of 32 different layers. These layers work like a tagging system, and are not visual. A collidable can use these layers to select with which objects it can collide, using the collision_mask property. @@ -96,8 +94,6 @@ <member name="parent_collision_ignore" type="NodePath" setter="set_parent_collision_ignore" getter="get_parent_collision_ignore" default="NodePath("")"> [NodePath] to a [CollisionObject3D] this SoftBody3D should avoid clipping. </member> - <member name="pose_matching_coefficient" type="float" setter="set_pose_matching_coefficient" getter="get_pose_matching_coefficient" default="0.0"> - </member> <member name="pressure_coefficient" type="float" setter="set_pressure_coefficient" getter="get_pressure_coefficient" default="0.0"> </member> <member name="ray_pickable" type="bool" setter="set_ray_pickable" getter="is_ray_pickable" default="true"> @@ -109,8 +105,6 @@ <member name="total_mass" type="float" setter="set_total_mass" getter="get_total_mass" default="0.0"> The SoftBody3D's mass. </member> - <member name="volume_stiffness" type="float" setter="set_volume_stiffness" getter="get_volume_stiffness" default="0.0"> - </member> </members> <constants> </constants> diff --git a/doc/classes/Sprite2D.xml b/doc/classes/Sprite2D.xml index e4df753674..7a949d26e0 100644 --- a/doc/classes/Sprite2D.xml +++ b/doc/classes/Sprite2D.xml @@ -73,10 +73,10 @@ <member name="offset" type="Vector2" setter="set_offset" getter="get_offset" default="Vector2( 0, 0 )"> The texture's drawing offset. </member> - <member name="region_enabled" type="bool" setter="set_region" getter="is_region" default="false"> + <member name="region_enabled" type="bool" setter="set_region_enabled" getter="is_region_enabled" default="false"> If [code]true[/code], texture is cut from a larger atlas texture. See [member region_rect]. </member> - <member name="region_filter_clip" type="bool" setter="set_region_filter_clip" getter="is_region_filter_clip_enabled" default="false"> + <member name="region_filter_clip_enabled" type="bool" setter="set_region_filter_clip_enabled" getter="is_region_filter_clip_enabled" default="false"> If [code]true[/code], the outermost pixels get blurred out. [member region_enabled] must be [code]true[/code]. </member> <member name="region_rect" type="Rect2" setter="set_region_rect" getter="get_region_rect" default="Rect2( 0, 0, 0, 0 )"> diff --git a/doc/classes/Sprite3D.xml b/doc/classes/Sprite3D.xml index f9b947fa3d..658fd1a4f2 100644 --- a/doc/classes/Sprite3D.xml +++ b/doc/classes/Sprite3D.xml @@ -20,7 +20,7 @@ <member name="hframes" type="int" setter="set_hframes" getter="get_hframes" default="1"> The number of columns in the sprite sheet. </member> - <member name="region_enabled" type="bool" setter="set_region" getter="is_region" default="false"> + <member name="region_enabled" type="bool" setter="set_region_enabled" getter="is_region_enabled" default="false"> If [code]true[/code], texture will be cut from a larger atlas texture. See [member region_rect]. </member> <member name="region_rect" type="Rect2" setter="set_region_rect" getter="get_region_rect" default="Rect2( 0, 0, 0, 0 )"> diff --git a/doc/classes/SpriteBase3D.xml b/doc/classes/SpriteBase3D.xml index 078520a095..06b9c2b042 100644 --- a/doc/classes/SpriteBase3D.xml +++ b/doc/classes/SpriteBase3D.xml @@ -73,7 +73,7 @@ The texture's drawing offset. </member> <member name="opacity" type="float" setter="set_opacity" getter="get_opacity" default="1.0"> - The objects visibility on a scale from [code]0[/code] fully invisible to [code]1[/code] fully visible. + The objects' visibility on a scale from [code]0[/code] fully invisible to [code]1[/code] fully visible. </member> <member name="pixel_size" type="float" setter="set_pixel_size" getter="get_pixel_size" default="0.01"> The size of one pixel's width on the sprite to scale it in 3D. diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index af0074f080..be32f6a234 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -4,7 +4,7 @@ An optimized string type for unique names. </brief_description> <description> - [StringName]s are immutable strings designed for general-purpose represention of unique names. [StringName] ensures that only one instance of a given name exists (so two [StringName]s with the same value are the same object). Comparing them is much faster than with regular [String]s, because only the pointers are compared, not the whole strings. + [StringName]s are immutable strings designed for general-purpose representation of unique names. [StringName] ensures that only one instance of a given name exists (so two [StringName]s with the same value are the same object). Comparing them is much faster than with regular [String]s, because only the pointers are compared, not the whole strings. </description> <tutorials> </tutorials> diff --git a/doc/classes/Tabs.xml b/doc/classes/Tabs.xml index df9680bf28..24c114c18b 100644 --- a/doc/classes/Tabs.xml +++ b/doc/classes/Tabs.xml @@ -261,7 +261,7 @@ If [code]true[/code], tabs can be rearranged with mouse drag. </member> <member name="scrolling_enabled" type="bool" setter="set_scrolling_enabled" getter="get_scrolling_enabled" default="true"> - if [code]true[/code], the mouse's scroll wheel cab be used to navigate the scroll view. + if [code]true[/code], the mouse's scroll wheel can be used to navigate the scroll view. </member> <member name="tab_align" type="int" setter="set_tab_align" getter="get_tab_align" enum="Tabs.TabAlign" default="1"> The alignment of all tabs. See [enum TabAlign] for details. diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index 99eb8b81d4..69bf823ccd 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -145,7 +145,7 @@ <argument index="4" name="dc_color" type="Color" default="Color( 1, 1, 1, 1 )"> </argument> <description> - Draw outilines of all lines of the text and drop cap into a canvas item at a given position, with [code]color[/code]. [code]pos[/code] specifies the top left corner of the bounding box. + Draw outlines of all lines of the text and drop cap into a canvas item at a given position, with [code]color[/code]. [code]pos[/code] specifies the top left corner of the bounding box. </description> </method> <method name="get_dropcap_lines" qualifiers="const"> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 5635ec2be0..4a41b68fee 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -359,7 +359,7 @@ <argument index="0" name="font" type="RID"> </argument> <description> - Returns extra spacing for each glyphs in pixels. + Returns extra spacing for each glyph in pixels. </description> </method> <method name="font_get_spacing_space" qualifiers="const"> @@ -368,7 +368,7 @@ <argument index="0" name="font" type="RID"> </argument> <description> - Sets extra spacing for each glyphs in pixels. + Sets extra spacing for each glyph in pixels. </description> </method> <method name="font_get_supported_chars" qualifiers="const"> diff --git a/doc/classes/Timer.xml b/doc/classes/Timer.xml index 5265e75429..807d8033c1 100644 --- a/doc/classes/Timer.xml +++ b/doc/classes/Timer.xml @@ -5,7 +5,7 @@ </brief_description> <description> Counts down a specified interval and emits a signal on reaching 0. Can be set to repeat or "one-shot" mode. - [b]Note:[/b] To create an one-shot timer without instantiating a node, use [method SceneTree.create_timer]. + [b]Note:[/b] To create a one-shot timer without instantiating a node, use [method SceneTree.create_timer]. </description> <tutorials> <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index b32bb1e2d9..00cca40093 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -23,7 +23,7 @@ tween.Start(); [/csharp] [/codeblocks] - Many methods require a property name, such as [code]"position"[/code] above. You can find the correct property name by hovering over the property in the Inspector. You can also provide the components of a property directly by using [code]"property:component"[/code] (eg. [code]position:x[/code]), where it would only apply to that particular component. + Many methods require a property name, such as [code]"position"[/code] above. You can find the correct property name by hovering over the property in the Inspector. You can also provide the components of a property directly by using [code]"property:component"[/code] (e.g. [code]position:x[/code]), where it would only apply to that particular component. Many of the methods accept [code]trans_type[/code] and [code]ease_type[/code]. The first accepts an [enum TransitionType] constant, and refers to the way the timing of the animation is handled (see [url=https://easings.net/]easings.net[/url] for some examples). The second accepts an [enum EaseType] constant, and controls where the [code]trans_type[/code] is applied to the interpolation (in the beginning, the end, or both). If you don't know which transition and easing to pick, you can try different [enum TransitionType] constants with [constant EASE_IN_OUT], and use the one that looks best. [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.png]Tween easing and transition types cheatsheet[/url] </description> diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml index a0330de4fa..aba6183124 100644 --- a/doc/classes/UndoRedo.xml +++ b/doc/classes/UndoRedo.xml @@ -186,7 +186,7 @@ <return type="int"> </return> <description> - Return how many element are in the history. + Return how many elements are in the history. </description> </method> <method name="get_version" qualifiers="const"> diff --git a/doc/classes/VisualShaderNodeCustom.xml b/doc/classes/VisualShaderNodeCustom.xml index 59b501660a..17fc2f8c4d 100644 --- a/doc/classes/VisualShaderNodeCustom.xml +++ b/doc/classes/VisualShaderNodeCustom.xml @@ -20,7 +20,7 @@ <return type="String"> </return> <description> - Override this method to define the path to the associated custom node in the Visual Shader Editor's members dialog. The path may looks like [code]"MyGame/MyFunctions/Noise"[/code]. + Override this method to define the path to the associated custom node in the Visual Shader Editor's members dialog. The path may look like [code]"MyGame/MyFunctions/Noise"[/code]. Defining this method is [b]optional[/b]. If not overridden, the node will be filed under the "Addons" category. </description> </method> diff --git a/doc/classes/VisualShaderNodeDeterminant.xml b/doc/classes/VisualShaderNodeDeterminant.xml index 72be31872d..6b042f6172 100644 --- a/doc/classes/VisualShaderNodeDeterminant.xml +++ b/doc/classes/VisualShaderNodeDeterminant.xml @@ -4,7 +4,7 @@ Calculates the determinant of a [Transform] within the visual shader graph. </brief_description> <description> - Translates to [code]deteminant(x)[/code] in the shader language. + Translates to [code]determinant(x)[/code] in the shader language. </description> <tutorials> </tutorials> diff --git a/doc/classes/VisualShaderNodeExpression.xml b/doc/classes/VisualShaderNodeExpression.xml index f571edaab3..c2cbf41f45 100644 --- a/doc/classes/VisualShaderNodeExpression.xml +++ b/doc/classes/VisualShaderNodeExpression.xml @@ -5,7 +5,7 @@ </brief_description> <description> Custom Godot Shading Language expression, with a custom amount of input and output ports. - The provided code is directly injected into the graph's matching shader function ([code]vertex[/code], [code]fragment[/code], or [code]light[/code]), so it cannot be used to to declare functions, varyings, uniforms, or global constants. See [VisualShaderNodeGlobalExpression] for such global definitions. + The provided code is directly injected into the graph's matching shader function ([code]vertex[/code], [code]fragment[/code], or [code]light[/code]), so it cannot be used to declare functions, varyings, uniforms, or global constants. See [VisualShaderNodeGlobalExpression] for such global definitions. </description> <tutorials> </tutorials> diff --git a/doc/classes/VisualShaderNodeFaceForward.xml b/doc/classes/VisualShaderNodeFaceForward.xml index 5ef08ea8c2..48f84e5495 100644 --- a/doc/classes/VisualShaderNodeFaceForward.xml +++ b/doc/classes/VisualShaderNodeFaceForward.xml @@ -4,7 +4,7 @@ Returns the vector that points in the same direction as a reference vector within the visual shader graph. </brief_description> <description> - Translates to [code]faceforward(N, I, Nref)[/code] in the shader language. The function has three vector parameters: [code]N[/code], the vector to orient, [code]I[/code], the incident vector, and [code]Nref[/code], the reference vector. If the dot product of [code]I[/code] and [code]Nref[/code] is smaller than zero the return value is [code]N[/code]. Otherwise [code]-N[/code] is returned. + Translates to [code]faceforward(N, I, Nref)[/code] in the shader language. The function has three vector parameters: [code]N[/code], the vector to orient, [code]I[/code], the incident vector, and [code]Nref[/code], the reference vector. If the dot product of [code]I[/code] and [code]Nref[/code] is smaller than zero the return value is [code]N[/code]. Otherwise, [code]-N[/code] is returned. </description> <tutorials> </tutorials> diff --git a/doc/classes/VisualShaderNodeGroupBase.xml b/doc/classes/VisualShaderNodeGroupBase.xml index afa14c776e..be3f7f173d 100644 --- a/doc/classes/VisualShaderNodeGroupBase.xml +++ b/doc/classes/VisualShaderNodeGroupBase.xml @@ -74,7 +74,7 @@ <return type="String"> </return> <description> - Returns a [String] description of the input ports as as colon-separated list using the format [code]id,type,name;[/code] (see [method add_input_port]). + Returns a [String] description of the input ports as a colon-separated list using the format [code]id,type,name;[/code] (see [method add_input_port]). </description> </method> <method name="get_output_port_count" qualifiers="const"> @@ -88,7 +88,7 @@ <return type="String"> </return> <description> - Returns a [String] description of the output ports as as colon-separated list using the format [code]id,type,name;[/code] (see [method add_output_port]). + Returns a [String] description of the output ports as a colon-separated list using the format [code]id,type,name;[/code] (see [method add_output_port]). </description> </method> <method name="has_input_port" qualifiers="const"> diff --git a/doc/classes/VisualShaderNodeIf.xml b/doc/classes/VisualShaderNodeIf.xml index ad0b21a370..418999863b 100644 --- a/doc/classes/VisualShaderNodeIf.xml +++ b/doc/classes/VisualShaderNodeIf.xml @@ -4,7 +4,7 @@ Compares two floating-point numbers in order to return a required vector within the visual shader graph. </brief_description> <description> - First two ports are scalar floatin-point numbers to compare, third is tolerance comparison amount and last three ports represents a vectors returned if [code]a == b[/code], [code]a > b[/code] and [code]a < b[/code] respectively. + First two ports are scalar floating-point numbers to compare, third is tolerance comparison amount and last three ports represents a vectors returned if [code]a == b[/code], [code]a > b[/code] and [code]a < b[/code] respectively. </description> <tutorials> </tutorials> diff --git a/doc/classes/VisualShaderNodeInput.xml b/doc/classes/VisualShaderNodeInput.xml index 8e819b011c..067f78dffe 100644 --- a/doc/classes/VisualShaderNodeInput.xml +++ b/doc/classes/VisualShaderNodeInput.xml @@ -14,7 +14,7 @@ <return type="String"> </return> <description> - Returns a translated name of the current constant in the Godot Shader Language. eg. [code]"ALBEDO"[/code] if the [member input_name] equal to [code]"albedo"[/code]. + Returns a translated name of the current constant in the Godot Shader Language. E.g. [code]"ALBEDO"[/code] if the [member input_name] equal to [code]"albedo"[/code]. </description> </method> </methods> diff --git a/doc/classes/VisualShaderNodeSDFToScreenUV.xml b/doc/classes/VisualShaderNodeSDFToScreenUV.xml index ea04180095..40fb66e364 100644 --- a/doc/classes/VisualShaderNodeSDFToScreenUV.xml +++ b/doc/classes/VisualShaderNodeSDFToScreenUV.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeSDFToScreenUV" inherits="VisualShaderNode" version="4.0"> <brief_description> - A function to convert a SDF (signed-distance field) to screen UV, to be used within the visual shader graph. + A function to convert an SDF (signed-distance field) to screen UV, to be used within the visual shader graph. </brief_description> <description> Translates to [code]sdf_to_screen_uv(sdf_pos)[/code] in the shader language. diff --git a/doc/classes/VisualShaderNodeScreenUVToSDF.xml b/doc/classes/VisualShaderNodeScreenUVToSDF.xml index 438c8dc67b..2e121ffc54 100644 --- a/doc/classes/VisualShaderNodeScreenUVToSDF.xml +++ b/doc/classes/VisualShaderNodeScreenUVToSDF.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeScreenUVToSDF" inherits="VisualShaderNode" version="4.0"> <brief_description> - A function to convert screen UV to a SDF (signed-distance field), to be used within the visual shader graph. + A function to convert screen UV to an SDF (signed-distance field), to be used within the visual shader graph. </brief_description> <description> Translates to [code]screen_uv_to_sdf(uv)[/code] in the shader language. If the UV port isn't connected, [code]SCREEN_UV[/code] is used instead. diff --git a/doc/classes/VisualShaderNodeSmoothStep.xml b/doc/classes/VisualShaderNodeSmoothStep.xml index fa22d16da8..0ed53a8c26 100644 --- a/doc/classes/VisualShaderNodeSmoothStep.xml +++ b/doc/classes/VisualShaderNodeSmoothStep.xml @@ -5,7 +5,7 @@ </brief_description> <description> Translates to [code]smoothstep(edge0, edge1, x)[/code] in the shader language. - Returns [code]0.0[/code] if [code]x[/code] is smaller than [code]edge0[/code] and [code]1.0[/code] if [code]x[/code] is larger than [code]edge1[/code]. Otherwise the return value is interpolated between [code]0.0[/code] and [code]1.0[/code] using Hermite polynomials. + Returns [code]0.0[/code] if [code]x[/code] is smaller than [code]edge0[/code] and [code]1.0[/code] if [code]x[/code] is larger than [code]edge1[/code]. Otherwise, the return value is interpolated between [code]0.0[/code] and [code]1.0[/code] using Hermite polynomials. </description> <tutorials> </tutorials> diff --git a/doc/classes/VisualShaderNodeTextureSDF.xml b/doc/classes/VisualShaderNodeTextureSDF.xml index 7d3d654bd0..b5c89c2c31 100644 --- a/doc/classes/VisualShaderNodeTextureSDF.xml +++ b/doc/classes/VisualShaderNodeTextureSDF.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeTextureSDF" inherits="VisualShaderNode" version="4.0"> <brief_description> - Performs a SDF (signed-distance field) texture lookup within the visual shader graph. + Performs an SDF (signed-distance field) texture lookup within the visual shader graph. </brief_description> <description> Translates to [code]texture_sdf(sdf_pos)[/code] in the shader language. diff --git a/doc/classes/VisualShaderNodeTextureSDFNormal.xml b/doc/classes/VisualShaderNodeTextureSDFNormal.xml index 5dbf3e545a..25fe1c4b28 100644 --- a/doc/classes/VisualShaderNodeTextureSDFNormal.xml +++ b/doc/classes/VisualShaderNodeTextureSDFNormal.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeTextureSDFNormal" inherits="VisualShaderNode" version="4.0"> <brief_description> - Performs a SDF (signed-distance field) normal texture lookup within the visual shader graph. + Performs an SDF (signed-distance field) normal texture lookup within the visual shader graph. </brief_description> <description> Translates to [code]texture_sdf_normal(sdf_pos)[/code] in the shader language. diff --git a/doc/classes/bool.xml b/doc/classes/bool.xml index 03e8bee7d5..8da1eb3c88 100644 --- a/doc/classes/bool.xml +++ b/doc/classes/bool.xml @@ -4,7 +4,7 @@ Boolean built-in type. </brief_description> <description> - Boolean is a built-in type. There are two boolean values: [code]true[/code] and [code]false[/code]. You can think of it as an switch with on or off (1 or 0) setting. Booleans are used in programming for logic in condition statements, like [code]if[/code] statements. + Boolean is a built-in type. There are two boolean values: [code]true[/code] and [code]false[/code]. You can think of it as a switch with on or off (1 or 0) setting. Booleans are used in programming for logic in condition statements, like [code]if[/code] statements. Booleans can be directly used in [code]if[/code] statements. The code below demonstrates this on the [code]if can_shoot:[/code] line. You don't need to use [code]== true[/code], you only need [code]if can_shoot:[/code]. Similarly, use [code]if not can_shoot:[/code] rather than [code]== false[/code]. [codeblocks] [gdscript] @@ -49,6 +49,7 @@ [/csharp] [/codeblocks] The following code will set [code]can_shoot[/code] to [code]false[/code] and start a timer. This will prevent player from shooting until the timer runs out. Next [code]can_shoot[/code] will be set to [code]true[/code] again allowing player to shoot once again. + [codeblocks] [gdscript] var _can_shoot = true onready var _cool_down = $CoolDownTimer diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 804f02765c..de8f8b1d3b 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -4439,6 +4439,8 @@ void AnimationTrackEditor::_add_track(int p_type) { } adding_track_type = p_type; pick_track->popup_scenetree_dialog(); + pick_track->get_filter_line_edit()->clear(); + pick_track->get_filter_line_edit()->grab_focus(); } void AnimationTrackEditor::_new_track_property_selected(String p_name) { @@ -5635,6 +5637,70 @@ void AnimationTrackEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::FLOAT, "step"))); } +void AnimationTrackEditor::_pick_track_filter_text_changed(const String &p_newtext) { + TreeItem *root_item = pick_track->get_scene_tree()->get_scene_tree()->get_root(); + + Vector<Node *> select_candidates; + Node *to_select = nullptr; + + String filter = pick_track->get_filter_line_edit()->get_text(); + + _pick_track_select_recursive(root_item, filter, select_candidates); + + if (!select_candidates.is_empty()) { + for (int i = 0; i < select_candidates.size(); ++i) { + Node *candidate = select_candidates[i]; + + if (((String)candidate->get_name()).to_lower().begins_with(filter.to_lower())) { + to_select = candidate; + break; + } + } + + if (!to_select) { + to_select = select_candidates[0]; + } + } + + pick_track->get_scene_tree()->set_selected(to_select); +} + +void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates) { + if (!p_item) { + return; + } + + NodePath np = p_item->get_metadata(0); + Node *node = get_node(np); + + if (p_filter != String() && ((String)node->get_name()).findn(p_filter) != -1) { + p_select_candidates.push_back(node); + } + + TreeItem *c = p_item->get_children(); + + while (c) { + _pick_track_select_recursive(c, p_filter, p_select_candidates); + c = c->get_next(); + } +} + +void AnimationTrackEditor::_pick_track_filter_input(const Ref<InputEvent> &p_ie) { + Ref<InputEventKey> k = p_ie; + + if (k.is_valid()) { + switch (k->get_keycode()) { + case KEY_UP: + case KEY_DOWN: + case KEY_PAGEUP: + case KEY_PAGEDOWN: { + pick_track->get_scene_tree()->get_scene_tree()->call("_gui_input", k); + pick_track->get_filter_line_edit()->accept_event(); + } break; + } + } +} + AnimationTrackEditor::AnimationTrackEditor() { root = nullptr; @@ -5805,8 +5871,12 @@ AnimationTrackEditor::AnimationTrackEditor() { pick_track = memnew(SceneTreeDialog); add_child(pick_track); + pick_track->register_text_enter(pick_track->get_filter_line_edit()); pick_track->set_title(TTR("Pick a node to animate:")); pick_track->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_node_selected)); + pick_track->get_filter_line_edit()->connect("text_changed", callable_mp(this, &AnimationTrackEditor::_pick_track_filter_text_changed)); + pick_track->get_filter_line_edit()->connect("gui_input", callable_mp(this, &AnimationTrackEditor::_pick_track_filter_input)); + prop_selector = memnew(PropertySelector); add_child(prop_selector); prop_selector->connect("selected", callable_mp(this, &AnimationTrackEditor::_new_track_property_selected)); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index e8e4f915fa..c25865effb 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -499,6 +499,10 @@ class AnimationTrackEditor : public VBoxContainer { void _insert_animation_key(NodePath p_path, const Variant &p_value); + void _pick_track_filter_text_changed(const String &p_newtext); + void _pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates); + void _pick_track_filter_input(const Ref<InputEvent> &p_ie); + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 7f5f51cf70..3c0fe1571c 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -874,6 +874,20 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & continue; } + String importer_type = config->get_value("remap", "importer"); + + if (importer_type == "keep") { + //just keep file as-is + Vector<uint8_t> array = FileAccess::get_file_as_array(path); + err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + + if (err != OK) { + return err; + } + + continue; + } + List<String> remaps; config->get_section_keys("remap", &remaps); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index dce022e86e..fb0dc57501 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -405,6 +405,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo memdelete(f); + if (importer_name == "keep") { + return false; //keep mode, do not reimport + } + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); if (importer->get_format_version() > version) { @@ -1532,6 +1536,10 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector source_file_options[p_files[i]] = Map<StringName, Variant>(); importer_name = file_importer_name; + if (importer_name == "keep") { + continue; //do nothing + } + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); ERR_FAIL_COND_V(!importer.is_valid(), ERR_FILE_CORRUPT); List<ResourceImporter::ImportOption> options; @@ -1555,6 +1563,10 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector base_paths[p_files[i]] = ResourceFormatImporter::get_singleton()->get_import_base_path(p_files[i]); } + if (importer_name == "keep") { + return OK; // (do nothing) + } + ERR_FAIL_COND_V(importer_name == String(), ERR_UNCONFIGURED); Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); @@ -1668,7 +1680,7 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector return err; } -void EditorFileSystem::_reimport_file(const String &p_file) { +void EditorFileSystem::_reimport_file(const String &p_file, const Map<StringName, Variant> *p_custom_options, const String &p_custom_importer) { EditorFileSystemDirectory *fs = nullptr; int cpos = -1; bool found = _find_file(p_file, &fs, cpos); @@ -1677,23 +1689,32 @@ void EditorFileSystem::_reimport_file(const String &p_file) { //try to obtain existing params Map<StringName, Variant> params; - String importer_name; + String importer_name; //empty by default though + + if (p_custom_importer != String()) { + importer_name = p_custom_importer; + } + if (p_custom_options != nullptr) { + params = *p_custom_options; + } if (FileAccess::exists(p_file + ".import")) { //use existing - Ref<ConfigFile> cf; - cf.instance(); - Error err = cf->load(p_file + ".import"); - if (err == OK) { - if (cf->has_section("params")) { - List<String> sk; - cf->get_section_keys("params", &sk); - for (List<String>::Element *E = sk.front(); E; E = E->next()) { - params[E->get()] = cf->get_value("params", E->get()); + if (p_custom_options == nullptr) { + Ref<ConfigFile> cf; + cf.instance(); + Error err = cf->load(p_file + ".import"); + if (err == OK) { + if (cf->has_section("params")) { + List<String> sk; + cf->get_section_keys("params", &sk); + for (List<String>::Element *E = sk.front(); E; E = E->next()) { + params[E->get()] = cf->get_value("params", E->get()); + } + } + if (p_custom_importer == String() && cf->has_section("remap")) { + importer_name = cf->get_value("remap", "importer"); } - } - if (cf->has_section("remap")) { - importer_name = cf->get_value("remap", "importer"); } } @@ -1701,6 +1722,16 @@ void EditorFileSystem::_reimport_file(const String &p_file) { late_added_files.insert(p_file); //imported files do not call update_file(), but just in case.. } + if (importer_name == "keep") { + //keep files, do nothing. + fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); + fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); + fs->files[cpos]->deps.clear(); + fs->files[cpos]->type = ""; + fs->files[cpos]->import_valid = false; + EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); + return; + } Ref<ResourceImporter> importer; bool load_default = false; //find the importer @@ -1887,6 +1918,10 @@ void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, Map<Str } } +void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const Map<StringName, Variant> &p_custom_params) { + _reimport_file(p_file, &p_custom_params, p_importer); +} + void EditorFileSystem::reimport_files(const Vector<String> &p_files) { { // Ensure that ProjectSettings::IMPORTED_FILES_PATH exists. diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 59bde238a8..6f4f058503 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -203,7 +203,7 @@ class EditorFileSystem : public Node { void _update_extensions(); - void _reimport_file(const String &p_file); + void _reimport_file(const String &p_file, const Map<StringName, Variant> *p_custom_options = nullptr, const String &p_custom_importer = String()); Error _reimport_group(const String &p_group_file, const Vector<String> &p_files); bool _test_for_reimport(const String &p_path, bool p_only_imported_files); @@ -257,6 +257,8 @@ public: void reimport_files(const Vector<String> &p_files); + void reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const Map<StringName, Variant> &p_custom_params); + void update_script_classes(); bool is_group_file(const String &p_path) const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c3e15f2840..2e402d013b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -102,6 +102,7 @@ #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_texture_atlas.h" #include "editor/import/resource_importer_wav.h" +#include "editor/import/scene_import_settings.h" #include "editor/import/scene_importer_mesh_node_3d.h" #include "editor/import_dock.h" #include "editor/multi_node_edit.h" @@ -6179,6 +6180,9 @@ EditorNode::EditorNode() { project_settings = memnew(ProjectSettingsEditor(&editor_data)); gui_base->add_child(project_settings); + scene_import_settings = memnew(SceneImportSettings); + gui_base->add_child(scene_import_settings); + export_template_manager = memnew(ExportTemplateManager); gui_base->add_child(export_template_manager); diff --git a/editor/editor_node.h b/editor/editor_node.h index 91d873d16f..7e16936f5d 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -88,6 +88,7 @@ class Button; class VSplitContainer; class Window; class SubViewport; +class SceneImportSettings; class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -410,6 +411,7 @@ private: EditorResourcePreview *resource_preview; EditorFolding editor_folding; + SceneImportSettings *scene_import_settings; struct BottomPanelItem { String name; Control *control = nullptr; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index ef1f8030fa..814925f5c6 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -620,8 +620,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // is increased significantly more than it really should need to be. hints["editors/3d/grid_division_level_max"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_max", PROPERTY_HINT_RANGE, "-1,3,1", PROPERTY_USAGE_DEFAULT); - // Default smallest grid size is 1cm, 10^-2. - _initial_set("editors/3d/grid_division_level_min", -2); + // Default smallest grid size is 1m, 10^0. + _initial_set("editors/3d/grid_division_level_min", 0); // Lower values produce graphical artifacts regardless of view clipping planes, so limit to -2 as a lower bound. hints["editors/3d/grid_division_level_min"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_min", PROPERTY_HINT_RANGE, "-2,2,1", PROPERTY_USAGE_DEFAULT); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 030d36ff78..9aad1aa8d9 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -241,20 +241,14 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = // Generate icons. if (!p_only_thumbs) { for (int i = 0; i < editor_icons_count; i++) { - float icon_scale = EDSCALE; float saturation = p_icon_saturation; - // Always keep the DefaultProjectIcon at the default size - if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0) { - icon_scale = 1.0f; - } - if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0 || strcmp(editor_icons_names[i], "Godot") == 0 || strcmp(editor_icons_names[i], "Logo") == 0) { saturation = 1.0; } const int is_exception = exceptions.has(editor_icons_names[i]); - const Ref<ImageTexture> icon = editor_generate_icon(i, !is_exception, icon_scale, saturation); + const Ref<ImageTexture> icon = editor_generate_icon(i, !is_exception, EDSCALE, saturation); p_theme->set_icon(editor_icons_names[i], "EditorIcons", icon); } @@ -1398,3 +1392,15 @@ Ref<Theme> create_custom_theme(const Ref<Theme> p_theme) { return theme; } + +Ref<ImageTexture> create_unscaled_default_project_icon() { +#ifdef MODULE_SVG_ENABLED + for (int i = 0; i < editor_icons_count; i++) { + // ESCALE should never affect size of the icon + if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0) { + return editor_generate_icon(i, false, 1.0); + } + } +#endif + return Ref<ImageTexture>(memnew(ImageTexture)); +} diff --git a/editor/editor_themes.h b/editor/editor_themes.h index 852edf7669..c040654220 100644 --- a/editor/editor_themes.h +++ b/editor/editor_themes.h @@ -31,10 +31,13 @@ #ifndef EDITOR_THEMES_H #define EDITOR_THEMES_H +#include "scene/resources/texture.h" #include "scene/resources/theme.h" Ref<Theme> create_editor_theme(Ref<Theme> p_theme = nullptr); Ref<Theme> create_custom_theme(Ref<Theme> p_theme = nullptr); +Ref<ImageTexture> create_unscaled_default_project_icon(); + #endif diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 43bfccc656..772eff5f45 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -945,7 +945,25 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit } } else if (fpath != "Favorites") { if (ResourceLoader::get_resource_type(fpath) == "PackedScene") { - editor->open_request(fpath); + bool is_imported = false; + + { + List<String> importer_exts; + ResourceImporterScene::get_singleton()->get_recognized_extensions(&importer_exts); + String extension = fpath.get_extension(); + for (List<String>::Element *E = importer_exts.front(); E; E = E->next()) { + if (extension.nocasecmp_to(E->get())) { + is_imported = true; + break; + } + } + } + + if (is_imported) { + ResourceImporterScene::get_singleton()->show_advanced_options(fpath); + } else { + editor->open_request(fpath); + } } else { editor->load_resource(fpath); } diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 50b13673fa..080393e570 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -79,6 +79,9 @@ struct ColladaImport { Vector<int> valid_animated_properties; Map<String, bool> bones_with_animation; + Set<String> mesh_unique_names; + Set<String> material_unique_names; + Error _populate_skeleton(Skeleton3D *p_skeleton, Collada::Node *p_node, int &r_bone, int p_parent); Error _create_scene_skeletons(Collada::Node *p_node); Error _create_scene(Collada::Node *p_node, Node3D *p_parent); @@ -326,12 +329,25 @@ Error ColladaImport::_create_material(const String &p_target) { Ref<StandardMaterial3D> material = memnew(StandardMaterial3D); + String base_name; if (src_mat.name != "") { - material->set_name(src_mat.name); + base_name = src_mat.name; } else if (effect.name != "") { - material->set_name(effect.name); + base_name = effect.name; + } else { + base_name = "Material"; } + String name = base_name; + int counter = 2; + while (material_unique_names.has(name)) { + name = base_name + itos(counter++); + } + + material_unique_names.insert(name); + + material->set_name(name); + // DIFFUSE if (effect.diffuse.texture != "") { @@ -681,7 +697,8 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImpor int vertex_index = p.indices[src + vertex_ofs]; //used for index field (later used by controllers) int vertex_pos = (vertex_src->stride ? vertex_src->stride : 3) * vertex_index; - ERR_FAIL_INDEX_V(vertex_pos, vertex_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(vertex_pos + 0, vertex_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(vertex_pos + 2, vertex_src->array.size(), ERR_INVALID_DATA); vertex.vertex = Vector3(vertex_src->array[vertex_pos + 0], vertex_src->array[vertex_pos + 1], vertex_src->array[vertex_pos + 2]); if (pre_weights.has(vertex_index)) { @@ -690,16 +707,19 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImpor if (normal_src) { int normal_pos = (normal_src->stride ? normal_src->stride : 3) * p.indices[src + normal_ofs]; - ERR_FAIL_INDEX_V(normal_pos, normal_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(normal_pos + 0, normal_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(normal_pos + 2, normal_src->array.size(), ERR_INVALID_DATA); vertex.normal = Vector3(normal_src->array[normal_pos + 0], normal_src->array[normal_pos + 1], normal_src->array[normal_pos + 2]); if (tangent_src && binormal_src) { int binormal_pos = (binormal_src->stride ? binormal_src->stride : 3) * p.indices[src + binormal_ofs]; - ERR_FAIL_INDEX_V(binormal_pos, binormal_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(binormal_pos + 0, binormal_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(binormal_pos + 2, binormal_src->array.size(), ERR_INVALID_DATA); Vector3 binormal = Vector3(binormal_src->array[binormal_pos + 0], binormal_src->array[binormal_pos + 1], binormal_src->array[binormal_pos + 2]); int tangent_pos = (tangent_src->stride ? tangent_src->stride : 3) * p.indices[src + tangent_ofs]; - ERR_FAIL_INDEX_V(tangent_pos, tangent_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(tangent_pos + 0, tangent_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(tangent_pos + 2, tangent_src->array.size(), ERR_INVALID_DATA); Vector3 tangent = Vector3(tangent_src->array[tangent_pos + 0], tangent_src->array[tangent_pos + 1], tangent_src->array[tangent_pos + 2]); vertex.tangent.normal = tangent; @@ -709,19 +729,22 @@ Error ColladaImport::_create_mesh_surfaces(bool p_optimize, Ref<EditorSceneImpor if (uv_src) { int uv_pos = (uv_src->stride ? uv_src->stride : 2) * p.indices[src + uv_ofs]; - ERR_FAIL_INDEX_V(uv_pos, uv_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(uv_pos + 0, uv_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(uv_pos + 1, uv_src->array.size(), ERR_INVALID_DATA); vertex.uv = Vector3(uv_src->array[uv_pos + 0], 1.0 - uv_src->array[uv_pos + 1], 0); } if (uv2_src) { int uv2_pos = (uv2_src->stride ? uv2_src->stride : 2) * p.indices[src + uv2_ofs]; - ERR_FAIL_INDEX_V(uv2_pos, uv2_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(uv2_pos + 0, uv2_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(uv2_pos + 1, uv2_src->array.size(), ERR_INVALID_DATA); vertex.uv2 = Vector3(uv2_src->array[uv2_pos + 0], 1.0 - uv2_src->array[uv2_pos + 1], 0); } if (color_src) { int color_pos = (color_src->stride ? color_src->stride : 3) * p.indices[src + color_ofs]; // colors are RGB in collada.. - ERR_FAIL_INDEX_V(color_pos, color_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(color_pos + 0, color_src->array.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(color_pos + ((color_src->stride > 3) ? 3 : 2), color_src->array.size(), ERR_INVALID_DATA); vertex.color = Color(color_src->array[color_pos + 0], color_src->array[color_pos + 1], color_src->array[color_pos + 2], (color_src->stride > 3) ? color_src->array[color_pos + 3] : 1.0); } @@ -1121,7 +1144,22 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres ERR_FAIL_COND_V(!collada.state.mesh_data_map.has(meshid), ERR_INVALID_DATA); mesh = Ref<EditorSceneImporterMesh>(memnew(EditorSceneImporterMesh)); const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid]; - mesh->set_name(meshdata.name); + String name = meshdata.name; + if (name == "") { + name = "Mesh"; + } + int counter = 2; + while (mesh_unique_names.has(name)) { + name = meshdata.name; + if (name == "") { + name = "Mesh"; + } + name += itos(counter++); + } + + mesh_unique_names.insert(name); + + mesh->set_name(name); Error err = _create_mesh_surfaces(morphs.size() == 0, mesh, ng2->material_map, meshdata, apply_xform, bone_remap, skin, morph, morphs, p_use_compression, use_mesh_builtin_materials); ERR_FAIL_COND_V_MSG(err, err, "Cannot create mesh surface."); @@ -1638,16 +1676,23 @@ void EditorSceneImporterCollada::get_extensions(List<String> *r_extensions) cons } Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { + if (r_err) { + *r_err = OK; + } ColladaImport state; uint32_t flags = Collada::IMPORT_FLAG_SCENE; if (p_flags & IMPORT_ANIMATION) { flags |= Collada::IMPORT_FLAG_ANIMATION; } - state.use_mesh_builtin_materials = !(p_flags & IMPORT_MATERIALS_IN_INSTANCES); + state.use_mesh_builtin_materials = true; state.bake_fps = p_bake_fps; - Error err = state.load(p_path, flags, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, p_flags & EditorSceneImporter::IMPORT_USE_COMPRESSION); + Error err = state.load(p_path, flags, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 0); + + if (r_err) { + *r_err = err; + } ERR_FAIL_COND_V_MSG(err != OK, nullptr, "Cannot load scene from file '" + p_path + "'."); @@ -1667,7 +1712,7 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ } if (p_flags & IMPORT_ANIMATION) { - state.create_animations(p_flags & IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS, p_flags & EditorSceneImporter::IMPORT_ANIMATION_KEEP_VALUE_TRACKS); + state.create_animations(true, true); AnimationPlayer *ap = memnew(AnimationPlayer); for (int i = 0; i < state.animations.size(); i++) { String name; @@ -1677,12 +1722,6 @@ Node *EditorSceneImporterCollada::import_scene(const String &p_path, uint32_t p_ name = state.animations[i]->get_name(); } - if (p_flags & IMPORT_ANIMATION_DETECT_LOOP) { - if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { - state.animations.write[i]->set_loop(true); - } - } - ap->add_animation(name, state.animations[i]); } state.scene->add_child(ap); @@ -1700,7 +1739,7 @@ Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path Error err = state.load(p_path, Collada::IMPORT_FLAG_ANIMATION, p_flags & EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS); ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load animation from file '" + p_path + "'."); - state.create_animations(p_flags & EditorSceneImporter::IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS, p_flags & EditorSceneImporter::IMPORT_ANIMATION_KEEP_VALUE_TRACKS); + state.create_animations(true, true); if (state.scene) { memdelete(state.scene); } @@ -1709,12 +1748,6 @@ Ref<Animation> EditorSceneImporterCollada::import_animation(const String &p_path return Ref<Animation>(); } Ref<Animation> anim = state.animations[0]; - String base = p_path.get_basename().to_lower(); - if (p_flags & IMPORT_ANIMATION_DETECT_LOOP) { - if (base.begins_with("loop") || base.ends_with("loop") || base.begins_with("cycle") || base.ends_with("cycle")) { - anim->set_loop(true); - } - } return anim; } diff --git a/editor/import/resource_importer_obj.cpp b/editor/import/resource_importer_obj.cpp index 9111252943..5c522e3176 100644 --- a/editor/import/resource_importer_obj.cpp +++ b/editor/import/resource_importer_obj.cpp @@ -427,7 +427,7 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh>> &r_meshes, bool p_ Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { List<Ref<Mesh>> meshes; - Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, p_flags & IMPORT_USE_COMPRESSION, Vector3(1, 1, 1), Vector3(0, 0, 0), r_missing_deps); + Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, 0, Vector3(1, 1, 1), Vector3(0, 0, 0), r_missing_deps); if (err != OK) { if (r_err) { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 14ecccc13e..4eba609c0d 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -32,7 +32,9 @@ #include "core/io/resource_saver.h" #include "editor/editor_node.h" +#include "editor/import/scene_import_settings.h" #include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" @@ -111,20 +113,14 @@ void EditorSceneImporter::_bind_methods() { BIND_CONSTANT(IMPORT_SCENE); BIND_CONSTANT(IMPORT_ANIMATION); - BIND_CONSTANT(IMPORT_ANIMATION_DETECT_LOOP); - BIND_CONSTANT(IMPORT_ANIMATION_OPTIMIZE); - BIND_CONSTANT(IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS); - BIND_CONSTANT(IMPORT_ANIMATION_KEEP_VALUE_TRACKS); - BIND_CONSTANT(IMPORT_GENERATE_TANGENT_ARRAYS); BIND_CONSTANT(IMPORT_FAIL_ON_MISSING_DEPENDENCIES); - BIND_CONSTANT(IMPORT_MATERIALS_IN_INSTANCES); - BIND_CONSTANT(IMPORT_USE_COMPRESSION); + BIND_CONSTANT(IMPORT_GENERATE_TANGENT_ARRAYS); + BIND_CONSTANT(IMPORT_USE_NAMED_SKIN_BINDS); } ///////////////////////////////// void EditorScenePostImport::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::OBJECT, "post_import", PropertyInfo(Variant::OBJECT, "scene"))); - ClassDB::bind_method(D_METHOD("get_source_folder"), &EditorScenePostImport::get_source_folder); ClassDB::bind_method(D_METHOD("get_source_file"), &EditorScenePostImport::get_source_file); } @@ -136,16 +132,11 @@ Node *EditorScenePostImport::post_import(Node *p_scene) { return p_scene; } -String EditorScenePostImport::get_source_folder() const { - return source_folder; -} - String EditorScenePostImport::get_source_file() const { return source_file; } -void EditorScenePostImport::init(const String &p_source_folder, const String &p_source_file) { - source_folder = p_source_folder; +void EditorScenePostImport::init(const String &p_source_file) { source_file = p_source_file; } @@ -183,29 +174,9 @@ bool ResourceImporterScene::get_option_visibility(const String &p_option, const if (p_option != "animation/import" && !bool(p_options["animation/import"])) { return false; } - - if (p_option == "animation/keep_custom_tracks" && int(p_options["animation/storage"]) == 0) { - return false; - } - - if (p_option.begins_with("animation/optimizer/") && p_option != "animation/optimizer/enabled" && !bool(p_options["animation/optimizer/enabled"])) { - return false; - } - - if (p_option.begins_with("animation/clip_")) { - int max_clip = p_options["animation/clips/amount"]; - int clip = p_option.get_slice("/", 1).get_slice("_", 1).to_int() - 1; - if (clip >= max_clip) { - return false; - } - } - } - - if (p_option == "materials/keep_on_reimport" && int(p_options["materials/storage"]) == 0) { - return false; } - if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) < 2) { + if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) < 3) { return false; } @@ -213,34 +184,11 @@ bool ResourceImporterScene::get_option_visibility(const String &p_option, const } int ResourceImporterScene::get_preset_count() const { - return PRESET_MAX; + return 0; } String ResourceImporterScene::get_preset_name(int p_idx) const { - switch (p_idx) { - case PRESET_SINGLE_SCENE: - return TTR("Import as Single Scene"); - case PRESET_SEPARATE_ANIMATIONS: - return TTR("Import with Separate Animations"); - case PRESET_SEPARATE_MATERIALS: - return TTR("Import with Separate Materials"); - case PRESET_SEPARATE_MESHES: - return TTR("Import with Separate Objects"); - case PRESET_SEPARATE_MESHES_AND_MATERIALS: - return TTR("Import with Separate Objects+Materials"); - case PRESET_SEPARATE_MESHES_AND_ANIMATIONS: - return TTR("Import with Separate Objects+Animations"); - case PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS: - return TTR("Import with Separate Materials+Animations"); - case PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS: - return TTR("Import with Separate Objects+Materials+Animations"); - case PRESET_MULTIPLE_SCENES: - return TTR("Import as Multiple Scenes"); - case PRESET_MULTIPLE_SCENES_AND_MATERIALS: - return TTR("Import as Multiple Scenes+Materials"); - } - - return ""; + return String(); } static bool _teststr(const String &p_what, const String &p_str) { @@ -299,10 +247,24 @@ static void _gen_shape_list(const Ref<Mesh> &mesh, List<Ref<Shape3D>> &r_shape_l } } -Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh>, List<Ref<Shape3D>>> &collision_map, LightBakeMode p_light_bake_mode) { +static void _pre_gen_shape_list(const Ref<EditorSceneImporterMesh> &mesh, List<Ref<Shape3D>> &r_shape_list, bool p_convex) { + if (!p_convex) { + Ref<Shape3D> shape = mesh->create_trimesh_shape(); + r_shape_list.push_back(shape); + } else { + Vector<Ref<Shape3D>> cd = mesh->convex_decompose(); + if (cd.size()) { + for (int i = 0; i < cd.size(); i++) { + r_shape_list.push_back(cd[i]); + } + } + } +} + +Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map) { // children first for (int i = 0; i < p_node->get_child_count(); i++) { - Node *r = _fix_node(p_node->get_child(i), p_root, collision_map, p_light_bake_mode); + Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map); if (!r) { i--; //was erased } @@ -317,33 +279,29 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> return nullptr; } - if (Object::cast_to<MeshInstance3D>(p_node)) { - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); + if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); - Ref<ArrayMesh> m = mi->get_mesh(); + Ref<EditorSceneImporterMesh> m = mi->get_mesh(); if (m.is_valid()) { for (int i = 0; i < m->get_surface_count(); i++) { - Ref<StandardMaterial3D> mat = m->surface_get_material(i); + Ref<BaseMaterial3D> mat = m->get_surface_material(i); if (!mat.is_valid()) { continue; } if (_teststr(mat->get_name(), "alpha")) { - mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + mat->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); mat->set_name(_fixstr(mat->get_name(), "alpha")); } if (_teststr(mat->get_name(), "vcol")) { - mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + mat->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + mat->set_flag(BaseMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); mat->set_name(_fixstr(mat->get_name(), "vcol")); } } } - - if (p_light_bake_mode != LIGHT_BAKE_DISABLED) { - mi->set_gi_mode(GeometryInstance3D::GI_MODE_BAKED); - } } if (Object::cast_to<AnimationPlayer>(p_node)) { @@ -367,6 +325,17 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> } } } + + String animname = E->get(); + const int loop_string_count = 3; + static const char *loop_strings[loop_string_count] = { "loops", "loop", "cycle" }; + for (int i = 0; i < loop_string_count; i++) { + if (_teststr(animname, loop_strings[i])) { + anim->set_loop(true); + animname = _fixstr(animname, loop_strings[i]); + ap->rename_animation(E->get(), animname); + } + } } } @@ -374,9 +343,9 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> if (isroot) { return p_node; } - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); if (mi) { - Ref<Mesh> mesh = mi->get_mesh(); + Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { List<Ref<Shape3D>> shapes; @@ -384,10 +353,10 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> if (collision_map.has(mesh)) { shapes = collision_map[mesh]; } else if (_teststr(name, "colonly")) { - _gen_shape_list(mesh, shapes, false); + _pre_gen_shape_list(mesh, shapes, false); collision_map[mesh] = shapes; } else if (_teststr(name, "convcolonly")) { - _gen_shape_list(mesh, shapes, true); + _pre_gen_shape_list(mesh, shapes, true); collision_map[mesh] = shapes; } @@ -413,7 +382,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> cshape->set_shape(E->get()); col->add_child(cshape); - cshape->set_name("shape" + itos(idx)); cshape->set_owner(col->get_owner()); idx++; } @@ -433,34 +401,30 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> BoxShape3D *boxShape = memnew(BoxShape3D); boxShape->set_size(Vector3(2, 2, 2)); colshape->set_shape(boxShape); - colshape->set_name("BoxShape3D"); } else if (empty_draw_type == "SINGLE_ARROW") { RayShape3D *rayShape = memnew(RayShape3D); rayShape->set_length(1); colshape->set_shape(rayShape); - colshape->set_name("RayShape3D"); Object::cast_to<Node3D>(sb)->rotate_x(Math_PI / 2); } else if (empty_draw_type == "IMAGE") { WorldMarginShape3D *world_margin_shape = memnew(WorldMarginShape3D); colshape->set_shape(world_margin_shape); - colshape->set_name("WorldMarginShape3D"); } else { SphereShape3D *sphereShape = memnew(SphereShape3D); sphereShape->set_radius(1); colshape->set_shape(sphereShape); - colshape->set_name("SphereShape3D"); } sb->add_child(colshape); colshape->set_owner(sb->get_owner()); } - } else if (_teststr(name, "rigid") && Object::cast_to<MeshInstance3D>(p_node)) { + } else if (_teststr(name, "rigid") && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { if (isroot) { return p_node; } - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); - Ref<Mesh> mesh = mi->get_mesh(); + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { List<Ref<Shape3D>> shapes; @@ -475,7 +439,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> p_node->replace_by(rigid_body); rigid_body->set_transform(mi->get_transform()); p_node = rigid_body; - mi->set_name("mesh"); mi->set_transform(Transform()); rigid_body->add_child(mi); mi->set_owner(rigid_body->get_owner()); @@ -486,16 +449,15 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> cshape->set_shape(E->get()); rigid_body->add_child(cshape); - cshape->set_name("shape" + itos(idx)); cshape->set_owner(p_node->get_owner()); idx++; } } - } else if ((_teststr(name, "col") || (_teststr(name, "convcol"))) && Object::cast_to<MeshInstance3D>(p_node)) { - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); + } else if ((_teststr(name, "col") || (_teststr(name, "convcol"))) && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); - Ref<Mesh> mesh = mi->get_mesh(); + Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { List<Ref<Shape3D>> shapes; @@ -524,7 +486,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> if (shapes.size()) { StaticBody3D *col = memnew(StaticBody3D); - col->set_name("static_collision"); mi->add_child(col); col->set_owner(mi->get_owner()); @@ -534,7 +495,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> cshape->set_shape(E->get()); col->add_child(cshape); - cshape->set_name("shape" + itos(idx)); cshape->set_owner(p_node->get_owner()); idx++; @@ -542,71 +502,31 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> } } - } else if (_teststr(name, "navmesh") && Object::cast_to<MeshInstance3D>(p_node)) { + } else if (_teststr(name, "navmesh") && Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { if (isroot) { return p_node; } - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); - Ref<ArrayMesh> mesh = mi->get_mesh(); + Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); ERR_FAIL_COND_V(mesh.is_null(), nullptr); NavigationRegion3D *nmi = memnew(NavigationRegion3D); nmi->set_name(_fixstr(name, "navmesh")); - Ref<NavigationMesh> nmesh = memnew(NavigationMesh); - nmesh->create_from_mesh(mesh); + Ref<NavigationMesh> nmesh = mesh->create_navigation_mesh(); nmi->set_navigation_mesh(nmesh); Object::cast_to<Node3D>(nmi)->set_transform(mi->get_transform()); p_node->replace_by(nmi); memdelete(p_node); p_node = nmi; - } else if (_teststr(name, "vehicle")) { - if (isroot) { - return p_node; - } - - Node *owner = p_node->get_owner(); - Node3D *s = Object::cast_to<Node3D>(p_node); - VehicleBody3D *bv = memnew(VehicleBody3D); - String n = _fixstr(p_node->get_name(), "vehicle"); - bv->set_name(n); - p_node->replace_by(bv); - p_node->set_name(n); - bv->add_child(p_node); - bv->set_owner(owner); - p_node->set_owner(owner); - bv->set_transform(s->get_transform()); - s->set_transform(Transform()); - - p_node = bv; - - } else if (_teststr(name, "wheel")) { - if (isroot) { - return p_node; - } - Node *owner = p_node->get_owner(); - Node3D *s = Object::cast_to<Node3D>(p_node); - VehicleWheel3D *bv = memnew(VehicleWheel3D); - String n = _fixstr(p_node->get_name(), "wheel"); - bv->set_name(n); - p_node->replace_by(bv); - p_node->set_name(n); - bv->add_child(p_node); - bv->set_owner(owner); - p_node->set_owner(owner); - bv->set_transform(s->get_transform()); - s->set_transform(Transform()); - - p_node = bv; - - } else if (Object::cast_to<MeshInstance3D>(p_node)) { + } else if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { //last attempt, maybe collision inside the mesh data - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); - Ref<ArrayMesh> mesh = mi->get_mesh(); + Ref<EditorSceneImporterMesh> mesh = mi->get_mesh(); if (!mesh.is_null()) { List<Ref<Shape3D>> shapes; if (collision_map.has(mesh)) { @@ -623,7 +543,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> if (shapes.size()) { StaticBody3D *col = memnew(StaticBody3D); - col->set_name("static_collision"); p_node->add_child(col); col->set_owner(p_node->get_owner()); @@ -633,7 +552,6 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> cshape->set_shape(E->get()); col->add_child(cshape); - cshape->set_name("shape" + itos(idx)); cshape->set_owner(p_node->get_owner()); idx++; } @@ -644,27 +562,311 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> return p_node; } -void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, bool p_bake_all) { - if (!scene->has_node(String("AnimationPlayer"))) { - return; +Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) { + // children first + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *r = _post_fix_node(p_node->get_child(i), p_root, collision_map, r_scanned_meshes, p_node_data, p_material_data, p_animation_data, p_animation_fps); + if (!r) { + i--; //was erased + } + } + + bool isroot = p_node == p_root; + + String import_id; + + if (p_node->has_meta("import_id")) { + import_id = p_node->get_meta("import_id"); + } else { + import_id = "PATH:" + p_root->get_path_to(p_node); + } + + Dictionary node_settings; + if (p_node_data.has(import_id)) { + node_settings = p_node_data[import_id]; + } + + if (!isroot && (node_settings.has("import/skip_import") && bool(node_settings["import/skip_import"]))) { + memdelete(p_node); + return nullptr; + } + + if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + + Ref<EditorSceneImporterMesh> m = mi->get_mesh(); + + if (m.is_valid()) { + if (!r_scanned_meshes.has(m)) { + for (int i = 0; i < m->get_surface_count(); i++) { + Ref<Material> mat = m->get_surface_material(i); + if (mat.is_valid()) { + String mat_id; + if (mat->has_meta("import_id")) { + mat_id = mat->get_meta("import_id"); + } else { + mat_id = mat->get_name(); + } + + if (mat_id != String() && p_material_data.has(mat_id)) { + Dictionary matdata = p_material_data[mat_id]; + if (matdata.has("use_external/enabled") && bool(matdata["use_external/enabled"]) && matdata.has("use_external/path")) { + String path = matdata["use_external/path"]; + Ref<Material> external_mat = ResourceLoader::load(path); + if (external_mat.is_valid()) { + m->set_surface_material(i, external_mat); + } + } + } + } + } + + r_scanned_meshes.insert(m); + } + + if (node_settings.has("generate/physics")) { + int mesh_physics_mode = node_settings["generate/physics"]; + + if (mesh_physics_mode != MESH_PHYSICS_DISABLED) { + List<Ref<Shape3D>> shapes; + + if (collision_map.has(m)) { + shapes = collision_map[m]; + } else { + switch (mesh_physics_mode) { + case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: { + _pre_gen_shape_list(m, shapes, false); + } break; + case MESH_PHYSICS_RIGID_BODY_AND_MESH: { + _pre_gen_shape_list(m, shapes, true); + } break; + case MESH_PHYSICS_STATIC_COLLIDER_ONLY: { + _pre_gen_shape_list(m, shapes, false); + } break; + case MESH_PHYSICS_AREA_ONLY: { + _pre_gen_shape_list(m, shapes, true); + } break; + } + } + + if (shapes.size()) { + CollisionObject3D *base = nullptr; + switch (mesh_physics_mode) { + case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: { + StaticBody3D *col = memnew(StaticBody3D); + p_node->add_child(col); + base = col; + } break; + case MESH_PHYSICS_RIGID_BODY_AND_MESH: { + RigidBody3D *rigid_body = memnew(RigidBody3D); + rigid_body->set_name(p_node->get_name()); + p_node->replace_by(rigid_body); + rigid_body->set_transform(mi->get_transform()); + p_node = rigid_body; + mi->set_transform(Transform()); + rigid_body->add_child(mi); + mi->set_owner(rigid_body->get_owner()); + base = rigid_body; + } break; + case MESH_PHYSICS_STATIC_COLLIDER_ONLY: { + StaticBody3D *col = memnew(StaticBody3D); + col->set_transform(mi->get_transform()); + col->set_name(p_node->get_name()); + p_node->replace_by(col); + memdelete(p_node); + p_node = col; + base = col; + } break; + case MESH_PHYSICS_AREA_ONLY: { + Area3D *area = memnew(Area3D); + area->set_transform(mi->get_transform()); + area->set_name(p_node->get_name()); + p_node->replace_by(area); + memdelete(p_node); + p_node = area; + base = area; + + } break; + } + + int idx = 0; + for (List<Ref<Shape3D>>::Element *E = shapes.front(); E; E = E->next()) { + CollisionShape3D *cshape = memnew(CollisionShape3D); + cshape->set_shape(E->get()); + base->add_child(cshape); + + cshape->set_owner(base->get_owner()); + idx++; + } + } + } + } + } + } + + //navmesh (node may have changed type above) + if (Object::cast_to<EditorSceneImporterMeshNode3D>(p_node)) { + EditorSceneImporterMeshNode3D *mi = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + + Ref<EditorSceneImporterMesh> m = mi->get_mesh(); + + if (m.is_valid()) { + if (node_settings.has("generate/navmesh")) { + int navmesh_mode = node_settings["generate/navmesh"]; + + if (navmesh_mode != NAVMESH_DISABLED) { + NavigationRegion3D *nmi = memnew(NavigationRegion3D); + + Ref<NavigationMesh> nmesh = m->create_navigation_mesh(); + nmi->set_navigation_mesh(nmesh); + + if (navmesh_mode == NAVMESH_NAVMESH_ONLY) { + nmi->set_transform(mi->get_transform()); + p_node->replace_by(nmi); + memdelete(p_node); + p_node = nmi; + } else { + mi->add_child(nmi); + nmi->set_owner(mi->get_owner()); + } + } + } + } + } + + if (Object::cast_to<AnimationPlayer>(p_node)) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node); + + { + //make sure this is unique + node_settings = node_settings.duplicate(true); + //fill node settings for this node with default values + List<ImportOption> iopts; + get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts); + for (List<ImportOption>::Element *E = iopts.front(); E; E = E->next()) { + if (!node_settings.has(E->get().option.name)) { + node_settings[E->get().option.name] = E->get().default_value; + } + } + } + + bool use_optimizer = node_settings["optimizer/enabled"]; + float anim_optimizer_linerr = node_settings["optimizer/max_linear_error"]; + float anim_optimizer_angerr = node_settings["optimizer/max_angular_error"]; + float anim_optimizer_maxang = node_settings["optimizer/max_angle"]; + + if (use_optimizer) { + _optimize_animations(ap, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang); + } + + Array animation_clips; + { + int clip_count = node_settings["clips/amount"]; + + for (int i = 0; i < clip_count; i++) { + String name = node_settings["clip_" + itos(i + 1) + "/name"]; + int from_frame = node_settings["clip_" + itos(i + 1) + "/start_frame"]; + int end_frame = node_settings["clip_" + itos(i + 1) + "/end_frame"]; + bool loop = node_settings["clip_" + itos(i + 1) + "/loops"]; + bool save_to_file = node_settings["clip_" + itos(i + 1) + "/save_to_file/enabled"]; + bool save_to_path = node_settings["clip_" + itos(i + 1) + "/save_to_file/path"]; + bool save_to_file_keep_custom = node_settings["clip_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"]; + + animation_clips.push_back(name); + animation_clips.push_back(from_frame / p_animation_fps); + animation_clips.push_back(end_frame / p_animation_fps); + animation_clips.push_back(loop); + animation_clips.push_back(save_to_file); + animation_clips.push_back(save_to_path); + animation_clips.push_back(save_to_file_keep_custom); + } + } + + if (animation_clips.size()) { + _create_clips(ap, animation_clips, true); + } else { + List<StringName> anims; + ap->get_animation_list(&anims); + for (List<StringName>::Element *E = anims.front(); E; E = E->next()) { + String name = E->get(); + Ref<Animation> anim = ap->get_animation(name); + if (p_animation_data.has(name)) { + Dictionary anim_settings = p_animation_data[name]; + { + //fill with default values + List<ImportOption> iopts; + get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION, &iopts); + for (List<ImportOption>::Element *F = iopts.front(); F; F = F->next()) { + if (!anim_settings.has(F->get().option.name)) { + anim_settings[F->get().option.name] = F->get().default_value; + } + } + } + + anim->set_loop(anim_settings["settings/loops"]); + bool save = anim_settings["save_to_file/enabled"]; + String path = anim_settings["save_to_file/path"]; + bool keep_custom = anim_settings["save_to_file/keep_custom_tracks"]; + + Ref<Animation> saved_anim = _save_animation_to_file(anim, save, path, keep_custom); + + if (saved_anim != anim) { + ap->add_animation(name, saved_anim); //replace + } + } + } + } } - Node *n = scene->get_node(String("AnimationPlayer")); - ERR_FAIL_COND(!n); - AnimationPlayer *anim = Object::cast_to<AnimationPlayer>(n); - ERR_FAIL_COND(!anim); + return p_node; +} + +Ref<Animation> ResourceImporterScene::_save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks) { + if (!p_save_to_file || !p_save_to_path.is_resource_file()) { + return anim; + } + + if (FileAccess::exists(p_save_to_path) && p_keep_custom_tracks) { + // Copy custom animation tracks from previously imported files. + Ref<Animation> old_anim = ResourceLoader::load(p_save_to_path, "Animation", ResourceFormatLoader::CACHE_MODE_IGNORE); + if (old_anim.is_valid()) { + for (int i = 0; i < old_anim->get_track_count(); i++) { + if (!old_anim->track_is_imported(i)) { + old_anim->copy_track(i, anim); + } + } + anim->set_loop(old_anim->has_loop()); + } + } + if (ResourceCache::has(p_save_to_path)) { + Ref<Animation> old_anim = Ref<Resource>(ResourceCache::get(p_save_to_path)); + if (old_anim.is_valid()) { + old_anim->copy_from(anim); + anim = old_anim; + } + } + anim->set_path(p_save_to_path, true); // Set path to save externally. + Error err = ResourceSaver::save(p_save_to_path, anim, ResourceSaver::FLAG_CHANGE_PATH); + ERR_FAIL_COND_V_MSG(err != OK, anim, "Saving of animation failed: " + p_save_to_path); + return anim; +} + +void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all) { if (!anim->has_animation("default")) { return; } Ref<Animation> default_anim = anim->get_animation("default"); - for (int i = 0; i < p_clips.size(); i += 4) { + for (int i = 0; i < p_clips.size(); i += 7) { String name = p_clips[i]; float from = p_clips[i + 1]; float to = p_clips[i + 2]; bool loop = p_clips[i + 3]; + bool save_to_file = p_clips[i + 4]; + String save_to_path = p_clips[i + 5]; + bool keep_current = p_clips[i + 6]; if (from >= to) { continue; } @@ -752,141 +954,17 @@ void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, boo new_anim->set_loop(loop); new_anim->set_length(to - from); anim->add_animation(name, new_anim); - } - - anim->remove_animation("default"); //remove default (no longer needed) -} - -void ResourceImporterScene::_filter_anim_tracks(Ref<Animation> anim, Set<String> &keep) { - Ref<Animation> a = anim; - ERR_FAIL_COND(!a.is_valid()); - for (int j = 0; j < a->get_track_count(); j++) { - String path = a->track_get_path(j); - - if (!keep.has(path)) { - a->remove_track(j); - j--; + Ref<Animation> saved_anim = _save_animation_to_file(new_anim, save_to_file, save_to_path, keep_current); + if (saved_anim != new_anim) { + anim->add_animation(name, saved_anim); } } -} - -void ResourceImporterScene::_filter_tracks(Node *scene, const String &p_text) { - if (!scene->has_node(String("AnimationPlayer"))) { - return; - } - Node *n = scene->get_node(String("AnimationPlayer")); - ERR_FAIL_COND(!n); - AnimationPlayer *anim = Object::cast_to<AnimationPlayer>(n); - ERR_FAIL_COND(!anim); - - Vector<String> strings = p_text.split("\n"); - for (int i = 0; i < strings.size(); i++) { - strings.write[i] = strings[i].strip_edges(); - } - - List<StringName> anim_names; - anim->get_animation_list(&anim_names); - for (List<StringName>::Element *E = anim_names.front(); E; E = E->next()) { - String name = E->get(); - bool valid_for_this = false; - bool valid = false; - - Set<String> keep; - Set<String> keep_local; - - for (int i = 0; i < strings.size(); i++) { - if (strings[i].begins_with("@")) { - valid_for_this = false; - for (Set<String>::Element *F = keep_local.front(); F; F = F->next()) { - keep.insert(F->get()); - } - keep_local.clear(); - Vector<String> filters = strings[i].substr(1, strings[i].length()).split(","); - for (int j = 0; j < filters.size(); j++) { - String fname = filters[j].strip_edges(); - if (fname == "") { - continue; - } - int fc = fname[0]; - bool plus; - if (fc == '+') { - plus = true; - } else if (fc == '-') { - plus = false; - } else { - continue; - } - - String filter = fname.substr(1, fname.length()).strip_edges(); - - if (!name.matchn(filter)) { - continue; - } - valid_for_this = plus; - } - - if (valid_for_this) { - valid = true; - } - - } else if (valid_for_this) { - Ref<Animation> a = anim->get_animation(name); - if (!a.is_valid()) { - continue; - } - - for (int j = 0; j < a->get_track_count(); j++) { - String path = a->track_get_path(j); - - String tname = strings[i]; - if (tname == "") { - continue; - } - int fc = tname[0]; - bool plus; - if (fc == '+') { - plus = true; - } else if (fc == '-') { - plus = false; - } else { - continue; - } - - String filter = tname.substr(1, tname.length()).strip_edges(); - - if (!path.matchn(filter)) { - continue; - } - - if (plus) { - keep_local.insert(path); - } else if (!keep.has(path)) { - keep_local.erase(path); - } - } - } - } - - if (valid) { - for (Set<String>::Element *F = keep_local.front(); F; F = F->next()) { - keep.insert(F->get()); - } - _filter_anim_tracks(anim->get_animation(name), keep); - } - } + anim->remove_animation("default"); //remove default (no longer needed) } -void ResourceImporterScene::_optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle) { - if (!scene->has_node(String("AnimationPlayer"))) { - return; - } - Node *n = scene->get_node(String("AnimationPlayer")); - ERR_FAIL_COND(!n); - AnimationPlayer *anim = Object::cast_to<AnimationPlayer>(n); - ERR_FAIL_COND(!anim); - +void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle) { List<StringName> anim_names; anim->get_animation_list(&anim_names); for (List<StringName>::Element *E = anim_names.front(); E; E = E->next()) { @@ -895,208 +973,99 @@ void ResourceImporterScene::_optimize_animations(Node *scene, float p_max_lin_er } } -static String _make_extname(const String &p_str) { - String ext_name = p_str.replace(".", "_"); - ext_name = ext_name.replace(":", "_"); - ext_name = ext_name.replace("\"", "_"); - ext_name = ext_name.replace("<", "_"); - ext_name = ext_name.replace(">", "_"); - ext_name = ext_name.replace("/", "_"); - ext_name = ext_name.replace("|", "_"); - ext_name = ext_name.replace("\\", "_"); - ext_name = ext_name.replace("?", "_"); - ext_name = ext_name.replace("*", "_"); - - return ext_name; -} - -void ResourceImporterScene::_find_meshes(Node *p_node, Map<Ref<ArrayMesh>, Transform> &meshes) { - List<PropertyInfo> pi; - p_node->get_property_list(&pi); - - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_node); - - if (mi) { - Ref<ArrayMesh> mesh = mi->get_mesh(); - - if (mesh.is_valid() && !meshes.has(mesh)) { - Node3D *s = mi; - Transform transform; - while (s) { - transform = transform * s->get_transform(); - s = Object::cast_to<Node3D>(s->get_parent()); +void ResourceImporterScene::get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const { + switch (p_category) { + case INTERNAL_IMPORT_CATEGORY_NODE: { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + } break; + case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/physics", PROPERTY_HINT_ENUM, "Disabled,Mesh + Static Collider,Rigid Body + Mesh,Static Collider Only,Area Only"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0)); + } break; + case INTERNAL_IMPORT_CATEGORY_MESH: { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "save_to_file/path", PROPERTY_HINT_SAVE_FILE, "*.res,*.tres"), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/make_streamable"), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/shadow_meshes", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lightmap_uv", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0)); + } break; + case INTERNAL_IMPORT_CATEGORY_MATERIAL: { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "use_external/path", PROPERTY_HINT_FILE, "*.material,*.res,*.tres"), "")); + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION: { + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "settings/loops"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "save_to_file/path", PROPERTY_HINT_SAVE_FILE, "*.res,*.tres"), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/keep_custom_tracks"), "")); + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_linear_error"), 0.05)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error"), 0.01)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angle"), 22)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); + + for (int i = 0; i < 256; i++) { + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "slice_" + itos(i + 1) + "/name"), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slice_" + itos(i + 1) + "/start_frame"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slice_" + itos(i + 1) + "/end_frame"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/loops"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "slice_" + itos(i + 1) + "/save_to_file/path", PROPERTY_HINT_SAVE_FILE, ".res,*.tres"), "")); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"), false)); } - - meshes[mesh] = transform; + } break; + default: { } } - for (int i = 0; i < p_node->get_child_count(); i++) { - _find_meshes(p_node->get_child(i), meshes); - } } -void ResourceImporterScene::_make_external_resources(Node *p_node, const String &p_base_path, bool p_make_animations, bool p_animations_as_text, bool p_keep_animations, bool p_make_materials, bool p_materials_as_text, bool p_keep_materials, bool p_make_meshes, bool p_meshes_as_text, Map<Ref<Animation>, Ref<Animation>> &p_animations, Map<Ref<Material>, Ref<Material>> &p_materials, Map<Ref<ArrayMesh>, Ref<ArrayMesh>> &p_meshes) { - List<PropertyInfo> pi; - - if (p_make_animations) { - if (Object::cast_to<AnimationPlayer>(p_node)) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node); - - List<StringName> anims; - ap->get_animation_list(&anims); - for (List<StringName>::Element *E = anims.front(); E; E = E->next()) { - Ref<Animation> anim = ap->get_animation(E->get()); - ERR_CONTINUE(anim.is_null()); - - if (!p_animations.has(anim)) { - // Tracks from source file should be set as imported, anything else is a custom track. - for (int i = 0; i < anim->get_track_count(); i++) { - anim->track_set_imported(i, true); - } - - String ext_name; - - if (p_animations_as_text) { - ext_name = p_base_path.plus_file(_make_extname(E->get()) + ".tres"); - } else { - ext_name = p_base_path.plus_file(_make_extname(E->get()) + ".anim"); - } - - if (FileAccess::exists(ext_name) && p_keep_animations) { - // Copy custom animation tracks from previously imported files. - Ref<Animation> old_anim = ResourceLoader::load(ext_name, "Animation", ResourceFormatLoader::CACHE_MODE_IGNORE); - if (old_anim.is_valid()) { - for (int i = 0; i < old_anim->get_track_count(); i++) { - if (!old_anim->track_is_imported(i)) { - old_anim->copy_track(i, anim); - } - } - anim->set_loop(old_anim->has_loop()); - } - } - - anim->set_path(ext_name, true); // Set path to save externally. - ResourceSaver::save(ext_name, anim, ResourceSaver::FLAG_CHANGE_PATH); - p_animations[anim] = anim; - } +bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const { + if (p_options.has("import/skip_import") && p_option != "import/skip_import" && bool(p_options["import/skip_import"])) { + return false; //if skip import + } + switch (p_category) { + case INTERNAL_IMPORT_CATEGORY_NODE: { + } break; + case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: { + } break; + case INTERNAL_IMPORT_CATEGORY_MESH: { + if (p_option == "save_to_file/path" || p_option == "save_to_file/make_streamable") { + return p_options["save_to_file/enabled"]; + } + } break; + case INTERNAL_IMPORT_CATEGORY_MATERIAL: { + if (p_option == "use_external/path") { + return p_options["use_external/enabled"]; + } + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION: { + if (p_option == "save_to_file/path" || p_option == "save_to_file/keep_custom_tracks") { + return p_options["save_to_file/enabled"]; + } + } break; + case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: { + if (p_option.begins_with("animation/optimizer/") && p_option != "animation/optimizer/enabled" && !bool(p_options["animation/optimizer/enabled"])) { + return false; } - } - } - - p_node->get_property_list(&pi); - - for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) { - if (E->get().type == Variant::OBJECT) { - Ref<Material> mat = p_node->get(E->get().name); - - if (p_make_materials && mat.is_valid() && mat->get_name() != "") { - if (!p_materials.has(mat)) { - String ext_name; - - if (p_materials_as_text) { - ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".tres"); - } else { - ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".material"); - } - - if (p_keep_materials && FileAccess::exists(ext_name)) { - //if exists, use it - p_materials[mat] = ResourceLoader::load(ext_name); - } else { - ResourceSaver::save(ext_name, mat, ResourceSaver::FLAG_CHANGE_PATH); - p_materials[mat] = ResourceLoader::load(ext_name, "", ResourceFormatLoader::CACHE_MODE_IGNORE); // disable loading from the cache. - } - } - - if (p_materials[mat] != mat) { - p_node->set(E->get().name, p_materials[mat]); - } - } else { - Ref<ArrayMesh> mesh = p_node->get(E->get().name); - - if (mesh.is_valid()) { - bool mesh_just_added = false; - - if (p_make_meshes) { - if (!p_meshes.has(mesh)) { - //meshes are always overwritten, keeping them is not practical - String ext_name; - - if (p_meshes_as_text) { - ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".tres"); - } else { - ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".mesh"); - } - - ResourceSaver::save(ext_name, mesh, ResourceSaver::FLAG_CHANGE_PATH); - p_meshes[mesh] = ResourceLoader::load(ext_name); - p_node->set(E->get().name, p_meshes[mesh]); - mesh_just_added = true; - } - } - - if (p_make_materials) { - if (mesh_just_added || !p_meshes.has(mesh)) { - for (int i = 0; i < mesh->get_surface_count(); i++) { - mat = mesh->surface_get_material(i); - - if (!mat.is_valid()) { - continue; - } - if (mat->get_name() == "") { - continue; - } - - if (!p_materials.has(mat)) { - String ext_name; - - if (p_materials_as_text) { - ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".tres"); - } else { - ext_name = p_base_path.plus_file(_make_extname(mat->get_name()) + ".material"); - } - - if (p_keep_materials && FileAccess::exists(ext_name)) { - //if exists, use it - p_materials[mat] = ResourceLoader::load(ext_name); - } else { - ResourceSaver::save(ext_name, mat, ResourceSaver::FLAG_CHANGE_PATH); - p_materials[mat] = ResourceLoader::load(ext_name, "", ResourceFormatLoader::CACHE_MODE_IGNORE); // disable loading from the cache. - } - } - - if (p_materials[mat] != mat) { - mesh->surface_set_material(i, p_materials[mat]); - - //re-save the mesh since a material is now assigned - if (p_make_meshes) { - String ext_name; - - if (p_meshes_as_text) { - ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".tres"); - } else { - ext_name = p_base_path.plus_file(_make_extname(mesh->get_name()) + ".mesh"); - } - - ResourceSaver::save(ext_name, mesh, ResourceSaver::FLAG_CHANGE_PATH); - p_meshes[mesh] = ResourceLoader::load(ext_name); - } - } - } - if (!p_make_meshes) { - p_meshes[mesh] = Ref<ArrayMesh>(); //save it anyway, so it won't be checked again - } - } - } + if (p_option.begins_with("animation/slice_")) { + int max_slice = p_options["animation/slices/amount"]; + int slice = p_option.get_slice("/", 1).get_slice("_", 1).to_int() - 1; + if (slice >= max_slice) { + return false; } } + } break; + default: { } } - for (int i = 0; i < p_node->get_child_count(); i++) { - _make_external_resources(p_node->get_child(i), p_base_path, p_make_animations, p_animations_as_text, p_keep_animations, p_make_materials, p_materials_as_text, p_keep_materials, p_make_meshes, p_meshes_as_text, p_animations, p_materials, p_meshes); - } + return true; } void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, int p_preset) const { @@ -1115,42 +1084,18 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in script_ext_hint += "*." + E->get(); } - bool materials_out = p_preset == PRESET_SEPARATE_MATERIALS || p_preset == PRESET_SEPARATE_MESHES_AND_MATERIALS || p_preset == PRESET_MULTIPLE_SCENES_AND_MATERIALS || p_preset == PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; - bool meshes_out = p_preset == PRESET_SEPARATE_MESHES || p_preset == PRESET_SEPARATE_MESHES_AND_MATERIALS || p_preset == PRESET_SEPARATE_MESHES_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; - bool scenes_out = p_preset == PRESET_MULTIPLE_SCENES || p_preset == PRESET_MULTIPLE_SCENES_AND_MATERIALS; - bool animations_out = p_preset == PRESET_SEPARATE_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS || p_preset == PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS; - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/custom_script", PROPERTY_HINT_FILE, script_ext_hint), "")); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "nodes/storage", PROPERTY_HINT_ENUM, "Single Scene,Instanced Sub-Scenes"), scenes_out ? 1 : 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/location", PROPERTY_HINT_ENUM, "Node,Mesh"), (meshes_out || materials_out) ? 1 : 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.material),Files (.tres)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), materials_out ? 1 : 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "materials/keep_on_reimport"), materials_out)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.mesh),Files (.tres)"), meshes_out ? 1 : 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/generate_lods"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/create_shadow_meshes"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Enable,Gen Lightmaps", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Dynamic,Static,Static Lightmaps", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 2)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "external_files/store_in_subdir"), false)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 15)); - r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/filter_script", PROPERTY_HINT_MULTILINE_TEXT), "")); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.anim),Files (.tres)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), animations_out)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/keep_custom_tracks"), animations_out)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/optimizer/max_linear_error"), 0.05)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/optimizer/max_angular_error"), 0.01)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/optimizer/max_angle"), 22)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/remove_unused_tracks"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clips/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); - for (int i = 0; i < 256; i++) { - r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/clip_" + itos(i + 1) + "/name"), "")); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clip_" + itos(i + 1) + "/start_frame"), 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clip_" + itos(i + 1) + "/end_frame"), 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/clip_" + itos(i + 1) + "/loops"), false)); - } + r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); + + r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "_subresources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), Dictionary())); } void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner) { @@ -1222,7 +1167,7 @@ Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(Edito return importer->import_animation(p_path, p_flags, p_bake_fps); } -void ResourceImporterScene::_generate_meshes(Node *p_node, bool p_generate_lods, bool p_create_shadow_meshes) { +void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache) { EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); if (src_mesh_node) { //is mesh @@ -1235,14 +1180,94 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, bool p_generate_lods, Ref<ArrayMesh> mesh; if (!src_mesh_node->get_mesh()->has_mesh()) { //do mesh processing - if (p_generate_lods) { + + bool generate_lods = p_generate_lods; + bool create_shadow_meshes = p_create_shadow_meshes; + bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS; + String save_to_file; + + String mesh_id; + + if (src_mesh_node->get_mesh()->has_meta("import_id")) { + mesh_id = src_mesh_node->get_mesh()->get_meta("import_id"); + } else { + mesh_id = src_mesh_node->get_mesh()->get_name(); + } + + if (mesh_id != String() && p_mesh_data.has(mesh_id)) { + Dictionary mesh_settings = p_mesh_data[mesh_id]; + + if (mesh_settings.has("generate/shadow_meshes")) { + int shadow_meshes = mesh_settings["generate/shadow_meshes"]; + if (shadow_meshes == MESH_OVERRIDE_ENABLE) { + create_shadow_meshes = true; + } else if (shadow_meshes == MESH_OVERRIDE_DISABLE) { + create_shadow_meshes = false; + } + } + + if (mesh_settings.has("generate/lightmap_uv")) { + int lightmap_uv = mesh_settings["generate/lightmap_uv"]; + if (lightmap_uv == MESH_OVERRIDE_ENABLE) { + bake_lightmaps = true; + } else if (lightmap_uv == MESH_OVERRIDE_DISABLE) { + bake_lightmaps = false; + } + } + + if (mesh_settings.has("generate/lods")) { + int lods = mesh_settings["generate/lods"]; + if (lods == MESH_OVERRIDE_ENABLE) { + generate_lods = true; + } else if (lods == MESH_OVERRIDE_DISABLE) { + generate_lods = false; + } + } + + if (mesh_settings.has("save_to_file/enabled") && bool(mesh_settings["save_to_file/enabled"]) && mesh_settings.has("save_to_file/path")) { + save_to_file = mesh_settings["save_to_file/path"]; + if (!save_to_file.is_resource_file()) { + save_to_file = ""; + } + } + } + + if (generate_lods) { src_mesh_node->get_mesh()->generate_lods(); } - if (p_create_shadow_meshes) { + if (create_shadow_meshes) { src_mesh_node->get_mesh()->create_shadow_mesh(); } + + if (bake_lightmaps) { + Transform xf; + Node3D *n = src_mesh_node; + while (n) { + xf = n->get_transform() * xf; + n = n->get_parent_spatial(); + } + + //use xf as transform for mesh, and bake it + } + + if (save_to_file != String()) { + Ref<Mesh> existing = Ref<Resource>(ResourceCache::get(save_to_file)); + if (existing.is_valid()) { + //if somehow an existing one is useful, create + existing->reset_state(); + } + mesh = src_mesh_node->get_mesh()->get_mesh(existing); + + ResourceSaver::save(save_to_file, mesh); //override + + mesh->set_path(save_to_file, true); //takeover existing, if needed + + } else { + mesh = src_mesh_node->get_mesh()->get_mesh(); + } + } else { + mesh = src_mesh_node->get_mesh()->get_mesh(); } - mesh = src_mesh_node->get_mesh()->get_mesh(); if (mesh.is_valid()) { mesh_node->set_mesh(mesh); @@ -1251,15 +1276,68 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, bool p_generate_lods, } } } + + switch (p_light_bake_mode) { + case LIGHT_BAKE_DISABLED: { + mesh_node->set_gi_mode(GeometryInstance3D::GI_MODE_DISABLED); + } break; + case LIGHT_BAKE_DYNAMIC: { + mesh_node->set_gi_mode(GeometryInstance3D::GI_MODE_DYNAMIC); + } break; + case LIGHT_BAKE_STATIC: + case LIGHT_BAKE_STATIC_LIGHTMAPS: { + mesh_node->set_gi_mode(GeometryInstance3D::GI_MODE_BAKED); + } break; + } + p_node->replace_by(mesh_node); memdelete(p_node); p_node = mesh_node; } for (int i = 0; i < p_node->get_child_count(); i++) { - _generate_meshes(p_node->get_child(i), p_generate_lods, p_create_shadow_meshes); + _generate_meshes(p_node->get_child(i), p_mesh_data, p_generate_lods, p_create_shadow_meshes, p_light_bake_mode, p_lightmap_texel_size, p_src_lightmap_cache, r_dst_lightmap_cache); } } + +Node *ResourceImporterScene::pre_import(const String &p_source_file) { + Ref<EditorSceneImporter> importer; + String ext = p_source_file.get_extension().to_lower(); + + EditorProgress progress("pre-import", TTR("Pre-Import Scene"), 0); + progress.step(TTR("Importing Scene..."), 0); + + for (Set<Ref<EditorSceneImporter>>::Element *E = importers.front(); E; E = E->next()) { + List<String> extensions; + E->get()->get_extensions(&extensions); + + for (List<String>::Element *F = extensions.front(); F; F = F->next()) { + if (F->get().to_lower() == ext) { + importer = E->get(); + break; + } + } + + if (importer.is_valid()) { + break; + } + } + + ERR_FAIL_COND_V(!importer.is_valid(), nullptr); + + Error err; + Node *scene = importer->import_scene(p_source_file, EditorSceneImporter::IMPORT_ANIMATION | EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS, 15, nullptr, &err); + if (!scene || err != OK) { + return nullptr; + } + + Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map; + + _pre_fix_node(scene, scene, collision_map); + + return scene; +} + Error ResourceImporterScene::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { const String &src_path = p_source_file; @@ -1289,27 +1367,21 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p float fps = p_options["animation/fps"]; - int import_flags = EditorSceneImporter::IMPORT_ANIMATION_DETECT_LOOP; - if (!bool(p_options["animation/optimizer/remove_unused_tracks"])) { - import_flags |= EditorSceneImporter::IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS; - } + int import_flags = 0; if (bool(p_options["animation/import"])) { import_flags |= EditorSceneImporter::IMPORT_ANIMATION; } - if (bool(p_options["meshes/ensure_tangents"])) { - import_flags |= EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; - } - - if (int(p_options["materials/location"]) == 0) { - import_flags |= EditorSceneImporter::IMPORT_MATERIALS_IN_INSTANCES; - } - if (bool(p_options["skins/use_named_skins"])) { import_flags |= EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; } + bool ensure_tangents = p_options["meshes/ensure_tangents"]; + if (ensure_tangents) { + import_flags |= EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; + } + Error err = OK; List<String> missing_deps; // for now, not much will be done with this Node *scene = importer->import_scene(src_path, import_flags, fps, &missing_deps, &err); @@ -1317,6 +1389,29 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p return err; } + Dictionary subresources = p_options["_subresources"]; + + Dictionary node_data; + if (subresources.has("nodes")) { + node_data = subresources["nodes"]; + } + + Dictionary material_data; + if (subresources.has("materials")) { + material_data = subresources["materials"]; + } + + Dictionary animation_data; + if (subresources.has("animations")) { + animation_data = subresources["animations"]; + } + + Set<Ref<EditorSceneImporterMesh>> scanned_meshes; + Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map; + + _pre_fix_node(scene, scene, collision_map); + _post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps); + String root_type = p_options["nodes/root_type"]; root_type = root_type.split(" ")[0]; // full root_type is "ClassName (filename.gd)" for a script global class. @@ -1354,73 +1449,35 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p bool gen_lods = bool(p_options["meshes/generate_lods"]); bool create_shadow_meshes = bool(p_options["meshes/create_shadow_meshes"]); - - _generate_meshes(scene, gen_lods, create_shadow_meshes); - - err = OK; - - String animation_filter = String(p_options["animation/filter_script"]).strip_edges(); - - bool use_optimizer = p_options["animation/optimizer/enabled"]; - float anim_optimizer_linerr = p_options["animation/optimizer/max_linear_error"]; - float anim_optimizer_angerr = p_options["animation/optimizer/max_angular_error"]; - float anim_optimizer_maxang = p_options["animation/optimizer/max_angle"]; int light_bake_mode = p_options["meshes/light_baking"]; + float texel_size = p_options["meshes/lightmap_texel_size"]; + float lightmap_texel_size = MAX(0.001, texel_size); - Map<Ref<Mesh>, List<Ref<Shape3D>>> collision_map; + Vector<uint8_t> src_lightmap_cache; + Vector<uint8_t> dst_lightmap_cache; - scene = _fix_node(scene, scene, collision_map, LightBakeMode(light_bake_mode)); - - if (use_optimizer) { - _optimize_animations(scene, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang); - } - - Array animation_clips; { - int clip_count = p_options["animation/clips/amount"]; - - for (int i = 0; i < clip_count; i++) { - String name = p_options["animation/clip_" + itos(i + 1) + "/name"]; - int from_frame = p_options["animation/clip_" + itos(i + 1) + "/start_frame"]; - int end_frame = p_options["animation/clip_" + itos(i + 1) + "/end_frame"]; - bool loop = p_options["animation/clip_" + itos(i + 1) + "/loops"]; - - animation_clips.push_back(name); - animation_clips.push_back(from_frame / fps); - animation_clips.push_back(end_frame / fps); - animation_clips.push_back(loop); + src_lightmap_cache = FileAccess::get_file_as_array(p_source_file + ".unwrap_cache", &err); + if (err != OK) { + src_lightmap_cache.clear(); } } - if (animation_clips.size()) { - _create_clips(scene, animation_clips, !bool(p_options["animation/optimizer/remove_unused_tracks"])); - } - if (animation_filter != "") { - _filter_tracks(scene, animation_filter); + Dictionary mesh_data; + if (subresources.has("meshes")) { + mesh_data = subresources["meshes"]; } + _generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, dst_lightmap_cache); - bool external_animations = int(p_options["animation/storage"]) == 1 || int(p_options["animation/storage"]) == 2; - bool external_animations_as_text = int(p_options["animation/storage"]) == 2; - bool keep_custom_tracks = p_options["animation/keep_custom_tracks"]; - bool external_materials = int(p_options["materials/storage"]) == 1 || int(p_options["materials/storage"]) == 2; - bool external_materials_as_text = int(p_options["materials/storage"]) == 2; - bool external_meshes = int(p_options["meshes/storage"]) == 1 || int(p_options["meshes/storage"]) == 2; - bool external_meshes_as_text = int(p_options["meshes/storage"]) == 2; - bool external_scenes = int(p_options["nodes/storage"]) == 1; - - String base_path = p_source_file.get_base_dir(); - - if (external_animations || external_materials || external_meshes || external_scenes) { - if (bool(p_options["external_files/store_in_subdir"])) { - String subdir_name = p_source_file.get_file().get_basename(); - DirAccess *da = DirAccess::open(base_path); - Error err2 = da->make_dir(subdir_name); - memdelete(da); - ERR_FAIL_COND_V_MSG(err2 != OK && err2 != ERR_ALREADY_EXISTS, err2, "Cannot make directory '" + subdir_name + "'."); - base_path = base_path.plus_file(subdir_name); + if (dst_lightmap_cache.size()) { + FileAccessRef f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE); + if (f) { + f->store_buffer(dst_lightmap_cache.ptr(), dst_lightmap_cache.size()); } } + err = OK; +#if 0 if (light_bake_mode == 2 /* || generate LOD */) { Map<Ref<ArrayMesh>, Transform> meshes; _find_meshes(scene, meshes); @@ -1445,9 +1502,6 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } } - float texel_size = p_options["meshes/lightmap_texel_size"]; - texel_size = MAX(0.001, texel_size); - Map<String, unsigned int> used_unwraps; EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); @@ -1469,7 +1523,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p if (err2 != OK) { EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); } else { - String hash = String::md5((unsigned char *)ret_cache_data); +` String hash = String::md5((unsigned char *)ret_cache_data); used_unwraps.insert(hash, ret_cache_size); if (!ret_used_cache) { @@ -1482,7 +1536,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } else { int current_size = cache_data.size(); cache_data.resize(cache_data.size() + ret_cache_size); - unsigned char *ptrw = cache_data.ptrw(); + unsigned char *ptrw = cache_data.ptrw(); memcpy(&ptrw[current_size], ret_cache_data, ret_cache_size); int *data = (int *)ptrw; data[0] += 1; @@ -1530,20 +1584,11 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p file->close(); } } - - if (external_animations || external_materials || external_meshes) { - Map<Ref<Animation>, Ref<Animation>> anim_map; - Map<Ref<Material>, Ref<Material>> mat_map; - Map<Ref<ArrayMesh>, Ref<ArrayMesh>> mesh_map; - - bool keep_materials = bool(p_options["materials/keep_on_reimport"]); - - _make_external_resources(scene, base_path, external_animations, external_animations_as_text, keep_custom_tracks, external_materials, external_materials_as_text, keep_materials, external_meshes, external_meshes_as_text, anim_map, mat_map, mesh_map); - } +#endif progress.step(TTR("Running Custom Script..."), 2); - String post_import_script_path = p_options["nodes/custom_script"]; + String post_import_script_path = p_options["import_script/path"]; Ref<EditorScenePostImport> post_import_script; if (post_import_script_path != "") { @@ -1562,7 +1607,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } if (post_import_script.is_valid()) { - post_import_script->init(base_path, p_source_file); + post_import_script->init(p_source_file); scene = post_import_script->post_import(scene); if (!scene) { EditorNode::add_io_error( @@ -1574,29 +1619,6 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p progress.step(TTR("Saving..."), 104); - if (external_scenes) { - //save sub-scenes as instances! - for (int i = 0; i < scene->get_child_count(); i++) { - Node *child = scene->get_child(i); - if (child->get_owner() != scene) { - continue; //not a real child probably created by scene type (ig, a scrollbar) - } - _replace_owner(child, scene, child); - - String cn = String(child->get_name()).strip_edges().replace(".", "_").replace(":", "_"); - if (cn == String()) { - cn = "ChildNode" + itos(i); - } - String path = base_path.plus_file(cn + ".scn"); - child->set_filename(path); - - Ref<PackedScene> packer = memnew(PackedScene); - packer->pack(child); - err = ResourceSaver::save(path, packer); //do not take over, let the changed files reload themselves - ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + path + "'."); - } - } - Ref<PackedScene> packer = memnew(PackedScene); packer->pack(scene); print_verbose("Saving scene to: " + p_save_path + ".scn"); @@ -1613,6 +1635,13 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p ResourceImporterScene *ResourceImporterScene::singleton = nullptr; +bool ResourceImporterScene::ResourceImporterScene::has_advanced_options() const { + return true; +} +void ResourceImporterScene::ResourceImporterScene::show_advanced_options(const String &p_path) { + SceneImportSettings::get_singleton()->open_settings(p_path); +} + ResourceImporterScene::ResourceImporterScene() { singleton = this; } diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index aced0226ff..36573779f2 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -39,7 +39,9 @@ #include "scene/resources/skin.h" class Material; +class AnimationPlayer; +class EditorSceneImporterMesh; class EditorSceneImporter : public Reference { GDCLASS(EditorSceneImporter, Reference); @@ -53,15 +55,9 @@ public: enum ImportFlags { IMPORT_SCENE = 1, IMPORT_ANIMATION = 2, - IMPORT_ANIMATION_DETECT_LOOP = 4, - IMPORT_ANIMATION_OPTIMIZE = 8, - IMPORT_ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS = 16, - IMPORT_ANIMATION_KEEP_VALUE_TRACKS = 32, - IMPORT_GENERATE_TANGENT_ARRAYS = 256, - IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 512, - IMPORT_MATERIALS_IN_INSTANCES = 1024, - IMPORT_USE_COMPRESSION = 2048, - IMPORT_USE_NAMED_SKIN_BINDS = 4096, + IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4, + IMPORT_GENERATE_TANGENT_ARRAYS = 8, + IMPORT_USE_NAMED_SKIN_BINDS = 16, }; @@ -76,17 +72,15 @@ public: class EditorScenePostImport : public Reference { GDCLASS(EditorScenePostImport, Reference); - String source_folder; String source_file; protected: static void _bind_methods(); public: - String get_source_folder() const; String get_source_file() const; virtual Node *post_import(Node *p_scene); - virtual void init(const String &p_source_folder, const String &p_source_file); + virtual void init(const String &p_source_file); EditorScenePostImport(); }; @@ -97,31 +91,35 @@ class ResourceImporterScene : public ResourceImporter { static ResourceImporterScene *singleton; - enum Presets { - PRESET_SEPARATE_MATERIALS, - PRESET_SEPARATE_MESHES, - PRESET_SEPARATE_ANIMATIONS, - - PRESET_SINGLE_SCENE, + enum LightBakeMode { + LIGHT_BAKE_DISABLED, + LIGHT_BAKE_DYNAMIC, + LIGHT_BAKE_STATIC, + LIGHT_BAKE_STATIC_LIGHTMAPS + }; - PRESET_SEPARATE_MESHES_AND_MATERIALS, - PRESET_SEPARATE_MESHES_AND_ANIMATIONS, - PRESET_SEPARATE_MATERIALS_AND_ANIMATIONS, - PRESET_SEPARATE_MESHES_MATERIALS_AND_ANIMATIONS, + enum MeshPhysicsMode { + MESH_PHYSICS_DISABLED, + MESH_PHYSICS_MESH_AND_STATIC_COLLIDER, + MESH_PHYSICS_RIGID_BODY_AND_MESH, + MESH_PHYSICS_STATIC_COLLIDER_ONLY, + MESH_PHYSICS_AREA_ONLY, + }; - PRESET_MULTIPLE_SCENES, - PRESET_MULTIPLE_SCENES_AND_MATERIALS, - PRESET_MAX + enum NavMeshMode { + NAVMESH_DISABLED, + NAVMESH_MESH_AND_NAVMESH, + NAVMESH_NAVMESH_ONLY, }; - enum LightBakeMode { - LIGHT_BAKE_DISABLED, - LIGHT_BAKE_ENABLE, - LIGHT_BAKE_LIGHTMAPS + enum MeshOverride { + MESH_OVERRIDE_DEFAULT, + MESH_OVERRIDE_ENABLE, + MESH_OVERRIDE_DISABLE, }; void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner); - void _generate_meshes(Node *p_node, bool p_generate_lods, bool p_create_shadow_meshes); + void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache); public: static ResourceImporterScene *get_singleton() { return singleton; } @@ -141,26 +139,39 @@ public: virtual int get_preset_count() const override; virtual String get_preset_name(int p_idx) const override; + enum InternalImportCategory { + INTERNAL_IMPORT_CATEGORY_NODE, + INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, + INTERNAL_IMPORT_CATEGORY_MESH, + INTERNAL_IMPORT_CATEGORY_MATERIAL, + INTERNAL_IMPORT_CATEGORY_ANIMATION, + INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, + INTERNAL_IMPORT_CATEGORY_MAX + }; + + void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const; + bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const; + virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override; virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; virtual int get_import_order() const override { return 100; } //after everything - void _find_meshes(Node *p_node, Map<Ref<ArrayMesh>, Transform> &meshes); + Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map); + Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); - void _make_external_resources(Node *p_node, const String &p_base_path, bool p_make_animations, bool p_animations_as_text, bool p_keep_animations, bool p_make_materials, bool p_materials_as_text, bool p_keep_materials, bool p_make_meshes, bool p_meshes_as_text, Map<Ref<Animation>, Ref<Animation>> &p_animations, Map<Ref<Material>, Ref<Material>> &p_materials, Map<Ref<ArrayMesh>, Ref<ArrayMesh>> &p_meshes); - - Node *_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh>, List<Ref<Shape3D>>> &collision_map, LightBakeMode p_light_bake_mode); - - void _create_clips(Node *scene, const Array &p_clips, bool p_bake_all); - void _filter_anim_tracks(Ref<Animation> anim, Set<String> &keep); - void _filter_tracks(Node *scene, const String &p_text); - void _optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle); + Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks); + void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all); + void _optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle); + Node *pre_import(const String &p_source_file); virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; Node *import_scene_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); Ref<Animation> import_animation_from_other_importer(EditorSceneImporter *p_exception, const String &p_path, uint32_t p_flags, int p_bake_fps); + virtual bool has_advanced_options() const override; + virtual void show_advanced_options(const String &p_path) override; + ResourceImporterScene(); }; diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp new file mode 100644 index 0000000000..5ae67f413a --- /dev/null +++ b/editor/import/scene_import_settings.cpp @@ -0,0 +1,1199 @@ +/*************************************************************************/ +/* scene_import_settings.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "scene_import_settings.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/import/scene_importer_mesh_node_3d.h" +#include "scene/resources/surface_tool.h" + +class SceneImportSettingsData : public Object { + GDCLASS(SceneImportSettingsData, Object) + friend class SceneImportSettings; + Map<StringName, Variant> *settings = nullptr; + Map<StringName, Variant> current; + Map<StringName, Variant> defaults; + List<ResourceImporter::ImportOption> options; + + ResourceImporterScene::InternalImportCategory category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX; + + bool _set(const StringName &p_name, const Variant &p_value) { + if (settings) { + if (defaults.has(p_name) && defaults[p_name] == p_value) { + settings->erase(p_name); + } else { + (*settings)[p_name] = p_value; + } + + current[p_name] = p_value; + return true; + } + return false; + } + bool _get(const StringName &p_name, Variant &r_ret) const { + if (settings) { + if (settings->has(p_name)) { + r_ret = (*settings)[p_name]; + return true; + } + } + if (defaults.has(p_name)) { + r_ret = defaults[p_name]; + return true; + } + return false; + } + void _get_property_list(List<PropertyInfo> *p_list) const { + for (const List<ResourceImporter::ImportOption>::Element *E = options.front(); E; E = E->next()) { + if (ResourceImporterScene::get_singleton()->get_internal_option_visibility(category, E->get().option.name, current)) { + p_list->push_back(E->get().option); + } + } + } +}; + +void SceneImportSettings::_fill_material(Tree *p_tree, const Ref<Material> &p_material, TreeItem *p_parent) { + String import_id; + bool has_import_id = false; + + if (p_material->has_meta("import_id")) { + import_id = p_material->get_meta("import_id"); + has_import_id = true; + } else if (p_material->get_name() != "") { + import_id = p_material->get_name(); + has_import_id = true; + } else { + import_id = "@MATERIAL:" + itos(material_set.size()); + } + + if (!material_map.has(import_id)) { + MaterialData md; + md.has_import_id = has_import_id; + md.material = p_material; + + _load_default_subresource_settings(md.settings, "materials", import_id, ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MATERIAL); + + material_map[import_id] = md; + } + + MaterialData &material_data = material_map[import_id]; + + Ref<Texture2D> icon = get_theme_icon("StandardMaterial3D", "EditorIcons"); + + TreeItem *item = p_tree->create_item(p_parent); + item->set_text(0, p_material->get_name()); + item->set_icon(0, icon); + + bool created = false; + if (!material_set.has(p_material)) { + material_set.insert(p_material); + created = true; + } + + item->set_meta("type", "Material"); + item->set_meta("import_id", import_id); + item->set_tooltip(0, vformat(TTR("Import ID: %s"), import_id)); + item->set_selectable(0, true); + + if (p_tree == scene_tree) { + material_data.scene_node = item; + } else if (p_tree == mesh_tree) { + material_data.mesh_node = item; + } else { + material_data.material_node = item; + } + + if (created) { + _fill_material(material_tree, p_material, material_tree->get_root()); + } +} + +void SceneImportSettings::_fill_mesh(Tree *p_tree, const Ref<Mesh> &p_mesh, TreeItem *p_parent) { + String import_id; + + bool has_import_id = false; + if (p_mesh->has_meta("import_id")) { + import_id = p_mesh->get_meta("import_id"); + has_import_id = true; + } else if (p_mesh->get_name() != String()) { + import_id = p_mesh->get_name(); + has_import_id = true; + } else { + import_id = "@MESH:" + itos(mesh_set.size()); + } + + if (!mesh_map.has(import_id)) { + MeshData md; + md.has_import_id = has_import_id; + md.mesh = p_mesh; + + _load_default_subresource_settings(md.settings, "meshes", import_id, ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH); + + mesh_map[import_id] = md; + } + + MeshData &mesh_data = mesh_map[import_id]; + + Ref<Texture2D> icon = get_theme_icon("Mesh", "EditorIcons"); + + TreeItem *item = p_tree->create_item(p_parent); + item->set_text(0, p_mesh->get_name()); + item->set_icon(0, icon); + + bool created = false; + if (!mesh_set.has(p_mesh)) { + mesh_set.insert(p_mesh); + created = true; + } + + item->set_meta("type", "Mesh"); + item->set_meta("import_id", import_id); + item->set_tooltip(0, vformat(TTR("Import ID: %s"), import_id)); + + item->set_selectable(0, true); + + if (p_tree == scene_tree) { + mesh_data.scene_node = item; + } else { + mesh_data.mesh_node = item; + } + + item->set_collapsed(true); + + for (int i = 0; i < p_mesh->get_surface_count(); i++) { + Ref<Material> mat = p_mesh->surface_get_material(i); + if (mat.is_valid()) { + _fill_material(p_tree, mat, item); + } + } + + if (created) { + _fill_mesh(mesh_tree, p_mesh, mesh_tree->get_root()); + } +} + +void SceneImportSettings::_fill_animation(Tree *p_tree, const Ref<Animation> &p_anim, const String &p_name, TreeItem *p_parent) { + if (!animation_map.has(p_name)) { + AnimationData ad; + ad.animation = p_anim; + + _load_default_subresource_settings(ad.settings, "animations", p_name, ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION); + + animation_map[p_name] = ad; + } + + AnimationData &animation_data = animation_map[p_name]; + + Ref<Texture2D> icon = get_theme_icon("Animation", "EditorIcons"); + + TreeItem *item = p_tree->create_item(p_parent); + item->set_text(0, p_name); + item->set_icon(0, icon); + + item->set_meta("type", "Animation"); + item->set_meta("import_id", p_name); + + item->set_selectable(0, true); + + animation_data.scene_node = item; +} + +void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { + String import_id; + + if (p_node->has_meta("import_id")) { + import_id = p_node->get_meta("import_id"); + } else { + import_id = "PATH:" + String(scene->get_path_to(p_node)); + p_node->set_meta("import_id", import_id); + } + + EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node); + + if (src_mesh_node) { + MeshInstance3D *mesh_node = memnew(MeshInstance3D); + mesh_node->set_name(src_mesh_node->get_name()); + mesh_node->set_transform(src_mesh_node->get_transform()); + mesh_node->set_skin(src_mesh_node->get_skin()); + mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path()); + if (src_mesh_node->get_mesh().is_valid()) { + Ref<EditorSceneImporterMesh> editor_mesh = src_mesh_node->get_mesh(); + mesh_node->set_mesh(editor_mesh->get_mesh()); + } + + p_node->replace_by(mesh_node); + memdelete(p_node); + p_node = mesh_node; + } + + String type = p_node->get_class(); + + if (!has_theme_icon(type, "EditorIcons")) { + type = "Node3D"; + } + + Ref<Texture2D> icon = get_theme_icon(type, "EditorIcons"); + + TreeItem *item = scene_tree->create_item(p_parent_item); + item->set_text(0, p_node->get_name()); + + if (p_node == scene) { + icon = get_theme_icon("PackedScene", "EditorIcons"); + item->set_text(0, "Scene"); + } + + item->set_icon(0, icon); + + item->set_meta("type", "Node"); + item->set_meta("class", type); + item->set_meta("import_id", import_id); + item->set_tooltip(0, vformat(TTR("Type: %s\nImport ID: %s"), type, import_id)); + + item->set_selectable(0, true); + + if (!node_map.has(import_id)) { + NodeData nd; + + if (p_node != scene) { + ResourceImporterScene::InternalImportCategory category; + if (src_mesh_node) { + category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE; + } else if (Object::cast_to<AnimationPlayer>(p_node)) { + category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE; + } else { + category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; + } + + _load_default_subresource_settings(nd.settings, "nodes", import_id, category); + } + + node_map[import_id] = nd; + } + NodeData &node_data = node_map[import_id]; + + node_data.node = p_node; + node_data.scene_node = item; + + AnimationPlayer *anim_node = Object::cast_to<AnimationPlayer>(p_node); + if (anim_node) { + List<StringName> animations; + anim_node->get_animation_list(&animations); + for (List<StringName>::Element *E = animations.front(); E; E = E->next()) { + _fill_animation(scene_tree, anim_node->get_animation(E->get()), E->get(), item); + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _fill_scene(p_node->get_child(i), item); + } + MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(p_node); + if (mesh_node && mesh_node->get_mesh().is_valid()) { + _fill_mesh(scene_tree, mesh_node->get_mesh(), item); + + Transform accum_xform; + Node3D *base = mesh_node; + while (base) { + accum_xform = base->get_transform() * accum_xform; + base = Object::cast_to<Node3D>(base->get_parent()); + } + + AABB aabb = accum_xform.xform(mesh_node->get_mesh()->get_aabb()); + if (first_aabb) { + contents_aabb = aabb; + first_aabb = false; + } else { + contents_aabb.merge_with(aabb); + } + } +} + +void SceneImportSettings::_update_scene() { + scene_tree->clear(); + material_tree->clear(); + mesh_tree->clear(); + + //hiden roots + material_tree->create_item(); + mesh_tree->create_item(); + + _fill_scene(scene, nullptr); +} + +void SceneImportSettings::_update_camera() { + AABB camera_aabb; + + float rot_x = cam_rot_x; + float rot_y = cam_rot_y; + float zoom = cam_zoom; + + if (selected_type == "Node" || selected_type == "") { + camera_aabb = contents_aabb; + } else { + if (mesh_preview->get_mesh().is_valid()) { + camera_aabb = mesh_preview->get_transform().xform(mesh_preview->get_mesh()->get_aabb()); + } else { + camera_aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)); + } + if (selected_type == "Mesh" && mesh_map.has(selected_id)) { + const MeshData &md = mesh_map[selected_id]; + rot_x = md.cam_rot_x; + rot_y = md.cam_rot_y; + zoom = md.cam_zoom; + } else if (selected_type == "Material" && material_map.has(selected_id)) { + const MaterialData &md = material_map[selected_id]; + rot_x = md.cam_rot_x; + rot_y = md.cam_rot_y; + zoom = md.cam_zoom; + } + } + + Vector3 center = camera_aabb.position + camera_aabb.size * 0.5; + float camera_size = camera_aabb.get_longest_axis_size(); + + camera->set_orthogonal(camera_size * zoom, 0.0001, camera_size * 2); + + Transform xf; + xf.basis = Basis(Vector3(0, 1, 0), rot_y) * Basis(Vector3(1, 0, 0), rot_x); + xf.origin = center; + xf.translate(0, 0, camera_size); + + camera->set_transform(xf); +} + +void SceneImportSettings::_load_default_subresource_settings(Map<StringName, Variant> &settings, const String &p_type, const String &p_import_id, ResourceImporterScene::InternalImportCategory p_category) { + if (base_subresource_settings.has(p_type)) { + Dictionary d = base_subresource_settings[p_type]; + if (d.has(p_import_id)) { + d = d[p_import_id]; + List<ResourceImporterScene::ImportOption> options; + ResourceImporterScene::get_singleton()->get_internal_import_options(p_category, &options); + for (List<ResourceImporterScene::ImportOption>::Element *E = options.front(); E; E = E->next()) { + String key = E->get().option.name; + if (d.has(key)) { + settings[key] = d[key]; + } + } + } + } +} + +void SceneImportSettings::open_settings(const String &p_path) { + if (scene) { + memdelete(scene); + scene = nullptr; + } + scene = ResourceImporterScene::get_singleton()->pre_import(p_path); + if (scene == nullptr) { + EditorNode::get_singleton()->show_warning(TTR("Error opening scene")); + return; + } + + base_path = p_path; + + material_set.clear(); + mesh_set.clear(); + material_map.clear(); + mesh_map.clear(); + node_map.clear(); + defaults.clear(); + + selected_id = ""; + selected_type = ""; + + cam_rot_x = -Math_PI / 4; + cam_rot_y = -Math_PI / 4; + cam_zoom = 1; + + { + base_subresource_settings.clear(); + + Ref<ConfigFile> config; + config.instance(); + Error err = config->load(p_path + ".import"); + if (err == OK) { + List<String> keys; + config->get_section_keys("params", &keys); + for (List<String>::Element *E = keys.front(); E; E = E->next()) { + Variant value = config->get_value("params", E->get()); + if (E->get() == "_subresources") { + base_subresource_settings = value; + } else { + defaults[E->get()] = value; + } + } + } + } + + first_aabb = true; + + _update_scene(); + + base_viewport->add_child(scene); + + if (first_aabb) { + contents_aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)); + first_aabb = false; + } + + popup_centered_ratio(); + _update_camera(); + + set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file())); +} + +SceneImportSettings *SceneImportSettings::singleton = nullptr; + +SceneImportSettings *SceneImportSettings::get_singleton() { + return singleton; +} + +void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { + selecting = true; + + if (p_type == "Node") { + node_selected->hide(); //always hide just in case + mesh_preview->hide(); + if (Object::cast_to<Node3D>(scene)) { + Object::cast_to<Node3D>(scene)->show(); + } + //NodeData &nd=node_map[p_id]; + material_tree->deselect_all(); + mesh_tree->deselect_all(); + NodeData &nd = node_map[p_id]; + + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(nd.node); + if (mi) { + Ref<Mesh> base_mesh = mi->get_mesh(); + if (base_mesh.is_valid()) { + AABB aabb = base_mesh->get_aabb(); + Transform aabb_xf; + aabb_xf.basis.scale(aabb.size); + aabb_xf.origin = aabb.position; + + aabb_xf = mi->get_global_transform() * aabb_xf; + node_selected->set_transform(aabb_xf); + node_selected->show(); + } + } + + if (nd.node == scene) { + scene_import_settings_data->settings = &defaults; + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX; + } else { + scene_import_settings_data->settings = &nd.settings; + if (mi) { + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE; + } else if (Object::cast_to<AnimationPlayer>(nd.node)) { + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE; + } else { + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; + } + } + } else if (p_type == "Animation") { + node_selected->hide(); //always hide just in case + mesh_preview->hide(); + if (Object::cast_to<Node3D>(scene)) { + Object::cast_to<Node3D>(scene)->show(); + } + //NodeData &nd=node_map[p_id]; + material_tree->deselect_all(); + mesh_tree->deselect_all(); + AnimationData &ad = animation_map[p_id]; + + scene_import_settings_data->settings = &ad.settings; + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION; + } else if (p_type == "Mesh") { + node_selected->hide(); + if (Object::cast_to<Node3D>(scene)) { + Object::cast_to<Node3D>(scene)->hide(); + } + + MeshData &md = mesh_map[p_id]; + if (p_from != mesh_tree) { + md.mesh_node->uncollapse_tree(); + md.mesh_node->select(0); + mesh_tree->ensure_cursor_is_visible(); + } + if (p_from != scene_tree) { + md.scene_node->uncollapse_tree(); + md.scene_node->select(0); + scene_tree->ensure_cursor_is_visible(); + } + + mesh_preview->set_mesh(md.mesh); + mesh_preview->show(); + + material_tree->deselect_all(); + + scene_import_settings_data->settings = &md.settings; + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH; + } else if (p_type == "Material") { + node_selected->hide(); + if (Object::cast_to<Node3D>(scene)) { + Object::cast_to<Node3D>(scene)->hide(); + } + + mesh_preview->show(); + + MaterialData &md = material_map[p_id]; + + material_preview->set_material(md.material); + mesh_preview->set_mesh(material_preview); + + if (p_from != mesh_tree) { + md.mesh_node->uncollapse_tree(); + md.mesh_node->select(0); + mesh_tree->ensure_cursor_is_visible(); + } + if (p_from != scene_tree) { + md.scene_node->uncollapse_tree(); + md.scene_node->select(0); + scene_tree->ensure_cursor_is_visible(); + } + if (p_from != material_tree) { + md.material_node->uncollapse_tree(); + md.material_node->select(0); + material_tree->ensure_cursor_is_visible(); + } + + scene_import_settings_data->settings = &md.settings; + scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MATERIAL; + } + + selected_type = p_type; + selected_id = p_id; + + selecting = false; + + _update_camera(); + + List<ResourceImporter::ImportOption> options; + + if (scene_import_settings_data->category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) { + ResourceImporterScene::get_singleton()->get_import_options(&options); + } else { + ResourceImporterScene::get_singleton()->get_internal_import_options(scene_import_settings_data->category, &options); + } + + scene_import_settings_data->defaults.clear(); + scene_import_settings_data->current.clear(); + + for (List<ResourceImporter::ImportOption>::Element *E = options.front(); E; E = E->next()) { + scene_import_settings_data->defaults[E->get().option.name] = E->get().default_value; + //needed for visibility toggling (fails if something is missing) + if (scene_import_settings_data->settings->has(E->get().option.name)) { + scene_import_settings_data->current[E->get().option.name] = (*scene_import_settings_data->settings)[E->get().option.name]; + } else { + scene_import_settings_data->current[E->get().option.name] = E->get().default_value; + } + } + scene_import_settings_data->options = options; + inspector->edit(scene_import_settings_data); + scene_import_settings_data->notify_property_list_changed(); +} + +void SceneImportSettings::_material_tree_selected() { + if (selecting) { + return; + } + TreeItem *item = material_tree->get_selected(); + String type = item->get_meta("type"); + String import_id = item->get_meta("import_id"); + + _select(material_tree, type, import_id); +} +void SceneImportSettings::_mesh_tree_selected() { + if (selecting) { + return; + } + + TreeItem *item = mesh_tree->get_selected(); + String type = item->get_meta("type"); + String import_id = item->get_meta("import_id"); + + _select(mesh_tree, type, import_id); +} +void SceneImportSettings::_scene_tree_selected() { + if (selecting) { + return; + } + TreeItem *item = scene_tree->get_selected(); + String type = item->get_meta("type"); + String import_id = item->get_meta("import_id"); + + _select(scene_tree, type, import_id); +} + +void SceneImportSettings::_viewport_input(const Ref<InputEvent> &p_input) { + float *rot_x = &cam_rot_x; + float *rot_y = &cam_rot_y; + float *zoom = &cam_zoom; + + if (selected_type == "Mesh" && mesh_map.has(selected_id)) { + MeshData &md = mesh_map[selected_id]; + rot_x = &md.cam_rot_x; + rot_y = &md.cam_rot_y; + zoom = &md.cam_zoom; + } else if (selected_type == "Material" && material_map.has(selected_id)) { + MaterialData &md = material_map[selected_id]; + rot_x = &md.cam_rot_x; + rot_y = &md.cam_rot_y; + zoom = &md.cam_zoom; + } + Ref<InputEventMouseMotion> mm = p_input; + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + (*rot_x) -= mm->get_relative().y * 0.01 * EDSCALE; + (*rot_y) -= mm->get_relative().x * 0.01 * EDSCALE; + (*rot_x) = CLAMP((*rot_x), -Math_PI / 2, Math_PI / 2); + _update_camera(); + } + Ref<InputEventMouseButton> mb = p_input; + if (mb.is_valid() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + (*zoom) *= 1.1; + if ((*zoom) > 10.0) { + (*zoom) = 10.0; + } + _update_camera(); + } + if (mb.is_valid() && mb->get_button_index() == BUTTON_WHEEL_UP) { + (*zoom) /= 1.1; + if ((*zoom) < 0.1) { + (*zoom) = 0.1; + } + _update_camera(); + } +} + +void SceneImportSettings::_re_import() { + Map<StringName, Variant> main_settings; + + main_settings = defaults; + main_settings.erase("_subresources"); + Dictionary nodes; + Dictionary materials; + Dictionary meshes; + Dictionary animations; + + Dictionary subresources; + + for (Map<String, NodeData>::Element *E = node_map.front(); E; E = E->next()) { + if (E->get().settings.size()) { + Dictionary d; + for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { + d[String(F->key())] = F->get(); + } + nodes[E->key()] = d; + } + } + if (nodes.size()) { + subresources["nodes"] = nodes; + } + + for (Map<String, MaterialData>::Element *E = material_map.front(); E; E = E->next()) { + if (E->get().settings.size()) { + Dictionary d; + for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { + d[String(F->key())] = F->get(); + } + materials[E->key()] = d; + } + } + if (materials.size()) { + subresources["materials"] = materials; + } + + for (Map<String, MeshData>::Element *E = mesh_map.front(); E; E = E->next()) { + if (E->get().settings.size()) { + Dictionary d; + for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { + d[String(F->key())] = F->get(); + } + meshes[E->key()] = d; + } + } + if (meshes.size()) { + subresources["meshes"] = meshes; + } + + for (Map<String, AnimationData>::Element *E = animation_map.front(); E; E = E->next()) { + if (E->get().settings.size()) { + Dictionary d; + for (Map<StringName, Variant>::Element *F = E->get().settings.front(); F; F = F->next()) { + d[String(F->key())] = F->get(); + } + animations[E->key()] = d; + } + } + if (animations.size()) { + subresources["animations"] = animations; + } + + if (subresources.size()) { + main_settings["_subresources"] = subresources; + } + + EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, "scene", main_settings); +} + +void SceneImportSettings::_notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + connect("confirmed", callable_mp(this, &SceneImportSettings::_re_import)); + } +} + +void SceneImportSettings::_menu_callback(int p_id) { + switch (p_id) { + case ACTION_EXTRACT_MATERIALS: { + save_path->set_text(TTR("Select folder to extract material resources")); + external_extension_type->select(0); + } break; + case ACTION_CHOOSE_MESH_SAVE_PATHS: { + save_path->set_text(TTR("Select folder where mesh resources will save on import")); + external_extension_type->select(1); + } break; + case ACTION_CHOOSE_ANIMATION_SAVE_PATHS: { + save_path->set_text(TTR("Select folder where animations will save on import")); + external_extension_type->select(1); + } break; + } + + save_path->set_current_dir(base_path.get_base_dir()); + current_action = p_id; + save_path->popup_centered_ratio(); +} + +void SceneImportSettings::_save_path_changed(const String &p_path) { + save_path_item->set_text(1, p_path); + + if (FileAccess::exists(p_path)) { + save_path_item->set_text(2, "Warning: File exists"); + save_path_item->set_tooltip(2, TTR("Existing file with the same name will be replaced.")); + save_path_item->set_icon(2, get_theme_icon("StatusWarning", "EditorIcons")); + + } else { + save_path_item->set_text(2, "Will create new File"); + save_path_item->set_icon(2, get_theme_icon("StatusSuccess", "EditorIcons")); + } +} + +void SceneImportSettings::_browse_save_callback(Object *p_item, int p_column, int p_id) { + TreeItem *item = Object::cast_to<TreeItem>(p_item); + + String path = item->get_text(1); + + item_save_path->set_current_file(path); + save_path_item = item; + + item_save_path->popup_centered_ratio(); +} + +void SceneImportSettings::_save_dir_callback(const String &p_path) { + external_path_tree->clear(); + TreeItem *root = external_path_tree->create_item(); + save_path_items.clear(); + + switch (current_action) { + case ACTION_EXTRACT_MATERIALS: { + for (Map<String, MaterialData>::Element *E = material_map.front(); E; E = E->next()) { + MaterialData &md = material_map[E->key()]; + + TreeItem *item = external_path_tree->create_item(root); + + String name = md.material_node->get_text(0); + + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_icon(0, get_theme_icon("StandardMaterial3D", "EditorIcons")); + item->set_text(0, name); + + if (md.has_import_id) { + if (md.settings.has("use_external/enabled") && bool(md.settings["use_external/enabled"])) { + item->set_text(2, "Already External"); + item->set_tooltip(2, TTR("This material already references an external file, no action will be taken.\nDisable the external property for it to be extracted again.")); + } else { + item->set_metadata(0, E->key()); + item->set_editable(0, true); + item->set_checked(0, true); + String path = p_path.plus_file(name); + if (external_extension_type->get_selected() == 0) { + path += ".tres"; + } else { + path += ".res"; + } + + item->set_text(1, path); + if (FileAccess::exists(path)) { + item->set_text(2, "Warning: File exists"); + item->set_tooltip(2, TTR("Existing file with the same name will be replaced.")); + item->set_icon(2, get_theme_icon("StatusWarning", "EditorIcons")); + + } else { + item->set_text(2, "Will create new File"); + item->set_icon(2, get_theme_icon("StatusSuccess", "EditorIcons")); + } + + item->add_button(1, get_theme_icon("Folder", "EditorIcons")); + } + + } else { + item->set_text(2, "No import ID"); + item->set_tooltip(2, TTR("Material has no name nor any other way to identify on re-import.\nPlease name it or ensure it is exported with an unique ID.")); + item->set_icon(2, get_theme_icon("StatusError", "EditorIcons")); + } + + save_path_items.push_back(item); + } + + external_paths->set_title(TTR("Extract Materials to Resource Files")); + external_paths->get_ok_button()->set_text(TTR("Extract")); + } break; + case ACTION_CHOOSE_MESH_SAVE_PATHS: { + for (Map<String, MeshData>::Element *E = mesh_map.front(); E; E = E->next()) { + MeshData &md = mesh_map[E->key()]; + + TreeItem *item = external_path_tree->create_item(root); + + String name = md.mesh_node->get_text(0); + + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_icon(0, get_theme_icon("Mesh", "EditorIcons")); + item->set_text(0, name); + + if (md.has_import_id) { + if (md.settings.has("save_to_file/enabled") && bool(md.settings["save_to_file/enabled"])) { + item->set_text(2, "Already Saving"); + item->set_tooltip(2, TTR("This mesh already saves to an external resource, no action will be taken.")); + } else { + item->set_metadata(0, E->key()); + item->set_editable(0, true); + item->set_checked(0, true); + String path = p_path.plus_file(name); + if (external_extension_type->get_selected() == 0) { + path += ".tres"; + } else { + path += ".res"; + } + + item->set_text(1, path); + if (FileAccess::exists(path)) { + item->set_text(2, "Warning: File exists"); + item->set_tooltip(2, TTR("Existing file with the same name will be replaced on import.")); + item->set_icon(2, get_theme_icon("StatusWarning", "EditorIcons")); + + } else { + item->set_text(2, "Will save to new File"); + item->set_icon(2, get_theme_icon("StatusSuccess", "EditorIcons")); + } + + item->add_button(1, get_theme_icon("Folder", "EditorIcons")); + } + + } else { + item->set_text(2, "No import ID"); + item->set_tooltip(2, TTR("Mesh has no name nor any other way to identify on re-import.\nPlease name it or ensure it is exported with an unique ID.")); + item->set_icon(2, get_theme_icon("StatusError", "EditorIcons")); + } + + save_path_items.push_back(item); + } + + external_paths->set_title(TTR("Set paths to save meshes as resource files on Reimport")); + external_paths->get_ok_button()->set_text(TTR("Set Paths")); + } break; + case ACTION_CHOOSE_ANIMATION_SAVE_PATHS: { + for (Map<String, AnimationData>::Element *E = animation_map.front(); E; E = E->next()) { + AnimationData &ad = animation_map[E->key()]; + + TreeItem *item = external_path_tree->create_item(root); + + String name = ad.scene_node->get_text(0); + + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_icon(0, get_theme_icon("Animation", "EditorIcons")); + item->set_text(0, name); + + if (ad.settings.has("save_to_file/enabled") && bool(ad.settings["save_to_file/enabled"])) { + item->set_text(2, "Already Saving"); + item->set_tooltip(2, TTR("This animation already saves to an external resource, no action will be taken.")); + } else { + item->set_metadata(0, E->key()); + item->set_editable(0, true); + item->set_checked(0, true); + String path = p_path.plus_file(name); + if (external_extension_type->get_selected() == 0) { + path += ".tres"; + } else { + path += ".res"; + } + + item->set_text(1, path); + if (FileAccess::exists(path)) { + item->set_text(2, "Warning: File exists"); + item->set_tooltip(2, TTR("Existing file with the same name will be replaced on import.")); + item->set_icon(2, get_theme_icon("StatusWarning", "EditorIcons")); + + } else { + item->set_text(2, "Will save to new File"); + item->set_icon(2, get_theme_icon("StatusSuccess", "EditorIcons")); + } + + item->add_button(1, get_theme_icon("Folder", "EditorIcons")); + } + + save_path_items.push_back(item); + } + + external_paths->set_title(TTR("Set paths to save animations as resource files on Reimport")); + external_paths->get_ok_button()->set_text(TTR("Set Paths")); + + } break; + } + + external_paths->popup_centered_ratio(); +} + +void SceneImportSettings::_save_dir_confirm() { + for (int i = 0; i < save_path_items.size(); i++) { + TreeItem *item = save_path_items[i]; + if (!item->is_checked(0)) { + continue; //ignore + } + String path = item->get_text(1); + if (!path.is_resource_file()) { + continue; + } + + String id = item->get_metadata(0); + + switch (current_action) { + case ACTION_EXTRACT_MATERIALS: { + ERR_CONTINUE(!material_map.has(id)); + MaterialData &md = material_map[id]; + + Error err = ResourceSaver::save(path, md.material); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Can't make material external to file, write error:") + "\n\t" + path); + continue; + } + + md.settings["use_external/enabled"] = true; + md.settings["use_external/path"] = path; + + } break; + case ACTION_CHOOSE_MESH_SAVE_PATHS: { + ERR_CONTINUE(!mesh_map.has(id)); + MeshData &md = mesh_map[id]; + + md.settings["save_to_file/enabled"] = true; + md.settings["save_to_file/path"] = path; + } break; + case ACTION_CHOOSE_ANIMATION_SAVE_PATHS: { + ERR_CONTINUE(!animation_map.has(id)); + AnimationData &ad = animation_map[id]; + + ad.settings["save_to_file/enabled"] = true; + ad.settings["save_to_file/path"] = path; + + } break; + } + } + + if (current_action == ACTION_EXTRACT_MATERIALS) { + //as this happens right now, the scene needs to be saved and reimported. + _re_import(); + open_settings(base_path); + } else { + scene_import_settings_data->notify_property_list_changed(); + } +} + +SceneImportSettings::SceneImportSettings() { + singleton = this; + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + HBoxContainer *menu_hb = memnew(HBoxContainer); + main_vb->add_child(menu_hb); + + action_menu = memnew(MenuButton); + action_menu->set_text(TTR("Actions...")); + menu_hb->add_child(action_menu); + + action_menu->get_popup()->add_item(TTR("Extract Materials"), ACTION_EXTRACT_MATERIALS); + action_menu->get_popup()->add_separator(); + action_menu->get_popup()->add_item(TTR("Set Animation Save Paths"), ACTION_CHOOSE_ANIMATION_SAVE_PATHS); + action_menu->get_popup()->add_item(TTR("Set Mesh Save Paths"), ACTION_CHOOSE_MESH_SAVE_PATHS); + + action_menu->get_popup()->connect("id_pressed", callable_mp(this, &SceneImportSettings::_menu_callback)); + + tree_split = memnew(HSplitContainer); + main_vb->add_child(tree_split); + tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + data_mode = memnew(TabContainer); + tree_split->add_child(data_mode); + data_mode->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); + + property_split = memnew(HSplitContainer); + tree_split->add_child(property_split); + property_split->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + scene_tree = memnew(Tree); + scene_tree->set_name(TTR("Scene")); + data_mode->add_child(scene_tree); + scene_tree->connect("cell_selected", callable_mp(this, &SceneImportSettings::_scene_tree_selected)); + + mesh_tree = memnew(Tree); + mesh_tree->set_name(TTR("Meshes")); + data_mode->add_child(mesh_tree); + mesh_tree->set_hide_root(true); + mesh_tree->connect("cell_selected", callable_mp(this, &SceneImportSettings::_mesh_tree_selected)); + + material_tree = memnew(Tree); + material_tree->set_name(TTR("Materials")); + data_mode->add_child(material_tree); + material_tree->connect("cell_selected", callable_mp(this, &SceneImportSettings::_material_tree_selected)); + + material_tree->set_hide_root(true); + + SubViewportContainer *vp_container = memnew(SubViewportContainer); + vp_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vp_container->set_custom_minimum_size(Size2(10, 10)); + vp_container->set_stretch(true); + vp_container->connect("gui_input", callable_mp(this, &SceneImportSettings::_viewport_input)); + property_split->add_child(vp_container); + + base_viewport = memnew(SubViewport); + vp_container->add_child(base_viewport); + + base_viewport->set_use_own_world_3d(true); + + camera = memnew(Camera3D); + base_viewport->add_child(camera); + camera->make_current(); + + light = memnew(DirectionalLight3D); + light->set_transform(Transform().looking_at(Vector3(-1, -2, -0.6), Vector3(0, 1, 0))); + base_viewport->add_child(light); + light->set_shadow(true); + + { + Ref<StandardMaterial3D> selection_mat; + selection_mat.instance(); + selection_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_mat->set_albedo(Color(1, 0.8, 1.0)); + + Ref<SurfaceTool> st; + st.instance(); + st->begin(Mesh::PRIMITIVE_LINES); + + AABB base_aabb; + base_aabb.size = Vector3(1, 1, 1); + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + base_aabb.get_edge(i, a, b); + + st->add_vertex(a); + st->add_vertex(a.lerp(b, 0.2)); + st->add_vertex(b); + st->add_vertex(b.lerp(a, 0.2)); + } + + selection_mesh.instance(); + st->commit(selection_mesh); + selection_mesh->surface_set_material(0, selection_mat); + + node_selected = memnew(MeshInstance3D); + node_selected->set_mesh(selection_mesh); + base_viewport->add_child(node_selected); + node_selected->hide(); + } + + { + mesh_preview = memnew(MeshInstance3D); + base_viewport->add_child(mesh_preview); + mesh_preview->hide(); + + material_preview.instance(); + } + + inspector = memnew(EditorInspector); + inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); + + property_split->add_child(inspector); + + scene_import_settings_data = memnew(SceneImportSettingsData); + + get_ok_button()->set_text(TTR("Reimport")); + get_cancel_button()->set_text(TTR("Close")); + + external_paths = memnew(ConfirmationDialog); + add_child(external_paths); + external_path_tree = memnew(Tree); + external_paths->add_child(external_path_tree); + external_path_tree->connect("button_pressed", callable_mp(this, &SceneImportSettings::_browse_save_callback)); + external_paths->connect("confirmed", callable_mp(this, &SceneImportSettings::_save_dir_confirm)); + external_path_tree->set_columns(3); + external_path_tree->set_column_titles_visible(true); + external_path_tree->set_column_expand(0, true); + external_path_tree->set_column_min_width(0, 100 * EDSCALE); + external_path_tree->set_column_title(0, TTR("Resource")); + external_path_tree->set_column_expand(1, true); + external_path_tree->set_column_min_width(1, 100 * EDSCALE); + external_path_tree->set_column_title(1, TTR("Path")); + external_path_tree->set_column_expand(2, false); + external_path_tree->set_column_min_width(2, 200 * EDSCALE); + external_path_tree->set_column_title(2, TTR("Status")); + save_path = memnew(EditorFileDialog); + save_path->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); + HBoxContainer *extension_hb = memnew(HBoxContainer); + save_path->get_vbox()->add_child(extension_hb); + extension_hb->add_spacer(); + extension_hb->add_child(memnew(Label(TTR("Save Extension: ")))); + external_extension_type = memnew(OptionButton); + extension_hb->add_child(external_extension_type); + external_extension_type->add_item(TTR("Text: *.tres")); + external_extension_type->add_item(TTR("Binary: *.res")); + external_path_tree->set_hide_root(true); + add_child(save_path); + + item_save_path = memnew(EditorFileDialog); + item_save_path->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + item_save_path->add_filter("*.tres;Text Resource"); + item_save_path->add_filter("*.res;Binary Resource"); + add_child(item_save_path); + item_save_path->connect("file_selected", callable_mp(this, &SceneImportSettings::_save_path_changed)); + + save_path->connect("dir_selected", callable_mp(this, &SceneImportSettings::_save_dir_callback)); +} + +SceneImportSettings::~SceneImportSettings() { + memdelete(scene_import_settings_data); +} diff --git a/editor/import/scene_import_settings.h b/editor/import/scene_import_settings.h new file mode 100644 index 0000000000..ddcf4a6d5d --- /dev/null +++ b/editor/import/scene_import_settings.h @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* scene_import_settings.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 SCENEIMPORTSETTINGS_H +#define SCENEIMPORTSETTINGS_H + +#include "editor/editor_file_dialog.h" +#include "editor/editor_inspector.h" +#include "editor/import/resource_importer_scene.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/light_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" +#include "scene/gui/split_container.h" +#include "scene/gui/subviewport_container.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/tree.h" +#include "scene/resources/primitive_meshes.h" + +class SceneImportSettingsData; + +class SceneImportSettings : public ConfirmationDialog { + GDCLASS(SceneImportSettings, ConfirmationDialog) + + static SceneImportSettings *singleton; + + enum Actions { + ACTION_EXTRACT_MATERIALS, + ACTION_CHOOSE_MESH_SAVE_PATHS, + ACTION_CHOOSE_ANIMATION_SAVE_PATHS, + }; + + Node *scene = nullptr; + + HSplitContainer *tree_split; + HSplitContainer *property_split; + TabContainer *data_mode; + Tree *scene_tree; + Tree *mesh_tree; + Tree *material_tree; + + EditorInspector *inspector; + + SubViewport *base_viewport; + + Camera3D *camera; + bool first_aabb = false; + AABB contents_aabb; + + DirectionalLight3D *light; + Ref<ArrayMesh> selection_mesh; + MeshInstance3D *node_selected; + + MeshInstance3D *mesh_preview; + Ref<SphereMesh> material_preview; + + float cam_rot_x; + float cam_rot_y; + float cam_zoom; + + void _update_scene(); + + struct MaterialData { + bool has_import_id; + Ref<Material> material; + TreeItem *scene_node; + TreeItem *mesh_node; + TreeItem *material_node; + + float cam_rot_x = -Math_PI / 4; + float cam_rot_y = -Math_PI / 4; + float cam_zoom = 1; + + Map<StringName, Variant> settings; + }; + Map<String, MaterialData> material_map; + + struct MeshData { + bool has_import_id; + Ref<Mesh> mesh; + TreeItem *scene_node; + TreeItem *mesh_node; + + float cam_rot_x = -Math_PI / 4; + float cam_rot_y = -Math_PI / 4; + float cam_zoom = 1; + Map<StringName, Variant> settings; + }; + Map<String, MeshData> mesh_map; + + struct AnimationData { + Ref<Animation> animation; + TreeItem *scene_node; + Map<StringName, Variant> settings; + }; + Map<String, AnimationData> animation_map; + + struct NodeData { + Node *node; + TreeItem *scene_node; + Map<StringName, Variant> settings; + }; + Map<String, NodeData> node_map; + + void _fill_material(Tree *p_tree, const Ref<Material> &p_material, TreeItem *p_parent); + void _fill_mesh(Tree *p_tree, const Ref<Mesh> &p_mesh, TreeItem *p_parent); + void _fill_animation(Tree *p_tree, const Ref<Animation> &p_anim, const String &p_name, TreeItem *p_parent); + void _fill_scene(Node *p_node, TreeItem *p_parent_item); + + Set<Ref<Mesh>> mesh_set; + Set<Ref<Material>> material_set; + + String selected_type; + String selected_id; + + bool selecting = false; + + void _update_camera(); + void _select(Tree *p_from, String p_type, String p_id); + void _material_tree_selected(); + void _mesh_tree_selected(); + void _scene_tree_selected(); + + void _viewport_input(const Ref<InputEvent> &p_input); + + Map<StringName, Variant> defaults; + + SceneImportSettingsData *scene_import_settings_data; + + void _re_import(); + + String base_path; + + MenuButton *action_menu; + + ConfirmationDialog *external_paths; + Tree *external_path_tree; + EditorFileDialog *save_path; + OptionButton *external_extension_type; + + EditorFileDialog *item_save_path; + + void _menu_callback(int p_id); + void _save_dir_callback(const String &p_path); + + int current_action; + + Vector<TreeItem *> save_path_items; + + TreeItem *save_path_item = nullptr; + void _save_path_changed(const String &p_path); + void _browse_save_callback(Object *p_item, int p_column, int p_id); + void _save_dir_confirm(); + + Dictionary base_subresource_settings; + + void _load_default_subresource_settings(Map<StringName, Variant> &settings, const String &p_type, const String &p_import_id, ResourceImporterScene::InternalImportCategory p_category); + +protected: + void _notification(int p_what); + +public: + void open_settings(const String &p_path); + static SceneImportSettings *get_singleton(); + SceneImportSettings(); + ~SceneImportSettings(); +}; + +#endif // SCENEIMPORTSETTINGS_H diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 46eb4e4fdc..28fdd4ddbd 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -136,6 +136,11 @@ Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const return surfaces[p_surface].material; } +void EditorSceneImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_material) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + surfaces.write[p_surface].material = p_material; +} + void EditorSceneImporterMesh::generate_lods() { if (!SurfaceTool::simplify_func) { return; @@ -219,11 +224,20 @@ bool EditorSceneImporterMesh::has_mesh() const { return mesh.is_valid(); } -Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh() { +Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<Mesh> &p_base) { ERR_FAIL_COND_V(surfaces.size() == 0, Ref<ArrayMesh>()); if (mesh.is_null()) { - mesh.instance(); + if (p_base.is_valid()) { + mesh = p_base; + } + if (mesh.is_null()) { + mesh.instance(); + } + mesh->set_name(get_name()); + if (has_meta("import_id")) { + mesh->set_meta("import_id", get_meta("import_id")); + } for (int i = 0; i < blend_shapes.size(); i++) { mesh->add_blend_shape(blend_shapes[i]); } @@ -251,6 +265,8 @@ Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh() { } } + mesh->set_lightmap_size_hint(lightmap_size_hint); + if (shadow_mesh.is_valid()) { Ref<ArrayMesh> shadow = shadow_mesh->get_mesh(); mesh->set_shadow_mesh(shadow); @@ -436,6 +452,338 @@ Dictionary EditorSceneImporterMesh::_get_data() const { return data; } +Vector<Face3> EditorSceneImporterMesh::get_faces() const { + Vector<Face3> faces; + for (int i = 0; i < surfaces.size(); i++) { + if (surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { + Vector<Vector3> vertices = surfaces[i].arrays[Mesh::ARRAY_VERTEX]; + Vector<int> indices = surfaces[i].arrays[Mesh::ARRAY_INDEX]; + if (indices.size()) { + for (int j = 0; j < indices.size(); j += 3) { + Face3 f; + f.vertex[0] = vertices[indices[j + 0]]; + f.vertex[1] = vertices[indices[j + 1]]; + f.vertex[2] = vertices[indices[j + 2]]; + faces.push_back(f); + } + } else { + for (int j = 0; j < vertices.size(); j += 3) { + Face3 f; + f.vertex[0] = vertices[j + 0]; + f.vertex[1] = vertices[j + 1]; + f.vertex[2] = vertices[j + 2]; + faces.push_back(f); + } + } + } + } + + return faces; +} + +Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose() const { + ERR_FAIL_COND_V(!Mesh::convex_composition_function, Vector<Ref<Shape3D>>()); + + const Vector<Face3> faces = get_faces(); + + Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces); + + Vector<Ref<Shape3D>> ret; + + for (int i = 0; i < decomposed.size(); i++) { + Set<Vector3> points; + for (int j = 0; j < decomposed[i].size(); j++) { + points.insert(decomposed[i][j].vertex[0]); + points.insert(decomposed[i][j].vertex[1]); + points.insert(decomposed[i][j].vertex[2]); + } + + Vector<Vector3> convex_points; + convex_points.resize(points.size()); + { + Vector3 *w = convex_points.ptrw(); + int idx = 0; + for (Set<Vector3>::Element *E = points.front(); E; E = E->next()) { + w[idx++] = E->get(); + } + } + + Ref<ConvexPolygonShape3D> shape; + shape.instance(); + shape->set_points(convex_points); + ret.push_back(shape); + } + + return ret; +} + +Ref<Shape3D> EditorSceneImporterMesh::create_trimesh_shape() const { + Vector<Face3> faces = get_faces(); + if (faces.size() == 0) { + return Ref<Shape3D>(); + } + + Vector<Vector3> face_points; + face_points.resize(faces.size() * 3); + + for (int i = 0; i < face_points.size(); i += 3) { + Face3 f = faces.get(i / 3); + face_points.set(i, f.vertex[0]); + face_points.set(i + 1, f.vertex[1]); + face_points.set(i + 2, f.vertex[2]); + } + + Ref<ConcavePolygonShape3D> shape = memnew(ConcavePolygonShape3D); + shape->set_faces(face_points); + return shape; +} + +Ref<NavigationMesh> EditorSceneImporterMesh::create_navigation_mesh() { + Vector<Face3> faces = get_faces(); + if (faces.size() == 0) { + return Ref<NavigationMesh>(); + } + + Map<Vector3, int> unique_vertices; + LocalVector<int> face_indices; + + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < 3; j++) { + Vector3 v = faces[i].vertex[j]; + int idx; + if (unique_vertices.has(v)) { + idx = unique_vertices[v]; + } else { + idx = unique_vertices.size(); + unique_vertices[v] = idx; + } + face_indices.push_back(idx); + } + } + + Vector<Vector3> vertices; + vertices.resize(unique_vertices.size()); + for (Map<Vector3, int>::Element *E = unique_vertices.front(); E; E = E->next()) { + vertices.write[E->get()] = E->key(); + } + + Ref<NavigationMesh> nm; + nm.instance(); + nm->set_vertices(vertices); + + Vector<int> v3; + v3.resize(3); + for (uint32_t i = 0; i < face_indices.size(); i += 3) { + v3.write[0] = face_indices[i + 0]; + v3.write[1] = face_indices[i + 1]; + v3.write[2] = face_indices[i + 2]; + nm->add_polygon(v3); + } + + return nm; +} + +extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache); + +struct EditorSceneImporterMeshLightmapSurface { + Ref<Material> material; + LocalVector<SurfaceTool::Vertex> vertices; + Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX; + uint32_t format = 0; + String name; +}; + +Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size) { + ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); + + Vector<float> vertices; + Vector<float> normals; + Vector<int> indices; + Vector<float> uv; + Vector<Pair<int, int>> uv_indices; + + Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces; + + // Keep only the scale + Transform transform = p_base_transform; + transform.origin = Vector3(); + transform.looking_at(Vector3(1, 0, 0), Vector3(0, 1, 0)); + + Basis normal_basis = transform.basis.inverse().transposed(); + + for (int i = 0; i < get_surface_count(); i++) { + EditorSceneImporterMeshLightmapSurface s; + s.primitive = get_surface_primitive_type(i); + + ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); + Array arrays = get_surface_arrays(i); + s.material = get_surface_material(i); + s.name = get_surface_name(i); + + SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + + Vector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX]; + int vc = rvertices.size(); + const Vector3 *r = rvertices.ptr(); + + Vector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL]; + + ERR_FAIL_COND_V_MSG(rnormals.size() == 0, ERR_UNAVAILABLE, "Normals are required for lightmap unwrap."); + + const Vector3 *rn = rnormals.ptr(); + + int vertex_ofs = vertices.size() / 3; + + vertices.resize((vertex_ofs + vc) * 3); + normals.resize((vertex_ofs + vc) * 3); + uv_indices.resize(vertex_ofs + vc); + + for (int j = 0; j < vc; j++) { + Vector3 v = transform.xform(r[j]); + Vector3 n = normal_basis.xform(rn[j]).normalized(); + + vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; + vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; + vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; + normals.write[(j + vertex_ofs) * 3 + 0] = n.x; + normals.write[(j + vertex_ofs) * 3 + 1] = n.y; + normals.write[(j + vertex_ofs) * 3 + 2] = n.z; + uv_indices.write[j + vertex_ofs] = Pair<int, int>(i, j); + } + + Vector<int> rindices = arrays[Mesh::ARRAY_INDEX]; + int ic = rindices.size(); + + if (ic == 0) { + for (int j = 0; j < vc / 3; j++) { + if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) { + continue; + } + + indices.push_back(vertex_ofs + j * 3 + 0); + indices.push_back(vertex_ofs + j * 3 + 1); + indices.push_back(vertex_ofs + j * 3 + 2); + } + + } else { + const int *ri = rindices.ptr(); + + for (int j = 0; j < ic / 3; j++) { + if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) { + continue; + } + indices.push_back(vertex_ofs + ri[j * 3 + 0]); + indices.push_back(vertex_ofs + ri[j * 3 + 1]); + indices.push_back(vertex_ofs + ri[j * 3 + 2]); + } + } + + lightmap_surfaces.push_back(s); + } + + //unwrap + + float *gen_uvs; + int *gen_vertices; + int *gen_indices; + int gen_vertex_count; + int gen_index_count; + int size_x; + int size_y; + + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y, r_cache_data, r_cache_size, r_used_cache); + + if (!ok) { + return ERR_CANT_CREATE; + } + + //remove surfaces + clear(); + + //create surfacetools for each surface.. + Vector<Ref<SurfaceTool>> surfaces_tools; + + for (int i = 0; i < lightmap_surfaces.size(); i++) { + Ref<SurfaceTool> st; + st.instance(); + st->begin(Mesh::PRIMITIVE_TRIANGLES); + st->set_material(lightmap_surfaces[i].material); + st->set_meta("name", lightmap_surfaces[i].name); + surfaces_tools.push_back(st); //stay there + } + + print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); + //go through all indices + for (int i = 0; i < gen_index_count; i += 3) { + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_indices.size(), ERR_BUG); + + ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); + + int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; + + for (int j = 0; j < 3; j++) { + SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; + + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) { + surfaces_tools.write[surface]->set_color(v.color); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) { + surfaces_tools.write[surface]->set_uv(v.uv); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) { + surfaces_tools.write[surface]->set_normal(v.normal); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) { + Plane t; + t.normal = v.tangent; + t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; + surfaces_tools.write[surface]->set_tangent(t); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) { + surfaces_tools.write[surface]->set_bones(v.bones); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) { + surfaces_tools.write[surface]->set_weights(v.weights); + } + + Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); + surfaces_tools.write[surface]->set_uv2(uv2); + + surfaces_tools.write[surface]->add_vertex(v.vertex); + } + } + + //generate surfaces + + for (int i = 0; i < surfaces_tools.size(); i++) { + surfaces_tools.write[i]->index(); + Array arrays = surfaces_tools.write[i]->commit_to_arrays(); + add_surface(surfaces_tools.write[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools.write[i]->get_material(), surfaces_tools.write[i]->get_meta("name")); + } + + set_lightmap_size_hint(Size2(size_x, size_y)); + + if (!r_used_cache) { + //free stuff + ::free(gen_vertices); + ::free(gen_indices); + ::free(gen_uvs); + } + + return OK; +} + +void EditorSceneImporterMesh::set_lightmap_size_hint(const Size2i &p_size) { + lightmap_size_hint = p_size; +} + +Size2i EditorSceneImporterMesh::get_lightmap_size_hint() const { + return lightmap_size_hint; +} + void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &EditorSceneImporterMesh::add_blend_shape); ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &EditorSceneImporterMesh::get_blend_shape_count); @@ -462,5 +810,8 @@ void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_data", "data"), &EditorSceneImporterMesh::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &EditorSceneImporterMesh::_get_data); + ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &EditorSceneImporterMesh::set_lightmap_size_hint); + ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &EditorSceneImporterMesh::get_lightmap_size_hint); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data"); } diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index 42507cbe8c..3326fab55d 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -32,7 +32,10 @@ #define EDITOR_SCENE_IMPORTER_MESH_H #include "core/io/resource.h" +#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/mesh.h" +#include "scene/resources/navigation_mesh.h" // The following classes are used by importers instead of ArrayMesh and MeshInstance3D // so the data is not registered (hence, quality loss), importing happens faster and // its easier to modify before saving @@ -63,6 +66,8 @@ class EditorSceneImporterMesh : public Resource { Ref<EditorSceneImporterMesh> shadow_mesh; + Size2i lightmap_size_hint; + protected: void _set_data(const Dictionary &p_data); Dictionary _get_data() const; @@ -89,13 +94,24 @@ public: float get_surface_lod_size(int p_surface, int p_lod) const; Ref<Material> get_surface_material(int p_surface) const; + void set_surface_material(int p_surface, const Ref<Material> &p_material); + void generate_lods(); void create_shadow_mesh(); Ref<EditorSceneImporterMesh> get_shadow_mesh() const; + Vector<Face3> get_faces() const; + Vector<Ref<Shape3D>> convex_decompose() const; + Ref<Shape3D> create_trimesh_shape() const; + Ref<NavigationMesh> create_navigation_mesh(); + Error lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size); + + void set_lightmap_size_hint(const Size2i &p_size); + Size2i get_lightmap_size_hint() const; + bool has_mesh() const; - Ref<ArrayMesh> get_mesh(); + Ref<ArrayMesh> get_mesh(const Ref<Mesh> &p_base = Ref<Mesh>()); void clear(); }; #endif // EDITOR_SCENE_IMPORTER_MESH_H diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 97a04e6557..ddc363113c 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -98,11 +98,9 @@ void ImportDock::set_edit_path(const String &p_path) { return; } - params->importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(config->get_value("remap", "importer")); - if (params->importer.is_null()) { - clear(); - return; - } + String importer_name = config->get_value("remap", "importer"); + + params->importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); params->paths.clear(); params->paths.push_back(p_path); @@ -124,11 +122,18 @@ void ImportDock::set_edit_path(const String &p_path) { for (List<Pair<String, String>>::Element *E = importer_names.front(); E; E = E->next()) { import_as->add_item(E->get().first); import_as->set_item_metadata(import_as->get_item_count() - 1, E->get().second); - if (E->get().second == params->importer->get_importer_name()) { + if (E->get().second == importer_name) { import_as->select(import_as->get_item_count() - 1); } } + import_as->add_separator(); + import_as->add_item(TTR("Keep File (No Import)")); + import_as->set_item_metadata(import_as->get_item_count() - 1, "keep"); + if (importer_name == "keep") { + import_as->select(import_as->get_item_count() - 1); + } + import->set_disabled(false); import_as->set_disabled(false); preset->set_disabled(false); @@ -138,7 +143,10 @@ void ImportDock::set_edit_path(const String &p_path) { void ImportDock::_update_options(const Ref<ConfigFile> &p_config) { List<ResourceImporter::ImportOption> options; - params->importer->get_import_options(&options); + + if (params->importer.is_valid()) { + params->importer->get_import_options(&options); + } params->properties.clear(); params->values.clear(); @@ -156,6 +164,14 @@ void ImportDock::_update_options(const Ref<ConfigFile> &p_config) { params->update(); _update_preset_menu(); + + if (params->importer.is_valid() && params->paths.size() == 1 && params->importer->has_advanced_options()) { + advanced->show(); + advanced_spacer->show(); + } else { + advanced->hide(); + advanced_spacer->hide(); + } } void ImportDock::set_edit_multiple_paths(const Vector<String> &p_paths) { @@ -258,11 +274,26 @@ void ImportDock::set_edit_multiple_paths(const Vector<String> &p_paths) { preset->set_disabled(false); imported->set_text(vformat(TTR("%d Files"), p_paths.size())); + + if (params->paths.size() == 1 && params->importer->has_advanced_options()) { + advanced->show(); + advanced_spacer->show(); + } else { + advanced->hide(); + advanced_spacer->hide(); + } } void ImportDock::_update_preset_menu() { preset->get_popup()->clear(); + if (params->importer.is_null()) { + preset->get_popup()->add_item(TTR("Default")); + preset->hide(); + return; + } + preset->show(); + if (params->importer->get_preset_count() == 0) { preset->get_popup()->add_item(TTR("Default")); } else { @@ -282,20 +313,25 @@ void ImportDock::_update_preset_menu() { void ImportDock::_importer_selected(int i_idx) { String name = import_as->get_selected_metadata(); - Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(name); - ERR_FAIL_COND(importer.is_null()); + if (name == "keep") { + params->importer.unref(); + _update_options(Ref<ConfigFile>()); + } else { + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(name); + ERR_FAIL_COND(importer.is_null()); - params->importer = importer; + params->importer = importer; - Ref<ConfigFile> config; - if (params->paths.size()) { - config.instance(); - Error err = config->load(params->paths[0] + ".import"); - if (err != OK) { - config.unref(); + Ref<ConfigFile> config; + if (params->paths.size()) { + config.instance(); + Error err = config->load(params->paths[0] + ".import"); + if (err != OK) { + config.unref(); + } } + _update_options(config); } - _update_options(config); } void ImportDock::_preset_selected(int p_idx) { @@ -391,6 +427,13 @@ static bool _find_owners(EditorFileSystemDirectory *efsd, const String &p_path) void ImportDock::_reimport_attempt() { bool need_restart = false; bool used_in_resources = false; + + String importer_name; + if (params->importer.is_valid()) { + importer_name = params->importer->get_importer_name(); + } else { + importer_name = "keep"; + } for (int i = 0; i < params->paths.size(); i++) { Ref<ConfigFile> config; config.instance(); @@ -398,7 +441,7 @@ void ImportDock::_reimport_attempt() { ERR_CONTINUE(err != OK); String imported_with = config->get_value("remap", "importer"); - if (imported_with != params->importer->get_importer_name()) { + if (imported_with != importer_name) { need_restart = true; if (_find_owners(EditorFileSystem::get_singleton()->get_filesystem(), params->paths[i])) { used_in_resources = true; @@ -422,6 +465,11 @@ void ImportDock::_reimport_and_restart() { EditorNode::get_singleton()->restart_editor(); } +void ImportDock::_advanced_options() { + if (params->paths.size() == 1 && params->importer.is_valid()) { + params->importer->show_advanced_options(params->paths[0]); + } +} void ImportDock::_reimport() { for (int i = 0; i < params->paths.size(); i++) { Ref<ConfigFile> config; @@ -429,38 +477,45 @@ void ImportDock::_reimport() { Error err = config->load(params->paths[i] + ".import"); ERR_CONTINUE(err != OK); - String importer_name = params->importer->get_importer_name(); + if (params->importer.is_valid()) { + String importer_name = params->importer->get_importer_name(); + + if (params->checking && config->get_value("remap", "importer") == params->importer->get_importer_name()) { + //update only what is edited (checkboxes) if the importer is the same + for (List<PropertyInfo>::Element *E = params->properties.front(); E; E = E->next()) { + if (params->checked.has(E->get().name)) { + config->set_value("params", E->get().name, params->values[E->get().name]); + } + } + } else { + //override entirely + config->set_value("remap", "importer", importer_name); + if (config->has_section("params")) { + config->erase_section("params"); + } - if (params->checking && config->get_value("remap", "importer") == params->importer->get_importer_name()) { - //update only what is edited (checkboxes) if the importer is the same - for (List<PropertyInfo>::Element *E = params->properties.front(); E; E = E->next()) { - if (params->checked.has(E->get().name)) { + for (List<PropertyInfo>::Element *E = params->properties.front(); E; E = E->next()) { config->set_value("params", E->get().name, params->values[E->get().name]); } } - } else { - //override entirely - config->set_value("remap", "importer", importer_name); - if (config->has_section("params")) { - config->erase_section("params"); - } - for (List<PropertyInfo>::Element *E = params->properties.front(); E; E = E->next()) { - config->set_value("params", E->get().name, params->values[E->get().name]); + //handle group file + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); + ERR_CONTINUE(!importer.is_valid()); + String group_file_property = importer->get_option_group_file(); + if (group_file_property != String()) { + //can import from a group (as in, atlas) + ERR_CONTINUE(!params->values.has(group_file_property)); + String group_file = params->values[group_file_property]; + config->set_value("remap", "group_file", group_file); + } else { + config->set_value("remap", "group_file", Variant()); //clear group file if unused } - } - //handle group file - Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); - ERR_CONTINUE(!importer.is_valid()); - String group_file_property = importer->get_option_group_file(); - if (group_file_property != String()) { - //can import from a group (as in, atlas) - ERR_CONTINUE(!params->values.has(group_file_property)); - String group_file = params->values[group_file_property]; - config->set_value("remap", "group_file", group_file); } else { - config->set_value("remap", "group_file", Variant()); //clear group file if unused + //set to no import + config->clear(); + config->set_value("remap", "importer", "keep"); } config->save(params->paths[i] + ".import"); @@ -531,10 +586,27 @@ ImportDock::ImportDock() { import->set_text(TTR("Reimport")); import->set_disabled(true); import->connect("pressed", callable_mp(this, &ImportDock::_reimport_attempt)); + if (!DisplayServer::get_singleton()->get_swap_cancel_ok()) { + advanced_spacer = hb->add_spacer(); + advanced = memnew(Button); + advanced->set_text(TTR("Advanced...")); + hb->add_child(advanced); + } hb->add_spacer(); hb->add_child(import); hb->add_spacer(); + if (DisplayServer::get_singleton()->get_swap_cancel_ok()) { + advanced = memnew(Button); + advanced->set_text(TTR("Advanced...")); + hb->add_child(advanced); + advanced_spacer = hb->add_spacer(); + } + + advanced->hide(); + advanced_spacer->hide(); + advanced->connect("pressed", callable_mp(this, &ImportDock::_advanced_options)); + reimport_confirm = memnew(ConfirmationDialog); reimport_confirm->get_ok_button()->set_text(TTR("Save Scenes, Re-Import, and Restart")); add_child(reimport_confirm); diff --git a/editor/import_dock.h b/editor/import_dock.h index 6c5779ddce..2be48dd505 100644 --- a/editor/import_dock.h +++ b/editor/import_dock.h @@ -57,6 +57,9 @@ class ImportDock : public VBoxContainer { Label *label_warning; Button *import; + Control *advanced_spacer; + Button *advanced; + ImportDockParameters *params; void _preset_selected(int p_idx); @@ -69,6 +72,7 @@ class ImportDock : public VBoxContainer { void _reimport_and_restart(); void _reimport(); + void _advanced_options(); enum { ITEM_SET_AS_DEFAULT = 100, ITEM_LOAD_DEFAULT, diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index 5963092860..3553450672 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -105,6 +105,8 @@ void AudioStreamEditor::_audio_changed() { void AudioStreamEditor::_play() { if (_player->is_playing()) { + // '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'. + _pausing = true; _player->stop(); _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); set_process(false); @@ -125,10 +127,13 @@ void AudioStreamEditor::_stop() { void AudioStreamEditor::_on_finished() { _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); - if (_current == _player->get_stream()->get_length()) { + if (!_pausing) { _current = 0; _indicator->update(); + } else { + _pausing = false; } + set_process(false); } void AudioStreamEditor::_draw_indicator() { @@ -194,8 +199,6 @@ void AudioStreamEditor::_bind_methods() { AudioStreamEditor::AudioStreamEditor() { set_custom_minimum_size(Size2(1, 100) * EDSCALE); - _current = 0; - _dragging = false; _player = memnew(AudioStreamPlayer); _player->connect("finished", callable_mp(this, &AudioStreamEditor::_on_finished)); diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/audio_stream_editor_plugin.h index aa906a6a05..14e829d025 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/plugins/audio_stream_editor_plugin.h @@ -41,17 +41,18 @@ class AudioStreamEditor : public ColorRect { GDCLASS(AudioStreamEditor, ColorRect); Ref<AudioStream> stream; - AudioStreamPlayer *_player; - ColorRect *_preview; - Control *_indicator; - Label *_current_label; - Label *_duration_label; + AudioStreamPlayer *_player = nullptr; + ColorRect *_preview = nullptr; + Control *_indicator = nullptr; + Label *_current_label = nullptr; + Label *_duration_label = nullptr; - Button *_play_button; - Button *_stop_button; + Button *_play_button = nullptr; + Button *_stop_button = nullptr; - float _current; - bool _dragging; + float _current = 0; + bool _dragging = false; + bool _pausing = false; void _audio_changed(); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 1d08a7821d..449968eb7b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5820,7 +5820,7 @@ void Node3DEditor::_init_grid() { // Offsets division_level for bigger or smaller grids. // Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids. real_t division_level_bias = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_bias"); - // Default largest grid size is 100m, 10^2 (default value is 2). + // Default largest grid size is 8^2 when primary_grid_steps is 8 (64m apart, so primary grid lines are 512m apart). int division_level_max = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_max"); // Default smallest grid size is 1cm, 10^-2 (default value is -2). int division_level_min = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_min"); diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 4949d2b9b7..4acacf3058 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -179,7 +179,7 @@ void Sprite2DEditor::_update_mesh_data() { } Rect2 rect; - if (node->is_region()) { + if (node->is_region_enabled()) { rect = node->get_region_rect(); } else { rect.size = Size2(image->get_width(), image->get_height()); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 63255e6547..4cbfe18594 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -897,7 +897,7 @@ void TextureRegionEditor::edit(Object *p_obj) { atlas_tex = Ref<AtlasTexture>(nullptr); } edit_draw->update(); - if ((node_sprite && !node_sprite->is_region()) || (node_sprite_3d && !node_sprite_3d->is_region())) { + if ((node_sprite && !node_sprite->is_region_enabled()) || (node_sprite_3d && !node_sprite_3d->is_region_enabled())) { set_process(true); } if (!p_obj) { @@ -1115,7 +1115,7 @@ void TextureRegionEditorPlugin::_editor_visiblity_changed() { void TextureRegionEditorPlugin::make_visible(bool p_visible) { if (p_visible) { texture_region_button->show(); - bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch() || (region_editor->get_sprite() && region_editor->get_sprite()->is_region()) || (region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region()); + bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch() || (region_editor->get_sprite() && region_editor->get_sprite()->is_region_enabled()) || (region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled()); if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) { editor->make_bottom_panel_item_visible(region_editor); } diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index 74b01b3c36..59730a4fd3 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -966,9 +966,9 @@ void TileMapEditor::_update_copydata() { tcd.flip_v = node->is_cell_y_flipped(j, i); tcd.transpose = node->is_cell_transposed(j, i); tcd.autotile_coord = node->get_cell_autotile_coord(j, i); - } - copydata.push_back(tcd); + copydata.push_back(tcd); + } } } } diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index c628fe8367..b97ee10743 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -80,7 +80,7 @@ void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) { Vector2 phys_offset; Size2 s; - if (mi->is_region()) { + if (mi->is_region_enabled()) { s = mi->get_region_rect().size; p_library->tile_set_region(id, mi->get_region_rect()); } else { diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 7d421bdf81..f4aa628b65 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -487,7 +487,7 @@ private: if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("project.godot"), initial_settings, Vector<String>(), false) != OK) { set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); } else { - ResourceSaver::save(dir.plus_file("icon.png"), msg->get_theme_icon("DefaultProjectIcon", "EditorIcons")); + ResourceSaver::save(dir.plus_file("icon.png"), create_unscaled_default_project_icon()); FileAccess *f = FileAccess::open(dir.plus_file("default_env.tres"), FileAccess::WRITE); if (!f) { diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index 6b505a6784..fd5157f04f 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -181,6 +181,7 @@ protected: public: void popup_scenetree_dialog(); SceneTreeEditor *get_scene_tree() { return tree; } + LineEdit *get_filter_line_edit() { return filter; } SceneTreeDialog(); ~SceneTreeDialog(); }; diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 7c27292e59..93642f2d5c 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -433,12 +433,6 @@ void BulletPhysicsServer3D::area_set_ray_pickable(RID p_area, bool p_enable) { area->set_ray_pickable(p_enable); } -bool BulletPhysicsServer3D::area_is_ray_pickable(RID p_area) const { - AreaBullet *area = area_owner.getornull(p_area); - ERR_FAIL_COND_V(!area, false); - return area->is_ray_pickable(); -} - RID BulletPhysicsServer3D::body_create(BodyMode p_mode, bool p_init_sleeping) { RigidBodyBullet *body = bulletnew(RigidBodyBullet); body->set_mode(p_mode); @@ -842,12 +836,6 @@ void BulletPhysicsServer3D::body_set_ray_pickable(RID p_body, bool p_enable) { body->set_ray_pickable(p_enable); } -bool BulletPhysicsServer3D::body_is_ray_pickable(RID p_body) const { - RigidBodyBullet *body = rigid_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, false); - return body->is_ray_pickable(); -} - PhysicsDirectBodyState3D *BulletPhysicsServer3D::body_get_direct_state(RID p_body) { RigidBodyBullet *body = rigid_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, nullptr); @@ -880,7 +868,7 @@ RID BulletPhysicsServer3D::soft_body_create(bool p_init_sleeping) { CreateThenReturnRID(soft_body_owner, body); } -void BulletPhysicsServer3D::soft_body_update_rendering_server(RID p_body, class SoftBodyRenderingServerHandler *p_rendering_server_handler) { +void BulletPhysicsServer3D::soft_body_update_rendering_server(RID p_body, RenderingServerHandler *p_rendering_server_handler) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -922,6 +910,13 @@ void BulletPhysicsServer3D::soft_body_set_mesh(RID p_body, const REF &p_mesh) { body->set_soft_mesh(p_mesh); } +AABB BulletPhysicsServer::soft_body_get_bounds(RID p_body) const { + SoftBodyBullet *body = soft_body_owner.get(p_body); + ERR_FAIL_COND_V(!body, AABB()); + + return body->get_bounds(); +} + void BulletPhysicsServer3D::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -1002,34 +997,19 @@ void BulletPhysicsServer3D::soft_body_set_transform(RID p_body, const Transform body->set_soft_transform(p_transform); } -Vector3 BulletPhysicsServer3D::soft_body_get_vertex_position(RID p_body, int vertex_index) const { - const SoftBodyBullet *body = soft_body_owner.getornull(p_body); - Vector3 pos; - ERR_FAIL_COND_V(!body, pos); - - body->get_node_position(vertex_index, pos); - return pos; -} - void BulletPhysicsServer3D::soft_body_set_ray_pickable(RID p_body, bool p_enable) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_ray_pickable(p_enable); } -bool BulletPhysicsServer3D::soft_body_is_ray_pickable(RID p_body) const { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, false); - return body->is_ray_pickable(); -} - void BulletPhysicsServer3D::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_simulation_precision(p_simulation_precision); } -int BulletPhysicsServer3D::soft_body_get_simulation_precision(RID p_body) { +int BulletPhysicsServer3D::soft_body_get_simulation_precision(RID p_body) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_simulation_precision(); @@ -1041,13 +1021,13 @@ void BulletPhysicsServer3D::soft_body_set_total_mass(RID p_body, real_t p_total_ body->set_total_mass(p_total_mass); } -real_t BulletPhysicsServer3D::soft_body_get_total_mass(RID p_body) { +real_t BulletPhysicsServer3D::soft_body_get_total_mass(RID p_body) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_total_mass(); } -void BulletPhysicsServer3D::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) { +void BulletPhysicsServer3D::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_linear_stiffness(p_stiffness); @@ -1059,61 +1039,25 @@ real_t BulletPhysicsServer3D::soft_body_get_linear_stiffness(RID p_body) { return body->get_linear_stiffness(); } -void BulletPhysicsServer3D::soft_body_set_angular_stiffness(RID p_body, real_t p_stiffness) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND(!body); - body->set_angular_stiffness(p_stiffness); -} - -real_t BulletPhysicsServer3D::soft_body_get_angular_stiffness(RID p_body) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, 0.f); - return body->get_angular_stiffness(); -} - -void BulletPhysicsServer3D::soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND(!body); - body->set_volume_stiffness(p_stiffness); -} - -real_t BulletPhysicsServer3D::soft_body_get_volume_stiffness(RID p_body) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, 0.f); - return body->get_volume_stiffness(); -} - void BulletPhysicsServer3D::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_pressure_coefficient(p_pressure_coefficient); } -real_t BulletPhysicsServer3D::soft_body_get_pressure_coefficient(RID p_body) { +real_t BulletPhysicsServer3D::soft_body_get_pressure_coefficient(RID p_body) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_pressure_coefficient(); } -void BulletPhysicsServer3D::soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND(!body); - return body->set_pose_matching_coefficient(p_pose_matching_coefficient); -} - -real_t BulletPhysicsServer3D::soft_body_get_pose_matching_coefficient(RID p_body) { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, 0.f); - return body->get_pose_matching_coefficient(); -} - void BulletPhysicsServer3D::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_damping_coefficient(p_damping_coefficient); } -real_t BulletPhysicsServer3D::soft_body_get_damping_coefficient(RID p_body) { +real_t BulletPhysicsServer3D::soft_body_get_damping_coefficient(RID p_body) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_damping_coefficient(); @@ -1125,7 +1069,7 @@ void BulletPhysicsServer3D::soft_body_set_drag_coefficient(RID p_body, real_t p_ body->set_drag_coefficient(p_drag_coefficient); } -real_t BulletPhysicsServer3D::soft_body_get_drag_coefficient(RID p_body) { +real_t BulletPhysicsServer3D::soft_body_get_drag_coefficient(RID p_body) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_drag_coefficient(); @@ -1137,7 +1081,7 @@ void BulletPhysicsServer3D::soft_body_move_point(RID p_body, int p_point_index, body->set_node_position(p_point_index, p_global_position); } -Vector3 BulletPhysicsServer3D::soft_body_get_point_global_position(RID p_body, int p_point_index) { +Vector3 BulletPhysicsServer3D::soft_body_get_point_global_position(RID p_body, int p_point_index) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, Vector3(0., 0., 0.)); Vector3 pos; @@ -1145,14 +1089,6 @@ Vector3 BulletPhysicsServer3D::soft_body_get_point_global_position(RID p_body, i return pos; } -Vector3 BulletPhysicsServer3D::soft_body_get_point_offset(RID p_body, int p_point_index) const { - SoftBodyBullet *body = soft_body_owner.getornull(p_body); - ERR_FAIL_COND_V(!body, Vector3()); - Vector3 res; - body->get_node_offset(p_point_index, res); - return res; -} - void BulletPhysicsServer3D::soft_body_remove_all_pinned_points(RID p_body) { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND(!body); @@ -1165,7 +1101,7 @@ void BulletPhysicsServer3D::soft_body_pin_point(RID p_body, int p_point_index, b body->set_node_mass(p_point_index, p_pin ? 0 : 1); } -bool BulletPhysicsServer3D::soft_body_is_point_pinned(RID p_body, int p_point_index) { +bool BulletPhysicsServer3D::soft_body_is_point_pinned(RID p_body, int p_point_index) const { SoftBodyBullet *body = soft_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0.f); return body->get_node_mass(p_point_index); diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index 97b719ae8e..856ff74963 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -163,7 +163,6 @@ public: virtual void area_set_monitor_callback(RID p_area, Object *p_receiver, const StringName &p_method) override; virtual void area_set_area_monitor_callback(RID p_area, Object *p_receiver, const StringName &p_method) override; virtual void area_set_ray_pickable(RID p_area, bool p_enable) override; - virtual bool area_is_ray_pickable(RID p_area) const override; /* RIGID BODY API */ @@ -250,7 +249,6 @@ public: virtual void body_set_force_integration_callback(RID p_body, Object *p_receiver, const StringName &p_method, const Variant &p_udata = Variant()) override; virtual void body_set_ray_pickable(RID p_body, bool p_enable) override; - virtual bool body_is_ray_pickable(RID p_body) const override; // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override; @@ -262,13 +260,15 @@ public: virtual RID soft_body_create(bool p_init_sleeping = false) override; - virtual void soft_body_update_rendering_server(RID p_body, class SoftBodyRenderingServerHandler *p_rendering_server_handler) override; + virtual void soft_body_update_rendering_server(RID p_body, RenderingServerHandler *p_rendering_server_handler) override; virtual void soft_body_set_space(RID p_body, RID p_space) override; virtual RID soft_body_get_space(RID p_body) const override; virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) override; + virtual AABB soft_body_get_bounds(RID p_body) const override; + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) override; virtual uint32_t soft_body_get_collision_layer(RID p_body) const override; @@ -284,46 +284,33 @@ public: /// Special function. This function has bad performance virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) override; - virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const override; virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) override; - virtual bool soft_body_is_ray_pickable(RID p_body) const override; virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) override; - virtual int soft_body_get_simulation_precision(RID p_body) override; + virtual int soft_body_get_simulation_precision(RID p_body) const override; virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) override; - virtual real_t soft_body_get_total_mass(RID p_body) override; + virtual real_t soft_body_get_total_mass(RID p_body) const override; virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) override; - virtual real_t soft_body_get_linear_stiffness(RID p_body) override; - - virtual void soft_body_set_angular_stiffness(RID p_body, real_t p_stiffness) override; - virtual real_t soft_body_get_angular_stiffness(RID p_body) override; - - virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) override; - virtual real_t soft_body_get_volume_stiffness(RID p_body) override; + virtual real_t soft_body_get_linear_stiffness(RID p_body) const override; virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) override; - virtual real_t soft_body_get_pressure_coefficient(RID p_body) override; - - virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) override; - virtual real_t soft_body_get_pose_matching_coefficient(RID p_body) override; + virtual real_t soft_body_get_pressure_coefficient(RID p_body) const override; virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) override; - virtual real_t soft_body_get_damping_coefficient(RID p_body) override; + virtual real_t soft_body_get_damping_coefficient(RID p_body) const override; virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) override; - virtual real_t soft_body_get_drag_coefficient(RID p_body) override; + virtual real_t soft_body_get_drag_coefficient(RID p_body) const override; virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) override; - virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) override; - - virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const override; + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) const override; virtual void soft_body_remove_all_pinned_points(RID p_body) override; virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) override; - virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) override; + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) const override; /* JOINT API */ diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index 82876ab77c..471b154813 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -375,11 +375,17 @@ ConcavePolygonShapeBullet::~ConcavePolygonShapeBullet() { } void ConcavePolygonShapeBullet::set_data(const Variant &p_data) { - setup(p_data); + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("faces")); + + setup(d["faces"]); } Variant ConcavePolygonShapeBullet::get_data() const { - return faces; + Dictionary d; + d["faces"] = faces; + + return d; } PhysicsServer3D::ShapeType ConcavePolygonShapeBullet::get_type() const { diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp index a8980984a7..2c8727baf2 100644 --- a/modules/bullet/soft_body_bullet.cpp +++ b/modules/bullet/soft_body_bullet.cpp @@ -65,7 +65,7 @@ void SoftBodyBullet::on_enter_area(AreaBullet *p_area) {} void SoftBodyBullet::on_exit_area(AreaBullet *p_area) {} -void SoftBodyBullet::update_rendering_server(SoftBodyRenderingServerHandler *p_rendering_server_handler) { +void SoftBodyBullet::update_rendering_server(RenderingServerHandler *p_rendering_server_handler) { if (!bt_soft_body) { return; } @@ -141,6 +141,24 @@ void SoftBodyBullet::set_soft_transform(const Transform &p_transform) { move_all_nodes(p_transform); } +AABB SoftBodyBullet::get_bounds() const { + if (!bt_soft_body) { + return AABB(); + } + + btVector3 aabb_min; + btVector3 aabb_max; + bt_soft_body->getAabb(aabb_min, aabb_max); + + btVector3 size(aabb_max - aabb_min); + + AABB aabb; + B_TO_G(aabb_min, aabb.position); + B_TO_G(size, aabb.size); + + return aabb; +} + void SoftBodyBullet::move_all_nodes(const Transform &p_transform) { if (!bt_soft_body) { return; @@ -169,25 +187,6 @@ void SoftBodyBullet::get_node_position(int p_node_index, Vector3 &r_position) co } } -void SoftBodyBullet::get_node_offset(int p_node_index, Vector3 &r_offset) const { - if (soft_mesh.is_null()) { - return; - } - - Array arrays = soft_mesh->surface_get_arrays(0); - Vector<Vector3> vertices(arrays[RS::ARRAY_VERTEX]); - - if (0 <= p_node_index && vertices.size() > p_node_index) { - r_offset = vertices[p_node_index]; - } -} - -void SoftBodyBullet::get_node_offset(int p_node_index, btVector3 &r_offset) const { - Vector3 off; - get_node_offset(p_node_index, off); - G_TO_B(off, r_offset); -} - void SoftBodyBullet::set_node_mass(int node_index, btScalar p_mass) { if (0 >= p_mass) { pin_node(node_index); @@ -259,20 +258,6 @@ void SoftBodyBullet::set_linear_stiffness(real_t p_val) { } } -void SoftBodyBullet::set_angular_stiffness(real_t p_val) { - angular_stiffness = p_val; - if (bt_soft_body) { - mat0->m_kAST = angular_stiffness; - } -} - -void SoftBodyBullet::set_volume_stiffness(real_t p_val) { - volume_stiffness = p_val; - if (bt_soft_body) { - mat0->m_kVST = volume_stiffness; - } -} - void SoftBodyBullet::set_simulation_precision(int p_val) { simulation_precision = p_val; if (bt_soft_body) { @@ -290,13 +275,6 @@ void SoftBodyBullet::set_pressure_coefficient(real_t p_val) { } } -void SoftBodyBullet::set_pose_matching_coefficient(real_t p_val) { - pose_matching_coefficient = p_val; - if (bt_soft_body) { - bt_soft_body->m_cfg.kMT = pose_matching_coefficient; - } -} - void SoftBodyBullet::set_damping_coefficient(real_t p_val) { damping_coefficient = p_val; if (bt_soft_body) { @@ -409,8 +387,6 @@ void SoftBodyBullet::setup_soft_body() { bt_soft_body->generateBendingConstraints(2, mat0); mat0->m_kLST = linear_stiffness; - mat0->m_kAST = angular_stiffness; - mat0->m_kVST = volume_stiffness; // Clusters allow to have Soft vs Soft collision but doesn't work well right now @@ -430,7 +406,6 @@ void SoftBodyBullet::setup_soft_body() { bt_soft_body->m_cfg.kDP = damping_coefficient; bt_soft_body->m_cfg.kDG = drag_coefficient; bt_soft_body->m_cfg.kPR = pressure_coefficient; - bt_soft_body->m_cfg.kMT = pose_matching_coefficient; bt_soft_body->setTotalMass(total_mass); btSoftBodyHelpers::ReoptimizeLinkOrder(bt_soft_body); diff --git a/modules/bullet/soft_body_bullet.h b/modules/bullet/soft_body_bullet.h index 23f6fba9a6..87023b2517 100644 --- a/modules/bullet/soft_body_bullet.h +++ b/modules/bullet/soft_body_bullet.h @@ -55,6 +55,8 @@ @author AndreaCatania */ +class RenderingServerHandler; + class SoftBodyBullet : public CollisionObjectBullet { private: btSoftBody *bt_soft_body = nullptr; @@ -67,10 +69,7 @@ private: int simulation_precision = 5; real_t total_mass = 1.; real_t linear_stiffness = 0.5; // [0,1] - real_t angular_stiffness = 0.5; // [0,1] - real_t volume_stiffness = 0.5; // [0,1] real_t pressure_coefficient = 0.; // [-inf,+inf] - real_t pose_matching_coefficient = 0.; // [0,1] real_t damping_coefficient = 0.01; // [0,1] real_t drag_coefficient = 0.; // [0,1] Vector<int> pinned_nodes; @@ -99,7 +98,7 @@ public: _FORCE_INLINE_ btSoftBody *get_bt_soft_body() const { return bt_soft_body; } - void update_rendering_server(class SoftBodyRenderingServerHandler *p_rendering_server_handler); + void update_rendering_server(RenderingServerHandler *p_rendering_server_handler); void set_soft_mesh(const Ref<Mesh> &p_mesh); void destroy_soft_body(); @@ -107,14 +106,12 @@ public: // Special function. This function has bad performance void set_soft_transform(const Transform &p_transform); + AABB get_bounds() const; + void move_all_nodes(const Transform &p_transform); void set_node_position(int node_index, const Vector3 &p_global_position); void set_node_position(int node_index, const btVector3 &p_global_position); void get_node_position(int node_index, Vector3 &r_position) const; - // Heavy function, Please cache this info - void get_node_offset(int node_index, Vector3 &r_offset) const; - // Heavy function, Please cache this info - void get_node_offset(int node_index, btVector3 &r_offset) const; void set_node_mass(int node_index, btScalar p_mass); btScalar get_node_mass(int node_index) const; @@ -129,21 +126,12 @@ public: void set_linear_stiffness(real_t p_val); _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; } - void set_angular_stiffness(real_t p_val); - _FORCE_INLINE_ real_t get_angular_stiffness() const { return angular_stiffness; } - - void set_volume_stiffness(real_t p_val); - _FORCE_INLINE_ real_t get_volume_stiffness() const { return volume_stiffness; } - void set_simulation_precision(int p_val); _FORCE_INLINE_ int get_simulation_precision() const { return simulation_precision; } void set_pressure_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; } - void set_pose_matching_coefficient(real_t p_val); - _FORCE_INLINE_ real_t get_pose_matching_coefficient() const { return pose_matching_coefficient; } - void set_damping_coefficient(real_t p_val); _FORCE_INLINE_ real_t get_damping_coefficient() const { return damping_coefficient; } diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp index 883651943e..b088dd8640 100644 --- a/modules/fbx/data/fbx_mesh_data.cpp +++ b/modules/fbx/data/fbx_mesh_data.cpp @@ -417,6 +417,7 @@ void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { int bind_id = 0; for (const FBXDocParser::Cluster *cluster : fbx_skin->Clusters()) { + ERR_CONTINUE_MSG(!state.fbx_bone_map.has(cluster->TargetNode()->ID()), "Missing bone map for cluster target node with id " + uitos(cluster->TargetNode()->ID()) + "."); Ref<FBXBone> bone = state.fbx_bone_map[cluster->TargetNode()->ID()]; skeleton_to_skin_bind_id.insert(bone->godot_bone_id, bind_id); bind_id++; diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index 6576147b2b..e18ebe3930 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -628,7 +628,7 @@ Node3D *EditorSceneImporterFBX::_generate_scene( mesh_data_precached->mesh_node = fbx_node; // mesh node, mesh id - mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, (p_flags & IMPORT_USE_COMPRESSION) != 0); + mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, 0); if (!state.MeshNodes.has(mesh_id)) { state.MeshNodes.insert(mesh_id, fbx_node); } @@ -1132,7 +1132,7 @@ Node3D *EditorSceneImporterFBX::_generate_scene( max_duration = animation_track_time; } - rot_values.push_back(final_rotation); + rot_values.push_back(final_rotation.normalized()); rot_times.push_back(animation_track_time); } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 319778daab..e432dfc891 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -898,6 +898,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); _advance(); break; + } else { + // Not a multiline string termination, add consumed quote. + result += quote_char; } } else { // Ended single-line string. diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp index 6ea722a216..35f44ca122 100644 --- a/modules/gltf/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -99,7 +99,9 @@ Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, Ref<GLTFDocument> gltf_document; gltf_document.instance(); Error err = gltf_document->parse(r_state, p_path); - *r_err = err; + if (r_err) { + *r_err = err; + } ERR_FAIL_COND_V(err != Error::OK, nullptr); Node3D *root = memnew(Node3D); diff --git a/platform/android/detect.py b/platform/android/detect.py index 5f0fcc9b77..996b6dcf41 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -197,12 +197,11 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(LINKFLAGS=["-O2"]) env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"]) - env.Append(CPPDEFINES=["NDEBUG"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os"]) - env.Append(CPPDEFINES=["NDEBUG"]) env.Append(LINKFLAGS=["-Os"]) + env.Append(CPPDEFINES=["NDEBUG"]) if can_vectorize: env.Append(CCFLAGS=["-ftree-vectorize"]) if env["target"] == "release_debug": diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index c601864880..0c16214c8a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -268,7 +268,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC GodotLib.setup(command_line); - final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name"); if (videoDriver.equals("Vulkan")) { mRenderView = new GodotVulkanRenderView(activity, this); } else { diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index ab03599dc3..f49b0e843a 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -38,6 +38,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, return false; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); MethodInfo *method = nullptr; for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) { @@ -965,6 +966,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { return class_cache[p_class]; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>()); jclass bclass = env->FindClass(p_class.utf8().get_data()); ERR_FAIL_COND_V(!bclass, Ref<JavaClass>()); @@ -1149,6 +1151,7 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { singleton = this; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 41201db32b..ec3b6f8ac0 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -73,6 +73,7 @@ jobject GodotIOJavaWrapper::get_instance() { Error GodotIOJavaWrapper::open_uri(const String &p_uri) { if (_open_URI) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; } else { @@ -83,6 +84,7 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { String GodotIOJavaWrapper::get_user_data_dir() { if (_get_data_dir) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { @@ -93,6 +95,7 @@ String GodotIOJavaWrapper::get_user_data_dir() { String GodotIOJavaWrapper::get_locale() { if (_get_locale) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale); return jstring_to_string(s, env); } else { @@ -103,6 +106,7 @@ String GodotIOJavaWrapper::get_locale() { String GodotIOJavaWrapper::get_model() { if (_get_model) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model); return jstring_to_string(s, env); } else { @@ -113,6 +117,7 @@ String GodotIOJavaWrapper::get_model() { int GodotIOJavaWrapper::get_screen_dpi() { if (_get_screen_DPI) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 160); return env->CallIntMethod(godot_io_instance, _get_screen_DPI); } else { return 160; @@ -122,6 +127,7 @@ int GodotIOJavaWrapper::get_screen_dpi() { void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { if (_screen_get_usable_rect) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect); ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4); jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE); @@ -135,6 +141,7 @@ void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { String GodotIOJavaWrapper::get_unique_id() { if (_get_unique_id) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id); return jstring_to_string(s, env); } else { @@ -149,6 +156,7 @@ bool GodotIOJavaWrapper::has_vk() { void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (_show_keyboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); } @@ -157,6 +165,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int void GodotIOJavaWrapper::hide_vk() { if (_hide_keyboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _hide_keyboard); } } @@ -164,6 +173,7 @@ void GodotIOJavaWrapper::hide_vk() { void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { if (_set_screen_orientation) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient); } } @@ -171,6 +181,7 @@ void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { int GodotIOJavaWrapper::get_screen_orientation() { if (_get_screen_orientation) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); return env->CallIntMethod(godot_io_instance, _get_screen_orientation); } else { return 0; @@ -180,6 +191,7 @@ int GodotIOJavaWrapper::get_screen_orientation() { String GodotIOJavaWrapper::get_system_dir(int p_dir) { if (_get_system_dir) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String(".")); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); return jstring_to_string(s, env); } else { diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index 5b638300ef..6b5e44f371 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -34,6 +34,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); _godot_view = env->NewGlobalRef(godot_view); @@ -48,6 +49,8 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { void GodotJavaViewWrapper::request_pointer_capture() { if (_request_pointer_capture != 0) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _request_pointer_capture); } } @@ -55,12 +58,16 @@ void GodotJavaViewWrapper::request_pointer_capture() { void GodotJavaViewWrapper::release_pointer_capture() { if (_request_pointer_capture != 0) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _release_pointer_capture); } } GodotJavaViewWrapper::~GodotJavaViewWrapper() { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->DeleteGlobalRef(_godot_view); env->DeleteGlobalRef(_cls); } diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index 548c278292..bfb4369fb8 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -34,6 +34,8 @@ #include <android/log.h> #include <jni.h> +#include "string_android.h" + // Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++ class GodotJavaViewWrapper { private: diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index ff1bbec40d..bfd93345f3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -94,6 +94,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND_V(p_env == nullptr, nullptr); + jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); return p_env->GetStaticObjectField(godot_class, fid); } else { @@ -104,6 +106,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl jobject GodotJavaWrapper::get_class_loader() { if (_get_class_loader) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(activity, _get_class_loader); } else { return nullptr; @@ -115,6 +119,8 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { return _godot_view; } JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); return _godot_view; @@ -124,6 +130,7 @@ void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { if (_on_video_init) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); p_env->CallVoidMethod(godot_instance, _on_video_init); } @@ -143,6 +150,7 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) { if (p_env == nullptr) { p_env = get_jni_env(); } + ERR_FAIL_COND(p_env == nullptr); p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); } } @@ -151,6 +159,7 @@ void GodotJavaWrapper::restart(JNIEnv *p_env) { if (_restart) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); p_env->CallVoidMethod(godot_instance, _restart); } @@ -160,6 +169,7 @@ void GodotJavaWrapper::force_quit(JNIEnv *p_env) { if (_finish) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); p_env->CallVoidMethod(godot_instance, _finish); } @@ -168,6 +178,8 @@ void GodotJavaWrapper::force_quit(JNIEnv *p_env) { void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { if (_set_keep_screen_on) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled); } } @@ -175,6 +187,8 @@ void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { if (_alert) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); @@ -183,6 +197,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { int GodotJavaWrapper::get_gles_version_code() { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); + if (_get_GLES_version_code) { return env->CallIntMethod(godot_instance, _get_GLES_version_code); } @@ -197,6 +213,8 @@ bool GodotJavaWrapper::has_get_clipboard() { String GodotJavaWrapper::get_clipboard() { if (_get_clipboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard); return jstring_to_string(s, env); } else { @@ -207,6 +225,8 @@ String GodotJavaWrapper::get_clipboard() { String GodotJavaWrapper::get_input_fallback_mapping() { if (_get_input_fallback_mapping) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping); return jstring_to_string(fallback_mapping, env); } else { @@ -221,6 +241,8 @@ bool GodotJavaWrapper::has_set_clipboard() { void GodotJavaWrapper::set_clipboard(const String &p_text) { if (_set_clipboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); } @@ -229,6 +251,8 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { bool GodotJavaWrapper::request_permission(const String &p_name) { if (_request_permission) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); } else { @@ -239,6 +263,8 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { bool GodotJavaWrapper::request_permissions() { if (_request_permissions) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _request_permissions); } else { return false; @@ -249,6 +275,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { Vector<String> permissions_list; if (_get_granted_permissions) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, permissions_list); + jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); @@ -267,6 +295,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _init_input_devices); } } @@ -274,6 +304,8 @@ void GodotJavaWrapper::init_input_devices() { jobject GodotJavaWrapper::get_surface() { if (_get_surface) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(godot_instance, _get_surface); } else { return nullptr; @@ -283,6 +315,8 @@ jobject GodotJavaWrapper::get_surface() { bool GodotJavaWrapper::is_activity_resumed() { if (_is_activity_resumed) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _is_activity_resumed); } else { return false; @@ -292,6 +326,8 @@ bool GodotJavaWrapper::is_activity_resumed() { void GodotJavaWrapper::vibrate(int p_duration_ms) { if (_vibrate) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); } } diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index afcc7294f2..ba379c8d43 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -30,17 +30,34 @@ #include "thread_jandroid.h" +#include <android/log.h> + #include "core/os/thread.h" static JavaVM *java_vm = nullptr; static thread_local JNIEnv *env = nullptr; +// The logic here need to improve, init_thread/term_tread are designed to work with Thread::callback +// Calling init_thread from setup_android_thread and get_jni_env to setup an env we're keeping and not detaching +// could cause issues on app termination. +// +// We should be making sure that any thread started calls a nice cleanup function when it's done, +// especially now that we use many more threads. + static void init_thread() { + if (env) { + // thread never detached! just keep using... + return; + } + java_vm->AttachCurrentThread(&env, nullptr); } static void term_thread() { java_vm->DetachCurrentThread(); + + // this is no longer valid, must called init_thread to re-establish + env = nullptr; } void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { @@ -50,9 +67,17 @@ void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { } void setup_android_thread() { - init_thread(); + if (!env) { + // !BAS! see remarks above + init_thread(); + } } JNIEnv *get_jni_env() { + if (!env) { + // !BAS! see remarks above + init_thread(); + } + return env; } diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 17796beb6f..cf358e0878 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -54,7 +54,7 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"]) env.Append(LINKFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) env.Append(LINKFLAGS=["-Os"]) diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index e80ef374ec..ac8d8de7e0 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -64,21 +64,21 @@ def configure(env): sys.exit(255) ## Build type - if env["target"] == "release": + if env["target"].startswith("release"): # Use -Os to prioritize optimizing for reduced file size. This is # particularly valuable for the web platform because it directly # decreases download time. # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about # 100 KiB over -Os, which does not justify the negative impact on # run-time performance. - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - elif env["target"] == "release_debug": - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) - # Retain function names for backtraces at the cost of file size. - env.Append(LINKFLAGS=["--profiling-funcs"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["-Os"]) + env.Append(LINKFLAGS=["-Os"]) + + if env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + # Retain function names for backtraces at the cost of file size. + env.Append(LINKFLAGS=["--profiling-funcs"]) else: # "debug" env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index 04fbba8a41..b3b15a1574 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -21,7 +21,11 @@ def get_build_version(): name = "custom_build" if os.getenv("BUILD_NAME") != None: name = os.getenv("BUILD_NAME") - return "%d.%d.%d.%s.%s" % (version.major, version.minor, version.patch, version.status, name) + v = "%d.%d" % (version.major, version.minor) + if version.patch > 0: + v += ".%d" % version.patch + v += ".%s.%s" % (version.status, name) + return v def create_engine_file(env, target, source, externs): diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 19a0552c8c..7211ebbfd8 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -102,7 +102,7 @@ const Engine = (function () { const me = this; function doInit(promise) { return promise.then(function (response) { - return Godot(me.config.getModuleConfig(loadPath, response.clone())); + return Godot(me.config.getModuleConfig(loadPath, new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] }))); }).then(function (module) { const paths = me.config.persistentPaths; return module['initFS'](paths).then(function (err) { diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 09d185ae2b..6b527c6fb5 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -90,7 +90,7 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"]: @@ -99,7 +99,7 @@ def configure(env): elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) diff --git a/platform/osx/detect.py b/platform/osx/detect.py index c39a4426be..5b320da82f 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -50,7 +50,7 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) if env["arch"] != "arm64": env.Prepend(CCFLAGS=["-msse2"]) @@ -61,7 +61,7 @@ def configure(env): elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"]: diff --git a/platform/server/detect.py b/platform/server/detect.py index c799ce03e1..16ddbe1768 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -56,7 +56,7 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"]: @@ -65,7 +65,7 @@ def configure(env): elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index fda8fdec66..28922a4f59 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -54,16 +54,19 @@ def configure(env): ## Build type if env["target"] == "release": - env.Append(CCFLAGS=["/O2", "/GL"]) env.Append(CCFLAGS=["/MD"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS", "/LTCG"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/GL"]) + env.Append(LINKFLAGS=["/LTCG"]) elif env["target"] == "release_debug": - env.Append(CCFLAGS=["/O2", "/Zi"]) env.Append(CCFLAGS=["/MD"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/Zi"]) elif env["target"] == "debug": env.Append(CCFLAGS=["/Zi"]) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 0b2145a92b..7772ba2dbe 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -191,18 +191,20 @@ def configure_msvc(env, manual_msvc_config): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "debug": env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od", "/EHsc"]) diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index 31040020dd..7c93edbff9 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -77,14 +77,14 @@ Rect2 Sprite2D::get_anchorable_rect() const { return get_rect(); } -void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const { +void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const { Rect2 base_rect; - if (region) { - r_filter_clip = region_filter_clip; + if (region_enabled) { + r_filter_clip_enabled = region_filter_clip_enabled; base_rect = region_rect; } else { - r_filter_clip = false; + r_filter_clip_enabled = false; base_rect = Rect2(0, 0, texture->get_width(), texture->get_height()); } @@ -129,10 +129,10 @@ void Sprite2D::_notification(int p_what) { */ Rect2 src_rect, dst_rect; - bool filter_clip; - _get_rects(src_rect, dst_rect, filter_clip); + bool filter_clip_enabled; + _get_rects(src_rect, dst_rect, filter_clip_enabled); - texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip); + texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip_enabled); } break; } } @@ -199,18 +199,18 @@ bool Sprite2D::is_flipped_v() const { return vflip; } -void Sprite2D::set_region(bool p_region) { - if (p_region == region) { +void Sprite2D::set_region_enabled(bool p_region_enabled) { + if (p_region_enabled == region_enabled) { return; } - region = p_region; + region_enabled = p_region_enabled; update(); notify_property_list_changed(); } -bool Sprite2D::is_region() const { - return region; +bool Sprite2D::is_region_enabled() const { + return region_enabled; } void Sprite2D::set_region_rect(const Rect2 &p_region_rect) { @@ -220,7 +220,7 @@ void Sprite2D::set_region_rect(const Rect2 &p_region_rect) { region_rect = p_region_rect; - if (region) { + if (region_enabled) { item_rect_changed(); } } @@ -229,13 +229,13 @@ Rect2 Sprite2D::get_region_rect() const { return region_rect; } -void Sprite2D::set_region_filter_clip(bool p_enable) { - region_filter_clip = p_enable; +void Sprite2D::set_region_filter_clip_enabled(bool p_region_filter_clip_enabled) { + region_filter_clip_enabled = p_region_filter_clip_enabled; update(); } bool Sprite2D::is_region_filter_clip_enabled() const { - return region_filter_clip; + return region_filter_clip_enabled; } void Sprite2D::set_frame(int p_frame) { @@ -299,8 +299,8 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { } Rect2 src_rect, dst_rect; - bool filter_clip; - _get_rects(src_rect, dst_rect, filter_clip); + bool filter_clip_enabled; + _get_rects(src_rect, dst_rect, filter_clip_enabled); dst_rect.size = dst_rect.size.abs(); if (!dst_rect.has_point(p_point)) { @@ -350,7 +350,7 @@ Rect2 Sprite2D::get_rect() const { Size2i s; - if (region) { + if (region_enabled) { s = region_rect.size; } else { s = texture->get_size(); @@ -385,7 +385,7 @@ void Sprite2D::_validate_property(PropertyInfo &property) const { property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } - if (!region && (property.name == "region_rect" || property.name == "region_filter_clip")) { + if (!region_enabled && (property.name == "region_rect" || property.name == "region_filter_clip")) { property.usage = PROPERTY_USAGE_NOEDITOR; } } @@ -414,15 +414,15 @@ void Sprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &Sprite2D::set_flip_v); ClassDB::bind_method(D_METHOD("is_flipped_v"), &Sprite2D::is_flipped_v); - ClassDB::bind_method(D_METHOD("set_region", "enabled"), &Sprite2D::set_region); - ClassDB::bind_method(D_METHOD("is_region"), &Sprite2D::is_region); + ClassDB::bind_method(D_METHOD("set_region_enabled", "enabled"), &Sprite2D::set_region_enabled); + ClassDB::bind_method(D_METHOD("is_region_enabled"), &Sprite2D::is_region_enabled); ClassDB::bind_method(D_METHOD("is_pixel_opaque", "pos"), &Sprite2D::is_pixel_opaque); ClassDB::bind_method(D_METHOD("set_region_rect", "rect"), &Sprite2D::set_region_rect); ClassDB::bind_method(D_METHOD("get_region_rect"), &Sprite2D::get_region_rect); - ClassDB::bind_method(D_METHOD("set_region_filter_clip", "enabled"), &Sprite2D::set_region_filter_clip); + ClassDB::bind_method(D_METHOD("set_region_filter_clip_enabled", "enabled"), &Sprite2D::set_region_filter_clip_enabled); ClassDB::bind_method(D_METHOD("is_region_filter_clip_enabled"), &Sprite2D::is_region_filter_clip_enabled); ClassDB::bind_method(D_METHOD("set_frame", "frame"), &Sprite2D::set_frame); @@ -455,9 +455,9 @@ void Sprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region", "is_region"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_filter_clip"), "set_region_filter_clip", "is_region_filter_clip_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_filter_clip_enabled"), "set_region_filter_clip_enabled", "is_region_filter_clip_enabled"); } Sprite2D::Sprite2D() { diff --git a/scene/2d/sprite_2d.h b/scene/2d/sprite_2d.h index fa765f457d..f6e9b4cde2 100644 --- a/scene/2d/sprite_2d.h +++ b/scene/2d/sprite_2d.h @@ -46,16 +46,16 @@ class Sprite2D : public Node2D { bool hflip = false; bool vflip = false; - bool region = false; + bool region_enabled = false; Rect2 region_rect; - bool region_filter_clip = false; + bool region_filter_clip_enabled = false; int frame = 0; int vframes = 1; int hframes = 1; - void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const; + void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const; void _texture_changed(); @@ -97,10 +97,10 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - void set_region(bool p_region); - bool is_region() const; + void set_region_enabled(bool p_enabled); + bool is_region_enabled() const; - void set_region_filter_clip(bool p_enable); + void set_region_filter_clip_enabled(bool p_enabled); bool is_region_filter_clip_enabled() const; void set_region_rect(const Rect2 &p_region_rect); diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 2d8f22ab37..3fde4d6ef3 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -37,7 +37,6 @@ #include "scene/3d/collision_object_3d.h" #include "scene/3d/physics_body_3d.h" #include "scene/3d/skeleton_3d.h" -#include "servers/physics_server_3d.h" SoftBodyRenderingServerHandler::SoftBodyRenderingServerHandler() {} @@ -48,27 +47,28 @@ void SoftBodyRenderingServerHandler::prepare(RID p_mesh, int p_surface) { mesh = p_mesh; surface = p_surface; -#ifndef _MSC_VER -#warning Softbody is not working, needs to be redone considering that these functions no longer exist -#endif -#if 0 - const uint32_t surface_format = RS::get_singleton()->mesh_surface_get_format(mesh, surface); - const int surface_vertex_len = RS::get_singleton()->mesh_surface_get_array_len(mesh, p_surface); - const int surface_index_len = RS::get_singleton()->mesh_surface_get_array_index_len(mesh, p_surface); + + RS::SurfaceData surface_data = RS::get_singleton()->mesh_get_surface(mesh, surface); + uint32_t surface_offsets[RS::ARRAY_MAX]; + uint32_t vertex_stride; + uint32_t attrib_stride; + uint32_t skin_stride; + RS::get_singleton()->mesh_surface_make_offsets_from_format(surface_data.format, surface_data.vertex_count, surface_data.index_count, surface_offsets, vertex_stride, attrib_stride, skin_stride); - buffer = RS::get_singleton()->mesh_surface_get_array(mesh, surface); - stride = RS::get_singleton()->mesh_surface_make_offsets_from_format(surface_format, surface_vertex_len, surface_index_len, surface_offsets); + buffer = surface_data.vertex_data; + stride = vertex_stride; offset_vertices = surface_offsets[RS::ARRAY_VERTEX]; offset_normal = surface_offsets[RS::ARRAY_NORMAL]; -#endif } void SoftBodyRenderingServerHandler::clear() { - if (mesh.is_valid()) { - buffer.resize(0); - } + buffer.resize(0); + stride = 0; + offset_vertices = 0; + offset_normal = 0; + surface = 0; mesh = RID(); } @@ -77,7 +77,7 @@ void SoftBodyRenderingServerHandler::open() { } void SoftBodyRenderingServerHandler::close() { - //write_buffer.release(); + write_buffer = nullptr; } void SoftBodyRenderingServerHandler::commit_changes() { @@ -309,6 +309,8 @@ void SoftBody3D::_notification(int p_what) { } void SoftBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_physics_rid"), &SoftBody3D::get_physics_rid); + ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SoftBody3D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &SoftBody3D::get_collision_mask); @@ -337,18 +339,9 @@ void SoftBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_linear_stiffness", "linear_stiffness"), &SoftBody3D::set_linear_stiffness); ClassDB::bind_method(D_METHOD("get_linear_stiffness"), &SoftBody3D::get_linear_stiffness); - ClassDB::bind_method(D_METHOD("set_angular_stiffness", "angular_stiffness"), &SoftBody3D::set_angular_stiffness); - ClassDB::bind_method(D_METHOD("get_angular_stiffness"), &SoftBody3D::get_angular_stiffness); - - ClassDB::bind_method(D_METHOD("set_volume_stiffness", "volume_stiffness"), &SoftBody3D::set_volume_stiffness); - ClassDB::bind_method(D_METHOD("get_volume_stiffness"), &SoftBody3D::get_volume_stiffness); - ClassDB::bind_method(D_METHOD("set_pressure_coefficient", "pressure_coefficient"), &SoftBody3D::set_pressure_coefficient); ClassDB::bind_method(D_METHOD("get_pressure_coefficient"), &SoftBody3D::get_pressure_coefficient); - ClassDB::bind_method(D_METHOD("set_pose_matching_coefficient", "pose_matching_coefficient"), &SoftBody3D::set_pose_matching_coefficient); - ClassDB::bind_method(D_METHOD("get_pose_matching_coefficient"), &SoftBody3D::get_pose_matching_coefficient); - ClassDB::bind_method(D_METHOD("set_damping_coefficient", "damping_coefficient"), &SoftBody3D::set_damping_coefficient); ClassDB::bind_method(D_METHOD("get_damping_coefficient"), &SoftBody3D::get_damping_coefficient); @@ -366,12 +359,9 @@ void SoftBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "simulation_precision", PROPERTY_HINT_RANGE, "1,100,1"), "set_simulation_precision", "get_simulation_precision"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_mass", PROPERTY_HINT_RANGE, "0.01,10000,1"), "set_total_mass", "get_total_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_linear_stiffness", "get_linear_stiffness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_angular_stiffness", "get_angular_stiffness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_stiffness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_volume_stiffness", "get_volume_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure_coefficient"), "set_pressure_coefficient", "get_pressure_coefficient"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_damping_coefficient", "get_damping_coefficient"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_coefficient", "get_drag_coefficient"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pose_matching_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_pose_matching_coefficient", "get_pose_matching_coefficient"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ray_pickable"), "set_ray_pickable", "is_ray_pickable"); } @@ -612,34 +602,10 @@ real_t SoftBody3D::get_linear_stiffness() { return PhysicsServer3D::get_singleton()->soft_body_get_linear_stiffness(physics_rid); } -void SoftBody3D::set_angular_stiffness(real_t p_angular_stiffness) { - PhysicsServer3D::get_singleton()->soft_body_set_angular_stiffness(physics_rid, p_angular_stiffness); -} - -real_t SoftBody3D::get_angular_stiffness() { - return PhysicsServer3D::get_singleton()->soft_body_get_angular_stiffness(physics_rid); -} - -void SoftBody3D::set_volume_stiffness(real_t p_volume_stiffness) { - PhysicsServer3D::get_singleton()->soft_body_set_volume_stiffness(physics_rid, p_volume_stiffness); -} - -real_t SoftBody3D::get_volume_stiffness() { - return PhysicsServer3D::get_singleton()->soft_body_get_volume_stiffness(physics_rid); -} - real_t SoftBody3D::get_pressure_coefficient() { return PhysicsServer3D::get_singleton()->soft_body_get_pressure_coefficient(physics_rid); } -void SoftBody3D::set_pose_matching_coefficient(real_t p_pose_matching_coefficient) { - PhysicsServer3D::get_singleton()->soft_body_set_pose_matching_coefficient(physics_rid, p_pose_matching_coefficient); -} - -real_t SoftBody3D::get_pose_matching_coefficient() { - return PhysicsServer3D::get_singleton()->soft_body_get_pose_matching_coefficient(physics_rid); -} - void SoftBody3D::set_pressure_coefficient(real_t p_pressure_coefficient) { PhysicsServer3D::get_singleton()->soft_body_set_pressure_coefficient(physics_rid, p_pressure_coefficient); } @@ -768,7 +734,9 @@ void SoftBody3D::_reset_points_offsets() { PinnedPoint *w = pinned_points.ptrw(); for (int i = pinned_points.size() - 1; 0 <= i; --i) { if (!r[i].spatial_attachment) { - w[i].spatial_attachment = Object::cast_to<Node3D>(get_node(r[i].spatial_attachment_path)); + if (!r[i].spatial_attachment_path.is_empty() && has_node(r[i].spatial_attachment_path)) { + w[i].spatial_attachment = Object::cast_to<Node3D>(get_node(r[i].spatial_attachment_path)); + } } if (!r[i].spatial_attachment) { diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index 6e24a530bd..f98df39209 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -32,10 +32,11 @@ #define SOFT_PHYSICS_BODY_H #include "scene/3d/mesh_instance_3d.h" +#include "servers/physics_server_3d.h" class SoftBody3D; -class SoftBodyRenderingServerHandler { +class SoftBodyRenderingServerHandler : public RenderingServerHandler { friend class SoftBody3D; RID mesh; @@ -57,9 +58,9 @@ private: void commit_changes(); public: - void set_vertex(int p_vertex_id, const void *p_vector3); - void set_normal(int p_vertex_id, const void *p_vector3); - void set_aabb(const AABB &p_aabb); + void set_vertex(int p_vertex_id, const void *p_vector3) override; + void set_normal(int p_vertex_id, const void *p_vector3) override; + void set_aabb(const AABB &p_aabb) override; }; class SoftBody3D : public MeshInstance3D { @@ -122,6 +123,8 @@ public: void prepare_physics_server(); void become_mesh_owner(); + RID get_physics_rid() const { return physics_rid; } + void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; @@ -149,18 +152,9 @@ public: void set_linear_stiffness(real_t p_linear_stiffness); real_t get_linear_stiffness(); - void set_angular_stiffness(real_t p_angular_stiffness); - real_t get_angular_stiffness(); - - void set_volume_stiffness(real_t p_volume_stiffness); - real_t get_volume_stiffness(); - void set_pressure_coefficient(real_t p_pressure_coefficient); real_t get_pressure_coefficient(); - void set_pose_matching_coefficient(real_t p_pose_matching_coefficient); - real_t get_pose_matching_coefficient(); - void set_damping_coefficient(real_t p_damping_coefficient); real_t get_damping_coefficient(); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index a3265ffb30..0be54e7243 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -366,7 +366,7 @@ void Sprite3D::_draw() { } Rect2 base_rect; - if (region) { + if (region_enabled) { base_rect = region_rect; } else { base_rect = Rect2(0, 0, texture->get_width(), texture->get_height()); @@ -511,24 +511,24 @@ Ref<Texture2D> Sprite3D::get_texture() const { return texture; } -void Sprite3D::set_region(bool p_region) { - if (p_region == region) { +void Sprite3D::set_region_enabled(bool p_region_enabled) { + if (p_region_enabled == region_enabled) { return; } - region = p_region; + region_enabled = p_region_enabled; _queue_update(); notify_property_list_changed(); } -bool Sprite3D::is_region() const { - return region; +bool Sprite3D::is_region_enabled() const { + return region_enabled; } void Sprite3D::set_region_rect(const Rect2 &p_region_rect) { bool changed = region_rect != p_region_rect; region_rect = p_region_rect; - if (region && changed) { + if (region_enabled && changed) { _queue_update(); } } @@ -595,7 +595,7 @@ Rect2 Sprite3D::get_item_rect() const { Size2i s; - if (region) { + if (region_enabled) { s = region_rect.size; } else { s = texture->get_size(); @@ -625,7 +625,7 @@ void Sprite3D::_validate_property(PropertyInfo &property) const { property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } - if (!region && property.name == "region_rect") { + if (!region_enabled && property.name == "region_rect") { property.usage = PROPERTY_USAGE_NOEDITOR; } } @@ -634,8 +634,8 @@ void Sprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Sprite3D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &Sprite3D::get_texture); - ClassDB::bind_method(D_METHOD("set_region", "enabled"), &Sprite3D::set_region); - ClassDB::bind_method(D_METHOD("is_region"), &Sprite3D::is_region); + ClassDB::bind_method(D_METHOD("set_region_enabled", "enabled"), &Sprite3D::set_region_enabled); + ClassDB::bind_method(D_METHOD("is_region_enabled"), &Sprite3D::is_region_enabled); ClassDB::bind_method(D_METHOD("set_region_rect", "rect"), &Sprite3D::set_region_rect); ClassDB::bind_method(D_METHOD("get_region_rect"), &Sprite3D::get_region_rect); @@ -659,14 +659,14 @@ void Sprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region", "is_region"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); ADD_SIGNAL(MethodInfo("frame_changed")); } Sprite3D::Sprite3D() { - region = false; + region_enabled = false; frame = 0; vframes = 1; hframes = 1; diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index e46a53da0d..b48660eb2d 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -107,8 +107,8 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - void set_region(bool p_region); - bool is_region() const; + void set_region_enabled(bool p_region_enabled); + bool is_region_enabled() const; void set_region_rect(const Rect2 &p_region_rect); Rect2 get_region_rect() const; @@ -147,7 +147,7 @@ class Sprite3D : public SpriteBase3D { GDCLASS(Sprite3D, SpriteBase3D); Ref<Texture2D> texture; - bool region; + bool region_enabled; Rect2 region_rect; int frame; @@ -167,8 +167,8 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - void set_region(bool p_region); - bool is_region() const; + void set_region_enabled(bool p_region_enabled); + bool is_region_enabled() const; void set_region_rect(const Rect2 &p_region_rect); Rect2 get_region_rect() const; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index c570438b6a..7407ad5b8f 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -313,7 +313,7 @@ BoxContainer::AlignMode BoxContainer::get_alignment() const { return align; } -void BoxContainer::add_spacer(bool p_begin) { +Control *BoxContainer::add_spacer(bool p_begin) { Control *c = memnew(Control); c->set_mouse_filter(MOUSE_FILTER_PASS); //allow spacer to pass mouse events @@ -327,6 +327,8 @@ void BoxContainer::add_spacer(bool p_begin) { if (p_begin) { move_child(c, 0); } + + return c; } BoxContainer::BoxContainer(bool p_vertical) { diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 31050d1feb..23feea565c 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -55,7 +55,7 @@ protected: static void _bind_methods(); public: - void add_spacer(bool p_begin = false); + Control *add_spacer(bool p_begin = false); void set_alignment(AlignMode p_align); AlignMode get_alignment() const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 331f0380c5..71d31434d4 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -180,7 +180,12 @@ void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { accept_event(); } else if (mm.is_valid() && is_pressing) { if (is_resizing) { - ge->set_minimap_size(ge->get_minimap_size() - mm->get_relative()); + // Prevent setting minimap wider than GraphEdit + Vector2 new_minimap_size; + new_minimap_size.x = MIN(get_size().x - mm->get_relative().x, ge->get_size().x - 2.0 * minimap_padding.x); + new_minimap_size.y = MIN(get_size().y - mm->get_relative().y, ge->get_size().y - 2.0 * minimap_padding.y); + ge->set_minimap_size(new_minimap_size); + update(); } else { Vector2 click_position = _convert_to_graph_position(mm->get_position() - minimap_padding) - graph_padding; @@ -1312,25 +1317,17 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { minimap->update(); } - if (b->get_button_index() == BUTTON_WHEEL_UP && b->is_pressed()) { - //too difficult to get right - //set_zoom(zoom*ZOOM_SCALE); - } - - if (b->get_button_index() == BUTTON_WHEEL_DOWN && b->is_pressed()) { - //too difficult to get right - //set_zoom(zoom/ZOOM_SCALE); - } - if (b->get_button_index() == BUTTON_WHEEL_UP && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (b->get_button_index() == BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + set_zoom(zoom * ZOOM_SCALE); + } else if (b->get_button_index() == BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + set_zoom(zoom / ZOOM_SCALE); + } else if (b->get_button_index() == BUTTON_WHEEL_UP && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); - } - if (b->get_button_index() == BUTTON_WHEEL_DOWN && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + } else if (b->get_button_index() == BUTTON_WHEEL_DOWN && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); - } - if (b->get_button_index() == BUTTON_WHEEL_RIGHT || (b->get_button_index() == BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + } else if (b->get_button_index() == BUTTON_WHEEL_RIGHT || (b->get_button_index() == BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * b->get_factor() / 8); - } - if (b->get_button_index() == BUTTON_WHEEL_LEFT || (b->get_button_index() == BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + } else if (b->get_button_index() == BUTTON_WHEEL_LEFT || (b->get_button_index() == BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * b->get_factor() / 8); } } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 61266f6d22..cf3978ca41 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1557,7 +1557,7 @@ void TextEdit::_notification(int p_what) { completion_rect.position.x = rect_left_border_x; } - if (cursor_pos.y + row_height + total_height > get_size().height) { + if (cursor_pos.y + row_height + total_height > get_size().height && cursor_pos.y > total_height) { // Completion panel above the cursor line completion_rect.position.y = cursor_pos.y - total_height; } else { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 6f51a61329..0445e5f345 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -410,6 +410,14 @@ bool TreeItem::is_collapsed() { return collapsed; } +void TreeItem::uncollapse_tree() { + TreeItem *t = this; + while (t) { + t->set_collapsed(false); + t = t->parent; + } +} + void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; _changed_notify(); @@ -842,6 +850,8 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); + ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree); + ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height); ClassDB::bind_method(D_METHOD("get_custom_minimum_height"), &TreeItem::get_custom_minimum_height); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 1be21cb4a4..d1407e24d4 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -229,6 +229,8 @@ public: void set_collapsed(bool p_collapsed); bool is_collapsed(); + void uncollapse_tree(); + void set_custom_minimum_height(int p_height); int get_custom_minimum_height() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 9aaddfd373..66f3a2ebde 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -389,6 +389,7 @@ void SceneTree::set_group(const StringName &p_group, const String &p_name, const } void SceneTree::initialize() { + ERR_FAIL_COND(!root); initialized = true; root->_set_tree(this); MainLoop::initialize(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index d697a1a5dd..c146ae6869 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -893,12 +893,13 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { } if (exclusive_child != nullptr) { + /* Window *focus_target = exclusive_child; focus_target->grab_focus(); while (focus_target->exclusive_child != nullptr) { focus_target = focus_target->exclusive_child; focus_target->grab_focus(); - } + }*/ if (!is_embedding_subwindows()) { //not embedding, no need for event return; diff --git a/scene/resources/concave_polygon_shape_3d.cpp b/scene/resources/concave_polygon_shape_3d.cpp index f067695d7d..3fed700383 100644 --- a/scene/resources/concave_polygon_shape_3d.cpp +++ b/scene/resources/concave_polygon_shape_3d.cpp @@ -35,13 +35,12 @@ Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const { Set<DrawEdge> edges; - Vector<Vector3> data = get_faces(); - int datalen = data.size(); - ERR_FAIL_COND_V((datalen % 3) != 0, Vector<Vector3>()); + int index_count = faces.size(); + ERR_FAIL_COND_V((index_count % 3) != 0, Vector<Vector3>()); - const Vector3 *r = data.ptr(); + const Vector3 *r = faces.ptr(); - for (int i = 0; i < datalen; i += 3) { + for (int i = 0; i < index_count; i += 3) { for (int j = 0; j < 3; j++) { DrawEdge de(r[i + j], r[i + ((j + 1) % 3)]); edges.insert(de); @@ -71,22 +70,46 @@ real_t ConcavePolygonShape3D::get_enclosing_radius() const { } void ConcavePolygonShape3D::_update_shape() { + Dictionary d; + d["faces"] = faces; + d["backface_collision"] = backface_collision; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); } void ConcavePolygonShape3D::set_faces(const Vector<Vector3> &p_faces) { - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), p_faces); + faces = p_faces; + _update_shape(); notify_change_to_owners(); } Vector<Vector3> ConcavePolygonShape3D::get_faces() const { - return PhysicsServer3D::get_singleton()->shape_get_data(get_shape()); + return faces; +} + +void ConcavePolygonShape3D::set_backface_collision_enabled(bool p_enabled) { + backface_collision = p_enabled; + + if (!faces.is_empty()) { + _update_shape(); + notify_change_to_owners(); + } +} + +bool ConcavePolygonShape3D::is_backface_collision_enabled() const { + return backface_collision; } void ConcavePolygonShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_faces", "faces"), &ConcavePolygonShape3D::set_faces); ClassDB::bind_method(D_METHOD("get_faces"), &ConcavePolygonShape3D::get_faces); + + ClassDB::bind_method(D_METHOD("set_backface_collision_enabled", "enabled"), &ConcavePolygonShape3D::set_backface_collision_enabled); + ClassDB::bind_method(D_METHOD("is_backface_collision_enabled"), &ConcavePolygonShape3D::is_backface_collision_enabled); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backface_collision"), "set_backface_collision_enabled", "is_backface_collision_enabled"); } ConcavePolygonShape3D::ConcavePolygonShape3D() : diff --git a/scene/resources/concave_polygon_shape_3d.h b/scene/resources/concave_polygon_shape_3d.h index 391459a3d7..bb28359dcc 100644 --- a/scene/resources/concave_polygon_shape_3d.h +++ b/scene/resources/concave_polygon_shape_3d.h @@ -36,6 +36,9 @@ class ConcavePolygonShape3D : public Shape3D { GDCLASS(ConcavePolygonShape3D, Shape3D); + Vector<Vector3> faces; + bool backface_collision = false; + struct DrawEdge { Vector3 a; Vector3 b; @@ -65,6 +68,9 @@ public: void set_faces(const Vector<Vector3> &p_faces); Vector<Vector3> get_faces() const; + void set_backface_collision_enabled(bool p_enabled); + bool is_backface_collision_enabled() const; + virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 47933bd69a..3d3900ecc5 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -1059,6 +1059,10 @@ void SurfaceTool::set_material(const Ref<Material> &p_material) { material = p_material; } +Ref<Material> SurfaceTool::get_material() const { + return material; +} + void SurfaceTool::clear() { begun = false; primitive = Mesh::PRIMITIVE_LINES; @@ -1088,6 +1092,10 @@ void SurfaceTool::set_custom_format(int p_index, CustomFormat p_format) { ERR_FAIL_COND(begun); last_custom_format[p_index] = p_format; } + +Mesh::PrimitiveType SurfaceTool::get_primitive() const { + return primitive; +} SurfaceTool::CustomFormat SurfaceTool::get_custom_format(int p_index) const { ERR_FAIL_INDEX_V(p_index, RS::ARRAY_CUSTOM_COUNT, CUSTOM_MAX); return last_custom_format[p_index]; @@ -1174,6 +1182,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_lod", "nd_threshold", "target_index_count"), &SurfaceTool::generate_lod, DEFVAL(3)); ClassDB::bind_method(D_METHOD("set_material", "material"), &SurfaceTool::set_material); + ClassDB::bind_method(D_METHOD("get_primitive"), &SurfaceTool::get_primitive); ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 17efdcba71..28addf2245 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -143,6 +143,8 @@ public: void set_custom_format(int p_index, CustomFormat p_format); CustomFormat get_custom_format(int p_index) const; + Mesh::PrimitiveType get_primitive() const; + void begin(Mesh::PrimitiveType p_primitive); void set_color(Color p_color); @@ -171,6 +173,7 @@ public: Vector<int> generate_lod(float p_threshold, int p_target_index_count = 3); void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; void clear(); diff --git a/servers/physics_2d/body_pair_2d_sw.cpp b/servers/physics_2d/body_pair_2d_sw.cpp index feced36a2b..da70ac7d4b 100644 --- a/servers/physics_2d/body_pair_2d_sw.cpp +++ b/servers/physics_2d/body_pair_2d_sw.cpp @@ -221,11 +221,21 @@ real_t combine_friction(Body2DSW *A, Body2DSW *B) { bool BodyPair2DSW::setup(real_t p_step) { //cannot collide - if (!A->test_collision_mask(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self()) || (A->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC && B->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC && A->get_max_contacts_reported() == 0 && B->get_max_contacts_reported() == 0)) { + if (!A->test_collision_mask(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self())) { collided = false; return false; } + bool report_contacts_only = false; + if ((A->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC) && (B->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC)) { + if ((A->get_max_contacts_reported() > 0) || (B->get_max_contacts_reported() > 0)) { + report_contacts_only = true; + } else { + collided = false; + return false; + } + } + if (A->is_shape_set_as_disabled(shape_A) || B->is_shape_set_as_disabled(shape_B)) { collided = false; return false; @@ -350,51 +360,44 @@ bool BodyPair2DSW::setup(real_t p_step) { for (int i = 0; i < contact_count; i++) { Contact &c = contacts[i]; + c.active = false; + Vector2 global_A = xform_Au.xform(c.local_A); Vector2 global_B = xform_Bu.xform(c.local_B); real_t depth = c.normal.dot(global_A - global_B); if (depth <= 0 || !c.reused) { - c.active = false; continue; } - c.active = true; #ifdef DEBUG_ENABLED if (space->is_debugging_contacts()) { space->add_debug_contact(global_A + offset_A); space->add_debug_contact(global_B + offset_A); } #endif - int gather_A = A->can_report_contacts(); - int gather_B = B->can_report_contacts(); c.rA = global_A; c.rB = global_B - offset_B; - if (gather_A | gather_B) { - //Vector2 crB( -B->get_angular_velocity() * c.rB.y, B->get_angular_velocity() * c.rB.x ); - - global_A += offset_A; - global_B += offset_A; + if (A->can_report_contacts()) { + Vector2 crB(-B->get_angular_velocity() * c.rB.y, B->get_angular_velocity() * c.rB.x); + A->add_contact(global_A + offset_A, -c.normal, depth, shape_A, global_B + offset_A, shape_B, B->get_instance_id(), B->get_self(), crB + B->get_linear_velocity()); + } - if (gather_A) { - Vector2 crB(-B->get_angular_velocity() * c.rB.y, B->get_angular_velocity() * c.rB.x); - A->add_contact(global_A, -c.normal, depth, shape_A, global_B, shape_B, B->get_instance_id(), B->get_self(), crB + B->get_linear_velocity()); - } - if (gather_B) { - Vector2 crA(-A->get_angular_velocity() * c.rA.y, A->get_angular_velocity() * c.rA.x); - B->add_contact(global_B, c.normal, depth, shape_B, global_A, shape_A, A->get_instance_id(), A->get_self(), crA + A->get_linear_velocity()); - } + if (B->can_report_contacts()) { + Vector2 crA(-A->get_angular_velocity() * c.rA.y, A->get_angular_velocity() * c.rA.x); + B->add_contact(global_B + offset_A, c.normal, depth, shape_B, global_A + offset_A, shape_A, A->get_instance_id(), A->get_self(), crA + A->get_linear_velocity()); } - if ((A->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC && B->get_mode() <= PhysicsServer2D::BODY_MODE_KINEMATIC)) { - c.active = false; + if (report_contacts_only) { collided = false; continue; } + c.active = true; + // Precompute normal mass, tangent mass, and bias. real_t rnA = c.rA.dot(c.normal); real_t rnB = c.rB.dot(c.normal); diff --git a/servers/physics_3d/body_3d_sw.cpp b/servers/physics_3d/body_3d_sw.cpp index 9eff14bbeb..64ba0cb09d 100644 --- a/servers/physics_3d/body_3d_sw.cpp +++ b/servers/physics_3d/body_3d_sw.cpp @@ -501,20 +501,18 @@ void Body3DSW::integrate_forces(real_t p_step) { if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC) { //compute motion, angular and etc. velocities from prev transform - linear_velocity = (new_transform.origin - get_transform().origin) / p_step; + motion = new_transform.origin - get_transform().origin; + do_motion = true; + linear_velocity = motion / p_step; //compute a FAKE angular velocity, not so easy - Basis rot = new_transform.basis.orthonormalized().transposed() * get_transform().basis.orthonormalized(); + Basis rot = new_transform.basis.orthonormalized() * get_transform().basis.orthonormalized().transposed(); Vector3 axis; real_t angle; rot.get_axis_angle(axis, angle); axis.normalize(); - angular_velocity = axis.normalized() * (angle / p_step); - - motion = new_transform.origin - get_transform().origin; - do_motion = true; - + angular_velocity = axis * (angle / p_step); } else { if (!omit_force_integration && !first_integration) { //overridden by direct state query diff --git a/servers/physics_3d/body_3d_sw.h b/servers/physics_3d/body_3d_sw.h index 8e21003a5f..e87ff2364b 100644 --- a/servers/physics_3d/body_3d_sw.h +++ b/servers/physics_3d/body_3d_sw.h @@ -290,10 +290,10 @@ public: void update_inertias(); _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } - _FORCE_INLINE_ Vector3 get_inv_inertia() const { return _inv_inertia; } - _FORCE_INLINE_ Basis get_inv_inertia_tensor() const { return _inv_inertia_tensor; } + _FORCE_INLINE_ const Vector3 &get_inv_inertia() const { return _inv_inertia; } + _FORCE_INLINE_ const Basis &get_inv_inertia_tensor() const { return _inv_inertia_tensor; } _FORCE_INLINE_ real_t get_friction() const { return friction; } - _FORCE_INLINE_ Vector3 get_gravity() const { return gravity; } + _FORCE_INLINE_ const Vector3 &get_gravity() const { return gravity; } _FORCE_INLINE_ real_t get_bounce() const { return bounce; } void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool lock); diff --git a/servers/physics_3d/body_pair_3d_sw.cpp b/servers/physics_3d/body_pair_3d_sw.cpp index 6012ff1522..36114c0c91 100644 --- a/servers/physics_3d/body_pair_3d_sw.cpp +++ b/servers/physics_3d/body_pair_3d_sw.cpp @@ -49,12 +49,12 @@ #define MIN_VELOCITY 0.0001 #define MAX_BIAS_ROTATION (Math_PI / 8) -void BodyPair3DSW::_contact_added_callback(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata) { +void BodyPair3DSW::_contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { BodyPair3DSW *pair = (BodyPair3DSW *)p_userdata; - pair->contact_added_callback(p_point_A, p_point_B); + pair->contact_added_callback(p_point_A, p_index_A, p_point_B, p_index_B); } -void BodyPair3DSW::contact_added_callback(const Vector3 &p_point_A, const Vector3 &p_point_B) { +void BodyPair3DSW::contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B) { // check if we already have the contact //Vector3 local_A = A->get_inv_transform().xform(p_point_A); @@ -73,6 +73,8 @@ void BodyPair3DSW::contact_added_callback(const Vector3 &p_point_A, const Vector contact.acc_bias_impulse = 0; contact.acc_bias_impulse_center_of_mass = 0; contact.acc_tangent_impulse = Vector3(); + contact.index_A = p_index_A; + contact.index_B = p_index_B; contact.local_A = local_A; contact.local_B = local_B; contact.normal = (p_point_A - p_point_B).normalized(); @@ -211,11 +213,21 @@ real_t combine_friction(Body3DSW *A, Body3DSW *B) { bool BodyPair3DSW::setup(real_t p_step) { //cannot collide - if (!A->test_collision_mask(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self()) || (A->get_mode() <= PhysicsServer3D::BODY_MODE_KINEMATIC && B->get_mode() <= PhysicsServer3D::BODY_MODE_KINEMATIC && A->get_max_contacts_reported() == 0 && B->get_max_contacts_reported() == 0)) { + if (!A->test_collision_mask(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self())) { collided = false; return false; } + bool report_contacts_only = false; + if ((A->get_mode() <= PhysicsServer3D::BODY_MODE_KINEMATIC) && (B->get_mode() <= PhysicsServer3D::BODY_MODE_KINEMATIC)) { + if ((A->get_max_contacts_reported() > 0) || (B->get_max_contacts_reported() > 0)) { + report_contacts_only = true; + } else { + collided = false; + return false; + } + } + if (A->is_shape_set_as_disabled(shape_A) || B->is_shape_set_as_disabled(shape_B)) { collided = false; return false; @@ -279,12 +291,9 @@ bool BodyPair3DSW::setup(real_t p_step) { real_t depth = c.normal.dot(global_A - global_B); if (depth <= 0) { - c.active = false; continue; } - c.active = true; - #ifdef DEBUG_ENABLED if (space->is_debugging_contacts()) { @@ -308,6 +317,11 @@ bool BodyPair3DSW::setup(real_t p_step) { B->add_contact(global_B, c.normal, depth, shape_B, global_A, shape_A, A->get_instance_id(), A->get_self(), crB); } + if (report_contacts_only) { + collided = false; + continue; + } + c.active = true; // Precompute normal mass, tangent mass, and bias. @@ -456,7 +470,7 @@ void BodyPair3DSW::solve(real_t p_step) { } BodyPair3DSW::BodyPair3DSW(Body3DSW *p_A, int p_shape_A, Body3DSW *p_B, int p_shape_B) : - Constraint3DSW(_arr, 2) { + BodyContact3DSW(_arr, 2) { A = p_A; B = p_B; shape_A = p_shape_A; @@ -472,3 +486,305 @@ BodyPair3DSW::~BodyPair3DSW() { A->remove_constraint(this); B->remove_constraint(this); } + +void BodySoftBodyPair3DSW::_contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { + BodySoftBodyPair3DSW *pair = (BodySoftBodyPair3DSW *)p_userdata; + pair->contact_added_callback(p_point_A, p_index_A, p_point_B, p_index_B); +} + +void BodySoftBodyPair3DSW::contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B) { + Vector3 local_A = body->get_inv_transform().xform(p_point_A); + Vector3 local_B = p_point_B - soft_body->get_node_position(p_index_B); + + Contact contact; + contact.index_A = p_index_A; + contact.index_B = p_index_B; + contact.acc_normal_impulse = 0; + contact.acc_bias_impulse = 0; + contact.acc_bias_impulse_center_of_mass = 0; + contact.acc_tangent_impulse = Vector3(); + contact.local_A = local_A; + contact.local_B = local_B; + contact.normal = (p_point_A - p_point_B).normalized(); + contact.mass_normal = 0; + + // Attempt to determine if the contact will be reused. + real_t contact_recycle_radius = space->get_contact_recycle_radius(); + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + if (c.index_B == p_index_B) { + if (c.local_A.distance_squared_to(local_A) < (contact_recycle_radius * contact_recycle_radius) && + c.local_B.distance_squared_to(local_B) < (contact_recycle_radius * contact_recycle_radius)) { + contact.acc_normal_impulse = c.acc_normal_impulse; + contact.acc_bias_impulse = c.acc_bias_impulse; + contact.acc_bias_impulse_center_of_mass = c.acc_bias_impulse_center_of_mass; + contact.acc_tangent_impulse = c.acc_tangent_impulse; + } + c = contact; + return; + } + } + + contacts.push_back(contact); +} + +void BodySoftBodyPair3DSW::validate_contacts() { + // Make sure to erase contacts that are no longer valid. + const Transform &transform_A = body->get_transform(); + + real_t contact_max_separation = space->get_contact_max_separation(); + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + + Vector3 global_A = transform_A.xform(c.local_A); + Vector3 global_B = soft_body->get_node_position(c.index_B) + c.local_B; + Vector3 axis = global_A - global_B; + real_t depth = axis.dot(c.normal); + + if (depth < -contact_max_separation || (global_B + c.normal * depth - global_A).length() > contact_max_separation) { + // Contact no longer needed, remove. + if ((contact_index + 1) < contact_count) { + // Swap with the last one. + SWAP(c, contacts[contact_count - 1]); + } + + contact_index--; + contact_count--; + } + } + + contacts.resize(contact_count); +} + +bool BodySoftBodyPair3DSW::setup(real_t p_step) { + if (!body->test_collision_mask(soft_body) || body->has_exception(soft_body->get_self()) || soft_body->has_exception(body->get_self())) { + collided = false; + return false; + } + + if (body->is_shape_set_as_disabled(body_shape)) { + collided = false; + return false; + } + + const Transform &xform_Au = body->get_transform(); + Transform xform_A = xform_Au * body->get_shape_transform(body_shape); + + Transform xform_Bu = soft_body->get_transform(); + Transform xform_B = xform_Bu * soft_body->get_shape_transform(0); + + validate_contacts(); + + Shape3DSW *shape_A_ptr = body->get_shape(body_shape); + Shape3DSW *shape_B_ptr = soft_body->get_shape(0); + + bool collided = CollisionSolver3DSW::solve_static(shape_A_ptr, xform_A, shape_B_ptr, xform_B, _contact_added_callback, this, &sep_axis); + this->collided = collided; + + real_t max_penetration = space->get_contact_max_allowed_penetration(); + + real_t bias = (real_t)0.3; + if (shape_A_ptr->get_custom_bias()) { + bias = shape_A_ptr->get_custom_bias(); + } + + real_t inv_dt = 1.0 / p_step; + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + c.active = false; + + real_t node_inv_mass = soft_body->get_node_inv_mass(c.index_B); + if (node_inv_mass == 0.0) { + continue; + } + + Vector3 global_A = xform_Au.xform(c.local_A); + Vector3 global_B = soft_body->get_node_position(c.index_B) + c.local_B; + + real_t depth = c.normal.dot(global_A - global_B); + + if (depth <= 0) { + continue; + } + + c.active = true; + +#ifdef DEBUG_ENABLED + + if (space->is_debugging_contacts()) { + space->add_debug_contact(global_A); + space->add_debug_contact(global_B); + } +#endif + + c.rA = global_A - xform_Au.origin - body->get_center_of_mass(); + c.rB = global_B; + + if (body->can_report_contacts()) { + Vector3 crA = body->get_angular_velocity().cross(c.rA) + body->get_linear_velocity(); + body->add_contact(global_A, -c.normal, depth, body_shape, global_B, 0, soft_body->get_instance_id(), soft_body->get_self(), crA); + } + + if (body->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC) { + body->set_active(true); + } + + // Precompute normal mass, tangent mass, and bias. + Vector3 inertia_A = body->get_inv_inertia_tensor().xform(c.rA.cross(c.normal)); + real_t kNormal = body->get_inv_mass() + node_inv_mass; + kNormal += c.normal.dot(inertia_A.cross(c.rA)); + c.mass_normal = 1.0f / kNormal; + + c.bias = -bias * inv_dt * MIN(0.0f, -depth + max_penetration); + c.depth = depth; + + Vector3 j_vec = c.normal * c.acc_normal_impulse + c.acc_tangent_impulse; + body->apply_impulse(c.rA + body->get_center_of_mass(), -j_vec); + soft_body->apply_node_impulse(c.index_B, j_vec); + c.acc_bias_impulse = 0; + c.acc_bias_impulse_center_of_mass = 0; + + c.bounce = body->get_bounce(); + + if (c.bounce) { + Vector3 crA = body->get_angular_velocity().cross(c.rA); + Vector3 dv = soft_body->get_node_velocity(c.index_B) - body->get_linear_velocity() - crA; + + // Normal impulse. + c.bounce = c.bounce * dv.dot(c.normal); + } + } + + return true; +} + +void BodySoftBodyPair3DSW::solve(real_t p_step) { + if (!collided) { + return; + } + + uint32_t contact_count = contacts.size(); + for (uint32_t contact_index = 0; contact_index < contact_count; ++contact_index) { + Contact &c = contacts[contact_index]; + if (!c.active) { + continue; + } + + c.active = false; + + // Bias impulse. + Vector3 crbA = body->get_biased_angular_velocity().cross(c.rA); + Vector3 dbv = soft_body->get_node_biased_velocity(c.index_B) - body->get_biased_linear_velocity() - crbA; + + real_t vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn = (-vbn + c.bias) * c.mass_normal; + real_t jbnOld = c.acc_bias_impulse; + c.acc_bias_impulse = MAX(jbnOld + jbn, 0.0f); + + Vector3 jb = c.normal * (c.acc_bias_impulse - jbnOld); + + body->apply_bias_impulse(c.rA + body->get_center_of_mass(), -jb, MAX_BIAS_ROTATION / p_step); + soft_body->apply_node_bias_impulse(c.index_B, jb); + + crbA = body->get_biased_angular_velocity().cross(c.rA); + dbv = soft_body->get_node_biased_velocity(c.index_B) - body->get_biased_linear_velocity() - crbA; + + vbn = dbv.dot(c.normal); + + if (Math::abs(-vbn + c.bias) > MIN_VELOCITY) { + real_t jbn_com = (-vbn + c.bias) / (body->get_inv_mass() + soft_body->get_node_inv_mass(c.index_B)); + real_t jbnOld_com = c.acc_bias_impulse_center_of_mass; + c.acc_bias_impulse_center_of_mass = MAX(jbnOld_com + jbn_com, 0.0f); + + Vector3 jb_com = c.normal * (c.acc_bias_impulse_center_of_mass - jbnOld_com); + + body->apply_bias_impulse(body->get_center_of_mass(), -jb_com, 0.0f); + soft_body->apply_node_bias_impulse(c.index_B, -jb_com); + } + + c.active = true; + } + + Vector3 crA = body->get_angular_velocity().cross(c.rA); + Vector3 dv = soft_body->get_node_velocity(c.index_B) - body->get_linear_velocity() - crA; + + // Normal impulse. + real_t vn = dv.dot(c.normal); + + if (Math::abs(vn) > MIN_VELOCITY) { + real_t jn = -(c.bounce + vn) * c.mass_normal; + real_t jnOld = c.acc_normal_impulse; + c.acc_normal_impulse = MAX(jnOld + jn, 0.0f); + + Vector3 j = c.normal * (c.acc_normal_impulse - jnOld); + + body->apply_impulse(c.rA + body->get_center_of_mass(), -j); + soft_body->apply_node_impulse(c.index_B, j); + + c.active = true; + } + + // Friction impulse. + real_t friction = body->get_friction(); + + Vector3 lvA = body->get_linear_velocity() + body->get_angular_velocity().cross(c.rA); + Vector3 lvB = soft_body->get_node_velocity(c.index_B); + Vector3 dtv = lvB - lvA; + + real_t tn = c.normal.dot(dtv); + + // Tangential velocity. + Vector3 tv = dtv - c.normal * tn; + real_t tvl = tv.length(); + + if (tvl > MIN_VELOCITY) { + tv /= tvl; + + Vector3 temp1 = body->get_inv_inertia_tensor().xform(c.rA.cross(tv)); + + real_t t = -tvl / + (body->get_inv_mass() + soft_body->get_node_inv_mass(c.index_B) + tv.dot(temp1.cross(c.rA))); + + Vector3 jt = t * tv; + + Vector3 jtOld = c.acc_tangent_impulse; + c.acc_tangent_impulse += jt; + + real_t fi_len = c.acc_tangent_impulse.length(); + real_t jtMax = c.acc_normal_impulse * friction; + + if (fi_len > CMP_EPSILON && fi_len > jtMax) { + c.acc_tangent_impulse *= jtMax / fi_len; + } + + jt = c.acc_tangent_impulse - jtOld; + + body->apply_impulse(c.rA + body->get_center_of_mass(), -jt); + soft_body->apply_node_impulse(c.index_B, jt); + + c.active = true; + } + } +} + +BodySoftBodyPair3DSW::BodySoftBodyPair3DSW(Body3DSW *p_A, int p_shape_A, SoftBody3DSW *p_B) { + body = p_A; + soft_body = p_B; + body_shape = p_shape_A; + space = p_A->get_space(); + body->add_constraint(this, 0); + soft_body->add_constraint(this); +} + +BodySoftBodyPair3DSW::~BodySoftBodyPair3DSW() { + body->remove_constraint(this); + soft_body->remove_constraint(this); +} diff --git a/servers/physics_3d/body_pair_3d_sw.h b/servers/physics_3d/body_pair_3d_sw.h index 4d049eafdc..74dddfa6aa 100644 --- a/servers/physics_3d/body_pair_3d_sw.h +++ b/servers/physics_3d/body_pair_3d_sw.h @@ -28,32 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BODY_PAIR_SW_H -#define BODY_PAIR_SW_H +#ifndef BODY_PAIR_3D_SW_H +#define BODY_PAIR_3D_SW_H #include "body_3d_sw.h" #include "constraint_3d_sw.h" +#include "core/templates/local_vector.h" +#include "soft_body_3d_sw.h" -class BodyPair3DSW : public Constraint3DSW { - enum { - MAX_CONTACTS = 4 - }; - - union { - struct { - Body3DSW *A; - Body3DSW *B; - }; - - Body3DSW *_arr[2]; - }; - - int shape_A; - int shape_B; - +class BodyContact3DSW : public Constraint3DSW { +protected: struct Contact { Vector3 position; Vector3 normal; + int index_A, index_B; Vector3 local_A, local_B; real_t acc_normal_impulse; // accumulated normal impulse (Pn) Vector3 acc_tangent_impulse; // accumulated tangent impulse (Pt) @@ -68,22 +56,45 @@ class BodyPair3DSW : public Constraint3DSW { Vector3 rA, rB; // Offset in world orientation with respect to center of mass }; + Vector3 sep_axis; + bool collided; + + Space3DSW *space; + + BodyContact3DSW(Body3DSW **p_body_ptr = nullptr, int p_body_count = 0) : + Constraint3DSW(p_body_ptr, p_body_count) { + } +}; + +class BodyPair3DSW : public BodyContact3DSW { + enum { + MAX_CONTACTS = 4 + }; + + union { + struct { + Body3DSW *A; + Body3DSW *B; + }; + + Body3DSW *_arr[2]; + }; + + int shape_A; + int shape_B; + Vector3 offset_B; //use local A coordinates to avoid numerical issues on collision detection - Vector3 sep_axis; Contact contacts[MAX_CONTACTS]; int contact_count; - bool collided; - static void _contact_added_callback(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata); + static void _contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); - void contact_added_callback(const Vector3 &p_point_A, const Vector3 &p_point_B); + void contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B); void validate_contacts(); bool _test_ccd(real_t p_step, Body3DSW *p_A, int p_shape_A, const Transform &p_xform_A, Body3DSW *p_B, int p_shape_B, const Transform &p_xform_B); - Space3DSW *space; - public: bool setup(real_t p_step); void solve(real_t p_step); @@ -92,4 +103,26 @@ public: ~BodyPair3DSW(); }; -#endif // BODY_PAIR__SW_H +class BodySoftBodyPair3DSW : public BodyContact3DSW { + Body3DSW *body; + SoftBody3DSW *soft_body; + + int body_shape; + + LocalVector<Contact> contacts; + + static void _contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); + + void contact_added_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B); + + void validate_contacts(); + +public: + bool setup(real_t p_step); + void solve(real_t p_step); + + BodySoftBodyPair3DSW(Body3DSW *p_A, int p_shape_A, SoftBody3DSW *p_B); + ~BodySoftBodyPair3DSW(); +}; + +#endif // BODY_PAIR_3D_SW_H diff --git a/servers/physics_3d/collision_object_3d_sw.h b/servers/physics_3d/collision_object_3d_sw.h index 3847b81381..85221b7746 100644 --- a/servers/physics_3d/collision_object_3d_sw.h +++ b/servers/physics_3d/collision_object_3d_sw.h @@ -48,7 +48,8 @@ class CollisionObject3DSW : public ShapeOwner3DSW { public: enum Type { TYPE_AREA, - TYPE_BODY + TYPE_BODY, + TYPE_SOFT_BODY, }; private: @@ -129,8 +130,8 @@ public: _FORCE_INLINE_ const AABB &get_shape_aabb(int p_index) const { return shapes[p_index].aabb_cache; } _FORCE_INLINE_ real_t get_shape_area(int p_index) const { return shapes[p_index].area_cache; } - _FORCE_INLINE_ Transform get_transform() const { return transform; } - _FORCE_INLINE_ Transform get_inv_transform() const { return inv_transform; } + _FORCE_INLINE_ const Transform &get_transform() const { return transform; } + _FORCE_INLINE_ const Transform &get_inv_transform() const { return inv_transform; } _FORCE_INLINE_ Space3DSW *get_space() const { return space; } _FORCE_INLINE_ void set_ray_pickable(bool p_enable) { ray_pickable = p_enable; } diff --git a/servers/physics_3d/collision_solver_3d_sat.cpp b/servers/physics_3d/collision_solver_3d_sat.cpp index 33075a38be..9d5448dbfa 100644 --- a/servers/physics_3d/collision_solver_3d_sat.cpp +++ b/servers/physics_3d/collision_solver_3d_sat.cpp @@ -74,9 +74,9 @@ struct _CollectorCallback { _FORCE_INLINE_ void call(const Vector3 &p_point_A, const Vector3 &p_point_B) { if (swap) { - callback(p_point_B, p_point_A, userdata); + callback(p_point_B, 0, p_point_A, 0, userdata); } else { - callback(p_point_A, p_point_B, userdata); + callback(p_point_A, 0, p_point_B, 0, userdata); } } }; @@ -626,7 +626,7 @@ public: } } - _FORCE_INLINE_ bool test_axis(const Vector3 &p_axis) { + _FORCE_INLINE_ bool test_axis(const Vector3 &p_axis, bool p_directional = false) { Vector3 axis = p_axis; if (Math::abs(axis.x) < CMP_EPSILON && @@ -662,7 +662,12 @@ public: //use the smallest depth if (min_B < 0.0) { // could be +0.0, we don't want it to become -0.0 - min_B = -min_B; + if (p_directional) { + min_B = max_B; + axis = -axis; + } else { + min_B = -min_B; + } } if (max_B < min_B) { @@ -680,7 +685,7 @@ public: return true; } - static _FORCE_INLINE_ void test_contact_points(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata) { + static _FORCE_INLINE_ void test_contact_points(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { SeparatorAxisTest<ShapeA, ShapeB, withMargin> *separator = (SeparatorAxisTest<ShapeA, ShapeB, withMargin> *)p_userdata; Vector3 axis = (p_point_B - p_point_A); real_t depth = axis.length(); @@ -1006,23 +1011,31 @@ static void _collision_sphere_face(const Shape3DSW *p_a, const Transform &p_tran p_transform_b.xform(face_B->vertex[2]), }; - if (!separator.test_axis((vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized())) { + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal, !face_B->backface_collision)) { return; } // edges and points of B for (int i = 0; i < 3; i++) { Vector3 n1 = vertex[i] - p_transform_a.origin; + if (n1.dot(normal) < 0.0) { + n1 *= -1.0; + } - if (!separator.test_axis(n1.normalized())) { + if (!separator.test_axis(n1.normalized(), !face_B->backface_collision)) { return; } Vector3 n2 = vertex[(i + 1) % 3] - vertex[i]; Vector3 axis = n1.cross(n2).cross(n2).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1467,15 +1480,20 @@ static void _collision_box_face(const Shape3DSW *p_a, const Transform &p_transfo p_transform_b.xform(face_B->vertex[2]), }; - if (!separator.test_axis((vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized())) { + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal, !face_B->backface_collision)) { return; } // faces of A for (int i = 0; i < 3; i++) { Vector3 axis = p_transform_a.basis.get_axis(i).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1486,9 +1504,12 @@ static void _collision_box_face(const Shape3DSW *p_a, const Transform &p_transfo Vector3 e = vertex[i] - vertex[(i + 1) % 3]; for (int j = 0; j < 3; j++) { - Vector3 axis = p_transform_a.basis.get_axis(j); + Vector3 axis = e.cross(p_transform_a.basis.get_axis(j)).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(e.cross(axis).normalized())) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1508,8 +1529,11 @@ static void _collision_box_face(const Shape3DSW *p_a, const Transform &p_transfo (cnormal_a.z < 0) ? -box_A->get_half_extents().z : box_A->get_half_extents().z)); Vector3 axis_ab = support_a - vertex[v]; + if (axis_ab.dot(normal) < 0.0) { + axis_ab *= -1.0; + } - if (!separator.test_axis(axis_ab.normalized())) { + if (!separator.test_axis(axis_ab.normalized(), !face_B->backface_collision)) { return; } @@ -1519,7 +1543,12 @@ static void _collision_box_face(const Shape3DSW *p_a, const Transform &p_transfo //a ->b Vector3 axis_a = p_transform_a.basis.get_axis(i); - if (!separator.test_axis(axis_ab.cross(axis_a).cross(axis_a).normalized())) { + Vector3 axis = axis_ab.cross(axis_a).cross(axis_a).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1544,7 +1573,12 @@ static void _collision_box_face(const Shape3DSW *p_a, const Transform &p_transfo Vector3 n = (p2 - p1); - if (!separator.test_axis((point - p2).cross(n).cross(n).normalized())) { + Vector3 axis = (point - p2).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1759,7 +1793,9 @@ static void _collision_capsule_face(const Shape3DSW *p_a, const Transform &p_tra p_transform_b.xform(face_B->vertex[2]), }; - if (!separator.test_axis((vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized())) { + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal, !face_B->backface_collision)) { return; } @@ -1770,13 +1806,22 @@ static void _collision_capsule_face(const Shape3DSW *p_a, const Transform &p_tra for (int i = 0; i < 3; i++) { // edge-cylinder Vector3 edge_axis = vertex[i] - vertex[(i + 1) % 3]; + Vector3 axis = edge_axis.cross(capsule_axis).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } - if (!separator.test_axis((p_transform_a.origin - vertex[i]).cross(capsule_axis).cross(capsule_axis).normalized())) { + Vector3 dir_axis = (p_transform_a.origin - vertex[i]).cross(capsule_axis).cross(capsule_axis).normalized(); + if (dir_axis.dot(normal) < 0.0) { + dir_axis *= -1.0; + } + + if (!separator.test_axis(dir_axis, !face_B->backface_collision)) { return; } @@ -1785,16 +1830,22 @@ static void _collision_capsule_face(const Shape3DSW *p_a, const Transform &p_tra Vector3 sphere_pos = p_transform_a.origin + ((j == 0) ? capsule_axis : -capsule_axis); Vector3 n1 = sphere_pos - vertex[i]; + if (n1.dot(normal) < 0.0) { + n1 *= -1.0; + } - if (!separator.test_axis(n1.normalized())) { + if (!separator.test_axis(n1.normalized(), !face_B->backface_collision)) { return; } Vector3 n2 = edge_axis; axis = n1.cross(n2).cross(n2); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis.normalized())) { + if (!separator.test_axis(axis.normalized(), !face_B->backface_collision)) { return; } } @@ -1891,18 +1942,21 @@ static void _collision_cylinder_face(const Shape3DSW *p_a, const Transform &p_tr p_transform_b.xform(face_B->vertex[2]), }; + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + // Face B normal. - if (!separator.test_axis((vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized())) { + if (!separator.test_axis(normal, !face_B->backface_collision)) { return; } Vector3 cyl_axis = p_transform_a.basis.get_axis(1).normalized(); + if (cyl_axis.dot(normal) < 0.0) { + cyl_axis *= -1.0; + } // Cylinder end caps. - { - if (!separator.test_axis(cyl_axis)) { - return; - } + if (!separator.test_axis(cyl_axis, !face_B->backface_collision)) { + return; } // Edges of B, cylinder lateral surface. @@ -1913,7 +1967,11 @@ static void _collision_cylinder_face(const Shape3DSW *p_a, const Transform &p_tr continue; } - if (!separator.test_axis(axis.normalized())) { + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis.normalized(), !face_B->backface_collision)) { return; } } @@ -1922,8 +1980,11 @@ static void _collision_cylinder_face(const Shape3DSW *p_a, const Transform &p_tr for (int i = 0; i < 3; i++) { const Vector3 &point = vertex[i]; Vector3 axis = Plane(cyl_axis, 0).project(point).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -1956,8 +2017,11 @@ static void _collision_cylinder_face(const Shape3DSW *p_a, const Transform &p_tr // Axis is orthogonal both to tangent and edge direction. Vector3 axis = tangent.cross(edge_dir); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis.normalized())) { + if (!separator.test_axis(axis.normalized(), !face_B->backface_collision)) { return; } } @@ -2097,7 +2161,9 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform p_transform_b.xform(face_B->vertex[2]), }; - if (!separator.test_axis((vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized())) { + Vector3 normal = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]).normalized(); + + if (!separator.test_axis(normal, !face_B->backface_collision)) { return; } @@ -2105,8 +2171,11 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform for (int i = 0; i < face_count; i++) { //Vector3 axis = p_transform_a.xform( faces[i].plane ).normal; Vector3 axis = p_transform_a.basis.xform(faces[i].plane.normal).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -2119,8 +2188,11 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform Vector3 e2 = vertex[j] - vertex[(j + 1) % 3]; Vector3 axis = e1.cross(e2).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } - if (!separator.test_axis(axis)) { + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -2132,7 +2204,12 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform Vector3 va = p_transform_a.xform(vertices[i]); for (int j = 0; j < 3; j++) { - if (!separator.test_axis((va - vertex[j]).normalized())) { + Vector3 axis = (va - vertex[j]).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -2147,7 +2224,12 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform for (int j = 0; j < 3; j++) { Vector3 e3 = vertex[j]; - if (!separator.test_axis((e1 - e3).cross(n).cross(n).normalized())) { + Vector3 axis = (e1 - e3).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } @@ -2161,7 +2243,12 @@ static void _collision_convex_polygon_face(const Shape3DSW *p_a, const Transform for (int j = 0; j < vertex_count; j++) { Vector3 e3 = p_transform_a.xform(vertices[j]); - if (!separator.test_axis((e1 - e3).cross(n).cross(n).normalized())) { + Vector3 axis = (e1 - e3).cross(n).cross(n).normalized(); + if (axis.dot(normal) < 0.0) { + axis *= -1.0; + } + + if (!separator.test_axis(axis, !face_B->backface_collision)) { return; } } diff --git a/servers/physics_3d/collision_solver_3d_sw.cpp b/servers/physics_3d/collision_solver_3d_sw.cpp index fd9ea00d92..f655c4626c 100644 --- a/servers/physics_3d/collision_solver_3d_sw.cpp +++ b/servers/physics_3d/collision_solver_3d_sw.cpp @@ -30,6 +30,7 @@ #include "collision_solver_3d_sw.h" #include "collision_solver_3d_sat.h" +#include "soft_body_3d_sw.h" #include "gjk_epa.h" @@ -78,9 +79,9 @@ bool CollisionSolver3DSW::solve_static_plane(const Shape3DSW *p_shape_A, const T if (p_result_callback) { if (p_swap_result) { - p_result_callback(supports[i], support_A, p_userdata); + p_result_callback(supports[i], 0, support_A, 0, p_userdata); } else { - p_result_callback(support_A, supports[i], p_userdata); + p_result_callback(support_A, 0, supports[i], 0, p_userdata); } } } @@ -113,14 +114,148 @@ bool CollisionSolver3DSW::solve_ray(const Shape3DSW *p_shape_A, const Transform if (p_result_callback) { if (p_swap_result) { - p_result_callback(support_B, support_A, p_userdata); + p_result_callback(support_B, 0, support_A, 0, p_userdata); } else { - p_result_callback(support_A, support_B, p_userdata); + p_result_callback(support_A, 0, support_B, 0, p_userdata); } } return true; } +struct _SoftBodyContactCollisionInfo { + int node_index = 0; + CollisionSolver3DSW::CallbackResult result_callback = nullptr; + void *userdata = nullptr; + bool swap_result = false; + int contact_count = 0; +}; + +void CollisionSolver3DSW::soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { + _SoftBodyContactCollisionInfo &cinfo = *(_SoftBodyContactCollisionInfo *)(p_userdata); + + ++cinfo.contact_count; + + if (cinfo.swap_result) { + cinfo.result_callback(p_point_B, cinfo.node_index, p_point_A, p_index_A, cinfo.userdata); + } else { + cinfo.result_callback(p_point_A, p_index_A, p_point_B, cinfo.node_index, cinfo.userdata); + } +} + +struct _SoftBodyQueryInfo { + SoftBody3DSW *soft_body = nullptr; + const Shape3DSW *shape_A = nullptr; + const Shape3DSW *shape_B = nullptr; + Transform transform_A; + Transform node_transform; + _SoftBodyContactCollisionInfo contact_info; +#ifdef DEBUG_ENABLED + int node_query_count = 0; + int convex_query_count = 0; +#endif +}; + +bool CollisionSolver3DSW::soft_body_query_callback(uint32_t p_node_index, void *p_userdata) { + _SoftBodyQueryInfo &query_cinfo = *(_SoftBodyQueryInfo *)(p_userdata); + + Vector3 node_position = query_cinfo.soft_body->get_node_position(p_node_index); + + Transform transform_B; + transform_B.origin = query_cinfo.node_transform.xform(node_position); + + query_cinfo.contact_info.node_index = p_node_index; + solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info); + +#ifdef DEBUG_ENABLED + ++query_cinfo.node_query_count; +#endif + + // Continue with the query. + return false; +} + +void CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex) { + _SoftBodyQueryInfo &query_cinfo = *(_SoftBodyQueryInfo *)(p_userdata); + + query_cinfo.shape_A = p_convex; + + // Calculate AABB for internal soft body query (in world space). + AABB shape_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis; + axis[i] = 1.0; + + real_t smin, smax; + p_convex->project_range(axis, query_cinfo.transform_A, smin, smax); + + shape_aabb.position[i] = smin; + shape_aabb.size[i] = smax - smin; + } + + shape_aabb.grow_by(query_cinfo.soft_body->get_collision_margin()); + + query_cinfo.soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo); + +#ifdef DEBUG_ENABLED + ++query_cinfo.convex_query_count; +#endif +} + +bool CollisionSolver3DSW::solve_soft_body(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result) { + const SoftBodyShape3DSW *soft_body_shape_B = static_cast<const SoftBodyShape3DSW *>(p_shape_B); + + SoftBody3DSW *soft_body = soft_body_shape_B->get_soft_body(); + const Transform &world_to_local = soft_body->get_inv_transform(); + + const real_t collision_margin = soft_body->get_collision_margin(); + + SphereShape3DSW sphere_shape; + sphere_shape.set_data(collision_margin); + + _SoftBodyQueryInfo query_cinfo; + query_cinfo.contact_info.result_callback = p_result_callback; + query_cinfo.contact_info.userdata = p_userdata; + query_cinfo.contact_info.swap_result = p_swap_result; + query_cinfo.soft_body = soft_body; + query_cinfo.node_transform = p_transform_B * world_to_local; + query_cinfo.shape_A = p_shape_A; + query_cinfo.transform_A = p_transform_A; + query_cinfo.shape_B = &sphere_shape; + + if (p_shape_A->is_concave()) { + // In case of concave shape, query convex shapes first. + const ConcaveShape3DSW *concave_shape_A = static_cast<const ConcaveShape3DSW *>(p_shape_A); + + AABB soft_body_aabb = soft_body->get_bounds(); + soft_body_aabb.grow_by(collision_margin); + + // Calculate AABB for internal concave shape query (in local space). + AABB local_aabb; + for (int i = 0; i < 3; i++) { + Vector3 axis(p_transform_A.basis.get_axis(i)); + real_t axis_scale = 1.0 / axis.length(); + + real_t smin = soft_body_aabb.position[i]; + real_t smax = smin + soft_body_aabb.size[i]; + + smin *= axis_scale; + smax *= axis_scale; + + local_aabb.position[i] = smin; + local_aabb.size[i] = smax - smin; + } + + concave_shape_A->cull(local_aabb, soft_body_concave_callback, &query_cinfo); + } else { + AABB shape_aabb = p_transform_A.xform(p_shape_A->get_aabb()); + shape_aabb.grow_by(collision_margin); + + soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo); + } + + return (query_cinfo.contact_info.contact_count > 0); +} + struct _ConcaveCollisionInfo { const Transform *transform_A; const Shape3DSW *shape_A; @@ -215,6 +350,9 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo if (type_B == PhysicsServer3D::SHAPE_RAY) { return false; } + if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { + return false; + } if (swap) { return solve_static_plane(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true); @@ -233,6 +371,18 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo return solve_ray(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false); } + } else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) { + if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) { + // Soft Body / Soft Body not supported. + return false; + } + + if (swap) { + return solve_soft_body(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true); + } else { + return solve_soft_body(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false); + } + } else if (concave_B) { if (concave_A) { return false; diff --git a/servers/physics_3d/collision_solver_3d_sw.h b/servers/physics_3d/collision_solver_3d_sw.h index 81d87e9773..34ac2c6d3f 100644 --- a/servers/physics_3d/collision_solver_3d_sw.h +++ b/servers/physics_3d/collision_solver_3d_sw.h @@ -35,12 +35,16 @@ class CollisionSolver3DSW { public: - typedef void (*CallbackResult)(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata); + typedef void (*CallbackResult)(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); private: + static bool soft_body_query_callback(uint32_t p_node_index, void *p_userdata); + static void soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); + static void soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex); static void concave_callback(void *p_userdata, Shape3DSW *p_convex); static bool solve_static_plane(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); static bool solve_ray(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); + static bool solve_soft_body(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result); static bool solve_concave(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0); static void concave_distance_callback(void *p_userdata, Shape3DSW *p_convex); static bool solve_distance_plane(const Shape3DSW *p_shape_A, const Transform &p_transform_A, const Shape3DSW *p_shape_B, const Transform &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B); diff --git a/servers/physics_3d/gjk_epa.cpp b/servers/physics_3d/gjk_epa.cpp index e44c92da79..1a8c7f704f 100644 --- a/servers/physics_3d/gjk_epa.cpp +++ b/servers/physics_3d/gjk_epa.cpp @@ -1011,9 +1011,9 @@ bool gjk_epa_calculate_penetration(const Shape3DSW *p_shape_A, const Transform & if (GjkEpa2::Penetration(p_shape_A, p_transform_A, p_margin_A, p_shape_B, p_transform_B, p_margin_B, p_transform_B.origin - p_transform_A.origin, res)) { if (p_result_callback) { if (p_swap) { - p_result_callback(res.witnesses[1], res.witnesses[0], p_userdata); + p_result_callback(res.witnesses[1], 0, res.witnesses[0], 0, p_userdata); } else { - p_result_callback(res.witnesses[0], res.witnesses[1], p_userdata); + p_result_callback(res.witnesses[0], 0, res.witnesses[1], 0, p_userdata); } } return true; diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp index 6bbef09907..3d0063b0fa 100644 --- a/servers/physics_3d/physics_server_3d_sw.cpp +++ b/servers/physics_3d/physics_server_3d_sw.cpp @@ -611,9 +611,18 @@ uint32_t PhysicsServer3DSW::body_get_collision_mask(RID p_body) const { void PhysicsServer3DSW::body_attach_object_instance_id(RID p_body, ObjectID p_id) { Body3DSW *body = body_owner.getornull(p_body); - ERR_FAIL_COND(!body); + if (body) { + body->set_instance_id(p_id); + return; + } + + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + if (soft_body) { + soft_body->set_instance_id(p_id); + return; + } - body->set_instance_id(p_id); + ERR_FAIL_MSG("Invalid ID."); }; ObjectID PhysicsServer3DSW::body_get_object_instance_id(RID p_body) const { @@ -893,6 +902,266 @@ PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) { return direct_state; } +/* SOFT BODY */ + +RID PhysicsServer3DSW::soft_body_create() { + SoftBody3DSW *soft_body = memnew(SoftBody3DSW); + RID rid = soft_body_owner.make_rid(soft_body); + soft_body->set_self(rid); + return rid; +} + +void PhysicsServer3DSW::soft_body_update_rendering_server(RID p_body, RenderingServerHandler *p_rendering_server_handler) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->update_rendering_server(p_rendering_server_handler); +} + +void PhysicsServer3DSW::soft_body_set_space(RID p_body, RID p_space) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + Space3DSW *space = nullptr; + if (p_space.is_valid()) { + space = space_owner.getornull(p_space); + ERR_FAIL_COND(!space); + } + + if (soft_body->get_space() == space) { + return; + } + + soft_body->set_space(space); +} + +RID PhysicsServer3DSW::soft_body_get_space(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, RID()); + + Space3DSW *space = soft_body->get_space(); + if (!space) { + return RID(); + } + return space->get_self(); +} + +void PhysicsServer3DSW::soft_body_set_collision_layer(RID p_body, uint32_t p_layer) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_collision_layer(p_layer); +} + +uint32_t PhysicsServer3DSW::soft_body_get_collision_layer(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0); + + return soft_body->get_collision_layer(); +} + +void PhysicsServer3DSW::soft_body_set_collision_mask(RID p_body, uint32_t p_mask) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_collision_mask(p_mask); +} + +uint32_t PhysicsServer3DSW::soft_body_get_collision_mask(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0); + + return soft_body->get_collision_mask(); +} + +void PhysicsServer3DSW::soft_body_add_collision_exception(RID p_body, RID p_body_b) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->add_exception(p_body_b); +} + +void PhysicsServer3DSW::soft_body_remove_collision_exception(RID p_body, RID p_body_b) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->remove_exception(p_body_b); +} + +void PhysicsServer3DSW::soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + for (int i = 0; i < soft_body->get_exceptions().size(); i++) { + p_exceptions->push_back(soft_body->get_exceptions()[i]); + } +} + +void PhysicsServer3DSW::soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_state(p_state, p_variant); +} + +Variant PhysicsServer3DSW::soft_body_get_state(RID p_body, BodyState p_state) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, Variant()); + + return soft_body->get_state(p_state); +} + +void PhysicsServer3DSW::soft_body_set_transform(RID p_body, const Transform &p_transform) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_state(BODY_STATE_TRANSFORM, p_transform); +} + +void PhysicsServer3DSW::soft_body_set_ray_pickable(RID p_body, bool p_enable) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_ray_pickable(p_enable); +} + +void PhysicsServer3DSW::soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_iteration_count(p_simulation_precision); +} + +int PhysicsServer3DSW::soft_body_get_simulation_precision(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_iteration_count(); +} + +void PhysicsServer3DSW::soft_body_set_total_mass(RID p_body, real_t p_total_mass) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_total_mass(p_total_mass); +} + +real_t PhysicsServer3DSW::soft_body_get_total_mass(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_total_mass(); +} + +void PhysicsServer3DSW::soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_linear_stiffness(p_stiffness); +} + +real_t PhysicsServer3DSW::soft_body_get_linear_stiffness(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_linear_stiffness(); +} + +void PhysicsServer3DSW::soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_pressure_coefficient(p_pressure_coefficient); +} + +real_t PhysicsServer3DSW::soft_body_get_pressure_coefficient(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_pressure_coefficient(); +} + +void PhysicsServer3DSW::soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_damping_coefficient(p_damping_coefficient); +} + +real_t PhysicsServer3DSW::soft_body_get_damping_coefficient(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_damping_coefficient(); +} + +void PhysicsServer3DSW::soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_drag_coefficient(p_drag_coefficient); +} + +real_t PhysicsServer3DSW::soft_body_get_drag_coefficient(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, 0.f); + + return soft_body->get_drag_coefficient(); +} + +void PhysicsServer3DSW::soft_body_set_mesh(RID p_body, const REF &p_mesh) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_mesh(p_mesh); +} + +AABB PhysicsServer3DSW::soft_body_get_bounds(RID p_body) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, AABB()); + + return soft_body->get_bounds(); +} + +void PhysicsServer3DSW::soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->set_vertex_position(p_point_index, p_global_position); +} + +Vector3 PhysicsServer3DSW::soft_body_get_point_global_position(RID p_body, int p_point_index) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, Vector3()); + + return soft_body->get_vertex_position(p_point_index); +} + +void PhysicsServer3DSW::soft_body_remove_all_pinned_points(RID p_body) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + soft_body->unpin_all_vertices(); +} + +void PhysicsServer3DSW::soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND(!soft_body); + + if (p_pin) { + soft_body->pin_vertex(p_point_index); + } else { + soft_body->unpin_vertex(p_point_index); + } +} + +bool PhysicsServer3DSW::soft_body_is_point_pinned(RID p_body, int p_point_index) const { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_body); + ERR_FAIL_COND_V(!soft_body, false); + + return soft_body->is_vertex_pinned(p_point_index); +} + /* JOINT API */ RID PhysicsServer3DSW::joint_create() { @@ -1278,7 +1547,13 @@ void PhysicsServer3DSW::free(RID p_rid) { body_owner.free(p_rid); memdelete(body); + } else if (soft_body_owner.owns(p_rid)) { + SoftBody3DSW *soft_body = soft_body_owner.getornull(p_rid); + + soft_body->set_space(nullptr); + soft_body_owner.free(p_rid); + memdelete(soft_body); } else if (area_owner.owns(p_rid)) { Area3DSW *area = area_owner.getornull(p_rid); @@ -1444,7 +1719,7 @@ void PhysicsServer3DSW::_update_shapes() { } } -void PhysicsServer3DSW::_shape_col_cbk(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata) { +void PhysicsServer3DSW::_shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { CollCbkData *cbk = (CollCbkData *)p_userdata; if (cbk->max == 0) { diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h index afda161fa8..f92652bfad 100644 --- a/servers/physics_3d/physics_server_3d_sw.h +++ b/servers/physics_3d/physics_server_3d_sw.h @@ -63,6 +63,7 @@ class PhysicsServer3DSW : public PhysicsServer3D { mutable RID_PtrOwner<Space3DSW, true> space_owner; mutable RID_PtrOwner<Area3DSW, true> area_owner; mutable RID_PtrOwner<Body3DSW, true> body_owner; + mutable RID_PtrOwner<SoftBody3DSW, true> soft_body_owner; mutable RID_PtrOwner<Joint3DSW, true> joint_owner; //void _clear_query(QuerySW *p_query); @@ -79,7 +80,7 @@ public: Vector3 *ptr; }; - static void _shape_col_cbk(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata); + static void _shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata); virtual RID plane_shape_create() override; virtual RID ray_shape_create() override; @@ -252,68 +253,58 @@ public: /* SOFT BODY */ - virtual RID soft_body_create() override { return RID(); } + virtual RID soft_body_create() override; - virtual void soft_body_update_rendering_server(RID p_body, class SoftBodyRenderingServerHandler *p_rendering_server_handler) override {} + virtual void soft_body_update_rendering_server(RID p_body, RenderingServerHandler *p_rendering_server_handler) override; - virtual void soft_body_set_space(RID p_body, RID p_space) override {} - virtual RID soft_body_get_space(RID p_body) const override { return RID(); } + virtual void soft_body_set_space(RID p_body, RID p_space) override; + virtual RID soft_body_get_space(RID p_body) const override; - virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) override {} - virtual uint32_t soft_body_get_collision_layer(RID p_body) const override { return 0; } + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) override; + virtual uint32_t soft_body_get_collision_layer(RID p_body) const override; - virtual void soft_body_set_collision_mask(RID p_body, uint32_t p_mask) override {} - virtual uint32_t soft_body_get_collision_mask(RID p_body) const override { return 0; } + virtual void soft_body_set_collision_mask(RID p_body, uint32_t p_mask) override; + virtual uint32_t soft_body_get_collision_mask(RID p_body) const override; - virtual void soft_body_add_collision_exception(RID p_body, RID p_body_b) override {} - virtual void soft_body_remove_collision_exception(RID p_body, RID p_body_b) override {} - virtual void soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) override {} + virtual void soft_body_add_collision_exception(RID p_body, RID p_body_b) override; + virtual void soft_body_remove_collision_exception(RID p_body, RID p_body_b) override; + virtual void soft_body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) override; - virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override {} - virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const override { return Variant(); } + virtual void soft_body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; + virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const override; - virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) override {} - virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const override { return Vector3(); } + virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) override; - virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) override {} + virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) override; - virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) override {} - virtual int soft_body_get_simulation_precision(RID p_body) const override { return 0; } + virtual void soft_body_set_simulation_precision(RID p_body, int p_simulation_precision) override; + virtual int soft_body_get_simulation_precision(RID p_body) const override; - virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) override {} - virtual real_t soft_body_get_total_mass(RID p_body) const override { return 0.; } + virtual void soft_body_set_total_mass(RID p_body, real_t p_total_mass) override; + virtual real_t soft_body_get_total_mass(RID p_body) const override; - virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) override {} - virtual real_t soft_body_get_linear_stiffness(RID p_body) const override { return 0.; } + virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) override; + virtual real_t soft_body_get_linear_stiffness(RID p_body) const override; - virtual void soft_body_set_angular_stiffness(RID p_body, real_t p_stiffness) override {} - virtual real_t soft_body_get_angular_stiffness(RID p_body) const override { return 0.; } + virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) override; + virtual real_t soft_body_get_pressure_coefficient(RID p_body) const override; - virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) override {} - virtual real_t soft_body_get_volume_stiffness(RID p_body) const override { return 0.; } + virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) override; + virtual real_t soft_body_get_damping_coefficient(RID p_body) const override; - virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) override {} - virtual real_t soft_body_get_pressure_coefficient(RID p_body) const override { return 0.; } + virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) override; + virtual real_t soft_body_get_drag_coefficient(RID p_body) const override; - virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) override {} - virtual real_t soft_body_get_pose_matching_coefficient(RID p_body) const override { return 0.; } + virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) override; - virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) override {} - virtual real_t soft_body_get_damping_coefficient(RID p_body) const override { return 0.; } + virtual AABB soft_body_get_bounds(RID p_body) const override; - virtual void soft_body_set_drag_coefficient(RID p_body, real_t p_drag_coefficient) override {} - virtual real_t soft_body_get_drag_coefficient(RID p_body) const override { return 0.; } + virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) override; + virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) const override; - virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) override {} - - virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) override {} - virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) const override { return Vector3(); } - - virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const override { return Vector3(); } - - virtual void soft_body_remove_all_pinned_points(RID p_body) override {} - virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) override {} - virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) const override { return false; } + virtual void soft_body_remove_all_pinned_points(RID p_body) override; + virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) override; + virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) const override; /* JOINT API */ diff --git a/servers/physics_3d/physics_server_3d_wrap_mt.h b/servers/physics_3d/physics_server_3d_wrap_mt.h index f60e1332d5..49ae60db92 100644 --- a/servers/physics_3d/physics_server_3d_wrap_mt.h +++ b/servers/physics_3d/physics_server_3d_wrap_mt.h @@ -273,7 +273,7 @@ public: FUNCRID(soft_body) - FUNC2(soft_body_update_rendering_server, RID, class SoftBodyRenderingServerHandler *) + FUNC2(soft_body_update_rendering_server, RID, class RenderingServerHandler *) FUNC2(soft_body_set_space, RID, RID) FUNC1RC(RID, soft_body_get_space, RID) @@ -294,7 +294,6 @@ public: FUNC2RC(Variant, soft_body_get_state, RID, BodyState); FUNC2(soft_body_set_transform, RID, const Transform &); - FUNC2RC(Vector3, soft_body_get_vertex_position, RID, int); FUNC2(soft_body_set_simulation_precision, RID, int); FUNC1RC(int, soft_body_get_simulation_precision, RID); @@ -305,18 +304,9 @@ public: FUNC2(soft_body_set_linear_stiffness, RID, real_t); FUNC1RC(real_t, soft_body_get_linear_stiffness, RID); - FUNC2(soft_body_set_angular_stiffness, RID, real_t); - FUNC1RC(real_t, soft_body_get_angular_stiffness, RID); - - FUNC2(soft_body_set_volume_stiffness, RID, real_t); - FUNC1RC(real_t, soft_body_get_volume_stiffness, RID); - FUNC2(soft_body_set_pressure_coefficient, RID, real_t); FUNC1RC(real_t, soft_body_get_pressure_coefficient, RID); - FUNC2(soft_body_set_pose_matching_coefficient, RID, real_t); - FUNC1RC(real_t, soft_body_get_pose_matching_coefficient, RID); - FUNC2(soft_body_set_damping_coefficient, RID, real_t); FUNC1RC(real_t, soft_body_get_damping_coefficient, RID); @@ -325,9 +315,10 @@ public: FUNC2(soft_body_set_mesh, RID, const REF &); + FUNC1RC(AABB, soft_body_get_bounds, RID); + FUNC3(soft_body_move_point, RID, int, const Vector3 &); FUNC2RC(Vector3, soft_body_get_point_global_position, RID, int); - FUNC2RC(Vector3, soft_body_get_point_offset, RID, int); FUNC1(soft_body_remove_all_pinned_points, RID); FUNC3(soft_body_pin_point, RID, int, bool); diff --git a/servers/physics_3d/shape_3d_sw.cpp b/servers/physics_3d/shape_3d_sw.cpp index 02d0c66215..687a7b288f 100644 --- a/servers/physics_3d/shape_3d_sw.cpp +++ b/servers/physics_3d/shape_3d_sw.cpp @@ -1134,7 +1134,7 @@ void FaceShape3DSW::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_ Vector3 n = p_normal; /** TEST FACE AS SUPPORT **/ - if (normal.dot(n) > _FACE_IS_VALID_SUPPORT_THRESHOLD) { + if (Math::abs(normal.dot(n)) > _FACE_IS_VALID_SUPPORT_THRESHOLD) { r_amount = 3; r_type = FEATURE_FACE; for (int i = 0; i < 3; i++) { @@ -1187,7 +1187,11 @@ bool FaceShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_e if (c) { r_normal = Plane(vertex[0], vertex[1], vertex[2]).normal; if (r_normal.dot(p_end - p_begin) > 0) { - r_normal = -r_normal; + if (backface_collision) { + r_normal = -r_normal; + } else { + c = false; + } } } @@ -1285,30 +1289,24 @@ void ConcavePolygonShape3DSW::_cull_segment(int p_idx, _SegmentCullParams *p_par } if (bvh->face_index >= 0) { - Vector3 res; - Vector3 vertices[3] = { - p_params->vertices[p_params->faces[bvh->face_index].indices[0]], - p_params->vertices[p_params->faces[bvh->face_index].indices[1]], - p_params->vertices[p_params->faces[bvh->face_index].indices[2]] - }; + const Face *f = &p_params->faces[bvh->face_index]; + FaceShape3DSW *face = p_params->face; + face->normal = f->normal; + face->vertex[0] = p_params->vertices[f->indices[0]]; + face->vertex[1] = p_params->vertices[f->indices[1]]; + face->vertex[2] = p_params->vertices[f->indices[2]]; - if (Geometry3D::segment_intersects_triangle( - p_params->from, - p_params->to, - vertices[0], - vertices[1], - vertices[2], - &res)) { + Vector3 res; + Vector3 normal; + if (face->intersect_segment(p_params->from, p_params->to, res, normal)) { real_t d = p_params->dir.dot(res) - p_params->dir.dot(p_params->from); - //TODO, seems segmen/triangle intersection is broken :( - if (d > 0 && d < p_params->min_d) { + if ((d > 0) && (d < p_params->min_d)) { p_params->min_d = d; p_params->result = res; - p_params->normal = Plane(vertices[0], vertices[1], vertices[2]).normal; + p_params->normal = normal; p_params->collisions++; } } - } else { if (bvh->left >= 0) { _cull_segment(bvh->left, p_params); @@ -1329,17 +1327,20 @@ bool ConcavePolygonShape3DSW::intersect_segment(const Vector3 &p_begin, const Ve const Vector3 *vr = vertices.ptr(); const BVH *br = bvh.ptr(); + FaceShape3DSW face; + face.backface_collision = backface_collision; + _SegmentCullParams params; params.from = p_begin; params.to = p_end; - params.collisions = 0; params.dir = (p_end - p_begin).normalized(); params.faces = fr; params.vertices = vr; params.bvh = br; - params.min_d = 1e20; + params.face = &face; + // cull _cull_segment(0, ¶ms); @@ -1401,6 +1402,7 @@ void ConcavePolygonShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback const BVH *br = bvh.ptr(); FaceShape3DSW face; // use this to send in the callback + face.backface_collision = backface_collision; _CullParams params; params.aabb = local_aabb; @@ -1532,7 +1534,7 @@ void ConcavePolygonShape3DSW::_fill_bvh(_VolumeSW_BVH *p_bvh_tree, BVH *p_bvh_ar memdelete(p_bvh_tree); } -void ConcavePolygonShape3DSW::_setup(Vector<Vector3> p_faces) { +void ConcavePolygonShape3DSW::_setup(const Vector<Vector3> &p_faces, bool p_backface_collision) { int src_face_count = p_faces.size(); if (src_face_count == 0) { configure(AABB()); @@ -1587,15 +1589,24 @@ void ConcavePolygonShape3DSW::_setup(Vector<Vector3> p_faces) { int idx = 0; _fill_bvh(bvh_tree, bvh_arrayw2, idx); + backface_collision = p_backface_collision; + configure(_aabb); // this type of shape has no margin } void ConcavePolygonShape3DSW::set_data(const Variant &p_data) { - _setup(p_data); + Dictionary d = p_data; + ERR_FAIL_COND(!d.has("faces")); + + _setup(d["faces"], d["backface_collision"]); } Variant ConcavePolygonShape3DSW::get_data() const { - return get_faces(); + Dictionary d; + d["faces"] = get_faces(); + d["backface_collision"] = backface_collision; + + return d; } ConcavePolygonShape3DSW::ConcavePolygonShape3DSW() { diff --git a/servers/physics_3d/shape_3d_sw.h b/servers/physics_3d/shape_3d_sw.h index cafe978abb..988e76c699 100644 --- a/servers/physics_3d/shape_3d_sw.h +++ b/servers/physics_3d/shape_3d_sw.h @@ -334,34 +334,37 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW { struct _CullParams { AABB aabb; - Callback callback; - void *userdata; - const Face *faces; - const Vector3 *vertices; - const BVH *bvh; - FaceShape3DSW *face; + Callback callback = nullptr; + void *userdata = nullptr; + const Face *faces = nullptr; + const Vector3 *vertices = nullptr; + const BVH *bvh = nullptr; + FaceShape3DSW *face = nullptr; }; struct _SegmentCullParams { Vector3 from; Vector3 to; - const Face *faces; - const Vector3 *vertices; - const BVH *bvh; Vector3 dir; + const Face *faces = nullptr; + const Vector3 *vertices = nullptr; + const BVH *bvh = nullptr; + FaceShape3DSW *face = nullptr; Vector3 result; Vector3 normal; - real_t min_d; - int collisions; + real_t min_d = 1e20; + int collisions = 0; }; + bool backface_collision = false; + void _cull_segment(int p_idx, _SegmentCullParams *p_params) const; void _cull(int p_idx, _CullParams *p_params) const; void _fill_bvh(_VolumeSW_BVH *p_bvh_tree, BVH *p_bvh_array, int &p_idx); - void _setup(Vector<Vector3> p_faces); + void _setup(const Vector<Vector3> &p_faces, bool p_backface_collision); public: Vector<Vector3> get_faces() const; @@ -424,6 +427,7 @@ public: struct FaceShape3DSW : public Shape3DSW { Vector3 normal; //cache Vector3 vertex[3]; + bool backface_collision = false; virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_CONCAVE_POLYGON; } diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp new file mode 100644 index 0000000000..f63a470cbe --- /dev/null +++ b/servers/physics_3d/soft_body_3d_sw.cpp @@ -0,0 +1,1221 @@ +/*************************************************************************/ +/* soft_body_3d_sw.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "soft_body_3d_sw.h" +#include "space_3d_sw.h" + +#include "core/math/geometry_3d.h" +#include "core/templates/map.h" + +// Based on Bullet soft body. + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ +///btSoftBody implementation by Nathanael Presson + +SoftBody3DSW::SoftBody3DSW() : + CollisionObject3DSW(TYPE_SOFT_BODY), + active_list(this) { + _set_static(false); +} + +void SoftBody3DSW::_shapes_changed() { +} + +void SoftBody3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant) { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + _set_transform(p_variant); + _set_inv_transform(get_transform().inverse()); + + apply_nodes_transform(get_transform()); + + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + // Not supported. + ERR_FAIL_MSG("Linear velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + ERR_FAIL_MSG("Angular velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + ERR_FAIL_MSG("Sleeping state is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + ERR_FAIL_MSG("Sleeping state is not supported for Soft bodies."); + } break; + } +} + +Variant SoftBody3DSW::get_state(PhysicsServer3D::BodyState p_state) const { + switch (p_state) { + case PhysicsServer3D::BODY_STATE_TRANSFORM: { + return get_transform(); + } break; + case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { + ERR_FAIL_V_MSG(Vector3(), "Linear velocity is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { + ERR_FAIL_V_MSG(Vector3(), "Angular velocity is not supported for Soft bodies."); + return Vector3(); + } break; + case PhysicsServer3D::BODY_STATE_SLEEPING: { + ERR_FAIL_V_MSG(false, "Sleeping state is not supported for Soft bodies."); + } break; + case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { + ERR_FAIL_V_MSG(false, "Sleeping state is not supported for Soft bodies."); + } break; + } + + return Variant(); +} + +void SoftBody3DSW::set_space(Space3DSW *p_space) { + if (get_space()) { + get_space()->soft_body_remove_from_active_list(&active_list); + + deinitialize_shape(); + } + + _set_space(p_space); + + if (get_space()) { + get_space()->soft_body_add_to_active_list(&active_list); + + if (bounds != AABB()) { + initialize_shape(true); + } + } +} + +void SoftBody3DSW::set_mesh(const Ref<Mesh> &p_mesh) { + destroy(); + + soft_mesh = p_mesh; + + if (soft_mesh.is_null()) { + return; + } + + Array arrays = soft_mesh->surface_get_arrays(0); + ERR_FAIL_COND(!(soft_mesh->surface_get_format(0) & RS::ARRAY_FORMAT_INDEX)); + + bool success = create_from_trimesh(arrays[RS::ARRAY_INDEX], arrays[RS::ARRAY_VERTEX]); + if (!success) { + destroy(); + soft_mesh = Ref<Mesh>(); + } +} + +void SoftBody3DSW::update_rendering_server(RenderingServerHandler *p_rendering_server_handler) { + if (soft_mesh.is_null()) { + return; + } + + const uint32_t vertex_count = map_visual_to_physics.size(); + for (uint32_t i = 0; i < vertex_count; ++i) { + const uint32_t node_index = map_visual_to_physics[i]; + const Node &node = nodes[node_index]; + const Vector3 &vertex_position = node.x; + const Vector3 &vertex_normal = node.n; + + p_rendering_server_handler->set_vertex(i, &vertex_position); + p_rendering_server_handler->set_normal(i, &vertex_normal); + } + + p_rendering_server_handler->set_aabb(bounds); +} + +void SoftBody3DSW::update_normals() { + uint32_t i, ni; + + for (i = 0, ni = nodes.size(); i < ni; ++i) { + nodes[i].n = Vector3(); + } + + for (i = 0, ni = faces.size(); i < ni; ++i) { + Face &face = faces[i]; + const Vector3 n = vec3_cross(face.n[0]->x - face.n[2]->x, face.n[0]->x - face.n[1]->x); + face.n[0]->n += n; + face.n[1]->n += n; + face.n[2]->n += n; + face.normal = n; + face.normal.normalize(); + } + + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + real_t len = node.n.length(); + if (len > CMP_EPSILON) { + node.n /= len; + } + } +} + +void SoftBody3DSW::update_bounds() { + AABB prev_bounds = bounds; + prev_bounds.grow_by(collision_margin); + + bounds = AABB(); + + const uint32_t nodes_count = nodes.size(); + if (nodes_count == 0) { + deinitialize_shape(); + return; + } + + bool first = true; + bool moved = false; + for (uint32_t node_index = 0; node_index < nodes_count; ++node_index) { + const Node &node = nodes[node_index]; + if (!prev_bounds.has_point(node.x)) { + moved = true; + } + if (first) { + bounds.position = node.x; + first = false; + } else { + bounds.expand_to(node.x); + } + } + + if (get_space()) { + initialize_shape(moved); + } +} + +void SoftBody3DSW::update_constants() { + reset_link_rest_lengths(); + update_link_constants(); + update_area(); +} + +void SoftBody3DSW::update_area() { + int i, ni; + + // Face area. + for (i = 0, ni = faces.size(); i < ni; ++i) { + Face &face = faces[i]; + + const Vector3 &x0 = face.n[0]->x; + const Vector3 &x1 = face.n[1]->x; + const Vector3 &x2 = face.n[2]->x; + + const Vector3 a = x1 - x0; + const Vector3 b = x2 - x0; + const Vector3 cr = vec3_cross(a, b); + face.ra = cr.length(); + } + + // Node area. + LocalVector<int> counts; + counts.resize(nodes.size()); + memset(counts.ptr(), 0, counts.size() * sizeof(int)); + + for (i = 0, ni = nodes.size(); i < ni; ++i) { + nodes[i].area = 0.0; + } + + for (i = 0, ni = faces.size(); i < ni; ++i) { + const Face &face = faces[i]; + for (int j = 0; j < 3; ++j) { + const int index = (int)(face.n[j] - &nodes[0]); + counts[index]++; + face.n[j]->area += Math::abs(face.ra); + } + } + + for (i = 0, ni = nodes.size(); i < ni; ++i) { + if (counts[i] > 0) { + nodes[i].area /= (real_t)counts[i]; + } else { + nodes[i].area = 0.0; + } + } +} + +void SoftBody3DSW::reset_link_rest_lengths() { + for (uint32_t i = 0, ni = links.size(); i < ni; ++i) { + Link &link = links[i]; + link.rl = (link.n[0]->x - link.n[1]->x).length(); + link.c1 = link.rl * link.rl; + } +} + +void SoftBody3DSW::update_link_constants() { + real_t inv_linear_stiffness = 1.0 / linear_stiffness; + for (uint32_t i = 0, ni = links.size(); i < ni; ++i) { + Link &link = links[i]; + link.c0 = (link.n[0]->im + link.n[1]->im) * inv_linear_stiffness; + } +} + +void SoftBody3DSW::apply_nodes_transform(const Transform &p_transform) { + if (soft_mesh.is_null()) { + return; + } + + uint32_t node_count = nodes.size(); + Vector3 leaf_size = Vector3(collision_margin, collision_margin, collision_margin) * 2.0; + for (uint32_t node_index = 0; node_index < node_count; ++node_index) { + Node &node = nodes[node_index]; + + node.x = p_transform.xform(node.x); + node.q = node.x; + node.v = Vector3(); + node.bv = Vector3(); + + AABB node_aabb(node.x, leaf_size); + node_tree.update(node.leaf, node_aabb); + } + + face_tree.clear(); + + update_normals(); + update_bounds(); + update_constants(); +} + +Vector3 SoftBody3DSW::get_vertex_position(int p_index) const { + if (soft_mesh.is_null()) { + return Vector3(); + } + + ERR_FAIL_INDEX_V(p_index, (int)map_visual_to_physics.size(), Vector3()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND_V(node_index >= nodes.size(), Vector3()); + return nodes[node_index].x; +} + +void SoftBody3DSW::set_vertex_position(int p_index, const Vector3 &p_position) { + if (soft_mesh.is_null()) { + return; + } + + ERR_FAIL_INDEX(p_index, (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.q = node.x; + node.x = p_position; +} + +void SoftBody3DSW::pin_vertex(int p_index) { + if (is_vertex_pinned(p_index)) { + return; + } + + pinned_vertices.push_back(p_index); + + if (!soft_mesh.is_null()) { + ERR_FAIL_INDEX(p_index, (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.im = 0.0; + } +} + +void SoftBody3DSW::unpin_vertex(int p_index) { + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + if (p_index == pinned_vertices[i]) { + pinned_vertices.remove(i); + + if (!soft_mesh.is_null()) { + ERR_FAIL_INDEX(p_index, (int)map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[p_index]; + + ERR_FAIL_COND(node_index >= nodes.size()); + real_t inv_node_mass = nodes.size() * inv_total_mass; + + Node &node = nodes[node_index]; + node.im = inv_node_mass; + } + + return; + } + } +} + +void SoftBody3DSW::unpin_all_vertices() { + if (!soft_mesh.is_null()) { + real_t inv_node_mass = nodes.size() * inv_total_mass; + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + uint32_t vertex_index = pinned_vertices[i]; + + ERR_CONTINUE(vertex_index >= map_visual_to_physics.size()); + uint32_t node_index = map_visual_to_physics[vertex_index]; + + ERR_CONTINUE(node_index >= nodes.size()); + Node &node = nodes[node_index]; + node.im = inv_node_mass; + } + } + + pinned_vertices.clear(); +} + +bool SoftBody3DSW::is_vertex_pinned(int p_index) const { + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + if (p_index == pinned_vertices[i]) { + return true; + } + } + + return false; +} + +uint32_t SoftBody3DSW::get_node_count() const { + return nodes.size(); +} + +real_t SoftBody3DSW::get_node_inv_mass(uint32_t p_node_index) const { + ERR_FAIL_COND_V(p_node_index >= nodes.size(), 0.0); + return nodes[p_node_index].im; +} + +Vector3 SoftBody3DSW::get_node_position(uint32_t p_node_index) const { + ERR_FAIL_COND_V(p_node_index >= nodes.size(), Vector3()); + return nodes[p_node_index].x; +} + +Vector3 SoftBody3DSW::get_node_velocity(uint32_t p_node_index) const { + ERR_FAIL_COND_V(p_node_index >= nodes.size(), Vector3()); + return nodes[p_node_index].v; +} + +Vector3 SoftBody3DSW::get_node_biased_velocity(uint32_t p_node_index) const { + ERR_FAIL_COND_V(p_node_index >= nodes.size(), Vector3()); + return nodes[p_node_index].bv; +} + +void SoftBody3DSW::apply_node_impulse(uint32_t p_node_index, const Vector3 &p_impulse) { + ERR_FAIL_COND(p_node_index >= nodes.size()); + Node &node = nodes[p_node_index]; + node.v += p_impulse * node.im; +} + +void SoftBody3DSW::apply_node_bias_impulse(uint32_t p_node_index, const Vector3 &p_impulse) { + ERR_FAIL_COND(p_node_index >= nodes.size()); + Node &node = nodes[p_node_index]; + node.bv += p_impulse * node.im; +} + +uint32_t SoftBody3DSW::get_face_count() const { + return faces.size(); +} + +void SoftBody3DSW::get_face_points(uint32_t p_face_index, Vector3 &r_point_1, Vector3 &r_point_2, Vector3 &r_point_3) const { + ERR_FAIL_COND(p_face_index >= faces.size()); + const Face &face = faces[p_face_index]; + r_point_1 = face.n[0]->x; + r_point_2 = face.n[1]->x; + r_point_3 = face.n[2]->x; +} + +Vector3 SoftBody3DSW::get_face_normal(uint32_t p_face_index) const { + ERR_FAIL_COND_V(p_face_index >= faces.size(), Vector3()); + return faces[p_face_index].normal; +} + +bool SoftBody3DSW::create_from_trimesh(const Vector<int> &p_indices, const Vector<Vector3> &p_vertices) { + uint32_t node_count = 0; + LocalVector<Vector3> vertices; + const int visual_vertex_count(p_vertices.size()); + + LocalVector<int> triangles; + const uint32_t triangle_count(p_indices.size() / 3); + triangles.resize(triangle_count * 3); + + // Merge all overlapping vertices and create a map of physical vertices to visual vertices. + { + // Process vertices. + { + uint32_t vertex_count = 0; + Map<Vector3, uint32_t> unique_vertices; + + vertices.resize(visual_vertex_count); + map_visual_to_physics.resize(visual_vertex_count); + + for (int visual_vertex_index = 0; visual_vertex_index < visual_vertex_count; ++visual_vertex_index) { + const Vector3 &vertex = p_vertices[visual_vertex_index]; + + Map<Vector3, uint32_t>::Element *e = unique_vertices.find(vertex); + uint32_t vertex_id; + if (e) { + // Already existing. + vertex_id = e->value(); + } else { + // Create new one. + vertex_id = vertex_count++; + unique_vertices[vertex] = vertex_id; + vertices[vertex_id] = vertex; + } + + map_visual_to_physics[visual_vertex_index] = vertex_id; + } + + vertices.resize(vertex_count); + } + + // Process triangles. + { + for (uint32_t triangle_index = 0; triangle_index < triangle_count; ++triangle_index) { + for (int i = 0; i < 3; ++i) { + int visual_index = 3 * triangle_index + i; + int physics_index = map_visual_to_physics[p_indices[visual_index]]; + triangles[visual_index] = physics_index; + node_count = MAX((int)node_count, physics_index); + } + } + } + } + + ++node_count; + + // Create nodes from vertices. + nodes.resize(node_count); + real_t inv_node_mass = node_count * inv_total_mass; + Vector3 leaf_size = Vector3(collision_margin, collision_margin, collision_margin) * 2.0; + for (uint32_t i = 0; i < node_count; ++i) { + Node &node = nodes[i]; + node.s = vertices[i]; + node.x = node.s; + node.q = node.s; + node.im = inv_node_mass; + + AABB node_aabb(node.x, leaf_size); + node.leaf = node_tree.insert(node_aabb, &node); + + node.index = i; + } + + // Create links and faces from triangles. + LocalVector<bool> chks; + chks.resize(node_count * node_count); + memset(chks.ptr(), 0, chks.size() * sizeof(bool)); + + for (uint32_t i = 0; i < triangle_count * 3; i += 3) { + const int idx[] = { triangles[i], triangles[i + 1], triangles[i + 2] }; + + for (int j = 2, k = 0; k < 3; j = k++) { + int chk = idx[k] * node_count + idx[j]; + if (!chks[chk]) { + chks[chk] = true; + int inv_chk = idx[j] * node_count + idx[k]; + chks[inv_chk] = true; + + append_link(idx[j], idx[k]); + } + } + + append_face(idx[0], idx[1], idx[2]); + } + + // Set pinned nodes. + uint32_t pinned_count = pinned_vertices.size(); + for (uint32_t i = 0; i < pinned_count; ++i) { + int pinned_vertex = pinned_vertices[i]; + + ERR_CONTINUE(pinned_vertex >= visual_vertex_count); + uint32_t node_index = map_visual_to_physics[pinned_vertex]; + + ERR_CONTINUE(node_index >= node_count); + Node &node = nodes[node_index]; + node.im = 0.0; + } + + generate_bending_constraints(2); + reoptimize_link_order(); + + update_constants(); + update_normals(); + update_bounds(); + + return true; +} + +void SoftBody3DSW::generate_bending_constraints(int p_distance) { + uint32_t i, j; + + if (p_distance > 1) { + // Build graph. + const uint32_t n = nodes.size(); + const unsigned inf = (~(unsigned)0) >> 1; + const uint32_t adj_size = n * n; + unsigned *adj = memnew_arr(unsigned, adj_size); + +#define IDX(_x_, _y_) ((_y_)*n + (_x_)) + for (j = 0; j < n; ++j) { + for (i = 0; i < n; ++i) { + int idx_ij = j * n + i; + int idx_ji = i * n + j; + if (i != j) { + adj[idx_ij] = adj[idx_ji] = inf; + } else { + adj[idx_ij] = adj[idx_ji] = 0; + } + } + } + for (i = 0; i < links.size(); ++i) { + const int ia = (int)(links[i].n[0] - &nodes[0]); + const int ib = (int)(links[i].n[1] - &nodes[0]); + int idx = ib * n + ia; + int idx_inv = ia * n + ib; + adj[idx] = 1; + adj[idx_inv] = 1; + } + + // Special optimized case for distance == 2. + if (p_distance == 2) { + LocalVector<LocalVector<int>> node_links; + + // Build node links. + node_links.resize(nodes.size()); + + for (i = 0; i < links.size(); ++i) { + const int ia = (int)(links[i].n[0] - &nodes[0]); + const int ib = (int)(links[i].n[1] - &nodes[0]); + if (node_links[ia].find(ib) == -1) { + node_links[ia].push_back(ib); + } + + if (node_links[ib].find(ia) == -1) { + node_links[ib].push_back(ia); + } + } + for (uint32_t ii = 0; ii < node_links.size(); ii++) { + for (uint32_t jj = 0; jj < node_links[ii].size(); jj++) { + int k = node_links[ii][jj]; + for (uint32_t kk = 0; kk < node_links[k].size(); kk++) { + int l = node_links[k][kk]; + if ((int)ii != l) { + int idx_ik = k * n + ii; + int idx_kj = l * n + k; + const unsigned sum = adj[idx_ik] + adj[idx_kj]; + ERR_FAIL_COND(sum != 2); + int idx_ij = l * n + ii; + if (adj[idx_ij] > sum) { + int idx_ji = l * n + ii; + adj[idx_ij] = adj[idx_ji] = sum; + } + } + } + } + } + } else { + // Generic Floyd's algorithm. + for (uint32_t k = 0; k < n; ++k) { + for (j = 0; j < n; ++j) { + for (i = j + 1; i < n; ++i) { + int idx_ik = k * n + i; + int idx_kj = j * n + k; + const unsigned sum = adj[idx_ik] + adj[idx_kj]; + int idx_ij = j * n + i; + if (adj[idx_ij] > sum) { + int idx_ji = j * n + i; + adj[idx_ij] = adj[idx_ji] = sum; + } + } + } + } + } + + // Build links. + for (j = 0; j < n; ++j) { + for (i = j + 1; i < n; ++i) { + int idx_ij = j * n + i; + if (adj[idx_ij] == (unsigned)p_distance) { + append_link(i, j); + } + } + } + memdelete_arr(adj); + } +} + +//=================================================================== +// +// +// This function takes in a list of interdependent Links and tries +// to maximize the distance between calculation +// of dependent links. This increases the amount of parallelism that can +// be exploited by out-of-order instruction processors with large but +// (inevitably) finite instruction windows. +// +//=================================================================== + +// A small structure to track lists of dependent link calculations. +class LinkDeps { +public: + int value; // A link calculation that is dependent on this one + // Positive values = "input A" while negative values = "input B" + LinkDeps *next; // Next dependence in the list +}; +typedef LinkDeps *LinkDepsPtr; + +void SoftBody3DSW::reoptimize_link_order() { + const int reop_not_dependent = -1; + const int reop_node_complete = -2; + + uint32_t i, link_count = links.size(), node_count = nodes.size(); + Link *lr; + int ar, br; + Node *node0 = &(nodes[0]); + Node *node1 = &(nodes[1]); + LinkDepsPtr link_dep; + int ready_list_head, ready_list_tail, link_num, link_dep_frees, dep_link; + + // Allocate temporary buffers. + int *node_written_at = memnew_arr(int, node_count + 1); // What link calculation produced this node's current values? + int *link_dep_A = memnew_arr(int, link_count); // Link calculation input is dependent upon prior calculation #N + int *link_dep_B = memnew_arr(int, link_count); + int *ready_list = memnew_arr(int, link_count); // List of ready-to-process link calculations (# of links, maximum) + LinkDeps *link_dep_free_list = memnew_arr(LinkDeps, 2 * link_count); // Dependent-on-me list elements (2x# of links, maximum) + LinkDepsPtr *link_dep_list_starts = memnew_arr(LinkDepsPtr, link_count); // Start nodes of dependent-on-me lists, one for each link + + // Copy the original, unsorted links to a side buffer. + Link *link_buffer = memnew_arr(Link, link_count); + memcpy(link_buffer, &(links[0]), sizeof(Link) * link_count); + + // Clear out the node setup and ready list. + for (i = 0; i < node_count + 1; i++) { + node_written_at[i] = reop_not_dependent; + } + for (i = 0; i < link_count; i++) { + link_dep_list_starts[i] = nullptr; + } + ready_list_head = ready_list_tail = link_dep_frees = 0; + + // Initial link analysis to set up data structures. + for (i = 0; i < link_count; i++) { + // Note which prior link calculations we are dependent upon & build up dependence lists. + lr = &(links[i]); + ar = (lr->n[0] - node0) / (node1 - node0); + br = (lr->n[1] - node0) / (node1 - node0); + if (node_written_at[ar] > reop_not_dependent) { + link_dep_A[i] = node_written_at[ar]; + link_dep = &link_dep_free_list[link_dep_frees++]; + link_dep->value = i; + link_dep->next = link_dep_list_starts[node_written_at[ar]]; + link_dep_list_starts[node_written_at[ar]] = link_dep; + } else { + link_dep_A[i] = reop_not_dependent; + } + if (node_written_at[br] > reop_not_dependent) { + link_dep_B[i] = node_written_at[br]; + link_dep = &link_dep_free_list[link_dep_frees++]; + link_dep->value = -(int)(i + 1); + link_dep->next = link_dep_list_starts[node_written_at[br]]; + link_dep_list_starts[node_written_at[br]] = link_dep; + } else { + link_dep_B[i] = reop_not_dependent; + } + + // Add this link to the initial ready list, if it is not dependent on any other links. + if ((link_dep_A[i] == reop_not_dependent) && (link_dep_B[i] == reop_not_dependent)) { + ready_list[ready_list_tail++] = i; + link_dep_A[i] = link_dep_B[i] = reop_node_complete; // Probably not needed now. + } + + // Update the nodes to mark which ones are calculated by this link. + node_written_at[ar] = node_written_at[br] = i; + } + + // Process the ready list and create the sorted list of links: + // -- By treating the ready list as a queue, we maximize the distance between any + // inter-dependent node calculations. + // -- All other (non-related) nodes in the ready list will automatically be inserted + // in between each set of inter-dependent link calculations by this loop. + i = 0; + while (ready_list_head != ready_list_tail) { + // Use ready list to select the next link to process. + link_num = ready_list[ready_list_head++]; + // Copy the next-to-calculate link back into the original link array. + links[i++] = link_buffer[link_num]; + + // Free up any link inputs that are dependent on this one. + link_dep = link_dep_list_starts[link_num]; + while (link_dep) { + dep_link = link_dep->value; + if (dep_link >= 0) { + link_dep_A[dep_link] = reop_not_dependent; + } else { + dep_link = -dep_link - 1; + link_dep_B[dep_link] = reop_not_dependent; + } + // Add this dependent link calculation to the ready list if *both* inputs are clear. + if ((link_dep_A[dep_link] == reop_not_dependent) && (link_dep_B[dep_link] == reop_not_dependent)) { + ready_list[ready_list_tail++] = dep_link; + link_dep_A[dep_link] = link_dep_B[dep_link] = reop_node_complete; // Probably not needed now. + } + link_dep = link_dep->next; + } + } + + // Delete the temporary buffers. + memdelete_arr(node_written_at); + memdelete_arr(link_dep_A); + memdelete_arr(link_dep_B); + memdelete_arr(ready_list); + memdelete_arr(link_dep_free_list); + memdelete_arr(link_dep_list_starts); + memdelete_arr(link_buffer); +} + +void SoftBody3DSW::append_link(uint32_t p_node1, uint32_t p_node2) { + if (p_node1 == p_node2) { + return; + } + + Node *node1 = &nodes[p_node1]; + Node *node2 = &nodes[p_node2]; + + Link link; + link.n[0] = node1; + link.n[1] = node2; + link.rl = (node1->x - node2->x).length(); + + links.push_back(link); +} + +void SoftBody3DSW::append_face(uint32_t p_node1, uint32_t p_node2, uint32_t p_node3) { + if (p_node1 == p_node2) { + return; + } + if (p_node1 == p_node3) { + return; + } + if (p_node2 == p_node3) { + return; + } + + Node *node1 = &nodes[p_node1]; + Node *node2 = &nodes[p_node2]; + Node *node3 = &nodes[p_node3]; + + Face face; + face.n[0] = node1; + face.n[1] = node2; + face.n[2] = node3; + + face.index = faces.size(); + + faces.push_back(face); +} + +void SoftBody3DSW::set_iteration_count(int p_val) { + iteration_count = p_val; +} + +void SoftBody3DSW::set_total_mass(real_t p_val) { + ERR_FAIL_COND(p_val < 0.0); + + inv_total_mass = 1.0 / p_val; + real_t mass_factor = total_mass * inv_total_mass; + total_mass = p_val; + + uint32_t node_count = nodes.size(); + for (uint32_t node_index = 0; node_index < node_count; ++node_index) { + Node &node = nodes[node_index]; + node.im *= mass_factor; + } + + update_constants(); +} + +void SoftBody3DSW::set_collision_margin(real_t p_val) { + collision_margin = p_val; +} + +void SoftBody3DSW::set_linear_stiffness(real_t p_val) { + linear_stiffness = p_val; +} + +void SoftBody3DSW::set_pressure_coefficient(real_t p_val) { + pressure_coefficient = p_val; +} + +void SoftBody3DSW::set_damping_coefficient(real_t p_val) { + damping_coefficient = p_val; +} + +void SoftBody3DSW::set_drag_coefficient(real_t p_val) { + drag_coefficient = p_val; +} + +void SoftBody3DSW::add_velocity(const Vector3 &p_velocity) { + for (uint32_t i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + if (node.im > 0) { + node.v += p_velocity; + } + } +} + +void SoftBody3DSW::apply_forces() { + if (pressure_coefficient < CMP_EPSILON) { + return; + } + + if (nodes.is_empty()) { + return; + } + + uint32_t i, ni; + + // Calculate volume. + real_t volume = 0.0; + const Vector3 &org = nodes[0].x; + for (i = 0, ni = faces.size(); i < ni; ++i) { + const Face &face = faces[i]; + volume += vec3_dot(face.n[0]->x - org, vec3_cross(face.n[1]->x - org, face.n[2]->x - org)); + } + volume /= 6.0; + + // Apply per node forces. + real_t ivolumetp = 1.0 / Math::abs(volume) * pressure_coefficient; + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + if (node.im > 0) { + node.f += node.n * (node.area * ivolumetp); + } + } +} + +void SoftBody3DSW::predict_motion(real_t p_delta) { + const real_t inv_delta = 1.0 / p_delta; + + ERR_FAIL_COND(!get_space()); + + Area3DSW *def_area = get_space()->get_default_area(); + ERR_FAIL_COND(!def_area); + + // Apply forces. + Vector3 gravity = def_area->get_gravity_vector() * def_area->get_gravity(); + add_velocity(gravity * p_delta); + apply_forces(); + + // Avoid soft body from 'exploding' so use some upper threshold of maximum motion + // that a node can travel per frame. + const real_t max_displacement = 1000.0; + real_t clamp_delta_v = max_displacement * inv_delta; + + // Integrate. + uint32_t i, ni; + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + node.q = node.x; + Vector3 delta_v = node.f * node.im * p_delta; + for (int c = 0; c < 3; c++) { + delta_v[c] = CLAMP(delta_v[c], -clamp_delta_v, clamp_delta_v); + } + node.v += delta_v; + node.x += node.v * p_delta; + node.f = Vector3(); + } + + // Bounds and tree update. + update_bounds(); + + // Node tree update. + for (i = 0, ni = nodes.size(); i < ni; ++i) { + const Node &node = nodes[i]; + + AABB node_aabb(node.x, Vector3()); + node_aabb.expand_to(node.x + node.v * p_delta); + node_aabb.grow_by(collision_margin); + + node_tree.update(node.leaf, node_aabb); + } + + // Face tree update. + if (!face_tree.is_empty()) { + update_face_tree(p_delta); + } + + // Optimize node tree. + node_tree.optimize_incremental(1); + face_tree.optimize_incremental(1); +} + +void SoftBody3DSW::solve_constraints(real_t p_delta) { + const real_t inv_delta = 1.0 / p_delta; + + uint32_t i, ni; + + for (i = 0, ni = links.size(); i < ni; ++i) { + Link &link = links[i]; + link.c3 = link.n[1]->q - link.n[0]->q; + link.c2 = 1 / (link.c3.length_squared() * link.c0); + } + + // Solve velocities. + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + node.x = node.q + node.v * p_delta; + } + + // Solve positions. + for (int isolve = 0; isolve < iteration_count; ++isolve) { + const real_t ti = isolve / (real_t)iteration_count; + solve_links(1.0, ti); + } + const real_t vc = (1.0 - damping_coefficient) * inv_delta; + for (i = 0, ni = nodes.size(); i < ni; ++i) { + Node &node = nodes[i]; + + node.x += node.bv * p_delta; + node.bv = Vector3(); + + node.v = (node.x - node.q) * vc; + + node.q = node.x; + } + + update_normals(); +} + +void SoftBody3DSW::solve_links(real_t kst, real_t ti) { + for (uint32_t i = 0, ni = links.size(); i < ni; ++i) { + Link &link = links[i]; + if (link.c0 > 0) { + Node &node_a = *link.n[0]; + Node &node_b = *link.n[1]; + const Vector3 del = node_b.x - node_a.x; + const real_t len = del.length_squared(); + if (link.c1 + len > CMP_EPSILON) { + const real_t k = ((link.c1 - len) / (link.c0 * (link.c1 + len))) * kst; + node_a.x -= del * (k * node_a.im); + node_b.x += del * (k * node_b.im); + } + } + } +} + +struct AABBQueryResult { + const SoftBody3DSW *soft_body = nullptr; + void *userdata = nullptr; + SoftBody3DSW::QueryResultCallback result_callback = nullptr; + + _FORCE_INLINE_ bool operator()(void *p_data) { + return result_callback(soft_body->get_node_index(p_data), userdata); + }; +}; + +void SoftBody3DSW::query_aabb(const AABB &p_aabb, SoftBody3DSW::QueryResultCallback p_result_callback, void *p_userdata) { + AABBQueryResult query_result; + query_result.soft_body = this; + query_result.result_callback = p_result_callback; + query_result.userdata = p_userdata; + + node_tree.aabb_query(p_aabb, query_result); +} + +struct RayQueryResult { + const SoftBody3DSW *soft_body = nullptr; + void *userdata = nullptr; + SoftBody3DSW::QueryResultCallback result_callback = nullptr; + + _FORCE_INLINE_ bool operator()(void *p_data) { + return result_callback(soft_body->get_face_index(p_data), userdata); + }; +}; + +void SoftBody3DSW::query_ray(const Vector3 &p_from, const Vector3 &p_to, SoftBody3DSW::QueryResultCallback p_result_callback, void *p_userdata) { + if (face_tree.is_empty()) { + initialize_face_tree(); + } + + RayQueryResult query_result; + query_result.soft_body = this; + query_result.result_callback = p_result_callback; + query_result.userdata = p_userdata; + + face_tree.ray_query(p_from, p_to, query_result); +} + +void SoftBody3DSW::initialize_face_tree() { + face_tree.clear(); + for (uint32_t i = 0; i < faces.size(); ++i) { + Face &face = faces[i]; + + AABB face_aabb; + + face_aabb.position = face.n[0]->x; + face_aabb.expand_to(face.n[1]->x); + face_aabb.expand_to(face.n[2]->x); + + face_aabb.grow_by(collision_margin); + + face.leaf = face_tree.insert(face_aabb, &face); + } +} + +void SoftBody3DSW::update_face_tree(real_t p_delta) { + for (uint32_t i = 0; i < faces.size(); ++i) { + const Face &face = faces[i]; + + AABB face_aabb; + + const Node *node0 = face.n[0]; + face_aabb.position = node0->x; + face_aabb.expand_to(node0->x + node0->v * p_delta); + + const Node *node1 = face.n[1]; + face_aabb.expand_to(node1->x); + face_aabb.expand_to(node1->x + node1->v * p_delta); + + const Node *node2 = face.n[2]; + face_aabb.expand_to(node2->x); + face_aabb.expand_to(node2->x + node2->v * p_delta); + + face_aabb.grow_by(collision_margin); + + face_tree.update(face.leaf, face_aabb); + } +} + +void SoftBody3DSW::initialize_shape(bool p_force_move) { + if (get_shape_count() == 0) { + SoftBodyShape3DSW *soft_body_shape = memnew(SoftBodyShape3DSW(this)); + add_shape(soft_body_shape); + } else if (p_force_move) { + SoftBodyShape3DSW *soft_body_shape = static_cast<SoftBodyShape3DSW *>(get_shape(0)); + soft_body_shape->update_bounds(); + } +} + +void SoftBody3DSW::deinitialize_shape() { + if (get_shape_count() > 0) { + Shape3DSW *shape = get_shape(0); + remove_shape(shape); + memdelete(shape); + } +} + +void SoftBody3DSW::destroy() { + map_visual_to_physics.clear(); + + node_tree.clear(); + face_tree.clear(); + + nodes.clear(); + links.clear(); + faces.clear(); + + bounds = AABB(); + deinitialize_shape(); +} + +void SoftBodyShape3DSW::update_bounds() { + ERR_FAIL_COND(!soft_body); + + AABB collision_aabb = soft_body->get_bounds(); + collision_aabb.grow_by(soft_body->get_collision_margin()); + configure(collision_aabb); +} + +SoftBodyShape3DSW::SoftBodyShape3DSW(SoftBody3DSW *p_soft_body) { + soft_body = p_soft_body; + update_bounds(); +} + +struct _SoftBodyIntersectSegmentInfo { + const SoftBody3DSW *soft_body = nullptr; + Vector3 from; + Vector3 dir; + Vector3 hit_position; + uint32_t hit_face_index = -1; + real_t hit_dist_sq = Math_INF; + + static bool process_hit(uint32_t p_face_index, void *p_userdata) { + _SoftBodyIntersectSegmentInfo &query_info = *(_SoftBodyIntersectSegmentInfo *)(p_userdata); + + Vector3 points[3]; + query_info.soft_body->get_face_points(p_face_index, points[0], points[1], points[2]); + + Vector3 result; + if (Geometry3D::ray_intersects_triangle(query_info.from, query_info.dir, points[0], points[1], points[2], &result)) { + real_t dist_sq = query_info.from.distance_squared_to(result); + if (dist_sq < query_info.hit_dist_sq) { + query_info.hit_dist_sq = dist_sq; + query_info.hit_position = result; + query_info.hit_face_index = p_face_index; + } + } + + // Continue with the query. + return false; + } +}; + +bool SoftBodyShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const { + _SoftBodyIntersectSegmentInfo query_info; + query_info.soft_body = soft_body; + query_info.from = p_begin; + query_info.dir = (p_end - p_begin).normalized(); + + soft_body->query_ray(p_begin, p_end, _SoftBodyIntersectSegmentInfo::process_hit, &query_info); + + if (query_info.hit_dist_sq != Math_INF) { + r_result = query_info.hit_position; + r_normal = soft_body->get_face_normal(query_info.hit_face_index); + return true; + } + + return false; +} + +bool SoftBodyShape3DSW::intersect_point(const Vector3 &p_point) const { + return false; +} + +Vector3 SoftBodyShape3DSW::get_closest_point_to(const Vector3 &p_point) const { + ERR_FAIL_V_MSG(Vector3(), "Get closest point is not supported for soft bodies."); +} diff --git a/servers/physics_3d/soft_body_3d_sw.h b/servers/physics_3d/soft_body_3d_sw.h new file mode 100644 index 0000000000..6c0451ff45 --- /dev/null +++ b/servers/physics_3d/soft_body_3d_sw.h @@ -0,0 +1,247 @@ +/*************************************************************************/ +/* soft_body_3d_sw.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 SOFT_BODY_3D_SW_H +#define SOFT_BODY_3D_SW_H + +#include "collision_object_3d_sw.h" + +#include "core/math/aabb.h" +#include "core/math/dynamic_bvh.h" +#include "core/math/vector3.h" +#include "core/templates/local_vector.h" +#include "core/templates/set.h" +#include "core/templates/vset.h" +#include "scene/resources/mesh.h" + +class Constraint3DSW; + +class SoftBody3DSW : public CollisionObject3DSW { + Ref<Mesh> soft_mesh; + + struct Node { + Vector3 s; // Source position + Vector3 x; // Position + Vector3 q; // Previous step position/Test position + Vector3 f; // Force accumulator + Vector3 v; // Velocity + Vector3 bv; // Biased Velocity + Vector3 n; // Normal + real_t area = 0.0; // Area + real_t im = 0.0; // 1/mass + DynamicBVH::ID leaf; // Leaf data + uint32_t index = 0; + }; + + struct Link { + Vector3 c3; // gradient + Node *n[2] = { nullptr, nullptr }; // Node pointers + real_t rl = 0.0; // Rest length + real_t c0 = 0.0; // (ima+imb)*kLST + real_t c1 = 0.0; // rl^2 + real_t c2 = 0.0; // |gradient|^2/c0 + }; + + struct Face { + Node *n[3] = { nullptr, nullptr, nullptr }; // Node pointers + Vector3 normal; // Normal + real_t ra = 0.0; // Rest area + DynamicBVH::ID leaf; // Leaf data + uint32_t index = 0; + }; + + LocalVector<Node> nodes; + LocalVector<Link> links; + LocalVector<Face> faces; + + DynamicBVH node_tree; + DynamicBVH face_tree; + + LocalVector<uint32_t> map_visual_to_physics; + + AABB bounds; + + real_t collision_margin = 0.05; + + real_t total_mass = 1.0; + real_t inv_total_mass = 1.0; + + int iteration_count = 5; + real_t linear_stiffness = 0.5; // [0,1] + real_t pressure_coefficient = 0.0; // [-inf,+inf] + real_t damping_coefficient = 0.01; // [0,1] + real_t drag_coefficient = 0.0; // [0,1] + LocalVector<int> pinned_vertices; + + SelfList<SoftBody3DSW> active_list; + + Set<Constraint3DSW *> constraints; + + VSet<RID> exceptions; + +public: + SoftBody3DSW(); + + const AABB &get_bounds() const { return bounds; } + + void set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant); + Variant get_state(PhysicsServer3D::BodyState p_state) const; + + _FORCE_INLINE_ void add_constraint(Constraint3DSW *p_constraint) { constraints.insert(p_constraint); } + _FORCE_INLINE_ void remove_constraint(Constraint3DSW *p_constraint) { constraints.erase(p_constraint); } + _FORCE_INLINE_ const Set<Constraint3DSW *> &get_constraints() const { return constraints; } + _FORCE_INLINE_ void clear_constraints() { constraints.clear(); } + + _FORCE_INLINE_ void add_exception(const RID &p_exception) { exceptions.insert(p_exception); } + _FORCE_INLINE_ void remove_exception(const RID &p_exception) { exceptions.erase(p_exception); } + _FORCE_INLINE_ bool has_exception(const RID &p_exception) const { return exceptions.has(p_exception); } + _FORCE_INLINE_ const VSet<RID> &get_exceptions() const { return exceptions; } + + virtual void set_space(Space3DSW *p_space); + + void set_mesh(const Ref<Mesh> &p_mesh); + + void update_rendering_server(RenderingServerHandler *p_rendering_server_handler); + + Vector3 get_vertex_position(int p_index) const; + void set_vertex_position(int p_index, const Vector3 &p_position); + + void pin_vertex(int p_index); + void unpin_vertex(int p_index); + void unpin_all_vertices(); + bool is_vertex_pinned(int p_index) const; + + uint32_t get_node_count() const; + real_t get_node_inv_mass(uint32_t p_node_index) const; + Vector3 get_node_position(uint32_t p_node_index) const; + Vector3 get_node_velocity(uint32_t p_node_index) const; + Vector3 get_node_biased_velocity(uint32_t p_node_index) const; + void apply_node_impulse(uint32_t p_node_index, const Vector3 &p_impulse); + void apply_node_bias_impulse(uint32_t p_node_index, const Vector3 &p_impulse); + + uint32_t get_face_count() const; + void get_face_points(uint32_t p_face_index, Vector3 &r_point_1, Vector3 &r_point_2, Vector3 &r_point_3) const; + Vector3 get_face_normal(uint32_t p_face_index) const; + + void set_iteration_count(int p_val); + _FORCE_INLINE_ real_t get_iteration_count() const { return iteration_count; } + + void set_total_mass(real_t p_val); + _FORCE_INLINE_ real_t get_total_mass() const { return total_mass; } + _FORCE_INLINE_ real_t get_total_inv_mass() const { return inv_total_mass; } + + void set_collision_margin(real_t p_val); + _FORCE_INLINE_ real_t get_collision_margin() const { return collision_margin; } + + void set_linear_stiffness(real_t p_val); + _FORCE_INLINE_ real_t get_linear_stiffness() const { return linear_stiffness; } + + void set_pressure_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_pressure_coefficient() const { return pressure_coefficient; } + + void set_damping_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_damping_coefficient() const { return damping_coefficient; } + + void set_drag_coefficient(real_t p_val); + _FORCE_INLINE_ real_t get_drag_coefficient() const { return drag_coefficient; } + + void predict_motion(real_t p_delta); + void solve_constraints(real_t p_delta); + + _FORCE_INLINE_ uint32_t get_node_index(void *p_node) const { return ((Node *)p_node)->index; } + _FORCE_INLINE_ uint32_t get_face_index(void *p_face) const { return ((Face *)p_face)->index; } + + // Return true to stop the query. + // p_index is the node index for AABB query, face index for Ray query. + typedef bool (*QueryResultCallback)(uint32_t p_index, void *p_userdata); + + void query_aabb(const AABB &p_aabb, QueryResultCallback p_result_callback, void *p_userdata); + void query_ray(const Vector3 &p_from, const Vector3 &p_to, QueryResultCallback p_result_callback, void *p_userdata); + +protected: + virtual void _shapes_changed(); + +private: + void update_normals(); + void update_bounds(); + void update_constants(); + void update_area(); + void reset_link_rest_lengths(); + void update_link_constants(); + + void apply_nodes_transform(const Transform &p_transform); + + void add_velocity(const Vector3 &p_velocity); + + void apply_forces(); + + bool create_from_trimesh(const Vector<int> &p_indices, const Vector<Vector3> &p_vertices); + void generate_bending_constraints(int p_distance); + void reoptimize_link_order(); + void append_link(uint32_t p_node1, uint32_t p_node2); + void append_face(uint32_t p_node1, uint32_t p_node2, uint32_t p_node3); + + void solve_links(real_t kst, real_t ti); + + void initialize_face_tree(); + void update_face_tree(real_t p_delta); + + void initialize_shape(bool p_force_move = true); + void deinitialize_shape(); + + void destroy(); +}; + +class SoftBodyShape3DSW : public Shape3DSW { + SoftBody3DSW *soft_body = nullptr; + +public: + SoftBody3DSW *get_soft_body() const { return soft_body; } + + virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_SOFT_BODY; } + virtual void project_range(const Vector3 &p_normal, const Transform &p_transform, real_t &r_min, real_t &r_max) const { r_min = r_max = 0.0; } + virtual Vector3 get_support(const Vector3 &p_normal) const { return Vector3(); } + virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { r_amount = 0; } + + virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const; + virtual bool intersect_point(const Vector3 &p_point) const; + virtual Vector3 get_closest_point_to(const Vector3 &p_point) const; + virtual Vector3 get_moment_of_inertia(real_t p_mass) const { return Vector3(); } + + virtual void set_data(const Variant &p_data) {} + virtual Variant get_data() const { return Variant(); } + + void update_bounds(); + + SoftBodyShape3DSW(SoftBody3DSW *p_soft_body); + ~SoftBodyShape3DSW() {} +}; + +#endif // SOFT_BODY_3D_SW_H diff --git a/servers/physics_3d/space_3d_sw.cpp b/servers/physics_3d/space_3d_sw.cpp index c8741dc930..2df824b320 100644 --- a/servers/physics_3d/space_3d_sw.cpp +++ b/servers/physics_3d/space_3d_sw.cpp @@ -47,6 +47,10 @@ _FORCE_INLINE_ static bool _can_collide_with(CollisionObject3DSW *p_object, uint return false; } + if (p_object->get_type() == CollisionObject3DSW::TYPE_SOFT_BODY && !p_collide_with_bodies) { + return false; + } + return true; } @@ -332,7 +336,8 @@ bool PhysicsDirectSpaceState3DSW::cast_motion(const RID &p_shape, const Transfor best_first = false; if (col_obj->get_type() == CollisionObject3DSW::TYPE_BODY) { const Body3DSW *body = static_cast<const Body3DSW *>(col_obj); - r_info->linear_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(body->get_transform().origin - closest_B); + Vector3 rel_vec = closest_B - (body->get_transform().origin + body->get_center_of_mass()); + r_info->linear_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); } } } @@ -407,7 +412,7 @@ struct _RestCallbackData { real_t min_allowed_depth; }; -static void _rest_cbk_result(const Vector3 &p_point_A, const Vector3 &p_point_B, void *p_userdata) { +static void _rest_cbk_result(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata) { _RestCallbackData *rd = (_RestCallbackData *)p_userdata; Vector3 contact_rel = p_point_B - p_point_A; @@ -478,8 +483,8 @@ bool PhysicsDirectSpaceState3DSW::rest_info(RID p_shape, const Transform &p_shap r_info->rid = rcd.best_object->get_self(); if (rcd.best_object->get_type() == CollisionObject3DSW::TYPE_BODY) { const Body3DSW *body = static_cast<const Body3DSW *>(rcd.best_object); - r_info->linear_velocity = body->get_linear_velocity() + - (body->get_angular_velocity()).cross(body->get_transform().origin - rcd.best_contact); // * mPos); + Vector3 rel_vec = rcd.best_contact - (body->get_transform().origin + body->get_center_of_mass()); + r_info->linear_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); } else { r_info->linear_velocity = Vector3(); @@ -544,6 +549,8 @@ int Space3DSW::_cull_aabb_for_body(Body3DSW *p_body, const AABB &p_aabb) { keep = false; } else if (intersection_query_results[i]->get_type() == CollisionObject3DSW::TYPE_AREA) { keep = false; + } else if (intersection_query_results[i]->get_type() == CollisionObject3DSW::TYPE_SOFT_BODY) { + keep = false; } else if ((static_cast<Body3DSW *>(intersection_query_results[i])->test_collision_mask(p_body)) == 0) { keep = false; } else if (static_cast<Body3DSW *>(intersection_query_results[i])->has_exception(p_body->get_self()) || p_body->has_exception(intersection_query_results[i]->get_self())) { @@ -684,10 +691,8 @@ int Space3DSW::test_body_ray_separation(Body3DSW *p_body, const Transform &p_tra //result.collider_metadata = col_obj->get_shape_metadata(shape_idx); if (col_obj->get_type() == CollisionObject3DSW::TYPE_BODY) { Body3DSW *body = (Body3DSW *)col_obj; - - Vector3 rel_vec = b - body->get_transform().get_origin(); - //result.collider_velocity = Vector3(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); - result.collider_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(body->get_transform().origin - rel_vec); // * mPos); + Vector3 rel_vec = b - (body->get_transform().origin + body->get_center_of_mass()); + result.collider_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); } } } @@ -1009,9 +1014,9 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform &p_from, cons //r_result->collider_metadata = rcd.best_object->get_shape_metadata(rcd.best_shape); const Body3DSW *body = static_cast<const Body3DSW *>(rcd.best_object); - //Vector3 rel_vec = r_result->collision_point - body->get_transform().get_origin(); - // r_result->collider_velocity = Vector3(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); - r_result->collider_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(body->get_transform().origin - rcd.best_contact); // * mPos); + + Vector3 rel_vec = rcd.best_contact - (body->get_transform().origin + body->get_center_of_mass()); + r_result->collider_velocity = body->get_linear_velocity() + (body->get_angular_velocity()).cross(rel_vec); r_result->motion = safe * p_motion; r_result->remainder = p_motion - safe * p_motion; @@ -1054,14 +1059,23 @@ void *Space3DSW::_broadphase_pair(CollisionObject3DSW *A, int p_subindex_A, Coll Area3DSW *area_b = static_cast<Area3DSW *>(B); Area2Pair3DSW *area2_pair = memnew(Area2Pair3DSW(area_b, p_subindex_B, area, p_subindex_A)); return area2_pair; + } else if (type_B == CollisionObject3DSW::TYPE_SOFT_BODY) { + // Area/Soft Body, not supported. } else { Body3DSW *body = static_cast<Body3DSW *>(B); AreaPair3DSW *area_pair = memnew(AreaPair3DSW(body, p_subindex_B, area, p_subindex_A)); return area_pair; } + } else if (type_A == CollisionObject3DSW::TYPE_BODY) { + if (type_B == CollisionObject3DSW::TYPE_SOFT_BODY) { + BodySoftBodyPair3DSW *soft_pair = memnew(BodySoftBodyPair3DSW((Body3DSW *)A, p_subindex_A, (SoftBody3DSW *)B)); + return soft_pair; + } else { + BodyPair3DSW *b = memnew(BodyPair3DSW((Body3DSW *)A, p_subindex_A, (Body3DSW *)B, p_subindex_B)); + return b; + } } else { - BodyPair3DSW *b = memnew(BodyPair3DSW((Body3DSW *)A, p_subindex_A, (Body3DSW *)B, p_subindex_B)); - return b; + // Soft Body/Soft Body, not supported. } return nullptr; @@ -1144,6 +1158,18 @@ const SelfList<Area3DSW>::List &Space3DSW::get_moved_area_list() const { return area_moved_list; } +const SelfList<SoftBody3DSW>::List &Space3DSW::get_active_soft_body_list() const { + return active_soft_body_list; +} + +void Space3DSW::soft_body_add_to_active_list(SelfList<SoftBody3DSW> *p_soft_body) { + active_soft_body_list.add(p_soft_body); +} + +void Space3DSW::soft_body_remove_from_active_list(SelfList<SoftBody3DSW> *p_soft_body) { + active_soft_body_list.remove(p_soft_body); +} + void Space3DSW::call_queries() { while (state_query_list.first()) { Body3DSW *b = state_query_list.first()->self(); diff --git a/servers/physics_3d/space_3d_sw.h b/servers/physics_3d/space_3d_sw.h index eed3d86a72..3a8f452e54 100644 --- a/servers/physics_3d/space_3d_sw.h +++ b/servers/physics_3d/space_3d_sw.h @@ -40,6 +40,7 @@ #include "core/config/project_settings.h" #include "core/templates/hash_map.h" #include "core/typedefs.h" +#include "soft_body_3d_sw.h" class PhysicsDirectSpaceState3DSW : public PhysicsDirectSpaceState3D { GDCLASS(PhysicsDirectSpaceState3DSW, PhysicsDirectSpaceState3D); @@ -82,6 +83,7 @@ private: SelfList<Body3DSW>::List state_query_list; SelfList<Area3DSW>::List monitor_query_list; SelfList<Area3DSW>::List area_moved_list; + SelfList<SoftBody3DSW>::List active_soft_body_list; static void *_broadphase_pair(CollisionObject3DSW *A, int p_subindex_A, CollisionObject3DSW *B, int p_subindex_B, void *p_self); static void _broadphase_unpair(CollisionObject3DSW *A, int p_subindex_A, CollisionObject3DSW *B, int p_subindex_B, void *p_data, void *p_self); @@ -145,6 +147,10 @@ public: void area_remove_from_moved_list(SelfList<Area3DSW> *p_area); const SelfList<Area3DSW>::List &get_moved_area_list() const; + const SelfList<SoftBody3DSW>::List &get_active_soft_body_list() const; + void soft_body_add_to_active_list(SelfList<SoftBody3DSW> *p_soft_body); + void soft_body_remove_from_active_list(SelfList<SoftBody3DSW> *p_soft_body); + BroadPhase3DSW *get_broadphase(); void add_object(CollisionObject3DSW *p_object); diff --git a/servers/physics_3d/step_3d_sw.cpp b/servers/physics_3d/step_3d_sw.cpp index d9370de6a3..9a73e11562 100644 --- a/servers/physics_3d/step_3d_sw.cpp +++ b/servers/physics_3d/step_3d_sw.cpp @@ -146,6 +146,8 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) { const SelfList<Body3DSW>::List *body_list = &p_space->get_active_body_list(); + const SelfList<SoftBody3DSW>::List *soft_body_list = &p_space->get_active_soft_body_list(); + /* INTEGRATE FORCES */ uint64_t profile_begtime = OS::get_singleton()->get_ticks_usec(); @@ -160,6 +162,15 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) { active_count++; } + /* UPDATE SOFT BODY MOTION */ + + const SelfList<SoftBody3DSW> *sb = soft_body_list->first(); + while (sb) { + sb->self()->predict_motion(p_delta); + sb = sb->next(); + active_count++; + } + p_space->set_active_objects(active_count); { //profile @@ -214,6 +225,20 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) { p_space->area_remove_from_moved_list((SelfList<Area3DSW> *)aml.first()); //faster to remove here } + sb = soft_body_list->first(); + while (sb) { + for (const Set<Constraint3DSW *>::Element *E = sb->self()->get_constraints().front(); E; E = E->next()) { + Constraint3DSW *c = E->get(); + if (c->get_island_step() == _step) + continue; + c->set_island_step(_step); + c->set_island_next(NULL); + c->set_island_list_next(constraint_island_list); + constraint_island_list = c; + } + sb = sb->next(); + } + { //profile profile_endtime = OS::get_singleton()->get_ticks_usec(); p_space->set_elapsed_time(Space3DSW::ELAPSED_TIME_GENERATE_ISLANDS, profile_endtime - profile_begtime); @@ -272,6 +297,14 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) { } } + /* UPDATE SOFT BODY CONSTRAINTS */ + + sb = soft_body_list->first(); + while (sb) { + sb->self()->solve_constraints(p_delta); + sb = sb->next(); + } + { //profile profile_endtime = OS::get_singleton()->get_ticks_usec(); p_space->set_elapsed_time(Space3DSW::ELAPSED_TIME_INTEGRATE_VELOCITIES, profile_endtime - profile_begtime); diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index af25029f04..586845de99 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -556,6 +556,10 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state); + /* SOFT BODY API */ + + ClassDB::bind_method(D_METHOD("soft_body_get_bounds", "body"), &PhysicsServer3D::soft_body_get_bounds); + /* JOINT API */ ClassDB::bind_method(D_METHOD("joint_create"), &PhysicsServer3D::joint_create); @@ -693,6 +697,7 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(SHAPE_CONVEX_POLYGON); BIND_ENUM_CONSTANT(SHAPE_CONCAVE_POLYGON); BIND_ENUM_CONSTANT(SHAPE_HEIGHTMAP); + BIND_ENUM_CONSTANT(SHAPE_SOFT_BODY); BIND_ENUM_CONSTANT(SHAPE_CUSTOM); BIND_ENUM_CONSTANT(AREA_PARAM_GRAVITY); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index e16857192c..69f5c1c0ad 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -216,6 +216,15 @@ public: PhysicsShapeQueryResult3D(); }; +class RenderingServerHandler { +public: + virtual void set_vertex(int p_vertex_id, const void *p_vector3) = 0; + virtual void set_normal(int p_vertex_id, const void *p_vector3) = 0; + virtual void set_aabb(const AABB &p_aabb) = 0; + + virtual ~RenderingServerHandler() {} +}; + class PhysicsServer3D : public Object { GDCLASS(PhysicsServer3D, Object); @@ -237,6 +246,7 @@ public: SHAPE_CONVEX_POLYGON, ///< array of planes:"planes" SHAPE_CONCAVE_POLYGON, ///< vector3 array:"triangles" , or Dictionary with "indices" (int array) and "triangles" (Vector3 array) SHAPE_HEIGHTMAP, ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights" + SHAPE_SOFT_BODY, ///< Used internally, can't be created from the physics server. SHAPE_CUSTOM, ///< Server-Implementation based custom shape, calling shape_create() with this value will result in an error }; @@ -522,13 +532,15 @@ public: virtual RID soft_body_create() = 0; - virtual void soft_body_update_rendering_server(RID p_body, class SoftBodyRenderingServerHandler *p_rendering_server_handler) = 0; + virtual void soft_body_update_rendering_server(RID p_body, RenderingServerHandler *p_rendering_server_handler) = 0; virtual void soft_body_set_space(RID p_body, RID p_space) = 0; virtual RID soft_body_get_space(RID p_body) const = 0; virtual void soft_body_set_mesh(RID p_body, const REF &p_mesh) = 0; + virtual AABB soft_body_get_bounds(RID p_body) const = 0; + virtual void soft_body_set_collision_layer(RID p_body, uint32_t p_layer) = 0; virtual uint32_t soft_body_get_collision_layer(RID p_body) const = 0; @@ -543,7 +555,6 @@ public: virtual Variant soft_body_get_state(RID p_body, BodyState p_state) const = 0; virtual void soft_body_set_transform(RID p_body, const Transform &p_transform) = 0; - virtual Vector3 soft_body_get_vertex_position(RID p_body, int vertex_index) const = 0; virtual void soft_body_set_ray_pickable(RID p_body, bool p_enable) = 0; @@ -556,18 +567,9 @@ public: virtual void soft_body_set_linear_stiffness(RID p_body, real_t p_stiffness) = 0; virtual real_t soft_body_get_linear_stiffness(RID p_body) const = 0; - virtual void soft_body_set_angular_stiffness(RID p_body, real_t p_stiffness) = 0; - virtual real_t soft_body_get_angular_stiffness(RID p_body) const = 0; - - virtual void soft_body_set_volume_stiffness(RID p_body, real_t p_stiffness) = 0; - virtual real_t soft_body_get_volume_stiffness(RID p_body) const = 0; - virtual void soft_body_set_pressure_coefficient(RID p_body, real_t p_pressure_coefficient) = 0; virtual real_t soft_body_get_pressure_coefficient(RID p_body) const = 0; - virtual void soft_body_set_pose_matching_coefficient(RID p_body, real_t p_pose_matching_coefficient) = 0; - virtual real_t soft_body_get_pose_matching_coefficient(RID p_body) const = 0; - virtual void soft_body_set_damping_coefficient(RID p_body, real_t p_damping_coefficient) = 0; virtual real_t soft_body_get_damping_coefficient(RID p_body) const = 0; @@ -577,8 +579,6 @@ public: virtual void soft_body_move_point(RID p_body, int p_point_index, const Vector3 &p_global_position) = 0; virtual Vector3 soft_body_get_point_global_position(RID p_body, int p_point_index) const = 0; - virtual Vector3 soft_body_get_point_offset(RID p_body, int p_point_index) const = 0; - virtual void soft_body_remove_all_pinned_points(RID p_body) = 0; virtual void soft_body_pin_point(RID p_body, int p_point_index, bool p_pin) = 0; virtual bool soft_body_is_point_pinned(RID p_body, int p_point_index) const = 0; diff --git a/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp index 74afbad9d1..bb324d8ffe 100644 --- a/tests/test_physics_3d.cpp +++ b/tests/test_physics_3d.cpp @@ -187,8 +187,10 @@ protected: RenderingServer *vs = RenderingServer::get_singleton(); PhysicsServer3D *ps = PhysicsServer3D::get_singleton(); RID trimesh_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON); - ps->shape_set_data(trimesh_shape, p_faces); - p_faces = ps->shape_get_data(trimesh_shape); // optimized one + Dictionary trimesh_params; + trimesh_params["faces"] = p_faces; + trimesh_params["backface_collision"] = false; + ps->shape_set_data(trimesh_shape, trimesh_params); Vector<Vector3> normals; // for drawing for (int i = 0; i < p_faces.size() / 3; i++) { Plane p(p_faces[i * 3 + 0], p_faces[i * 3 + 1], p_faces[i * 3 + 2]); |