summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--core/os/thread.cpp6
-rw-r--r--core/os/thread.h9
-rw-r--r--core/string/translation.cpp13
-rw-r--r--core/string/translation.h5
-rw-r--r--core/variant/native_ptr.h2
-rw-r--r--doc/classes/@GlobalScope.xml56
-rw-r--r--doc/classes/LineEdit.xml18
-rw-r--r--doc/classes/MultiplayerPeerExtension.xml4
-rw-r--r--doc/classes/PacketPeerExtension.xml4
-rw-r--r--doc/classes/StreamPeerExtension.xml4
-rw-r--r--doc/classes/Translation.xml18
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp9
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp5
-rw-r--r--modules/gdscript/gdscript_compiler.cpp36
-rw-r--r--modules/gdscript/gdscript_editor.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out8
-rw-r--r--modules/gltf/gltf_document.cpp631
-rw-r--r--modules/gltf/gltf_document.h63
-rw-r--r--modules/gltf/gltf_state.h5
-rw-r--r--modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml4
-rw-r--r--platform/javascript/.eslintrc.js8
-rw-r--r--platform/javascript/display_server_javascript.cpp7
-rw-r--r--platform/javascript/display_server_javascript.h2
-rw-r--r--platform/javascript/js/libs/library_godot_audio.js2
-rw-r--r--scene/gui/line_edit.cpp17
-rw-r--r--scene/gui/line_edit.h3
-rw-r--r--scene/main/http_request.cpp8
-rw-r--r--servers/physics_3d/soft_body_3d_sw.cpp6
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp1
-rw-r--r--tests/test_translation.h31
42 files changed, 620 insertions, 415 deletions
diff --git a/.gitignore b/.gitignore
index 00d87b6bf6..5b3414fe7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -390,3 +390,6 @@ gcov.css
# https://clangd.llvm.org/ cache folder
.clangd/
.cache/
+
+# Generated by unit tests files
+tests/data/*.translation
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index 92e43963d2..27aefc98de 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -28,9 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-// Define PLATFORM_CUSTOM_THREAD_H in platform_config.h
-// Overriding the platform implementation is required in some proprietary platforms
-#ifndef PLATFORM_CUSTOM_THREAD_H
+#ifndef PLATFORM_THREAD_OVERRIDE // See details in thread.h
#include "thread.h"
@@ -130,4 +128,4 @@ Thread::~Thread() {
}
#endif
-#endif // PLATFORM_CUSTOM_THREAD_H
+#endif // PLATFORM_THREAD_OVERRIDE
diff --git a/core/os/thread.h b/core/os/thread.h
index 3a0938c7f7..59cb58ac57 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -28,10 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-// Define PLATFORM_CUSTOM_THREAD_H in platform_config.h
+// Define PLATFORM_THREAD_OVERRIDE in your platform's `platform_config.h`
+// to use a custom Thread implementation defined in `platform/[your_platform]/platform_thread.h`
// Overriding the platform implementation is required in some proprietary platforms
-#ifdef PLATFORM_CUSTOM_THREAD_H
-#include PLATFORM_CUSTOM_THREAD_H
+#ifdef PLATFORM_THREAD_OVERRIDE
+#include "platform_thread.h"
#else
#ifndef THREAD_H
#define THREAD_H
@@ -121,4 +122,4 @@ public:
};
#endif // THREAD_H
-#endif // PLATFORM_CUSTOM_THREAD_H
+#endif // PLATFORM_THREAD_OVERRIDE
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index cb7d924556..5c0eb388f5 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -875,6 +875,11 @@ void Translation::add_plural_message(const StringName &p_src_text, const Vector<
}
StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ StringName ret;
+ if (GDVIRTUAL_CALL(_get_message, p_src_text, p_context, ret)) {
+ return ret;
+ }
+
if (p_context != StringName()) {
WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
}
@@ -888,6 +893,11 @@ StringName Translation::get_message(const StringName &p_src_text, const StringNa
}
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ StringName ret;
+ if (GDVIRTUAL_CALL(_get_plural_message, p_src_text, p_plural_text, p_n, p_context, ret)) {
+ return ret;
+ }
+
WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
return get_message(p_src_text);
}
@@ -923,6 +933,9 @@ void Translation::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages);
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
+ GDVIRTUAL_BIND(_get_plural_message, "src_message", "src_plural_message", "n", "context");
+ GDVIRTUAL_BIND(_get_message, "src_message", "context");
+
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
diff --git a/core/string/translation.h b/core/string/translation.h
index 4f179ac0fe..6aec0bb8ea 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -32,6 +32,8 @@
#define TRANSLATION_H
#include "core/io/resource.h"
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/script_language.h"
class Translation : public Resource {
GDCLASS(Translation, Resource);
@@ -48,6 +50,9 @@ class Translation : public Resource {
protected:
static void _bind_methods();
+ GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
+ GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
+
public:
void set_locale(const String &p_locale);
_FORCE_INLINE_ String get_locale() const { return locale; }
diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h
index b7e8d92f62..913d4d8f7c 100644
--- a/core/variant/native_ptr.h
+++ b/core/variant/native_ptr.h
@@ -55,7 +55,7 @@ struct GDNativePtr {
#define GDVIRTUAL_NATIVE_PTR(m_type) \
template <> \
- struct GDNativeConstPtr<m_type> { \
+ struct GDNativeConstPtr<const m_type> { \
const m_type *data = nullptr; \
GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \
static const char *get_name() { return "const " #m_type; } \
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 0334bab32a..4c306b6df1 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -23,7 +23,7 @@
Returns the absolute value of float parameter [code]x[/code] (i.e. positive value).
[codeblock]
# a is 1.2
- a = absf(-1.2)
+ var a = absf(-1.2)
[/codeblock]
</description>
</method>
@@ -34,7 +34,7 @@
Returns the absolute value of int parameter [code]x[/code] (i.e. positive value).
[codeblock]
# a is 1
- a = absi(-1)
+ var a = absi(-1)
[/codeblock]
</description>
</method>
@@ -45,7 +45,7 @@
Returns the arc cosine of [code]x[/code] in radians. Use to get the angle of cosine [code]x[/code]. [code]x[/code] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method acos] will return [constant @GDScript.NAN].
[codeblock]
# c is 0.523599 or 30 degrees if converted with rad2deg(c)
- c = acos(0.866025)
+ var c = acos(0.866025)
[/codeblock]
</description>
</method>
@@ -56,7 +56,7 @@
Returns the arc sine of [code]x[/code] in radians. Use to get the angle of sine [code]x[/code]. [code]x[/code] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method asin] will return [constant @GDScript.NAN].
[codeblock]
# s is 0.523599 or 30 degrees if converted with rad2deg(s)
- s = asin(0.5)
+ var s = asin(0.5)
[/codeblock]
</description>
</method>
@@ -64,11 +64,12 @@
<return type="float" />
<argument index="0" name="x" type="float" />
<description>
- Returns the arc tangent of [code]x[/code] in radians. Use it to get the angle from an angle's tangent in trigonometry: [code]atan(tan(angle)) == angle[/code].
+ Returns the arc tangent of [code]x[/code] in radians. Use it to get the angle from an angle's tangent in trigonometry.
The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code]x[/code].
[codeblock]
- a = atan(0.5) # a is 0.463648
+ var a = atan(0.5) # a is 0.463648
[/codeblock]
+ If [code]x[/code] is between [code]-PI / 2[/code] and [code]PI / 2[/code] (inclusive), [code]atan(tan(x))[/code] is equal to [code]x[/code].
</description>
</method>
<method name="atan2">
@@ -79,7 +80,7 @@
Returns the arc tangent of [code]y/x[/code] in radians. Use to get the angle of tangent [code]y/x[/code]. To compute the value, the method takes into account the sign of both arguments in order to determine the quadrant.
Important note: The Y coordinate comes first, by convention.
[codeblock]
- a = atan2(0, -1) # a is 3.141593
+ var a = atan2(0, -1) # a is 3.141593
[/codeblock]
</description>
</method>
@@ -105,8 +106,8 @@
<description>
Rounds [code]x[/code] upward (towards positive infinity), returning the smallest whole number that is not less than [code]x[/code].
[codeblock]
- i = ceil(1.45) # i is 2
- i = ceil(1.001) # i is 2
+ var i = ceil(1.45) # i is 2.0
+ i = ceil(1.001) # i is 2.0
[/codeblock]
See also [method floor], [method round], and [method snapped].
</description>
@@ -127,9 +128,9 @@
<description>
Clamps the float [code]value[/code] and returns a value not less than [code]min[/code] and not more than [code]max[/code].
[codeblock]
- speed = 42.1
+ var speed = 42.1
# a is 20.0
- a = clampf(speed, 1.0, 20.0)
+ var a = clampf(speed, 1.0, 20.0)
speed = -10.0
# a is -1.0
@@ -145,9 +146,9 @@
<description>
Clamps the integer [code]value[/code] and returns a value not less than [code]min[/code] and not more than [code]max[/code].
[codeblock]
- speed = 42
+ var speed = 42
# a is 20
- a = clampi(speed, 1, 20)
+ var a = clampi(speed, 1, 20)
speed = -10
# a is -1
@@ -161,9 +162,9 @@
<description>
Returns the cosine of angle [code]angle_rad[/code] in radians.
[codeblock]
- # Prints 1 then -1
- print(cos(PI * 2))
- print(cos(PI))
+ cos(PI * 2) # Returns 1.0
+ cos(PI) # Returns -1.0
+ cos(deg2rad(90)) # Returns 0.0
[/codeblock]
</description>
</method>
@@ -192,7 +193,7 @@
Converts an angle expressed in degrees to radians.
[codeblock]
# r is 3.141593
- r = deg2rad(180)
+ var r = deg2rad(180)
[/codeblock]
</description>
</method>
@@ -219,7 +220,7 @@
[b]e[/b] has an approximate value of 2.71828, and can be obtained with [code]exp(1)[/code].
For exponents to other bases use the method [method pow].
[codeblock]
- a = exp(2) # Approximately 7.39
+ var a = exp(2) # Approximately 7.39
[/codeblock]
</description>
</method>
@@ -230,7 +231,7 @@
Rounds [code]x[/code] downward (towards negative infinity), returning the largest whole number that is not more than [code]x[/code].
[codeblock]
# a is 2.0
- a = floor(2.99)
+ var a = floor(2.99)
# a is -3.0
a = floor(-2.99)
[/codeblock]
@@ -550,7 +551,7 @@
<description>
Converts one or more arguments of any type to string in the best way possible and prints them to the console.
[codeblock]
- a = [1, 2, 3]
+ var a = [1, 2, 3]
print("a", "b", a) # Prints ab[1, 2, 3]
[/codeblock]
[b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed.
@@ -729,7 +730,7 @@
<description>
Sets seed for the random number generator.
[codeblock]
- my_seed = "Godot Rocks"
+ var my_seed = "Godot Rocks"
seed(my_seed.hash())
[/codeblock]
</description>
@@ -770,7 +771,8 @@
<description>
Returns the sine of angle [code]angle_rad[/code] in radians.
[codeblock]
- sin(0.523599) # Returns 0.5
+ sin(0.523599) # Returns 0.5
+ sin(deg2rad(90)) # Returns 1.0
[/codeblock]
</description>
</method>
@@ -780,7 +782,7 @@
<description>
Returns the hyperbolic sine of [code]x[/code].
[codeblock]
- a = log(2.0) # Returns 0.693147
+ var a = log(2.0) # Returns 0.693147
sinh(a) # Returns 0.75
[/codeblock]
</description>
@@ -833,7 +835,7 @@
Returns the position of the first non-zero digit, after the decimal point. Note that the maximum return value is 10, which is a design decision in the implementation.
[codeblock]
# n is 0
- n = step_decimals(5)
+ var n = step_decimals(5)
# n is 4
n = step_decimals(1.0005)
# n is 9
@@ -853,8 +855,8 @@
<description>
Converts a formatted string that was returned by [method var2str] to the original value.
[codeblock]
- a = '{ "a": 1, "b": 2 }'
- b = str2var(a)
+ var a = '{ "a": 1, "b": 2 }'
+ var b = str2var(a)
print(b["a"]) # Prints 1
[/codeblock]
</description>
@@ -875,7 +877,7 @@
<description>
Returns the hyperbolic tangent of [code]x[/code].
[codeblock]
- a = log(2.0) # Returns 0.693147
+ var a = log(2.0) # Returns 0.693147
tanh(a) # Returns 0.6
[/codeblock]
</description>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index 834b5a41db..83e3f5b05a 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -82,6 +82,24 @@
Returns the scroll offset due to [member caret_column], as a number of characters.
</description>
</method>
+ <method name="get_selection_from_column" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the selection begin column.
+ </description>
+ </method>
+ <method name="get_selection_to_column" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the selection end column.
+ </description>
+ </method>
+ <method name="has_selection" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the user has selected text.
+ </description>
+ </method>
<method name="insert_text_at_caret">
<return type="void" />
<argument index="0" name="text" type="String" />
diff --git a/doc/classes/MultiplayerPeerExtension.xml b/doc/classes/MultiplayerPeerExtension.xml
index d9c173a4a1..46f9b22758 100644
--- a/doc/classes/MultiplayerPeerExtension.xml
+++ b/doc/classes/MultiplayerPeerExtension.xml
@@ -24,7 +24,7 @@
</method>
<method name="_get_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="r_buffer" type="const void*" />
+ <argument index="0" name="r_buffer" type="const uint8_t **" />
<argument index="1" name="r_buffer_size" type="int32_t*" />
<description>
</description>
@@ -66,7 +66,7 @@
</method>
<method name="_put_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="p_buffer" type="const void*" />
+ <argument index="0" name="p_buffer" type="const uint8_t*" />
<argument index="1" name="p_buffer_size" type="int" />
<description>
</description>
diff --git a/doc/classes/PacketPeerExtension.xml b/doc/classes/PacketPeerExtension.xml
index 6804053484..f6b925eb30 100644
--- a/doc/classes/PacketPeerExtension.xml
+++ b/doc/classes/PacketPeerExtension.xml
@@ -19,14 +19,14 @@
</method>
<method name="_get_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="r_buffer" type="const void*" />
+ <argument index="0" name="r_buffer" type="const uint8_t **" />
<argument index="1" name="r_buffer_size" type="int32_t*" />
<description>
</description>
</method>
<method name="_put_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="p_buffer" type="const void*" />
+ <argument index="0" name="p_buffer" type="const uint8_t*" />
<argument index="1" name="p_buffer_size" type="int" />
<description>
</description>
diff --git a/doc/classes/StreamPeerExtension.xml b/doc/classes/StreamPeerExtension.xml
index 93fda8cf5d..ceb9486a33 100644
--- a/doc/classes/StreamPeerExtension.xml
+++ b/doc/classes/StreamPeerExtension.xml
@@ -30,7 +30,7 @@
</method>
<method name="_put_data" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="p_data" type="const void*" />
+ <argument index="0" name="p_data" type="const uint8_t*" />
<argument index="1" name="p_bytes" type="int" />
<argument index="2" name="r_sent" type="int32_t*" />
<description>
@@ -38,7 +38,7 @@
</method>
<method name="_put_partial_data" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="p_data" type="const void*" />
+ <argument index="0" name="p_data" type="const uint8_t*" />
<argument index="1" name="p_bytes" type="int" />
<argument index="2" name="r_sent" type="int32_t*" />
<description>
diff --git a/doc/classes/Translation.xml b/doc/classes/Translation.xml
index c27d6f5667..2a0695d42e 100644
--- a/doc/classes/Translation.xml
+++ b/doc/classes/Translation.xml
@@ -11,6 +11,24 @@
<link title="Locales">https://docs.godotengine.org/en/latest/tutorials/i18n/locales.html</link>
</tutorials>
<methods>
+ <method name="_get_message" qualifiers="virtual const">
+ <return type="StringName" />
+ <argument index="0" name="src_message" type="StringName" />
+ <argument index="1" name="context" type="StringName" />
+ <description>
+ Virtual method to override [method get_message].
+ </description>
+ </method>
+ <method name="_get_plural_message" qualifiers="virtual const">
+ <return type="StringName" />
+ <argument index="0" name="src_message" type="StringName" />
+ <argument index="1" name="src_plural_message" type="StringName" />
+ <argument index="2" name="n" type="int" />
+ <argument index="3" name="context" type="StringName" />
+ <description>
+ Virtual method to override [method get_plural_message].
+ </description>
+ </method>
<method name="add_message">
<return type="void" />
<argument index="0" name="src_message" type="StringName" />
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 13f7908170..bb0c270baa 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -510,7 +510,14 @@ void Path3DEditorPlugin::_close_curve() {
if (c->get_point_count() < 2) {
return;
}
- c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0));
+ if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) {
+ return;
+ }
+ UndoRedo *ur = editor->get_undo_redo();
+ ur->create_action(TTR("Close Curve"));
+ ur->add_do_method(c.ptr(), "add_point", c->get_point_position(0), c->get_point_in(0), c->get_point_out(0), -1);
+ ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count());
+ ur->commit_action();
}
void Path3DEditorPlugin::_handle_option_pressed(int p_option) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 23e88ae059..aa62ad20ff 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -175,6 +175,11 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me
return ERR_PARSE_ERROR;
}
+ if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) {
+ push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node);
+ return ERR_PARSE_ERROR;
+ }
+
return OK;
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index a8aef84db3..947224e93e 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1071,19 +1071,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
}
- GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value);
- GDScriptCodeGenerator::Address op_result;
+ GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
- if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ GDScriptCodeGenerator::Address to_assign;
+ bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE;
+ if (has_operation) {
// Perform operation.
- op_result = codegen.add_temporary();
- gen->write_binary_operator(op_result, assignment->variant_op, target, assigned);
+ GDScriptCodeGenerator::Address op_result = codegen.add_temporary();
+ GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee);
+ gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value);
+ to_assign = op_result;
+
+ if (og_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
} else {
- op_result = assigned;
- assigned = GDScriptCodeGenerator::Address();
+ to_assign = assigned_value;
}
GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
@@ -1091,25 +1097,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (has_setter && !is_in_setter) {
// Call setter.
Vector<GDScriptCodeGenerator::Address> args;
- args.push_back(op_result);
+ args.push_back(to_assign);
gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
} else {
// Just assign.
if (assignment->use_conversion_assign) {
- gen->write_assign_with_conversion(target, op_result);
+ gen->write_assign_with_conversion(target, to_assign);
} else {
- gen->write_assign(target, op_result);
+ gen->write_assign(target, to_assign);
}
}
- if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary(); // Pop assigned value or temp operation result.
}
- if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary(); // Pop assigned value if not done before.
}
if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ gen->pop_temporary(); // Pop the target to assignment.
}
}
return GDScriptCodeGenerator::Address(); // Assignment does not return a value.
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 2f8a054b2a..044ac4b661 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -637,7 +637,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String
}
static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) {
- if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") {
+ if (p_annotation->name == "@export_range") {
if (p_argument == 3 || p_argument == 4) {
// Slider hint.
ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 86b3a3a326..f4b55cac02 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -603,7 +603,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
}
}
-const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) {
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) {
const lsp::DocumentSymbol *symbol = nullptr;
String path = get_file_path(p_doc_pos.textDocument.uri);
@@ -628,7 +628,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
} else {
ScriptLanguage::LookupResult ret;
- if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) {
+ if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) {
+ symbol_identifier = "_init";
+ }
+ if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) {
String target_script_path = path;
if (!ret.script.is_null()) {
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index e5cd4d9824..6f5600b5cf 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -87,7 +87,7 @@ public:
void publish_diagnostics(const String &p_path);
void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options);
- const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false);
+ const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false);
void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..b84ccdce81
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd
@@ -0,0 +1,5 @@
+class Vector2:
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..a7c0a29a69
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+const Vector2 = 0
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..930f91b389
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+enum Vector2 { A, B }
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd
new file mode 100644
index 0000000000..7cba29884c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd
@@ -0,0 +1,4 @@
+var Vector2
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out
new file mode 100644
index 0000000000..87863baf75
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+The member "Vector2" cannot have the same name as a builtin type.
diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd
new file mode 100644
index 0000000000..3eb02816ed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd
@@ -0,0 +1,11 @@
+#GDTEST_OK
+var prop : int = 0:
+ get:
+ return prop
+ set(value):
+ prop = value % 7
+
+func test():
+ for i in 7:
+ prop += 1
+ print(prop)
diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out
new file mode 100644
index 0000000000..76157853f2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out
@@ -0,0 +1,8 @@
+GDTEST_OK
+1
+2
+3
+4
+5
+6
+0
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index a92eb88edb..5f2e8d4ba6 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -78,7 +78,10 @@
Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &p_path) {
uint64_t begin_time = OS::get_singleton()->get_ticks_usec();
- _convert_scene_node(state, p_root, p_root, -1, -1);
+ state->skeleton3d_to_gltf_skeleton.clear();
+ state->skin_and_skeleton3d_to_gltf_skin.clear();
+
+ _convert_scene_node(state, p_root, -1, -1);
if (!state->buffers.size()) {
state->buffers.push_back(Vector<uint8_t>());
}
@@ -97,11 +100,7 @@ Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &
if (err != OK) {
return Error::FAILED;
}
- /* STEP 4 CREATE BONE ATTACHMENTS */
- err = _serialize_bone_attachment(state);
- if (err != OK) {
- return Error::FAILED;
- }
+
/* STEP 5 SERIALIZE MESHES (we have enough info now) */
err = _serialize_meshes(state);
if (err != OK) {
@@ -249,30 +248,6 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> state) {
return OK;
}
-Error GLTFDocument::_serialize_bone_attachment(Ref<GLTFState> state) {
- for (int skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) {
- for (int attachment_i = 0; attachment_i < state->skeletons[skeleton_i]->bone_attachments.size(); attachment_i++) {
- BoneAttachment3D *bone_attachment = state->skeletons[skeleton_i]->bone_attachments[attachment_i];
- String bone_name = bone_attachment->get_bone_name();
- bone_name = _sanitize_bone_name(bone_name);
- int32_t bone = state->skeletons[skeleton_i]->godot_skeleton->find_bone(bone_name);
- ERR_CONTINUE(bone == -1);
- for (int skin_i = 0; skin_i < state->skins.size(); skin_i++) {
- if (state->skins[skin_i]->skeleton != skeleton_i) {
- continue;
- }
-
- for (int node_i = 0; node_i < bone_attachment->get_child_count(); node_i++) {
- ERR_CONTINUE(bone >= state->skins[skin_i]->joints.size());
- _convert_scene_node(state, bone_attachment->get_child(node_i), bone_attachment->get_owner(), state->skins[skin_i]->joints[bone], 0);
- }
- break;
- }
- }
- }
- return OK;
-}
-
Error GLTFDocument::_parse_glb(const String &p_path, Ref<GLTFState> state) {
Error err;
FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err);
@@ -2131,11 +2106,14 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
continue;
}
Array primitives;
- Array targets;
Dictionary gltf_mesh;
Array target_names;
Array weights;
+ for (int morph_i = 0; morph_i < import_mesh->get_blend_shape_count(); morph_i++) {
+ target_names.push_back(import_mesh->get_blend_shape_name(morph_i));
+ }
for (int surface_i = 0; surface_i < import_mesh->get_surface_count(); surface_i++) {
+ Array targets;
Dictionary primitive;
Mesh::PrimitiveType primitive_type = import_mesh->get_surface_primitive_type(surface_i);
switch (primitive_type) {
@@ -2337,10 +2315,10 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
const Array &a = array[Mesh::ARRAY_WEIGHTS];
const Vector<Vector3> &vertex_array = array[Mesh::ARRAY_VERTEX];
if ((a.size() / JOINT_GROUP_SIZE) == vertex_array.size()) {
- const int ret_size = a.size() / JOINT_GROUP_SIZE;
+ int32_t vertex_count = vertex_array.size();
Vector<Color> attribs;
- attribs.resize(ret_size);
- for (int i = 0; i < ret_size; i++) {
+ attribs.resize(vertex_count);
+ for (int i = 0; i < vertex_count; i++) {
attribs.write[i] = Color(a[(i * JOINT_GROUP_SIZE) + 0], a[(i * JOINT_GROUP_SIZE) + 1], a[(i * JOINT_GROUP_SIZE) + 2], a[(i * JOINT_GROUP_SIZE) + 3]);
}
attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true);
@@ -2410,7 +2388,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
ArrayMesh::BlendShapeMode shape_mode = import_mesh->get_blend_shape_mode();
for (int morph_i = 0; morph_i < import_mesh->get_blend_shape_count(); morph_i++) {
Array array_morph = import_mesh->get_surface_blend_shape_arrays(surface_i, morph_i);
- target_names.push_back(import_mesh->get_blend_shape_name(morph_i));
Dictionary t;
Vector<Vector3> varr = array_morph[Mesh::ARRAY_VERTEX];
Array mesh_arrays = import_mesh->get_surface_arrays(surface_i);
@@ -2427,22 +2404,21 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
}
Vector<Vector3> narr = array_morph[Mesh::ARRAY_NORMAL];
- if (varr.size()) {
+ if (narr.size()) {
t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true);
}
Vector<real_t> tarr = array_morph[Mesh::ARRAY_TANGENT];
if (tarr.size()) {
const int ret_size = tarr.size() / 4;
- Vector<Color> attribs;
+ Vector<Vector3> attribs;
attribs.resize(ret_size);
for (int i = 0; i < ret_size; i++) {
- Color tangent;
- tangent.r = tarr[(i * 4) + 0];
- tangent.g = tarr[(i * 4) + 1];
- tangent.b = tarr[(i * 4) + 2];
- tangent.a = tarr[(i * 4) + 3];
+ Vector3 vec3;
+ vec3.x = tarr[(i * 4) + 0];
+ vec3.y = tarr[(i * 4) + 1];
+ vec3.z = tarr[(i * 4) + 2];
}
- t["TANGENT"] = _encode_accessor_as_color(state, attribs, true);
+ t["TANGENT"] = _encode_accessor_as_vec3(state, attribs, true);
}
targets.push_back(t);
}
@@ -2471,12 +2447,13 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
Dictionary e;
e["targetNames"] = target_names;
- for (int j = 0; j < target_names.size(); j++) {
+ weights.resize(target_names.size());
+ for (int name_i = 0; name_i < target_names.size(); name_i++) {
real_t weight = 0.0;
- if (j < state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) {
- weight = state->meshes.write[gltf_mesh_i]->get_blend_weights()[j];
+ if (name_i < state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) {
+ weight = state->meshes.write[gltf_mesh_i]->get_blend_weights()[name_i];
}
- weights.push_back(weight);
+ weights[name_i] = weight;
}
if (weights.size()) {
gltf_mesh["weights"] = weights;
@@ -4285,6 +4262,7 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> state) {
Skeleton3D *skeleton = memnew(Skeleton3D);
gltf_skeleton->godot_skeleton = skeleton;
+ state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skel_i;
// Make a unique name, no gltf node represents this skeleton
skeleton->set_name(_gen_unique_name(state, "Skeleton3D"));
@@ -4370,6 +4348,16 @@ Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFSt
Error GLTFDocument::_serialize_skins(Ref<GLTFState> state) {
_remove_duplicate_skins(state);
+ Array json_skins;
+ for (int skin_i = 0; skin_i < state->skins.size(); skin_i++) {
+ Ref<GLTFSkin> gltf_skin = state->skins[skin_i];
+ Dictionary json_skin;
+ json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(state, gltf_skin->inverse_binds, false);
+ json_skin["joints"] = gltf_skin->get_joints();
+ json_skin["name"] = gltf_skin->get_name();
+ json_skins.push_back(json_skin);
+ }
+ state->json["skins"] = json_skins;
return OK;
}
@@ -4748,30 +4736,74 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) {
channels.push_back(t);
}
if (track.weight_tracks.size()) {
+ double length = 0.0f;
+
+ for (int32_t track_idx = 0; track_idx < track.weight_tracks.size(); track_idx++) {
+ int32_t last_time_index = track.weight_tracks[track_idx].times.size() - 1;
+ length = MAX(length, track.weight_tracks[track_idx].times[last_time_index]);
+ }
+
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
-
Vector<real_t> times;
- Vector<real_t> values;
+ const double increment = 1.0 / BAKE_FPS;
+ {
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= length) {
+ last = true;
+ time = length;
+ }
+ }
+ }
- for (int32_t times_i = 0; times_i < track.weight_tracks[0].times.size(); times_i++) {
- real_t time = track.weight_tracks[0].times[times_i];
- times.push_back(time);
+ for (int32_t track_idx = 0; track_idx < track.weight_tracks.size(); track_idx++) {
+ double time = 0.0;
+ bool last = false;
+ Vector<real_t> weight_track;
+ while (true) {
+ float weight = _interpolate_track<float>(track.weight_tracks[track_idx].times,
+ track.weight_tracks[track_idx].values,
+ time,
+ track.weight_tracks[track_idx].interpolation);
+ weight_track.push_back(weight);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= length) {
+ last = true;
+ time = length;
+ }
+ }
+ track.weight_tracks.write[track_idx].times = times;
+ track.weight_tracks.write[track_idx].values = weight_track;
}
- values.resize(times.size() * track.weight_tracks.size());
- // TODO Sort by order in blend shapes
+ Vector<real_t> all_track_times = times;
+ Vector<real_t> all_track_values;
+ int32_t values_size = track.weight_tracks[0].values.size();
+ int32_t weight_tracks_size = track.weight_tracks.size();
+ all_track_values.resize(weight_tracks_size * values_size);
for (int k = 0; k < track.weight_tracks.size(); k++) {
Vector<float> wdata = track.weight_tracks[k].values;
for (int l = 0; l < wdata.size(); l++) {
- values.write[l * track.weight_tracks.size() + k] = wdata.write[l];
+ int32_t index = l * weight_tracks_size + k;
+ ERR_BREAK(index >= all_track_values.size());
+ all_track_values.write[index] = wdata.write[l];
}
}
s["interpolation"] = interpolation_to_string(track.weight_tracks[track.weight_tracks.size() - 1].interpolation);
- s["input"] = _encode_accessor_as_floats(state, times, false);
- s["output"] = _encode_accessor_as_floats(state, values, false);
+ s["input"] = _encode_accessor_as_floats(state, all_track_times, false);
+ s["output"] = _encode_accessor_as_floats(state, all_track_values, false);
samplers.push_back(s);
@@ -4905,7 +4937,7 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) {
track->weight_tracks.resize(wc);
const int expected_value_count = times.size() * output_count * wc;
- ERR_FAIL_COND_V_MSG(weights.size() != expected_value_count, ERR_PARSE_ERROR, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
+ ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
const int wlen = weights.size() / wc;
for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
@@ -4970,7 +5002,7 @@ BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state,
return bone_attachment;
}
-GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref<GLTFState> state, MeshInstance3D *p_mesh_instance) {
+GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> state, MeshInstance3D *p_mesh_instance) {
ERR_FAIL_NULL_V(p_mesh_instance, -1);
if (p_mesh_instance->get_mesh().is_null()) {
return -1;
@@ -5167,17 +5199,6 @@ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_lig
return light_index;
}
-GLTFSkeletonIndex GLTFDocument::_convert_skeleton(Ref<GLTFState> state, Skeleton3D *p_skeleton) {
- print_verbose("glTF: Converting skeleton: " + p_skeleton->get_name());
- Ref<GLTFSkeleton> gltf_skeleton;
- gltf_skeleton.instantiate();
- gltf_skeleton->set_name(_gen_unique_name(state, p_skeleton->get_name()));
- gltf_skeleton->godot_skeleton = p_skeleton;
- GLTFSkeletonIndex skeleton_i = state->skeletons.size();
- state->skeletons.push_back(gltf_skeleton);
- return skeleton_i;
-}
-
void GLTFDocument::_convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node) {
Transform3D xform = p_spatial->get_transform();
p_node->scale = xform.basis.get_scale();
@@ -5193,7 +5214,7 @@ Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> state, Node *scene_parent
return spatial;
}
-void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, Node *p_root, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) {
+void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) {
bool retflag = true;
_check_visibility(p_current, retflag);
if (retflag) {
@@ -5207,37 +5228,41 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, No
_convert_spatial(state, spatial, gltf_node);
}
if (cast_to<MeshInstance3D>(p_current)) {
- Node3D *spatial = cast_to<Node3D>(p_current);
- _convert_mesh_to_gltf(p_current, state, spatial, gltf_node);
+ MeshInstance3D *mi = cast_to<MeshInstance3D>(p_current);
+ _convert_mesh_instance_to_gltf(mi, state, gltf_node);
} else if (cast_to<BoneAttachment3D>(p_current)) {
- _convert_bone_attachment_to_gltf(p_current, state, gltf_node, retflag);
- // TODO 2020-12-21 iFire Handle the case of objects under the bone attachment.
+ BoneAttachment3D *bone = cast_to<BoneAttachment3D>(p_current);
+ _convert_bone_attachment_to_gltf(bone, state, p_gltf_parent, p_gltf_root, gltf_node);
return;
} else if (cast_to<Skeleton3D>(p_current)) {
- _convert_skeleton_to_gltf(p_current, state, p_gltf_parent, p_gltf_root, gltf_node, p_root);
+ Skeleton3D *skel = cast_to<Skeleton3D>(p_current);
+ _convert_skeleton_to_gltf(skel, state, p_gltf_parent, p_gltf_root, gltf_node);
// We ignore the Godot Engine node that is the skeleton.
return;
} else if (cast_to<MultiMeshInstance3D>(p_current)) {
- _convert_mult_mesh_instance_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root);
+ MultiMeshInstance3D *multi = cast_to<MultiMeshInstance3D>(p_current);
+ _convert_mult_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, state);
#ifdef MODULE_CSG_ENABLED
} else if (cast_to<CSGShape3D>(p_current)) {
- if (p_current->get_parent() && cast_to<CSGShape3D>(p_current)->is_root_shape()) {
- _convert_csg_shape_to_gltf(p_current, p_gltf_parent, gltf_node, state);
+ CSGShape3D *shape = cast_to<CSGShape3D>(p_current);
+ if (shape->get_parent() && shape->is_root_shape()) {
+ _convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, state);
}
#endif // MODULE_CSG_ENABLED
#ifdef MODULE_GRIDMAP_ENABLED
} else if (cast_to<GridMap>(p_current)) {
- _convert_grid_map_to_gltf(p_current, p_gltf_parent, p_gltf_root, gltf_node, state, p_root);
+ GridMap *gridmap = Object::cast_to<GridMap>(p_current);
+ _convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, state);
#endif // MODULE_GRIDMAP_ENABLED
} else if (cast_to<Camera3D>(p_current)) {
Camera3D *camera = Object::cast_to<Camera3D>(p_current);
- _convert_camera_to_gltf(camera, state, camera, gltf_node);
+ _convert_camera_to_gltf(camera, state, gltf_node);
} else if (cast_to<Light3D>(p_current)) {
Light3D *light = Object::cast_to<Light3D>(p_current);
- _convert_light_to_gltf(light, state, light, gltf_node);
+ _convert_light_to_gltf(light, state, gltf_node);
} else if (cast_to<AnimationPlayer>(p_current)) {
AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
- _convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current, p_root);
+ _convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current);
}
GLTFNodeIndex current_node_i = state->nodes.size();
GLTFNodeIndex gltf_root = p_gltf_root;
@@ -5249,13 +5274,13 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, No
}
_create_gltf_node(state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
- _convert_scene_node(state, p_current->get_child(node_i), p_root, current_node_i, gltf_root);
+ _convert_scene_node(state, p_current->get_child(node_i), current_node_i, gltf_root);
}
}
#ifdef MODULE_CSG_ENABLED
-void GLTFDocument::_convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) {
- CSGShape3D *csg = Object::cast_to<CSGShape3D>(p_current);
+void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) {
+ CSGShape3D *csg = p_current;
csg->call("_update_shape");
Array meshes = csg->get_meshes();
if (meshes.size() != 2) {
@@ -5286,16 +5311,15 @@ void GLTFDocument::_create_gltf_node(Ref<GLTFState> state, Node *p_scene_parent,
GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> gltf_node) {
state->scene_nodes.insert(current_node_i, p_scene_parent);
state->nodes.push_back(gltf_node);
- if (current_node_i == p_parent_node_index) {
- return;
- }
+ ERR_FAIL_COND(current_node_i == p_parent_node_index);
+ state->nodes.write[current_node_i]->parent = p_parent_node_index;
if (p_parent_node_index == -1) {
return;
}
state->nodes.write[p_parent_node_index]->children.push_back(current_node_i);
}
-void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *animation_player, Ref<GLTFState> state, const GLTFNodeIndex &p_gltf_current, const GLTFNodeIndex &p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent, Node *p_root) {
+void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *animation_player, Ref<GLTFState> state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
ERR_FAIL_COND(!animation_player);
state->animation_players.push_back(animation_player);
print_verbose(String("glTF: Converting animation player: ") + animation_player->get_name());
@@ -5314,7 +5338,7 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &retflag) {
retflag = false;
}
-void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) {
+void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) {
ERR_FAIL_COND(!camera);
GLTFCameraIndex camera_index = _convert_camera(state, camera);
if (camera_index != -1) {
@@ -5322,7 +5346,7 @@ void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> stat
}
}
-void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) {
+void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) {
ERR_FAIL_COND(!light);
GLTFLightIndex light_index = _convert_light(state, light);
if (light_index != -1) {
@@ -5331,43 +5355,39 @@ void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> state,
}
#ifdef MODULE_GRIDMAP_ENABLED
-void GLTFDocument::_convert_grid_map_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) {
- GridMap *grid_map = Object::cast_to<GridMap>(p_scene_parent);
- ERR_FAIL_COND(!grid_map);
- Array cells = grid_map->get_used_cells();
+void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) {
+ Array cells = p_grid_map->get_used_cells();
for (int32_t k = 0; k < cells.size(); k++) {
GLTFNode *new_gltf_node = memnew(GLTFNode);
gltf_node->children.push_back(state->nodes.size());
state->nodes.push_back(new_gltf_node);
Vector3 cell_location = cells[k];
- int32_t cell = grid_map->get_cell_item(
+ int32_t cell = p_grid_map->get_cell_item(
Vector3(cell_location.x, cell_location.y, cell_location.z));
EditorSceneImporterMeshNode3D *import_mesh_node = memnew(EditorSceneImporterMeshNode3D);
- import_mesh_node->set_mesh(grid_map->get_mesh_library()->get_item_mesh(cell));
+ import_mesh_node->set_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell));
Transform3D cell_xform;
cell_xform.basis.set_orthogonal_index(
- grid_map->get_cell_item_orientation(
+ p_grid_map->get_cell_item_orientation(
Vector3(cell_location.x, cell_location.y, cell_location.z)));
- cell_xform.basis.scale(Vector3(grid_map->get_cell_scale(),
- grid_map->get_cell_scale(),
- grid_map->get_cell_scale()));
- cell_xform.set_origin(grid_map->map_to_world(
+ cell_xform.basis.scale(Vector3(p_grid_map->get_cell_scale(),
+ p_grid_map->get_cell_scale(),
+ p_grid_map->get_cell_scale()));
+ cell_xform.set_origin(p_grid_map->map_to_world(
Vector3(cell_location.x, cell_location.y, cell_location.z)));
Ref<GLTFMesh> gltf_mesh;
gltf_mesh.instantiate();
gltf_mesh = import_mesh_node;
new_gltf_node->mesh = state->meshes.size();
state->meshes.push_back(gltf_mesh);
- new_gltf_node->xform = cell_xform * grid_map->get_transform();
- new_gltf_node->set_name(_gen_unique_name(state, grid_map->get_mesh_library()->get_item_name(cell)));
+ new_gltf_node->xform = cell_xform * p_grid_map->get_transform();
+ new_gltf_node->set_name(_gen_unique_name(state, p_grid_map->get_mesh_library()->get_item_name(cell)));
}
}
#endif // MODULE_GRIDMAP_ENABLED
-void GLTFDocument::_convert_mult_mesh_instance_to_gltf(Node *p_scene_parent, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state, Node *p_root_node) {
- MultiMeshInstance3D *multi_mesh_instance = Object::cast_to<MultiMeshInstance3D>(p_scene_parent);
- ERR_FAIL_COND(!multi_mesh_instance);
- Ref<MultiMesh> multi_mesh = multi_mesh_instance->get_multimesh();
+void GLTFDocument::_convert_mult_mesh_instance_to_gltf(MultiMeshInstance3D *p_multi_mesh_instance, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) {
+ Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh();
if (multi_mesh.is_valid()) {
for (int32_t instance_i = 0; instance_i < multi_mesh->get_instance_count();
instance_i++) {
@@ -5383,9 +5403,9 @@ void GLTFDocument::_convert_mult_mesh_instance_to_gltf(Node *p_scene_parent, con
transform.basis.set_quaternion_scale(quaternion,
Vector3(scale.x, 0, scale.y));
transform =
- multi_mesh_instance->get_transform() * transform;
+ p_multi_mesh_instance->get_transform() * transform;
} else if (multi_mesh->get_transform_format() == MultiMesh::TRANSFORM_3D) {
- transform = multi_mesh_instance->get_transform() *
+ transform = p_multi_mesh_instance->get_transform() *
multi_mesh->get_instance_transform(instance_i);
}
Ref<ArrayMesh> mm = multi_mesh->get_mesh();
@@ -5405,56 +5425,102 @@ void GLTFDocument::_convert_mult_mesh_instance_to_gltf(Node *p_scene_parent, con
state->meshes.push_back(gltf_mesh);
}
new_gltf_node->xform = transform;
- new_gltf_node->set_name(_gen_unique_name(state, multi_mesh_instance->get_name()));
+ new_gltf_node->set_name(_gen_unique_name(state, p_multi_mesh_instance->get_name()));
gltf_node->children.push_back(state->nodes.size());
state->nodes.push_back(new_gltf_node);
}
}
}
-void GLTFDocument::_convert_skeleton_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, const GLTFNodeIndex &p_parent_node_index, const GLTFNodeIndex &p_root_node_index, Ref<GLTFNode> gltf_node, Node *p_root_node) {
- Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_scene_parent);
- if (skeleton) {
- // Remove placeholder skeleton3d node by not creating the gltf node
- // Skins are per mesh
- for (int node_i = 0; node_i < skeleton->get_child_count(); node_i++) {
- _convert_scene_node(state, skeleton->get_child(node_i), p_root_node, p_parent_node_index, p_root_node_index);
+void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFState> state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node) {
+ Skeleton3D *skeleton = p_skeleton3d;
+ Ref<GLTFSkeleton> gltf_skeleton;
+ gltf_skeleton.instantiate();
+ // GLTFSkeleton is only used to hold internal state data. It will not be written to the document.
+ //
+ gltf_skeleton->godot_skeleton = skeleton;
+ GLTFSkeletonIndex skeleton_i = state->skeletons.size();
+ state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skeleton_i;
+ state->skeletons.push_back(gltf_skeleton);
+
+ BoneId bone_count = skeleton->get_bone_count();
+ for (BoneId bone_i = 0; bone_i < bone_count; bone_i++) {
+ Ref<GLTFNode> joint_node;
+ joint_node.instantiate();
+ // Note that we cannot use _gen_unique_bone_name here, because glTF spec requires all node
+ // names to be unique regardless of whether or not they are used as joints.
+ joint_node->set_name(_gen_unique_name(state, skeleton->get_bone_name(bone_i)));
+ Transform3D xform = skeleton->get_bone_rest(bone_i) * skeleton->get_bone_pose(bone_i);
+ joint_node->scale = xform.basis.get_scale();
+ joint_node->rotation = xform.basis.get_rotation_quaternion();
+ joint_node->position = xform.origin;
+ joint_node->joint = true;
+ GLTFNodeIndex current_node_i = state->nodes.size();
+ state->scene_nodes.insert(current_node_i, skeleton);
+ state->nodes.push_back(joint_node);
+
+ gltf_skeleton->joints.push_back(current_node_i);
+ if (skeleton->get_bone_parent(bone_i) == -1) {
+ gltf_skeleton->roots.push_back(current_node_i);
+ }
+ gltf_skeleton->godot_bone_node.insert(bone_i, current_node_i);
+ }
+ for (BoneId bone_i = 0; bone_i < bone_count; bone_i++) {
+ GLTFNodeIndex current_node_i = gltf_skeleton->godot_bone_node[bone_i];
+ BoneId parent_bone_id = skeleton->get_bone_parent(bone_i);
+ if (parent_bone_id == -1) {
+ if (p_parent_node_index != -1) {
+ state->nodes.write[current_node_i]->parent = p_parent_node_index;
+ state->nodes.write[p_parent_node_index]->children.push_back(current_node_i);
+ }
+ } else {
+ GLTFNodeIndex parent_node_i = gltf_skeleton->godot_bone_node[parent_bone_id];
+ state->nodes.write[current_node_i]->parent = parent_node_i;
+ state->nodes.write[parent_node_i]->children.push_back(current_node_i);
}
}
+ // Remove placeholder skeleton3d node by not creating the gltf node
+ // Skins are per mesh
+ for (int node_i = 0; node_i < skeleton->get_child_count(); node_i++) {
+ _convert_scene_node(state, skeleton->get_child(node_i), p_parent_node_index, p_root_node_index);
+ }
}
-void GLTFDocument::_convert_bone_attachment_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, Ref<GLTFNode> gltf_node, bool &retflag) {
- retflag = true;
- BoneAttachment3D *bone_attachment = Object::cast_to<BoneAttachment3D>(p_scene_parent);
- if (bone_attachment) {
- Node *node = bone_attachment->get_parent();
- while (node) {
- Skeleton3D *bone_attachment_skeleton = Object::cast_to<Skeleton3D>(node);
- if (bone_attachment_skeleton) {
- for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) {
- if (state->skeletons[skeleton_i]->godot_skeleton != bone_attachment_skeleton) {
- continue;
- }
- state->skeletons.write[skeleton_i]->bone_attachments.push_back(bone_attachment);
- break;
- }
- break;
- }
- node = node->get_parent();
+void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_attachment, Ref<GLTFState> state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node) {
+ Skeleton3D *skeleton;
+ // Note that relative transforms to external skeletons and pose overrides are not supported.
+ if (p_bone_attachment->get_use_external_skeleton()) {
+ skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
+ } else {
+ skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent());
+ }
+ GLTFSkeletonIndex skel_gltf_i = -1;
+ if (skeleton != nullptr && state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) {
+ skel_gltf_i = state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()];
+ }
+ int bone_idx = -1;
+ if (skeleton != nullptr) {
+ bone_idx = p_bone_attachment->get_bone_idx();
+ if (bone_idx == -1) {
+ bone_idx = skeleton->find_bone(p_bone_attachment->get_bone_name());
}
- gltf_node.unref();
- return;
}
- retflag = false;
+ GLTFNodeIndex par_node_index = p_parent_node_index;
+ if (skeleton != nullptr && bone_idx != -1 && skel_gltf_i != -1) {
+ Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_gltf_i];
+ gltf_skeleton->bone_attachments.push_back(p_bone_attachment);
+ par_node_index = gltf_skeleton->joints[bone_idx];
+ }
+
+ for (int node_i = 0; node_i < p_bone_attachment->get_child_count(); node_i++) {
+ _convert_scene_node(state, p_bone_attachment->get_child(node_i), par_node_index, p_root_node_index);
+ }
}
-void GLTFDocument::_convert_mesh_to_gltf(Node *p_scene_parent, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node) {
- MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_scene_parent);
- if (mi) {
- GLTFMeshIndex gltf_mesh_index = _convert_mesh_instance(state, mi);
- if (gltf_mesh_index != -1) {
- gltf_node->mesh = gltf_mesh_index;
- }
+void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) {
+ GLTFMeshIndex gltf_mesh_index = _convert_mesh_to_gltf(state, p_scene_parent);
+ if (gltf_mesh_index != -1) {
+ gltf_node->mesh = gltf_mesh_index;
}
}
@@ -5908,10 +5974,6 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
if (node->mesh < 0) {
continue;
}
- Array json_skins;
- if (state->json.has("skins")) {
- json_skins = state->json["skins"];
- }
Map<GLTFNodeIndex, Node *>::Element *mi_element = state->scene_nodes.find(mi_node_i);
if (!mi_element) {
continue;
@@ -5923,7 +5985,6 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
node->rotation = mi_xform.basis.get_rotation_quaternion();
node->position = mi_xform.origin;
- Dictionary json_skin;
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(mi->get_node(mi->get_skeleton_path()));
if (!skeleton) {
continue;
@@ -5932,121 +5993,75 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
continue;
}
Ref<Skin> skin = mi->get_skin();
- if (skin.is_null()) {
- skin = skeleton->register_skin(nullptr)->get_skin();
- }
Ref<GLTFSkin> gltf_skin;
gltf_skin.instantiate();
Array json_joints;
- GLTFSkeletonIndex skeleton_gltf_i = -1;
NodePath skeleton_path = mi->get_skeleton_path();
- bool is_unique = true;
- for (int32_t skin_i = 0; skin_i < state->skins.size(); skin_i++) {
- Ref<GLTFSkin> prev_gltf_skin = state->skins.write[skin_i];
- if (gltf_skin.is_null()) {
- continue;
- }
- GLTFSkeletonIndex prev_skeleton = prev_gltf_skin->get_skeleton();
- if (prev_skeleton == -1 || prev_skeleton >= state->skeletons.size()) {
- continue;
- }
- if (prev_gltf_skin->get_godot_skin() == skin && state->skeletons[prev_skeleton]->godot_skeleton == skeleton) {
- node->skin = skin_i;
- node->skeleton = prev_skeleton;
- is_unique = false;
- break;
- }
- }
- if (!is_unique) {
- continue;
- }
- GLTFSkeletonIndex skeleton_i = _convert_skeleton(state, skeleton);
- skeleton_gltf_i = skeleton_i;
- ERR_CONTINUE(skeleton_gltf_i == -1);
- gltf_skin->skeleton = skeleton_gltf_i;
- Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skeleton_gltf_i];
- for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) {
- String godot_bone_name = skin->get_bind_name(bind_i);
- if (godot_bone_name.is_empty()) {
- int32_t bone = skin->get_bind_bone(bind_i);
- godot_bone_name = skeleton->get_bone_name(bone);
- }
- if (skeleton->find_bone(godot_bone_name) == -1) {
- godot_bone_name = skeleton->get_bone_name(0);
- }
- BoneId bone_index = skeleton->find_bone(godot_bone_name);
- ERR_CONTINUE(bone_index == -1);
- Ref<GLTFNode> joint_node;
- joint_node.instantiate();
- String gltf_bone_name = _gen_unique_bone_name(state, skeleton_gltf_i, godot_bone_name);
- joint_node->set_name(gltf_bone_name);
-
- Transform3D bone_rest_xform = skeleton->get_bone_rest(bone_index);
- joint_node->scale = bone_rest_xform.basis.get_scale();
- joint_node->rotation = bone_rest_xform.basis.get_rotation_quaternion();
- joint_node->position = bone_rest_xform.origin;
- joint_node->joint = true;
-
- int32_t joint_node_i = state->nodes.size();
- state->nodes.push_back(joint_node);
- gltf_skeleton->godot_bone_node.insert(bone_index, joint_node_i);
- int32_t joint_index = gltf_skin->joints.size();
- gltf_skin->joint_i_to_bone_i.insert(joint_index, bone_index);
- gltf_skin->joints.push_back(joint_node_i);
- gltf_skin->joints_original.push_back(joint_node_i);
- gltf_skin->inverse_binds.push_back(skin->get_bind_pose(bind_i));
- json_joints.push_back(joint_node_i);
- for (Map<GLTFNodeIndex, Node *>::Element *skin_scene_node_i = state->scene_nodes.front(); skin_scene_node_i; skin_scene_node_i = skin_scene_node_i->next()) {
- if (skin_scene_node_i->get() == skeleton) {
- gltf_skin->skin_root = skin_scene_node_i->key();
- json_skin["skeleton"] = skin_scene_node_i->key();
- }
- }
- gltf_skin->godot_skin = skin;
- gltf_skin->set_name(_gen_unique_name(state, skin->get_name()));
- }
- for (int32_t bind_i = 0; bind_i < skin->get_bind_count(); bind_i++) {
- String bone_name = skeleton->get_bone_name(bind_i);
- String godot_bone_name = skin->get_bind_name(bind_i);
- int32_t bone = -1;
- if (skin->get_bind_bone(bind_i) != -1) {
- bone = skin->get_bind_bone(bind_i);
- godot_bone_name = skeleton->get_bone_name(bone);
- }
- bone = skeleton->find_bone(godot_bone_name);
- if (bone == -1) {
- continue;
- }
- BoneId bone_parent = skeleton->get_bone_parent(bone);
- GLTFNodeIndex joint_node_i = gltf_skeleton->godot_bone_node[bone];
- ERR_CONTINUE(joint_node_i >= state->nodes.size());
- if (bone_parent != -1) {
- GLTFNodeIndex parent_joint_gltf_node = gltf_skin->joints[bone_parent];
- Ref<GLTFNode> parent_joint_node = state->nodes.write[parent_joint_gltf_node];
- parent_joint_node->children.push_back(joint_node_i);
+ Node *skel_node = mi->get_node_or_null(skeleton_path);
+ Skeleton3D *godot_skeleton = nullptr;
+ if (skel_node != nullptr) {
+ godot_skeleton = cast_to<Skeleton3D>(skel_node);
+ }
+ if (godot_skeleton != nullptr && state->skeleton3d_to_gltf_skeleton.has(godot_skeleton->get_instance_id())) {
+ // This is a skinned mesh. If the mesh has no ARRAY_WEIGHTS or ARRAY_BONES, it will be invisible.
+ const GLTFSkeletonIndex skeleton_gltf_i = state->skeleton3d_to_gltf_skeleton[godot_skeleton->get_instance_id()];
+ Ref<GLTFSkeleton> gltf_skeleton = state->skeletons[skeleton_gltf_i];
+ int bone_cnt = skeleton->get_bone_count();
+ ERR_FAIL_COND(bone_cnt != gltf_skeleton->joints.size());
+
+ ObjectID gltf_skin_key = skin->get_instance_id();
+ ObjectID gltf_skel_key = godot_skeleton->get_instance_id();
+ GLTFSkinIndex skin_gltf_i = -1;
+ GLTFNodeIndex root_gltf_i = -1;
+ if (!gltf_skeleton->roots.is_empty()) {
+ root_gltf_i = gltf_skeleton->roots[0];
+ }
+ if (state->skin_and_skeleton3d_to_gltf_skin.has(gltf_skin_key) && state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key].has(gltf_skel_key)) {
+ skin_gltf_i = state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key];
} else {
- Node *node_parent = skeleton->get_parent();
- ERR_CONTINUE(!node_parent);
- for (Map<GLTFNodeIndex, Node *>::Element *E = state->scene_nodes.front(); E; E = E->next()) {
- if (E->get() == node_parent) {
- GLTFNodeIndex gltf_node_i = E->key();
- Ref<GLTFNode> gltf_node = state->nodes.write[gltf_node_i];
- gltf_node->children.push_back(joint_node_i);
- break;
+ if (skin.is_null()) {
+ // Note that gltf_skin_key should remain null, so these can share a reference.
+ skin = skeleton->register_skin(nullptr)->get_skin();
+ }
+ gltf_skin.instantiate();
+ gltf_skin->godot_skin = skin;
+ gltf_skin->set_name(skin->get_name());
+ gltf_skin->skeleton = skeleton_gltf_i;
+ gltf_skin->skin_root = root_gltf_i;
+ //gltf_state->godot_to_gltf_node[skel_node]
+ HashMap<StringName, int> bone_name_to_idx;
+ for (int bone_i = 0; bone_i < bone_cnt; bone_i++) {
+ bone_name_to_idx[skeleton->get_bone_name(bone_i)] = bone_i;
+ }
+ for (int bind_i = 0, cnt = skin->get_bind_count(); bind_i < cnt; bind_i++) {
+ int bone_i = skin->get_bind_bone(bind_i);
+ Transform3D bind_pose = skin->get_bind_pose(bind_i);
+ StringName bind_name = skin->get_bind_name(bind_i);
+ if (bind_name != StringName()) {
+ bone_i = bone_name_to_idx[bind_name];
}
+ ERR_CONTINUE(bone_i < 0 || bone_i >= bone_cnt);
+ if (bind_name == StringName()) {
+ bind_name = skeleton->get_bone_name(bone_i);
+ }
+ GLTFNodeIndex skeleton_bone_i = gltf_skeleton->joints[bone_i];
+ gltf_skin->joints_original.push_back(skeleton_bone_i);
+ gltf_skin->joints.push_back(skeleton_bone_i);
+ gltf_skin->inverse_binds.push_back(bind_pose);
+ if (skeleton->get_bone_parent(bone_i) == -1) {
+ gltf_skin->roots.push_back(skeleton_bone_i);
+ }
+ gltf_skin->joint_i_to_bone_i[bind_i] = bone_i;
+ gltf_skin->joint_i_to_name[bind_i] = bind_name;
}
+ skin_gltf_i = state->skins.size();
+ state->skins.push_back(gltf_skin);
+ state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key] = skin_gltf_i;
}
+ node->skin = skin_gltf_i;
+ node->skeleton = skeleton_gltf_i;
}
- _expand_skin(state, gltf_skin);
- node->skin = state->skins.size();
- state->skins.push_back(gltf_skin);
-
- json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(state, gltf_skin->inverse_binds, false);
- json_skin["joints"] = json_joints;
- json_skin["name"] = gltf_skin->get_name();
- json_skins.push_back(json_skin);
- state->json["skins"] = json_skins;
}
}
@@ -6129,7 +6144,6 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
for (int32_t key_i = 0; key_i < key_count; key_i++) {
times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i);
}
- const float BAKE_FPS = 30.0f;
if (track_type == Animation::TYPE_TRANSFORM3D) {
p_track.position_track.times = times;
p_track.position_track.interpolation = gltf_interpolation;
@@ -6367,69 +6381,58 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/");
const NodePath path = node_suffix[0];
const String suffix = node_suffix[1];
- const Node *node = ap->get_parent()->get_node_or_null(path);
- for (Map<GLTFNodeIndex, Node *>::Element *transform_track_i = state->scene_nodes.front(); transform_track_i; transform_track_i = transform_track_i->next()) {
- if (transform_track_i->get() == node) {
- const MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(node);
- if (!mi) {
- continue;
- }
- Ref<ArrayMesh> array_mesh = mi->get_mesh();
- if (array_mesh.is_null()) {
+ Node *node = ap->get_parent()->get_node_or_null(path);
+ MeshInstance3D *mi = cast_to<MeshInstance3D>(node);
+ Ref<Mesh> mesh = mi->get_mesh();
+ ERR_CONTINUE(mesh.is_null());
+ int32_t mesh_index = -1;
+ for (Map<GLTFNodeIndex, Node *>::Element *mesh_track_i = state->scene_nodes.front(); mesh_track_i; mesh_track_i = mesh_track_i->next()) {
+ if (mesh_track_i->get() == node) {
+ mesh_index = mesh_track_i->key();
+ }
+ }
+ ERR_CONTINUE(mesh_index == -1);
+ Map<int, GLTFAnimation::Track> &tracks = gltf_animation->get_tracks();
+ GLTFAnimation::Track track = gltf_animation->get_tracks().has(mesh_index) ? gltf_animation->get_tracks()[mesh_index] : GLTFAnimation::Track();
+ if (!tracks.has(mesh_index)) {
+ for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
+ String shape_name = mesh->get_blend_shape_name(shape_i);
+ NodePath shape_path = String(path) + ":blend_shapes/" + shape_name;
+ int32_t shape_track_i = animation->find_track(shape_path);
+ if (shape_track_i == -1) {
+ GLTFAnimation::Channel<float> weight;
+ weight.interpolation = GLTFAnimation::INTERP_LINEAR;
+ weight.times.push_back(0.0f);
+ weight.times.push_back(0.0f);
+ weight.values.push_back(0.0f);
+ weight.values.push_back(0.0f);
+ track.weight_tracks.push_back(weight);
continue;
}
- if (node_suffix.size() != 2) {
- continue;
+ Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i);
+ GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
+ gltf_interpolation = GLTFAnimation::INTERP_STEP;
+ } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
+ gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
}
- GLTFNodeIndex mesh_index = -1;
- for (GLTFNodeIndex node_i = 0; node_i < state->scene_nodes.size(); node_i++) {
- if (state->scene_nodes[node_i] == node) {
- mesh_index = node_i;
- break;
- }
+ int32_t key_count = animation->track_get_key_count(shape_track_i);
+ GLTFAnimation::Channel<float> weight;
+ weight.interpolation = gltf_interpolation;
+ weight.times.resize(key_count);
+ for (int32_t time_i = 0; time_i < key_count; time_i++) {
+ weight.times.write[time_i] = animation->track_get_key_time(shape_track_i, time_i);
}
- ERR_CONTINUE(mesh_index == -1);
- Ref<Mesh> mesh = mi->get_mesh();
- ERR_CONTINUE(mesh.is_null());
- for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
- if (mesh->get_blend_shape_name(shape_i) != suffix) {
- continue;
- }
- GLTFAnimation::Track track;
- Map<int, GLTFAnimation::Track>::Element *blend_shape_track_i = gltf_animation->get_tracks().find(mesh_index);
- if (blend_shape_track_i) {
- track = blend_shape_track_i->get();
- }
- Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i);
-
- GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
- gltf_interpolation = GLTFAnimation::INTERP_STEP;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
- gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
- }
- Animation::TrackType track_type = animation->track_get_type(track_i);
- if (track_type == Animation::TYPE_VALUE) {
- int32_t key_count = animation->track_get_key_count(track_i);
- GLTFAnimation::Channel<float> weight;
- weight.interpolation = gltf_interpolation;
- weight.times.resize(key_count);
- for (int32_t time_i = 0; time_i < key_count; time_i++) {
- weight.times.write[time_i] = animation->track_get_key_time(track_i, time_i);
- }
- weight.values.resize(key_count);
- for (int32_t value_i = 0; value_i < key_count; value_i++) {
- weight.values.write[value_i] = animation->track_get_key_value(track_i, value_i);
- }
- track.weight_tracks.push_back(weight);
- }
- gltf_animation->get_tracks()[mesh_index] = track;
+ weight.values.resize(key_count);
+ for (int32_t value_i = 0; value_i < key_count; value_i++) {
+ weight.values.write[value_i] = animation->track_get_key_value(shape_track_i, value_i);
}
+ track.weight_tracks.push_back(weight);
}
+ tracks[mesh_index] = track;
}
-
} else if (String(orig_track_path).find(":") != -1) {
//Process skeleton
const Vector<String> node_suffix = String(orig_track_path).split(":");
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index fb798a055a..18aeb81bc0 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -51,6 +51,9 @@ class GLTFSkin;
class GLTFNode;
class GLTFSpecGloss;
class GLTFSkeleton;
+class CSGShape3D;
+class GridMap;
+class MultiMeshInstance3D;
using GLTFAccessorIndex = int;
using GLTFAnimationIndex = int;
@@ -72,6 +75,9 @@ class GLTFDocument : public Resource {
friend class GLTFSkin;
friend class GLTFSkeleton;
+private:
+ const float BAKE_FPS = 30.0f;
+
public:
const int32_t JOINT_GROUP_SIZE = 4;
enum GLTFType {
@@ -350,7 +356,6 @@ private:
GLTFNodeIndex p_node_i);
Error _encode_buffer_bins(Ref<GLTFState> state, const String &p_path);
Error _encode_buffer_glb(Ref<GLTFState> state, const String &p_path);
- Error _serialize_bone_attachment(Ref<GLTFState> state);
Dictionary _serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material);
Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material);
Error _serialize_version(Ref<GLTFState> state);
@@ -381,20 +386,17 @@ public:
void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index);
void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
const GLTFAnimationIndex index, const int bake_fps);
- GLTFMeshIndex _convert_mesh_instance(Ref<GLTFState> state,
- MeshInstance3D *p_mesh_instance);
void _convert_mesh_instances(Ref<GLTFState> state);
GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera3D *p_camera);
- void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Node3D *spatial, Ref<GLTFNode> gltf_node);
+ void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node);
GLTFLightIndex _convert_light(Ref<GLTFState> state, Light3D *p_light);
- GLTFSkeletonIndex _convert_skeleton(Ref<GLTFState> state, Skeleton3D *p_skeleton);
void _convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node);
- void _convert_scene_node(Ref<GLTFState> state, Node *p_current, Node *p_root,
+ void _convert_scene_node(Ref<GLTFState> state, Node *p_current,
const GLTFNodeIndex p_gltf_current,
const GLTFNodeIndex p_gltf_root);
#ifdef MODULE_CSG_ENABLED
- void _convert_csg_shape_to_gltf(Node *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state);
+ void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state);
#endif // MODULE_CSG_ENABLED
void _create_gltf_node(Ref<GLTFState> state,
@@ -405,40 +407,39 @@ public:
Ref<GLTFNode> gltf_node);
void _convert_animation_player_to_gltf(
AnimationPlayer *animation_player, Ref<GLTFState> state,
- const GLTFNodeIndex &p_gltf_current,
- const GLTFNodeIndex &p_gltf_root_index,
- Ref<GLTFNode> p_gltf_node, Node *p_scene_parent,
- Node *p_root);
+ GLTFNodeIndex p_gltf_current,
+ GLTFNodeIndex p_gltf_root_index,
+ Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
void _check_visibility(Node *p_node, bool &retflag);
void _convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state,
- Node3D *spatial,
Ref<GLTFNode> gltf_node);
#ifdef MODULE_GRIDMAP_ENABLED
void _convert_grid_map_to_gltf(
- Node *p_scene_parent,
- const GLTFNodeIndex &p_parent_node_index,
- const GLTFNodeIndex &p_root_node_index,
- Ref<GLTFNode> gltf_node, Ref<GLTFState> state,
- Node *p_root_node);
+ GridMap *p_grid_map,
+ GLTFNodeIndex p_parent_node_index,
+ GLTFNodeIndex p_root_node_index,
+ Ref<GLTFNode> gltf_node, Ref<GLTFState> state);
#endif // MODULE_GRIDMAP_ENABLED
void _convert_mult_mesh_instance_to_gltf(
- Node *p_scene_parent,
- const GLTFNodeIndex &p_parent_node_index,
- const GLTFNodeIndex &p_root_node_index,
- Ref<GLTFNode> gltf_node, Ref<GLTFState> state,
- Node *p_root_node);
+ MultiMeshInstance3D *p_scene_parent,
+ GLTFNodeIndex p_parent_node_index,
+ GLTFNodeIndex p_root_node_index,
+ Ref<GLTFNode> gltf_node, Ref<GLTFState> state);
void _convert_skeleton_to_gltf(
- Node *p_scene_parent, Ref<GLTFState> state,
- const GLTFNodeIndex &p_parent_node_index,
- const GLTFNodeIndex &p_root_node_index,
- Ref<GLTFNode> gltf_node, Node *p_root_node);
- void _convert_bone_attachment_to_gltf(Node *p_scene_parent,
+ Skeleton3D *p_scene_parent, Ref<GLTFState> state,
+ GLTFNodeIndex p_parent_node_index,
+ GLTFNodeIndex p_root_node_index,
+ Ref<GLTFNode> gltf_node);
+ void _convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_attachment,
+ Ref<GLTFState> state,
+ GLTFNodeIndex p_parent_node_index,
+ GLTFNodeIndex p_root_node_index,
+ Ref<GLTFNode> gltf_node);
+ void _convert_mesh_instance_to_gltf(MeshInstance3D *p_mesh_instance,
Ref<GLTFState> state,
- Ref<GLTFNode> gltf_node,
- bool &retflag);
- void _convert_mesh_to_gltf(Node *p_scene_parent,
- Ref<GLTFState> state, Node3D *spatial,
Ref<GLTFNode> gltf_node);
+ GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> state,
+ MeshInstance3D *p_mesh_instance);
void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
String p_animation_track_name);
Error serialize(Ref<GLTFState> state, Node *p_root, const String &p_path);
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 896ea5fc56..d6614da804 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -44,6 +44,8 @@
#include "gltf_texture.h"
#include "core/io/resource.h"
+#include "core/templates/map.h"
+#include "core/templates/pair.h"
#include "core/templates/vector.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/texture.h"
@@ -87,6 +89,9 @@ class GLTFState : public Resource {
Vector<Ref<GLTFAnimation>> animations;
Map<GLTFNodeIndex, Node *> scene_nodes;
+ Map<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton;
+ Map<ObjectID, Map<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_gltf_skin;
+
protected:
static void _bind_methods();
diff --git a/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml b/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml
index 26a4391b83..746fabd6e5 100644
--- a/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml
+++ b/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml
@@ -49,7 +49,7 @@
</method>
<method name="_get_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="r_buffer" type="const void*" />
+ <argument index="0" name="r_buffer" type="const uint8_t **" />
<argument index="1" name="r_buffer_size" type="int32_t*" />
<description>
</description>
@@ -86,7 +86,7 @@
</method>
<method name="_put_packet" qualifiers="virtual">
<return type="int" />
- <argument index="0" name="p_buffer" type="const void*" />
+ <argument index="0" name="p_buffer" type="const uint8_t*" />
<argument index="1" name="p_buffer_size" type="int" />
<description>
</description>
diff --git a/platform/javascript/.eslintrc.js b/platform/javascript/.eslintrc.js
index 0ff9d67d26..2c81f1f02d 100644
--- a/platform/javascript/.eslintrc.js
+++ b/platform/javascript/.eslintrc.js
@@ -39,5 +39,13 @@ module.exports = {
// Closure compiler (exported properties)
"quote-props": ["error", "consistent"],
"dot-notation": "off",
+ // No comma dangle for functions (it's madness, and ES2017)
+ "comma-dangle": ["error", {
+ "arrays": "always-multiline",
+ "objects": "always-multiline",
+ "imports": "always-multiline",
+ "exports": "always-multiline",
+ "functions": "never"
+ }],
}
};
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index be4d2cba20..124b4ee1c8 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -595,6 +595,12 @@ void DisplayServerJavaScript::virtual_keyboard_hide() {
godot_js_display_vk_hide();
}
+// Window blur callback
+EM_BOOL DisplayServerJavaScript::blur_callback(int p_event_type, const EmscriptenFocusEvent *p_event, void *p_user_data) {
+ Input::get_singleton()->release_pressed_events();
+ return false;
+}
+
// Gamepad
void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) {
Input *input = Input::get_singleton();
@@ -797,6 +803,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback)
SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback)
SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback)
+ SET_EM_WINDOW_CALLBACK(blur, blur_callback)
SET_EM_CALLBACK(canvas_id, wheel, wheel_callback)
SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback)
SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback)
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index bf5e229c9a..1863ddefeb 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -85,6 +85,8 @@ private:
static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+ static EM_BOOL blur_callback(int p_event_type, const EmscriptenFocusEvent *p_event, void *p_user_data);
+
static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid);
void process_joypads();
diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js
index f6010fd12a..6cbb0567f4 100644
--- a/platform/javascript/js/libs/library_godot_audio.js
+++ b/platform/javascript/js/libs/library_godot_audio.js
@@ -229,7 +229,7 @@ const GodotAudioWorklet = {
'godot-processor',
{
'outputChannelCount': [channels],
- },
+ }
);
return Promise.resolve();
});
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 09128770d5..89fd9bfc88 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1560,6 +1560,20 @@ void LineEdit::deselect() {
update();
}
+bool LineEdit::has_selection() const {
+ return selection.enabled;
+}
+
+int LineEdit::get_selection_from_column() const {
+ ERR_FAIL_COND_V(!selection.enabled, -1);
+ return selection.begin;
+}
+
+int LineEdit::get_selection_to_column() const {
+ ERR_FAIL_COND_V(!selection.enabled, -1);
+ return selection.end;
+}
+
void LineEdit::selection_delete() {
if (selection.enabled) {
delete_text(selection.begin, selection.end);
@@ -2094,6 +2108,9 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all);
ClassDB::bind_method(D_METHOD("deselect"), &LineEdit::deselect);
+ ClassDB::bind_method(D_METHOD("has_selection"), &LineEdit::has_selection);
+ ClassDB::bind_method(D_METHOD("get_selection_from_column"), &LineEdit::get_selection_from_column);
+ ClassDB::bind_method(D_METHOD("get_selection_to_column"), &LineEdit::get_selection_to_column);
ClassDB::bind_method(D_METHOD("set_text", "text"), &LineEdit::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &LineEdit::get_text);
ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &LineEdit::get_draw_control_chars);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 9f28c84f50..923024dd56 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -231,6 +231,9 @@ public:
void select_all();
void selection_delete();
void deselect();
+ bool has_selection() const;
+ int get_selection_from_column() const;
+ int get_selection_to_column() const;
void delete_char();
void delete_text(int p_from_column, int p_to_column);
diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp
index f24d880045..14fd14dd18 100644
--- a/scene/main/http_request.cpp
+++ b/scene/main/http_request.cpp
@@ -104,9 +104,11 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_h
CharString charstr = p_request_data.utf8();
size_t len = charstr.length();
- raw_data.resize(len);
- uint8_t *w = raw_data.ptrw();
- memcpy(w, charstr.ptr(), len);
+ if (len > 0) {
+ raw_data.resize(len);
+ uint8_t *w = raw_data.ptrw();
+ memcpy(w, charstr.ptr(), len);
+ }
return request_raw(p_url, p_custom_headers, p_ssl_validate_domain, p_method, raw_data);
}
diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp
index 5f6e202c73..752d5f3a91 100644
--- a/servers/physics_3d/soft_body_3d_sw.cpp
+++ b/servers/physics_3d/soft_body_3d_sw.cpp
@@ -249,8 +249,10 @@ void SoftBody3DSW::update_area() {
// Node area.
LocalVector<int> counts;
- counts.resize(nodes.size());
- memset(counts.ptr(), 0, counts.size() * sizeof(int));
+ if (nodes.size() > 0) {
+ 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;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index f507a83072..c2227587ec 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -1394,6 +1394,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = true;
}
}
+ c = c->next;
}
}
diff --git a/tests/test_translation.h b/tests/test_translation.h
index 52ff49bf9b..93c53bbbc9 100644
--- a/tests/test_translation.h
+++ b/tests/test_translation.h
@@ -35,6 +35,10 @@
#include "core/string/translation.h"
#include "core/string/translation_po.h"
+#ifdef TOOLS_ENABLED
+#include "editor/import/resource_importer_csv_translation.h"
+#endif
+
#include "thirdparty/doctest/doctest.h"
namespace TestTranslation {
@@ -145,6 +149,33 @@ TEST_CASE("[OptimizedTranslation] Generate from Translation and read messages")
CHECK(messages.size() == 0);
}
+#ifdef TOOLS_ENABLED
+TEST_CASE("[Translation] CSV import") {
+ Ref<ResourceImporterCSVTranslation> import_csv_translation = memnew(ResourceImporterCSVTranslation);
+
+ Map<StringName, Variant> options;
+ options["compress"] = false;
+ options["delimiter"] = 0;
+
+ List<String> gen_files;
+
+ Error result = import_csv_translation->import(TestUtils::get_data_path("translations.csv"),
+ "", options, nullptr, &gen_files);
+ CHECK(result == OK);
+ CHECK(gen_files.size() == 2);
+
+ for (const String &file : gen_files) {
+ Ref<Translation> translation = ResourceLoader::load(file);
+ CHECK(translation.is_valid());
+ TranslationServer::get_singleton()->add_translation(translation);
+ }
+
+ TranslationServer::get_singleton()->set_locale("de");
+
+ CHECK(Object().tr("GOOD_MORNING", "") == "Guten Morgen");
+}
+#endif // TOOLS_ENABLED
+
} // namespace TestTranslation
#endif // TEST_TRANSLATION_H